diff --git a/plugins/tiddlywiki/multiwikiserver/modules/init.js b/plugins/tiddlywiki/multiwikiserver/modules/init.js index 13adcc003..b56161ea9 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/init.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/init.js @@ -34,14 +34,19 @@ exports.startup = function() { logger.alert("The plugin 'tiddlywiki/multiwikiserver' requires the better-sqlite3 npm package to be installed. Run 'npm install' in the root of the TiddlyWiki repository"); return; } - // Compute the database path - var databasePath = path.resolve($tw.boot.wikiPath,"store/database.sqlite"); - // Create and initialise the tiddler store - var SqlTiddlerStore = require("$:/plugins/tiddlywiki/multiwikiserver/sql-tiddler-store.js").SqlTiddlerStore; + // Create and initialise the tiddler store and upload manager + var SqlTiddlerStore = require("$:/plugins/tiddlywiki/multiwikiserver/sql-tiddler-store.js").SqlTiddlerStore, + store = new SqlTiddlerStore({ + databasePath: path.resolve($tw.boot.wikiPath,"store/database.sqlite") + }), + UploadManager = require("$:/plugins/tiddlywiki/multiwikiserver/upload-manager.js").UploadManager, + uploadManager = new UploadManager({ + inboxPath: path.resolve($tw.boot.wikiPath,"store/inbox"), + store: store + }); $tw.mws = { - store: new SqlTiddlerStore({ - databasePath: databasePath - }) + store: store, + uploadManager: uploadManager }; // Create docs bag and recipe $tw.mws.store.createBag("docs","TiddlyWiki Documentation from https://tiddlywiki.com/"); diff --git a/plugins/tiddlywiki/multiwikiserver/modules/upload-manager.js b/plugins/tiddlywiki/multiwikiserver/modules/upload-manager.js new file mode 100644 index 000000000..b59b14da2 --- /dev/null +++ b/plugins/tiddlywiki/multiwikiserver/modules/upload-manager.js @@ -0,0 +1,95 @@ +/*\ +title: $:/plugins/tiddlywiki/multiwikiserver/upload-manager.js +type: application/javascript +module-type: library + +A class that handles an incoming multipart/form-data stream, streaming the data to temporary files +in the store/inbox folder. It invokes a callback when all the data is available. The callback can explicitly +claim some or all of the files, otherwise they are deleted on return from the callback. Claimed files should +be moved out of the store/inbox folder. + +\*/ + +(function() { + +/* +Create an instance of the upload manager. Options include: + +inboxPath - path to the inbox folder +store - sqlTiddlerStore to use for saving tiddlers +*/ +function UploadManager(options) { + const path = require("path"); + options = options || {}; + this.inboxPath = path.resolve(options.inboxPath,); + this.store = options.store; +} + +/* +Process a new multipart/form-data stream. Options include: + +state - provided by server.js +recipe - optional name of recipe to write to (one of recipe or bag must be specified) +bag - optional name of bag to write to (one of recipe or bag must be specified) +callback - invoked as callback(err,results). Results is an array of {title:,bag_name:} + +formData is: +{ + parts: [ + { + name: "fieldname", + filename: "filename", + filePath: "/users/home/mywiki/store/inbox/09cabc74-8163-4ead-a35b-4ca768f02d62/64131628-cbff-4677-b146-d85c42c232dc", + headers: { + name: "value", + ... + } + }, + ... + ] +} +*/ +UploadManager.prototype.processNewStream = function(options) { + let fileStream = null; + let fieldValue = ""; + state.streamMultipartData({ + cbPartStart: function(headers,name,filename) { + console.log(`Received file ${name} and ${filename} with ${JSON.stringify(headers)}`) + if(filename) { + fileStream = fs.createWriteStream(filename); + } else { + fieldValue = ""; + } + }, + cbPartChunk: function(chunk) { + if(fileStream) { + fileStream.write(chunk); + } else { + fieldValue = fieldValue + chunk; + } + }, + cbPartEnd: function() { + if(fileStream) { + fileStream.end(); + fileStream = null; + } else { + console.log("Data was " + fieldValue); + fieldValue = ""; + } + }, + cbFinished: function(err) { + if(err) { + state.sendResponse(400,{"Content-Type": "text/plain"},"Bad Request: " + err); + } else { + state.sendResponse(200, {"Content-Type": "text/plain"},"Multipart data processed"); + } + } + }); +} + +UploadManager.prototype.close = function() { +}; + +exports.UploadManager = UploadManager; + +})(); \ No newline at end of file