mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 01:55:50 +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() {
|
||||
File dir;
|
||||
File outputFile;
|
||||
try {
|
||||
dir = deviceSupport.getWritableExportDirectory();
|
||||
File outputFile = new File(dir, currentlyDownloading.getFileName());
|
||||
outputFile = new File(dir, currentlyDownloading.getFileName());
|
||||
FileUtils.copyStreamToFile(new ByteArrayInputStream(currentlyDownloading.dataHolder.array()), outputFile);
|
||||
outputFile.setLastModified(currentlyDownloading.directoryEntry.fileDate.getTime());
|
||||
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to save file", e);
|
||||
return; // do not signal file as saved
|
||||
}
|
||||
|
||||
FileDownloadedDeviceEvent fileDownloadedDeviceEvent = new FileDownloadedDeviceEvent();
|
||||
fileDownloadedDeviceEvent.directoryEntry = currentlyDownloading.directoryEntry;
|
||||
fileDownloadedDeviceEvent.localPath = outputFile.getAbsolutePath();
|
||||
deviceSupport.evaluateGBDeviceEvent(fileDownloadedDeviceEvent);
|
||||
}
|
||||
|
||||
|
@ -14,13 +14,13 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.Timer;
|
||||
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.SupportedFileTypesDeviceEvent;
|
||||
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.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
@ -98,7 +98,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
private ICommunicator communicator;
|
||||
private MusicStateSpec musicStateSpec;
|
||||
private Timer musicStateTimer;
|
||||
|
||||
private final List<FileType> supportedFileTypeList = new ArrayList<>();
|
||||
private final List<File> filesToProcess = new ArrayList<>();
|
||||
|
||||
public GarminSupport() {
|
||||
super(LOG);
|
||||
@ -277,14 +279,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
|
||||
|
||||
if (entry.getFiletype().isFitFile()) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
filesToProcess.add(new File(((FileDownloadedDeviceEvent) deviceEvent).localPath));
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
moveFilesFromLegacyCache(); //TODO: remove before merging
|
||||
|
||||
if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
|
||||
if (!gbDevice.isBusy()) {
|
||||
isBusyFetching = true;
|
||||
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().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
try {
|
||||
FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
|
||||
while (checkFileExists(directoryEntry.getFileName()) || checkFileExists(directoryEntry.getLegacyFileName())) {
|
||||
while (!filesToDownload.isEmpty()) {
|
||||
final FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
|
||||
if (checkFileExists(directoryEntry.getFileName()) || checkFileExists(directoryEntry.getLegacyFileName())) {
|
||||
LOG.debug("File: {} already downloaded, not downloading again.", directoryEntry.getFileName());
|
||||
if (!getKeepActivityDataOnDevice()) { // delete file from watch if already downloaded
|
||||
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) {
|
||||
sendOutgoingMessage(downloadRequestMessage);
|
||||
return;
|
||||
} else {
|
||||
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();
|
||||
GB.updateTransferNotification(null, "", false, 100, 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))) {
|
||||
getDevice().unsetBusyTask();
|
||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
// Keep the device marked as busy while we process the files asynchronously
|
||||
|
||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||
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);
|
||||
}
|
||||
|
||||
try {
|
||||
int i = 0;
|
||||
for (final File file : fitFiles) {
|
||||
i++;
|
||||
LOG.debug("Parsing {}", file);
|
||||
|
||||
GB.updateTransferNotification("Parsing fit files", "File " + i + " of " + fitFiles.length, 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);
|
||||
}
|
||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||
fitAsyncProcessor.process(Arrays.asList(fitFiles), new FitAsyncProcessor.Callback() {
|
||||
@Override
|
||||
public void onProgress(final int i) {
|
||||
GB.updateTransferNotification(
|
||||
"Parsing fit files", "File " + i + " of " + fitFiles.length,
|
||||
true,
|
||||
(i * 100) / fitFiles.length, getContext()
|
||||
);
|
||||
}
|
||||
} 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 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