From 985f37f6666ff6f25712f39b80f0b89ba1c37fb3 Mon Sep 17 00:00:00 2001 From: Fabian No ID Date: Wed, 15 Nov 2023 23:55:14 +0100 Subject: [PATCH] Easy option to keep deleted or modified files (via rclone's ""--backup-dir"): If enabled - instead of being deleted or overwritten - deleted or modified files (and their folder structure) will be moved to the folder "_backup_deleted/(YYYY-MM-dd)" in the sync/copy target. --- .../rcloneexplorer/Activities/TaskActivity.kt | 4 +++ .../Database/DatabaseHandler.kt | 7 ++++- .../rcloneexplorer/Database/DatabaseInfo.kt | 1 + .../java/ca/pkay/rcloneexplorer/Items/Task.kt | 5 +++- .../java/ca/pkay/rcloneexplorer/Rclone.java | 27 +++++++++++++++++-- .../rcloneexplorer/workmanager/SyncWorker.kt | 3 ++- app/src/main/res/layout/content_task.xml | 14 ++++++++++ app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 58 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Activities/TaskActivity.kt b/app/src/main/java/ca/pkay/rcloneexplorer/Activities/TaskActivity.kt index 124ce5b7..80ba781e 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Activities/TaskActivity.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Activities/TaskActivity.kt @@ -38,6 +38,7 @@ class TaskActivity : AppCompatActivity(), FolderSelectorCallback { private lateinit var switchWifi: Switch private lateinit var switchMD5sum: Switch + private lateinit var switchKeepDeleted: Switch private var existingTask: Task? = null @@ -98,6 +99,7 @@ class TaskActivity : AppCompatActivity(), FolderSelectorCallback { fab = findViewById(R.id.saveButton) switchWifi = findViewById(R.id.task_wifionly) switchMD5sum = findViewById(R.id.task_md5sum) + switchKeepDeleted = findViewById(R.id.task_keep_deleted) rcloneInstance = Rclone(this) dbHandler = DatabaseHandler(this) @@ -129,6 +131,7 @@ class TaskActivity : AppCompatActivity(), FolderSelectorCallback { findViewById(R.id.task_title_textfield).text = existingTask?.title switchWifi.isChecked = existingTask?.wifionly ?: false switchMD5sum.isChecked = existingTask?.md5sum ?: false + switchKeepDeleted.isChecked = existingTask?.keepDeleted ?: false prepareSyncDirectionDropdown() prepareLocal() prepareRemote() @@ -183,6 +186,7 @@ class TaskActivity : AppCompatActivity(), FolderSelectorCallback { taskToPopulate.wifionly = switchWifi.isChecked taskToPopulate.md5sum = switchMD5sum.isChecked + taskToPopulate.keepDeleted = switchKeepDeleted.isChecked // Verify if data is completed if (localPath.text.toString() == "") { diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseHandler.kt b/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseHandler.kt index 8578edaf..276f62dd 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseHandler.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseHandler.kt @@ -11,6 +11,7 @@ import ca.pkay.rcloneexplorer.Database.DatabaseInfo.Companion.SQL_CREATE_TABLES_ import ca.pkay.rcloneexplorer.Database.DatabaseInfo.Companion.SQL_CREATE_TABLE_TRIGGER import ca.pkay.rcloneexplorer.Database.DatabaseInfo.Companion.SQL_UPDATE_TASK_ADD_MD5 import ca.pkay.rcloneexplorer.Database.DatabaseInfo.Companion.SQL_UPDATE_TASK_ADD_WIFI +import ca.pkay.rcloneexplorer.Database.DatabaseInfo.Companion.SQL_UPDATE_TASK_ADD_KEEP_DELETED import ca.pkay.rcloneexplorer.Database.DatabaseInfo.Companion.SQL_UPDATE_TRIGGER_ADD_TYPE import ca.pkay.rcloneexplorer.Items.Task import ca.pkay.rcloneexplorer.Items.Trigger @@ -24,6 +25,7 @@ class DatabaseHandler(context: Context?) : sqLiteDatabase.execSQL(SQL_CREATE_TABLE_TRIGGER) sqLiteDatabase.execSQL(SQL_UPDATE_TASK_ADD_MD5) sqLiteDatabase.execSQL(SQL_UPDATE_TASK_ADD_WIFI) + sqLiteDatabase.execSQL(SQL_UPDATE_TASK_ADD_KEEP_DELETED) sqLiteDatabase.execSQL(SQL_UPDATE_TRIGGER_ADD_TYPE) } @@ -118,7 +120,8 @@ class DatabaseHandler(context: Context?) : Task.COLUMN_NAME_LOCAL_PATH, Task.COLUMN_NAME_SYNC_DIRECTION, Task.COLUMN_NAME_MD5SUM, - Task.COLUMN_NAME_WIFI_ONLY + Task.COLUMN_NAME_WIFI_ONLY, + Task.COLUMN_NAME_KEEP_DELETED ) private fun taskFromCursor(cursor: Cursor): Task { @@ -131,6 +134,7 @@ class DatabaseHandler(context: Context?) : task.direction = cursor.getInt(6) task.md5sum = getBoolean(cursor, 7) task.wifionly = getBoolean(cursor, 8) + task.keepDeleted = getBoolean(cursor, 9) return task } @@ -159,6 +163,7 @@ class DatabaseHandler(context: Context?) : values.put(Task.COLUMN_NAME_SYNC_DIRECTION, task.direction) values.put(Task.COLUMN_NAME_MD5SUM, task.md5sum) values.put(Task.COLUMN_NAME_WIFI_ONLY, task.wifionly) + values.put(Task.COLUMN_NAME_KEEP_DELETED, task.keepDeleted) return values } diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseInfo.kt b/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseInfo.kt index 50d33a3c..4798b8e4 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseInfo.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Database/DatabaseInfo.kt @@ -32,6 +32,7 @@ class DatabaseInfo { val SQL_UPDATE_TASK_ADD_MD5 = "ALTER TABLE ${Task.TABLE_NAME} ADD COLUMN ${Task.COLUMN_NAME_MD5SUM} INTEGER" val SQL_UPDATE_TASK_ADD_WIFI = "ALTER TABLE ${Task.TABLE_NAME} ADD COLUMN ${Task.COLUMN_NAME_WIFI_ONLY} INTEGER" + val SQL_UPDATE_TASK_ADD_KEEP_DELETED = "ALTER TABLE ${Task.TABLE_NAME} ADD COLUMN ${Task.COLUMN_NAME_KEEP_DELETED} INTEGER" val SQL_UPDATE_TRIGGER_ADD_TYPE = "ALTER TABLE ${Trigger.TABLE_NAME} ADD COLUMN ${Trigger.COLUMN_NAME_TYPE} INTEGER DEFAULT ${Trigger.TRIGGER_TYPE_SCHEDULE}" } diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Items/Task.kt b/app/src/main/java/ca/pkay/rcloneexplorer/Items/Task.kt index e1f80178..fb37ea5b 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Items/Task.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Items/Task.kt @@ -17,9 +17,10 @@ data class Task(var id: Long) { @JsonNames("syncDirection") var direction = 0 var md5sum = TASK_MD5SUM_DEFAULT var wifionly = TASK_WIFIONLY_DEFAULT + var keepDeleted = TASK_KEEP_DELETED_DEFAULT override fun toString(): String { - return "$title: $remoteId: $remoteType: $remotePath: $localPath: $direction" + return "$title: $remoteId: $remoteType: $remotePath: $localPath: $direction: $keepDeleted" } companion object { @@ -33,9 +34,11 @@ data class Task(var id: Long) { var COLUMN_NAME_SYNC_DIRECTION = "task_direction" var COLUMN_NAME_MD5SUM = "task_use_md5sum" var COLUMN_NAME_WIFI_ONLY = "task_use_only_wifi" + var COLUMN_NAME_KEEP_DELETED = "task_keep_deleted" const val TASK_MD5SUM_DEFAULT = false const val TASK_WIFIONLY_DEFAULT = false + const val TASK_KEEP_DELETED_DEFAULT = false fun fromString(json: String): Task { return Json.decodeFromString(json) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java index e89b08f1..9d50ac58 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java @@ -31,9 +31,11 @@ import java.io.OutputStreamWriter; import java.io.Reader; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -58,6 +60,7 @@ public class Rclone { private static final String TAG = "Rclone"; + private static final String backupPath = "_backup_deleted"; public static final int SYNC_DIRECTION_LOCAL_TO_REMOTE = 1; public static final int SYNC_DIRECTION_REMOTE_TO_LOCAL = 2; public static final int SERVE_PROTOCOL_HTTP = 1; @@ -663,10 +666,10 @@ public Process serve(int protocol, int port, boolean allowRemoteAccess, String u */ @Deprecated public Process sync(RemoteItem remoteItem, String localPath, String remotePath, int syncDirection) { - return sync(remoteItem, localPath, remotePath, syncDirection, false); + return sync(remoteItem, localPath, remotePath, syncDirection, false, false); } - public Process sync(RemoteItem remoteItem, String localPath, String remotePath, int syncDirection, boolean useMD5Sum) { + public Process sync(RemoteItem remoteItem, String localPath, String remotePath, int syncDirection, boolean useMD5Sum, boolean keepDeleted) { String[] command; String remoteName = remoteItem.getName(); String localRemotePath = (remoteItem.isRemoteType(RemoteItem.LOCAL)) ? getLocalRemotePathPrefix(remoteItem, context) + "/" : ""; @@ -679,19 +682,39 @@ public Process sync(RemoteItem remoteItem, String localPath, String remotePath, defaultParameter.add("--checksum"); } + String dateForBackupPath = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + String backupPathPlusDate = "/" + backupPath + "/" + dateForBackupPath + "/"; + if(keepDeleted) { + // Exclude backupPath because rclone requires it in order to allow a backup-dir inside the target path + defaultParameter.add("--filter"); + defaultParameter.add("- " + backupPath + "/**"); + defaultParameter.add("--backup-dir"); + } if (syncDirection == SyncDirectionObject.SYNC_LOCAL_TO_REMOTE) { + if(keepDeleted) { + defaultParameter.add(remoteSection + backupPathPlusDate); + } Collections.addAll(directionParameter, "sync", localPath, remoteSection); directionParameter.addAll(defaultParameter); command = createCommandWithOptions(directionParameter); } else if (syncDirection == SyncDirectionObject.SYNC_REMOTE_TO_LOCAL) { + if(keepDeleted) { + defaultParameter.add(localPath + backupPathPlusDate); + } Collections.addAll(directionParameter, "sync", remoteSection, localPath); directionParameter.addAll(defaultParameter); command = createCommandWithOptions(directionParameter); } else if (syncDirection == SyncDirectionObject.COPY_LOCAL_TO_REMOTE) { + if(keepDeleted) { + defaultParameter.add(remoteSection + backupPathPlusDate); + } Collections.addAll(directionParameter, "copy", localPath, remoteSection); directionParameter.addAll(defaultParameter); command = createCommandWithOptions(directionParameter); }else if (syncDirection == SyncDirectionObject.COPY_REMOTE_TO_LOCAL) { + if(keepDeleted) { + defaultParameter.add(localPath + backupPathPlusDate); + } Collections.addAll(directionParameter, "copy", remoteSection, localPath); directionParameter.addAll(defaultParameter); command = createCommandWithOptions(directionParameter); diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt index 2ef604b4..041ac361 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -158,7 +158,8 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) mTask.localPath, mTask.remotePath, mTask.direction, - mTask.md5sum + mTask.md5sum, + mTask.keepDeleted ) handleSync(mTitle) sendUploadFinishedBroadcast(remoteItem.name, mTask.remotePath) diff --git a/app/src/main/res/layout/content_task.xml b/app/src/main/res/layout/content_task.xml index 134420d8..0ba01ce8 100644 --- a/app/src/main/res/layout/content_task.xml +++ b/app/src/main/res/layout/content_task.xml @@ -83,6 +83,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/task_hint_wifi_ignore_defaults" /> + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b8a020d..850fb0ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -561,4 +561,6 @@ Battery Optimizations If the device is asleep, your tasks might be delayed when this permission is not granted. Starting Sync... + Backup deleted files + If enabled, deleted or overwritten files will be moved to the folder \"_backup_deleted/(date)\" in the target. This folder will never be synced, copied or deleted as long as this setting is enabled.