mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 17:43:06 +02:00
@@ -16,10 +16,10 @@ import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackStoppedBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportUtils;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.WorkRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -41,7 +46,6 @@ import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
/**
|
||||
@@ -56,7 +60,8 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
* So, for this check actually a different file name might be used than in the ExportService.
|
||||
* * Saved state as an object instead of individual values.
|
||||
*/
|
||||
public class ExportActivity extends AppCompatActivity implements ExportService.ExportServiceResultReceiver.Receiver {
|
||||
//TODO Make an AbstractActivity
|
||||
public class ExportActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = ExportActivity.class.getSimpleName();
|
||||
|
||||
@@ -84,8 +89,6 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private Uri directoryUri;
|
||||
|
||||
private ExportService.ExportServiceResultReceiver resultReceiver;
|
||||
|
||||
private List<String> directoryFiles;
|
||||
|
||||
private int trackExportSuccessCount;
|
||||
@@ -161,8 +164,6 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
String directoryDisplayName = FileUtils.getPath(documentFile);
|
||||
|
||||
resultReceiver = new ExportService.ExportServiceResultReceiver(new Handler(), this);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
autoConflict = ConflictResolutionStrategy.CONFLICT_NONE;
|
||||
setProgress();
|
||||
@@ -231,19 +232,45 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
|
||||
|
||||
if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_NONE) {
|
||||
conflict(exportTask);
|
||||
} else if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_SKIP) {
|
||||
return;
|
||||
}
|
||||
if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_SKIP) {
|
||||
trackExportSkippedCount++;
|
||||
nextExport(exportTask);
|
||||
} else {
|
||||
ExportService.enqueue(this, resultReceiver, exportTask, directoryUri);
|
||||
return;
|
||||
}
|
||||
|
||||
WorkManager workManager = WorkManager.getInstance(this);
|
||||
WorkRequest exportRequest = new OneTimeWorkRequest.Builder(ExportWorker.class)
|
||||
.setInputData(new Data.Builder()
|
||||
.putLongArray(ExportWorker.TRACKIDS_KEY, exportTask.getTrackIds().stream().mapToLong(Track.Id::id).toArray())
|
||||
.putString(ExportWorker.DIRECTORY_URI_KEY, directoryUri.toString())
|
||||
.putString(ExportWorker.TRACKFILEFORMAT_KEY, exportTask.getTrackFileFormat().toString())
|
||||
.putString(ExportWorker.FILENAME_KEY, exportTask.getFilename())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
workManager
|
||||
.getWorkInfoByIdLiveData(exportRequest.getId())
|
||||
.observe(this, workInfo -> {
|
||||
if (workInfo != null) {
|
||||
WorkInfo.State state = workInfo.getState();
|
||||
switch (state) {
|
||||
case SUCCEEDED -> onExportSuccess(exportTask);
|
||||
case FAILED ->
|
||||
onExportError(exportTask, workInfo.getOutputData().getString(ExportWorker.RESULT_EXPORT_ERROR_MESSAGE_KEY));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
workManager.enqueue(exportRequest);
|
||||
}
|
||||
|
||||
private void export(ExportTask exportTask) {
|
||||
export(exportTask, autoConflict);
|
||||
}
|
||||
|
||||
@Deprecated //TODO Check should be done in ExportService
|
||||
@Deprecated //TODO Check should be done in ExportWorker
|
||||
private boolean exportFileExists(ExportTask exportTask) {
|
||||
String filename;
|
||||
if (exportTask.isMultiExport()) {
|
||||
@@ -314,7 +341,6 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportSuccess(ExportTask exportTask) {
|
||||
if (exportFileExists(exportTask)) {
|
||||
trackExportOverwrittenCount++;
|
||||
@@ -325,7 +351,6 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
|
||||
nextExport(exportTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportError(ExportTask exportTask, String errorMessage) {
|
||||
trackExportErrorCount++;
|
||||
String name;
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.app.job.JobService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
|
||||
public class ExportService extends JobIntentService {
|
||||
|
||||
private static final String TAG = ExportService.class.getSimpleName();
|
||||
|
||||
private static final int JOB_ID = 1;
|
||||
|
||||
private static final String EXTRA_RECEIVER = "extra_receiver";
|
||||
private static final String EXTRA_EXPORT_TASK = "export_task";
|
||||
private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri";
|
||||
|
||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, ExportTask exportTask, Uri directoryUri) {
|
||||
Intent intent = new Intent(context, JobService.class);
|
||||
intent.putExtra(EXTRA_RECEIVER, receiver);
|
||||
intent.putExtra(EXTRA_EXPORT_TASK, exportTask);
|
||||
intent.putExtra(EXTRA_DIRECTORY_URI, directoryUri);
|
||||
enqueueWork(context, ExportService.class, JOB_ID, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
// Get all data.
|
||||
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER);
|
||||
ExportTask exportTask = intent.getParcelableExtra(EXTRA_EXPORT_TASK);
|
||||
Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI);
|
||||
|
||||
// Prepare resultCode and bundle to send to the receiver.
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK, exportTask);
|
||||
|
||||
// Build directory file.
|
||||
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
if (directoryFile == null || !directoryFile.canWrite()) {
|
||||
bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, getString(R.string.export_cannot_write_to_dir) + ": " + directoryFile);
|
||||
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Export and send result
|
||||
try {
|
||||
ExportUtils.exportTrack(this, directoryFile, exportTask);
|
||||
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_SUCCESS, bundle);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Export failed: " + e);
|
||||
e.printStackTrace(); //TODO Remove
|
||||
|
||||
bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, e.getMessage());
|
||||
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExportServiceResultReceiver extends ResultReceiver {
|
||||
|
||||
public static final int RESULT_CODE_SUCCESS = 1;
|
||||
public static final int RESULT_CODE_ERROR = 0;
|
||||
|
||||
public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task";
|
||||
|
||||
public static final String EXTRA_EXPORT_ERROR_MESSAGE = "extra_export_error_message";
|
||||
|
||||
private final Receiver receiver;
|
||||
|
||||
public ExportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) {
|
||||
super(handler);
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
ExportTask exportTask = resultData.getParcelable(RESULT_EXTRA_EXPORT_TASK);
|
||||
switch (resultCode) {
|
||||
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(exportTask);
|
||||
case RESULT_CODE_ERROR -> receiver.onExportError(exportTask, resultData.getString(EXTRA_EXPORT_ERROR_MESSAGE));
|
||||
default -> throw new RuntimeException("Unknown resultCode.");
|
||||
}
|
||||
}
|
||||
|
||||
public interface Receiver {
|
||||
default void onExportSuccess(ExportTask exportTask) {
|
||||
}
|
||||
|
||||
default void onExportError(ExportTask exportTask, String errorMessage) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
-17
@@ -1,15 +1,21 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.WorkRequest;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -22,36 +28,81 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportService;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportService.ExportServiceResultReceiver;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportTask;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
public class ExportUtils {
|
||||
|
||||
private static final String TAG = ExportUtils.class.getSimpleName();
|
||||
|
||||
public static void postWorkoutExport(Context context, Track.Id trackId) {
|
||||
public static void postWorkoutExport(AppCompatActivity context, Track.Id trackId) {
|
||||
if (PreferencesUtils.shouldInstantExportAfterWorkout()) {
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||
DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri());
|
||||
|
||||
ExportServiceResultReceiver resultReceiver = new ExportServiceResultReceiver(new Handler(), new ExportServiceResultReceiver.Receiver() {
|
||||
@Override
|
||||
public void onExportError(ExportTask unused, String errorMessage) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(SettingsActivity.EXTRAS_EXPORT_ERROR_MESSAGE, errorMessage);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
WorkManager workManager = WorkManager.getInstance(context);
|
||||
WorkRequest exportRequest = new OneTimeWorkRequest.Builder(ExportWorker.class)
|
||||
.setInputData(new Data.Builder()
|
||||
.putLongArray(ExportWorker.TRACKIDS_KEY, new long[]{trackId.id()})
|
||||
.putString(ExportWorker.DIRECTORY_URI_KEY, directory.toString())
|
||||
.putString(ExportWorker.TRACKFILEFORMAT_KEY, trackFileFormat.toString())
|
||||
.putString(ExportWorker.FILENAME_KEY, null)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
ExportService.enqueue(context, resultReceiver, new ExportTask(null, trackFileFormat, List.of(trackId)), directory.getUri());
|
||||
workManager
|
||||
.getWorkInfoByIdLiveData(exportRequest.getId())
|
||||
.observe(context, workInfo -> {
|
||||
if (workInfo != null) {
|
||||
if (workInfo.getState() == WorkInfo.State.FAILED) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(SettingsActivity.EXTRAS_EXPORT_ERROR_MESSAGE, workInfo.getProgress().getString(ExportWorker.RESULT_EXPORT_ERROR_MESSAGE_KEY));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
workManager.enqueue(exportRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public static void exportTrack(Context context, DocumentFile directory, @Nullable String filenameForMultiple, TrackFileFormat trackFileFormat, List<Track.Id> trackIds) {
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
List<Track> tracks = trackIds.stream()
|
||||
.map(contentProviderUtils::getTrack)
|
||||
.toList();
|
||||
|
||||
Uri exportDocumentFileUri;
|
||||
if (tracks.size() == 1) {
|
||||
exportDocumentFileUri = getExportDocumentFileUri(context, tracks.get(0), trackFileFormat, directory);
|
||||
} else {
|
||||
String filename = TrackFilenameGenerator.format(filenameForMultiple, trackFileFormat);
|
||||
exportDocumentFileUri = getExportDocumentFileUri(context, filename, trackFileFormat, directory);
|
||||
}
|
||||
|
||||
if (exportDocumentFileUri == null) {
|
||||
throw new RuntimeException("Couldn't create document file for export");
|
||||
}
|
||||
|
||||
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context, contentProviderUtils);
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(exportDocumentFileUri, "wt")) {
|
||||
if (!trackExporter.writeTrack(tracks, outputStream)) {
|
||||
if (!DocumentFile.fromSingleUri(context, exportDocumentFileUri).delete()) {
|
||||
throw new RuntimeException("Unable to delete exportDocumentFile");
|
||||
}
|
||||
throw new RuntimeException("Unable to export track");
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException("Unable to open exportDocumentFile " + exportDocumentFileUri, e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to close exportDocumentFile output stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void exportTrack(Context context, DocumentFile directory, ExportTask exportTask) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
List<Track> tracks = exportTask.getTrackIds().stream().map(contentProviderUtils::getTrack).collect(Collectors.toList());
|
||||
@@ -0,0 +1,70 @@
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
public class ExportWorker extends Worker {
|
||||
|
||||
private static final String TAG = ExportWorker.class.getSimpleName();
|
||||
|
||||
static final String TRACKIDS_KEY = "TRACKIDS_KEY";
|
||||
static final String DIRECTORY_URI_KEY = "DIRECTORY_URI_KEY";
|
||||
static final String TRACKFILEFORMAT_KEY = "TRACKFILEFORMAT_KEY";
|
||||
static final String FILENAME_KEY = "FILENAME_KEY"; //optional; only used for multiple tracks into one file
|
||||
|
||||
static final String RESULT_EXPORT_ERROR_MESSAGE_KEY = "EXPORT_ERROR_MESSAGE";
|
||||
|
||||
private final List<Track.Id> trackIds;
|
||||
|
||||
private final DocumentFile directoryFile;
|
||||
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
private final String filename;
|
||||
|
||||
public ExportWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
|
||||
trackIds = Arrays.stream(getInputData().getLongArray(TRACKIDS_KEY))
|
||||
.mapToObj(Track.Id::new)
|
||||
.toList();
|
||||
|
||||
directoryFile = DocumentFile.fromTreeUri(context, Uri.parse(getInputData().getString(DIRECTORY_URI_KEY)));
|
||||
|
||||
trackFileFormat = TrackFileFormat.valueOf(getInputData().getString(TRACKFILEFORMAT_KEY));
|
||||
|
||||
filename = getInputData().getString(FILENAME_KEY);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
if (directoryFile == null || !directoryFile.canWrite()) {
|
||||
return Result.failure(new Data.Builder().putString(RESULT_EXPORT_ERROR_MESSAGE_KEY, getApplicationContext().getString(R.string.export_cannot_write_to_dir) + ": " + directoryFile).build());
|
||||
}
|
||||
|
||||
try {
|
||||
//TODO move method to ExportWorker?
|
||||
ExportUtils.exportTrack(getApplicationContext(), directoryFile, filename, trackFileFormat, trackIds);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Export failed: " + e);
|
||||
|
||||
return Result.failure(new Data.Builder().putString(RESULT_EXPORT_ERROR_MESSAGE_KEY, e.getMessage()).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package de.dennisguse.opentracks.publicapi;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportUtils;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
|
||||
public class StopRecording extends AbstractAPIActivity {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user