mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-28 03:25:49 +01:00
Garmin: Store pending files for processing in the database
This commit is contained in:
parent
d0b525f420
commit
09865f3943
@ -45,7 +45,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(76, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(77, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -116,6 +116,7 @@ public class GBDaoGenerator {
|
||||
addGarminEventSample(schema, user, device);
|
||||
addGarminHrvSummarySample(schema, user, device);
|
||||
addGarminHrvValueSample(schema, user, device);
|
||||
addPendingFile(schema, user, device);
|
||||
addWena3EnergySample(schema, user, device);
|
||||
addWena3BehaviorSample(schema, user, device);
|
||||
addWena3CaloriesSample(schema, user, device);
|
||||
@ -755,6 +756,28 @@ public class GBDaoGenerator {
|
||||
return hrvValueSample;
|
||||
}
|
||||
|
||||
private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
|
||||
Entity pendingFile = addEntity(schema, "PendingFile");
|
||||
pendingFile.setJavaDoc(
|
||||
"This class represents a file that was fetched from the device and is pending processing."
|
||||
);
|
||||
|
||||
// We need a single-column primary key so that we can delete records
|
||||
pendingFile.addIdProperty().autoincrement();
|
||||
|
||||
Property path = pendingFile.addStringProperty("path").notNull().getProperty();
|
||||
Property deviceId = pendingFile.addLongProperty("deviceId").notNull().getProperty();
|
||||
pendingFile.addToOne(device, deviceId);
|
||||
|
||||
final Index indexUnique = new Index();
|
||||
indexUnique.addProperty(deviceId);
|
||||
indexUnique.addProperty(path);
|
||||
indexUnique.makeUnique();
|
||||
pendingFile.addIndex(indexUnique);
|
||||
|
||||
return pendingFile;
|
||||
}
|
||||
|
||||
private static Entity addWatchXPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "WatchXPlusActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
|
@ -0,0 +1,100 @@
|
||||
/* Copyright (C) 2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.greenrobot.dao.Property;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PendingFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PendingFileDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public final class PendingFileProvider {
|
||||
private final DaoSession mSession;
|
||||
private final GBDevice mDevice;
|
||||
|
||||
public PendingFileProvider(final GBDevice device, final DaoSession session) {
|
||||
mDevice = device;
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<PendingFile> getAllPendingFiles() {
|
||||
final QueryBuilder<PendingFile> qb = mSession.getPendingFileDao().queryBuilder();
|
||||
final Device dbDevice = DBHelper.findDevice(mDevice, mSession);
|
||||
if (dbDevice == null) {
|
||||
// no device, no pending files
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final Property deviceProperty = PendingFileDao.Properties.DeviceId;
|
||||
qb.where(deviceProperty.eq(dbDevice.getId()));
|
||||
final List<PendingFile> ret = qb.build().list();
|
||||
mSession.getPendingFileDao().detachAll();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void removePendingFile(final String path) {
|
||||
final PendingFile pendingFile = findByPath(path);
|
||||
if (pendingFile != null) {
|
||||
pendingFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void addPendingFile(final String path) {
|
||||
final PendingFile existingFile = findByPath(path);
|
||||
if (existingFile != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Device device = DBHelper.getDevice(mDevice, mSession);
|
||||
|
||||
final PendingFile pendingFile = new PendingFile();
|
||||
pendingFile.setPath(path);
|
||||
pendingFile.setDevice(device);
|
||||
|
||||
addPendingFile(pendingFile);
|
||||
}
|
||||
|
||||
public void addPendingFile(final PendingFile pendingFile) {
|
||||
mSession.getPendingFileDao().insertOrReplace(pendingFile);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PendingFile findByPath(final String path) {
|
||||
final Device device = DBHelper.getDevice(mDevice, mSession);
|
||||
|
||||
final PendingFileDao pendingFileDao = mSession.getPendingFileDao();
|
||||
final QueryBuilder<PendingFile> qb = pendingFileDao.queryBuilder();
|
||||
qb.where(PendingFileDao.Properties.DeviceId.eq(device.getId()));
|
||||
qb.where(PendingFileDao.Properties.Path.eq(path));
|
||||
final List<PendingFile> pendingFiles = qb.build().list();
|
||||
if (!pendingFiles.isEmpty()) {
|
||||
return pendingFiles.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2SampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PendingFileDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
|
||||
@ -71,6 +72,10 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
session.getBaseActivitySummaryDao().queryBuilder()
|
||||
.where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId))
|
||||
.buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
|
||||
session.getPendingFileDao().queryBuilder()
|
||||
.where(PendingFileDao.Properties.DeviceId.eq(deviceId))
|
||||
.buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -29,19 +29,18 @@ import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.PendingFileProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminGpxRouteInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
@ -97,7 +96,6 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
private final Queue<FileTransferHandler.DirectoryEntry> filesToDownload;
|
||||
private final List<MessageHandler> messageHandlers;
|
||||
private final List<FileType> supportedFileTypeList = new ArrayList<>();
|
||||
private final List<File> filesToProcess = new ArrayList<>();
|
||||
private ICommunicator communicator;
|
||||
private MusicStateSpec musicStateSpec;
|
||||
private Timer musicStateTimer;
|
||||
@ -298,7 +296,15 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
|
||||
|
||||
if (entry.getFiletype().isFitFile()) {
|
||||
filesToProcess.add(new File(((FileDownloadedDeviceEvent) deviceEvent).localPath));
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
|
||||
|
||||
pendingFileProvider.addPendingFile(((FileDownloadedDeviceEvent) deviceEvent).localPath);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(getContext(), "Error saving pending file", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!getKeepActivityDataOnDevice()) { // delete file from watch upon successful download
|
||||
@ -309,6 +315,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
super.evaluateGBDeviceEvent(deviceEvent);
|
||||
}
|
||||
|
||||
/** @noinspection BooleanMethodIsAlwaysInverted*/
|
||||
private boolean getKeepActivityDataOnDevice() {
|
||||
return getDevicePrefs().getBoolean("keep_activity_data_on_device", false);
|
||||
}
|
||||
@ -423,6 +430,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
|
||||
for (int day = 0; day < 4; day++) {
|
||||
if (day < weather.forecasts.size()) {
|
||||
//noinspection ExtractMethodRecommender
|
||||
WeatherSpec.Daily daily = weather.forecasts.get(day);
|
||||
int ts = weather.timestamp + (day + 1) * 24 * 60 * 60;
|
||||
RecordData weatherDailyForecast = new RecordData(recordDefinitionDaily, recordDefinitionDaily.getRecordHeader());
|
||||
@ -477,6 +485,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (config) {
|
||||
case PREF_SEND_APP_NOTIFICATIONS:
|
||||
NotificationSubscriptionDeviceEvent notificationSubscriptionDeviceEvent = new NotificationSubscriptionDeviceEvent();
|
||||
@ -484,8 +493,6 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
evaluateGBDeviceEvent(notificationSubscriptionDeviceEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void processDownloadQueue() {
|
||||
@ -515,7 +522,23 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
}
|
||||
|
||||
if (filesToDownload.isEmpty() && !fileTransferHandler.isDownloading() && isBusyFetching) {
|
||||
final List<File> filesToProcess;
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
|
||||
|
||||
filesToProcess = pendingFileProvider.getAllPendingFiles()
|
||||
.stream()
|
||||
.map(pf -> new File(pf.getPath()))
|
||||
.collect(Collectors.toList());
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to get pending files", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filesToProcess.isEmpty()) {
|
||||
LOG.debug("No pending files to process");
|
||||
// No downloaded fit files to process
|
||||
if (gbDevice.isBusy() && isBusyFetching) {
|
||||
GB.signalActivityDataFinish();
|
||||
@ -530,19 +553,17 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
// 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();
|
||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
||||
fitAsyncProcessor.process(filesToProcessClone, new FitAsyncProcessor.Callback() {
|
||||
fitAsyncProcessor.process(filesToProcess, new FitAsyncProcessor.Callback() {
|
||||
@Override
|
||||
public void onProgress(final int i) {
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now - lastNotificationUpdateTs[0] > 1500L) {
|
||||
lastNotificationUpdateTs[0] = now;
|
||||
GB.updateTransferNotification(
|
||||
"Parsing fit files", "File " + i + " of " + filesToProcessClone.size(),
|
||||
"Parsing fit files", "File " + i + " of " + filesToProcess.size(),
|
||||
true,
|
||||
(i * 100) / filesToProcessClone.size(), getContext()
|
||||
(i * 100) / filesToProcess.size(), getContext()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -9,7 +10,13 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.PendingFileProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.FileDownloadedDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FitAsyncProcessor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FitAsyncProcessor.class);
|
||||
@ -45,6 +52,17 @@ public class FitAsyncProcessor {
|
||||
fitImporter.importFile(file);
|
||||
} catch (final Exception ex) {
|
||||
LOG.error("Exception while importing {}", file, ex);
|
||||
continue; // do not remove from pending files
|
||||
}
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
|
||||
|
||||
pendingFileProvider.removePendingFile(file.getPath());
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Exception while removing pending file {}", file, e);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
|
Loading…
Reference in New Issue
Block a user