diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 8666ea78..77b92d4e 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -32,6 +32,7 @@ import com.muwire.core.files.UICommentEvent import com.muwire.core.files.UIPersistFilesEvent import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.DirectoryUnsharedEvent +import com.muwire.core.files.DirectoryWatchedEvent import com.muwire.core.files.DirectoryWatcher import com.muwire.core.hostcache.CacheClient import com.muwire.core.hostcache.HostCache @@ -287,7 +288,7 @@ public class Core { log.info("initializing directory watcher") directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props) - eventBus.register(FileSharedEvent.class, directoryWatcher) + eventBus.register(DirectoryWatchedEvent.class, directoryWatcher) eventBus.register(AllFilesLoadedEvent.class, directoryWatcher) eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher) @@ -331,6 +332,9 @@ public class Core { log.info("already shutting down") return } + log.info("saving settings") + File f = new File(home, "MuWire.properties") + f.withOutputStream { muOptions.write(it) } log.info("shutting down trust subscriber") trustSubscriber.stop() log.info("shutting down download manageer") diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index 3c5e9e55..aa5943da 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -37,6 +37,7 @@ class MuWireSettings { int inBw, outBw Set watchedKeywords Set watchedRegexes + Set negativeFileTree MuWireSettings() { this(new Properties()) @@ -76,6 +77,7 @@ class MuWireSettings { watchedDirectories = readEncodedSet(props, "watchedDirectories") watchedKeywords = readEncodedSet(props, "watchedKeywords") watchedRegexes = readEncodedSet(props, "watchedRegexes") + negativeFileTree = readEncodedSet(props, "negativeFileTree") trustSubscriptions = new HashSet<>() if (props.containsKey("trustSubscriptions")) { @@ -120,6 +122,7 @@ class MuWireSettings { writeEncodedSet(watchedDirectories, "watchedDirectories", props) writeEncodedSet(watchedKeywords, "watchedKeywords", props) writeEncodedSet(watchedRegexes, "watchedRegexes", props) + writeEncodedSet(negativeFileTree, "negativeFileTree", props) if (!trustSubscriptions.isEmpty()) { String encoded = trustSubscriptions.stream(). diff --git a/core/src/main/groovy/com/muwire/core/files/DirectoryWatchedEvent.groovy b/core/src/main/groovy/com/muwire/core/files/DirectoryWatchedEvent.groovy new file mode 100644 index 00000000..0212b0cf --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/files/DirectoryWatchedEvent.groovy @@ -0,0 +1,7 @@ +package com.muwire.core.files + +import com.muwire.core.Event + +class DirectoryWatchedEvent extends Event { + File directory +} diff --git a/core/src/main/groovy/com/muwire/core/files/DirectoryWatcher.groovy b/core/src/main/groovy/com/muwire/core/files/DirectoryWatcher.groovy index d90ae925..fb4efeb3 100644 --- a/core/src/main/groovy/com/muwire/core/files/DirectoryWatcher.groovy +++ b/core/src/main/groovy/com/muwire/core/files/DirectoryWatcher.groovy @@ -66,10 +66,8 @@ class DirectoryWatcher { watchService?.close() } - void onFileSharedEvent(FileSharedEvent e) { - if (!e.file.isDirectory()) - return - File canonical = e.file.getCanonicalFile() + void onDirectoryWatchedEvent(DirectoryWatchedEvent e) { + File canonical = e.directory.getCanonicalFile() Path path = canonical.toPath() WatchKey wk = path.register(watchService, kinds) watchedDirectories.put(canonical, wk) @@ -125,7 +123,8 @@ class DirectoryWatcher { private void processModified(Path parent, Path path) { File f = join(parent, path) log.fine("modified entry $f") - waitingFiles.put(f, System.currentTimeMillis()) + if (!fileManager.getNegativeTree().fileToNode.containsKey(f)) + waitingFiles.put(f, System.currentTimeMillis()) } private void processDeleted(Path parent, Path path) { @@ -133,7 +132,7 @@ class DirectoryWatcher { log.fine("deleted entry $f") SharedFile sf = fileManager.fileToSharedFile.get(f) if (sf != null) - eventBus.publish(new FileUnsharedEvent(unsharedFile : sf)) + eventBus.publish(new FileUnsharedEvent(unsharedFile : sf, deleted : true)) } private static File join(Path parent, Path path) { diff --git a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy index 6f6691a5..cefd3958 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy @@ -24,12 +24,17 @@ class FileManager { final Map> nameToFiles = new HashMap<>() final Map> commentToFile = new HashMap<>() final SearchIndex index = new SearchIndex() + final FileTree negativeTree = new FileTree() FileManager(EventBus eventBus, MuWireSettings settings) { this.settings = settings this.eventBus = eventBus + + for (String negative : settings.negativeFileTree) { + negativeTree.add(new File(negative)) + } } - + void onFileHashedEvent(FileHashedEvent e) { if (e.sharedFile != null) addToIndex(e.sharedFile) @@ -56,6 +61,13 @@ class FileManager { } existing.add(sf) fileToSharedFile.put(sf.file, sf) + + negativeTree.remove(sf.file) + String parent = sf.getFile().getParent() + if (parent != null && settings.watchedDirectories.contains(parent)) { + negativeTree.add(sf.file.getParentFile()) + } + saveNegativeTree() String name = sf.getFile().getName() Set existingFiles = nameToFiles.get(name) @@ -92,6 +104,10 @@ class FileManager { } fileToSharedFile.remove(sf.file) + if (!e.deleted && negativeTree.fileToNode.containsKey(sf.file.getParentFile())) { + negativeTree.add(sf.file) + saveNegativeTree() + } String name = sf.getFile().getName() Set existingFiles = nameToFiles.get(name) @@ -195,8 +211,10 @@ class FileManager { } rv } - + void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) { + negativeTree.remove(e.directory) + saveNegativeTree() e.directory.listFiles().each { if (it.isDirectory()) eventBus.publish(new DirectoryUnsharedEvent(directory : it)) @@ -207,4 +225,9 @@ class FileManager { } } } + + private void saveNegativeTree() { + settings.negativeFileTree.clear() + settings.negativeFileTree.addAll(negativeTree.fileToNode.keySet().collect { it.getAbsolutePath() }) + } } diff --git a/core/src/main/groovy/com/muwire/core/files/FileTree.groovy b/core/src/main/groovy/com/muwire/core/files/FileTree.groovy new file mode 100644 index 00000000..dd02389c --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/files/FileTree.groovy @@ -0,0 +1,62 @@ +package com.muwire.core.files + +class FileTree { + + private final TreeNode root = new TreeNode() + private final Map fileToNode = new HashMap<>() + + void add(File file) { + List path = new ArrayList<>() + path.add(file) + while (file.getParentFile() != null) { + path.add(file.getParentFile()) + file = file.getParentFile() + } + + Collections.reverse(path) + + TreeNode current = root + for (File element : path) { + TreeNode existing = fileToNode.get(element) + if (existing == null) { + existing = new TreeNode() + existing.file = element + existing.parent = current + fileToNode.put(element, existing) + current.children.add(existing) + } + current = existing + } + } + + boolean remove(File file) { + TreeNode node = fileToNode.remove(file) + if (node == null) { + return false + } + node.parent.children.remove(node) + if (node.parent.children.isEmpty() && node.parent != root) + remove(node.parent.file) + def copy = new ArrayList(node.children) + for (TreeNode child : copy) + remove(child.file) + true + } + + private static class TreeNode { + TreeNode parent + File file + final Set children = new HashSet<>() + + public int hashCode() { + file.hashCode() + } + + public boolean equals(Object o) { + if (!(o instanceof TreeNode)) + return false + TreeNode other = (TreeNode)o + file == other.file + } + } +} diff --git a/core/src/main/groovy/com/muwire/core/files/FileUnsharedEvent.groovy b/core/src/main/groovy/com/muwire/core/files/FileUnsharedEvent.groovy index 75691f4c..221a19c5 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileUnsharedEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileUnsharedEvent.groovy @@ -5,4 +5,5 @@ import com.muwire.core.SharedFile class FileUnsharedEvent extends Event { SharedFile unsharedFile + boolean deleted } diff --git a/core/src/main/groovy/com/muwire/core/files/HasherService.groovy b/core/src/main/groovy/com/muwire/core/files/HasherService.groovy index b0477cb2..85947e84 100644 --- a/core/src/main/groovy/com/muwire/core/files/HasherService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/HasherService.groovy @@ -47,7 +47,10 @@ class HasherService { private void process(File f) { if (f.isDirectory()) { - f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) } + eventBus.publish(new DirectoryWatchedEvent(directory : f)) + f.listFiles().each { + eventBus.publish new FileSharedEvent(file: it) + } } else { if (f.length() == 0) { eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f") diff --git a/core/src/test/groovy/com/muwire/core/files/FileTreeTest.groovy b/core/src/test/groovy/com/muwire/core/files/FileTreeTest.groovy new file mode 100644 index 00000000..12f3620b --- /dev/null +++ b/core/src/test/groovy/com/muwire/core/files/FileTreeTest.groovy @@ -0,0 +1,42 @@ +package com.muwire.core.files + +import org.junit.Test + +class FileTreeTest { + + @Test + public void testRemoveEmtpyDirs() { + File a = new File("a") + File b = new File(a, "b") + File c = new File(b, "c") + + FileTree tree = new FileTree() + tree.add(c) + + assert tree.root.children.size() == 1 + assert tree.fileToNode.size() == 3 + + tree.remove(b) + assert tree.root.children.size() == 0 + assert tree.fileToNode.isEmpty() + } + + @Test + public void testRemoveFileFromNonEmptyDir() { + File a = new File("a") + File b = new File(a,"b") + File c = new File(b, "c") + File d = new File(b, "d") + + FileTree tree = new FileTree() + tree.add(c) + + assert tree.fileToNode.size() == 3 + + tree.add(d) + assert tree.fileToNode.size() == 4 + + tree.remove(d) + assert tree.fileToNode.size() == 3 + } +} diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 8574f179..825b990e 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -28,6 +28,7 @@ import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.Downloader import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.DirectoryUnsharedEvent +import com.muwire.core.files.DirectoryWatchedEvent import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHashingEvent @@ -251,7 +252,7 @@ class MainFrameModel { void onAllFilesLoadedEvent(AllFilesLoadedEvent e) { runInsideUIAsync { - core.muOptions.watchedDirectories.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) } + core.muOptions.watchedDirectories.each { core.eventBus.publish(new DirectoryWatchedEvent(directory : new File(it))) } core.muOptions.trustSubscriptions.each { core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))