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