Browse Source

Move to approach without reflection

Mario Danic 8 years ago
parent
commit
8a978a28ce

+ 343 - 0
src/com/owncloud/android/services/observer/FileAlterationMagicObserver.java

@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modified a bit by Mario Danic
+ * Changes are Copyright (C) 2017 Mario Danic
+ *
+ * Those changes are under the following licence:
+ *
+ *  * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.services.observer;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.comparator.NameFileComparator;
+import org.apache.commons.io.monitor.FileAlterationListener;
+import org.apache.commons.io.monitor.FileAlterationObserver;
+import org.apache.commons.io.monitor.FileEntry;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class FileAlterationMagicObserver extends FileAlterationObserver implements Serializable {
+
+    private static final long serialVersionUID = 1185122225658782848L;
+    private final List<FileAlterationListener> listeners = new CopyOnWriteArrayList<>();
+    private FileEntry rootEntry;
+    private FileFilter fileFilter;
+    private Comparator<File> comparator;
+
+    static final FileEntry[] EMPTY_ENTRIES = new FileEntry[0];
+
+
+    public FileAlterationMagicObserver(File directory, FileFilter fileFilter) {
+        super(directory, fileFilter);
+
+        this.rootEntry = new FileEntry(directory);
+        this.fileFilter = fileFilter;
+        comparator = NameFileComparator.NAME_SYSTEM_COMPARATOR;
+    }
+
+    /**
+     * Return the directory being observed.
+     *
+     * @return the directory being observed
+     */
+    public File getDirectory() {
+        return rootEntry.getFile();
+    }
+
+    /**
+     * Return the fileFilter.
+     *
+     * @return the fileFilter
+     * @since 2.1
+     */
+    public FileFilter getFileFilter() {
+        return fileFilter;
+    }
+
+    public FileEntry getRootEntry() {
+        return rootEntry;
+    }
+
+    public void setRootEntry(FileEntry rootEntry) {
+        this.rootEntry = rootEntry;
+    }
+
+    /**
+     * Add a file system listener.
+     *
+     * @param listener The file system listener
+     */
+    public void addListener(final FileAlterationListener listener) {
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Remove a file system listener.
+     *
+     * @param listener The file system listener
+     */
+    public void removeListener(final FileAlterationListener listener) {
+        if (listener != null) {
+            while (listeners.remove(listener)) {
+            }
+        }
+    }
+
+    /**
+     * Returns the set of registered file system listeners.
+     *
+     * @return The file system listeners
+     */
+    public Iterable<FileAlterationListener> getListeners() {
+        return listeners;
+    }
+
+    /**
+     * Does nothing - hack for the monitor
+     *
+     *
+     */
+    public void initialize() {
+    }
+
+
+    /**
+     * Initializes everything
+     *
+     * @throws Exception if an error occurs
+     */
+    public void init() throws Exception {
+        rootEntry.refresh(rootEntry.getFile());
+        final FileEntry[] children = doListFiles(rootEntry.getFile(), rootEntry);
+        rootEntry.setChildren(children);
+    }
+
+
+    /**
+     * Final processing.
+     *
+     * @throws Exception if an error occurs
+     */
+    public void destroy() throws Exception {
+    }
+
+    /**
+     * Check whether the file and its chlidren have been created, modified or deleted.
+     */
+    public void checkAndNotify() {
+
+        /* fire onStart() */
+        for (final FileAlterationListener listener : listeners) {
+            listener.onStart(this);
+        }
+
+        /* fire directory/file events */
+        final File rootFile = rootEntry.getFile();
+        if (rootFile.exists()) {
+            checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile));
+        } else if (rootEntry.isExists()) {
+            checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
+        } else {
+            // Didn't exist and still doesn't
+        }
+
+        /* fire onStop() */
+        for (final FileAlterationListener listener : listeners) {
+            listener.onStop(this);
+        }
+    }
+
+    /**
+     * Compare two file lists for files which have been created, modified or deleted.
+     *
+     * @param parent   The parent entry
+     * @param previous The original list of files
+     * @param files    The current list of files
+     */
+    private void checkAndNotify(final FileEntry parent, final FileEntry[] previous, final File[] files) {
+        int c = 0;
+        final FileEntry[] current = files.length > 0 ? new FileEntry[files.length] : EMPTY_ENTRIES;
+        for (final FileEntry entry : previous) {
+            while (c < files.length && comparator.compare(entry.getFile(), files[c]) > 0) {
+                current[c] = createFileEntry(parent, files[c]);
+                doCreate(current[c]);
+                c++;
+            }
+            if (c < files.length && comparator.compare(entry.getFile(), files[c]) == 0) {
+                doMatch(entry, files[c]);
+                checkAndNotify(entry, entry.getChildren(), listFiles(files[c]));
+                current[c] = entry;
+                c++;
+            } else {
+                checkAndNotify(entry, entry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
+                doDelete(entry);
+            }
+        }
+        for (; c < files.length; c++) {
+            current[c] = createFileEntry(parent, files[c]);
+            doCreate(current[c]);
+        }
+        parent.setChildren(current);
+    }
+
+    /**
+     * Create a new file entry for the specified file.
+     *
+     * @param parent The parent file entry
+     * @param file   The file to create an entry for
+     * @return A new file entry
+     */
+    private FileEntry createFileEntry(final FileEntry parent, final File file) {
+        final FileEntry entry = parent.newChildInstance(file);
+        entry.refresh(file);
+        final FileEntry[] children = doListFiles(file, entry);
+        entry.setChildren(children);
+        return entry;
+    }
+
+    /**
+     * List the files
+     *
+     * @param file  The file to list files for
+     * @param entry the parent entry
+     * @return The child files
+     */
+    private FileEntry[] doListFiles(File file, FileEntry entry) {
+        final File[] files = listFiles(file);
+        final FileEntry[] children = files.length > 0 ? new FileEntry[files.length] : EMPTY_ENTRIES;
+        for (int i = 0; i < files.length; i++) {
+            children[i] = createFileEntry(entry, files[i]);
+        }
+        return children;
+    }
+
+    /**
+     * Fire directory/file created events to the registered listeners.
+     *
+     * @param entry The file entry
+     */
+    private void doCreate(final FileEntry entry) {
+        for (final FileAlterationListener listener : listeners) {
+            if (entry.isDirectory()) {
+                listener.onDirectoryCreate(entry.getFile());
+            } else {
+                listener.onFileCreate(entry.getFile());
+            }
+        }
+        final FileEntry[] children = entry.getChildren();
+        for (final FileEntry aChildren : children) {
+            doCreate(aChildren);
+        }
+    }
+
+    /**
+     * Fire directory/file change events to the registered listeners.
+     *
+     * @param entry The previous file system entry
+     * @param file  The current file
+     */
+    private void doMatch(final FileEntry entry, final File file) {
+        if (entry.refresh(file)) {
+            for (final FileAlterationListener listener : listeners) {
+                if (entry.isDirectory()) {
+                    listener.onDirectoryChange(file);
+                } else {
+                    listener.onFileChange(file);
+                }
+            }
+        }
+    }
+
+    /**
+     * Fire directory/file delete events to the registered listeners.
+     *
+     * @param entry The file entry
+     */
+    private void doDelete(final FileEntry entry) {
+        for (final FileAlterationListener listener : listeners) {
+            if (entry.isDirectory()) {
+                listener.onDirectoryDelete(entry.getFile());
+            } else {
+                listener.onFileDelete(entry.getFile());
+            }
+        }
+    }
+
+    /**
+     * List the contents of a directory
+     *
+     * @param file The file to list the contents of
+     * @return the directory contents or a zero length array if
+     * the empty or the file is not a directory
+     */
+    private File[] listFiles(final File file) {
+        File[] children = null;
+        if (file.isDirectory()) {
+            children = fileFilter == null ? file.listFiles() : file.listFiles(fileFilter);
+        }
+        if (children == null) {
+            children = FileUtils.EMPTY_FILE_ARRAY;
+        }
+        if (comparator != null && children.length > 1) {
+            Arrays.sort(children, comparator);
+        }
+        return children;
+    }
+
+    /**
+     * Provide a String representation of this observer.
+     *
+     * @return a String representation of this observer
+     */
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append("[file='");
+        builder.append(getDirectory().getPath());
+        builder.append('\'');
+        if (fileFilter != null) {
+            builder.append(", ");
+            builder.append(fileFilter.toString());
+        }
+        builder.append(", listeners=");
+        builder.append(listeners.size());
+        builder.append("]");
+        return builder.toString();
+    }
+
+}

