diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java index 5afca0659..78358ca92 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java @@ -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(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java index 873a345c3..2d29522b4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java @@ -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 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 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();