forked from upstream-mirrors/OpenTracks
@@ -395,11 +395,6 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
|
||||
<service
|
||||
android:name=".io.file.importer.ImportService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<!--Necessary for SDK < 33 to (re-)store per app locale information-->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
|
||||
@@ -25,13 +25,12 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
|
||||
@@ -43,7 +42,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ImportActivity extends FragmentActivity {
|
||||
public class ImportActivity extends AbstractActivity {
|
||||
|
||||
private static final String TAG = ImportActivity.class.getSimpleName();
|
||||
|
||||
@@ -57,14 +56,11 @@ public class ImportActivity extends FragmentActivity {
|
||||
private ArrayList<Uri> documentUris = new ArrayList<>();
|
||||
private boolean isDirectory;
|
||||
|
||||
private ImportViewModel viewModel;
|
||||
private ImportViewModel.Summary summary;
|
||||
private Importer importer;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewBinding = ImportActivityBinding.inflate(getLayoutInflater());
|
||||
setContentView(viewBinding.getRoot());
|
||||
|
||||
final Intent intent = getIntent();
|
||||
final ClipData intentClipData = intent.getClipData();
|
||||
@@ -99,16 +95,36 @@ public class ImportActivity extends FragmentActivity {
|
||||
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
|
||||
viewModel.getImportData(documentFiles).observe(this, data -> {
|
||||
summary = data;
|
||||
setProgress();
|
||||
});
|
||||
importer = new Importer(this, documentFiles);
|
||||
importer.getLiveData()
|
||||
.observe(this, summary -> {
|
||||
setProgress(summary);
|
||||
});
|
||||
importer.startImport();
|
||||
|
||||
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
|
||||
viewBinding.importActivityToolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View createRootView() {
|
||||
viewBinding = ImportActivityBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
importer.cancel();
|
||||
return super.onSupportNavigateUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -116,7 +132,7 @@ public class ImportActivity extends FragmentActivity {
|
||||
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
|
||||
}
|
||||
|
||||
private void setProgress() {
|
||||
private void setProgress(Importer.Summary summary) {
|
||||
int done = summary.getCount();
|
||||
viewBinding.importProgressDone.setText("" + done);
|
||||
viewBinding.importProgressTotal.setText("" + summary.getTotalCount());
|
||||
@@ -130,11 +146,11 @@ public class ImportActivity extends FragmentActivity {
|
||||
viewBinding.importProgressSummaryErrorsGroup.setVisibility(summary.getErrorCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (summary.isDone()) {
|
||||
onImportEnded();
|
||||
onImportEnded(summary);
|
||||
}
|
||||
}
|
||||
|
||||
private void onImportEnded() {
|
||||
private void onImportEnded(Importer.Summary summary) {
|
||||
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
|
||||
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -146,13 +162,13 @@ public class ImportActivity extends FragmentActivity {
|
||||
});
|
||||
|
||||
if (summary.getErrorCount() > 0) {
|
||||
toggleUIEndWithErrors();
|
||||
toggleUIEndWithErrors(summary);
|
||||
} else {
|
||||
toggleUIEndOk();
|
||||
toggleUIEndOk(summary);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleUIEndWithErrors() {
|
||||
private void toggleUIEndWithErrors(Importer.Summary summary) {
|
||||
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
|
||||
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
|
||||
@@ -160,7 +176,7 @@ public class ImportActivity extends FragmentActivity {
|
||||
viewBinding.importProgressAlertMsg.setText(getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount()));
|
||||
}
|
||||
|
||||
private void toggleUIEndOk() {
|
||||
private void toggleUIEndOk(Importer.Summary summary) {
|
||||
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_dialog_success_24dp));
|
||||
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
|
||||
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.app.job.JobService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class ImportService extends JobIntentService {
|
||||
|
||||
private static final String TAG = ImportService.class.getSimpleName();
|
||||
|
||||
private static final int JOB_ID = 2;
|
||||
|
||||
private static final String EXTRA_RECEIVER = "extra_receiver";
|
||||
private static final String EXTRA_URI = "extra_uri";
|
||||
|
||||
private ResultReceiver resultReceiver;
|
||||
|
||||
public static void enqueue(Context context, ImportServiceResultReceiver receiver, Uri uri) {
|
||||
Intent intent = new Intent(context, JobService.class);
|
||||
intent.putExtra(EXTRA_RECEIVER, receiver);
|
||||
intent.putExtra(EXTRA_URI, uri);
|
||||
enqueueWork(context, ImportService.class, JOB_ID, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER);
|
||||
Uri uri = intent.getParcelableExtra(EXTRA_URI);
|
||||
importFile(DocumentFile.fromSingleUri(this, uri));
|
||||
}
|
||||
|
||||
private void importFile(DocumentFile file) {
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||
|
||||
String fileExtension = FileUtils.getExtension(file);
|
||||
try {
|
||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
|
||||
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
|
||||
|
||||
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), maxRecordingDistance, preventReimport);
|
||||
|
||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new XMLImporter(new GPXTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
|
||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new XMLImporter(new KMLTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
|
||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new KMZTrackImporter(this, trackImporter).importFile(file.getUri()));
|
||||
} else {
|
||||
Log.d(TAG, "Unsupported file format.");
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trackIds.isEmpty()) {
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName()));
|
||||
} else {
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackIds, file, getString(R.string.import_unable_to_import_file, file.getName()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to import file", e);
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unable_to_import_file, e.getMessage()));
|
||||
} catch (ImportParserException e) {
|
||||
Log.d(TAG, "Parser error: " + e.getMessage(), e);
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_parser_error, e.getMessage()));
|
||||
} catch (ImportAlreadyExistsException e) {
|
||||
Log.d(TAG, "Track already exists: " + e.getMessage(), e);
|
||||
sendResult(ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS, null, file, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendResult(int resultCode, ArrayList<Track.Id> trackId, DocumentFile file, String message) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID, trackId);
|
||||
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, file.getName());
|
||||
bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE, message);
|
||||
resultReceiver.send(resultCode, bundle);
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Create a new ResultReceive to receive results.
|
||||
* Your {@link #onReceiveResult} method will be called from the thread running <var>handler</var> if given, or from an arbitrary thread if null.
|
||||
*/
|
||||
public class ImportServiceResultReceiver extends ResultReceiver {
|
||||
|
||||
public static final int RESULT_CODE_ERROR = 0;
|
||||
public static final int RESULT_CODE_IMPORTED = 1;
|
||||
public static final int RESULT_CODE_ALREADY_EXISTS = 2;
|
||||
|
||||
public static final String RESULT_EXTRA_LIST_TRACK_ID = "result_track_id";
|
||||
public static final String RESULT_EXTRA_FILENAME = "result_extra_filename";
|
||||
public static final String RESULT_EXTRA_MESSAGE = "result_extra_message";
|
||||
|
||||
private final Receiver receiver;
|
||||
|
||||
public ImportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) {
|
||||
super(handler);
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
receiver.onReceiveResult(resultCode, resultData);
|
||||
}
|
||||
|
||||
public interface Receiver {
|
||||
void onReceiveResult(int resultCode, Bundle resultData);
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class ImportViewModel extends AndroidViewModel implements ImportServiceResultReceiver.Receiver {
|
||||
|
||||
private static final String TAG = ImportViewModel.class.getSimpleName();
|
||||
|
||||
private MutableLiveData<Summary> importData;
|
||||
private final ImportServiceResultReceiver resultReceiver;
|
||||
private final Summary summary;
|
||||
private boolean cancel = false;
|
||||
private final List<DocumentFile> filesToImport = new ArrayList<>();
|
||||
|
||||
public ImportViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
resultReceiver = new ImportServiceResultReceiver(new Handler(), this);
|
||||
summary = new Summary();
|
||||
}
|
||||
|
||||
LiveData<Summary> getImportData(List<DocumentFile> documentFiles) {
|
||||
if (importData == null) {
|
||||
importData = new MutableLiveData<>();
|
||||
loadData(documentFiles);
|
||||
}
|
||||
return importData;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
private void loadData(List<DocumentFile> documentFiles) {
|
||||
List<ArrayList<DocumentFile>> nestedFileList = documentFiles.stream()
|
||||
.map(FileUtils::getFiles)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<DocumentFile> fileList = new ArrayList<>();
|
||||
nestedFileList.forEach(fileList::addAll);
|
||||
|
||||
summary.totalCount = fileList.size();
|
||||
filesToImport.addAll(fileList);
|
||||
importNextFile();
|
||||
}
|
||||
|
||||
private void importNextFile() {
|
||||
if (cancel || filesToImport.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ImportService.enqueue(getApplication(), resultReceiver, filesToImport.get(0).getUri());
|
||||
filesToImport.remove(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
if (resultData == null) {
|
||||
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL");
|
||||
}
|
||||
|
||||
ArrayList<Track.Id> trackIds = resultData.getParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID);
|
||||
String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
|
||||
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
||||
|
||||
switch (resultCode) {
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ERROR -> {
|
||||
summary.errorCount++;
|
||||
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
|
||||
}
|
||||
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED -> {
|
||||
summary.importedTrackIds.addAll(trackIds);
|
||||
summary.successCount++;
|
||||
}
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS -> summary.existsCount++;
|
||||
default ->
|
||||
throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode);
|
||||
}
|
||||
|
||||
importData.postValue(summary);
|
||||
importNextFile();
|
||||
}
|
||||
|
||||
static class Summary {
|
||||
private int totalCount;
|
||||
private int successCount;
|
||||
private int existsCount;
|
||||
private int errorCount;
|
||||
private final ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
|
||||
private final ArrayList<String> fileErrors = new ArrayList<>();
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public int getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public int getExistsCount() {
|
||||
return existsCount;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public ArrayList<Track.Id> getImportedTrackIds() {
|
||||
return importedTrackIds;
|
||||
}
|
||||
|
||||
public ArrayList<String> getFileErrors() {
|
||||
return fileErrors;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return getSuccessCount() + getExistsCount() + getErrorCount();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return getTotalCount() == getCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class ImportWorker extends Worker {
|
||||
|
||||
private static final String TAG = ImportWorker.class.getSimpleName();
|
||||
|
||||
static final String URI_KEY = "DIRECTORY_URI_KEY";
|
||||
|
||||
static final String RESULT_SUCCESS_LIST_TRACKIDS_KEY = "RESULT_TRACK_IDS";
|
||||
static final String RESULT_URI_KEY = "RESULT_URI";
|
||||
static final String RESULT_MESSAGE_KEY = "RESULT_MESSAGE";
|
||||
static final String RESULT_FAILURE_IS_DUPLICATE = "RESULT_FAILURE_IS_DUPLICATE";
|
||||
|
||||
private final Uri uri;
|
||||
|
||||
public ImportWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
|
||||
uri = Uri.parse(getInputData().getString(URI_KEY));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
ArrayList<Track.Id> trackIds = new ArrayList<>();
|
||||
Context context = getApplicationContext();
|
||||
|
||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
|
||||
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
|
||||
TrackImporter trackImporter = new TrackImporter(context, new ContentProviderUtils(context), maxRecordingDistance, preventReimport);
|
||||
|
||||
Data.Builder data = new Data.Builder()
|
||||
.putString(RESULT_URI_KEY, uri.toString());
|
||||
|
||||
|
||||
String fileExtension = FileUtils.getExtension(uri);
|
||||
try {
|
||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new XMLImporter(new GPXTrackImporter(getApplicationContext(), trackImporter)).importFile(context, uri));
|
||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new XMLImporter(new KMLTrackImporter(getApplicationContext(), trackImporter)).importFile(context, uri));
|
||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||
trackIds.addAll(new KMZTrackImporter(context, trackImporter).importFile(uri));
|
||||
} else {
|
||||
Log.d(TAG, "Unsupported file format.");
|
||||
return Result.failure(data
|
||||
.putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_unsupported_format))
|
||||
.build());
|
||||
}
|
||||
|
||||
if (!trackIds.isEmpty()) {
|
||||
return Result.success(data
|
||||
.putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_file_imported, uri.toString())) //TODO unused?
|
||||
.putLongArray(RESULT_SUCCESS_LIST_TRACKIDS_KEY, trackIds.stream().mapToLong(Track.Id::id).toArray())
|
||||
.build());
|
||||
}
|
||||
|
||||
return Result.failure(data
|
||||
.putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_unable_to_import_file, uri.toString()))
|
||||
.build());
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to import file", e);
|
||||
return Result.failure(data
|
||||
.putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_unable_to_import_file, e.getMessage()))
|
||||
.build());
|
||||
} catch (ImportParserException e) {
|
||||
Log.d(TAG, "Parser error: " + e.getMessage(), e);
|
||||
return Result.failure(data
|
||||
.putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_parser_error, e.getMessage()))
|
||||
.build());
|
||||
} catch (ImportAlreadyExistsException e) {
|
||||
Log.d(TAG, "Track already exists: " + e.getMessage(), e);
|
||||
return Result.failure(data
|
||||
.putString(RESULT_MESSAGE_KEY, e.getMessage()) //TODO This is not used
|
||||
.putBoolean(RESULT_FAILURE_IS_DUPLICATE, true)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
//TODO Cancel does not abort current import
|
||||
//TODO Provide some intermediate progress (not only when a file got imported)
|
||||
public class Importer {
|
||||
|
||||
private final MutableLiveData<Summary> liveData = new MutableLiveData<>();
|
||||
private final Summary summary = new Summary();
|
||||
private final List<DocumentFile> filesToImport = new ArrayList<>();
|
||||
|
||||
private boolean cancel = false;
|
||||
|
||||
private final ImportActivity context;
|
||||
private final WorkManager workManager;
|
||||
|
||||
//TODO Add summary callback
|
||||
Importer(ImportActivity context, List<DocumentFile> documentFiles) {
|
||||
this.context = context;
|
||||
this.workManager = WorkManager.getInstance(context);
|
||||
|
||||
List<ArrayList<DocumentFile>> nestedFileList = documentFiles.stream()
|
||||
.map(FileUtils::getFiles)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<DocumentFile> fileList = new ArrayList<>();
|
||||
nestedFileList.forEach(fileList::addAll);
|
||||
|
||||
summary.totalCount = fileList.size();
|
||||
filesToImport.addAll(fileList);
|
||||
}
|
||||
|
||||
LiveData<Summary> getLiveData() {
|
||||
return liveData;
|
||||
}
|
||||
|
||||
void startImport() {
|
||||
liveData.postValue(summary);
|
||||
importNextFile();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
private void importNextFile() {
|
||||
if (cancel || filesToImport.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DocumentFile documentFile = filesToImport.get(0);
|
||||
|
||||
WorkRequest importRequest = new OneTimeWorkRequest.Builder(ImportWorker.class)
|
||||
.setInputData(new Data.Builder()
|
||||
.putString(ImportWorker.URI_KEY, documentFile.getUri().toString())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
workManager
|
||||
.getWorkInfoByIdLiveData(importRequest.getId())
|
||||
.observe(context, workInfo -> {
|
||||
if (workInfo != null) {
|
||||
WorkInfo.State state = workInfo.getState();
|
||||
if (state.isFinished()) {
|
||||
switch (state) {
|
||||
case SUCCEEDED -> {
|
||||
summary.importedTrackIds.addAll(
|
||||
Arrays.stream(workInfo.getOutputData().getLongArray(ImportWorker.RESULT_SUCCESS_LIST_TRACKIDS_KEY))
|
||||
.mapToObj(Track.Id::new)
|
||||
.toList());
|
||||
|
||||
summary.successCount++;
|
||||
}
|
||||
case FAILED -> {
|
||||
if (workInfo.getOutputData().getBoolean(ImportWorker.RESULT_FAILURE_IS_DUPLICATE, false)) {
|
||||
summary.existsCount++;
|
||||
} else {
|
||||
// Some error happened
|
||||
String errorMessage = workInfo.getOutputData().getString(ImportWorker.RESULT_MESSAGE_KEY);
|
||||
summary.fileErrors.add(context.getString(R.string.import_error_info, documentFile.getName(), errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
liveData.postValue(summary);
|
||||
importNextFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
workManager.enqueue(importRequest);
|
||||
filesToImport.remove(0);
|
||||
}
|
||||
|
||||
static class Summary {
|
||||
private int totalCount;
|
||||
private int successCount;
|
||||
private int existsCount;
|
||||
private final ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
|
||||
private final ArrayList<String> fileErrors = new ArrayList<>();
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public int getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public int getExistsCount() {
|
||||
return existsCount;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return fileErrors.size();
|
||||
}
|
||||
|
||||
public ArrayList<Track.Id> getImportedTrackIds() {
|
||||
return importedTrackIds;
|
||||
}
|
||||
|
||||
public ArrayList<String> getFileErrors() {
|
||||
return fileErrors;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return getSuccessCount() + getExistsCount() + getErrorCount();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return getTotalCount() == getCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user