Skip to main content
summaryrefslogtreecommitdiffstats
blob: 4a02382a56912c5a4130e30b408a62c7e3e4fb1b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*******************************************************************************
 *  Copyright (c) 2007, 2017 aQute and others.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 * 
 *  Contributors:
 *  aQute - initial implementation and ideas 
 *  IBM Corporation - initial adaptation to Equinox provisioning use
 *******************************************************************************/
package org.eclipse.equinox.internal.provisional.p2.directorywatcher;

import java.io.File;
import java.util.*;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.BundleContext;

public class DirectoryWatcher {
	private static final String DEL_EXT = ".del"; //$NON-NLS-1$

	public class WatcherThread extends Thread {

		private final long pollFrequency;
		private boolean done = false;

		public WatcherThread(long pollFrequency) {
			super("Directory Watcher"); //$NON-NLS-1$
			this.pollFrequency = pollFrequency;
		}

		@Override
		public void run() {
			do {
				try {
					poll();
					synchronized (this) {
						wait(pollFrequency);
					}
				} catch (InterruptedException e) {
					// ignore
				} catch (Throwable e) {
					log(Messages.error_main_loop, e);
					done = true;
				}
			} while (!done);
		}

		public synchronized void done() {
			done = true;
			notify();
		}
	}

	public final static String POLL = "eclipse.p2.directory.watcher.poll"; //$NON-NLS-1$
	public final static String DIR = "eclipse.p2.directory.watcher.dir"; //$NON-NLS-1$
	private static final long DEFAULT_POLL_FREQUENCY = 2000;

	public static void log(String string, Throwable e) {
		System.err.println(string + ": " + e); //$NON-NLS-1$
	}

	final File[] directories;

	long poll = 2000;
	private Set<DirectoryChangeListener> listeners = new HashSet<>();
	private HashSet<File> scannedFiles = new HashSet<>();
	private HashSet<File> removals;
	private Set<File> pendingDeletions;
	private WatcherThread watcher;

	public DirectoryWatcher(Map<String, String> properties, BundleContext context) {
		String dir = properties.get(DIR);
		if (dir == null)
			dir = "./load"; //$NON-NLS-1$

		File targetDirectory = new File(dir);
		targetDirectory.mkdirs();
		directories = new File[] {targetDirectory};
	}

	public DirectoryWatcher(File directory) {
		if (directory == null)
			throw new IllegalArgumentException(Messages.null_folder);

		this.directories = new File[] {directory};
	}

	public DirectoryWatcher(File[] directories) {
		if (directories == null)
			throw new IllegalArgumentException(Messages.null_folder);
		this.directories = directories;
	}

	public synchronized void addListener(DirectoryChangeListener listener) {
		listeners.add(listener);
	}

	public synchronized void removeListener(DirectoryChangeListener listener) {
		listeners.remove(listener);
	}

	public void start() {
		start(DEFAULT_POLL_FREQUENCY);
	}

	public synchronized void poll() {
		startPoll();
		scanDirectories();
		stopPoll();
	}

	public synchronized void start(final long pollFrequency) {
		if (watcher != null)
			throw new IllegalStateException(Messages.thread_started);

		watcher = new WatcherThread(pollFrequency);
		watcher.start();
	}

	public synchronized void stop() {
		if (watcher == null)
			throw new IllegalStateException(Messages.thread_not_started);

		watcher.done();
		watcher = null;
	}

	public File[] getDirectories() {
		return directories;
	}

	private void startPoll() {
		removals = scannedFiles;
		scannedFiles = new HashSet<>();
		pendingDeletions = new HashSet<>();
		for (DirectoryChangeListener listener : listeners)
			listener.startPoll();
	}

	private void scanDirectories() {
		for (int index = 0; index < directories.length; index++) {
			File directory = directories[index];
			File list[] = directory.listFiles();
			if (list == null)
				continue;
			for (int i = 0; i < list.length; i++) {
				File file = list[i];
				// if this is a deletion marker then add to the list of pending deletions.
				if (list[i].getPath().endsWith(DEL_EXT)) {
					File target = new File(file.getPath().substring(0, file.getPath().length() - 4));
					removals.add(target);
					pendingDeletions.add(target);
				} else {
					// else remember that we saw the file and remove it from this list of files to be 
					// removed at the end.  Then notify all the listeners as needed.
					scannedFiles.add(file);
					removals.remove(file);
					for (DirectoryChangeListener listener : listeners) {
						if (isInterested(listener, file))
							processFile(file, listener);
					}
				}
			}
		}
	}

	private void stopPoll() {
		notifyRemovals();
		removals = scannedFiles;
		for (DirectoryChangeListener listener : listeners)
			listener.stopPoll();
		processPendingDeletions();
	}

	private boolean isInterested(DirectoryChangeListener listener, File file) {
		return listener.isInterested(file);
	}

	/**
	 * Notify the listeners of the files that have been deleted or marked for deletion.
	 */
	private void notifyRemovals() {
		Set<File> removed = removals;
		for (DirectoryChangeListener listener : listeners) {
			for (File file : removed) {
				if (isInterested(listener, file))
					listener.removed(file);
			}
		}
	}

	private void processFile(File file, DirectoryChangeListener listener) {
		try {
			Long oldTimestamp = listener.getSeenFile(file);
			if (oldTimestamp == null) {
				// The file is new
				listener.added(file);
			} else {
				// The file is not new but may have changed
				long lastModified = file.lastModified();
				if (oldTimestamp.longValue() != lastModified)
					listener.changed(file);
			}
		} catch (Exception e) {
			log(NLS.bind(Messages.error_processing, listener), e);
		}
	}

	/**
	 * Try to remove the files that have been marked for deletion.
	 */
	private void processPendingDeletions() {
		for (Iterator<File> iterator = pendingDeletions.iterator(); iterator.hasNext();) {
			File file = iterator.next();
			if (!file.exists() || file.delete())
				iterator.remove();
			new File(file.getPath() + DEL_EXT).delete();
		}
	}

}

Back to the top