Skip to content

Commit aaedc88

Browse files
committed
Support for ignoring paths
1 parent 6317765 commit aaedc88

6 files changed

Lines changed: 181 additions & 24 deletions

File tree

jsync-engine/src/main/java/com/fizzed/jsync/engine/DefaultJsyncEventHandler.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ public void willEnd(VirtualFileSystem sourceVfs, VirtualPath sourcePath, Virtual
2525
}
2626

2727
@Override
28-
public void willExcludePath(VirtualPath targetPath) {
29-
log.debug("Excluding path {}", targetPath);
28+
public void willExcludePath(VirtualPath sourcePath) {
29+
log.debug("Excluding path {}", sourcePath);
30+
}
31+
32+
@Override
33+
public void willIgnorePath(VirtualPath sourcePath) {
34+
log.debug("Ignoring path {}", sourcePath);
3035
}
3136

3237
@Override

jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ public class JsyncEngine {
2828
private boolean skipPermissions;
2929
private int maxFilesMaybeModifiedLimit;
3030
private List<String> excludes;
31+
private List<String> ignores;
3132
// when running a sync
3233
private Checksum negotiatedChecksum;
34+
private List<VirtualPath> excludePaths;
35+
private List<VirtualPath> ignoreSourcePaths;
36+
private List<VirtualPath> ignoreTargetPaths;
3337

3438
public JsyncEngine() {
3539
this.eventHandler = new DefaultJsyncEventHandler();
@@ -40,7 +44,6 @@ public JsyncEngine() {
4044
this.skipPermissions = false;
4145
this.preferredChecksums = new ArrayList<>(asList(Checksum.CK, Checksum.MD5));
4246
this.maxFilesMaybeModifiedLimit = 256;
43-
this.excludes = null;
4447
}
4548

4649
public JsyncEventHandler getEventHandler() {
@@ -134,6 +137,23 @@ public JsyncEngine addExclude(String exclude) {
134137
return this;
135138
}
136139

140+
public List<String> getIgnores() {
141+
return ignores;
142+
}
143+
144+
public JsyncEngine setIgnores(List<String> ignores) {
145+
this.ignores = ignores;
146+
return this;
147+
}
148+
149+
public JsyncEngine addIgnore(String ignore) {
150+
if (this.ignores == null) {
151+
this.ignores = new ArrayList<>();
152+
}
153+
this.ignores.add(ignore);
154+
return this;
155+
}
156+
137157
public JsyncResult sync(Path sourcePath, Path targetPath, JsyncMode mode) throws IOException {
138158
// local -> local
139159
final LocalVirtualFileSystem localVfs = LocalVirtualFileSystem.open();
@@ -213,6 +233,30 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF
213233
// find the best common checksum
214234
this.negotiatedChecksum = this.negotiateChecksum(sourceVfs, targetVfs);
215235

236+
// build exclude and ignore paths
237+
if (this.excludes != null) {
238+
this.excludePaths = this.excludes.stream()
239+
.map(VirtualPath::parse)
240+
.map(sourcePathAbsFinal::resolve)
241+
.collect(toList());
242+
} else {
243+
this.excludePaths = Collections.emptyList();
244+
}
245+
246+
if (this.ignores != null) {
247+
this.ignoreSourcePaths = this.ignores.stream()
248+
.map(VirtualPath::parse)
249+
.map(sourcePathAbsFinal::resolve)
250+
.collect(toList());
251+
this.ignoreTargetPaths = this.ignores.stream()
252+
.map(VirtualPath::parse)
253+
.map(targetPathAbsFinal::resolve)
254+
.collect(toList());
255+
} else {
256+
this.ignoreSourcePaths = Collections.emptyList();
257+
this.ignoreTargetPaths = Collections.emptyList();
258+
}
259+
216260

217261
final long now = System.currentTimeMillis();
218262

@@ -239,7 +283,7 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF
239283
// as we process files, only a subset may require more advanced methods of detecting whether they were modified
240284
// since that process could be "expensive", we keep a list of files on source/target that we will defer processing
241285
// until we have a chance to do some bulk processing of checksums, etc.
242-
this.syncDirectory(0, result, excludePaths, deferredFiles, sourceVfs, sourcePathAbsFinal, targetVfs, targetPathAbsFinal);
286+
this.syncDirectory(0, result, deferredFiles, sourceVfs, sourcePathAbsFinal, targetVfs, targetPathAbsFinal);
243287
} else {
244288
// we are only syncing a file, we may need to do some more expensive checks to determine if it needs to be updated
245289
this.syncFile(result, deferredFiles, sourceVfs, sourcePathAbsFinal, targetVfs, targetPathAbsFinal);
@@ -322,7 +366,7 @@ protected void syncDeferredFiles(JsyncResult result, List<VirtualPathPair> defer
322366
deferredFiles.clear();
323367
}
324368

325-
protected void syncDirectory(int level, JsyncResult result, List<VirtualPath> excludePaths, List<VirtualPathPair> deferredFiles, VirtualFileSystem sourceVfs, VirtualPath sourcePath, VirtualFileSystem targetVfs, VirtualPath targetPath) throws IOException {
369+
protected void syncDirectory(int level, JsyncResult result, List<VirtualPathPair> deferredFiles, VirtualFileSystem sourceVfs, VirtualPath sourcePath, VirtualFileSystem targetVfs, VirtualPath targetPath) throws IOException {
326370

327371
// source needs to be a directory
328372
if (!sourcePath.isDirectory()) {
@@ -365,15 +409,23 @@ protected void syncDirectory(int level, JsyncResult result, List<VirtualPath> ex
365409
List<VirtualPath> sourceChildPaths = sourceVfs.ls(sourcePath).stream()
366410
// apply filter to source files if they are on the exclude list
367411
.filter(v -> {
368-
//log.info("Checking for exlcude of path {} with excludes {}", v, excludePaths);
369-
for (VirtualPath excludePath : excludePaths) {
370-
if (v.startsWith(excludePath)) {
412+
for (VirtualPath p : this.excludePaths) {
413+
if (v.startsWith(p)) {
371414
this.eventHandler.willExcludePath(v);
372415
return false;
373416
}
374417
}
375418
return true;
376419
})
420+
.filter(v -> {
421+
for (VirtualPath p : this.ignoreSourcePaths) {
422+
if (v.startsWith(p)) {
423+
this.eventHandler.willIgnorePath(v);
424+
return false;
425+
}
426+
}
427+
return true;
428+
})
377429
// apply filter to excluding non-regular files (such as symlinks)
378430
.filter(v -> {
379431
switch (v.getStat().getType()) {
@@ -389,7 +441,16 @@ protected void syncDirectory(int level, JsyncResult result, List<VirtualPath> ex
389441
})
390442
.collect(toList());
391443

392-
final List<VirtualPath> targetChildPaths = targetVfs.ls(targetPath);
444+
final List<VirtualPath> targetChildPaths = targetVfs.ls(targetPath).stream()
445+
.filter(v -> {
446+
for (VirtualPath p : this.ignoreTargetPaths) {
447+
if (v.startsWith(p)) {
448+
return false;
449+
}
450+
}
451+
return true;
452+
})
453+
.collect(toList());
393454

394455
// its better to work with all dirs first, then files, so we sort the files before we process them
395456
this.sortPaths(sourceChildPaths);
@@ -411,7 +472,7 @@ protected void syncDirectory(int level, JsyncResult result, List<VirtualPath> ex
411472
}
412473

413474
if (sourceChildPath.isDirectory()) {
414-
this.syncDirectory(level+1, result, excludePaths, deferredFiles, sourceVfs, sourceChildPath, targetVfs, targetChildPath);
475+
this.syncDirectory(level+1, result, deferredFiles, sourceVfs, sourceChildPath, targetVfs, targetChildPath);
415476
} else {
416477
// NOTE: it's possible syncFile will "defer" processing if a checksum is required
417478
this.syncFile(result, deferredFiles, sourceVfs, sourceChildPath, targetVfs, targetChildPath);

jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEventHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public interface JsyncEventHandler {
1515

1616
void willExcludePath(VirtualPath targetPath);
1717

18+
void willIgnorePath(VirtualPath targetPath);
19+
1820
void willCreateDirectory(VirtualPath targetPath, boolean recursively);
1921

2022
void willDeleteDirectory(VirtualPath targetPath, boolean recursively);

jsync-engine/src/test/java/com/fizzed/jsync/engine/JsyncEngineTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fizzed.crux.util.Resources;
55
import com.fizzed.jsync.vfs.ParentDirectoryMissingException;
66
import com.fizzed.jsync.vfs.PathOverwriteException;
7+
import com.fizzed.jsync.vfs.util.Permissions;
78
import org.junit.jupiter.api.BeforeEach;
89
import org.junit.jupiter.api.Test;
910
import org.slf4j.Logger;
@@ -19,6 +20,7 @@
1920
import static org.assertj.core.api.Assertions.assertThat;
2021
import static org.assertj.core.api.Assertions.within;
2122
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
2224

2325
class JsyncEngineTest {
2426
static private final Logger log = LoggerFactory.getLogger(JsyncEngineTest.class);
@@ -502,6 +504,53 @@ public void syncDirTimestamp() throws Exception {
502504
assertThat(modifiedTime(targetADir)).isCloseTo(ts, within(2, ChronoUnit.SECONDS));
503505
}
504506

507+
@Test
508+
public void syncFilePermission() throws Exception {
509+
assumeTrue(Permissions.isPosix());
510+
511+
Path sourceAFile = this.syncSourceDir.resolve("a.txt");
512+
Files.write(sourceAFile, "hello".getBytes());
513+
514+
Path targetAFile = this.syncTargetDir.resolve("a.txt");
515+
Files.write(targetAFile, "hello".getBytes());
516+
517+
Permissions.setPosixInt(sourceAFile, 0755);
518+
Permissions.setPosixInt(targetAFile, 0644);
519+
520+
JsyncResult result = new JsyncEngine()
521+
.sync(sourceAFile, this.syncTargetDir, JsyncMode.NEST);
522+
523+
assertThat(Permissions.getPosixInt(targetAFile)).isEqualTo(0755);
524+
assertThat(result.getStatsUpdated()).isEqualTo(1);
525+
assertThat(result.getFilesCreated()).isEqualTo(0);
526+
assertThat(result.getFilesDeleted()).isEqualTo(0);
527+
assertThat(result.getFilesUpdated()).isEqualTo(0);
528+
}
529+
530+
@Test
531+
public void syncFileSkipPermissions() throws Exception {
532+
assumeTrue(Permissions.isPosix());
533+
534+
Path sourceAFile = this.syncSourceDir.resolve("a.txt");
535+
Files.write(sourceAFile, "hello".getBytes());
536+
537+
Path targetAFile = this.syncTargetDir.resolve("a.txt");
538+
Files.write(targetAFile, "hello".getBytes());
539+
540+
Permissions.setPosixInt(sourceAFile, 0755);
541+
Permissions.setPosixInt(targetAFile, 0644);
542+
543+
JsyncResult result = new JsyncEngine()
544+
.setSkipPermissions(true)
545+
.sync(sourceAFile, this.syncTargetDir, JsyncMode.NEST);
546+
547+
assertThat(Permissions.getPosixInt(targetAFile)).isEqualTo(0644);
548+
assertThat(result.getStatsUpdated()).isEqualTo(0);
549+
assertThat(result.getFilesCreated()).isEqualTo(0);
550+
assertThat(result.getFilesDeleted()).isEqualTo(0);
551+
assertThat(result.getFilesUpdated()).isEqualTo(0);
552+
}
553+
505554
@Test
506555
public void syncExcludeDir() throws Exception {
507556
Path sourceADir = this.syncSourceDir.resolve("a");
@@ -547,4 +596,36 @@ public void syncExcludeNonRegularFiles() throws Exception {
547596
assertThat(this.syncTargetDir.resolve("a/b.txt")).hasContent("hello");
548597
}
549598

599+
@Test
600+
public void syncIgnoreDir() throws Exception {
601+
Path sourceADir = this.syncSourceDir.resolve("a");
602+
Path sourceBFile = this.syncSourceDir.resolve("a/b.txt");
603+
this.writeFile(sourceBFile, "hello");
604+
605+
Path sourceBDir = this.syncSourceDir.resolve("b");
606+
Path sourceCFile = this.syncSourceDir.resolve("b/c.txt");
607+
Path sourceDFile = this.syncSourceDir.resolve("b/d.txt");
608+
this.writeFile(sourceCFile, "hello");
609+
this.writeFile(sourceDFile, "hello");
610+
611+
// with directory "a" fully excluded, it actually should be deleted with --exclude
612+
Path targetADir = this.syncTargetDir.resolve("a");
613+
Path targetBFile = this.syncTargetDir.resolve("a/b.txt");
614+
this.writeFile(targetBFile, "hello");
615+
Path targetBDir = this.syncTargetDir.resolve("b");
616+
Path targetCDir = this.syncTargetDir.resolve("c");
617+
Path targetEFile = this.syncTargetDir.resolve("c/e.txt");
618+
this.writeFile(targetEFile, "hello");
619+
620+
final JsyncResult result = new JsyncEngine()
621+
.addIgnore("c")
622+
.addIgnore("b")
623+
.setDelete(true)
624+
.sync(this.syncSourceDir, this.syncTargetDir, JsyncMode.MERGE);
625+
626+
assertThat(targetADir).isDirectory();
627+
assertThat(targetBDir).doesNotExist();
628+
assertThat(targetEFile).isRegularFile();
629+
}
630+
550631
}

jsync-vfs/src/main/java/com/fizzed/jsync/vfs/LocalVirtualFileSystem.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,11 @@ static public LocalVirtualFileSystem open(Path workingDir) {
3939

4040
final VirtualPath pwd = VirtualPath.parse(currentWorkingDir.toString(), true);
4141

42-
final boolean isPosixAttributes = FileSystems.getDefault()
43-
.supportedFileAttributeViews()
44-
.contains("posix");
42+
final boolean posix = Permissions.isPosix();
4543

46-
log.debug("Detected filesystem {} has pwd={}, posix={}", name, pwd, isPosixAttributes);
44+
log.debug("Detected filesystem {} with pwd={}, posix={}, caseSensitive={}", name, pwd, posix, posix);
4745

48-
// everything is case-sensitive except windows
49-
final boolean caseSensitive = !System.getProperty("os.name").toLowerCase().contains("windows");
50-
51-
log.debug("Detected filesystem {} is case-sensitive={}", name, caseSensitive);
52-
53-
return new LocalVirtualFileSystem(name, pwd, caseSensitive, isPosixAttributes);
46+
return new LocalVirtualFileSystem(name, pwd, posix, posix);
5447
}
5548

5649
@Override
@@ -127,17 +120,15 @@ protected VirtualPath withStat(VirtualPath path) throws IOException {
127120
perms = Permissions.toPosixInt(posixAttrs.permissions());
128121
} else {
129122
// use basic permissions, usually ends up being 700 from what I can gather
130-
final Set<PosixFilePermission> simulatedPosixPermissions = Permissions.toBasicPermissions(nativePath);
131-
perms = Permissions.toPosixInt(simulatedPosixPermissions);
123+
final Set<PosixFilePermission> basicPermissions = Permissions.toBasicPermissions(nativePath);
124+
perms = Permissions.toPosixInt(basicPermissions);
132125
}
133126

134127
final VirtualFileStat stat = new VirtualFileStat(type, size, modifiedTime, accessedTime, perms);
135128

136129
return new VirtualPath(path.getParentPath(), path.getName(), type == VirtualFileType.DIR, stat);
137130
}
138131

139-
140-
141132
@Override
142133
public VirtualPath stat(VirtualPath path) throws IOException {
143134
return this.withStat(path);

jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/Permissions.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.io.IOException;
5+
import java.nio.file.FileSystems;
56
import java.nio.file.Files;
67
import java.nio.file.Path;
78
import java.nio.file.attribute.PosixFilePermission;
@@ -11,6 +12,22 @@
1112

1213
public class Permissions {
1314

15+
static public boolean isPosix() {
16+
return FileSystems.getDefault()
17+
.supportedFileAttributeViews()
18+
.contains("posix");
19+
}
20+
21+
static public int getPosixInt(Path path) throws IOException {
22+
final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
23+
return toPosixInt(permissions);
24+
}
25+
26+
static public void setPosixInt(Path path, int perms) throws IOException {
27+
final Set<PosixFilePermission> permissions = toPosixFilePermissions(perms);
28+
Files.setPosixFilePermissions(path, permissions);
29+
}
30+
1431
/**
1532
* Converts a set of {@link PosixFilePermission} to its corresponding POSIX integer representation.
1633
* The resulting integer is a bitmask representing the file permission mode.

0 commit comments

Comments
 (0)