package com.wilutions.fx.util; 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.Closeable; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import com.wilutions.itol.db.Default; // https://msdn.microsoft.com/de-de/library/windows/desktop/dd378457.aspx // http://www.codejava.net/java-se/file-io/file-change-notification-example-with-watch-service-api public class WindowsRecentFolder implements Closeable { private final File recentFolder; private final int watchMaxFiles; private final TreeSet<File> recentFiles = new TreeSet<File>(CompareFileByLastModifiedDecending.instance); private WatchThread watchThread; private static Logger log = Logger.getLogger("WindowsRecentFolder"); public final static int FOLDERS = 1; public final static int FILES = 2; public final static int FILES_AND_FOLDERS = FOLDERS | FILES; public WindowsRecentFolder() { this(Integer.MAX_VALUE); } public WindowsRecentFolder(int watchMaxFiles) { this.watchMaxFiles = watchMaxFiles; recentFolder = getRecentFolderPath(); watchThread = new WatchThread(); watchThread.start(); } public List<File> getFiles(int maxFiles, int filesOrFolders) { List<File> links = null; synchronized(recentFiles) { links = new ArrayList<File>(recentFiles); } List<File> ret = new ArrayList<File>(links.size()); for (File link : links) { try { if (WindowsShortcut.isPotentialValidLink(link)) { WindowsShortcut wlink = new WindowsShortcut(link); String fname = wlink.getRealFilename(); File file = new File(fname); boolean add = (filesOrFolders == FILES_AND_FOLDERS) || ((filesOrFolders & FILES) != 0 && file.isFile()) || ((filesOrFolders & FOLDERS) != 0 && file.isDirectory()); if (add) { ret.add(file); // if (log.isLoggable(Level.INFO)) { // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // String lastModifiedIso = sdf.format(new Date(link.lastModified())); // System.out.println("Recent file=" + link + ", lastModified=" + lastModifiedIso); // } if (ret.size() >= maxFiles) break; } } } catch (Exception e) { } } return ret; } @Override public void close() throws IOException { watchThread.done(); } private static class CompareFileByLastModifiedDecending implements Comparator<File> { public final static CompareFileByLastModifiedDecending instance = new CompareFileByLastModifiedDecending(); public int compare(File lhs, File rhs) { long cmp = rhs.lastModified() - lhs.lastModified(); if (cmp < 0) return -1; if (cmp > 0) return 1; return 0; } } private void unsync_shrinkFileListToMax(TreeSet<File> files) { int index = 0; Iterator<File> it = files.iterator(); while(index++ < watchMaxFiles && it.hasNext()) { it.next(); } while(it.hasNext()) { it.next(); it.remove(); } } private class WatchThread extends Thread { private WatchService watcher; public WatchThread() { setName("watch-recent-files"); setDaemon(true); init(); } public synchronized void done() { try { watcher.close(); } catch (IOException e) { } } private synchronized void init() { try { watcher = FileSystems.getDefault().newWatchService(); } catch (Exception e) { log.log(Level.WARNING, "Watching directory=" + recentFolder + " failed.", e); } } public void run() { try { // Initial file list synchronized(recentFiles) { recentFiles.addAll(Arrays.asList(recentFolder.listFiles())); unsync_shrinkFileListToMax(recentFiles); } recentFolder.toPath().register(watcher, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); while (!Thread.currentThread().isInterrupted()) { WatchKey key = watcher.take(); ArrayList<File> modifiedFiles = new ArrayList<>(); ArrayList<File> deletedFiles = new ArrayList<>(); for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind != OVERFLOW) { @SuppressWarnings("unchecked") WatchEvent<Path> ev = (WatchEvent<Path>) event; File file = new File(recentFolder, ev.context().toFile().getName()); if (kind == ENTRY_DELETE) { deletedFiles.add(file); } else { modifiedFiles.add(file); } } } synchronized(recentFiles) { recentFiles.removeAll(deletedFiles); recentFiles.removeAll(modifiedFiles); recentFiles.addAll(modifiedFiles); unsync_shrinkFileListToMax(recentFiles); } if (!key.reset()) break; } } catch (InterruptedException e) { } catch (Exception e) { log.log(Level.WARNING, "Watching directory=" + recentFolder + " failed.", e); } finally { done(); } } } private static File getRecentFolderPath() { File ret = null; // %APPDATA%\Microsoft\Windows\Recent String appData = Default.value(System.getenv("APPDATA")); if (appData.isEmpty()) { // legacy location: %USERPROFILE%\Recent String userProfile = Default.value(System.getenv("USERPROFILE")); if (userProfile.isEmpty()) { File dir = new File(userProfile, "Recent"); if (dir.exists()) { ret = dir; } } } else { File dir = new File(new File(new File(appData, "Microsoft"), "Windows"), "Recent"); if (dir.exists()) { ret = dir; } } return ret; } }