package com.nutomic.syncthingandroid.util; import android.os.FileObserver; import android.util.Log; import com.nutomic.syncthingandroid.model.Folder; import java.io.File; import java.util.ArrayList; /** * Recursively watches a directory and all subfolders. */ public class FolderObserver extends FileObserver { private static final String TAG = "FolderObserver"; private final OnFolderFileChangeListener mListener; private final Folder mFolder; private final String mPath; private final ArrayList<FolderObserver> mChilds = new ArrayList<>(); public interface OnFolderFileChangeListener { public void onFolderFileChange(String folderId, String relativePath); } public FolderObserver(OnFolderFileChangeListener listener, Folder folder) throws FolderNotExistingException { this(listener, folder, ""); } public class FolderNotExistingException extends Exception { private final String mPath; public FolderNotExistingException(String path) { mPath = path; } @Override public String getMessage() { return "path " + mPath + " does not exist, aborting file observer"; } } /** * Constructs watcher and starts watching the given directory recursively. * * @param listener The listener where changes should be sent to. * @param folder The folder where this folder belongs to. * @param path path to the monitored folder, relative to folder root. */ private FolderObserver(OnFolderFileChangeListener listener, Folder folder, String path) throws FolderNotExistingException { super(folder.path + "/" + path, ATTRIB | CLOSE_WRITE | CREATE | DELETE | DELETE_SELF | MOVED_FROM | MOVED_TO | MOVE_SELF); mListener = listener; mFolder = folder; mPath = path; Log.v(TAG, "observer created for " + new File(mFolder.path, mPath).toString() + " (folder " + folder.id + ")"); startWatching(); File currentFolder = new File(folder.path, path); if (!currentFolder.exists()) { throw new FolderNotExistingException(currentFolder.getAbsolutePath()); } File[] directories = currentFolder.listFiles((current, name) -> new File(current, name).isDirectory()); if (directories != null) { for (File f : directories) { mChilds.add(new FolderObserver(mListener, mFolder, path + "/" + f.getName())); } } } /** * Handles incoming events for changed files. */ @Override public void onEvent(int event, String path) { // Ignore some weird events that we may receive. event &= FileObserver.ALL_EVENTS; if (event == 0) return; File fullPath = (path != null) ? new File(mPath, path) : new File(mPath); Log.v(TAG, "Received inotify event " + Integer.toHexString(event) + " at " + fullPath.getAbsolutePath()); switch (event) { case MOVED_FROM: // fall through case DELETE_SELF: // fall through case DELETE: for (FolderObserver c : mChilds) { if (c.mPath.equals(path)) { mChilds.remove(c); break; } } mListener.onFolderFileChange(mFolder.id, fullPath.getPath()); break; case MOVED_TO: // fall through case CREATE: if (fullPath.isDirectory()) { try { mChilds.add(new FolderObserver(mListener, mFolder, path)); } catch (FolderNotExistingException e) { Log.w(TAG, "Failed to add listener for nonexisting folder", e); } } // fall through default: mListener.onFolderFileChange(mFolder.id, fullPath.getPath()); } } /** * Recursively stops watching the directory. */ @Override public void stopWatching() { super.stopWatching(); for (FolderObserver ro : mChilds) { ro.stopWatching(); } } }