1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs.tasks;
18
19 import org.apache.commons.vfs.FileName;
20 import org.apache.commons.vfs.FileObject;
21 import org.apache.commons.vfs.FileType;
22 import org.apache.commons.vfs.NameScope;
23 import org.apache.commons.vfs.Selectors;
24 import org.apache.commons.vfs.util.Messages;
25 import org.apache.tools.ant.BuildException;
26 import org.apache.tools.ant.Project;
27
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.Set;
31 import java.util.StringTokenizer;
32
33 /***
34 * An abstract file synchronization task. Scans a set of source files and
35 * folders, and a destination folder, and performs actions on missing and
36 * out-of-date files. Specifically, performs actions on the following:
37 * <ul>
38 * <li>Missing destination file.
39 * <li>Missing source file.
40 * <li>Out-of-date destination file.
41 * <li>Up-to-date destination file.
42 * </ul>
43 *
44 * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
45 * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
46 * @todo Deal with case where dest file maps to a child of one of the source files
47 * @todo Deal with case where dest file already exists and is incorrect type (not file, not a folder)
48 * @todo Use visitors
49 * @todo Add default excludes
50 * @todo Allow selector, mapper, filters, etc to be specified.
51 * @todo Handle source/dest directories as well
52 * @todo Allow selector to be specified for choosing which dest files to sync
53 */
54 public abstract class AbstractSyncTask
55 extends VfsTask
56 {
57 private final ArrayList srcFiles = new ArrayList();
58 private String destFileUrl;
59 private String destDirUrl;
60 private String srcDirUrl;
61 private boolean srcDirIsBase;
62 private boolean failonerror = true;
63 private String filesList;
64
65 /***
66 * Sets the destination file.
67 */
68 public void setDestFile(final String destFile)
69 {
70 this.destFileUrl = destFile;
71 }
72
73 /***
74 * Sets the destination directory.
75 */
76 public void setDestDir(final String destDir)
77 {
78 this.destDirUrl = destDir;
79 }
80
81 /***
82 * Sets the source file
83 */
84 public void setSrc(final String srcFile)
85 {
86 final SourceInfo src = new SourceInfo();
87 src.setFile(srcFile);
88 addConfiguredSrc(src);
89 }
90
91 /***
92 * Sets the source directory
93 */
94 public void setSrcDir(final String srcDir)
95 {
96 this.srcDirUrl = srcDir;
97 }
98
99 /***
100 * Sets whether the source directory should be consider as the base directory.
101 */
102 public void setSrcDirIsBase(final boolean srcDirIsBase)
103 {
104 this.srcDirIsBase = srcDirIsBase;
105 }
106
107 /***
108 * Sets whether we should fail if there was an error or not
109 */
110 public void setFailonerror(final boolean failonerror)
111 {
112 this.failonerror = failonerror;
113 }
114
115 /***
116 * Sets whether we should fail if there was an error or not
117 */
118 public boolean isFailonerror()
119 {
120 return failonerror;
121 }
122
123 /***
124 * Sets the files to includes
125 */
126 public void setIncludes(final String filesList)
127 {
128 this.filesList = filesList;
129 }
130
131 /***
132 * Adds a nested <src> element.
133 */
134 public void addConfiguredSrc(final SourceInfo srcInfo)
135 throws BuildException
136 {
137 if (srcInfo.file == null)
138 {
139 final String message = Messages.getString("vfs.tasks/sync.no-source-file.error");
140 throw new BuildException(message);
141 }
142 srcFiles.add(srcInfo);
143 }
144
145 /***
146 * Executes this task.
147 */
148 public void execute() throws BuildException
149 {
150
151 if (destFileUrl == null && destDirUrl == null)
152 {
153 final String message =
154 Messages.getString("vfs.tasks/sync.no-destination.error");
155 logOrDie(message, Project.MSG_WARN);
156 return;
157 }
158
159 if (destFileUrl != null && destDirUrl != null)
160 {
161 final String message =
162 Messages.getString("vfs.tasks/sync.too-many-destinations.error");
163 logOrDie(message, Project.MSG_WARN);
164 return;
165 }
166
167
168 if (srcDirUrl != null && !srcDirUrl.equals(destDirUrl) && filesList != null && filesList.length() > 0)
169 {
170 if (!srcDirUrl.endsWith("/"))
171 {
172 srcDirUrl += "/";
173 }
174 StringTokenizer tok = new StringTokenizer(filesList, ", \t\n\r\f", false);
175 while (tok.hasMoreTokens())
176 {
177 String nextFile = tok.nextToken();
178
179
180 if (nextFile.endsWith("/**"))
181 {
182 nextFile = nextFile.substring(0, nextFile.length() - 2);
183 }
184
185 final SourceInfo src = new SourceInfo();
186 src.setFile(srcDirUrl + nextFile);
187 addConfiguredSrc(src);
188 }
189 }
190
191 if (srcFiles.size() == 0)
192 {
193 final String message = Messages.getString("vfs.tasks/sync.no-source-files.warn");
194 logOrDie(message, Project.MSG_WARN);
195 return;
196 }
197
198
199 try
200 {
201 if (destFileUrl != null)
202 {
203 handleSingleFile();
204 }
205 else
206 {
207 handleFiles();
208 }
209 }
210 catch (final BuildException e)
211 {
212 throw e;
213 }
214 catch (final Exception e)
215 {
216 throw new BuildException(e.getMessage(), e);
217 }
218 }
219
220 protected void logOrDie(final String message, int level)
221 {
222 if (!isFailonerror())
223 {
224 log(message, level);
225 return;
226 }
227 throw new BuildException(message);
228 }
229
230 /***
231 * Copies the source files to the destination.
232 */
233 private void handleFiles() throws Exception
234 {
235
236 final FileObject destFolder = resolveFile(destDirUrl);
237 destFolder.createFolder();
238
239
240 FileName srcDirName = null;
241 if (srcDirUrl !=null )
242 {
243 srcDirName = resolveFile(srcDirUrl).getName();
244 }
245 final ArrayList srcs = new ArrayList();
246 for (int i = 0; i < srcFiles.size(); i++)
247 {
248
249 final SourceInfo src = (SourceInfo) srcFiles.get(i);
250 final FileObject srcFile = resolveFile(src.file);
251 if (!srcFile.exists())
252 {
253 final String message =
254 Messages.getString("vfs.tasks/sync.src-file-no-exist.warn", srcFile);
255
256 logOrDie(message, Project.MSG_WARN);
257 }
258 else
259 {
260 srcs.add(srcFile);
261 }
262 }
263
264
265 final Set destFiles = new HashSet();
266 for (int i = 0; i < srcs.size(); i++)
267 {
268 final FileObject rootFile = (FileObject) srcs.get(i);
269 final FileName rootName = rootFile.getName();
270
271 if (rootFile.getType() == FileType.FILE)
272 {
273
274 String relName = null;
275 if (srcDirName == null || !srcDirIsBase)
276 {
277 relName = rootName.getBaseName();
278 }
279 else
280 {
281 relName = srcDirName.getRelativeName(rootName);
282 }
283 final FileObject destFile = destFolder.resolveFile(relName, NameScope.DESCENDENT);
284
285
286 handleFile(destFiles, rootFile, destFile);
287 }
288 else
289 {
290
291
292 final FileObject[] files = rootFile.findFiles(srcDirIsBase ? Selectors.SELECT_ALL : Selectors.SELECT_FILES);
293
294 for (int j = 0; j < files.length; j++)
295 {
296 final FileObject srcFile = files[j];
297
298
299 String relName = null;
300 if (srcDirName == null || !srcDirIsBase)
301 {
302 relName = rootName.getRelativeName(srcFile.getName());
303 }
304 else
305 {
306 relName = srcDirName.getRelativeName(srcFile.getName());
307 }
308
309 final FileObject destFile =
310 destFolder.resolveFile(relName, NameScope.DESCENDENT);
311
312
313 handleFile(destFiles, srcFile, destFile);
314 }
315 }
316 }
317
318
319 if (detectMissingSourceFiles())
320 {
321 final FileObject[] allDestFiles = destFolder.findFiles(Selectors.SELECT_FILES);
322 for (int i = 0; i < allDestFiles.length; i++)
323 {
324 final FileObject destFile = allDestFiles[i];
325 if (!destFiles.contains(destFile))
326 {
327 handleMissingSourceFile(destFile);
328 }
329 }
330 }
331 }
332
333 /***
334 * Handles a single file, checking for collisions where more than one
335 * source file maps to the same destination file.
336 */
337 private void handleFile(final Set destFiles,
338 final FileObject srcFile,
339 final FileObject destFile) throws Exception
340
341 {
342
343 if (destFiles.contains(destFile))
344 {
345 final String message = Messages.getString("vfs.tasks/sync.duplicate-source-files.warn", destFile);
346 logOrDie(message, Project.MSG_WARN);
347 }
348 else
349 {
350 destFiles.add(destFile);
351 }
352
353
354 handleFile(srcFile, destFile);
355 }
356
357 /***
358 * Copies a single file.
359 */
360 private void handleSingleFile() throws Exception
361 {
362
363
364 if (srcFiles.size() > 1)
365 {
366 final String message =
367 Messages.getString("vfs.tasks/sync.too-many-source-files.error");
368 logOrDie(message, Project.MSG_WARN);
369 return;
370 }
371 final SourceInfo src = (SourceInfo) srcFiles.get(0);
372 final FileObject srcFile = resolveFile(src.file);
373 if (srcFile.getType() != FileType.FILE)
374 {
375 final String message =
376 Messages.getString("vfs.tasks/sync.source-not-file.error", srcFile);
377 logOrDie(message, Project.MSG_WARN);
378 return;
379 }
380
381
382 final FileObject destFile = resolveFile(destFileUrl);
383
384
385 handleFile(srcFile, destFile);
386 }
387
388 /***
389 * Handles a single source file.
390 */
391 private void handleFile(final FileObject srcFile,
392 final FileObject destFile)
393 throws Exception
394 {
395 if (!destFile.exists()
396 || srcFile.getContent().getLastModifiedTime() > destFile.getContent().getLastModifiedTime())
397 {
398
399 handleOutOfDateFile(srcFile, destFile);
400 }
401 else
402 {
403
404 handleUpToDateFile(srcFile, destFile);
405 }
406 }
407
408 /***
409 * Handles an out-of-date file (a file where the destination file
410 * either doesn't exist, or is older than the source file).
411 * This implementation does nothing.
412 */
413 protected void handleOutOfDateFile(final FileObject srcFile,
414 final FileObject destFile)
415 throws Exception
416 {
417 }
418
419 /***
420 * Handles an up-to-date file (where the destination file exists and is
421 * newer than the source file). This implementation does nothing.
422 */
423 protected void handleUpToDateFile(final FileObject srcFile,
424 final FileObject destFile)
425 throws Exception
426 {
427 }
428
429 /***
430 * Handles a destination for which there is no corresponding source file.
431 * This implementation does nothing.
432 */
433 protected void handleMissingSourceFile(final FileObject destFile)
434 throws Exception
435 {
436 }
437
438 /***
439 * Check if this task cares about destination files with a missing source
440 * file. This implementation returns false.
441 */
442 protected boolean detectMissingSourceFiles()
443 {
444 return false;
445 }
446
447 /***
448 * Information about a source file.
449 */
450 public static class SourceInfo
451 {
452 private String file;
453
454 public void setFile(final String file)
455 {
456 this.file = file;
457 }
458 }
459
460 }