/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.io; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import divconq.filestore.CommonPath; import divconq.lang.op.FuncCallback; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationResult; import divconq.log.Logger; import divconq.xml.XElement; import net.contentobjects.jnotify.JNotify; import net.contentobjects.jnotify.JNotifyListener; public class LocalFileStore { protected Integer watchID = null; // absolute, normalized paths in Path and String forms protected Path path = null; protected String spath = null; protected Map<String, CacheFile> cache = new HashMap<>(); protected CopyOnWriteArrayList<FuncCallback<FileStoreEvent>> listeners = new CopyOnWriteArrayList<>(); public String getPath() { return this.spath; } public Path getFilePath() { return this.path; } public Path resolvePath(String path) { return this.path.resolve(path.startsWith("/") ? path.substring(1) : path).normalize().toAbsolutePath(); } public Path resolvePath(Path path) { if (path.isAbsolute()) { if (path.startsWith(this.path)) return path; return null; } return this.path.resolve(path).normalize().toAbsolutePath(); } public Path resolvePath(CommonPath path) { return this.path.resolve(path.toString().substring(1)).normalize().toAbsolutePath(); } public String relativize(Path path) { path = path.normalize().toAbsolutePath(); if (path == null) return null; String rpath = path.toString().replace('\\', '/'); if (!rpath.startsWith(this.spath)) return null; return rpath.substring(this.spath.length()); } // use CacheFile once and then let go, call this every time you need it or you may be holding on to stale content public CacheFile cacheResolvePath(String file) { Path lf = this.resolvePath(file); if (lf != null) { String ln = this.relativize(lf); CacheFile ra = this.cache.get(ln); if (ra != null) return ra; ra = CacheFile.fromFile(ln, lf); if (ra != null) { this.cache.put(ln, ra); return ra; } } return null; } // use CacheFile once and then let go, call this every time you need it or you may be holding on to stale content public CacheFile cacheResolvePath(Path file) { if (Logger.isDebug()) Logger.debug("cache resolve path: " + file); Path lf = this.resolvePath(file); if (Logger.isDebug()) Logger.debug("cache resolve path, resolve: " + lf); if (lf != null) { String ln = this.relativize(lf); if (Logger.isDebug()) Logger.debug("cache resolve path, relativize: " + ln); CacheFile ra = this.cache.get(ln); if (Logger.isDebug()) Logger.debug("cache resolve path, cache: " + ra); if (ra != null) return ra; ra = CacheFile.fromFile(ln, lf); if (Logger.isDebug()) Logger.debug("cache resolve path, file: " + ra); //System.out.println("rcache " + this.cache); if (ra != null) { this.cache.put(ln, ra); return ra; } } return null; } // use CacheFile once and then let go, call this every time you need it or you may be holding on to stale content public CacheFile cacheResolvePath(CommonPath file) { Path lf = this.resolvePath(file); if (lf != null) { String ln = this.relativize(lf); CacheFile ra = this.cache.get(ln); if (ra != null) return ra; ra = CacheFile.fromFile(ln, lf); if (ra != null) { this.cache.put(ln, ra); return ra; } } return null; } public boolean cacheHas(String file) { Path lf = this.resolvePath(file); if (lf != null) { String ln = this.relativize(lf); CacheFile ra = this.cache.get(ln); if (ra != null) return true; ra = CacheFile.fromFile(ln, lf); if (ra != null) { this.cache.put(ln, ra); return true; } } return false; } public void register(FuncCallback<FileStoreEvent> callback) { this.listeners.add(callback); } public void unregister(FuncCallback<FileStoreEvent> callback) { this.listeners.remove(callback); } public void fireEvent(String fname, boolean deleted) { OperationContext.useHubContext(); fname = "/" + fname.replace('\\', '/'); //System.out.println("lfs event: " + fname); this.cache.remove(fname); //CacheFile h = this.cache.remove(fname); //if (h != null) //System.out.println("removed from cache: " + fname); CommonPath p = new CommonPath(fname); if (p.getNameCount() == 0) return; // TODO create task and put it on the internal (service) work bucket //System.out.println("lfs fire event on: " + fname); FileStoreEvent evnt = new FileStoreEvent(); evnt.path = p; evnt.delete = deleted; for (FuncCallback<FileStoreEvent> cb : this.listeners) { cb.setResult(evnt); cb.complete(); } } public void start(OperationResult or, XElement fstore) { String fpath = fstore.hasAttribute("FolderPath") ? fstore.getAttribute("FolderPath") : fstore.getName().equals("PrivateFileStore") ? "./private" : fstore.getName().equals("PackageFileStore") ? "./packages" : "./public"; this.path = Paths.get(fpath).normalize().toAbsolutePath(); if (Files.exists(this.path) && !Files.isDirectory(this.path)) { or.error("File Store cannot be mounted: " + fpath); return; } try { Files.createDirectories(this.path); this.spath = this.path.toString().replace('\\', '/'); this.watchID = JNotify.addWatch( this.spath, JNotify.FILE_CREATED | JNotify.FILE_DELETED | JNotify.FILE_MODIFIED | JNotify.FILE_RENAMED, true, new JNotifyListener() { public void fileRenamed(int wd, String rootPath, String oldName, String newName) { LocalFileStore.this.fireEvent(oldName, true); LocalFileStore.this.fireEvent(newName, false); } public void fileModified(int wd, String rootPath, String name) { LocalFileStore.this.fireEvent(name, false); } public void fileDeleted(int wd, String rootPath, String name) { LocalFileStore.this.fireEvent(name, true); } public void fileCreated(int wd, String rootPath, String name) { LocalFileStore.this.fireEvent(name, false); } } ); } catch (Exception x) { or.errorTr(132, x); } } public void stop(OperationResult or) { try { if (this.watchID == null) return; JNotify.removeWatch(this.watchID); } catch (Exception x) { // unimportant } } }