From 42e44de1ac300ad3fcfce8dd11900ea93e9a0467 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Thu, 18 Apr 2024 17:26:40 +0200 Subject: [PATCH] Garmin: Support file archival (deletion) on watch Also add original timestamp to local cache filename as the file identifier are reused Also fix imports of Test class --- .../devices/garmin/FileTransferHandler.java | 5 +- .../devices/garmin/messages/GFDIMessage.java | 1 + .../garmin/messages/SetFileFlagsMessage.java | 41 +++++++++++++ .../messages/status/GFDIStatusMessage.java | 2 + .../status/SetFileFlagsStatusMessage.java | 58 +++++++++++++++++++ .../devices/garmin/GarminSupportTest.java | 7 --- 6 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java index 1c1a22fe3..6bdbe0479 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java @@ -10,6 +10,7 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.text.SimpleDateFormat; import java.util.Date; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.DownloadRequestMessage; @@ -319,7 +320,9 @@ public class FileTransferHandler implements MessageHandler { } public String getFileName() { - return getFiletype().name() + "_" + getFileIndex() + (getFiletype().isFitFile() ? ".fit" : ""); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); + String dateString = dateFormat.format(fileDate); + return getFiletype().name() + "_" + getFileIndex() + "_" + dateString + (getFiletype().isFitFile() ? ".fit" : ""); } @NonNull diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java index eb512a7c3..69baa214a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java @@ -103,6 +103,7 @@ public abstract class GFDIMessage { UPLOAD_REQUEST(5003, UploadRequestMessage.class), FILE_TRANSFER_DATA(5004, FileTransferDataMessage.class), CREATE_FILE(5005, CreateFileMessage.class), + SET_FILE_FLAG(5008, SetFileFlagsMessage.class), FIT_DEFINITION(5011, FitDefinitionMessage.class), FIT_DATA(5012, FitDataMessage.class), WEATHER_REQUEST(5014, WeatherMessage.class), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java new file mode 100644 index 000000000..0f2da90a6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java @@ -0,0 +1,41 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages; + +import org.apache.commons.lang3.EnumUtils; + +import java.util.EnumSet; + +public class SetFileFlagsMessage extends GFDIMessage { + + private final int fileIndex; + private final FileFlags flags; + + public SetFileFlagsMessage(int fileIndex, FileFlags flags) { + this.garminMessage = GarminMessage.SET_FILE_FLAG; + this.fileIndex = fileIndex; + this.flags = flags; + } + + @Override + protected boolean generateOutgoing() { + final MessageWriter writer = new MessageWriter(response); + writer.writeShort(0); // packet size will be filled below + writer.writeShort(this.garminMessage.getId()); + writer.writeShort(this.fileIndex); + writer.writeByte((int) EnumUtils.generateBitVector(FileFlags.class, this.flags)); + return true; + } + + public enum FileFlags { + UNK_00000001, + UNK_00000010, + UNK_00000100, + UNK_00001000, + ARCHIVE, + ; + + public static EnumSet fromBitMask(final int code) { + return EnumUtils.processBitVector(FileFlags.class, code); + } + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java index a1ed876d6..4276cd2ab 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java @@ -26,6 +26,8 @@ public abstract class GFDIStatusMessage extends GFDIMessage { SupportedFileTypesStatusMessage supportedFileTypesStatusMessage = SupportedFileTypesStatusMessage.parseIncoming(reader, garminMessage); LOG.info("{}", supportedFileTypesStatusMessage); return supportedFileTypesStatusMessage; + } else if (GarminMessage.SET_FILE_FLAG.equals(originalGarminMessage)) { + return SetFileFlagsStatusMessage.parseIncoming(reader, garminMessage); } else if (GarminMessage.FIT_DEFINITION.equals(originalGarminMessage)) { return FitDefinitionStatusMessage.parseIncoming(reader, originalGarminMessage); } else if (GarminMessage.FIT_DATA.equals(originalGarminMessage)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java new file mode 100644 index 000000000..b5416c4b2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java @@ -0,0 +1,58 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status; + +import java.util.EnumSet; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetFileFlagsMessage; + +public class SetFileFlagsStatusMessage extends GFDIStatusMessage { + private final Status status; + private final FlagsStatus flagsStatus; + private final int fileIdentifier; + private final EnumSet fileFlags; + + public SetFileFlagsStatusMessage(GarminMessage garminMessage, Status status, FlagsStatus flagsStatus, int fileIdentifier, EnumSet fileFlags) { + this.garminMessage = garminMessage; + this.status = status; + this.flagsStatus = flagsStatus; + this.fileIdentifier = fileIdentifier; + this.fileFlags = fileFlags; + } + + public static SetFileFlagsStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) { + final Status status = Status.fromCode(reader.readByte()); + + if (!status.equals(Status.ACK)) { + return null; + } + + final FlagsStatus flagsStatus = FlagsStatus.fromCode(reader.readByte()); + final int originalFileIdentifier = reader.readShort() + 1; //TODO: check if always or only on archival + final EnumSet fileFlags = SetFileFlagsMessage.FileFlags.fromBitMask(reader.readByte()); + + if (!FlagsStatus.APPLIED.equals(flagsStatus)) { + LOG.warn("Received {} / {} for file identifier {} and flags {} - message {}", status, flagsStatus, originalFileIdentifier, fileFlags, garminMessage); + } else { + LOG.info("Received {} / {} for file identifier {} and flags {} - message {}", status, flagsStatus, originalFileIdentifier, fileFlags, garminMessage); + } + + return new SetFileFlagsStatusMessage(garminMessage, status, flagsStatus, originalFileIdentifier, fileFlags); + + } + + + enum FlagsStatus { + APPLIED, + ERROR, //guessed + ; + + public static FlagsStatus fromCode(final int code) { + for (final FlagsStatus status : FlagsStatus.values()) { + if (status.ordinal() == code) { + return status; + } + } + throw new IllegalArgumentException("Unknown FlagsStatus code " + code); + } + } + +} diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java index aab8eced3..9fe5344ab 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java @@ -2,27 +2,20 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin; import org.junit.Assert; import org.junit.Test; -import org.threeten.bp.Instant; -import org.threeten.bp.ZoneId; import java.math.BigInteger; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.TimeZone; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.LocalMessage; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ChecksumCalculator; import nodomain.freeyourgadget.gadgetbridge.util.GB;