1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-12-03 14:59:57 +00:00
TiddlyWiki5/plugins/tiddlywiki/multiwikiserver/modules/store/attachments.js
webplusai 3287dce40c
MWS: fix editing attachment tiddlers (#8455)
* fix breaking bug in image tiddler attachment

* fix comments

* fix code format

* refactor processIncomingTiddler flow

* remove whitespaces after if statements

* refactor attachment_blob persistence flow

* refactor process tiddler to support different attachments

* add tests for attachment

* add more attachement test cases

* working on adding instanbul for test coverage report

* code coverage report generation

* remove unnecessary packages

* fix comments
2024-08-28 17:13:52 +01:00

182 lines
6.0 KiB
JavaScript

/*\
title: $:/plugins/tiddlywiki/multiwikiserver/store/attachments.js
type: application/javascript
module-type: library
Class to handle the attachments in the filing system
The store folder looks like this:
store/
inbox/ - files that are in the process of being uploaded via a multipart form upload
202402282125432742/
0
1
...
...
files/ - files that are the text content of large tiddlers
b7def178-79c4-4d88-b7a4-39763014a58b/
data.jpg - the extension is provided for convenience when directly inspecting the file system
meta.json - contains:
{
"filename": "data.jpg",
"type": "video/mp4",
"uploaded": "2024021821224823"
}
database.sql - The database file (managed by sql-tiddler-database.js)
\*/
(function() {
/*
Class to handle an attachment store. Options include:
storePath - path to the store
*/
function AttachmentStore(options) {
options = options || {};
this.storePath = options.storePath;
}
/*
Check if an attachment name is valid
*/
AttachmentStore.prototype.isValidAttachmentName = function(attachment_name) {
const re = new RegExp('^[a-f0-9]{64}$');
return re.test(attachment_name);
};
/*
Saves an attachment to a file. Options include:
text: text content (may be binary)
type: MIME type of content
reference: reference to use for debugging
_canonical_uri: canonical uri of the content
*/
AttachmentStore.prototype.saveAttachment = function(options) {
const path = require("path"),
fs = require("fs");
// Compute the content hash for naming the attachment
const contentHash = $tw.sjcl.codec.hex.fromBits($tw.sjcl.hash.sha256.hash(options.text)).slice(0,64).toString();
// Choose the best file extension for the attachment given its type
const contentTypeInfo = $tw.config.contentTypeInfo[options.type] || $tw.config.contentTypeInfo["application/octet-stream"];
// Creat the attachment directory
const attachmentPath = path.resolve(this.storePath,"files",contentHash);
$tw.utils.createDirectory(attachmentPath);
// Save the data file
const dataFilename = "data" + contentTypeInfo.extension;
fs.writeFileSync(path.resolve(attachmentPath,dataFilename),options.text,contentTypeInfo.encoding);
// Save the meta.json file
fs.writeFileSync(path.resolve(attachmentPath,"meta.json"),JSON.stringify({
_canonical_uri: options._canonical_uri,
created: $tw.utils.stringifyDate(new Date()),
modified: $tw.utils.stringifyDate(new Date()),
contentHash: contentHash,
filename: dataFilename,
type: options.type
},null,4));
return contentHash;
};
/*
Adopts an attachment file into the store
*/
AttachmentStore.prototype.adoptAttachment = function(incomingFilepath,type,hash,_canonical_uri) {
const path = require("path"),
fs = require("fs");
// Choose the best file extension for the attachment given its type
const contentTypeInfo = $tw.config.contentTypeInfo[type] || $tw.config.contentTypeInfo["application/octet-stream"];
// Creat the attachment directory
const attachmentPath = path.resolve(this.storePath,"files",hash);
$tw.utils.createDirectory(attachmentPath);
// Rename the data file
const dataFilename = "data" + contentTypeInfo.extension,
dataFilepath = path.resolve(attachmentPath,dataFilename);
fs.renameSync(incomingFilepath,dataFilepath);
// Save the meta.json file
fs.writeFileSync(path.resolve(attachmentPath,"meta.json"),JSON.stringify({
_canonical_uri: _canonical_uri,
created: $tw.utils.stringifyDate(new Date()),
modified: $tw.utils.stringifyDate(new Date()),
contentHash: hash,
filename: dataFilename,
type: type
},null,4));
return hash;
};
/*
Get an attachment ready to stream. Returns null if there is an error or:
stream: filestream of file
type: type of file
*/
AttachmentStore.prototype.getAttachmentStream = function(attachment_name) {
const path = require("path"),
fs = require("fs");
// Check the attachment name
if(this.isValidAttachmentName(attachment_name)) {
// Construct the path to the attachment directory
const attachmentPath = path.resolve(this.storePath,"files",attachment_name);
// Read the meta.json file
const metaJsonPath = path.resolve(attachmentPath,"meta.json");
if(fs.existsSync(metaJsonPath) && fs.statSync(metaJsonPath).isFile()) {
const meta = $tw.utils.parseJSONSafe(fs.readFileSync(metaJsonPath,"utf8"),function() {return null;});
if(meta) {
const dataFilepath = path.resolve(attachmentPath,meta.filename);
// Check if the data file exists
if(fs.existsSync(dataFilepath) && fs.statSync(dataFilepath).isFile()) {
// Stream the file
return {
stream: fs.createReadStream(dataFilepath),
type: meta.type
};
}
}
}
}
// An error occured
return null;
};
/*
Get the size of an attachment file given the contentHash.
Returns the size in bytes, or null if the file doesn't exist.
*/
AttachmentStore.prototype.getAttachmentFileSize = function(contentHash) {
const path = require("path"),
fs = require("fs");
// Construct the path to the attachment directory
const attachmentPath = path.resolve(this.storePath, "files", contentHash);
// Read the meta.json file
const metaJsonPath = path.resolve(attachmentPath, "meta.json");
if(fs.existsSync(metaJsonPath) && fs.statSync(metaJsonPath).isFile()) {
const meta = $tw.utils.parseJSONSafe(fs.readFileSync(metaJsonPath, "utf8"), function() { return null; });
if(meta) {
const dataFilepath = path.resolve(attachmentPath, meta.filename);
// Check if the data file exists and return its size
if(fs.existsSync(dataFilepath) && fs.statSync(dataFilepath).isFile()) {
return fs.statSync(dataFilepath).size;
}
}
}
// Return null if the file doesn't exist or there was an error
return null;
};
AttachmentStore.prototype.getAttachmentMetadata = function(attachmentBlob) {
const path = require("path"),
fs = require("fs");
const attachmentPath = path.resolve(this.storePath, "files", attachmentBlob);
const metaJsonPath = path.resolve(attachmentPath, "meta.json");
if(fs.existsSync(metaJsonPath)) {
const metadata = JSON.parse(fs.readFileSync(metaJsonPath, "utf8"));
return metadata;
}
return null;
};
exports.AttachmentStore = AttachmentStore;
})();