+ 5 - 14
src/com/owncloud/android/services/observer/SyncedFolderObserverService.java

@@ -45,7 +45,6 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.ObjectOutputStream;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -73,7 +72,6 @@ public class SyncedFolderObserverService extends Service {
             }
         };
 
-        FileEntry rootEntry;
 
         FileOutputStream fos = null;
         ArrayList<Pair<SyncedFolder, FileEntry>> pairArrayList = new ArrayList<>();
@@ -82,19 +80,13 @@ public class SyncedFolderObserverService extends Service {
         for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) {
             if (syncedFolder.isEnabled() && !syncedFolderMap.containsKey(syncedFolder.getLocalPath())) {
                 Log_OC.d(TAG, "start observer: " + syncedFolder.getLocalPath());
-                FileAlterationObserver observer = new FileAlterationObserver(syncedFolder.getLocalPath(), fileFilter);
-                Field f;
+                FileAlterationMagicObserver observer = new FileAlterationMagicObserver(new File(
+                        syncedFolder.getLocalPath()), fileFilter);
+
                 try {
-                    observer.initialize();
-                    f = observer.getClass().getDeclaredField("rootEntry");
-                    f.setAccessible(true);
-                    rootEntry = (FileEntry) f.get(observer);
-                    Pair<SyncedFolder, FileEntry> pair = new Pair<>(syncedFolder, rootEntry);
+                    observer.init();
+                    Pair<SyncedFolder, FileEntry> pair = new Pair<>(syncedFolder, observer.getRootEntry());
                     pairArrayList.add(pair);
-                } catch (NoSuchFieldException e) {
-                    Log_OC.d(TAG, "Failed getting private field rootEntry via NoSuchFieldException");
-                } catch (IllegalAccessException e) {
-                    Log_OC.d(TAG, "Failed getting private field rootEntry via IllegalAccessException");
                 } catch (Exception e) {
                     Log_OC.d(TAG, "Failed getting an observer to intialize");
                 }
@@ -120,7 +112,6 @@ public class SyncedFolderObserverService extends Service {
         }
 
 
-
         try {
             monitor.start();
         } catch (Exception e) {