mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 18:15:49 +01:00
Garmin: Process downloaded fit files asynchronously
Fixes occasional ANR while syncing activity data.
This commit is contained in:
parent
a25d8eae30
commit
f7bfd56d46
@ -134,18 +134,20 @@ public class FileTransferHandler implements MessageHandler {
|
|||||||
|
|
||||||
private void saveFileToExternalStorage() {
|
private void saveFileToExternalStorage() {
|
||||||
File dir;
|
File dir;
|
||||||
|
File outputFile;
|
||||||
try {
|
try {
|
||||||
dir = deviceSupport.getWritableExportDirectory();
|
dir = deviceSupport.getWritableExportDirectory();
|
||||||
File outputFile = new File(dir, currentlyDownloading.getFileName());
|
outputFile = new File(dir, currentlyDownloading.getFileName());
|
||||||
FileUtils.copyStreamToFile(new ByteArrayInputStream(currentlyDownloading.dataHolder.array()), outputFile);
|
FileUtils.copyStreamToFile(new ByteArrayInputStream(currentlyDownloading.dataHolder.array()), outputFile);
|
||||||
outputFile.setLastModified(currentlyDownloading.directoryEntry.fileDate.getTime());
|
outputFile.setLastModified(currentlyDownloading.directoryEntry.fileDate.getTime());
|
||||||
|
} catch (final IOException e) {
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Failed to save file", e);
|
LOG.error("Failed to save file", e);
|
||||||
|
return; // do not signal file as saved
|
||||||
}
|
}
|
||||||
|
|
||||||
FileDownloadedDeviceEvent fileDownloadedDeviceEvent = new FileDownloadedDeviceEvent();
|
FileDownloadedDeviceEvent fileDownloadedDeviceEvent = new FileDownloadedDeviceEvent();
|
||||||
fileDownloadedDeviceEvent.directoryEntry = currentlyDownloading.directoryEntry;
|
fileDownloadedDeviceEvent.directoryEntry = currentlyDownloading.directoryEntry;
|
||||||
|
fileDownloadedDeviceEvent.localPath = outputFile.getAbsolutePath();
|
||||||
deviceSupport.evaluateGBDeviceEvent(fileDownloadedDeviceEvent);
|
deviceSupport.evaluateGBDeviceEvent(fileDownloadedDeviceEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,13 +14,13 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
@ -65,7 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitImporter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitAsyncProcessor;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage;
|
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.RecordData;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||||
@ -98,7 +98,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
private ICommunicator communicator;
|
private ICommunicator communicator;
|
||||||
private MusicStateSpec musicStateSpec;
|
private MusicStateSpec musicStateSpec;
|
||||||
private Timer musicStateTimer;
|
private Timer musicStateTimer;
|
||||||
|
|
||||||
private final List<FileType> supportedFileTypeList = new ArrayList<>();
|
private final List<FileType> supportedFileTypeList = new ArrayList<>();
|
||||||
|
private final List<File> filesToProcess = new ArrayList<>();
|
||||||
|
|
||||||
public GarminSupport() {
|
public GarminSupport() {
|
||||||
super(LOG);
|
super(LOG);
|
||||||
@ -277,14 +279,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
|
LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
|
||||||
|
|
||||||
if (entry.getFiletype().isFitFile()) {
|
if (entry.getFiletype().isFitFile()) {
|
||||||
try {
|
filesToProcess.add(new File(((FileDownloadedDeviceEvent) deviceEvent).localPath));
|
||||||
final File dir = getWritableExportDirectory();
|
|
||||||
final File file = new File(dir, filename);
|
|
||||||
final FitImporter fitImporter = new FitImporter(getContext(), getDevice());
|
|
||||||
fitImporter.importFile(file);
|
|
||||||
} catch (final IOException e) {
|
|
||||||
LOG.error("Failed to import fit file", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getKeepActivityDataOnDevice()) { // delete file from watch upon successful download
|
if (!getKeepActivityDataOnDevice()) { // delete file from watch upon successful download
|
||||||
@ -473,47 +468,77 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBusyFetching;
|
||||||
|
|
||||||
private void processDownloadQueue() {
|
private void processDownloadQueue() {
|
||||||
|
|
||||||
moveFilesFromLegacyCache(); //TODO: remove before merging
|
moveFilesFromLegacyCache(); //TODO: remove before merging
|
||||||
|
|
||||||
if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
|
if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
|
||||||
if (!gbDevice.isBusy()) {
|
if (!gbDevice.isBusy()) {
|
||||||
|
isBusyFetching = true;
|
||||||
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
|
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
|
||||||
getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
|
getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
while (!filesToDownload.isEmpty()) {
|
||||||
FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
|
final FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
|
||||||
while (checkFileExists(directoryEntry.getFileName()) || checkFileExists(directoryEntry.getLegacyFileName())) {
|
if (checkFileExists(directoryEntry.getFileName()) || checkFileExists(directoryEntry.getLegacyFileName())) {
|
||||||
LOG.debug("File: {} already downloaded, not downloading again.", directoryEntry.getFileName());
|
LOG.debug("File: {} already downloaded, not downloading again.", directoryEntry.getFileName());
|
||||||
if (!getKeepActivityDataOnDevice()) { // delete file from watch if already downloaded
|
if (!getKeepActivityDataOnDevice()) { // delete file from watch if already downloaded
|
||||||
sendOutgoingMessage(new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
|
sendOutgoingMessage(new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
|
||||||
}
|
}
|
||||||
directoryEntry = filesToDownload.remove();
|
continue;
|
||||||
}
|
}
|
||||||
DownloadRequestMessage downloadRequestMessage = fileTransferHandler.downloadDirectoryEntry(directoryEntry);
|
|
||||||
|
final DownloadRequestMessage downloadRequestMessage = fileTransferHandler.downloadDirectoryEntry(directoryEntry);
|
||||||
if (downloadRequestMessage != null) {
|
if (downloadRequestMessage != null) {
|
||||||
sendOutgoingMessage(downloadRequestMessage);
|
sendOutgoingMessage(downloadRequestMessage);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("File: {} already downloaded, not downloading again, from inside.", directoryEntry.getFileName());
|
LOG.debug("File: {} already downloaded, not downloading again, from inside.", directoryEntry.getFileName());
|
||||||
}
|
}
|
||||||
} catch (NoSuchElementException e) {
|
}
|
||||||
// we ran out of files to download
|
}
|
||||||
// FIXME this is ugly
|
|
||||||
if (gbDevice.isBusy() && gbDevice.getBusyTask().equals(getContext().getString(R.string.busy_task_fetch_activity_data))) {
|
if (filesToDownload.isEmpty() && !fileTransferHandler.isDownloading() && isBusyFetching) {
|
||||||
|
if (filesToProcess.isEmpty()) {
|
||||||
|
// No downloaded fit files to process
|
||||||
|
if (gbDevice.isBusy() && isBusyFetching) {
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
getDevice().unsetBusyTask();
|
getDevice().unsetBusyTask();
|
||||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
}
|
}
|
||||||
|
isBusyFetching = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
|
|
||||||
if (gbDevice.isBusy() && gbDevice.getBusyTask().equals(getContext().getString(R.string.busy_task_fetch_activity_data))) {
|
// Keep the device marked as busy while we process the files asynchronously
|
||||||
getDevice().unsetBusyTask();
|
|
||||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
final List <File> filesToProcessClone = new ArrayList<>(filesToProcess);
|
||||||
}
|
filesToProcess.clear();
|
||||||
|
fitAsyncProcessor.process(filesToProcessClone, new FitAsyncProcessor.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onProgress(final int i) {
|
||||||
|
GB.updateTransferNotification(
|
||||||
|
"Parsing fit files", "File " + i + " of " + filesToProcessClone.size(),
|
||||||
|
true,
|
||||||
|
(i * 100) / filesToProcessClone.size(), getContext()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
|
getDevice().unsetBusyTask();
|
||||||
|
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||||
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
isBusyFetching = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -766,25 +791,22 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
|
GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||||
int i = 0;
|
fitAsyncProcessor.process(Arrays.asList(fitFiles), new FitAsyncProcessor.Callback() {
|
||||||
for (final File file : fitFiles) {
|
@Override
|
||||||
i++;
|
public void onProgress(final int i) {
|
||||||
LOG.debug("Parsing {}", file);
|
GB.updateTransferNotification(
|
||||||
|
"Parsing fit files", "File " + i + " of " + fitFiles.length,
|
||||||
GB.updateTransferNotification("Parsing fit files", "File " + i + " of " + fitFiles.length, true, (i * 100) / fitFiles.length, getContext());
|
true,
|
||||||
|
(i * 100) / fitFiles.length, getContext()
|
||||||
try {
|
);
|
||||||
final FitImporter fitImporter = new FitImporter(getContext(), getDevice());
|
|
||||||
fitImporter.importFile(file);
|
|
||||||
} catch (final Exception ex) {
|
|
||||||
LOG.error("Exception while importing {}", file, ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
|
||||||
LOG.error("Failed to parse from storage", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
GB.updateTransferNotification("", "", false, 100, getContext());
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
GB.updateTransferNotification("", "", false, 100, getContext());
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,5 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileTransferH
|
|||||||
|
|
||||||
public class FileDownloadedDeviceEvent extends GBDeviceEvent {
|
public class FileDownloadedDeviceEvent extends GBDeviceEvent {
|
||||||
public FileTransferHandler.DirectoryEntry directoryEntry;
|
public FileTransferHandler.DirectoryEntry directoryEntry;
|
||||||
|
public String localPath;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.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 nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
|
||||||
|
public class FitAsyncProcessor {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FitAsyncProcessor.class);
|
||||||
|
|
||||||
|
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<File> 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 {
|
||||||
|
final FitImporter fitImporter = new FitImporter(context, gbDevice);
|
||||||
|
fitImporter.importFile(file);
|
||||||
|
} catch (final Exception ex) {
|
||||||
|
LOG.error("Exception while importing {}", file, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to parse from storage", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
FitAsyncProcessor.this.handler.post(callback::onFinish);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onProgress(final int perc);
|
||||||
|
|
||||||
|
void onFinish();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user