package divconq.filestore.select; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import divconq.ctp.CtpConstants; import divconq.filestore.CommonPath; import divconq.filestore.IFileStoreFile; import divconq.struct.ListStruct; import divconq.struct.RecordStruct; import divconq.util.StringUtil; /* Select: { Mode: "[Detail]|Listing|Expanded", RelativeTo: "the base path for all Files Sets, defaults to /", // Select contains either File (fields below) or File Sets, not both - File checked first Path: "path relative to RelativeTo", Recursion: N [1] - where 0 means none, 1 means 1 level, etc, Rename: "optional, new name, used during MOVE, name only no path", Offset: "optional, bytes from start, used during READ for resume download", // if no file sets listed then match every thing under RelativeTo FileSets: [ // each file set is independent and cumulative - list within a list [ { Type: "File", // select specific files/folders Path: "path relative to RelativeTo", Recursion: N [1] - where 0 means none, 1 means 1 level, etc, Rename: "optional, new name, used during MOVE, name only no path", Offset: "optional, bytes from start, used during READ for resume download", Not: t/[f] }, { Type: "NameFilter", Pattern: "reg ex for name", Not: t/[f] }, { Type: "PathFilter", Pattern: "reg ex for path relative to RelativeTo", Not: t/[f] }, { Type: "SizeFilter", // conditions may be combined Equal: N, LessThan: N, GreaterThan: N, LessThanOrEqual: N, GreaterThanOrEqual: N, Not: t/[f] }, { Type: "ModifiedFilter", // if time is left off then matches on DATE only Equal: "ISO UTC DATE/TIME", LessThan: "ISO UTC DATE", GreaterThan: "ISO UTC DATE", LessThanOrEqual: "ISO UTC DATE", GreaterThanOrEqual: "ISO UTC DATE", Not: t/[f] } ] ], Sort: { // Match gets value from first RegEx pattern in filter Type: "[Name]|Path|Modified|Size|Match|Value", // if any sort is defined but not Type then Name is default Direction: "Asc|Desc", SortAs: "Number|[String]", // used with Match Value: "mix in %attrib% with text" // used with Value }, Attributes: [ 10, 20 ] } } * */ public class FileSelection { protected CommonPath relativeTo = CommonPath.ROOT; protected List<List<FileMatcher>> sets = new ArrayList<>(); protected List<Integer> attributes = new ArrayList<>(); protected FileSortType sortType = FileSortType.Name; protected FileSortDirection sortDirection = FileSortDirection.Ascending; protected FileSortAs sortAs = FileSortAs.String; protected String sortValueTemplate = null; protected FileSelectionMode mode = FileSelectionMode.Detail; public FileSelection withRelativeTo(CommonPath v) { this.relativeTo = v; return this; } public FileSelection withRelativeTo(String v) { this.relativeTo = new CommonPath(v); return this; } public CommonPath relativeTo() { return this.relativeTo; } public FileSelection withFileSet(FileMatcher... v) { List<FileMatcher> set = new ArrayList<>(); for (FileMatcher m : v) set.add(m); this.sets.add(set); return this; } public FileSelection withMode(FileSelectionMode v) { this.mode = v; return this; } public FileSelection withFileSet(CommonPath v) { this.withFileSet(new FileMatcherFile().withPath(v)); return this; } public FileSelection withFileSet(CommonPath path, int recursion) { this.withFileSet(new FileMatcherFile().withPath(path).withRecursion(recursion)); return this; } public FileSelection withFileSet(String path) { this.withFileSet(new FileMatcherFile().withPath(new CommonPath(path))); return this; } public FileSelection withFileSet(String path, int recursion) { this.withFileSet(new FileMatcherFile().withPath(new CommonPath(path)).withRecursion(recursion)); return this; } // see Ctp.CTP_F_ATTR_* public boolean hasAttr(int attr) { return this.attributes.contains(attr); } public FileSelection withAttrs(Integer... attrs) { for (Integer attr : attrs) this.attributes.add(attr); return this; } public FileSelection withSize() { this.attributes.add(CtpConstants.CTP_F_ATTR_SIZE); return this; } public FileSelection withModified() { this.attributes.add(CtpConstants.CTP_F_ATTR_MODTIME); return this; } public FileSelection withPermissions() { this.attributes.add(CtpConstants.CTP_F_ATTR_PERMISSIONS); return this; } public boolean hasContent() { return this.attributes.contains(CtpConstants.CTP_F_ATTR_DATA); } public FileSelection withContent() { this.attributes.add(CtpConstants.CTP_F_ATTR_DATA); return this; } public FileSelectionMode getMode() { return this.mode; } public void setMode(FileSelectionMode v) { this.mode = v; } // value shall be the first value matched by the approving Set public boolean approve(IFileStoreFile file, AtomicReference<String> value) { for (List<FileMatcher> set : this.sets) { boolean setApproved = true; for (FileMatcher m : set) { if (!m.approve(file, value, this)) { setApproved = false; break; } } if (setApproved) return true; value.set(null); // reset, only accept a value from approving set } return false; } // only FileMatcherFile can be a source of file lists, find them public List<FileMatcherFile> searchList() { this.init(); List<FileMatcherFile> list = new ArrayList<>(); for (List<FileMatcher> set : this.sets) { for (FileMatcher m : set) { if (m instanceof FileMatcherFile) { FileMatcherFile mf = (FileMatcherFile) m; // if it is an exclude ignore it, really it is a filter with the NOT if (!mf.exclude) list.add(mf); } } } return list; } public void init() { for (List<FileMatcher> set : this.sets) { for (FileMatcher m : set) { if (m instanceof FileMatcherFile) { FileMatcherFile mf = (FileMatcherFile) m; mf.expandedPath = this.relativeTo.resolve(mf.path); } } } } /* { Mode: "[Detail]|Listing|Download", RelativeTo: "the base path for all Files Sets, defaults to /", // Select contains either File (fields below) or File Sets, not both - File checked first Path: "path relative to RelativeTo", Rename: "optional, new name, used during MOVE, name only no path", Offset: "optional, bytes from start, used during READ for resume download", } * */ public FileSelection withInstructions(RecordStruct inst) { this.mode = FileSelectionMode.valueOf(inst.getFieldAsString("Mode")); if (!inst.isFieldEmpty("RelativeTo")) this.relativeTo = new CommonPath(inst.getFieldAsString("RelativeTo")); if (!inst.isFieldEmpty("Path")) { FileMatcherFile m = new FileMatcherFile() .withPath(new CommonPath(inst.getFieldAsString("Path"))); if (!inst.isFieldEmpty("Rename")) m.withRename(inst.getFieldAsString("Rename")); if (!inst.isFieldEmpty("Offset")) m.withOffset(inst.getFieldAsInteger("Offset")); this.withFileSet(m); } if (!inst.isFieldEmpty("Attributes")) inst.getFieldAsList("Attributes").integerStream() .forEach(num -> this.attributes.add(num.intValue())); return this; } public RecordStruct toInstructions() { RecordStruct inst = new RecordStruct(); inst.setField("Mode", this.mode.toString()); inst.setField("RelativeTo", this.relativeTo.toString()); if (this.attributes.size() > 0) inst.setField("Attributes", new ListStruct(this.attributes)); if (this.sets.size() == 0) return inst; // TODO get more advanced than this in checking filters/options List<FileMatcher> set = this.sets.get(0); if (set.size() == 0) return inst; FileMatcher fm = set.get(0); if (!(fm instanceof FileMatcherFile)) return inst; FileMatcherFile fmf = (FileMatcherFile) fm; inst.setField("Path", fmf.path.toString()); if (StringUtil.isNotEmpty(fmf.newname)) inst.setField("Rename", fmf.newname); if (fmf.offset > 0) inst.setField("Offset", fmf.offset); return inst; } }