| /* |
| * 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. |
| */ |
| package org.apache.sling.installer.provider.file.impl; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * This class is a monitor for the file system |
| * that periodically checks for changes. |
| */ |
| public class FileMonitor extends TimerTask { |
| |
| /** The logger. */ |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| private final Timer timer = new Timer(); |
| private boolean stop = false; |
| private boolean stopped = true; |
| |
| private final Monitorable root; |
| |
| private final FileChangesListener listener; |
| |
| /** |
| * Creates a new instance of this class. |
| * @param interval The interval between executions of the task, in milliseconds. |
| */ |
| public FileMonitor(final File rootDir, final Long interval, final FileChangesListener listener) { |
| this.listener = listener; |
| this.root = new Monitorable(rootDir); |
| createStatus(this.root); |
| final List<File> files = new ArrayList<File>(); |
| collect(this.root.file, files); |
| this.listener.initialSet(files); |
| logger.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval); |
| timer.schedule(this, 0, (interval != null ? interval : 5000)); |
| } |
| |
| public File getRoot() { |
| return this.root.file; |
| } |
| |
| public FileChangesListener getListener() { |
| return this.listener; |
| } |
| |
| private void collect(final File file, final List<File> files) { |
| if ( file.exists() ) { |
| if ( file.isDirectory() ) { |
| final File[] children = file.listFiles(); |
| if ( children != null ) { |
| for(final File child : children ) { |
| collect(child, files); |
| } |
| } |
| } else { |
| files.add(file); |
| } |
| } |
| } |
| |
| private void collectDeleted(final Monitorable m, final List<File> files) { |
| if ( m.status instanceof DirStatus ) { |
| for(final Monitorable child : ((DirStatus)m.status).children ) { |
| collectDeleted(child, files); |
| } |
| } else { |
| files.add(m.file); |
| } |
| } |
| |
| private final static class Collector { |
| public final List<File> added = new ArrayList<File>(); |
| public final List<File> removed = new ArrayList<File>(); |
| public final List<File> changed = new ArrayList<File>(); |
| } |
| |
| /** |
| * Stop periodically executing this task. If the task is currently executing it |
| * will never be run again after the current execution, otherwise it will simply |
| * never run (again). |
| */ |
| void stop() { |
| synchronized (timer) { |
| if (!stop) { |
| stop = true; |
| cancel(); |
| timer.cancel(); |
| } |
| |
| boolean interrupted = false; |
| while (!stopped) { |
| try { |
| timer.wait(); |
| } |
| catch (InterruptedException e) { |
| interrupted = true; |
| } |
| } |
| if (interrupted) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| logger.debug("Stopped file monitor for {}", this.root.file); |
| } |
| |
| /** |
| * @see java.util.TimerTask#run() |
| */ |
| public void run() { |
| synchronized (timer) { |
| stopped = false; |
| if (stop) { |
| stopped = true; |
| timer.notifyAll(); |
| return; |
| } |
| } |
| synchronized ( this ) { |
| try { |
| final Collector c = new Collector(); |
| this.check(this.root, c); |
| this.listener.updated(c.added, c.changed, c.removed); |
| } catch (Exception e) { |
| // ignore this |
| } |
| } |
| synchronized (timer) { |
| stopped = true; |
| timer.notifyAll(); |
| } |
| } |
| |
| /** |
| * Check the monitorable |
| * @param monitorable The monitorable to check |
| * @param localEA The event admin |
| */ |
| private void check(final Monitorable monitorable, final Collector collector) { |
| logger.debug("Checking {}", monitorable.file); |
| // if the file is non existing, check if it has been readded |
| if ( monitorable.status instanceof NonExistingStatus ) { |
| if ( monitorable.file.exists() ) { |
| // new file and reset status |
| createStatus(monitorable); |
| final List<File> files = new ArrayList<File>(); |
| collect(monitorable.file, files); |
| for(final File file : files ) { |
| collector.added.add(file); |
| } |
| } |
| } else { |
| // check if the file has been removed |
| if ( !monitorable.file.exists() ) { |
| // removed file and update status |
| final List<File> files = new ArrayList<File>(); |
| collectDeleted(monitorable, files); |
| for(final File file : files ) { |
| collector.removed.add(file); |
| } |
| monitorable.status = NonExistingStatus.SINGLETON; |
| } else { |
| // check for changes |
| final FileStatus fs = (FileStatus)monitorable.status; |
| boolean changed = false; |
| if ( fs.lastModified < monitorable.file.lastModified() ) { |
| fs.lastModified = monitorable.file.lastModified(); |
| // changed |
| if ( monitorable.file.isFile() ) { |
| collector.changed.add(monitorable.file); |
| } |
| changed = true; |
| } |
| if ( fs instanceof DirStatus ) { |
| // directory |
| final DirStatus ds = (DirStatus)fs; |
| for(int i=0; i<ds.children.length; i++) { |
| check(ds.children[i], collector); |
| } |
| // if the dir changed we have to update |
| if ( changed ) { |
| // and now update |
| final File[] files = monitorable.file.listFiles(); |
| if (files != null) { |
| final Monitorable[] children = new Monitorable[files.length]; |
| for (int i = 0; i < files.length; i++) { |
| // search in old list |
| for (int m = 0; m < ds.children.length; m++) { |
| if (ds.children[m].file.equals(files[i])) { |
| children[i] = ds.children[m]; |
| break; |
| } |
| } |
| if (children[i] == null) { |
| children[i] = new Monitorable(files[i]); |
| children[i].status = NonExistingStatus.SINGLETON; |
| check(children[i], collector); |
| } |
| } |
| ds.children = children; |
| } else { |
| ds.children = new Monitorable[0]; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Create a status object for the monitorable |
| */ |
| private static void createStatus(final Monitorable monitorable) { |
| if ( !monitorable.file.exists() ) { |
| monitorable.status = NonExistingStatus.SINGLETON; |
| } else if ( monitorable.file.isFile() ) { |
| monitorable.status = new FileStatus(monitorable.file); |
| } else { |
| monitorable.status = new DirStatus(monitorable.file); |
| } |
| } |
| |
| /** The monitorable to hold the resource path, the file and the status. */ |
| private static final class Monitorable { |
| public final File file; |
| public Object status; |
| |
| public Monitorable(final File file) { |
| this.file = file; |
| } |
| } |
| |
| /** Status for files. */ |
| private static class FileStatus { |
| public long lastModified; |
| public FileStatus(final File file) { |
| this.lastModified = file.lastModified(); |
| } |
| } |
| |
| /** Status for directories. */ |
| private static final class DirStatus extends FileStatus { |
| public Monitorable[] children; |
| |
| public DirStatus(final File dir) { |
| super(dir); |
| final File[] files = dir.listFiles(); |
| if (files != null) { |
| this.children = new Monitorable[files.length]; |
| for (int i = 0; i < files.length; i++) { |
| this.children[i] = new Monitorable(files[i]); |
| FileMonitor.createStatus(this.children[i]); |
| } |
| } else { |
| this.children = new Monitorable[0]; |
| } |
| } |
| } |
| |
| /** Status for non existing files. */ |
| private static final class NonExistingStatus { |
| public static NonExistingStatus SINGLETON = new NonExistingStatus(); |
| } |
| } |