View Javadoc
1   package org.argeo.fs;
2   
3   import java.io.IOException;
4   import java.nio.file.FileVisitResult;
5   import java.nio.file.Files;
6   import java.nio.file.Path;
7   import java.nio.file.SimpleFileVisitor;
8   import java.nio.file.StandardCopyOption;
9   import java.nio.file.attribute.BasicFileAttributes;
10  import java.nio.file.attribute.FileTime;
11  
12  import org.argeo.sync.SyncResult;
13  
14  /** Synchronises two directory structures. */
15  public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
16  	// TODO make it configurable
17  	private boolean trace = false;
18  
19  	private final Path sourceBasePath;
20  	private final Path targetBasePath;
21  	private final boolean delete;
22  	private final boolean recursive;
23  
24  	private SyncResult<Path> syncResult = new SyncResult<>();
25  
26  	public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
27  		this.sourceBasePath = sourceBasePath;
28  		this.targetBasePath = targetBasePath;
29  		this.delete = delete;
30  		this.recursive = recursive;
31  	}
32  
33  	@Override
34  	public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
35  		if (!recursive && !sourceDir.equals(sourceBasePath))
36  			return FileVisitResult.SKIP_SUBTREE;
37  		Path targetDir = toTargetPath(sourceDir);
38  		Files.createDirectories(targetDir);
39  		return FileVisitResult.CONTINUE;
40  	}
41  
42  	@Override
43  	public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
44  		if (delete) {
45  			Path targetDir = toTargetPath(sourceDir);
46  			for (Path targetPath : Files.newDirectoryStream(targetDir)) {
47  				Path sourcePath = sourceDir.resolve(targetPath.getFileName());
48  				if (!Files.exists(sourcePath)) {
49  					try {
50  						FsUtils.delete(targetPath);
51  						deleted(targetPath);
52  					} catch (Exception e) {
53  						deleteFailed(targetPath, exc);
54  					}
55  				}
56  			}
57  		}
58  		return FileVisitResult.CONTINUE;
59  	}
60  
61  	@Override
62  	public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
63  		Path targetFile = toTargetPath(sourceFile);
64  		try {
65  			if (!Files.exists(targetFile)) {
66  				Files.copy(sourceFile, targetFile);
67  				added(sourceFile, targetFile);
68  			} else {
69  				if (shouldOverwrite(sourceFile, targetFile)) {
70  					Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
71  				}
72  			}
73  		} catch (Exception e) {
74  			copyFailed(sourceFile, targetFile, e);
75  		}
76  		return FileVisitResult.CONTINUE;
77  	}
78  
79  	protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
80  		long sourceSize = Files.size(sourceFile);
81  		long targetSize = Files.size(targetFile);
82  		if (sourceSize != targetSize) {
83  			return true;
84  		}
85  		FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
86  		FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
87  		if (sourceLastModif.compareTo(targetLastModif) > 0)
88  			return true;
89  		return shouldOverwriteLaterSameSize(sourceFile, targetFile);
90  	}
91  
92  	protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
93  		return false;
94  	}
95  
96  //	@Override
97  //	public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
98  //		error("Cannot sync " + sourceFile, exc);
99  //		return FileVisitResult.CONTINUE;
100 //	}
101 
102 	private Path toTargetPath(Path sourcePath) {
103 		Path relativePath = sourceBasePath.relativize(sourcePath);
104 		Path targetPath = targetBasePath.resolve(relativePath.toString());
105 		return targetPath;
106 	}
107 
108 	public Path getSourceBasePath() {
109 		return sourceBasePath;
110 	}
111 
112 	public Path getTargetBasePath() {
113 		return targetBasePath;
114 	}
115 
116 	protected void added(Path sourcePath, Path targetPath) {
117 		syncResult.getAdded().add(targetPath);
118 		if (isTraceEnabled())
119 			trace("Added " + sourcePath + " as " + targetPath);
120 	}
121 
122 	protected void modified(Path sourcePath, Path targetPath) {
123 		syncResult.getModified().add(targetPath);
124 		if (isTraceEnabled())
125 			trace("Overwritten from " + sourcePath + " to " + targetPath);
126 	}
127 
128 	protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
129 		syncResult.addError(sourcePath, targetPath, e);
130 		if (isTraceEnabled())
131 			error("Cannot copy " + sourcePath + " to " + targetPath, e);
132 	}
133 
134 	protected void deleted(Path targetPath) {
135 		syncResult.getDeleted().add(targetPath);
136 		if (isTraceEnabled())
137 			trace("Deleted " + targetPath);
138 	}
139 
140 	protected void deleteFailed(Path targetPath, Exception e) {
141 		syncResult.addError(null, targetPath, e);
142 		if (isTraceEnabled())
143 			error("Cannot delete " + targetPath, e);
144 	}
145 
146 	/** Log error. */
147 	protected void error(Object obj, Throwable e) {
148 		System.err.println(obj);
149 		e.printStackTrace();
150 	}
151 
152 	protected boolean isTraceEnabled() {
153 		return trace;
154 	}
155 
156 	protected void trace(Object obj) {
157 		System.out.println(obj);
158 	}
159 
160 	public SyncResult<Path> getSyncResult() {
161 		return syncResult;
162 	}
163 
164 }