forked from upstream-mirrors/OpenTracks
Fix #1340 mass track deletion time
This commit is contained in:
@@ -84,8 +84,8 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
|
||||
/**
|
||||
* Called every time a track is deleted.
|
||||
*/
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
|
||||
if (trackDeleteServiceConnection != null) {
|
||||
trackDeleteServiceConnection.unbind(this);
|
||||
trackDeleteServiceConnection = null;
|
||||
onDeleteFinished();
|
||||
|
||||
@@ -232,9 +232,9 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||
super.onTrackDeleteStatus(deleteStatus);
|
||||
if (deleteStatus.isDeleted(trackId)) {
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
|
||||
super.onTrackDeleteStatus(deletionFinishedStatus);
|
||||
if (deletionFinishedStatus.isDeleted(trackId)) {
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,6 @@ public class ContentProviderUtils {
|
||||
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
||||
}
|
||||
|
||||
// Delete track last since it triggers a database vacuum call
|
||||
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new));
|
||||
}
|
||||
@@ -195,8 +194,6 @@ public class ContentProviderUtils {
|
||||
public void deleteTrack(Context context, @NonNull Track.Id trackId) {
|
||||
// Delete track folder resources.
|
||||
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
||||
|
||||
// Delete track last since it triggers a database vacuum call
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -39,6 +40,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* A {@link ContentProvider} that handles access to track points, tracks, and markers tables.
|
||||
@@ -53,6 +55,8 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
private static final String SQL_LIST_DELIMITER = ",";
|
||||
|
||||
private static final int TOTAL_DELETED_ROWS_VACUUM_THRESHOLD = 10000;
|
||||
|
||||
private final UriMatcher uriMatcher;
|
||||
|
||||
private SQLiteDatabase db;
|
||||
@@ -131,14 +135,12 @@ public class CustomContentProvider extends ContentProvider {
|
||||
@Override
|
||||
public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
|
||||
String table;
|
||||
boolean shouldVacuum = false;
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS:
|
||||
table = TrackPointsColumns.TABLE_NAME;
|
||||
break;
|
||||
case TRACKS:
|
||||
table = TracksColumns.TABLE_NAME;
|
||||
shouldVacuum = true;
|
||||
break;
|
||||
case MARKERS:
|
||||
table = MarkerColumns.TABLE_NAME;
|
||||
@@ -147,23 +149,40 @@ public class CustomContentProvider extends ContentProvider {
|
||||
throw new IllegalArgumentException("Unknown URL " + url);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Deleting table " + table);
|
||||
int count;
|
||||
Log.w(TAG, "Deleting from table " + table);
|
||||
int totalChangesBefore = getTotalChanges();
|
||||
int deletedRowsFromTable;
|
||||
try {
|
||||
db.beginTransaction();
|
||||
count = db.delete(table, where, selectionArgs);
|
||||
deletedRowsFromTable = db.delete(table, where, selectionArgs);
|
||||
Log.i(TAG, "Deleted " + deletedRowsFromTable + " rows of table " + table);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(url, null, false);
|
||||
|
||||
if (shouldVacuum) {
|
||||
// If a potentially large amount of data was deleted, reclaim its space.
|
||||
Log.i(TAG, "Vacuuming the database.");
|
||||
int totalChanges = getTotalChanges() - totalChangesBefore;
|
||||
Log.i(TAG, "Deleted " + totalChanges + " total rows from database");
|
||||
|
||||
PreferencesUtils.addTotalRowsDeleted(totalChanges);
|
||||
int totalRowsDeleted = PreferencesUtils.getTotalRowsDeleted();
|
||||
if (totalRowsDeleted > TOTAL_DELETED_ROWS_VACUUM_THRESHOLD) {
|
||||
Log.i(TAG, "TotalRowsDeleted " + totalRowsDeleted + ", starting to vacuum the database.");
|
||||
db.execSQL("VACUUM");
|
||||
PreferencesUtils.resetTotalRowsDeleted();
|
||||
}
|
||||
return count;
|
||||
|
||||
return deletedRowsFromTable;
|
||||
}
|
||||
|
||||
private int getTotalChanges() {
|
||||
int totalCount;
|
||||
try (Cursor cursor = db.rawQuery("SELECT total_changes()", null)) {
|
||||
cursor.moveToNext();
|
||||
totalCount = cursor.getInt(0);
|
||||
}
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,7 @@ import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
@@ -290,4 +291,5 @@ public class TrackImporter {
|
||||
public void cleanImport() {
|
||||
contentProviderUtils.deleteTracks(context, trackIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class TrackDeleteService extends Service {
|
||||
|
||||
@@ -31,7 +33,7 @@ public class TrackDeleteService extends Service {
|
||||
|
||||
private final Binder binder = new Binder();
|
||||
private ExecutorService serviceExecutor;
|
||||
private MutableLiveData<DeleteStatus> deleteResultObservable;
|
||||
private MutableLiveData<DeletionFinishedStatus> deleteResultObservable;
|
||||
private NotificationManager notificationManager;
|
||||
private NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
@@ -59,22 +61,18 @@ public class TrackDeleteService extends Service {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void deleteTracks(@NonNull ArrayList<Track.Id> trackIds) {
|
||||
sendResult(null,0, trackIds.size());
|
||||
private void deleteTracks(@NonNull List<Track.Id> trackIds) {
|
||||
serviceExecutor.execute(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
for (int i = 0; i < trackIds.size(); i++) {
|
||||
updateNotification(i + 1, trackIds.size());
|
||||
contentProviderUtils.deleteTrack(this, trackIds.get(i));
|
||||
sendResult(trackIds.get(i), i + 1, trackIds.size());
|
||||
}
|
||||
contentProviderUtils.deleteTracks(this, trackIds);
|
||||
sendResult(trackIds);
|
||||
stopSelf();
|
||||
});
|
||||
}
|
||||
|
||||
private void sendResult(Track.Id trackId, int deletes, int total) {
|
||||
private void sendResult(List<Track.Id> trackIds) {
|
||||
if (deleteResultObservable != null) {
|
||||
deleteResultObservable.postValue(new DeleteStatus(trackId, deletes, total));
|
||||
deleteResultObservable.postValue(new DeletionFinishedStatus(trackIds));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +81,7 @@ public class TrackDeleteService extends Service {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public LiveData<DeleteStatus> getDeletingStatusObservable() {
|
||||
public LiveData<DeletionFinishedStatus> getDeletingStatusObservable() {
|
||||
return deleteResultObservable;
|
||||
}
|
||||
|
||||
@@ -106,25 +104,13 @@ public class TrackDeleteService extends Service {
|
||||
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
|
||||
notificationBuilder
|
||||
.setContentTitle(this.getString(R.string.track_delete_progress_message))
|
||||
.setContentText(this.getString(R.string.track_delete_progress, 0, tracksToDelete))
|
||||
.setContentText(this.getString(R.string.track_delete_number_of_tracks, tracksToDelete))
|
||||
.setSmallIcon(R.drawable.ic_logo_color_24dp)
|
||||
.setProgress(tracksToDelete, 0, false);
|
||||
.setProgress(0, 0, true);
|
||||
|
||||
startForeground(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates notification progress.
|
||||
*
|
||||
* @param progress number of tracks already deleted.
|
||||
* @param total total of tracks to be deleted.
|
||||
*/
|
||||
private void updateNotification(int progress, int total) {
|
||||
notificationBuilder.setProgress(total, progress, false);
|
||||
notificationBuilder.setContentText(this.getString(R.string.track_delete_progress, progress, total));
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
||||
public class Binder extends android.os.Binder {
|
||||
|
||||
private Binder() {
|
||||
@@ -136,36 +122,25 @@ public class TrackDeleteService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeleteStatus {
|
||||
private final int progress;
|
||||
private final int max;
|
||||
private final Track.Id trackId;
|
||||
public static class DeletionFinishedStatus {
|
||||
private final List<Track.Id> trackIds;
|
||||
|
||||
/**
|
||||
* @param trackId Track.Id just deleted if any.
|
||||
* @param progress number of deletes.
|
||||
* @param max total of deletes to be done.
|
||||
* @param trackIds List of deleted Track.Ids.
|
||||
*/
|
||||
private DeleteStatus(@Nullable Track.Id trackId, int progress, int max) {
|
||||
this.trackId = trackId;
|
||||
this.progress = progress;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return progress == max;
|
||||
private DeletionFinishedStatus(@Nullable List<Track.Id> trackIds) {
|
||||
this.trackIds = trackIds;
|
||||
}
|
||||
|
||||
public boolean isDeleted(Track.Id trackId) {
|
||||
return this.trackId != null && this.trackId.equals(trackId);
|
||||
return this.trackIds != null && this.trackIds.contains(trackId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeleteStatus{" +
|
||||
"number of deletes=" + progress +
|
||||
", total=" + max +
|
||||
"trackIds=" + trackIds +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,4 +826,17 @@ public class PreferencesUtils {
|
||||
return getString(R.string.show_on_map_format_key, IntentDashboardUtils.PREFERENCE_ID_ASK);
|
||||
}
|
||||
|
||||
public static int getTotalRowsDeleted() {
|
||||
return getInt(R.string.total_rows_deleted_key, 0);
|
||||
}
|
||||
|
||||
public static void addTotalRowsDeleted(final int totalRowsDeletedToAdd) {
|
||||
int newTotalRowsDeleted = getTotalRowsDeleted() + totalRowsDeletedToAdd;
|
||||
setInt(R.string.total_rows_deleted_key, newTotalRowsDeleted);
|
||||
}
|
||||
|
||||
public static void resetTotalRowsDeleted() {
|
||||
setInt(R.string.total_rows_deleted_key, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user