From ae2e1874a4d4b0458bac8926577c22db577223de Mon Sep 17 00:00:00 2001 From: Catfriend1 Date: Sun, 6 Jan 2019 03:46:04 +0100 Subject: [PATCH] Fix ANR while processing deleted files (fixes #199) (#204) * Refactor file event finisher into EventProcessor#onItemFinished EventProcessor: Add new unhandled event "FolderWatchStateChanged" * Perform ContentResolver.delete operations asynchronously (fixes #199) --- .../service/EventProcessor.java | 85 +++++++++++++++---- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/EventProcessor.java b/app/src/main/java/com/nutomic/syncthingandroid/service/EventProcessor.java index 7182d267..363e230f 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/EventProcessor.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/EventProcessor.java @@ -1,6 +1,7 @@ package com.nutomic.syncthingandroid.service; import android.app.PendingIntent; +import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -10,6 +11,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; +import android.text.TextUtils; import android.util.Log; import com.annimon.stream.Stream; @@ -125,25 +127,27 @@ public class EventProcessor implements Runnable, RestApi.OnReceiveEventListener ); break; case "ItemFinished": - String folder = (String) event.data.get("folder"); + String action = (String) event.data.get("action"); + String error = (String) event.data.get("error"); + String folderId = (String) event.data.get("folder"); + String relativeFilePath = (String) event.data.get("item"); + + // Lookup folder.path for the given folder.id if all fields were containted in the event.data. String folderPath = null; - for (Folder f : mRestApi.getFolders()) { - if (f.id.equals(folder)) { - folderPath = f.path; + if (!TextUtils.isEmpty(action) && + !TextUtils.isEmpty(folderId) && + !TextUtils.isEmpty(relativeFilePath)) { + for (Folder folder : mRestApi.getFolders()) { + if (folder.id.equals(folderId)) { + folderPath = folder.path; + break; + } } } - File updatedFile = new File(folderPath, (String) event.data.get("item")); - if (!"delete".equals(event.data.get("action"))) { - Log.i(TAG, "Rescanned file via MediaScanner: " + updatedFile.toString()); - MediaScannerConnection.scanFile(mContext, new String[]{updatedFile.getPath()}, - null, null); + if (!TextUtils.isEmpty(folderPath)) { + onItemFinished(action, error, new File(folderPath, relativeFilePath)); } else { - // https://stackoverflow.com/a/29881556/1837158 - Log.i(TAG, "Deleted file from MediaStore: " + updatedFile.toString()); - Uri contentUri = MediaStore.Files.getContentUri("external"); - ContentResolver resolver = mContext.getContentResolver(); - resolver.delete(contentUri, MediaStore.Images.ImageColumns.DATA + " LIKE ?", - new String[]{updatedFile.getPath()}); + Log.w(TAG, "ItemFinished: Failed to determine folder.path for folder.id=\"" + (TextUtils.isEmpty(folderId) ? "" : folderId) + "\""); } break; case "Ping": @@ -156,6 +160,7 @@ public class EventProcessor implements Runnable, RestApi.OnReceiveEventListener case "FolderPaused": case "FolderScanProgress": case "FolderSummary": + case "FolderWatchStateChanged": case "ItemStarted": case "LocalIndexUpdated": case "LoginAttempt": @@ -285,4 +290,54 @@ public class EventProcessor implements Runnable, RestApi.OnReceiveEventListener // Show notification. mNotificationHandler.showConsentNotification(notificationId, title, piAccept, piIgnore); } + + /** + * Precondition: action != null + */ + private void onItemFinished(String action, String error, File updatedFile) { + String relativeFilePath = updatedFile.toString(); + if (!TextUtils.isEmpty(error)) { + Log.e(TAG, "onItemFinished: Error \"" + error + "\" reported on file: " + relativeFilePath); + return; + } + + switch (action) { + case "delete": // file deleted + Log.i(TAG, "Deleting file from MediaStore: " + relativeFilePath); + Uri contentUri = MediaStore.Files.getContentUri("external"); + ContentResolver resolver = mContext.getContentResolver(); + LoggingAsyncQueryHandler asyncQueryHandler = new LoggingAsyncQueryHandler(resolver); + asyncQueryHandler.startDelete( + 0, // this will be passed to "onUpdatedComplete#token" + relativeFilePath, // this will be passed to "onUpdatedComplete#cookie" + contentUri, + MediaStore.Images.ImageColumns.DATA + " LIKE ?", + new String[]{updatedFile.getPath()} + ); + break; + case "update": // file contents changed + case "metadata": // file metadata changed but not contents + Log.i(TAG, "Rescanning file via MediaScanner: " + relativeFilePath); + MediaScannerConnection.scanFile(mContext, new String[]{updatedFile.getPath()}, + null, null); + break; + default: + Log.w(TAG, "onItemFinished: Unhandled action \"" + action + "\""); + } + } + + private static class LoggingAsyncQueryHandler extends AsyncQueryHandler { + + public LoggingAsyncQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + protected void onDeleteComplete(int token, Object cookie, int result) { + super.onUpdateComplete(token, cookie, result); + if (result == 1 && cookie != null) { + // ToDo Log.v(TAG, "onItemFinished: onDeleteComplete: [ok] file=" + cookie.toString() + ", token=" + Integer.toString(token)); + } + } + } }