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}