TiddlyWiki5/plugins/tiddlywiki/filesystem/filesystemadaptor.js

232 lines
6.7 KiB
JavaScript

/*\
title: $:/plugins/tiddlywiki/filesystem/filesystemadaptor.js
type: application/javascript
module-type: syncadaptor
A sync adaptor module for synchronising with the local filesystem via node.js APIs
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
// Get a reference to the file system
var fs = !$tw.browser ? require("fs") : null;
function FileSystemAdaptor(syncer) {
var self = this;
this.syncer = syncer;
this.watchers = {};
this.pending = {};
this.setwatcher = function(filename, title) {
return this.watchers[filename] = this.watchers[filename] ||
fs.watch(filename, {persistent: false}, function(e) {
console.log("Filesystem:", e, filename);
if(e === "change") {
var tiddlers = $tw.loadTiddlersFromFile(filename).tiddlers;
for(var t in tiddlers) {
if(tiddlers[t].title) {
$tw.wiki.addTiddler(tiddlers[t]);
}
}
}
});
}
for(var f in $tw.boot.files) {
var fileInfo = $tw.boot.files[f];
this.setwatcher(fileInfo.filepath, f);
}
}
FileSystemAdaptor.prototype.getTiddlerInfo = function(tiddler) {
return {};
};
$tw.config.typeInfo = {
"text/vnd.tiddlywiki": {
fileType: "application/x-tiddler",
extension: ".tid"
},
"image/jpeg" : {
hasMetaFile: true
}
};
$tw.config.typeTemplates = {
"application/x-tiddler": "$:/core/templates/tid-tiddler"
};
FileSystemAdaptor.prototype.getTiddlerFileInfo = function(tiddler,callback) {
// See if we've already got information about this file
var self = this,
title = tiddler.fields.title,
fileInfo = $tw.boot.files[title];
// Get information about how to save tiddlers of this type
var type = tiddler.fields.type || "text/vnd.tiddlywiki",
typeInfo = $tw.config.typeInfo[type];
if(!typeInfo) {
typeInfo = $tw.config.typeInfo["text/vnd.tiddlywiki"];
}
var extension = typeInfo.extension || "";
if(!fileInfo) {
// If not, we'll need to generate it
// Start by getting a list of the existing files in the directory
fs.readdir($tw.boot.wikiTiddlersPath,function(err,files) {
if(err) {
return callback(err);
}
// Assemble the new fileInfo
fileInfo = {};
fileInfo.filepath = $tw.boot.wikiTiddlersPath + "/" + self.generateTiddlerFilename(title,extension,files);
fileInfo.type = typeInfo.fileType || tiddler.fields.type;
fileInfo.hasMetaFile = typeInfo.hasMetaFile;
// Save the newly created fileInfo
$tw.boot.files[title] = fileInfo;
self.pending[fileInfo.filepath] = title;
// Pass it to the callback
callback(null,fileInfo);
});
} else {
// Otherwise just invoke the callback
callback(null,fileInfo);
}
};
/*
Given a tiddler title and an array of existing filenames, generate a new legal filename for the title, case insensitively avoiding the array of existing filenames
*/
FileSystemAdaptor.prototype.generateTiddlerFilename = function(title,extension,existingFilenames) {
// First remove any of the characters that are illegal in Windows filenames
var baseFilename = title.replace(/\<|\>|\:|\"|\/|\\|\||\?|\*|\^/g,"_");
// Truncate the filename if it is too long
if(baseFilename.length > 200) {
baseFilename = baseFilename.substr(0,200) + extension;
}
// Start with the base filename plus the extension
var filename = baseFilename + extension,
count = 1;
// Add a discriminator if we're clashing with an existing filename
while(existingFilenames.indexOf(filename) !== -1) {
filename = baseFilename + " " + (count++) + extension;
}
return filename;
};
/*
Save a tiddler and invoke the callback with (err,adaptorInfo,revision)
*/
FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback) {
var self = this;
this.getTiddlerFileInfo(tiddler,function(err,fileInfo) {
var template, content, encoding;
function _finish() {
if(self.pending[fileInfo.filepath]) {
self.setwatcher(fileInfo.filepath, tiddler.fields.title);
delete self.pending[fileInfo.filepath];
}
callback(null, {}, 0);
}
if(err) {
return callback(err);
}
if($tw.boot.wikiInfo.doNotSave && $tw.boot.wikiInfo.doNotSave.indexOf(tiddler.fields.title) !== -1) {
// Don't save the tiddler if it is on the blacklist
return callback(null,{},0);
}
if(self.watchers[fileInfo.filepath]) {
self.watchers[fileInfo.filepath].close();
delete self.watchers[fileInfo.filepath];
self.pending[fileInfo.filepath] = tiddler.fields.title;
}
if(fileInfo.hasMetaFile) {
// Save the tiddler as a separate body and meta file
var typeInfo = $tw.config.contentTypeInfo[fileInfo.type];
fs.writeFile(fileInfo.filepath,tiddler.fields.text,{encoding: typeInfo.encoding},function(err) {
if(err) {
return callback(err);
}
content = $tw.wiki.renderTiddler("text/plain","$:/core/templates/tiddler-metadata",{tiddlerTitle: tiddler.fields.title});
fs.writeFile(fileInfo.filepath + ".meta",content,{encoding: "utf8"},function (err) {
if(err) {
return callback(err);
}
console.log("FileSystem: Saved file",fileInfo.filepath);
_finish();
});
});
} else {
// Save the tiddler as a self contained templated file
template = $tw.config.typeTemplates[fileInfo.type];
content = $tw.wiki.renderTiddler("text/plain",template,{tiddlerTitle: tiddler.fields.title});
fs.writeFile(fileInfo.filepath,content,{encoding: "utf8"},function (err) {
if(err) {
return callback(err);
}
console.log("FileSystem: Saved file",fileInfo.filepath);
_finish();
});
}
});
};
/*
Load a tiddler and invoke the callback with (err,tiddlerFields)
*/
FileSystemAdaptor.prototype.loadTiddler = function(title,callback) {
console.log("FileSystem: Loading",title);
callback(null,{title: title, text: "Fake tiddler: " + title});
};
/*
Delete a tiddler and invoke the callback with (err)
*/
FileSystemAdaptor.prototype.deleteTiddler = function(title,callback) {
var self = this,
fileInfo = $tw.boot.files[title];
// Only delete the tiddler if we have writable information for the file
if(fileInfo) {
if($tw.boot.wikiInfo.doNotSave && $tw.boot.wikiInfo.doNotSave.indexOf(title) !== -1) {
// Don't delete the tiddler if it is on the blacklist
callback(null);
} else {
if(this.watchers[fileInfo.filepath]) {
this.watchers[fileInfo.filepath].close();
delete this.watchers[fileInfo.filepath];
}
delete this.pending[fileInfo.filepath];
// Delete the file
fs.unlink(fileInfo.filepath,function(err) {
if(err) {
return callback(err);
}
console.log("FileSystem: Deleted file",fileInfo.filepath);
// Delete the metafile if present
if(fileInfo.hasMetaFile) {
fs.unlink(fileInfo.filepath + ".meta",function(err) {
if(err) {
return callback(err);
}
callback(null);
});
} else {
callback(null);
}
});
}
} else {
callback(null);
}
};
if(fs) {
exports.adaptorClass = FileSystemAdaptor;
}
})();