Garmin: prepare for more complex FIT upload scenarios

This commit is contained in:
Thomas Kuehne
2025-09-19 22:28:31 +02:00
parent 7f5a6e7f93
commit ddf008327c
3 changed files with 50 additions and 22 deletions
@@ -43,7 +43,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitCourse;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitWorkout;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
@@ -87,24 +86,7 @@ public class GarminFitFileInstallHandler implements InstallHandler {
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
rawBytes = FileUtils.readAll(in, 10 * 1024 * 1024); // 10MB
fitFile = FitFile.parseIncoming(rawBytes);
final Optional<FitFileId> fitFileIdOpt = fitFile.getRecords().stream()
.filter(r -> r instanceof FitFileId)
.map(r -> (FitFileId) r)
.findFirst();
if (!fitFileIdOpt.isPresent()) {
LOG.error("Fit file has no ID");
return;
}
final FitFileId fitFileId = fitFileIdOpt.get();
if (fitFileId.getType() == null) {
LOG.error("Fit file ID has null type");
return;
}
fileType = fitFileId.getType();
fileType = fitFile.getFileType();
} catch (final FitParseException e) {
LOG.error("Fit file is corrupted", e);
fitParseException = e;
@@ -90,7 +90,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GpxRouteF
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage;
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.exception.FitParseException;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarmLabel;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitAlarmSettings;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitDeviceSettings;
@@ -947,7 +946,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
"set alarms",
fileTransferHandler.initiateUpload(
fitFile.getOutgoingMessage(),
FileType.FILETYPE.SETTINGS
fitFile.getFileType()
).getOutgoingMessage()
);
}
@@ -1193,7 +1192,9 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
garminGpxRouteInstallHandler.getGpxFile(),
trackName
);
communicator.sendMessage("upload course file", fileTransferHandler.initiateUpload(gpxRouteFileConverter.getConvertedFile().getOutgoingMessage(), FileType.FILETYPE.DOWNLOAD_COURSE).getOutgoingMessage());
final FitFile convertedFile = gpxRouteFileConverter.getConvertedFile();
final FileType.FILETYPE fileType = convertedFile.getFileType();
communicator.sendMessage("upload " + fileType + " file", fileTransferHandler.initiateUpload(convertedFile.getOutgoingMessage(), fileType).getOutgoingMessage());
}
final GarminPrgFileInstallHandler prgFileInstallHandler = new GarminPrgFileInstallHandler(uri, getContext());
@@ -1,6 +1,23 @@
/* Copyright (C) 2024-2025 Daniele Gobbetti, José Rebelo, Thomas Kuehne
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,10 +32,13 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecordDataFactory;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
@@ -152,6 +172,31 @@ public class FitFile {
writer.writeShort(ChecksumCalculator.computeCrc(writer.getBytes(), this.header.getHeaderSize(), writer.getBytes().length - this.header.getHeaderSize()));
}
@Nullable
public FileType.FILETYPE getFileType() {
if (dataRecords == null || dataRecords.isEmpty()) {
LOG.error("FIT file has no dataRecords");
return null;
}
final Optional<FitFileId> fitFileIdOpt = dataRecords.stream()
.filter(r -> r instanceof FitFileId)
.map(r -> (FitFileId) r)
.findFirst();
if (!fitFileIdOpt.isPresent()) {
LOG.error("FIT file has no FILE_ID message");
return null;
}
final FitFileId fitFileId = fitFileIdOpt.get();
FileType.FILETYPE type = fitFileId.getType();
if (type == null) {
LOG.error("FIT file FILE_ID message has 'type' value null");
}
return type;
}
public byte[] getOutgoingMessage() {
// Compute the worst case scenario buffer size for the fit file
// A ~1.6MB gpx file with ~16k points results in a ~320KB buffer, ~150KB of which get actually used