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:
parent
94fae05b02
commit
f0825d1ab6
@ -811,13 +811,16 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
parseAllFitFilesFromStorage();
|
parseAllFitFilesFromStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean parsingFitFilesFromStorage = false;
|
||||||
|
|
||||||
private void parseAllFitFilesFromStorage() {
|
private void parseAllFitFilesFromStorage() {
|
||||||
// This function as-is should only be used for debug purposes
|
if (parsingFitFilesFromStorage) {
|
||||||
if (!BuildConfig.DEBUG) {
|
GB.toast(getContext(), "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
LOG.error("This should never be used in release builds");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsingFitFilesFromStorage = true;
|
||||||
|
|
||||||
LOG.info("Parsing all fit files from storage");
|
LOG.info("Parsing all fit files from storage");
|
||||||
|
|
||||||
final File[] fitFiles;
|
final File[] fitFiles;
|
||||||
@ -826,32 +829,38 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
|
|
||||||
if (!exportDir.exists() || !exportDir.isDirectory()) {
|
if (!exportDir.exists() || !exportDir.isDirectory()) {
|
||||||
LOG.error("export directory {} not found", exportDir);
|
LOG.error("export directory {} not found", exportDir);
|
||||||
|
GB.toast(getContext(), "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fitFiles = exportDir.listFiles((dir, name) -> name.endsWith(".fit"));
|
fitFiles = exportDir.listFiles((dir, name) -> name.endsWith(".fit"));
|
||||||
if (fitFiles == null) {
|
if (fitFiles == null) {
|
||||||
LOG.error("fitFiles is null for {}", exportDir);
|
LOG.error("fitFiles is null for {}", exportDir);
|
||||||
|
GB.toast(getContext(), "fitFiles is null for " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fitFiles.length == 0) {
|
if (fitFiles.length == 0) {
|
||||||
LOG.error("No fit files found in {}", exportDir);
|
LOG.error("No fit files found in {}", exportDir);
|
||||||
|
GB.toast(getContext(), "No fit files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
LOG.error("Failed to parse from storage", e);
|
LOG.error("Failed to parse from storage", e);
|
||||||
|
GB.toast(getContext(), "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||||
|
|
||||||
GB.updateTransferNotification("Parsing fit files", "...", true, 0, getContext());
|
GB.updateTransferNotification("Parsing fit files", "...", true, 0, getContext());
|
||||||
|
|
||||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
//try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
final DaoSession session = handler.getDaoSession();
|
// final DaoSession session = handler.getDaoSession();
|
||||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
// final Device device = DBHelper.getDevice(gbDevice, session);
|
||||||
getCoordinator().deleteAllActivityData(device, session);
|
// //getCoordinator().deleteAllActivityData(device, session);
|
||||||
} catch (final Exception e) {
|
//} catch (final Exception e) {
|
||||||
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);
|
||||||
}
|
//}
|
||||||
|
|
||||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
||||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||||
@ -871,6 +880,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish() {
|
public void onFinish() {
|
||||||
|
parsingFitFilesFromStorage = false;
|
||||||
GB.updateTransferNotification("", "", false, 100, getContext());
|
GB.updateTransferNotification("", "", false, 100, getContext());
|
||||||
GB.signalActivityDataFinish();
|
GB.signalActivityDataFinish();
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import java.util.Optional;
|
|||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import de.greenrobot.dao.query.QueryBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
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.GarminSpo2SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
|
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.ActivityPoint;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
|
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.enums.GarminSport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
|
||||||
@ -284,8 +285,18 @@ public class FitImporter {
|
|||||||
|
|
||||||
LOG.debug("Persisting workout for {}", fileId);
|
LOG.debug("Persisting workout for {}", fileId);
|
||||||
|
|
||||||
final BaseActivitySummary summary = new BaseActivitySummary();
|
final BaseActivitySummary summary;
|
||||||
summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
|
|
||||||
|
// 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();
|
final ActivitySummaryData summaryData = new ActivitySummaryData();
|
||||||
|
|
||||||
@ -297,17 +308,12 @@ public class FitImporter {
|
|||||||
activityKind = getActivityKind(session.getSport(), session.getSubSport());
|
activityKind = getActivityKind(session.getSport(), session.getSubSport());
|
||||||
}
|
}
|
||||||
summary.setActivityKind(activityKind.getCode());
|
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) {
|
if (session.getTotalElapsedTime() == null) {
|
||||||
LOG.error("No elapsed time for {}", fileId);
|
LOG.error("No elapsed time for {}", fileId);
|
||||||
return;
|
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) {
|
if (session.getTotalTimerTime() != null) {
|
||||||
summaryData.add(ACTIVE_SECONDS, session.getTotalTimerTime() / 1000f, UNIT_SECONDS);
|
summaryData.add(ACTIVE_SECONDS, session.getTotalTimerTime() / 1000f, UNIT_SECONDS);
|
||||||
@ -371,6 +377,34 @@ public class FitImporter {
|
|||||||
return ActivityKind.UNKNOWN;
|
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() {
|
private void reset() {
|
||||||
activitySamplesPerTimestamp.clear();
|
activitySamplesPerTimestamp.clear();
|
||||||
stressSamples.clear();
|
stressSamples.clear();
|
||||||
|
Loading…
Reference in New Issue
Block a user