From bfa062f23d2727c6f38e8d0ba48648ab906144b3 Mon Sep 17 00:00:00 2001 From: Joshua Fontany Date: Thu, 4 Feb 2021 08:11:07 -0800 Subject: [PATCH] Fix filesystem (#5465) --- core/modules/commands/savewikifolder.js | 18 ++++- core/modules/syncer.js | 4 -- core/modules/utils/filesystem.js | 67 +++++++++++++------ .../filesystem/filesystemadaptor.js | 42 ++++++------ plugins/tiddlywiki/savetrail/savetrail.js | 8 +-- .../tiddlywiki/tiddlyweb/tiddlywebadaptor.js | 14 ++-- 6 files changed, 96 insertions(+), 57 deletions(-) diff --git a/core/modules/commands/savewikifolder.js b/core/modules/commands/savewikifolder.js index 1e3ab36c6..f5cfb9cd7 100644 --- a/core/modules/commands/savewikifolder.js +++ b/core/modules/commands/savewikifolder.js @@ -158,11 +158,25 @@ WikiFolderMaker.prototype.saveCustomPlugin = function(pluginTiddler) { }; WikiFolderMaker.prototype.saveTiddler = function(directory,tiddler) { + var title = tiddler.fields.title, fileInfo, pathFilters, extFilters; + if(this.wiki.tiddlerExists("$:/config/FileSystemPaths")) { + pathFilters = this.wiki.getTiddlerText("$:/config/FileSystemPaths","").split("\n"); + } + if(this.wiki.tiddlerExists("$:/config/FileSystemExtensions")) { + extFilters = this.wiki.getTiddlerText("$:/config/FileSystemExtensions","").split("\n"); + } var fileInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{ directory: path.resolve(this.wikiFolderPath,directory), - wiki: this.wiki + wiki: this.wiki, + pathFilters: pathFilters, + extFilters: extFilters, + originalpath: this.wiki.extractTiddlerDataItem("$:/config/OriginalTiddlerPaths",title, "") }); - $tw.utils.saveTiddlerToFileSync(tiddler,fileInfo); + try { + $tw.utils.saveTiddlerToFileSync(tiddler,fileInfo); + } catch (err) { + console.log("SaveWikiFolder: Error saving file '" + fileInfo.filepath + "', tiddler: '" + tiddler.fields.title); + } }; WikiFolderMaker.prototype.saveJSONFile = function(filename,json) { diff --git a/core/modules/syncer.js b/core/modules/syncer.js index 90ed41032..23d21097f 100644 --- a/core/modules/syncer.js +++ b/core/modules/syncer.js @@ -640,10 +640,6 @@ DeleteTiddlerTask.prototype.run = function(callback) { } // Remove the info stored about this tiddler delete self.syncer.tiddlerInfo[self.title]; - if($tw.boot.files){ - // Remove the tiddler from $tw.boot.files - delete $tw.boot.files[self.title]; - } // Invoke the callback callback(null); },{ diff --git a/core/modules/utils/filesystem.js b/core/modules/utils/filesystem.js index 9d05f7a8d..b7fe2156c 100644 --- a/core/modules/utils/filesystem.js +++ b/core/modules/utils/filesystem.js @@ -252,7 +252,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) { extFilters: options.extFilters, wiki: options.wiki }); - if(metaExt){ + if(metaExt) { if(metaExt === ".tid") { // Overriding to the .tid extension needs special handling fileInfo.type = "application/x-tiddler"; @@ -388,20 +388,18 @@ exports.generateTiddlerFilepath = function(title,options) { // If the last write failed with an error, or if path does not start with: // the resolved options.directory, the resolved wikiPath directory, or the wikiTiddlersPath directory, // then encodeURIComponent() and resolve to tiddler directory - var newPath = fullPath, + var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath), encode = (options.fileInfo || {writeError: false}).writeError == true; - if(!encode){ + if(!encode) { encode = !(fullPath.indexOf(path.resolve(directory)) == 0 || fullPath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 || fullPath.indexOf($tw.boot.wikiTiddlersPath) == 0); } - if(encode){ - fullPath = path.resolve(directory, encodeURIComponent(fullPath)); + if(encode) { + writePath = path.resolve(directory,encodeURIComponent(fullPath)); } - // Call hook to allow plugins to modify the final path - fullPath = $tw.hooks.invokeHook("th-make-tiddler-path", newPath, fullPath); // Return the full path to the file - return fullPath; + return writePath; }; /* @@ -419,14 +417,29 @@ exports.saveTiddlerToFile = function(tiddler,fileInfo,callback) { if(err) { return callback(err); } - fs.writeFile(fileInfo.filepath + ".meta",tiddler.getFieldStringBlock({exclude: ["text","bag"]}),"utf8",callback); + fs.writeFile(fileInfo.filepath + ".meta",tiddler.getFieldStringBlock({exclude: ["text","bag"]}),"utf8",function(err) { + if(err) { + return callback(err); + } + return callback(null,fileInfo); + }); }); } else { // Save the tiddler as a self contained templated file if(fileInfo.type === "application/x-tiddler") { - fs.writeFile(fileInfo.filepath,tiddler.getFieldStringBlock({exclude: ["text","bag"]}) + (!!tiddler.fields.text ? "\n\n" + tiddler.fields.text : ""),"utf8",callback); + fs.writeFile(fileInfo.filepath,tiddler.getFieldStringBlock({exclude: ["text","bag"]}) + (!!tiddler.fields.text ? "\n\n" + tiddler.fields.text : ""),"utf8",function(err) { + if(err) { + return callback(err); + } + return callback(null,fileInfo); + }); } else { - fs.writeFile(fileInfo.filepath,JSON.stringify([tiddler.getFieldStrings({exclude: ["bag"]})],null,$tw.config.preferences.jsonSpaces),"utf8",callback); + fs.writeFile(fileInfo.filepath,JSON.stringify([tiddler.getFieldStrings({exclude: ["bag"]})],null,$tw.config.preferences.jsonSpaces),"utf8",function(err) { + if(err) { + return callback(err); + } + return callback(null,fileInfo); + }); } } }; @@ -457,10 +470,12 @@ exports.saveTiddlerToFileSync = function(tiddler,fileInfo) { /* Delete a file described by the fileInfo if it exits */ -exports.deleteTiddlerFile = function(fileInfo, callback) { +exports.deleteTiddlerFile = function(fileInfo,callback) { //Only attempt to delete files that exist on disk if(!fileInfo.filepath || !fs.existsSync(fileInfo.filepath)) { - return callback(null); + //For some reason, the tiddler is only in memory or we can't modify the file at this path + $tw.syncer.displayError("Server deleteTiddlerFile task failed for filepath: "+fileInfo.filepath); + return callback(null,fileInfo); } // Delete the file fs.unlink(fileInfo.filepath,function(err) { @@ -473,10 +488,20 @@ exports.deleteTiddlerFile = function(fileInfo, callback) { if(err) { return callback(err); } - return $tw.utils.deleteEmptyDirs(path.dirname(fileInfo.filepath),callback); + return $tw.utils.deleteEmptyDirs(path.dirname(fileInfo.filepath),function(err) { + if(err) { + return callback(err); + } + return callback(null,fileInfo); + }); }); } else { - return $tw.utils.deleteEmptyDirs(path.dirname(fileInfo.filepath),callback); + return $tw.utils.deleteEmptyDirs(path.dirname(fileInfo.filepath),function(err) { + if(err) { + return callback(err); + } + return callback(null,fileInfo); + }); } }); }; @@ -486,25 +511,25 @@ Cleanup old files on disk, by comparing the options values: adaptorInfo from $tw.syncer.tiddlerInfo bootInfo from $tw.boot.files */ -exports.cleanupTiddlerFiles = function(options, callback) { +exports.cleanupTiddlerFiles = function(options,callback) { var adaptorInfo = options.adaptorInfo || {}, bootInfo = options.bootInfo || {}, title = options.title || "undefined"; if(adaptorInfo.filepath && bootInfo.filepath && adaptorInfo.filepath !== bootInfo.filepath) { - return $tw.utils.deleteTiddlerFile(adaptorInfo, function(err){ + $tw.utils.deleteTiddlerFile(adaptorInfo,function(err) { if(err) { if ((err.code == "EPERM" || err.code == "EACCES") && err.syscall == "unlink") { // Error deleting the previous file on disk, should fail gracefully - $tw.syncer.displayError("Server desynchronized. Error cleaning up previous file for tiddler: "+title, err); - return callback(null); + $tw.syncer.displayError("Server desynchronized. Error cleaning up previous file for tiddler: \""+title+"\"",err); + return callback(null,bootInfo); } else { return callback(err); } } - return callback(null); + return callback(null,bootInfo); }); } else { - return callback(null); + return callback(null,bootInfo); } }; diff --git a/plugins/tiddlywiki/filesystem/filesystemadaptor.js b/plugins/tiddlywiki/filesystem/filesystemadaptor.js index 7e3cd2fa2..5b2eda092 100644 --- a/plugins/tiddlywiki/filesystem/filesystemadaptor.js +++ b/plugins/tiddlywiki/filesystem/filesystemadaptor.js @@ -54,10 +54,10 @@ It is the responsibility of the filesystem adaptor to update this.boot.files for FileSystemAdaptor.prototype.getTiddlerFileInfo = function(tiddler,callback) { // Always generate a fileInfo object when this fuction is called var title = tiddler.fields.title, newInfo, pathFilters, extFilters; - if(this.wiki.tiddlerExists("$:/config/FileSystemPaths")){ + if(this.wiki.tiddlerExists("$:/config/FileSystemPaths")) { pathFilters = this.wiki.getTiddlerText("$:/config/FileSystemPaths","").split("\n"); } - if(this.wiki.tiddlerExists("$:/config/FileSystemExtensions")){ + if(this.wiki.tiddlerExists("$:/config/FileSystemExtensions")) { extFilters = this.wiki.getTiddlerText("$:/config/FileSystemExtensions","").split("\n"); } newInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{ @@ -66,9 +66,8 @@ FileSystemAdaptor.prototype.getTiddlerFileInfo = function(tiddler,callback) { extFilters: extFilters, wiki: this.wiki, fileInfo: this.boot.files[title], - originalpath: this.wiki.extractTiddlerDataItem("$:/config/OriginalTiddlerPaths",title, "") + originalpath: this.wiki.extractTiddlerDataItem("$:/config/OriginalTiddlerPaths",title,"") }); - this.boot.files[title] = newInfo; callback(null,newInfo); }; @@ -76,35 +75,38 @@ FileSystemAdaptor.prototype.getTiddlerFileInfo = function(tiddler,callback) { /* Save a tiddler and invoke the callback with (err,adaptorInfo,revision) */ -FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback) { +FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback,options) { var self = this; + var syncerInfo = options.tiddlerInfo || {}; this.getTiddlerFileInfo(tiddler,function(err,fileInfo) { if(err) { return callback(err); } - $tw.utils.saveTiddlerToFile(tiddler,fileInfo,function(err) { + $tw.utils.saveTiddlerToFile(tiddler,fileInfo,function(err,fileInfo) { if(err) { if ((err.code == "EPERM" || err.code == "EACCES") && err.syscall == "open") { - var bootInfo = self.boot.files[tiddler.fields.title]; - bootInfo.writeError = true; - self.boot.files[tiddler.fields.title] = bootInfo; - $tw.syncer.displayError("Sync for tiddler [["+tiddler.fields.title+"]] will be retried with encoded filepath", encodeURIComponent(bootInfo.filepath)); + fileInfo = fileInfo || self.boot.files[tiddler.fields.title]; + fileInfo.writeError = true; + self.boot.files[tiddler.fields.title] = fileInfo; + $tw.syncer.logger.log("Sync failed for \""+tiddler.fields.title+"\" and will be retried with encoded filepath",encodeURIComponent(fileInfo.filepath)); return callback(err); } else { return callback(err); } } + // Store new boot info only after successful writes + self.boot.files[tiddler.fields.title] = fileInfo; // Cleanup duplicates if the file moved or changed extensions var options = { - adaptorInfo: ($tw.syncer.tiddlerInfo[tiddler.fields.title] || {adaptorInfo: {} }).adaptorInfo, - bootInfo: self.boot.files[tiddler.fields.title] || {}, + adaptorInfo: syncerInfo.adaptorInfo || {}, + bootInfo: fileInfo || {}, title: tiddler.fields.title }; - $tw.utils.cleanupTiddlerFiles(options, function(err){ + $tw.utils.cleanupTiddlerFiles(options,function(err,fileInfo) { if(err) { return callback(err); } - return callback(null, self.boot.files[tiddler.fields.title]); + return callback(null,fileInfo); }); }); }); @@ -127,20 +129,22 @@ FileSystemAdaptor.prototype.deleteTiddler = function(title,callback,options) { fileInfo = this.boot.files[title]; // Only delete the tiddler if we have writable information for the file if(fileInfo) { - $tw.utils.deleteTiddlerFile(fileInfo, function(err){ + $tw.utils.deleteTiddlerFile(fileInfo,function(err,fileInfo) { if(err) { if ((err.code == "EPERM" || err.code == "EACCES") && err.syscall == "unlink") { // Error deleting the file on disk, should fail gracefully - $tw.syncer.displayError("Server desynchronized. Error deleting file for deleted tiddler: "+title, err); - return callback(null); + $tw.syncer.displayError("Server desynchronized. Error deleting file for deleted tiddler \"" + title + "\"",err); + return callback(null,fileInfo); } else { return callback(err); } } - return callback(null); + // Remove the tiddler from self.boot.files & return null adaptorInfo + delete self.boot.files[title]; + return callback(null,null); }); } else { - callback(null); + callback(null,null); } }; diff --git a/plugins/tiddlywiki/savetrail/savetrail.js b/plugins/tiddlywiki/savetrail/savetrail.js index 69ef752a7..ec6fe7c01 100644 --- a/plugins/tiddlywiki/savetrail/savetrail.js +++ b/plugins/tiddlywiki/savetrail/savetrail.js @@ -106,7 +106,7 @@ SaveTrailSyncAdaptor.prototype.saveTiddler = function(tiddler,callback) { saveTiddlerFile(tiddler,{reason: "modified"}); } } - callback(null); + callback(null,null); }; /* @@ -120,7 +120,7 @@ SaveTrailSyncAdaptor.prototype.loadTiddler = function(title,callback) { Delete a tiddler and invoke the callback with (err) */ SaveTrailSyncAdaptor.prototype.deleteTiddler = function(title,callback,options) { - callback(null); + callback(null,null); }; function saveTiddlerFile(tiddler,options) { @@ -139,8 +139,8 @@ function saveTiddlerFile(tiddler,options) { link.setAttribute("target","_blank"); link.setAttribute("rel","noopener noreferrer"); if(Blob !== undefined) { - var blob = new Blob([text], {type: "text/plain"}); - link.setAttribute("href", URL.createObjectURL(blob)); + var blob = new Blob([text],{type: "text/plain"}); + link.setAttribute("href",URL.createObjectURL(blob)); } else { link.setAttribute("href","data:text/plain," + encodeURIComponent(text)); } diff --git a/plugins/tiddlywiki/tiddlyweb/tiddlywebadaptor.js b/plugins/tiddlywiki/tiddlyweb/tiddlywebadaptor.js index 135b91055..ff4db2185 100644 --- a/plugins/tiddlywiki/tiddlyweb/tiddlywebadaptor.js +++ b/plugins/tiddlywiki/tiddlyweb/tiddlywebadaptor.js @@ -182,10 +182,10 @@ TiddlyWebAdaptor.prototype.getSkinnyTiddlers = function(callback) { /* Save a tiddler and invoke the callback with (err,adaptorInfo,revision) */ -TiddlyWebAdaptor.prototype.saveTiddler = function(tiddler,callback) { +TiddlyWebAdaptor.prototype.saveTiddler = function(tiddler,callback,options) { var self = this; if(this.isReadOnly) { - return callback(null); + return callback(null,options.tiddlerInfo.adaptorInfo); } $tw.utils.httpRequest({ url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(tiddler.fields.title), @@ -207,7 +207,7 @@ TiddlyWebAdaptor.prototype.saveTiddler = function(tiddler,callback) { // Invoke the callback callback(null,{ bag: etagInfo.bag - }, etagInfo.revision); + },etagInfo.revision); } } }); @@ -238,12 +238,12 @@ tiddlerInfo: the syncer's tiddlerInfo for this tiddler TiddlyWebAdaptor.prototype.deleteTiddler = function(title,callback,options) { var self = this; if(this.isReadOnly) { - return callback(null); + return callback(null,options.tiddlerInfo.adaptorInfo); } // If we don't have a bag it means that the tiddler hasn't been seen by the server, so we don't need to delete it var bag = options.tiddlerInfo.adaptorInfo && options.tiddlerInfo.adaptorInfo.bag; if(!bag) { - return callback(null); + return callback(null,options.tiddlerInfo.adaptorInfo); } // Issue HTTP request to delete the tiddler $tw.utils.httpRequest({ @@ -253,8 +253,8 @@ TiddlyWebAdaptor.prototype.deleteTiddler = function(title,callback,options) { if(err) { return callback(err); } - // Invoke the callback - callback(null); + // Invoke the callback & return null adaptorInfo + callback(null,null); } }); };