refactored deleted file detection to monitor entire sd card. fixes #1968

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java  | 42 
src/main/java/eu/siacs/conversations/utils/ConversationsFileObserver.java | 72 
2 files changed, 95 insertions(+), 19 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -18,7 +18,7 @@ import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.FileObserver;
+import android.os.Environment;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
@@ -70,6 +70,7 @@ import eu.siacs.conversations.entities.Blockable;
 import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions;
 import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
@@ -91,6 +92,7 @@ import eu.siacs.conversations.parser.PresenceParser;
 import eu.siacs.conversations.persistance.DatabaseBackend;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.utils.ConversationsFileObserver;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
@@ -211,14 +213,14 @@ public class XmppConnectionService extends Service {
 	private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
 	private PushManagementService mPushManagementService = new PushManagementService(this);
 	private OnConversationUpdate mOnConversationUpdate = null;
-	private final FileObserver fileObserver = new FileObserver(
-			FileBackend.getConversationsImageDirectory()) {
 
+
+	private final ConversationsFileObserver fileObserver = new ConversationsFileObserver(
+			Environment.getExternalStorageDirectory().getAbsolutePath()
+	) {
 		@Override
 		public void onEvent(int event, String path) {
-			if (event == FileObserver.DELETE) {
-				markFileDeleted(path.split("\\.")[0]);
-			}
+			markFileDeleted(path);
 		}
 	};
 	private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
@@ -768,7 +770,6 @@ public class XmppConnectionService extends Service {
 
 		getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
 		this.fileObserver.startWatching();
-
 		if (Config.supportOpenPgp()) {
 			this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() {
 				@Override
@@ -814,6 +815,7 @@ public class XmppConnectionService extends Service {
 		} catch (IllegalArgumentException e) {
 			//ignored
 		}
+		fileObserver.stopWatching();
 		super.onDestroy();
 	}
 
@@ -1284,21 +1286,23 @@ public class XmppConnectionService extends Service {
 		});
 	}
 
-	private void markFileDeleted(String uuid) {
+	private void markFileDeleted(final String path) {
 		for (Conversation conversation : getConversations()) {
-			Message message = conversation.findMessageWithFileAndUuid(uuid);
-			if (message != null) {
-				if (!getFileBackend().isFileAvailable(message)) {
-					message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
-					final int s = message.getStatus();
-					if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
-						markMessage(message, Message.STATUS_SEND_FAILED);
-					} else {
-						updateConversationUi();
+			conversation.findMessagesWithFiles(new Conversation.OnMessageFound() {
+				@Override
+				public void onMessageFound(Message message) {
+					DownloadableFile file = fileBackend.getFile(message);
+					if (file.getAbsolutePath().equals(path) && !file.exists()) {
+						message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
+						final int s = message.getStatus();
+						if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
+							markMessage(message, Message.STATUS_SEND_FAILED);
+						} else {
+							updateConversationUi();
+						}
 					}
 				}
-				return;
-			}
+			});
 		}
 	}
 

src/main/java/eu/siacs/conversations/utils/ConversationsFileObserver.java 🔗

@@ -0,0 +1,72 @@
+package eu.siacs.conversations.utils;
+
+
+import android.os.FileObserver;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2016 Daniel Gultsch
+ */
+
+public abstract class ConversationsFileObserver {
+
+    private final String path;
+    private final List<SingleFileObserver> mObservers = new ArrayList<>();
+
+    public ConversationsFileObserver(String path) {
+        this.path = path;
+    }
+
+    public synchronized void startWatching() {
+        Stack<String> stack = new Stack<>();
+        stack.push(path);
+
+        while (!stack.empty()) {
+            String parent = stack.pop();
+            mObservers.add(new SingleFileObserver(parent, FileObserver.DELETE));
+            final File path = new File(parent);
+            final File[] files = path.listFiles();
+            if (files == null) {
+                continue;
+            }
+            for(File file : files) {
+                if (file.isDirectory() && !file.getName().equals(".") && !file.getName().equals("..")) {
+                    stack.push(file.getPath());
+                }
+            }
+        }
+        for(FileObserver observer : mObservers) {
+            observer.startWatching();
+        }
+    }
+
+    public synchronized void stopWatching() {
+        for(FileObserver observer : mObservers) {
+            observer.stopWatching();
+        }
+        mObservers.clear();
+    }
+
+    abstract public void onEvent(int event, String path);
+
+    private class SingleFileObserver extends FileObserver {
+        private final String path;
+
+        public SingleFileObserver(String path, int mask) {
+            super(path, mask);
+            this.path = path;
+        }
+
+        @Override
+        public void onEvent(int event, String filename) {
+            ConversationsFileObserver.this.onEvent(event, path+'/'+filename);
+        }
+
+    }
+}