2013-03-24 12:22:21 +00:00
|
|
|
/*\
|
|
|
|
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
|
2014-04-03 14:40:30 +00:00
|
|
|
var fs = !$tw.browser ? require("fs") : null,
|
|
|
|
path = !$tw.browser ? require("path") : null;
|
2013-10-11 21:10:10 +00:00
|
|
|
|
2013-03-24 12:22:21 +00:00
|
|
|
function FileSystemAdaptor(syncer) {
|
2013-10-11 22:43:51 +00:00
|
|
|
var self = this;
|
2013-03-24 12:22:21 +00:00
|
|
|
this.syncer = syncer;
|
2013-10-11 21:10:10 +00:00
|
|
|
this.watchers = {};
|
|
|
|
this.pending = {};
|
2014-02-14 07:53:41 +00:00
|
|
|
this.logger = new $tw.utils.Logger("FileSystem");
|
2013-10-11 21:10:10 +00:00
|
|
|
this.setwatcher = function(filename, title) {
|
2013-10-29 14:47:18 +00:00
|
|
|
return undefined;
|
2013-10-11 21:10:10 +00:00
|
|
|
return this.watchers[filename] = this.watchers[filename] ||
|
|
|
|
fs.watch(filename, {persistent: false}, function(e) {
|
2014-02-14 07:53:41 +00:00
|
|
|
self.logger.log("Error:",e,filename);
|
2013-10-11 21:10:10 +00:00
|
|
|
if(e === "change") {
|
|
|
|
var tiddlers = $tw.loadTiddlersFromFile(filename).tiddlers;
|
|
|
|
for(var t in tiddlers) {
|
2013-10-11 22:43:51 +00:00
|
|
|
if(tiddlers[t].title) {
|
|
|
|
$tw.wiki.addTiddler(tiddlers[t]);
|
|
|
|
}
|
2013-10-11 21:10:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for(var f in $tw.boot.files) {
|
|
|
|
var fileInfo = $tw.boot.files[f];
|
|
|
|
this.setwatcher(fileInfo.filepath, f);
|
|
|
|
}
|
2013-12-18 21:11:52 +00:00
|
|
|
// Create the <wiki>/tiddlers folder if it doesn't exist
|
|
|
|
// TODO: we should create the path recursively
|
|
|
|
if(!fs.existsSync($tw.boot.wikiTiddlersPath)) {
|
|
|
|
fs.mkdirSync($tw.boot.wikiTiddlersPath);
|
|
|
|
}
|
2013-03-24 12:22:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FileSystemAdaptor.prototype.getTiddlerInfo = function(tiddler) {
|
|
|
|
return {};
|
|
|
|
};
|
|
|
|
|
2013-03-25 10:43:46 +00:00
|
|
|
$tw.config.typeInfo = {
|
|
|
|
"text/vnd.tiddlywiki": {
|
|
|
|
fileType: "application/x-tiddler",
|
2013-03-25 12:12:53 +00:00
|
|
|
extension: ".tid"
|
2013-03-25 11:09:34 +00:00
|
|
|
},
|
2013-03-25 10:43:46 +00:00
|
|
|
"image/jpeg" : {
|
|
|
|
hasMetaFile: true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-25 11:09:34 +00:00
|
|
|
$tw.config.typeTemplates = {
|
|
|
|
"application/x-tiddler": "$:/core/templates/tid-tiddler"
|
|
|
|
};
|
|
|
|
|
2013-03-25 10:43:46 +00:00
|
|
|
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];
|
2013-03-25 12:11:34 +00:00
|
|
|
// 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 || "";
|
2013-03-25 10:43:46 +00:00
|
|
|
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 = {};
|
2014-03-31 16:17:36 +00:00
|
|
|
fileInfo.filepath = $tw.boot.wikiTiddlersPath + path.sep + self.generateTiddlerFilename(title,extension,files);
|
2013-03-25 12:11:34 +00:00
|
|
|
fileInfo.type = typeInfo.fileType || tiddler.fields.type;
|
2013-03-25 10:43:46 +00:00
|
|
|
fileInfo.hasMetaFile = typeInfo.hasMetaFile;
|
|
|
|
// Save the newly created fileInfo
|
|
|
|
$tw.boot.files[title] = fileInfo;
|
2013-10-11 21:10:10 +00:00
|
|
|
self.pending[fileInfo.filepath] = title;
|
2013-03-25 10:43:46 +00:00
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
|
2013-03-24 12:22:21 +00:00
|
|
|
/*
|
|
|
|
Save a tiddler and invoke the callback with (err,adaptorInfo,revision)
|
|
|
|
*/
|
|
|
|
FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback) {
|
2013-10-11 21:10:10 +00:00
|
|
|
var self = this;
|
2013-03-25 10:43:46 +00:00
|
|
|
this.getTiddlerFileInfo(tiddler,function(err,fileInfo) {
|
2013-03-25 12:11:34 +00:00
|
|
|
var template, content, encoding;
|
2013-10-11 21:10:10 +00:00
|
|
|
function _finish() {
|
|
|
|
if(self.pending[fileInfo.filepath]) {
|
|
|
|
self.setwatcher(fileInfo.filepath, tiddler.fields.title);
|
|
|
|
delete self.pending[fileInfo.filepath];
|
|
|
|
}
|
|
|
|
callback(null, {}, 0);
|
|
|
|
}
|
2013-03-25 10:43:46 +00:00
|
|
|
if(err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2013-10-11 22:43:51 +00:00
|
|
|
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) {
|
2013-03-25 12:11:34 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2013-11-08 08:51:14 +00:00
|
|
|
content = $tw.wiki.renderTiddler("text/plain","$:/core/templates/tiddler-metadata",{variables: {currentTiddler: tiddler.fields.title}});
|
2013-03-25 12:11:34 +00:00
|
|
|
fs.writeFile(fileInfo.filepath + ".meta",content,{encoding: "utf8"},function (err) {
|
|
|
|
if(err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2014-02-14 07:53:41 +00:00
|
|
|
self.logger.log("Saved file",fileInfo.filepath);
|
2013-10-11 21:10:10 +00:00
|
|
|
_finish();
|
2013-03-25 12:11:34 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Save the tiddler as a self contained templated file
|
|
|
|
template = $tw.config.typeTemplates[fileInfo.type];
|
2013-11-08 08:51:14 +00:00
|
|
|
content = $tw.wiki.renderTiddler("text/plain",template,{variables: {currentTiddler: tiddler.fields.title}});
|
2013-03-25 12:11:34 +00:00
|
|
|
fs.writeFile(fileInfo.filepath,content,{encoding: "utf8"},function (err) {
|
|
|
|
if(err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2014-02-14 07:53:41 +00:00
|
|
|
self.logger.log("Saved file",fileInfo.filepath);
|
2013-10-11 21:10:10 +00:00
|
|
|
_finish();
|
2013-03-25 12:11:34 +00:00
|
|
|
});
|
|
|
|
}
|
2013-03-25 10:43:46 +00:00
|
|
|
});
|
2013-03-24 12:22:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Load a tiddler and invoke the callback with (err,tiddlerFields)
|
2013-12-11 11:45:15 +00:00
|
|
|
|
|
|
|
We don't need to implement loading for the file system adaptor, because all the tiddler files will have been loaded during the boot process.
|
2013-03-24 12:22:21 +00:00
|
|
|
*/
|
|
|
|
FileSystemAdaptor.prototype.loadTiddler = function(title,callback) {
|
2013-12-11 11:45:15 +00:00
|
|
|
callback(null,null);
|
2013-03-24 12:22:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Delete a tiddler and invoke the callback with (err)
|
|
|
|
*/
|
|
|
|
FileSystemAdaptor.prototype.deleteTiddler = function(title,callback) {
|
2013-03-25 20:16:12 +00:00
|
|
|
var self = this,
|
|
|
|
fileInfo = $tw.boot.files[title];
|
|
|
|
// Only delete the tiddler if we have writable information for the file
|
|
|
|
if(fileInfo) {
|
2014-02-06 21:36:30 +00:00
|
|
|
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);
|
2013-10-11 21:10:10 +00:00
|
|
|
}
|
2014-02-14 07:53:41 +00:00
|
|
|
self.logger.log("Deleted file",fileInfo.filepath);
|
2014-02-06 21:36:30 +00:00
|
|
|
// Delete the metafile if present
|
|
|
|
if(fileInfo.hasMetaFile) {
|
|
|
|
fs.unlink(fileInfo.filepath + ".meta",function(err) {
|
|
|
|
if(err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2013-03-25 20:16:12 +00:00
|
|
|
callback(null);
|
2014-02-06 21:36:30 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
});
|
2013-03-25 20:16:12 +00:00
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
2013-03-24 12:22:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if(fs) {
|
|
|
|
exports.adaptorClass = FileSystemAdaptor;
|
|
|
|
}
|
|
|
|
|
|
|
|
})();
|