package de.vanitasvitae.sync_client; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.OVERFLOW; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.jft.JingleFileTransferManager; import org.jxmpp.jid.FullJid; public class Master extends Client { private final WatchService fileWatcher; private final HashMap fileKeys; private boolean trace; private JingleFileTransferManager jftm; public Master(String username, String password, String directory) throws IOException, InterruptedException, SmackException, XMPPException { super(username, password, directory); fileWatcher = FileSystems.getDefault().newWatchService(); fileKeys = new HashMap<>(); registerFileWatcher(); jftm = JingleFileTransferManager.getInstanceFor(connection); jftm.addIncomingFileOfferListener(offer -> { try { offer.accept(connection, new File(root.toFile(), offer.getFile().getName())); } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NoResponseException | SmackException.NotConnectedException e) { e.printStackTrace(); } }); } private void registerFileWatcher() throws IOException { Files.walkFileTree(root, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { WatchKey key = dir.register(fileWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); if (trace) { Path prev = fileKeys.get(key); if (prev == null) { System.out.format("register: %s\n", dir); } else { if (!dir.equals(prev)) { System.out.format("update: %s -> %s\n", prev, dir); } } } fileKeys.put(key, dir); return FileVisitResult.CONTINUE; } }); trace = true; Async.go(() -> { try { processEvents(); } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.FeatureNotSupportedException e) { e.printStackTrace(); } }); } void processEvents() throws InterruptedException, SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { for (;;) { // wait for key to be signalled WatchKey key; try { key = fileWatcher.take(); } catch (InterruptedException x) { return; } Path dir = fileKeys.get(key); if (dir == null) { System.err.println("WatchKey not recognized!!"); continue; } for (WatchEvent event: key.pollEvents()) { WatchEvent.Kind kind = event.kind(); // TBD - provide example of how OVERFLOW event is handled if (kind == OVERFLOW) { continue; } // Context for directory entry event is the file name of entry WatchEvent ev = (WatchEvent) event; Path name = ev.context(); Path child = dir.resolve(name); // print out event System.out.format("%s: %s\n", event.kind().name(), child); // if directory is created, and watching recursively, then // register it and its sub-directories if (kind == ENTRY_CREATE) { try { if (Files.isDirectory(child, NOFOLLOW_LINKS)) { Files.walkFileTree(child, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { WatchKey key = dir.register(fileWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); if (trace) { Path prev = fileKeys.get(key); if (prev == null) { System.out.format("register: %s\n", dir); } else { if (!dir.equals(prev)) { System.out.format("update: %s -> %s\n", prev, dir); } } File[] files = dir.toFile().listFiles(); for (File f : files) { if (f.isFile()) { try { sendFile(f); } catch (InterruptedException | SmackException.FeatureNotSupportedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { e.printStackTrace(); } } } } fileKeys.put(key, dir); return FileVisitResult.CONTINUE; } }); } else if (child.toFile().isFile()) { sendFile(child.toFile()); } } catch (IOException x) { } } else if (kind == ENTRY_MODIFY && child.toFile().isFile()) { sendFile(child.toFile()); } } // reset key and remove from set if directory no longer accessible boolean valid = key.reset(); if (!valid) { fileKeys.remove(key); // all directories are inaccessible if (fileKeys.isEmpty()) { break; } } } } public void sendFile(File file) throws InterruptedException, SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { String fileName = file.getAbsolutePath(); String rootPath = root.toAbsolutePath().toString(); if (fileName.startsWith(rootPath)) { fileName = fileName.substring(rootPath.length()); } else { throw new AssertionError("Illegal path! " + fileName); } for (FullJid recipient : remotes) { jftm.sendFile(file, fileName, recipient); } } }