From 9228b6b9462f20a177897fe3ff80071e84997dc1 Mon Sep 17 00:00:00 2001 From: michael Date: Wed, 1 Jul 2026 18:28:06 +0200 Subject: [PATCH 1/3] fix: initialize log2File in SyncWorker and EphemeralWorker log2File was declared but never assigned, so log2File?.log() was always a no-op even when logging was enabled. Initialize it in doWork() when logging is enabled. Also add null guard in Log2File for cases where getExternalFilesDir() returns null (e.g. on GrapheneOS). Co-Authored-By: Claude Sonnet 4.6 --- app/src/main/java/ca/pkay/rcloneexplorer/Log2File.java | 4 ++++ .../ca/pkay/rcloneexplorer/workmanager/EphemeralWorker.kt | 4 ++++ .../java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Log2File.java b/app/src/main/java/ca/pkay/rcloneexplorer/Log2File.java index 154595aa7..c107cdbb4 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Log2File.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Log2File.java @@ -22,6 +22,10 @@ public Log2File(Context context) { public void log(String message) { File path = context.getExternalFilesDir("logs"); + if (path == null) { + FLog.e(TAG, "log: external storage not available, cannot write log"); + return; + } File logFile = new File(path, "log.txt"); clearLogsIfTooBif(logFile); diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/EphemeralWorker.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/EphemeralWorker.kt index fe3d2c8a6..bb8306e05 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/EphemeralWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/EphemeralWorker.kt @@ -87,6 +87,10 @@ class EphemeralWorker (private var mContext: Context, workerParams: WorkerParame override fun doWork(): Result { + if (sIsLoggingEnabled) { + log2File = Log2File(mContext) + } + registerBroadcastReceivers() updateForegroundNotification(mNotificationManager?.updateNotification( 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 7056e7bae..1d5982a9d 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -90,6 +90,10 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) override fun doWork(): Result { + if (sIsLoggingEnabled) { + log2File = Log2File(mContext) + } + prepareNotifications() registerBroadcastReceivers() From b4b80daec5c61b663c1db485d69cac28441696fc Mon Sep 17 00:00:00 2001 From: michael Date: Wed, 1 Jul 2026 18:38:07 +0200 Subject: [PATCH 2/3] feat: log rclone INFO output to in-app sync log rclone was running at default log level (WARNING), so INFO messages like per-file operations and "There was nothing to transfer" were never visible. Added --log-level INFO to sync command and collect rclone INFO lines into a single SyncLog entry per sync run. Co-Authored-By: Claude Sonnet 4.6 --- .../java/ca/pkay/rcloneexplorer/Rclone.java | 2 +- .../rcloneexplorer/workmanager/SyncWorker.kt | 25 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java index b21b771b5..dc8a4a30d 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java @@ -688,7 +688,7 @@ public Process sync(RemoteItem remoteItem, String localPath, String remotePath, String localRemotePath = (remoteItem.isRemoteType(RemoteItem.LOCAL)) ? getLocalRemotePathPrefix(remoteItem, context) + "/" : ""; String remoteSection = (remotePath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + remotePath; - ArrayList defaultParameter = new ArrayList<>(Arrays.asList("--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--use-json-log")); + ArrayList defaultParameter = new ArrayList<>(Arrays.asList("--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--log-level", "INFO", "--use-json-log")); ArrayList directionParameter = new ArrayList<>(); if(useMD5Sum){ 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 1d5982a9d..bb5908f01 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -180,6 +180,7 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) SyncLog.info(mContext, mTitle, mContext.getString(R.string.operation_start_sync)) if (sRcloneProcess != null) { val localProcessReference = sRcloneProcess!! + val infoLines = StringBuilder() try { val reader = BufferedReader(InputStreamReader(localProcessReference.errorStream)) val iterator = reader.lineSequence().iterator() @@ -188,13 +189,22 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) try { val logline = JSONObject(line) //todo: migrate this to StatusObject, so that we can handle everything properly. - if (logline.getString("level") == "error") { - if (sIsLoggingEnabled) { - log2File?.log(line) + when (logline.getString("level")) { + "error" -> { + if (sIsLoggingEnabled) { + log2File?.log(line) + } + statusObject.parseLoglineToStatusObject(logline) + } + "warning" -> { + statusObject.parseLoglineToStatusObject(logline) + } + "info" -> { + val msg = logline.optString("msg", "") + if (msg.isNotEmpty()) { + infoLines.append(msg).append("\n") + } } - statusObject.parseLoglineToStatusObject(logline) - } else if (logline.getString("level") == "warning") { - statusObject.parseLoglineToStatusObject(logline) } updateForegroundNotification(mNotificationManager.updateSyncNotification( @@ -214,6 +224,9 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) } catch (e: IOException) { FLog.e(TAG, "onHandleIntent: error reading stdout", e) } + if (infoLines.isNotEmpty()) { + SyncLog.info(mContext, mTitle, infoLines.toString().trimEnd()) + } try { localProcessReference.waitFor() } catch (e: InterruptedException) { From 02030673f202d3585c80a0808460c7d188cab380 Mon Sep 17 00:00:00 2001 From: michael Date: Wed, 8 Jul 2026 19:05:50 +0200 Subject: [PATCH 3/3] fix: don't crash rclone with conflicting -vvv/--log-level flags --log-level INFO was always added, but conflicts with -vvv (added when debug logging is enabled), causing rclone to exit immediately with "Can't set -v and --log-level" -- the sync silently does nothing. Only add --log-level/--stats-log-level when verbose logging is off. Also capture NOTICE-level rclone output (transfer stats) and include it alongside INFO messages in the in-app sync log entry. Co-Authored-By: Claude Sonnet 4.6 --- .../java/ca/pkay/rcloneexplorer/Rclone.java | 8 +++++++- .../rcloneexplorer/workmanager/SyncWorker.kt | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java index dc8a4a30d..201f65520 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java @@ -688,7 +688,13 @@ public Process sync(RemoteItem remoteItem, String localPath, String remotePath, String localRemotePath = (remoteItem.isRemoteType(RemoteItem.LOCAL)) ? getLocalRemotePathPrefix(remoteItem, context) + "/" : ""; String remoteSection = (remotePath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + remotePath; - ArrayList defaultParameter = new ArrayList<>(Arrays.asList("--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--log-level", "INFO", "--use-json-log")); + boolean loggingEnabled = PreferenceManager + .getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.pref_key_logs), false); + ArrayList defaultParameter = new ArrayList<>(Arrays.asList("--transfers", "1", "--stats=1s", "--use-json-log")); + if (!loggingEnabled) { + defaultParameter.addAll(Arrays.asList("--stats-log-level", "NOTICE", "--log-level", "INFO")); + } ArrayList directionParameter = new ArrayList<>(); if(useMD5Sum){ 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 bb5908f01..5b7a7fb23 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -181,6 +181,7 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) if (sRcloneProcess != null) { val localProcessReference = sRcloneProcess!! val infoLines = StringBuilder() + var lastStats = "" try { val reader = BufferedReader(InputStreamReader(localProcessReference.errorStream)) val iterator = reader.lineSequence().iterator() @@ -205,6 +206,12 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) infoLines.append(msg).append("\n") } } + "notice" -> { + val msg = logline.optString("msg", "") + if (msg.isNotEmpty()) { + lastStats = msg + } + } } updateForegroundNotification(mNotificationManager.updateSyncNotification( @@ -224,8 +231,15 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) } catch (e: IOException) { FLog.e(TAG, "onHandleIntent: error reading stdout", e) } - if (infoLines.isNotEmpty()) { - SyncLog.info(mContext, mTitle, infoLines.toString().trimEnd()) + val detail = buildString { + if (infoLines.isNotEmpty()) append(infoLines.trimEnd()) + if (lastStats.isNotEmpty()) { + if (isNotEmpty()) append("\n\n") + append(lastStats) + } + } + if (detail.isNotEmpty()) { + SyncLog.info(mContext, mTitle, detail) } try { localProcessReference.waitFor()