1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-01 03:55:47 +02:00

Garmin: Enable fit re-processing in non-debug builds

- Make workout summary persisting idempotent
- Do not delete any data from the database during re-processing, since
  the entire process is idempotent now
- Improve feedback during re-processing using toasts
- Prevent re-processing from being started multiple times in parallel
This commit is contained in:
José Rebelo 2024-08-17 20:57:11 +01:00
parent 94fae05b02
commit f0825d1ab6
2 changed files with 64 additions and 20 deletions

View File

@ -811,13 +811,16 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
parseAllFitFilesFromStorage();
}
boolean parsingFitFilesFromStorage = false;
private void parseAllFitFilesFromStorage() {
// This function as-is should only be used for debug purposes
if (!BuildConfig.DEBUG) {
LOG.error("This should never be used in release builds");
if (parsingFitFilesFromStorage) {
GB.toast(getContext(), "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
return;
}
parsingFitFilesFromStorage = true;
LOG.info("Parsing all fit files from storage");
final File[] fitFiles;
@ -826,32 +829,38 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
if (!exportDir.exists() || !exportDir.isDirectory()) {
LOG.error("export directory {} not found", exportDir);
GB.toast(getContext(), "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
return;
}
fitFiles = exportDir.listFiles((dir, name) -> name.endsWith(".fit"));
if (fitFiles == null) {
LOG.error("fitFiles is null for {}", exportDir);
GB.toast(getContext(), "fitFiles is null for " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
return;
}
if (fitFiles.length == 0) {
LOG.error("No fit files found in {}", exportDir);
GB.toast(getContext(), "No fit files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
return;
}
} catch (final Exception e) {
LOG.error("Failed to parse from storage", e);
GB.toast(getContext(), "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
return;
}
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
GB.updateTransferNotification("Parsing fit files", "...", true, 0, getContext());
try (DBHandler handler = GBApplication.acquireDB()) {
final DaoSession session = handler.getDaoSession();
final Device device = DBHelper.getDevice(gbDevice, session);
getCoordinator().deleteAllActivityData(device, session);
} catch (final Exception e) {
GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
}
//try (DBHandler handler = GBApplication.acquireDB()) {
// final DaoSession session = handler.getDaoSession();
// final Device device = DBHelper.getDevice(gbDevice, session);
// //getCoordinator().deleteAllActivityData(device, session);
//} catch (final Exception e) {
// GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
//}
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
@ -871,6 +880,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
@Override
public void onFinish() {
parsingFitFilesFromStorage = false;
GB.updateTransferNotification("", "", false, 100, getContext());
GB.signalActivityDataFinish();
}

View File

@ -27,6 +27,7 @@ import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
@ -39,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStageSampl
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSpo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
@ -55,7 +57,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
@ -203,7 +204,7 @@ public class FitImporter {
} else if (record instanceof FitHrvSummary) {
final FitHrvSummary hrvSummary = (FitHrvSummary) record;
LOG.trace("HRV summary at {}: {}", ts, record);
final GarminHrvSummarySample sample = new GarminHrvSummarySample( );
final GarminHrvSummarySample sample = new GarminHrvSummarySample();
sample.setTimestamp(ts * 1000L);
sample.setWeeklyAverage(hrvSummary.getWeeklyAverage());
sample.setLastNightAverage(hrvSummary.getLastNightAverage());
@ -284,8 +285,18 @@ public class FitImporter {
LOG.debug("Persisting workout for {}", fileId);
final BaseActivitySummary summary = new BaseActivitySummary();
summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
final BaseActivitySummary summary;
// This ensures idempotency when re-processing
try (DBHandler dbHandler = GBApplication.acquireDB()) {
final DaoSession session = dbHandler.getDaoSession();
final Device device = DBHelper.getDevice(gbDevice, session);
final User user = DBHelper.getUser(session);
summary = findOrCreateBaseActivitySummary(session, device, user, Objects.requireNonNull(fileId.getTimeCreated()).intValue());
} catch (final Exception e) {
GB.toast(context, "Error finding base summary", Toast.LENGTH_LONG, GB.ERROR, e);
return;
}
final ActivitySummaryData summaryData = new ActivitySummaryData();
@ -297,17 +308,12 @@ public class FitImporter {
activityKind = getActivityKind(session.getSport(), session.getSubSport());
}
summary.setActivityKind(activityKind.getCode());
if (session.getStartTime() == null) {
LOG.error("No session start time for {}", fileId);
return;
}
summary.setStartTime(new Date(GarminTimeUtils.garminTimestampToJavaMillis(session.getStartTime().intValue())));
if (session.getTotalElapsedTime() == null) {
LOG.error("No elapsed time for {}", fileId);
return;
}
summary.setEndTime(new Date(GarminTimeUtils.garminTimestampToJavaMillis(session.getStartTime().intValue() + session.getTotalElapsedTime().intValue() / 1000)));
summary.setEndTime(new Date(summary.getStartTime().getTime() + session.getTotalElapsedTime().intValue()));
if (session.getTotalTimerTime() != null) {
summaryData.add(ACTIVE_SECONDS, session.getTotalTimerTime() / 1000f, UNIT_SECONDS);
@ -371,6 +377,34 @@ public class FitImporter {
return ActivityKind.UNKNOWN;
}
protected static BaseActivitySummary findOrCreateBaseActivitySummary(final DaoSession session,
final Device device,
final User user,
final int timestampSeconds) {
final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
qb.where(BaseActivitySummaryDao.Properties.StartTime.eq(new Date(timestampSeconds * 1000L)));
qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(device.getId()));
qb.where(BaseActivitySummaryDao.Properties.UserId.eq(user.getId()));
final List<BaseActivitySummary> summaries = qb.build().list();
if (summaries.isEmpty()) {
final BaseActivitySummary summary = new BaseActivitySummary();
summary.setStartTime(new Date(timestampSeconds * 1000L));
summary.setDevice(device);
summary.setUser(user);
// These will be set later, once we parse the summary
summary.setEndTime(new Date(timestampSeconds * 1000L));
summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
return summary;
}
if (summaries.size() > 1) {
LOG.warn("Found multiple summaries for {}", timestampSeconds);
}
return summaries.get(0);
}
private void reset() {
activitySamplesPerTimestamp.clear();
stressSamples.clear();