ConversationsFileObserver.java

  1package eu.siacs.conversations.utils;
  2
  3
  4import android.os.FileObserver;
  5import android.util.Log;
  6
  7import java.io.File;
  8import java.util.ArrayList;
  9import java.util.Collections;
 10import java.util.List;
 11import java.util.Stack;
 12import java.util.concurrent.Executor;
 13import java.util.concurrent.Executors;
 14import java.util.concurrent.atomic.AtomicBoolean;
 15
 16import eu.siacs.conversations.Config;
 17
 18/**
 19 * Copyright (C) 2012 Bartek Przybylski
 20 * Copyright (C) 2015 ownCloud Inc.
 21 * Copyright (C) 2016 Daniel Gultsch
 22 */
 23
 24public abstract class ConversationsFileObserver {
 25    private static final Executor EVENT_EXECUTOR = Executors.newSingleThreadExecutor();
 26    private static final int MASK = FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.CREATE;
 27
 28
 29    private final String path;
 30    private final List<SingleFileObserver> mObservers = new ArrayList<>();
 31    private final AtomicBoolean shouldStop = new AtomicBoolean(true);
 32
 33    protected ConversationsFileObserver(String path) {
 34        this.path = path;
 35    }
 36
 37    public void startWatching() {
 38        shouldStop.set(false);
 39        startWatchingInternal();
 40    }
 41
 42    private synchronized void startWatchingInternal() {
 43        final Stack<String> stack = new Stack<>();
 44        stack.push(path);
 45
 46        while (!stack.empty()) {
 47            if (shouldStop.get()) {
 48                Log.d(Config.LOGTAG, "file observer received command to stop");
 49                return;
 50            }
 51            final String parent = stack.pop();
 52            final File path = new File(parent);
 53            mObservers.add(new SingleFileObserver(path, MASK));
 54            final File[] files = path.listFiles();
 55            for (final File file : (files == null ? new File[0] : files)) {
 56                if (shouldStop.get()) {
 57                    Log.d(Config.LOGTAG, "file observer received command to stop");
 58                    return;
 59                }
 60                if (file.isDirectory() && file.getName().charAt(0) != '.') {
 61                    final String currentPath = file.getAbsolutePath();
 62                    if (depth(file) <= 8 && !stack.contains(currentPath) && !observing(file)) {
 63                        stack.push(currentPath);
 64                    }
 65                }
 66            }
 67        }
 68        for (FileObserver observer : mObservers) {
 69            observer.startWatching();
 70        }
 71    }
 72
 73    private static int depth(File file) {
 74        int depth = 0;
 75        while ((file = file.getParentFile()) != null) {
 76            depth++;
 77        }
 78        return depth;
 79    }
 80
 81    private boolean observing(final File path) {
 82        for (final SingleFileObserver observer : mObservers) {
 83            if (path.equals(observer.path)) {
 84                return true;
 85            }
 86        }
 87        return false;
 88    }
 89
 90    public void stopWatching() {
 91        shouldStop.set(true);
 92        stopWatchingInternal();
 93    }
 94
 95    private synchronized void stopWatchingInternal() {
 96        for (FileObserver observer : mObservers) {
 97            observer.stopWatching();
 98        }
 99        mObservers.clear();
100    }
101
102    abstract public void onEvent(final int event, File path);
103
104    public void restartWatching() {
105        stopWatching();
106        startWatching();
107    }
108
109    private class SingleFileObserver extends FileObserver {
110        private final File path;
111
112        SingleFileObserver(final File path, final int mask) {
113            super(path.getAbsolutePath(), mask);
114            this.path = path;
115        }
116
117        @Override
118        public void onEvent(final int event, final String filename) {
119            if (filename == null) {
120                Log.d(Config.LOGTAG, "ignored file event with NULL filename (event=" + event + ")");
121                return;
122            }
123            EVENT_EXECUTOR.execute(() -> {
124                final File file = new File(this.path, filename);
125                if ((event & FileObserver.ALL_EVENTS) == FileObserver.CREATE) {
126                    if (file.isDirectory()) {
127                        Log.d(Config.LOGTAG, "file observer observed new directory creation " + file);
128                        if (!observing(file)) {
129                            final SingleFileObserver observer = new SingleFileObserver(file, MASK);
130                            observer.startWatching();
131                        }
132                    }
133                    return;
134                }
135                ConversationsFileObserver.this.onEvent(event, file);
136            });
137        }
138    }
139}