com.owncloud.android.services.observer.AdvancedFileAlterationObserver.java Source code

Java tutorial

Introduction

Here is the source code for com.owncloud.android.services.observer.AdvancedFileAlterationObserver.java

Source

/*
 * 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.
 *
 * Original source code:
 * https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java
 *
 * Modified by Mario Danic
 * Changes are Copyright (C) 2017 Mario Danic
 * Copyright (C) 2017 Nextcloud GmbH
 *
 * All changes are under the same licence as the original.
 *
 */
package com.owncloud.android.services.observer;

import android.os.SystemClock;

import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.services.AdvancedFileAlterationListener;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.comparator.NameFileComparator;
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.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class AdvancedFileAlterationObserver extends FileAlterationObserver implements Serializable {

    private static final long serialVersionUID = 1185122225658782848L;
    private static final int DELAY_INVOCATION_MS = 2500;
    private final List<AdvancedFileAlterationListener> listeners = new CopyOnWriteArrayList<>();
    private FileEntry rootEntry;
    private FileFilter fileFilter;
    private Comparator<File> comparator;
    private SyncedFolder syncedFolder;

    private static final FileEntry[] EMPTY_ENTRIES = new FileEntry[0];

    public AdvancedFileAlterationObserver(SyncedFolder syncedFolder, FileFilter fileFilter) {
        super(syncedFolder.getLocalPath(), fileFilter);

        this.rootEntry = new FileEntry(new File(syncedFolder.getLocalPath()));
        this.fileFilter = fileFilter;
        this.syncedFolder = syncedFolder;
        comparator = NameFileComparator.NAME_SYSTEM_COMPARATOR;
    }

    public long getSyncedFolderID() {
        return syncedFolder.getId();
    }

    public SyncedFolder getSyncedFolder() {
        return syncedFolder;
    }

    /**
     * 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 AdvancedFileAlterationListener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    /**
     * Remove a file system listener.
     *
     * @param listener The file system listener
     */
    public void removeListener(final AdvancedFileAlterationListener listener) {
        if (listener != null) {
            while (listeners.remove(listener)) {
            }
        }
    }

    /**
     * Returns the set of registered file system listeners.
     *
     * @return The file system listeners
     */
    public Iterable<AdvancedFileAlterationListener> getMagicListeners() {
        return listeners;
    }

    /**
     * Does nothing - hack for the monitor
     *
     *
     */
    public void initialize() {
        // does nothing - hack the monitor
    }

    /**
     * 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 {
        Iterator iterator = getMagicListeners().iterator();
        while (iterator.hasNext()) {
            AdvancedFileAlterationListener AdvancedFileAlterationListener = (AdvancedFileAlterationListener) iterator
                    .next();
            while (AdvancedFileAlterationListener.getActiveTasksCount() > 0) {
                SystemClock.sleep(250);
            }
        }
    }

    public void checkAndNotifyNow() {
        /* fire onStart() */
        for (final AdvancedFileAlterationListener listener : listeners) {
            listener.onStart(this);
        }

        /* fire directory/file events */
        final File rootFile = rootEntry.getFile();
        if (rootFile.exists()) {
            checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile), 0);
        } else if (rootEntry.isExists()) {
            try {
                // try to init once more
                init();
                if (rootEntry.getFile().exists()) {
                    checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootEntry.getFile()), 0);
                } else {
                    checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, 0);
                }
            } catch (Exception e) {
                Log_OC.d("AdvancedFileAlterationObserver", "Failed getting an observer to intialize " + e);
                checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, 0);
            }
        } // else didn't exist and still doesn't

        /* fire onStop() */
        for (final AdvancedFileAlterationListener listener : listeners) {
            listener.onStop(this);
        }
    }

    /**
     * Check whether the file and its children have been created, modified or deleted.
     */
    public void checkAndNotify() {

        /* fire onStart() */
        for (final AdvancedFileAlterationListener listener : listeners) {
            listener.onStart(this);
        }

        /* fire directory/file events */
        final File rootFile = rootEntry.getFile();
        if (rootFile.exists()) {
            checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile), DELAY_INVOCATION_MS);
        } else if (rootEntry.isExists()) {
            try {
                // try to init once more
                init();
                if (rootEntry.getFile().exists()) {
                    checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootEntry.getFile()),
                            DELAY_INVOCATION_MS);
                } else {
                    checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY,
                            DELAY_INVOCATION_MS);
                }
            } catch (Exception e) {
                Log_OC.d("AdvancedFileAlterationObserver", "Failed getting an observer to intialize " + e);
                checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, DELAY_INVOCATION_MS);
            }
        } // else didn't exist and still doesn't

        /* fire onStop() */
        for (final AdvancedFileAlterationListener 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 delay) {
        if (files != null && files.length > 0) {
            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], delay);
                    c++;
                }
                if (c < files.length && comparator.compare(entry.getFile(), files[c]) == 0) {
                    doMatch(entry, files[c], delay);
                    checkAndNotify(entry, entry.getChildren(), listFiles(files[c]), delay);
                    current[c] = entry;
                    c++;
                } else {
                    checkAndNotify(entry, entry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, delay);
                    doDelete(entry);
                }
            }
            for (; c < files.length; c++) {
                current[c] = createFileEntry(parent, files[c]);
                doCreate(current[c], delay);
            }
            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, int delay) {
        for (final AdvancedFileAlterationListener listener : listeners) {
            if (entry.isDirectory()) {
                listener.onDirectoryCreate(entry.getFile());
            } else {
                listener.onFileCreate(entry.getFile(), delay);
            }
        }
        final FileEntry[] children = entry.getChildren();
        for (final FileEntry aChildren : children) {
            doCreate(aChildren, delay);
        }
    }

    /**
     * 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, int delay) {
        if (entry.refresh(file)) {
            for (final AdvancedFileAlterationListener listener : listeners) {
                if (entry.isDirectory()) {
                    listener.onDirectoryChange(file);
                } else {
                    listener.onFileChange(file, delay);
                }
            }
        }
    }

    /**
     * Fire directory/file delete events to the registered listeners.
     *
     * @param entry The file entry
     */
    private void doDelete(final FileEntry entry) {
        for (final AdvancedFileAlterationListener 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();
    }

}