diff --git a/README.md b/README.md index 096063b10..66a98b698 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Round Sync - Rclone for Android [![license: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/gulp79/Round-Sync/blob/master/LICENSE) [![GitHub release](https://img.shields.io/github/v/release/gulp79/Round-Sync?include_prereleases)](https://github.com/gulp79/Round-Sync/releases/latest) -![Downloads](https://img.shields.io/github/downloads/gulp79/Round-Sync/total) ![Latest Downloads](https://img.shields.io/github/downloads/gulp79/Round-Sync/latest/total) +![Latest Downloads](https://img.shields.io/github/downloads/gulp79/Round-Sync/total) > ⚠️ **Unofficial Fork Disclaimer** diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java index b21b771b5..a088ca73d 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java @@ -502,7 +502,7 @@ public Process configCreate(List options) { public Process configUpdate(List options) { return configCreate(options); } - + public Process config(String task, List options) { String[] command = createCommand("config", task); String[] opt = options.toArray(new String[0]); @@ -558,7 +558,7 @@ public HashMap getConfig(String name) { options.put(RCLONE_CONFIG_NAME_KEY, name); return options; - + } public Process configInteractive() throws IOException { @@ -688,7 +688,8 @@ 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("-v", "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--use-json-log")); + ArrayList directionParameter = new ArrayList<>(); if(useMD5Sum){ @@ -751,7 +752,7 @@ public Process downloadFile(RemoteItem remote, FileItem downloadItem, String dow localFilePath = encodePath(localFilePath); - command = createCommandWithOptions("copy", remoteFilePath, localFilePath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--use-json-log"); + command = createCommandWithOptions("copy", remoteFilePath, localFilePath, "-v", "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--use-json-log"); String[] env = getRcloneEnv(); try { @@ -783,7 +784,7 @@ public Process uploadFile(RemoteItem remote, String uploadPath, String uploadFil path = (uploadPath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + uploadPath; } - command = createCommandWithOptions("copy", uploadFile, path, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--use-json-log"); + command = createCommandWithOptions("copy", uploadFile, path, "-v", "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE", "--use-json-log"); String[] env = getRcloneEnv(); try { diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java b/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java index 5d6061399..26e2daddd 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java @@ -195,6 +195,10 @@ private void showFileMenu(View view, final Task task) { createShortcut(context, task); } break; + case R.id.action_view_log: + showLogDialog(task); + break; + default: return false; } @@ -233,6 +237,44 @@ public static class ViewHolder extends RecyclerView.ViewHolder { } } + private void showLogDialog(Task task) { + // Kotlin'deki task.title değişkeni Java'da getTitle() olarak çağrılır + String taskName = task.getTitle(); + java.io.File logFile = new java.io.File(context.getExternalFilesDir(null).getAbsolutePath() + "/logs/" + taskName + "_log.txt"); + + if (!logFile.exists()) { + android.widget.Toast.makeText(context, "Log henüz oluşmamış: " + logFile.getName(), android.widget.Toast.LENGTH_LONG).show(); + return; + } + + try { + StringBuilder logContent = new StringBuilder(); + java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(logFile)); + String line; + int lineCount = 0; + + // Uygulamanın donmaması için logun ilk 100 satırını okuyoruz + while ((line = reader.readLine()) != null && lineCount < 100) { + logContent.append(line).append("\n\n"); + lineCount++; + } + reader.close(); + + if (lineCount >= 100) { + logContent.append("... (Log çok uzun olduğu için kesildi) ..."); + } + + new androidx.appcompat.app.AlertDialog.Builder(context) + .setTitle("Log: " + taskName) + .setMessage(logContent.toString()) + .setPositiveButton("Kapat", null) + .show(); + + } catch (Exception e) { + android.widget.Toast.makeText(context, "Log okunamadı: " + e.getMessage(), android.widget.Toast.LENGTH_SHORT).show(); + } + } + private static void createShortcut(Context c, Task task) { Intent intent = new Intent(TASK_SYNC_ACTION, Uri.EMPTY, c, ShortcutServiceActivity.class); intent.setAction(TASK_SYNC_ACTION); @@ -252,3 +294,5 @@ private static void createShortcut(Context c, Task task) { } } + + diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TriggerRecyclerViewAdapter.java b/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TriggerRecyclerViewAdapter.java index 00022eb27..1688f6b31 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TriggerRecyclerViewAdapter.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TriggerRecyclerViewAdapter.java @@ -37,7 +37,9 @@ import ca.pkay.rcloneexplorer.R; import ca.pkay.rcloneexplorer.Services.TriggerService; import es.dmoral.toasty.Toasty; - +// #---Alps Begin +import androidx.appcompat.app.AlertDialog; +// #---Alps End public class TriggerRecyclerViewAdapter extends RecyclerView.Adapter{ private List triggers; @@ -162,6 +164,7 @@ public void deleteTrigger(Trigger trigger) { } } +// #---Alps Begin private void showFileMenu(View view, final Trigger trigger) { PopupMenu popupMenu = new PopupMenu(context, view); popupMenu.getMenuInflater().inflate(R.menu.trigger_item_menu, popupMenu.getMenu()); @@ -176,6 +179,9 @@ private void showFileMenu(View view, final Trigger trigger) { case R.id.action_delete_trigger: deleteTrigger(trigger); break; + case R.id.action_view_log: // YENİ EKLENEN SATIR + showLogDialog(trigger); + break; default: return false; } @@ -184,6 +190,35 @@ private void showFileMenu(View view, final Trigger trigger) { popupMenu.show(); } + private void showLogDialog(Trigger trigger) { + // Dosya yolunu SyncWorker/SyncService içinde belirttiğin yol ile birebir aynı yapmalısın + java.io.File logDir = new java.io.File(context.getExternalFilesDir(null), "logs"); + java.io.File logFile = new java.io.File(logDir, trigger.getTitle() + "_log.txt"); + + if (logFile.exists()) { + StringBuilder logContent = new StringBuilder(); + try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(logFile))) { + String line; + while ((line = br.readLine()) != null) { + logContent.append(line).append("\n"); + } + } catch (java.io.IOException e) { + e.printStackTrace(); + android.widget.Toast.makeText(context, "Log dosyası okunurken hata oluştu.", android.widget.Toast.LENGTH_SHORT).show(); + return; + } + + // Log içeriğini Alert Dialog ile ekranda göster + new androidx.appcompat.app.AlertDialog.Builder(context) + .setTitle(trigger.getTitle() + " Log") + .setMessage(logContent.toString()) + .setPositiveButton("Kapat", null) + .show(); + } else { + android.widget.Toast.makeText(context, "Bu işlem için henüz log oluşturulmamış.", android.widget.Toast.LENGTH_SHORT).show(); + } + } +// #---Alps End public static class ScheduleViewHolder extends RecyclerView.ViewHolder { final View mView; diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.kt b/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.kt index 3d9701475..8d5322841 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.kt @@ -24,7 +24,6 @@ class SyncService: IntentService("ca.pkay.rcexplorer.SYNC_SERCVICE"){ // Todo: Allow SyncWorker to run in silent mode, or remove this! val silentRun = intent.getBooleanExtra("notification", true) - if (action.equals("START_TASK")) { val db = DatabaseHandler(this) for (task in db.allTasks) { @@ -34,4 +33,4 @@ class SyncService: IntentService("ca.pkay.rcexplorer.SYNC_SERCVICE"){ } } } -} \ No newline at end of file +} 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 b18306aa7..187ec36f4 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -7,7 +7,9 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.net.wifi.WifiManager +import android.widget.Toast import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.preference.PreferenceManager import androidx.work.ForegroundInfo @@ -31,49 +33,38 @@ import kotlinx.serialization.json.Json import org.json.JSONException import org.json.JSONObject import java.io.BufferedReader +import java.io.File import java.io.IOException import java.io.InputStreamReader import java.io.InterruptedIOException +import java.text.SimpleDateFormat +import java.util.Locale import java.util.Random -class SyncWorker (private var mContext: Context, workerParams: WorkerParameters): Worker(mContext, workerParams) { +class SyncWorker (private val mContext: Context, workerParams: WorkerParameters): Worker(mContext, workerParams) { companion object { const val TASK_ID = "TASK_ID" const val TASK_EPHEMERAL = "TASK_EPHEMERAL" private const val TAG = "SyncWorker" - - //those Extras do not follow the above schema, because they are exposed to external applications - //That means shorter values make it easier to use. There is no other technical reason const val TASK_SYNC_ACTION = "START_TASK" const val TASK_CANCEL_ACTION = "CANCEL_TASK" const val EXTRA_TASK_ID = "task" - - // Todo: Allow SyncWorker to run in silent mode, or remove this! const val EXTRA_TASK_SILENT = "notification" } - - internal enum class FAILURE_REASON { NO_FAILURE, NO_UNMETERED, NO_CONNECTION, RCLONE_ERROR, CONNECTIVITY_CHANGED, CANCELLED, NO_TASK } - // Objects - private var mRclone = Rclone(mContext) - private var mDatabase = DatabaseHandler(mContext) - private var mNotificationManager = SyncServiceNotifications(mContext) + private val mRclone = Rclone(mContext) + private val mDatabase = DatabaseHandler(mContext) + private val mNotificationManager = SyncServiceNotifications(mContext) private val mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext) - private var log2File: Log2File? = null - - - - // States private val sIsLoggingEnabled = mPreferences.getBoolean(getString(R.string.pref_key_logs), false) private var sConnectivityChanged = false - private var sRcloneProcess: Process? = null private val statusObject = StatusObject(mContext) private var failureReason = FAILURE_REASON.NO_FAILURE @@ -81,41 +72,30 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) private var silentRun = false private val ongoingNotificationID = Random().nextInt() - - // Task private lateinit var mTask: Task private var mTitle: String = mContext.getString(R.string.sync_service_notification_startingsync) - - override fun doWork(): Result { - prepareNotifications() registerBroadcastReceivers() updateForegroundNotification(mNotificationManager.updateSyncNotification( - mTitle, - mTitle, - ArrayList(), - 0, - ongoingNotificationID + mTitle, mTitle, ArrayList(), 0, ongoingNotificationID )) - var ephemeralTask: Task? = null - - if(inputData.keyValueMap.containsKey(TASK_ID)){ + if (inputData.keyValueMap.containsKey(TASK_ID)) { val id = inputData.getLong(TASK_ID, -1) ephemeralTask = mDatabase.getTask(id) } - if(inputData.keyValueMap.containsKey(TASK_EPHEMERAL)){ + if (inputData.keyValueMap.containsKey(TASK_EPHEMERAL)) { val taskString = inputData.getString(TASK_EPHEMERAL) ?: "" - if(taskString.isNotEmpty()) { + if (taskString.isNotEmpty()) { try { ephemeralTask = Json.decodeFromString(taskString) } catch (e: Exception) { - log("Could not deserialize") + log("Could not deserialize task layout") } } } @@ -129,8 +109,6 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) mNotificationManager.cancelSyncNotification(ongoingNotificationID) return Result.failure() } - - // Indicate whether the work finished successfully with the Result return Result.success() } @@ -144,7 +122,11 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) private fun finishWork() { sRcloneProcess?.destroy() - mContext.unregisterReceiver(connectivityChangeBroadcastReceiver) + try { + mContext.unregisterReceiver(connectivityChangeBroadcastReceiver) + } catch (e: Exception) { + // Güvenli geçiş + } postSync() } @@ -156,17 +138,14 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) if (mTask.title == "") { mTitle = mTask.remotePath } - if(arePreconditionsMet()) { - val taskFilter = if(mTask.filterId != null ) mDatabase.getFilter(mTask.filterId!!) else null; + + if (arePreconditionsMet()) { + val taskFilter = if (mTask.filterId != null) mDatabase.getFilter(mTask.filterId!!) else null val taskFilterList = taskFilter?.getFilters() ?: ArrayList() + sRcloneProcess = mRclone.sync( - remoteItem, - mTask.localPath, - mTask.remotePath, - mTask.direction, - mTask.md5sum, - taskFilterList, - mTask.deleteExcluded + remoteItem, mTask.localPath, mTask.remotePath, mTask.direction, + mTask.md5sum, taskFilterList, mTask.deleteExcluded ) handleSync(mTitle) sendUploadFinishedBroadcast(remoteItem.name, mTask.remotePath) @@ -175,64 +154,124 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) private fun handleSync(title: String) { SyncLog.info(mContext, mTitle, mContext.getString(R.string.operation_start_sync)) - if (sRcloneProcess != null) { - val localProcessReference = sRcloneProcess!! - try { - val reader = BufferedReader(InputStreamReader(localProcessReference.errorStream)) - val iterator = reader.lineSequence().iterator() - while(iterator.hasNext()) { - val line = iterator.next() - 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) + + val logDirPath = mContext.getExternalFilesDir(null)?.absolutePath + "/logs" + val logDir = File(logDirPath) + if (!logDir.exists()) logDir.mkdirs() + + val logFile = File(logDir, "${title}_log.txt") + var finalStatsMsg = "" + + sRcloneProcess?.let { localProcessReference -> + logFile.bufferedWriter().use { logWriter -> + try { + val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()) + logWriter.write("${sdf.format(java.util.Date())} INFO : Senkronizasyon basladi: $title\n\n") + + val reader = BufferedReader(InputStreamReader(localProcessReference.errorStream)) + val iterator = reader.lineSequence().iterator() + while (iterator.hasNext()) { + val line = iterator.next() + try { + val logline = JSONObject(line) + val level = logline.optString("level").lowercase(Locale.getDefault()) + val msg = logline.optString("msg").trim() + val obj = logline.optString("object").trim() + + // Dosya transferi, silme ve kopyalama olaylarını yakala + val isFileAction = msg.contains("Copied") || + msg.contains("Deleted") || + msg.contains("Updated") || + msg.contains("Moved") + + if (level == "info" || (level == "notice" && isFileAction)) { + val itemText = if (obj.isNotEmpty()) "$obj: $msg" else msg + logWriter.write("INFO : $itemText\n") + } else if (level == "error") { + val itemText = if (obj.isNotEmpty()) "$obj: $msg" else msg + logWriter.write("ERROR : $itemText\n") + } else if (level == "notice" && msg.contains("Transferred:")) { + finalStatsMsg = msg + } else if (level == "notice" && msg.isNotEmpty()) { + logWriter.write("NOTICE: $msg\n") + } + + if (level == "error") { + if (sIsLoggingEnabled) log2File?.log(line) + statusObject.parseLoglineToStatusObject(logline) + } else if (level == "warning" || logline.has("stats")) { + statusObject.parseLoglineToStatusObject(logline) } - statusObject.parseLoglineToStatusObject(logline) - } else if (logline.getString("level") == "warning" || logline.has("stats")) { - statusObject.parseLoglineToStatusObject(logline) + + updateForegroundNotification(mNotificationManager.updateSyncNotification( + title, statusObject.notificationContent, statusObject.notificationBigText, + statusObject.notificationPercent, ongoingNotificationID + )) + } catch (e: JSONException) { + FLog.e(TAG, "SyncService-Error processing line JSON: $line") } + } + + - updateForegroundNotification(mNotificationManager.updateSyncNotification( - title, - statusObject.notificationContent, - statusObject.notificationBigText, - statusObject.notificationPercent, - ongoingNotificationID - )) - } catch (e: JSONException) { - FLog.e(TAG, "SyncService-Error: the offending line: $line") - //FLog.e(TAG, "onHandleIntent: error reading json", e) + if (finalStatsMsg.isNotEmpty()) { + logWriter.write("\n--- ISLEM OZETI ---\n$finalStatsMsg\n") } + val endSdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()) + logWriter.write("\n${endSdf.format(java.util.Date())} INFO : Islem sona erdi.\n") + + } catch (e: InterruptedIOException) { + FLog.e(TAG, "onHandleIntent: I/O interrupted, stream closed", e) + } catch (e: IOException) { + FLog.e(TAG, "onHandleIntent: error reading stdout", e) } - } catch (e: InterruptedIOException) { - FLog.e(TAG, "onHandleIntent: I/O interrupted, stream closed", e) - } catch (e: IOException) { - FLog.e(TAG, "onHandleIntent: error reading stdout", e) } try { localProcessReference.waitFor() } catch (e: InterruptedException) { FLog.e(TAG, "onHandleIntent: error waiting for process", e) } - } else { - log("Sync: No Rclone Process!") - } + } ?: log("Sync: No Rclone Process runtime detected!") + mNotificationManager.cancelSyncNotification(ongoingNotificationID) } + private fun showLogDialog(task: Task) { + var taskName = task.title + if (taskName.isNullOrEmpty()) { + taskName = task.remotePath + } - private fun postSync() { - if (endNotificationAlreadyPosted) { + val logFile = File(mContext.getExternalFilesDir(null)?.absolutePath + "/logs/" + taskName + "_log.txt") + if (!logFile.exists()) { + Toast.makeText(mContext, "Henüz log dosyası oluşturulmamış.", Toast.LENGTH_SHORT).show() return } - if (silentRun) { - return + + try { + val logContent = StringBuilder() + BufferedReader(java.io.FileReader(logFile)).use { reader -> + var line: String? + while (reader.readLine().also { line = it } != null) { + logContent.append(line).append("\n") + } + } + + AlertDialog.Builder(mContext) + .setTitle("Log: $taskName") + .setMessage(logContent.toString()) + .setPositiveButton("Kapat", null) + .show() + } catch (e: Exception) { + Toast.makeText(mContext, "Log okunamadı: ${e.message}", Toast.LENGTH_SHORT).show() } + } - val notificationId = System.currentTimeMillis().toInt() + private fun postSync() { + if (endNotificationAlreadyPosted || silentRun) return + val notificationId = System.currentTimeMillis().toInt() var content = mContext.getString(R.string.operation_failed_unknown, mTitle) + when (failureReason) { FAILURE_REASON.NO_FAILURE -> { showSuccessNotification(notificationId) @@ -244,21 +283,11 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) endNotificationAlreadyPosted = true return } - FAILURE_REASON.NO_TASK -> { - content = getString(R.string.operation_failed_notask) - } - FAILURE_REASON.CONNECTIVITY_CHANGED -> { - content = mContext.getString(R.string.operation_failed_data_change, mTitle) - } - FAILURE_REASON.NO_UNMETERED -> { - content = mContext.getString(R.string.operation_failed_no_unmetered, mTitle) - } - FAILURE_REASON.NO_CONNECTION -> { - content = mContext.getString(R.string.operation_failed_no_connection, mTitle) - } - FAILURE_REASON.RCLONE_ERROR -> { - content = mContext.getString(R.string.operation_failed_unknown_rclone_error, mTitle) - } + FAILURE_REASON.NO_TASK -> content = getString(R.string.operation_failed_notask) + FAILURE_REASON.CONNECTIVITY_CHANGED -> content = mContext.getString(R.string.operation_failed_data_change, mTitle) + FAILURE_REASON.NO_UNMETERED -> content = mContext.getString(R.string.operation_failed_no_unmetered, mTitle) + FAILURE_REASON.NO_CONNECTION -> content = mContext.getString(R.string.operation_failed_no_connection, mTitle) + FAILURE_REASON.RCLONE_ERROR -> content = mContext.getString(R.string.operation_failed_unknown_rclone_error, mTitle) } followupTask(mTask.onFailFollowup) showFailNotification(notificationId, content) @@ -268,70 +297,36 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) private fun showCancelledNotification(notificationId: Int) { SyncLog.info(mContext, mTitle, mContext.getString(R.string.operation_failed_cancelled)) - mNotificationManager.showCancelledNotificationOrReport( - mTitle, - notificationId, - mTask.id - ) + mNotificationManager.showCancelledNotificationOrReport(mTitle, notificationId, mTask.id) } private fun showSuccessNotification(notificationId: Int) { - //Todo: Show sync-errors in notification. Also see line 169 - var message = generateSuccessMessage(statusObject) - mNotificationManager.showSuccessNotificationOrReport( - mTitle, - message, - notificationId - ) - - message += """ - - Est. Speed: ${statusObject.getEstimatedAverageSpeed()} - Avg. Speed: ${statusObject.getLastItemAverageSpeed()} - """.trimIndent() + mNotificationManager.showSuccessNotificationOrReport(mTitle, message, notificationId) + message += "\n\nEst. Speed: ${statusObject.getEstimatedAverageSpeed()}\nAvg. Speed: ${statusObject.getLastItemAverageSpeed()}" SyncLog.info(mContext, mContext.getString(R.string.operation_success, mTitle), message) } - // this is currently only a useless mapper. It is supposed to keep this worker in sync with the ephemeral one. - // when they are merged eventually, this can be easily extracted. private fun generateSuccessMessage(statusObject: StatusObject): String { var message = mContext.resources.getQuantityString( - R.plurals.operation_success_description, - statusObject.getTotalTransfers(), - mTitle, - statusObject.getTotalSize(), - statusObject.getTotalTransfers() + R.plurals.operation_success_description, statusObject.getTotalTransfers(), + mTitle, statusObject.getTotalSize(), statusObject.getTotalTransfers() ) if (statusObject.getTotalTransfers() == 0) { message = mContext.resources.getString(R.string.operation_success_description_zero) } if (statusObject.getDeletions() > 0) { - message += """ - - ${ - mContext.getString( - R.string.operation_success_description_deletions_prefix, - statusObject.getDeletions() - ) - } - """.trimIndent() + message += "\n\n${mContext.getString(R.string.operation_success_description_deletions_prefix, statusObject.getDeletions())}" } return message } private fun showFailNotification(notificationId: Int, content: String, wasCancelled: Boolean = false) { var text = content - //Todo: check if we should also add errors on success statusObject.printErrors() val errors = statusObject.getAllErrorMessages() if (errors.isNotEmpty()) { - text += """ - - - - ${statusObject.getAllErrorMessages()} - """.trimIndent() + text += "\n\n\n${statusObject.getAllErrorMessages()}" } var notifyTitle = mContext.getString(R.string.operation_failed) @@ -339,12 +334,7 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) notifyTitle = mContext.getString(R.string.operation_failed_cancelled) } SyncLog.error(mContext, notifyTitle, "$mTitle: $text") - mNotificationManager.showFailedNotificationOrReport( - mTitle, - text, - notificationId, - mTask.id - ) + mNotificationManager.showFailedNotificationOrReport(mTitle, text, notificationId, mTask.id) } private fun arePreconditionsMet(): Boolean { @@ -356,60 +346,43 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) failureReason = FAILURE_REASON.NO_CONNECTION return false } - return true } private fun prepareNotifications() { - GenericSyncNotification(mContext).setNotificationChannel( - SyncServiceNotifications.CHANNEL_ID, - getString(R.string.sync_service_notification_channel_title), - getString(R.string.sync_service_notification_channel_description), - GROUP_ID, - getString(R.string.sync_service_notification_group) + SyncServiceNotifications.CHANNEL_ID, getString(R.string.sync_service_notification_channel_title), + getString(R.string.sync_service_notification_channel_description), GROUP_ID, getString(R.string.sync_service_notification_group) ) GenericSyncNotification(mContext).setNotificationChannel( - SyncServiceNotifications.CHANNEL_SUCCESS_ID, - getString(R.string.sync_service_notification_channel_success_title), - getString(R.string.sync_service_notification_channel_success_description), - GROUP_ID, - getString(R.string.sync_service_notification_group) + SyncServiceNotifications.CHANNEL_SUCCESS_ID, getString(R.string.sync_service_notification_channel_success_title), + getString(R.string.sync_service_notification_channel_success_description), GROUP_ID, getString(R.string.sync_service_notification_group) ) GenericSyncNotification(mContext).setNotificationChannel( - SyncServiceNotifications.CHANNEL_FAIL_ID, - getString(R.string.sync_service_notification_channel_fail_title), - getString(R.string.sync_service_notification_channel_fail_description), - GROUP_ID, - getString(R.string.sync_service_notification_group) + SyncServiceNotifications.CHANNEL_FAIL_ID, getString(R.string.sync_service_notification_channel_fail_title), + getString(R.string.sync_service_notification_channel_fail_description), GROUP_ID, getString(R.string.sync_service_notification_group) ) GenericSyncNotification(mContext).setNotificationChannel( - ReportNotifications.CHANNEL_REPORT_ID, - getString(R.string.sync_service_notification_channel_report_title), - getString(R.string.sync_service_notification_channel_report_description), - GROUP_ID, - getString(R.string.sync_service_notification_group) + ReportNotifications.CHANNEL_REPORT_ID, getString(R.string.sync_service_notification_channel_report_title), + getString(R.string.sync_service_notification_channel_report_description), GROUP_ID, getString(R.string.sync_service_notification_group) ) - } private fun sendUploadFinishedBroadcast(remote: String, path: String?) { - val intent = Intent() - intent.action = getString(R.string.background_service_broadcast) - intent.putExtra(getString(R.string.background_service_broadcast_data_remote), remote) - intent.putExtra(getString(R.string.background_service_broadcast_data_path), path) + val intent = Intent().apply { + action = getString(R.string.background_service_broadcast) + putExtra(getString(R.string.background_service_broadcast_data_remote), remote) + putExtra(getString(R.string.background_service_broadcast_data_path), path) + } LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent) } - // Creates an instance of ForegroundInfo which can be used to update the - // ongoing notification. private fun updateForegroundNotification(notification: Notification?) { notification?.let { setForegroundAsync(ForegroundInfo(ongoingNotificationID, it, FOREGROUND_SERVICE_TYPE_DATA_SYNC)) } } - private fun log(message: String) { FLog.e(TAG, "SyncWorker: $message") } @@ -419,27 +392,22 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) } private fun registerBroadcastReceivers() { - val intentFilter = IntentFilter() - intentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION) + val intentFilter = IntentFilter(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION) mContext.registerReceiver(connectivityChangeBroadcastReceiver, intentFilter) } - private val connectivityChangeBroadcastReceiver: BroadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if(endNotificationAlreadyPosted){ - return - } - sConnectivityChanged = true - failureReason = FAILURE_REASON.CONNECTIVITY_CHANGED - } + private val connectivityChangeBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (endNotificationAlreadyPosted) return + sConnectivityChanged = true + failureReason = FAILURE_REASON.CONNECTIVITY_CHANGED } + } private fun followupTask(followUpTaskID: Long?) { - if (followUpTaskID == null || followUpTaskID <= 0L) { - return - } + if (followUpTaskID == null || followUpTaskID <= 0L) return Thread.sleep(1000) SyncManager(mContext).queue(followUpTaskID) } } + diff --git a/app/src/main/res/menu/task_item_menu.xml b/app/src/main/res/menu/task_item_menu.xml index b7acdd69c..4172b0517 100644 --- a/app/src/main/res/menu/task_item_menu.xml +++ b/app/src/main/res/menu/task_item_menu.xml @@ -21,6 +21,8 @@ + - - \ No newline at end of file + diff --git a/app/src/main/res/menu/trigger_item_menu.xml b/app/src/main/res/menu/trigger_item_menu.xml index 8d8f3bae4..fd6440aa3 100644 --- a/app/src/main/res/menu/trigger_item_menu.xml +++ b/app/src/main/res/menu/trigger_item_menu.xml @@ -13,4 +13,4 @@ android:title="@string/delete" /> - \ No newline at end of file + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index c58c55db0..c8839869e 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -129,6 +129,7 @@ Introdueix la IP o el domini del servidor intermediari Port del servidor intermediari Introdueix el port del servidor intermediari + Veure registres Idioma Tria l\'idioma El canvi d\'idioma es farà efectiu en reiniciar l\'aplicació diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cb46cd346..a8260ec76 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -129,6 +129,7 @@ Fehler beim Abrufen der Dateien Hintergrunddienst für Synchronisierung Verschiebe… + Protokoll anzeigen Lösche… Synchronisierung fehlgeschlagen %1$s schlug aufgrund eines unbekannten Fehlers fehl. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 75a67ec23..9187f80f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,7 +128,7 @@ Round Sync relies on rclone and needs your permission to access all files. Please grant storage access when prompted, otherwise the application will not work properly. Community Round Sync is a community driven project. To contribute and improve the application, you can enable anonymous bug reporting. For more information, please visit felixnuesse.de/privacy-roundsync. - + View Log File Access Look and Feel Dark theme diff --git a/gradle.properties b/gradle.properties index 6a54c4790..47c7ce458 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,8 @@ org.gradle.jvmargs=--enable-native-access=ALL-UNNAMED -Xmx4096M -Dkotlin.daemon.jvm.options="-Xmx4096M" -XX:+UseParallelGC android.enableJetifier=false android.useAndroidX=true -de.felixnuesse.extract.goVersion=1.27.x -de.felixnuesse.extract.rCloneVersion=1.76.0 +de.felixnuesse.extract.goVersion=1.26.x +de.felixnuesse.extract.rCloneVersion=1.75.2 #de.felixnuesse.extract.ndkVersion=25.2.9519653 #de.felixnuesse.extract.ndkToolchainVersion=33 de.felixnuesse.extract.ndkVersion=27.3.13750724