mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin: Split fit files per type / year
This commit is contained in:
+36
-15
@@ -144,11 +144,13 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
||||
}
|
||||
|
||||
private void saveFileToExternalStorage() {
|
||||
File dir;
|
||||
File deviceDir;
|
||||
File outputFile;
|
||||
try {
|
||||
dir = deviceSupport.getWritableExportDirectory();
|
||||
outputFile = new File(dir, currentlyDownloading.getFileName());
|
||||
deviceDir = deviceSupport.getWritableExportDirectory();
|
||||
outputFile = new File(deviceDir, currentlyDownloading.directoryEntry.getOutputPath());
|
||||
final File parentFile = outputFile.getParentFile();
|
||||
parentFile.mkdirs();
|
||||
FileUtils.copyStreamToFile(new ByteArrayInputStream(currentlyDownloading.dataHolder.array()), outputFile);
|
||||
outputFile.setLastModified(currentlyDownloading.directoryEntry.fileDate.getTime());
|
||||
} catch (final IOException e) {
|
||||
@@ -316,10 +318,6 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
||||
return Math.min(maxBlockSize, GFDIMessage.getMaxPacketSize()); //TODO: can we use GFDIMessage.getMaxPacketSize() directly?
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return directoryEntry.getFileName();
|
||||
}
|
||||
|
||||
private void setSize(DownloadRequestStatusMessage downloadRequestStatusMessage) {
|
||||
if (0 != getDataSize())
|
||||
throw new IllegalStateException("Data size already set");
|
||||
@@ -366,7 +364,8 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
||||
}
|
||||
|
||||
public static class DirectoryEntry {
|
||||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ROOT);
|
||||
private static final SimpleDateFormat SDF_FULL = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ROOT);
|
||||
private static final SimpleDateFormat SDF_YEAR = new SimpleDateFormat("yyyy", Locale.ROOT);
|
||||
|
||||
private final int fileIndex;
|
||||
private final FileType.FILETYPE filetype;
|
||||
@@ -394,19 +393,41 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
||||
return filetype;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
public Date getFileDate() {
|
||||
return fileDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the output path.
|
||||
* Format: [FILE_TYPE]/[YEAR]/[FILE_TYPE]_[yyyy-MM-dd_HH-mm-ss]_[INDEX].[fit/bin]
|
||||
*/
|
||||
public String getOutputPath() {
|
||||
// [FILE_TYPE]/
|
||||
final StringBuilder sb = new StringBuilder(getFiletype().name());
|
||||
sb.append("/");
|
||||
|
||||
// If we have a valid date, place the file inside a folder for each year
|
||||
// [YEAR]/
|
||||
if (fileDate.getTime() != GarminTimeUtils.GARMIN_TIME_EPOCH * 1000L) {
|
||||
sb.append("_").append(SDF.format(fileDate));
|
||||
sb.append(SDF_YEAR.format(fileDate));
|
||||
sb.append("/");
|
||||
}
|
||||
sb.append("_").append(getFileIndex()).append(getFiletype().isFitFile() ? ".fit" : ".bin");
|
||||
|
||||
// Finally, the filename
|
||||
sb.append(getFileName());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getLegacyFileName() {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
|
||||
String dateString = dateFormat.format(fileDate);
|
||||
return getFiletype().name() + "_" + getFileIndex() + "_" + dateString + (getFiletype().isFitFile() ? ".fit" : ".bin");
|
||||
/**
|
||||
* [FILE_TYPE]_[yyyy-MM-dd_HH-mm-ss]_[INDEX].[fit/bin]
|
||||
*/
|
||||
public String getFileName() {
|
||||
final StringBuilder sb = new StringBuilder(getFiletype().name());
|
||||
if (fileDate.getTime() != GarminTimeUtils.GARMIN_TIME_EPOCH * 1000L) {
|
||||
sb.append("_").append(SDF_FULL.format(fileDate));
|
||||
}
|
||||
sb.append("_").append(getFileIndex()).append(getFiletype().isFitFile() ? ".fit" : ".bin");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
+41
-19
@@ -1,5 +1,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
@@ -18,8 +19,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -86,6 +87,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetF
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SupportedFileTypesMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SystemEventMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationSubscriptionStatusMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
@@ -627,7 +629,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
}
|
||||
|
||||
final DownloadRequestMessage downloadRequestMessage = fileTransferHandler.downloadDirectoryEntry(directoryEntry);
|
||||
LOG.debug("Will download file: {}", directoryEntry.getFileName());
|
||||
LOG.debug("Will download file: {}", directoryEntry.getOutputPath());
|
||||
sendOutgoingMessage("download file " + directoryEntry.getFileIndex(), downloadRequestMessage);
|
||||
return;
|
||||
}
|
||||
@@ -806,19 +808,42 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
}
|
||||
|
||||
private boolean alreadyDownloaded(final FileTransferHandler.DirectoryEntry entry) {
|
||||
final Optional<File> file = getFile(entry.getFileName());
|
||||
// Current filename
|
||||
final Optional<File> file = getFile(entry.getOutputPath());
|
||||
if (file.isPresent()) {
|
||||
if (file.get().length() == 0) {
|
||||
LOG.warn("File {} is empty", entry.getFileName());
|
||||
LOG.warn("File {} is empty", entry.getOutputPath());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
final Optional<File> legacyFile = getFile(entry.getLegacyFileName());
|
||||
if (legacyFile.isPresent()) {
|
||||
if (legacyFile.get().length() == 0) {
|
||||
LOG.warn("Legacy file {} is empty", entry.getFileName());
|
||||
// Legacy filename 1, before we had per-type/year folder
|
||||
@SuppressLint("SimpleDateFormat") final SimpleDateFormat legacyDateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ROOT);
|
||||
final StringBuilder sbLegacy1 = new StringBuilder(entry.getFiletype().name());
|
||||
if (entry.getFileDate().getTime() != GarminTimeUtils.GARMIN_TIME_EPOCH * 1000L) {
|
||||
sbLegacy1.append("_").append(legacyDateFormat.format(entry.getFileDate()));
|
||||
}
|
||||
sbLegacy1.append("_").append(entry.getFileIndex()).append(entry.getFiletype().isFitFile() ? ".fit" : ".bin");
|
||||
final String legacyName1 = sbLegacy1.toString();
|
||||
final Optional<File> legacyFile1 = getFile(legacyName1);
|
||||
if (legacyFile1.isPresent()) {
|
||||
if (legacyFile1.get().length() == 0) {
|
||||
LOG.warn("Legacy file 1 {} is empty", legacyName1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Legacy filename 2
|
||||
final String legacyName2 = entry.getFiletype().name() + "_" +
|
||||
entry.getFileIndex() + "_" +
|
||||
legacyDateFormat.format(entry.getFileDate()) +
|
||||
(entry.getFiletype().isFitFile() ? ".fit" : ".bin");
|
||||
final Optional<File> legacyFile2 = getFile(legacyName2);
|
||||
if (legacyFile2.isPresent()) {
|
||||
if (legacyFile2.get().length() == 0) {
|
||||
LOG.warn("Legacy file 2 {} is empty", legacyName2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -998,7 +1023,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
|
||||
LOG.info("Parsing all fit files from storage");
|
||||
|
||||
final File[] fitFiles;
|
||||
final List<File> fitFiles;
|
||||
try {
|
||||
final File exportDir = getWritableExportDirectory();
|
||||
|
||||
@@ -1008,13 +1033,8 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
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) {
|
||||
fitFiles = FileUtils.listRecursive(exportDir, (dir, name) -> name.endsWith(".fit"));
|
||||
if (fitFiles.isEmpty()) {
|
||||
LOG.error("No fit files found in {}", exportDir);
|
||||
GB.toast(getContext(), "No fit files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
@@ -1025,6 +1045,8 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Got {} fit files to parse", fitFiles.size());
|
||||
|
||||
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||
|
||||
GB.updateTransferNotification("Parsing fit files", "...", true, 0, getContext());
|
||||
@@ -1039,16 +1061,16 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
|
||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||
fitAsyncProcessor.process(Arrays.asList(fitFiles), new FitAsyncProcessor.Callback() {
|
||||
fitAsyncProcessor.process(fitFiles, 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 " + fitFiles.length,
|
||||
"Parsing fit files", "File " + i + " of " + fitFiles.size(),
|
||||
true,
|
||||
(i * 100) / fitFiles.length, getContext()
|
||||
(i * 100) / fitFiles.size(), getContext()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -331,6 +332,26 @@ public class FileUtils {
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public static List<File> listRecursive(final File dir, final FilenameFilter filter) {
|
||||
final List<File> ret = new ArrayList<>();
|
||||
listRecursive(ret, dir, filter);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void listRecursive(final List<File> ret, final File dir, final FilenameFilter filter) {
|
||||
final File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
listRecursive(ret, file, filter);
|
||||
} else if (filter.accept(dir, file.getName())) {
|
||||
ret.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteRecursively(File dir) {
|
||||
if (!dir.exists()) {
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user