/* ************************************************************************ # # 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.filestore.local; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import divconq.filestore.CommonPath; import divconq.filestore.IFileSelector; import divconq.filestore.IFileStoreDriver; import divconq.filestore.IFileStoreFile; import divconq.filestore.IFileStoreScanner; import divconq.filestore.IMimeProvider; import divconq.filestore.select.FileSelection; import divconq.lang.op.FuncCallback; import divconq.lang.op.OperationCallback; import divconq.lang.op.OperationResult; import divconq.script.StackEntry; import divconq.struct.FieldStruct; import divconq.struct.RecordStruct; import divconq.struct.Struct; import divconq.util.FileUtil; import divconq.util.StringUtil; import divconq.xml.XElement; public class FileSystemDriver extends RecordStruct implements IFileStoreDriver, AutoCloseable { //protected HashSet<CommonPath> locallocks = new HashSet<>(); //protected ReentrantLock locallockslock = new ReentrantLock(); @Override public void setMimeProvider(IMimeProvider v) { // TODO Auto-generated method stub } public FileSystemDriver() { this.setField("RootFolder", "."); } public FileSystemDriver(Path path) { this.setField("RootFolder", path.normalize().toString()); } @Override protected void doCopy(Struct n) { super.doCopy(n); //FileSystemDriver nn = (FileSystemDriver)n; //nn.cwd = this.cwd; } @Override public Struct deepCopy() { FileSystemDriver cp = new FileSystemDriver(); this.doCopy(cp); return cp; } @Override public void close() { if (this.isTemp()) FileUtil.deleteDirectory(this.localPath()); } @Override public IFileSelector select(FileSelection selection) { return new FileSystemSelector(this, selection); } /* @Override public void toBuilder(ICompositeBuilder builder) throws BuilderStateException { builder.startRecord(); for (FieldStruct f : this.fields.values()) f.toBuilder(builder); // TODO add in FS specific fields builder.endRecord(); } @Override public Struct select(PathPart... path) { if (path.length > 0) { PathPart part = path[0]; if (part.isField()) { String fld = part.getField(); if ("Scanner".equals(fld)) return this.search; } } return super.select(path); } */ @Override public void connect(RecordStruct params, OperationCallback callback) { if ((params != null) && !params.isFieldEmpty("RootFolder")) { this.setRootFolder(params.getFieldAsString("RootFolder")); } // create root folder if we have one specified and it is not present if (!this.isFieldEmpty("RootFolder")) { Path wd = Paths.get(this.getFieldAsString("RootFolder")); if (Files.notExists(wd)) try { Files.createDirectories(wd); } catch (IOException x) { if (callback != null) callback.error("Unable to mount root folder: " + x); } } //System.out.println("cwd: " + this.getFieldAsString("RootFolder")); if (callback == null) return; callback.complete(); } public Path resolveToLocalPath(CommonPath path) { // TODO make sure the resolved path is lower than the root (fspath) path, no access to higher folders allowed via this api Path wd = Paths.get(this.getFieldAsString("RootFolder")); return wd.resolve(path.toString().substring(1)); } public Path localPath() { return Paths.get(this.getFieldAsString("RootFolder")); } @Override public CommonPath resolvePath(CommonPath path) { return CommonPath.ROOT.resolve(path); } public FileSystemFile getReference(String path) { return new FileSystemFile(FileSystemDriver.this, new RecordStruct(new FieldStruct("Path", path))); } @Override public void close(OperationCallback callback) { if (callback == null) return; callback.complete(); } @Override public void operation(final StackEntry stack, final XElement codeEl) { if ("Connect".equals(codeEl.getName())) { this.connect(null, new OperationCallback() { @Override public void callback() { stack.resume(); } }); return; } if ("Close".equals(codeEl.getName())) { this.close(new OperationCallback() { @Override public void callback() { stack.resume(); } }); return; } if ("AllocateTempDir".equals(codeEl.getName())) { File tfolder = FileUtil.allocateTempFolder(); try { this.setField("RootFolder", tfolder.getCanonicalPath()); } catch (IOException x) { // TODO Auto-generated catch block } stack.resume(); return; } if ("Delete".equals(codeEl.getName())) { FileUtil.deleteDirectory(Paths.get(this.getFieldAsString("RootFolder"))); stack.resume(); return; } if ("MakeDir".equals(codeEl.getName())) { FileUtil.confirmOrCreateDir(Paths.get(this.getFieldAsString("RootFolder"))); stack.resume(); return; } if ("GetInfo".equals(codeEl.getName())) { String path = stack.stringFromElement(codeEl, "Path"); if (StringUtil.isEmpty(path)) { // TODO log missing stack.resume(); return; } boolean absolute = stack.boolFromElement(codeEl, "Absolute", false); String handle = stack.stringFromElement(codeEl, "Handle"); if (handle != null) if (absolute) stack.addVariable(handle, new FileSystemFile(FileSystemDriver.this, Paths.get(path))); else stack.addVariable(handle, new FileSystemFile(FileSystemDriver.this, new RecordStruct(new FieldStruct("Path", path)))); stack.resume(); return; } /* if ("Put".equals(codeEl.getName())) { // TODO integrate with put method below Struct src = stack.refFromElement(codeEl, "Source"); if (!(src instanceof IFileStoreFile) && ! (src instanceof RecordStruct)) { // TODO log wrong type stack.resume(); return; } boolean relative = stack.boolFromElement(codeEl, "Relative", true); this.put((IFileStoreFile)src, relative, new FuncCallback<IFileStoreFile>() { @Override public void callback() { // TODO check errors String handle = stack.stringFromElement(codeEl, "Handle"); if (handle != null) stack.addVariable(handle, (Struct) this.getResult()); stack.resume(); } }); return; } if ("PutAll".equals(codeEl.getName())) { // TODO integrate with put method below Struct src = stack.refFromElement(codeEl, "Source"); if (src == null) { // TODO log missing stack.resume(); return; } if (!(src instanceof IItemCollection)) { // TODO log wrong type stack.resume(); return; } boolean relative = stack.boolFromElement(codeEl, "Relative", true); this.putAll((IItemCollection)src, relative, new OperationCallback() { @Override public void callback() { // TODO check errors System.out.println("done"); stack.resume(); } }); return; } */ /* if ("ChangeDirectory".equals(code.getName())) { String path = stack.stringFromElement(code, "Path"); if (StringUtil.isEmpty(path)) { // TODO log stack.resume(); return; } this.cwd = new File(path); stack.resume(); return; } if ("ScanFilter".equals(code.getName())) { String path = stack.stringFromElement(code, "Path"); ... if (StringUtil.isEmpty(path)) { // TODO log stack.resume(); return; } this.cwd = new File(path); stack.resume(); return; } */ //System.out.println("fs operation: " + code); super.operation(stack, codeEl); } @Override public IFileStoreFile wrapFileRecord(RecordStruct file) { return new FileSystemFile(this, file); } @Override public void getFileDetail(CommonPath path, FuncCallback<IFileStoreFile> callback) { FileSystemFile f = new FileSystemFile(this, path); callback.setResult(f); callback.complete(); } public String getRootFolder() { return this.getFieldAsString("RootFolder"); } public void setRootFolder(String path) { this.setField("RootFolder", path); } @Override public void addFolder(CommonPath path, FuncCallback<IFileStoreFile> callback) { // use or to track any errors during this call OperationResult or = new OperationResult(); Path localpath = this.resolveToLocalPath(path); if (Files.exists(localpath)) { if (!Files.isDirectory(localpath)) or.error("Path is not a folder: " + localpath); } else { FileUtil.confirmOrCreateDir(localpath); } if (!or.hasErrors()) callback.setResult(new FileSystemFile(this, path)); callback.complete(); } @Override public void removeFolder(CommonPath path, OperationCallback callback) { Path localpath = this.resolveToLocalPath(path); FileUtil.deleteDirectory(callback, localpath); callback.complete(); } @Override public void queryFeatures(FuncCallback<RecordStruct> callback) { // TODO Auto-generated method stub } @Override public void customCommand(RecordStruct params, FuncCallback<RecordStruct> callback) { // TODO Auto-generated method stub } @Override public IFileStoreScanner scanner() { return new FileSystemScanner(this); } @Override public IFileStoreFile rootFolder() { return new FileSystemFile(this, CommonPath.ROOT); } @Override public void getFolderListing(CommonPath path, FuncCallback<List<IFileStoreFile>> callback) { Path folder = this.resolveToLocalPath(path); if (folder == null) { callback.error("Requested path is invalid"); callback.complete(); return; } if (!Files.exists(folder)) { callback.error("Requested path does not exist"); callback.complete(); return; } if (!Files.isDirectory(folder)) { callback.error("Requested path is not a folder"); callback.complete(); return; } List<IFileStoreFile> files = new ArrayList<>(); try (Stream<Path> strm = Files.list(folder)) { strm.forEach(entry -> { FileSystemFile f = new FileSystemFile(this, entry); files.add(f); }); callback.setResult(files); } catch (IOException x) { callback.error("Problem listing files: " + x); } callback.complete(); } protected boolean tempfolder = false; public void isTemp(boolean v) { this.tempfolder = v; } public boolean isTemp() { return this.tempfolder; } /* @Override public void put(IFileStoreFile source, boolean relative, final FuncCallback<IFileStoreFile> callback) { if (source == null) { // TODO log missing callback.completed(); return; } if (! (source instanceof RecordStruct)) { // TODO log wrong type callback.completed(); return; } RecordStruct rsrc = (RecordStruct)source; String cwd = this.getFieldAsString("RootFolder"); String dfilepath = cwd + "/" + (relative ? rsrc.getFieldAsString("Path") : rsrc.getFieldAsString("Name")); System.out.println("copied to: " + dfilepath); final Path dest = Paths.get(dfilepath); OperationResult mdres = FileUtil.confirmOrCreateDir(dest.getParent()); callback.copyMessages(mdres); if (mdres.hasErrors()) { callback.completed(); return; } try { final OutputStream out = Files.newOutputStream(dest); source.copyTo(out, new OperationCallback() { @Override public void callback() { // TODO improve, check abort, etc try { out.close(); } catch (IOException x) { } callback.setResult(new FileSystemFile(FileSystemDriver.this, dest)); callback.completed(); } }); } catch (Exception x) { // TODO //ssrc.abort(); callback.completed(); } } @Override public void put(final InputStream in, long size, final IFileStoreFile dest, boolean relative, final OperationCallback callback) { if (in == null) { // TODO log missing callback.completed(); return; } if (dest == null) { // TODO log missing callback.completed(); return; } if (! (dest instanceof RecordStruct)) { // TODO log wrong type callback.completed(); return; } final RecordStruct rsrc = (RecordStruct)dest; String cwd = this.getFieldAsString("RootFolder"); String dfilepath = cwd + "/" + (relative ? rsrc.getFieldAsString("Path") : rsrc.getFieldAsString("Name")); System.out.println("copied to: " + dfilepath); try { Path destp = Paths.get(dfilepath); Files.createDirectories(destp); try { Files.copy(in, destp); } catch (IOException x) { callback.error(1, "Unable to write file"); // TODO codes } finally { IOUtil.closeQuietly(in); } callback.completed(); } catch (Exception x) { // TODO //ssrc.abort(); callback.completed(); } } @Override public void putAll(IItemCollection files, final boolean relative, final OperationCallback callback) { /* TODO restore someday // launch three threads int threadcount = 3; final IAsyncIterator<Struct> it = files.getItemsAsync().iterator(); final ReentrantLock lock = new ReentrantLock(); final CountDownCallback cdcallback = new CountDownCallback(threadcount, callback); final AtomicReference<IWork> putcmd = new AtomicReference<IWork>(); IWork copy = new IWork() { @Override public void run(final Task task) { lock.lock(); it.hasNext(new FuncCallback<Boolean>() { @Override public void callback() { // TODO logging // if not next then complete if (!this.getResult()) { System.out.println(Thread.currentThread().getId() + " done"); lock.unlock(); cdcallback.countDown(); task.complete(); return; } it.next(new FuncCallback<Struct>() { @Override public void callback() { // TODO logging lock.unlock(); final IFileStoreFile source = (IFileStoreFile) this.getResult(); System.out.println(Thread.currentThread().getId() + " start copying " + source.getPath()); FileSystemDriver.this.put(source, relative, new FuncCallback<IFileStoreFile>() { @Override public void callback() { // TODO logging System.out.println(Thread.currentThread().getId() + " done copying " + source.getPath()); // regardless of success, set thread to copy next file in list Hub.instance.getScheduler().runNow(putcmd.get()); } }); } }); } }); } }; putcmd.set(copy); for (int i = 0; i < threadcount; i++) Hub.instance.getScheduler().runNow(copy); * / } */ /* // return true if got lock @Override public boolean tryLocalLock(CommonPath path) { this.locallockslock.lock(); try { if (!this.locallocks.contains(path)) { this.locallocks.add(path); System.out.println("File locked: " + path); return true; } } finally { this.locallockslock.unlock(); } System.out.println("Failed file locked: " + path); return false; } @Override public void releaseLocalLock(CommonPath path) { this.locallockslock.lock(); try { if (this.locallocks.remove(path)) System.out.println("File unlocked: " + path); else System.out.println("Bad file unlock: " + path); } finally { this.locallockslock.unlock(); } } */ }