1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one or more
3+ * contributor license agreements. See the NOTICE file distributed with
4+ * this work for additional information regarding copyright ownership.
5+ * The ASF licenses this file to You under the Apache License, Version 2.0
6+ * (the "License"); you may not use this file except in compliance with
7+ * the License. You may obtain a copy of the License at
8+ *
9+ * http://www.apache.org/licenses/LICENSE-2.0
10+ *
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
16+ *
17+ * Modified a bit by Mario Danic
18+ * Changes are Copyright (C) 2017 Mario Danic
19+ *
20+ * Those changes are under the following licence:
21+ *
22+ * * This program is free software: you can redistribute it and/or modify
23+ * it under the terms of the GNU Affero General Public License as published by
24+ * the Free Software Foundation, either version 3 of the License, or
25+ * at your option) any later version.
26+ *
27+ * This program is distributed in the hope that it will be useful,
28+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
29+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30+ * GNU Affero General Public License for more details.
31+ *
32+ * You should have received a copy of the GNU Affero General Public License
33+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
34+ */
35+ package com .owncloud .android .services .observer ;
36+
37+ import org .apache .commons .io .FileUtils ;
38+ import org .apache .commons .io .comparator .NameFileComparator ;
39+ import org .apache .commons .io .monitor .FileAlterationListener ;
40+ import org .apache .commons .io .monitor .FileAlterationObserver ;
41+ import org .apache .commons .io .monitor .FileEntry ;
42+
43+ import java .io .File ;
44+ import java .io .FileFilter ;
45+ import java .io .Serializable ;
46+ import java .util .Arrays ;
47+ import java .util .Comparator ;
48+ import java .util .List ;
49+ import java .util .concurrent .CopyOnWriteArrayList ;
50+
51+ public class FileAlterationMagicObserver extends FileAlterationObserver implements Serializable {
52+
53+ private static final long serialVersionUID = 1185122225658782848L ;
54+ private final List <FileAlterationListener > listeners = new CopyOnWriteArrayList <>();
55+ private FileEntry rootEntry ;
56+ private FileFilter fileFilter ;
57+ private Comparator <File > comparator ;
58+
59+ static final FileEntry [] EMPTY_ENTRIES = new FileEntry [0 ];
60+
61+
62+ public FileAlterationMagicObserver (File directory , FileFilter fileFilter ) {
63+ super (directory , fileFilter );
64+
65+ this .rootEntry = new FileEntry (directory );
66+ this .fileFilter = fileFilter ;
67+ comparator = NameFileComparator .NAME_SYSTEM_COMPARATOR ;
68+ }
69+
70+ /**
71+ * Return the directory being observed.
72+ *
73+ * @return the directory being observed
74+ */
75+ public File getDirectory () {
76+ return rootEntry .getFile ();
77+ }
78+
79+ /**
80+ * Return the fileFilter.
81+ *
82+ * @return the fileFilter
83+ * @since 2.1
84+ */
85+ public FileFilter getFileFilter () {
86+ return fileFilter ;
87+ }
88+
89+ public FileEntry getRootEntry () {
90+ return rootEntry ;
91+ }
92+
93+ public void setRootEntry (FileEntry rootEntry ) {
94+ this .rootEntry = rootEntry ;
95+ }
96+
97+ /**
98+ * Add a file system listener.
99+ *
100+ * @param listener The file system listener
101+ */
102+ public void addListener (final FileAlterationListener listener ) {
103+ if (listener != null ) {
104+ listeners .add (listener );
105+ }
106+ }
107+
108+ /**
109+ * Remove a file system listener.
110+ *
111+ * @param listener The file system listener
112+ */
113+ public void removeListener (final FileAlterationListener listener ) {
114+ if (listener != null ) {
115+ while (listeners .remove (listener )) {
116+ }
117+ }
118+ }
119+
120+ /**
121+ * Returns the set of registered file system listeners.
122+ *
123+ * @return The file system listeners
124+ */
125+ public Iterable <FileAlterationListener > getListeners () {
126+ return listeners ;
127+ }
128+
129+ /**
130+ * Does nothing - hack for the monitor
131+ *
132+ *
133+ */
134+ public void initialize () {
135+ }
136+
137+
138+ /**
139+ * Initializes everything
140+ *
141+ * @throws Exception if an error occurs
142+ */
143+ public void init () throws Exception {
144+ rootEntry .refresh (rootEntry .getFile ());
145+ final FileEntry [] children = doListFiles (rootEntry .getFile (), rootEntry );
146+ rootEntry .setChildren (children );
147+ }
148+
149+
150+ /**
151+ * Final processing.
152+ *
153+ * @throws Exception if an error occurs
154+ */
155+ public void destroy () throws Exception {
156+ }
157+
158+ /**
159+ * Check whether the file and its chlidren have been created, modified or deleted.
160+ */
161+ public void checkAndNotify () {
162+
163+ /* fire onStart() */
164+ for (final FileAlterationListener listener : listeners ) {
165+ listener .onStart (this );
166+ }
167+
168+ /* fire directory/file events */
169+ final File rootFile = rootEntry .getFile ();
170+ if (rootFile .exists ()) {
171+ checkAndNotify (rootEntry , rootEntry .getChildren (), listFiles (rootFile ));
172+ } else if (rootEntry .isExists ()) {
173+ checkAndNotify (rootEntry , rootEntry .getChildren (), FileUtils .EMPTY_FILE_ARRAY );
174+ } else {
175+ // Didn't exist and still doesn't
176+ }
177+
178+ /* fire onStop() */
179+ for (final FileAlterationListener listener : listeners ) {
180+ listener .onStop (this );
181+ }
182+ }
183+
184+ /**
185+ * Compare two file lists for files which have been created, modified or deleted.
186+ *
187+ * @param parent The parent entry
188+ * @param previous The original list of files
189+ * @param files The current list of files
190+ */
191+ private void checkAndNotify (final FileEntry parent , final FileEntry [] previous , final File [] files ) {
192+ int c = 0 ;
193+ final FileEntry [] current = files .length > 0 ? new FileEntry [files .length ] : EMPTY_ENTRIES ;
194+ for (final FileEntry entry : previous ) {
195+ while (c < files .length && comparator .compare (entry .getFile (), files [c ]) > 0 ) {
196+ current [c ] = createFileEntry (parent , files [c ]);
197+ doCreate (current [c ]);
198+ c ++;
199+ }
200+ if (c < files .length && comparator .compare (entry .getFile (), files [c ]) == 0 ) {
201+ doMatch (entry , files [c ]);
202+ checkAndNotify (entry , entry .getChildren (), listFiles (files [c ]));
203+ current [c ] = entry ;
204+ c ++;
205+ } else {
206+ checkAndNotify (entry , entry .getChildren (), FileUtils .EMPTY_FILE_ARRAY );
207+ doDelete (entry );
208+ }
209+ }
210+ for (; c < files .length ; c ++) {
211+ current [c ] = createFileEntry (parent , files [c ]);
212+ doCreate (current [c ]);
213+ }
214+ parent .setChildren (current );
215+ }
216+
217+ /**
218+ * Create a new file entry for the specified file.
219+ *
220+ * @param parent The parent file entry
221+ * @param file The file to create an entry for
222+ * @return A new file entry
223+ */
224+ private FileEntry createFileEntry (final FileEntry parent , final File file ) {
225+ final FileEntry entry = parent .newChildInstance (file );
226+ entry .refresh (file );
227+ final FileEntry [] children = doListFiles (file , entry );
228+ entry .setChildren (children );
229+ return entry ;
230+ }
231+
232+ /**
233+ * List the files
234+ *
235+ * @param file The file to list files for
236+ * @param entry the parent entry
237+ * @return The child files
238+ */
239+ private FileEntry [] doListFiles (File file , FileEntry entry ) {
240+ final File [] files = listFiles (file );
241+ final FileEntry [] children = files .length > 0 ? new FileEntry [files .length ] : EMPTY_ENTRIES ;
242+ for (int i = 0 ; i < files .length ; i ++) {
243+ children [i ] = createFileEntry (entry , files [i ]);
244+ }
245+ return children ;
246+ }
247+
248+ /**
249+ * Fire directory/file created events to the registered listeners.
250+ *
251+ * @param entry The file entry
252+ */
253+ private void doCreate (final FileEntry entry ) {
254+ for (final FileAlterationListener listener : listeners ) {
255+ if (entry .isDirectory ()) {
256+ listener .onDirectoryCreate (entry .getFile ());
257+ } else {
258+ listener .onFileCreate (entry .getFile ());
259+ }
260+ }
261+ final FileEntry [] children = entry .getChildren ();
262+ for (final FileEntry aChildren : children ) {
263+ doCreate (aChildren );
264+ }
265+ }
266+
267+ /**
268+ * Fire directory/file change events to the registered listeners.
269+ *
270+ * @param entry The previous file system entry
271+ * @param file The current file
272+ */
273+ private void doMatch (final FileEntry entry , final File file ) {
274+ if (entry .refresh (file )) {
275+ for (final FileAlterationListener listener : listeners ) {
276+ if (entry .isDirectory ()) {
277+ listener .onDirectoryChange (file );
278+ } else {
279+ listener .onFileChange (file );
280+ }
281+ }
282+ }
283+ }
284+
285+ /**
286+ * Fire directory/file delete events to the registered listeners.
287+ *
288+ * @param entry The file entry
289+ */
290+ private void doDelete (final FileEntry entry ) {
291+ for (final FileAlterationListener listener : listeners ) {
292+ if (entry .isDirectory ()) {
293+ listener .onDirectoryDelete (entry .getFile ());
294+ } else {
295+ listener .onFileDelete (entry .getFile ());
296+ }
297+ }
298+ }
299+
300+ /**
301+ * List the contents of a directory
302+ *
303+ * @param file The file to list the contents of
304+ * @return the directory contents or a zero length array if
305+ * the empty or the file is not a directory
306+ */
307+ private File [] listFiles (final File file ) {
308+ File [] children = null ;
309+ if (file .isDirectory ()) {
310+ children = fileFilter == null ? file .listFiles () : file .listFiles (fileFilter );
311+ }
312+ if (children == null ) {
313+ children = FileUtils .EMPTY_FILE_ARRAY ;
314+ }
315+ if (comparator != null && children .length > 1 ) {
316+ Arrays .sort (children , comparator );
317+ }
318+ return children ;
319+ }
320+
321+ /**
322+ * Provide a String representation of this observer.
323+ *
324+ * @return a String representation of this observer
325+ */
326+ @ Override
327+ public String toString () {
328+ final StringBuilder builder = new StringBuilder ();
329+ builder .append (getClass ().getSimpleName ());
330+ builder .append ("[file='" );
331+ builder .append (getDirectory ().getPath ());
332+ builder .append ('\'' );
333+ if (fileFilter != null ) {
334+ builder .append (", " );
335+ builder .append (fileFilter .toString ());
336+ }
337+ builder .append (", listeners=" );
338+ builder .append (listeners .size ());
339+ builder .append ("]" );
340+ return builder .toString ();
341+ }
342+
343+ }
0 commit comments