From bab5311d7f6f3c195ef74e6366294ad3159cc5c0 Mon Sep 17 00:00:00 2001 From: Vitaliy Tomin Date: Tue, 12 Aug 2025 17:58:32 +0800 Subject: [PATCH] igpsport: Copy FitAsyncProcessor from Garmin --- .../igpsport/IGPSportDeviceSupport.java | 1 + .../igpsport/IGPSportDownloadManager.java | 45 ++++++++--- .../igpsport/fit/FitAsyncProcessor.java | 81 +++++++++++++++++++ 3 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/fit/FitAsyncProcessor.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java index deddc55e4c..9f4c2af990 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java @@ -720,6 +720,7 @@ public class IGPSportDeviceSupport extends AbstractBTLESingleDeviceSupport { public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) { super.setContext(gbDevice, btAdapter, context); this.mediaManager = new MediaManager(context); + this.downloadManager.setContext(context); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDownloadManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDownloadManager.java index 1f588458c7..dc5b5e3c67 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDownloadManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDownloadManager.java @@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport; import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils.garminTimestampToJavaMillis; +import android.content.Context; + import com.google.protobuf.InvalidProtocolBufferException; import org.slf4j.Logger; @@ -43,10 +45,11 @@ import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Common; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.CyclingData; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.FileDownload; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException; +import nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport.fit.FitAsyncProcessor; import nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport.fit.FitImporter; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.notifications.GBProgressNotification; public class IGPSportDownloadManager { @@ -59,15 +62,23 @@ public class IGPSportDownloadManager { private Boolean downloadInProgress = false; private Boolean firstChunk = true; private FitImporter fitImporter; + private GBProgressNotification transferNotification; + private List filesToProcess = new ArrayList<>(); int pbSize=0; + public IGPSportDownloadManager(IGPSportDeviceSupport support) { this.support = support; + recievingDataBuffer = new ByteArrayOutputStream(); } + public void setContext(Context context) { + this.transferNotification = new GBProgressNotification(context, GB.NOTIFICATION_CHANNEL_ID_TRANSFER); + } + public void setFilesAvaliable(byte[] pbData) throws InvalidProtocolBufferException { List message = CyclingData.cycling_data_msg.parseFrom(pbData).getCyclingDataFileFlagMsgList(); @@ -76,6 +87,7 @@ public class IGPSportDownloadManager { } LOG.info("Found " + message.size() + " files"); + filesToProcess.clear(); syncNextFile(); } @@ -83,11 +95,26 @@ public class IGPSportDownloadManager { public void syncNextFile() { if (avaliableActivityFiles.isEmpty()) { LOG.info("No files to sync"); - if (support.getDevice().isBusy() ) { - support.getDevice().unsetBusyTask(); - GB.signalActivityDataFinish(support.getDevice()); - support.getDevice().sendDeviceUpdateIntent(getContext()); - } + + transferNotification.start(R.string.busy_task_processing_files, 0, filesToProcess.size()); + + final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), support.getDevice()); + fitAsyncProcessor.process(filesToProcess, new FitAsyncProcessor.Callback() { + @Override + public void onProgress(final int i) { + transferNotification.setTotalProgress(i); + } + + @Override + public void onFinish() { + support.getDevice().unsetBusyTask(); + GB.signalActivityDataFinish(support.getDevice()); + transferNotification.finish(); + support.getDevice().sendDeviceUpdateIntent(getContext()); + } + }); + + return; } else { if (!support.getDevice().isBusy() ) { @@ -117,7 +144,7 @@ public class IGPSportDownloadManager { recievingDataBuffer.reset(); downloadInProgress = true; firstChunk = true; - fitImporter = new FitImporter(support.getContext(), support.getDevice()); + } public void addData(byte[] data) { @@ -151,11 +178,9 @@ public class IGPSportDownloadManager { FileDownload.file_download pbInfo = FileDownload.file_download.parseFrom(pbData); FileUtils.copyStreamToFile(new ByteArrayInputStream(recievingDataBuffer.toByteArray(), 20+4+pbSize, pbInfo.getFileSize()), outputFile); outputFile.setLastModified(garminTimestampToJavaMillis(downloadingFile.getGarminTimeStamp())); - fitImporter.importFile(outputFile); + filesToProcess.add(outputFile); } catch (IOException e) { throw new RuntimeException(e); - } catch (FitParseException e) { - throw new RuntimeException(e); } syncNextFile(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/fit/FitAsyncProcessor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/fit/FitAsyncProcessor.java new file mode 100644 index 0000000000..95a3be48d1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/fit/FitAsyncProcessor.java @@ -0,0 +1,81 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport.fit; + +import android.content.Context; +import android.os.Handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.PendingFileProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport.fit.FitImporter; + +public class FitAsyncProcessor { + private static final Logger LOG = LoggerFactory.getLogger(FitAsyncProcessor.class); + private static final AtomicLong THREAD_COUNTER = new AtomicLong(0L); + + private final Context context; + private final GBDevice gbDevice; + private final Handler handler; + + public FitAsyncProcessor(final Context context, final GBDevice gbDevice) { + this.context = context; + this.gbDevice = gbDevice; + this.handler = new Handler(context.getMainLooper()); + } + + /** + * Process a list of files asynchronously. Callback is executed on the UI thread. + */ + public void process(final List files, final Callback callback) { + LOG.debug("Starting processor for {} files", files.size()); + + new Thread(() -> { + try { + int i = 0; + for (final File file : files) { + i++; + LOG.debug("Parsing {}", file); + + final int finalI = i; + FitAsyncProcessor.this.handler.post(() -> callback.onProgress(finalI)); + + try { + FitImporter fitImporter = new FitImporter(context, gbDevice); + fitImporter.importFile(file); + } catch (final Exception ex) { + LOG.error("Exception while importing {}", file, ex); + continue; // do not remove from pending files + } + + try (DBHandler handler = GBApplication.acquireDB()) { + final DaoSession session = handler.getDaoSession(); + + final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session); + + pendingFileProvider.removePendingFile(file.getPath()); + } catch (final Exception e) { + LOG.error("Exception while removing pending file {}", file, e); + } + } + } catch (final Exception e) { + LOG.error("Failed to parse from storage", e); + } + + FitAsyncProcessor.this.handler.post(callback::onFinish); + }, "FitAsyncProcessor_" + THREAD_COUNTER.getAndIncrement()).start(); + } + + public interface Callback { + void onProgress(final int perc); + + void onFinish(); + } +}