/* ************************************************************************
#
# 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.cms.service;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import divconq.bus.IService;
import divconq.bus.Message;
import divconq.bus.MessageUtil;
import divconq.cms.feed.CollectContext;
import divconq.cms.feed.DeleteMode;
import divconq.cms.feed.FeedInfo;
import divconq.cms.feed.FeedIndexer;
import divconq.db.DataRequest;
import divconq.db.ObjectFinalResult;
import divconq.db.ObjectResult;
import divconq.db.ReplicatedDataRequest;
import divconq.db.query.CollectorField;
import divconq.db.query.SelectDirectRequest;
import divconq.db.query.SelectFields;
import divconq.db.query.WhereEqual;
import divconq.db.query.WhereField;
import divconq.db.update.InsertRecordRequest;
import divconq.filestore.CommonPath;
import divconq.filestore.IFileStoreFile;
import divconq.filestore.local.FileSystemDriver;
import divconq.hub.DomainInfo;
import divconq.hub.Hub;
import divconq.io.LocalFileStore;
import divconq.lang.CountDownCallback;
import divconq.lang.op.FuncCallback;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationCallback;
import divconq.lang.op.OperationContext;
import divconq.log.Logger;
import divconq.mod.ExtensionBase;
import divconq.session.Session;
import divconq.session.DataStreamChannel;
import divconq.struct.CompositeStruct;
import divconq.struct.FieldStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.util.IOUtil;
import divconq.util.MimeUtil;
import divconq.util.StringUtil;
import divconq.work.TaskRun;
import divconq.xml.XElement;
import divconq.xml.XmlReader;
public class CmsService extends ExtensionBase implements IService {
protected FileSystemDriver fsd = new FileSystemDriver();
protected Session channels = null;
protected String bestEvidence = null;
protected String minEvidence = null;
@Override
public void init(XElement config) {
super.init(config);
this.fsd.setRootFolder(".\temp");
this.bestEvidence = "SHA256";
this.minEvidence = "Size";
LocalFileStore pubfs = Hub.instance.getPublicFileStore();
// TODO interleave private - LocalFileStore prifs = Hub.instance.getPrivateFileStore();
if (pubfs != null) {
// TODO interleave www-preview - domain's file system
// Path wpath = this.getWebFile(pubfs, "/dcw/" + this.alias + "/www-preview", path);
// look in the domain's file system
//Path wpath = this.getWebFile(pubfs, "/dcw/" + this.alias + "/www", path);
//if ("galleries".equals(path.getName(0)) || "files".equals(path.getName(0)))
// wpath = this.getWebFile(pubfs, "/dcw/" + this.alias + "/", path);
this.fsd.setRootFolder(pubfs.getPath());
// don't wait on this, it'll log correctly
this.fsd.connect(null, new OperationCallback() {
@Override
public void callback() {
// NA
}
});
}
this.channels = Hub.instance.getSessions().createForService();
}
@Override
public void handle(TaskRun request) {
Message msg = (Message) request.getTask().getParams();
String feature = msg.getFieldAsString("Feature");
String op = msg.getFieldAsString("Op");
if ("DomainFileStore".equals(feature) || "WebFileStore".equals(feature)) {
DomainInfo domain = OperationContext.get().getUserContext().getDomain();
LocalFileStore pubfs = Hub.instance.getPublicFileStore();
if (pubfs == null) {
request.error("Missing file store");
request.complete();
return;
}
CommonPath sectionpath = "DomainFileStore".equals(feature)
? new CommonPath("/dcw/" + domain.getAlias() + "/")
: new CommonPath("/dcw/" + domain.getAlias() + "/files");
if ("FileDetail".equals(op)) {
this.handleFileDetail(request, this.fsd, sectionpath);
return;
}
if ("DomainFileStore".equals(feature) && "ImportUIFile".equals(op)) {
this.handleImportUIFile(request, this.fsd, sectionpath);
return;
}
if ("DomainFileStore".equals(feature) && "LoadFile".equals(op)) {
this.handleLoadFile(request, this.fsd, sectionpath);
return;
}
if ("DomainFileStore".equals(feature) && "SaveFile".equals(op)) {
this.handleSaveFile(request, this.fsd, sectionpath);
return;
}
if ("DeleteFile".equals(op)) {
this.handleDeleteFile(request, this.fsd, sectionpath);
return;
}
if ("DeleteFolder".equals(op)) {
this.handleDeleteFolder(request, this.fsd, sectionpath);
return;
}
if ("AddFolder".equals(op)) {
this.handleAddFolder(request, this.fsd, sectionpath);
return;
}
if ("ListFiles".equals(op)) {
this.handleListFiles(request, this.fsd, sectionpath, "DomainFileStore".equals(feature));
return;
}
if ("StartUpload".equals(op)) {
this.handleStartUpload(request, this.fsd, sectionpath);
return;
}
if ("FinishUpload".equals(op)) {
this.handleFinishUpload(request, this.fsd, sectionpath);
return;
}
if ("StartDownload".equals(op)) {
this.handleStartDownload(request, this.fsd, sectionpath);
return;
}
if ("FinishDownload".equals(op)) {
this.handleFinishDownload(request, this.fsd, sectionpath);
return;
}
}
if ("WebGallery".equals(feature)) {
DomainInfo domain = OperationContext.get().getUserContext().getDomain();
LocalFileStore pubfs = Hub.instance.getPublicFileStore();
if (pubfs == null) {
request.error("Missing file store");
request.complete();
return;
}
CommonPath sectionpath = new CommonPath("/dcw/" + domain.getAlias() + "/galleries");
if ("ListFiles".equals(op)) {
this.handleListGallery(request, this.fsd, sectionpath);
return;
}
if ("ListVariations".equals(op)) {
this.handleListGalleryVariations(request, this.fsd, sectionpath);
return;
}
if ("FileDetail".equals(op)) {
this.handleFileDetail(request, this.fsd, sectionpath);
return;
}
if ("ImageDetail".equals(op)) {
this.handleImageDetail(request, this.fsd, sectionpath);
return;
}
if ("LoadSlideShow".equals(op)) {
this.handleLoadSlideShow(request, this.fsd, sectionpath);
return;
}
if ("UpdateGallery".equals(op)) {
this.handleUpdateGallery(request, this.fsd, sectionpath);
return;
}
if ("DeleteFile".equals(op)) {
this.handleDeleteFile(request, this.fsd, sectionpath);
return;
}
if ("DeleteFolder".equals(op)) {
this.handleDeleteFolder(request, this.fsd, sectionpath);
return;
}
if ("AddFolder".equals(op)) {
this.handleAddGalleryFolder(request, this.fsd, sectionpath);
return;
}
if ("AddImage".equals(op)) {
this.handleAddGalleryImage(request, this.fsd, sectionpath);
return;
}
if ("StartUpload".equals(op)) {
this.handleStartUpload(request, this.fsd, sectionpath);
return;
}
if ("FinishUpload".equals(op)) {
this.handleFinishUpload(request, this.fsd, sectionpath);
return;
}
if ("StartDownload".equals(op)) {
this.handleStartDownload(request, this.fsd, sectionpath);
return;
}
if ("FinishDownload".equals(op)) {
this.handleFinishDownload(request, this.fsd, sectionpath);
return;
}
}
// =========================================================
// custom file store
// =========================================================
if ("Buckets".equals(feature)) {
Buckets.handle(request, op, msg);
return;
}
// =========================================================
// store categories
// =========================================================
if ("Category".equals(feature)) {
Products.handleCategories(request, this.fsd, op, msg);
return;
}
// =========================================================
// store products
// =========================================================
if ("Product".equals(feature)) {
Products.handleProducts(request, this.fsd, op, msg);
return;
}
// =========================================================
// store coupons
// =========================================================
if ("Coupons".equals(feature)) {
Products.handleCoupons(request, this.fsd, op, msg);
return;
}
// =========================================================
// cms Site
// =========================================================
if ("Site".equals(feature)) {
if ("BuildMap".equals(op)) {
this.handleSiteBuildMap(request);
return;
}
if ("ImportSite".equals(op)) {
FeedIndexer iutil = new FeedIndexer();
iutil.collectDomain(new CollectContext().forIndex());
iutil.importDomain(new OperationCallback() {
@Override
public void callback() {
request.complete();
}
});
return;
}
}
// =========================================================
// cms Threads
// =========================================================
if ("Threads".equals(feature)) {
DataRequest req = null;
if ("NewThread".equals(op) || "UpdateThreadCore".equals(op) || "ChangePartiesAction".equals(op) || "ChangeFolderAction".equals(op) || "AddContentAction".equals(op) || "ChangeStatusAction".equals(op) || "ChangeLabelsAction".equals(op))
req = new ReplicatedDataRequest("dcmThread" + op)
.withParams(msg.getFieldAsComposite("Body"));
else if ("ThreadDetail".equals(op) || "FolderListing".equals(op) || "FolderCounting".equals(op))
req = new DataRequest("dcmThread" + op)
.withParams(msg.getFieldAsComposite("Body"));
if (req != null) {
Hub.instance.getDatabase().submit(req, new ObjectFinalResult(request));
return;
}
}
// =========================================================
// cms Feeds
// =========================================================
if ("Feeds".equals(feature)) {
if ("ListPages".equals(op)) {
this.handleListPages(request);
return;
}
if ("AddPageFolder".equals(op)) {
this.handleAddPageFolder(request);
return;
}
if ("LoadFeedsDefinition".equals(op)) {
this.handleLoadFeedsDefinitions(request);
return;
}
if ("LoadList".equals(op)) {
this.handleFeedLoadList(request);
return;
}
if ("AddFeedFiles".equals(op)) {
this.handleAddFeedFiles(request);
return;
}
if ("AddPageFiles".equals(op)) {
this.handleAddPageFiles(request);
return;
}
if ("LoadFeedFiles".equals(op)) {
this.handleLoadFeedFiles(request);
return;
}
if ("UpdateFeedFiles".equals(op)) {
this.handleUpdateFeedFiles(request);
return;
}
if ("UpdatePublishFeedFiles".equals(op)) {
this.handleUpdatePublishFeedFiles(request);
return;
}
if ("PublishFeedFiles".equals(op)) {
this.handlePublishFeedFiles(request);
return;
}
if ("ImportFeedFiles".equals(op)) {
this.handleImportFeedFiles(request);
return;
}
if ("DeleteFeedFiles".equals(op)) {
this.handleDeleteFeedFiles(request);
return;
}
}
request.errorTr(441, this.serviceName(), feature, op);
request.complete();
}
public void handleListPages(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String channel = "Pages";
String path = rec.getFieldAsString("Path");
String site = rec.getFieldAsString("Site");
// link to fake so we can use FeedInfo to locate the folders
FeedInfo fi = FeedInfo.buildInfo(site, channel, path + "/_fake.dcf.xml");
Path pubpath = fi.getPubpath().getParent();
Path prepath = fi.getPrepath().getParent();
Map<String, RecordStruct> collected = new HashMap<>();
// code to list files
BiConsumer<Path, Boolean> listing = new BiConsumer<Path, Boolean>() {
@Override
public void accept(Path apath, Boolean preview) {
Path wwwsrc1 = apath.toAbsolutePath().normalize();
if (!Files.exists(wwwsrc1))
return;
try {
Files.list(wwwsrc1).forEach(new Consumer<Path>() {
@Override
public void accept(Path sfile) {
Path relpath = wwwsrc1.relativize(sfile);
String fname = relpath.getFileName().toString();
// only collect dcf files and folders
boolean isdcf = fname.endsWith(".dcf.xml");
boolean isfolder = Files.isDirectory(sfile);
if (!isdcf && !isfolder)
return;
if (isdcf)
fname = fname.substring(0, fname.length() - 8);
// skip if already in the collect list
if (collected.containsKey(fname))
return;
try {
RecordStruct fdata = new RecordStruct();
fdata.setField("FileName", fname);
fdata.setField("IsFolder", isfolder);
fdata.setField("IsPreview", preview);
fdata.setField("LastModified", new DateTime(Files.getLastModifiedTime(sfile).toMillis(), DateTimeZone.UTC));
fdata.setField("Size", Files.size(sfile));
collected.put(fname, fdata);
}
catch (IOException x) {
Logger.error("Error collecting file: " + fname + " : " + x);
}
}
});
}
catch (IOException x) {
Logger.error("Error collecting files: " + path + " : " + x);
}
}
};
listing.accept(prepath, true);
listing.accept(pubpath, false);
ListStruct files = new ListStruct();
request.setResult(files);
for (RecordStruct rec1 : collected.values())
files.addItem(rec1);
request.complete();
}
public void handleAddPageFolder(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String channel = "Pages";
String path = rec.getFieldAsString("Path");
String site = rec.getFieldAsString("Site");
// link to fake so we can use FeedInfo to locate the folders
FeedInfo fi = FeedInfo.buildInfo(site, channel, path + "/_fake.dcf.xml");
Path pubpath = fi.getPubpath().getParent();
Path prepath = fi.getPrepath().getParent();
try {
Files.createDirectories(prepath);
Files.createDirectories(pubpath);
}
catch (IOException x) {
Logger.error("Error collecting files: " + path + " : " + x);
}
request.complete();
}
public void handleFeedLoadList(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String channel = rec.getFieldAsString("Channel");
// TODO add site support
Hub.instance.getDatabase().submit(
new SelectDirectRequest()
.withTable("dcmFeed")
.withSelect(new SelectFields()
.withField("Id")
.withField("dcmPath", "Path")
.withSubField("dcmFields", "Published.en", "Published")
.withSubField("dcmFields", "Title.en", "Title")
.withSubField("dcmFields", "Image.en", "Image")
.withSubField("dcmFields", "Description.en", "Description")
)
.withCollector(
new CollectorField("dcmChannel")
.withValues(channel)
),
new ObjectFinalResult(request));
}
public void handleAddFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
fi.initDraftFile(rec.getFieldAsString("Locale"), rec.getFieldAsString("Title"), null, new FuncCallback<CompositeStruct>() {
@Override
public void callback() {
//request.setResult(this.getResult());
request.complete();
}
});
}
public void handleAddPageFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String channel = "Pages";
String path = rec.getFieldAsString("Path");
String site = rec.getFieldAsString("Site");
String tname = rec.getFieldAsString("Template");
FeedInfo fi = FeedInfo.buildInfo(site, channel, path);
String dcui = null;
XElement chel = FeedIndexer.findChannel(site, channel);
for (XElement tel : chel.selectAll("Template")) {
if (tname.equals(tel.getAttribute("Name"))) {
XElement ctel = (XElement) tel.deepCopy();
ctel.setName("dcui");
dcui = ctel.toString(true);
break;
}
}
DomainInfo di = OperationContext.get().getDomain();
Path tpath = di.resolvePath("/config/templates/" + tname + ".dcui.xml");
if (Files.exists(tpath))
dcui = IOUtil.readEntireFile(tpath).getResult().toString();
fi.initDraftFile(rec.getFieldAsString("Locale"), rec.getFieldAsString("Title"), dcui, new FuncCallback<CompositeStruct>() {
@Override
public void callback() {
//request.setResult(this.getResult());
request.complete();
}
});
}
public void handleUpdateFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
fi.saveFile(true, rec.getFieldAsXml("ContentXml"), rec.getFieldAsList("UpdateFiles"), rec.getFieldAsList("DeleteFiles"), new FuncCallback<CompositeStruct>() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleUpdatePublishFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
fi.saveFile(false, rec.getFieldAsXml("ContentXml"), rec.getFieldAsList("UpdateFiles"), rec.getFieldAsList("DeleteFiles"), new FuncCallback<CompositeStruct>() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleLoadFeedsDefinitions(TaskRun request) {
DomainInfo domain = OperationContext.get().getUserContext().getDomain();
// TODO add site support
XElement feed = domain.getSettings().find("Feed");
if (feed == null) {
request.error("Feed definition does not exist.");
request.complete();
return;
}
RecordStruct resp = new RecordStruct()
.withField("FeedsXml", feed);
request.returnValue(resp);
}
public void handleLoadFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
String channel = fi.getChannel();
String path = fi.getOuterPath();
// copy it because we might alter it
XElement definition = (XElement) fi.getChannelDef().deepCopy();
String help = null;
// load Page definitions...
if ("Pages".equals(channel) || "Block".equals(channel)) {
DomainInfo domain = OperationContext.get().getUserContext().getDomain();
// TODO per site
Path srcpath = Hub.instance.getPublicFileStore().resolvePath("dcw/" + domain.getAlias() + "/www-preview/" + path + ".dcui.xml");
if (Files.notExists(srcpath))
srcpath = Hub.instance.getPublicFileStore().resolvePath("dcw/" + domain.getAlias() + "/www/" + path + ".dcui.xml");
if (Files.notExists(srcpath)) {
request.error("Feed page " + path + " does not exist.");
request.complete();
return;
}
FuncResult<XElement> res = XmlReader.loadFile(srcpath, false);
if (res.hasErrors()) {
request.error("Bad page file " + path + ".");
request.complete();
return;
}
for (XElement ppd : res.getResult().selectAll("PagePartDef"))
definition.add(ppd);
for (XElement cfd : res.getResult().selectAll("CustomFields"))
definition.add(cfd);
for (XElement ppd : res.getResult().selectAll("Help"))
definition.add(ppd);
XElement hd = res.getResult().find("Help");
if (hd != null)
help = hd.getValue();
}
boolean draft = true;
Path srcpath = fi.getPrepath();
if (Files.notExists(srcpath)) {
srcpath = fi.getPubpath();
draft = false;
}
if (Files.notExists(srcpath)) {
request.error("Feed file " + path + " does not exist.");
request.complete();
return;
}
// collect the external file contents
ListStruct files = new ListStruct();
for (String sname : fi.collectExternalFileNames(draft)) {
Path spath = fi.getPrepath().resolveSibling(sname);
if (Files.notExists(spath))
spath = fi.getPubpath().resolveSibling(sname);
FuncResult<CharSequence> mres = IOUtil.readEntireFile(spath);
if (mres.isNotEmptyResult()) {
RecordStruct fentry = new RecordStruct()
.withField("Name", sname)
.withField("Content", mres.getResult().toString());
files.addItem(fentry);
}
}
// assemble response
RecordStruct resp = new RecordStruct()
.withField("ChannelXml", definition)
.withField("ContentXml", draft ? fi.getDraftDcfContent() : fi.getPubDcfContent())
.withField("Files", files);
// add help
Path hpath = fi.getPubpath().resolveSibling("readme.en.md"); // TODO locale aware
if (Files.exists(hpath)) {
FuncResult<CharSequence> mres = IOUtil.readEntireFile(hpath);
if (mres.isNotEmptyResult())
resp.withField("Help", StringUtil.isNotEmpty(help) ? help + "\n\n----\n\n" + mres.getResult() : mres.getResult());
}
request.returnValue(resp);
}
public void handlePublishFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
fi.publicizeFile(new FuncCallback<CompositeStruct>() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleImportFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
fi.updateDb(new OperationCallback() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleDeleteFeedFiles(TaskRun request) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
FeedInfo fi = FeedInfo.recordToInfo(rec);
// delete pub and draft
fi.deleteFile(DeleteMode.Both, new OperationCallback() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleSiteBuildMap(TaskRun request) {
DomainInfo domain = OperationContext.get().getUserContext().getDomain();
XElement dsel = domain.getSettings();
if (dsel == null) {
request.warn("Missing domain settings");
request.complete();
return;
}
XElement wsel = dsel.find("Web");
if (wsel == null) {
request.warn("Missing Web config");
request.complete();
return;
}
Consumer<XElement> consumer = new Consumer<XElement>() {
@Override
public void accept(XElement wsel) {
String indexurl = wsel.getAttribute("IndexUrl");
if (StringUtil.isEmpty(indexurl)) {
request.warn("Missing IndexUrl");
request.complete();
return;
}
List<String> altlocales = new ArrayList<String>();
for (XElement locel : wsel.selectAll("Locale"))
altlocales.add(locel.getAttribute("Name"));
Path webdir = "root".equals(wsel.getAttribute("Name"))
? Hub.instance.getPublicFileStore().resolvePath("dcw/" + domain.getAlias() + "/www")
: Hub.instance.getPublicFileStore().resolvePath("dcw/" + domain.getAlias() + "/sites/" + wsel.getAttribute("Name") + "/www");
XElement smel = new XElement("urlset")
.withAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
.withAttribute("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
DateTimeFormatter lmFmt = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
try {
if (Files.exists(webdir)) {
Files.walkFileTree(webdir, EnumSet.of(FileVisitOption.FOLLOW_LINKS), 5,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException
{
Path relpath = webdir.relativize(file);
String outerpath = "/" + relpath.toString().replace('\\', '/');
if (!outerpath.endsWith(".dcui.xml"))
return FileVisitResult.CONTINUE;
outerpath = outerpath.substring(0, outerpath.length() - 9);
FuncResult<XElement> xres = XmlReader.loadFile(file, true);
if (xres.hasErrors())
return FileVisitResult.CONTINUE;
XElement root = xres.getResult();
if (!root.getName().equals("dcui"))
return FileVisitResult.CONTINUE;
if (!root.getAttribute("AuthTags", "Guest").contains("Guest"))
return FileVisitResult.CONTINUE;
if ("True".equals(root.getAttribute("NoIndex")))
return FileVisitResult.CONTINUE;
// TODO look for an indexing script in the page
// TODO look for a gallery list in the page
XElement sel = new XElement("url");
sel.add(new XElement("loc", indexurl + outerpath.substring(1)));
sel.add(new XElement("lastmod", lmFmt.print(Files.getLastModifiedTime(file).toMillis())));
for (String lname : altlocales)
sel.add(new XElement("xhtml:link")
.withAttribute("rel", "alternate")
.withAttribute("hreflang", lname)
.withAttribute("href", indexurl + lname + outerpath)
);
smel.add(sel);
return FileVisitResult.CONTINUE;
}
});
}
FeedIndexer iutil = new FeedIndexer();
iutil.collectSite(new CollectContext().forIndex(), wsel.getAttribute("Name"));
iutil.addToSitemap(indexurl, smel, altlocales);
Path smfile = webdir.resolve("sitemap.xml");
IOUtil.saveEntireFile2(smfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ smel.toString(true));
//System.out.println("map: " + smel.toString(true));
}
catch (IOException x) {
request.error("Error building sitemap file: " + x);
}
}
};
boolean rootfnd = false;
for (XElement site : wsel.selectAll("Site")) {
if ("root".equals(site.getAttribute("Name"))) {
rootfnd = true;
site.withAttribute("IndexUrl", wsel.getAttribute("IndexUrl"));
}
consumer.accept(site);
}
if (!rootfnd)
consumer.accept(new XElement("Site").withAttribute("Name", "root").withAttribute("IndexUrl", wsel.getAttribute("IndexUrl")));
request.complete();
}
/******************************************************************
* Gallery Files
******************************************************************/
public void handleListGallery(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FolderPath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFolderListing(path, new FuncCallback<List<IFileStoreFile>>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
RecordStruct info = new RecordStruct();
ListStruct files = new ListStruct();
info.setField("Files", files);
CountDownCallback cdcb = new CountDownCallback(1, new OperationCallback() {
@Override
public void callback() {
request.returnValue(info);
}
});
for (IFileStoreFile file : this.getResult()) {
if ("meta.json".equals(file.getName())) {
cdcb.increment();
file.readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.isNotEmptyResult())
info.setField("Settings", Struct.objectToComposite(this.getResult()));
cdcb.countDown();
}
});
continue;
}
// TODO localize
if ("readme.en.md".equals(file.getName())) {
cdcb.increment();
file.readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.isNotEmptyResult())
info.setField("Help", this.getResult());
cdcb.countDown();
}
});
continue;
}
if (file.getName().startsWith("."))
continue;
if (!file.isFolder())
continue;
boolean isImage = file.getName().endsWith(".v");
RecordStruct fdata = new RecordStruct();
fdata.setField("FileName", isImage ? file.getName().substring(0, file.getName().length() - 2) : file.getName());
fdata.setField("IsFolder", !isImage);
if (isImage) {
// TODO set modified and size based on the `original` variation
}
fdata.setField("LastModified", file.getModificationTime());
fdata.setField("Size", file.getSize());
files.addItem(fdata);
}
cdcb.countDown();
}
});
}
public void handleListGalleryVariations(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("Path");
if (!fpath.endsWith(".v"))
fpath += ".v";
CommonPath path = sectionpath.resolve(fpath);
fs.getFolderListing(path, new FuncCallback<List<IFileStoreFile>>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
RecordStruct info = new RecordStruct();
ListStruct files = new ListStruct();
info.setField("Variants", files);
CountDownCallback cdcb = new CountDownCallback(3, new OperationCallback() {
@Override
public void callback() {
request.returnValue(info);
}
});
for (IFileStoreFile file : this.getResult()) {
if (file.getName().startsWith("."))
continue;
if (file.isFolder())
continue;
RecordStruct fdata = new RecordStruct();
fdata.setField("Name", file.getName());
fdata.setField("LastModified", file.getModificationTime());
fdata.setField("Size", file.getSize());
files.addItem(fdata);
}
fs.getFileDetail(path.getParent().resolve("meta.json"), new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (this.getResult().exists()) {
this.getResult().readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.isNotEmptyResult())
info.setField("Settings", Struct.objectToComposite(this.getResult()));
cdcb.countDown();
}
});
}
else {
cdcb.countDown();
}
}
});
// localize
fs.getFileDetail(path.getParent().resolve("readme.en.md"), new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (this.getResult().exists()) {
this.getResult().readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.isNotEmptyResult())
info.setField("Help", this.getResult());
cdcb.countDown();
}
});
}
else {
cdcb.countDown();
}
}
});
cdcb.countDown();
}
});
}
public void handleImageDetail(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("ImagePath") + ".v";
CommonPath path = sectionpath.resolve(fpath);
RecordStruct info = new RecordStruct();
info.setField("GalleryPath", new CommonPath("/galleries" + fpath).getParent());
info.setField("FileName", path.getFileName());
ListStruct files = new ListStruct();
info.setField("Variations", files);
CountDownCallback cdcb = new CountDownCallback(2, new OperationCallback() {
@Override
public void callback() {
request.returnValue(info);
}
});
fs.getFolderListing(path, new FuncCallback<List<IFileStoreFile>>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
for (IFileStoreFile file : this.getResult()) {
if (file.getName().startsWith("."))
continue;
if (!file.getName().endsWith(".jpg") && !file.getName().endsWith(".jpeg") && !file.getName().endsWith(".png")
&& !file.getName().endsWith(".gif"))
continue;
RecordStruct fdata = new RecordStruct();
String ext = file.getExtension();
String name = file.getName();
fdata.setField("Alias", name.substring(0, name.length() - ext.length() - 1));
fdata.setField("Extension", ext);
fdata.setField("LastModified", file.getModificationTime());
fdata.setField("Size", file.getSize());
files.addItem(fdata);
}
cdcb.countDown();
}
});
fs.getFileDetail(path.resolvePeer("meta.json"), new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
//request.error("Meta file does not exist");
// default is empty
info.setField("GallerySettings", new RecordStruct(new FieldStruct("Variations", new ListStruct())));
cdcb.countDown();
return;
}
fi.readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.isNotEmptyResult())
info.setField("GallerySettings", Struct.objectToComposite(this.getResult()));
cdcb.countDown();
}
});
}
});
}
public void handleLoadSlideShow(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("GalleryPath");
String alias = rec.getFieldAsString("Alias");
// TODO check that user has access to this folder
fs.getFileDetail(sectionpath.resolve(fpath + "/meta.json"), new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
request.error("Meta file does not exist");
request.complete();
return;
}
fi.readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.isEmptyResult()) {
request.returnEmpty();
return;
}
RecordStruct meta = (RecordStruct) Struct.objectToComposite(this.getResult());
if (meta.isFieldEmpty("Shows")) {
request.error("Requested show not found");
request.returnEmpty();
return;
}
for (Struct ss : meta.getFieldAsList("Shows").getItems()) {
RecordStruct show = (RecordStruct) ss;
if (((alias == null) && !show.getFieldAsString("Alias").equals("default")) || !show.getFieldAsString("Alias").equals(alias))
continue;
RecordStruct info = new RecordStruct();
String valias = show.getFieldAsString("Variation");
info.setField("Title", show.getFieldAsString("Title"));
info.setField("Variation", valias);
info.setField("Images", show.getFieldAsList("Images"));
info.setField("Order", show.getFieldAsString("Order"));
if (!meta.isFieldEmpty("Variations")) {
for (Struct vs : meta.getFieldAsList("Variations").getItems()) {
RecordStruct var = (RecordStruct) vs;
if (!var.getFieldAsString("Alias").equals(valias))
continue;
info.setField("VariationSettings", var);
}
}
request.returnValue(info);
return;
}
request.error("Requested show not found");
request.returnEmpty();
}
});
}
});
}
public void handleUpdateGallery(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FolderPath");
CompositeStruct settings = rec.getFieldAsRecord("Settings");
if (settings == null) {
request.error("Settings invalid");
request.returnEmpty();
}
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path.resolve("meta.json"), new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
fi.writeAllText(settings.toPrettyString(), new OperationCallback() {
@Override
public void callback() {
request.returnEmpty();
}
});
}
});
}
/******************************************************************
* Domain Files
******************************************************************/
public void handleFileDetail(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
request.error("File does not exist");
request.complete();
return;
}
final RecordStruct fdata = new RecordStruct();
fdata.setField("FileName", fi.getName());
fdata.setField("IsFolder", fi.isFolder());
fdata.setField("LastModified", fi.getModificationTime());
fdata.setField("Size", fi.getSize());
String meth = rec.getFieldAsString("Method");
if (StringUtil.isEmpty(meth) || fi.isFolder()) {
request.setResult(fdata);
request.complete();
return;
}
fi.hash(meth, new FuncCallback<String>() {
@Override
public void callback() {
if (!request.hasErrors()) {
fdata.setField("Hash", this.getResult());
request.setResult(fdata);
}
request.complete();
}
});
}
});
}
public void handleImportUIFile(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
request.error("File does not exist");
request.complete();
return;
}
fi.readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.hasErrors()) {
request.error("Unable to read file");
request.complete();
return;
}
String text = this.getResult();
FuncResult<XElement> xres = XmlReader.parse(text, true);
if (xres.hasErrors()) {
System.out.println("Error parsing file: " + xres.getMessages());
request.complete();
return;
}
XElement root = xres.getResult();
String spath = path.subpath(3).toString(); // remove the www
if (fpath.endsWith(".dcuis.xml")) {
String fspath = spath.substring(0, spath.length() - 10); // remove the extension
System.out.println("Importing skeleton: " + root.getAttribute("Title") + " " + fspath);
InsertRecordRequest req = new InsertRecordRequest();
req
.withTable("dcmSkeleton")
.withSetField("dcmTitle", root.getAttribute("Title"))
.withSetField("dcmPath", fspath);
Hub.instance.getDatabase().submit(req, new ObjectFinalResult(request));
}
else if (root.getName().equals("dcuip")) {
// TODO deal with block
request.returnValue(5); // TODO page id
}
else {
// TODO check for ReqLib, ReqStyle, Function - these cannot be imported so return an error
String fspath = spath.substring(0, spath.length() - 9); // remove the extension
System.out.println("Importing page: " + root.getAttribute("Title") + " " + root.getAttribute("Skeleton"));
// only support external skeletons
if (!root.hasAttribute("Skeleton")) {
request.error("Missing skeleton path, skeleton must be external");
request.complete();
return;
}
String tpath = root.getAttribute("Skeleton");
Hub.instance.getDatabase().submit(
new SelectDirectRequest()
.withTable("dcmSkeleton")
.withSelect(new SelectFields().withField("Id"))
.withWhere(new WhereEqual(new WhereField("dcmPath"), tpath)),
new ObjectResult() {
@Override
public void process(CompositeStruct result) {
if (this.hasErrors()) {
request.complete();
return;
}
if (result == null) {
request.error("Search for skeleton failed");
request.complete();
return;
}
FuncCallback<String> processPage = new FuncCallback<String>() {
@Override
public void callback() {
if (this.isEmptyResult()) {
request.error("Skeleton id cannot be estalished");
request.complete();
return;
}
String sid = this.getResult();
InsertRecordRequest req = new InsertRecordRequest();
req
.withTable("dcmPage")
.withSetField("dcmTitle", root.getAttribute("Title"))
.withSetField("dcmPath", fspath)
.withSetField("dcmSkeleton", sid)
.withSetField("dcmAuthor", OperationContext.get().getUserContext().getUserId())
.withSetField("dcmCreated", new DateTime())
.withSetField("dcmModified", new DateTime());
XElement keywords = root.find("Keywords");
if ((keywords != null) && keywords.hasText())
req.withSetField("dcmKeywords", keywords.getText());
XElement desc = root.find("Description");
if ((desc != null) && desc.hasText())
req.withSetField("dcmDescription", desc.getText());
for (XElement part : root.selectAll("PagePart")) {
String content = part.getText();
String locale = part.getAttribute("Locale", "default");
String forid = part.getAttribute("For", "main-content");
String subkey = forid + "." + locale;
req.withSetField("dcmPartContent", subkey, content);
RecordStruct pattrs = new RecordStruct();
for (Entry<String, String> attr : part.getAttributes().entrySet())
pattrs.setField(attr.getKey(), attr.getValue());
req.withSetField("dcmPartAttributes", subkey, pattrs);
}
Hub.instance.getDatabase().submit(req, new ObjectFinalResult(request));
}
};
ListStruct sklist = (ListStruct) result;
if (sklist.getSize() == 0) {
InsertRecordRequest req = new InsertRecordRequest();
req
.withTable("dcmSkeleton")
.withSetField("dcmTitle", root.getAttribute("Title"))
.withSetField("dcmPath", root.getAttribute("Skeleton"));
Hub.instance.getDatabase().submit(req, new ObjectResult() {
@Override
public void process(CompositeStruct result) {
if (this.isNotEmptyResult())
processPage.setResult(this.getResultAsRec().getFieldAsString("Id"));
processPage.complete();
}
});
}
else {
processPage.setResult(sklist.getItemAsRecord(0).getFieldAsString("Id"));
processPage.complete();
}
}
});
}
}
});
}
});
}
public void handleLoadFile(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
request.error("File does not exist");
request.complete();
return;
}
if (fi.getSize() > 16 * 1000 * 1000) {
request.error("File too large");
request.complete();
return;
}
fi.readAllText(new FuncCallback<String>() {
@Override
public void callback() {
if (this.hasErrors()) {
request.error("Unable to read file");
request.complete();
return;
}
String text = this.getResult();
request.returnValue(new RecordStruct().withField("Content", text));
}
});
}
});
}
public void handleSaveFile(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
String content = rec.getFieldAsString("Content");
IFileStoreFile fi = this.getResult();
fi.writeAllText(content, new OperationCallback() {
@Override
public void callback() {
request.complete();
}
});
}
});
}
public void handleDeleteFile(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
request.complete();
return;
}
if (fi.isFolder()) {
request.error("Path is folder, use DeleteFolder to remove a folder");
request.complete();
return;
}
fi.remove(new OperationCallback() {
@Override
public void callback() {
request.complete();
}
});
}
});
}
public void handleDeleteFolder(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FolderPath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
IFileStoreFile fi = this.getResult();
if (!fi.exists()) {
request.complete();
return;
}
if (!fi.isFolder()) {
request.error("Path is not folder, use DeleteFile to remove a file");
request.complete();
return;
}
fi.remove(new OperationCallback() {
@Override
public void callback() {
request.complete();
}
});
}
});
}
public void handleAddFolder(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FolderPath");
CommonPath path = sectionpath.resolve(fpath);
fs.addFolder(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleAddGalleryFolder(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FolderPath");
CommonPath path = sectionpath.resolve(fpath);
fs.addFolder(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
// TODO really this is not going to be a remote/special store - just treat it like a FS throughout
Path rpath = OperationContext.get().getDomain().resolvePath("/galleries" + fpath);
Path mspath = rpath.getParent().resolve("meta-sub.json");
Path rspath = rpath.getParent().resolve("readme-sub.en.md");
try {
if (Files.exists(mspath)) {
Files.copy(mspath, rpath.resolve("meta.json"));
Files.copy(mspath, rpath.resolve("meta-sub.json"));
}
}
catch (Exception x) {
request.error("Error copying meta-sub.json: " + x);
}
try {
if (Files.exists(rspath)) {
Files.copy(rspath, rpath.resolve("readme.en.md")); // localize
Files.copy(rspath, rpath.resolve("readme-sub.en.md")); // localize
}
}
catch (Exception x) {
request.error("Error copying readme-sub.en.md: " + x);
}
request.complete();
}
});
}
public void handleAddGalleryImage(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath1 = rec.getFieldAsString("ImagePath");
if (!fpath1.endsWith(".v"))
fpath1 += ".v";
String fpath = fpath1;
CommonPath path = sectionpath.resolve(fpath);
fs.addFolder(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
request.complete();
}
});
}
public void handleListFiles(TaskRun request, FileSystemDriver fs, CommonPath sectionpath, boolean showHidden) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FolderPath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFolderListing(path, new FuncCallback<List<IFileStoreFile>>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
ListStruct files = new ListStruct();
for (IFileStoreFile file : this.getResult()) {
if (!showHidden && file.getName().startsWith(".")) // TODO make sure only right users can update/delete hidden
continue;
RecordStruct fdata = new RecordStruct();
fdata.setField("FileName", file.getName());
fdata.setField("IsFolder", file.isFolder());
fdata.setField("LastModified", file.getModificationTime());
fdata.setField("Size", file.getSize());
files.addItem(fdata);
}
request.returnValue(files);
}
});
}
public void handleStartUpload(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
boolean forceover = rec.getFieldAsBooleanOrFalse("ForceOverwrite");
boolean resume = !forceover && this.getResult().exists();
// define channel binding
RecordStruct binding = new RecordStruct(
new FieldStruct("FilePath", path),
new FieldStruct("FileSize", rec.getFieldAsInteger("FileSize")),
new FieldStruct("Hub", OperationContext.get().getSessionId().substring(0, 5)),
new FieldStruct("Session", OperationContext.get().getSessionId()),
new FieldStruct("Channel", rec.getFieldAsString("Channel")),
new FieldStruct("Append", resume)
);
final DataStreamChannel chan = new DataStreamChannel(CmsService.this.channels.getId(), "Uploading " + fpath, binding);
if (rec.hasField("Params"))
chan.setParams(rec.getField("Params"));
// apply the channel to a write stream from selected file
this.getResult().openWrite(chan, new FuncCallback<RecordStruct>() {
@Override
public void callback() {
if (!request.hasErrors()) {
// add the channel only after we know it is open
CmsService.this.channels.addChannel(chan);
RecordStruct res = this.getResult();
res.setField("BestEvidence", CmsService.this.bestEvidence);
res.setField("MinimumEvidence", CmsService.this.minEvidence);
// get the binding info to return
request.setResult(res);
}
request.complete();
}
});
}
});
}
public void handleFinishUpload(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
if ("Faliure".equals(rec.getFieldAsString("Status"))) {
request.warn("File upload incomplete or corrupt: " + path);
if (!rec.isFieldEmpty("Note"))
request.warn("File upload note: " + rec.getFieldAsString("Note"));
request.complete();
return;
}
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
RecordStruct evidinfo = rec.getFieldAsRecord("Evidence");
String evidenceType = null;
// pick best evidence if available, we really don't care if higher is available
if (!evidinfo.isFieldEmpty(CmsService.this.bestEvidence)) {
evidenceType = CmsService.this.bestEvidence;
}
// else pick the highest available evidence given
else {
for (FieldStruct fld : evidinfo.getFields())
evidenceType = CmsService.this.maxEvidence(fld.getName(), evidenceType);
}
final String selEvidenceType = evidenceType;
IFileStoreFile fresult = this.getResult();
final Consumer<Boolean> afterVerify = (pass) -> {
if (pass) {
if (CmsService.this.isSufficentEvidence(CmsService.this.bestEvidence, selEvidenceType))
request.info("Verified best evidence for upload: " + path);
else if (CmsService.this.isSufficentEvidence(CmsService.this.minEvidence, selEvidenceType))
request.info("Verified minimum evidence for upload: " + path);
else
request.error("Verified evidence for upload, however evidence is insuffcient: " + path);
}
else {
request.error("File upload incomplete or corrupt: " + path);
}
if (!request.hasErrors())
CmsService.this.watch("Upload", fresult);
if (!rec.isFieldEmpty("Note"))
request.info("File upload note: " + rec.getFieldAsString("Note"));
request.complete();
};
// TODO wait for file to flush out?
if ("Size".equals(selEvidenceType)) {
Long src = evidinfo.getFieldAsInteger("Size");
long dest = fresult.getSize();
boolean match = (src == dest);
if (match)
request.info("File sizes match");
else
request.error("File sizes do not match");
afterVerify.accept(match);
}
else if (StringUtil.isNotEmpty(selEvidenceType)) {
fresult.hash(selEvidenceType, new FuncCallback<String>() {
@Override
public void callback() {
if (request.hasErrors()) {
afterVerify.accept(false);
}
else {
String src = evidinfo.getFieldAsString(selEvidenceType);
String dest = this.getResult();
boolean match = (src.equals(dest));
if (match)
request.info("File hashes match (" + selEvidenceType + ")");
else
request.error("File hashes do not match (" + selEvidenceType + ")");
afterVerify.accept(match);
}
}
});
}
else {
request.error("Missing any form of evidence, supply at least size");
afterVerify.accept(false);
}
}
});
}
public Message handleStartDownload(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
// define channel binding
RecordStruct binding = new RecordStruct(
new FieldStruct("FilePath", path),
new FieldStruct("Offset", rec.getFieldAsInteger("Offset")),
new FieldStruct("Hub", OperationContext.get().getSessionId().substring(0, 5)),
new FieldStruct("Session", OperationContext.get().getSessionId()),
new FieldStruct("Channel", rec.getFieldAsString("Channel"))
);
final DataStreamChannel chan = new DataStreamChannel(CmsService.this.channels.getId(), "Downloading " + fpath, binding);
if (rec.hasField("Params"))
chan.setParams(rec.getField("Params"));
this.getResult().openRead(chan, new FuncCallback<RecordStruct>() {
@Override
public void callback() {
if (!request.hasErrors()) {
// add the channel only after we know it is open
CmsService.this.channels.addChannel(chan);
RecordStruct res = this.getResult();
// always return path - if token was used to get file then here is the first chance to know the path/file we are collecting
res.setField("FilePath", path);
res.setField("Mime", MimeUtil.getMimeType(path));
res.setField("BestEvidence", CmsService.this.bestEvidence);
res.setField("MinimumEvidence", CmsService.this.minEvidence);
// get the binding info to return
request.setResult(res);
}
request.complete();
}
});
}
});
return null;
}
public void handleFinishDownload(TaskRun request, FileSystemDriver fs, CommonPath sectionpath) {
RecordStruct rec = MessageUtil.bodyAsRecord(request);
String fpath = rec.getFieldAsString("FilePath");
CommonPath path = sectionpath.resolve(fpath);
if ("Faliure".equals(rec.getFieldAsString("Status"))) {
request.warn("File download incomplete or corrupt: " + path);
if (!rec.isFieldEmpty("Note"))
request.warn("File download note: " + rec.getFieldAsString("Note"));
request.complete();
return;
}
fs.getFileDetail(path, new FuncCallback<IFileStoreFile>() {
@Override
public void callback() {
if (request.hasErrors()) {
request.complete();
return;
}
RecordStruct evidinfo = rec.getFieldAsRecord("Evidence");
String evidenceType = null;
// pick best evidence if available, we really don't care if higher is available
if (!evidinfo.isFieldEmpty(CmsService.this.bestEvidence)) {
evidenceType = CmsService.this.bestEvidence;
}
// else pick the highest available evidence given
else {
for (FieldStruct fld : evidinfo.getFields())
evidenceType = CmsService.this.maxEvidence(fld.getName(), evidenceType);
}
final String selEvidenceType = evidenceType;
final Consumer<Boolean> afterVerify = (pass) -> {
if (pass) {
if (CmsService.this.isSufficentEvidence(CmsService.this.bestEvidence, selEvidenceType))
request.info("Verified best evidence for download: " + path);
else if (CmsService.this.isSufficentEvidence(CmsService.this.minEvidence, selEvidenceType))
request.info("Verified minimum evidence for download: " + path);
else
request.error("Verified evidence for download, however evidence is insuffcient: " + path);
}
else {
request.error("File download incomplete or corrupt: " + path);
}
if (!rec.isFieldEmpty("Note"))
request.info("File download note: " + rec.getFieldAsString("Note"));
request.complete();
};
if ("Size".equals(selEvidenceType)) {
Long src = evidinfo.getFieldAsInteger("Size");
long dest = this.getResult().getSize();
boolean match = (src == dest);
if (match)
request.info("File sizes match");
else
request.error("File sizes do not match");
afterVerify.accept(match);
}
else if (StringUtil.isNotEmpty(selEvidenceType)) {
this.getResult().hash(selEvidenceType, new FuncCallback<String>() {
@Override
public void callback() {
if (request.hasErrors()) {
afterVerify.accept(false);
}
else {
String src = evidinfo.getFieldAsString(selEvidenceType);
String dest = this.getResult();
boolean match = (src.equals(dest));
if (match)
request.info("File hashes match (" + selEvidenceType + ")");
else
request.error("File hashes do not match (" + selEvidenceType + ")");
afterVerify.accept(match);
}
}
});
}
else {
request.error("Missing any form of evidence, supply at least size");
afterVerify.accept(false);
}
}
});
}
public boolean isSufficentEvidence(String lookingfor, String got) {
if ("Size".equals(lookingfor))
return ("Size".equals(got) || "MD5".equals(got) || "SHA128".equals(got) || "SHA256".equals(got) || "SHA512".equals(got));
if ("MD5".equals(lookingfor))
return ("MD5".equals(got) || "SHA128".equals(got) || "SHA256".equals(got) || "SHA512".equals(got));
if ("SHA128".equals(lookingfor))
return ("SHA128".equals(got) || "SHA256".equals(got) || "SHA512".equals(got));
if ("SHA256".equals(lookingfor))
return ("SHA256".equals(got) || "SHA512".equals(got));
if ("SHA512".equals(lookingfor))
return ("SHA512".equals(got));
return false;
}
public String maxEvidence(String lhs, String rhs) {
if ("Size".equals(lhs) && ("MD5".equals(rhs) || "SHA128".equals(rhs) || "SHA256".equals(rhs) || "SHA512".equals(rhs)))
return rhs;
if ("MD5".equals(lhs) && ("SHA128".equals(rhs) || "SHA256".equals(rhs) || "SHA512".equals(rhs)))
return rhs;
if ("SHA128".equals(lhs) && ("SHA256".equals(rhs) || "SHA512".equals(rhs)))
return rhs;
if ("SHA256".equals(lhs) && "SHA512".equals(rhs))
return rhs;
return lhs;
}
public void watch(String op, IFileStoreFile file) {
/* TODO review
XElement settings = this.getLoader().getSettings();
if (settings != null) {
for (XElement watch : settings.selectAll("Watch")) {
String wpath = watch.getAttribute("FilePath");
// if we are filtering on path make sure the path is a parent of the triggered path
if (StringUtil.isNotEmpty(wpath)) {
CommonPath wp = new CommonPath(wpath);
if (!wp.isParent(file.path()))
continue;
}
String tasktag = op + "Task";
for (XElement task : watch.selectAll(tasktag)) {
String id = task.getAttribute("Id");
if (StringUtil.isEmpty(id))
id = Task.nextTaskId();
String title = task.getAttribute("Title");
String script = task.getAttribute("Script");
String params = task.selectFirstText("Params");
RecordStruct prec = null;
if (StringUtil.isNotEmpty(params)) {
FuncResult<CompositeStruct> pres = CompositeParser.parseJson(params);
if (pres.isNotEmptyResult())
prec = (RecordStruct) pres.getResult();
}
if (prec == null)
prec = new RecordStruct();
prec.setField("File", file);
if (script.startsWith("$"))
script = script.substring(1);
Task t = new Task()
.withId(id)
.withTitle(title)
.withParams(prec)
.withRootContext();
if (!ScriptWork.addScript(t, Paths.get(script))) {
Logger.error("Unable to run script for file watcher: " + watch.getAttribute("FilePath"));
continue;
}
Hub.instance.getWorkPool().submit(t);
}
}
}
*/
}
}