First pass at upgrade mechanism

This commit is contained in:
Jermolene
2014-07-12 09:09:36 +01:00
parent f08f57c5d2
commit 1f6e16318a
11 changed files with 356 additions and 34 deletions
+11
View File
@@ -0,0 +1,11 @@
title: $:/language/Import/
Listing/Cancel/Caption: Cancel
Listing/Hint: These tiddlers are ready to import:
Listing/Import/Caption: Import
Listing/Select/Caption: Select
Listing/Status/Caption: Status
Listing/Title/Caption: Title
Upgrader/Plugins/Suppressed: Suppressed plugin (due to incoming <<incoming>> being older than existing <<existing>>)
Upgrader/Plugins/Upgraded: Upgraded plugin from <<incoming>> to <<upgraded>>
Upgrader/System/Suppressed: Suppressed system tiddler
+70
View File
@@ -0,0 +1,70 @@
/*\
title: $:/core/modules/commands/makelibrary.js
type: application/javascript
module-type: command
Command to pack all of the plugins in the library into a plugin tiddler of type "library"
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "makelibrary",
synchronous: true
};
var UPGRADE_LIBRARY_TITLE = "$:/UpgradeLibrary";
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var wiki = this.commander.wiki,
fs = require("fs"),
path = require("path"),
upgradeLibraryTitle = this.params[0] || UPGRADE_LIBRARY_TITLE,
tiddlers = {};
// Collect up the library plugins
var collectPlugins = function(folder) {
var pluginFolders = fs.readdirSync(folder);
for(var p=0; p<pluginFolders.length; p++) {
if(!$tw.boot.excludeRegExp.test(pluginFolders[p])) {
pluginFields = $tw.loadPluginFolder(path.resolve(folder,"./" + pluginFolders[p]));
if(pluginFields && pluginFields.title) {
tiddlers[pluginFields.title] = pluginFields;
}
}
}
},
collectPublisherPlugins = function(folder) {
var publisherFolders = fs.readdirSync(folder);
for(var t=0; t<publisherFolders.length; t++) {
if(!$tw.boot.excludeRegExp.test(publisherFolders[t])) {
collectPlugins(path.resolve(folder,"./" + publisherFolders[t]));
}
}
};
collectPublisherPlugins(path.resolve($tw.boot.corePath,$tw.config.pluginsPath));
collectPublisherPlugins(path.resolve($tw.boot.corePath,$tw.config.themesPath));
collectPlugins(path.resolve($tw.boot.corePath,$tw.config.languagesPath));
// Save the upgrade library tiddler
var pluginFields = {
title: upgradeLibraryTitle,
type: "application/json",
"plugin-type": "library",
"text": JSON.stringify({tiddlers: tiddlers},null,$tw.config.preferences.jsonSpaces)
};
wiki.addTiddler(new $tw.Tiddler(pluginFields));
return null;
};
exports.Command = Command;
})();
+1 -1
View File
@@ -18,7 +18,7 @@ Export our filter function
exports.plugintiddlers = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var pluginInfo = options.wiki.getPluginInfo(title);
var pluginInfo = options.wiki.getPluginInfo(title) || options.wiki.getTiddlerData(title,{tiddlers:[]});
if(pluginInfo) {
$tw.utils.each(pluginInfo.tiddlers,function(fields,title) {
results.push(title);
+58
View File
@@ -0,0 +1,58 @@
/*\
title: $:/core/modules/upgraders/plugins.js
type: application/javascript
module-type: upgrader
Upgrader module that checks that plugins are newer than any already installed version
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var UPGRADE_LIBRARY_TITLE = "$:/UpgradeLibrary";
exports.upgrade = function(wiki,titles,tiddlers) {
var self = this,
messages = {},
upgradeLibrary,
getLibraryTiddler = function(title) {
if(!upgradeLibrary) {
upgradeLibrary = wiki.getTiddlerData(UPGRADE_LIBRARY_TITLE,{});
upgradeLibrary.tiddlers = upgradeLibrary.tiddlers || {};
}
return upgradeLibrary.tiddlers[title]
};
// Go through all the incoming tiddlers
$tw.utils.each(titles,function(title) {
var incomingTiddler = tiddlers[title];
// Check if we're dealing with a plugin
if(incomingTiddler && incomingTiddler["plugin-type"] && incomingTiddler["version"]) {
// Upgrade the incoming plugin if we've got a newer version in the upgrade library
var libraryTiddler = getLibraryTiddler(title);
if(libraryTiddler && libraryTiddler["plugin-type"] && libraryTiddler["version"]) {
if($tw.utils.checkVersions(libraryTiddler.version,incomingTiddler.version)) {
tiddlers[title] = libraryTiddler;
messages[title] = $tw.language.getString("Import/Upgrader/Plugins/Upgraded",{variables: {incoming: incomingTiddler.version, upgraded: libraryTiddler.version}});
return;
}
}
// Suppress the incoming plugin if it is older than the currently installed one
var existingTiddler = wiki.getTiddler(title);
if(existingTiddler && existingTiddler.hasField("plugin-type") && existingTiddler.hasField("version")) {
// Reject the incoming plugin by blanking all its fields
if($tw.utils.checkVersions(existingTiddler.fields.version,incomingTiddler.version)) {
tiddlers[title] = Object.create(null);
messages[title] = $tw.language.getString("Import/Upgrader/Plugins/Suppressed",{variables: {incoming: incomingTiddler.version, existing: existingTiddler.fields.version}});
return;
}
}
}
});
return messages;
};
})();
+30
View File
@@ -0,0 +1,30 @@
/*\
title: $:/core/modules/upgraders/system.js
type: application/javascript
module-type: upgrader
Upgrader module that suppresses certain system tiddlers that shouldn't be imported
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var DONT_IMPORT_LIST = ["$:/StoryList","$:/HistoryList"];
exports.upgrade = function(wiki,titles,tiddlers) {
var self = this,
messages = {};
// Check for tiddlers on our list
$tw.utils.each(titles,function(title) {
if(DONT_IMPORT_LIST.indexOf(title) !== -1) {
tiddlers[title] = Object.create(null);
messages[title] = $tw.language.getString("Import/Upgrader/System/Suppressed");
}
});
return messages;
};
})();
+56 -33
View File
@@ -12,6 +12,8 @@ Navigator widget
/*global $tw: false */
"use strict";
var IMPORT_TITLE = "$:/Import";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var NavigatorWidget = function(parseTreeNode,options) {
@@ -26,7 +28,8 @@ var NavigatorWidget = function(parseTreeNode,options) {
{type: "tw-close-all-tiddlers", handler: "handleCloseAllTiddlersEvent"},
{type: "tw-close-other-tiddlers", handler: "handleCloseOtherTiddlersEvent"},
{type: "tw-new-tiddler", handler: "handleNewTiddlerEvent"},
{type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"}
{type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"},
{type: "tw-perform-import", handler: "handlePerformImportEvent"}
]);
};
@@ -402,7 +405,7 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
return false;
};
// Import JSON tiddlers
// Import JSON tiddlers into a pending import tiddler
NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
var self = this;
// Get the tiddlers
@@ -411,54 +414,74 @@ NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
tiddlers = JSON.parse(event.param);
} catch(e) {
}
// Get the current $:/Import tiddler
var importTiddler = this.wiki.getTiddler(IMPORT_TITLE) || {fields: {}},
importData = this.wiki.getTiddlerData(IMPORT_TITLE,{}),
newFields = new Object({
title: IMPORT_TITLE,
type: "application/json",
"plugin-type": "import"
}),
incomingTiddlers = [];
// Process each tiddler
var importedTiddlers = [];
importData.tiddlers = importData.tiddlers || {};
$tw.utils.each(tiddlers,function(tiddlerFields) {
var title = tiddlerFields.title;
// Add it to the store
var imported = self.wiki.importTiddler(new $tw.Tiddler(
self.wiki.getCreationFields(),
self.wiki.getModificationFields(),
tiddlerFields
));
if(imported) {
importedTiddlers.push(title);
if(title) {
incomingTiddlers.push(title);
importData.tiddlers[title] = tiddlerFields;
}
});
// Give the active upgrader modules a chance to process the incoming tiddlers
var messages = this.wiki.invokeUpgraders(incomingTiddlers,importData.tiddlers);
$tw.utils.each(messages,function(message,title) {
newFields["message-" + title] = message;
});
// Deselect any suppressed tiddlers
$tw.utils.each(importData.tiddlers,function(tiddler,title) {
if($tw.utils.count(tiddler) === 0) {
newFields["selection-" + title] = "unchecked";
}
});
// Save the $:/Import tiddler
newFields.text = JSON.stringify(importData,null,$tw.config.preferences.jsonSpaces);
this.wiki.addTiddler(new $tw.Tiddler(importTiddler,newFields));
// Get the story and history details
var storyList = this.getStoryList(),
history = [];
// Create the import report tiddler
if(importedTiddlers.length === 0) {
return false;
}
var title;
if(importedTiddlers.length > 1) {
title = this.wiki.generateNewTitle("$:/temp/ImportReport");
var tiddlerFields = {
title: title,
text: "# [[" + importedTiddlers.join("]]\n# [[") + "]]\n"
};
this.wiki.addTiddler(new $tw.Tiddler(
self.wiki.getCreationFields(),
tiddlerFields,
self.wiki.getModificationFields()
));
} else {
title = importedTiddlers[0];
}
// Add it to the story
if(storyList.indexOf(title) === -1) {
storyList.unshift(title);
if(storyList.indexOf(IMPORT_TITLE) === -1) {
storyList.unshift(IMPORT_TITLE);
}
// And to history
history.push(title);
history.push(IMPORT_TITLE);
// Save the updated story and history
this.saveStoryList(storyList);
this.addToHistory(history);
return false;
};
//
NavigatorWidget.prototype.handlePerformImportEvent = function(event) {
var self = this,
importTiddler = this.wiki.getTiddler(event.param) || {fields: {}},
importData = this.wiki.getTiddlerData(event.param,{tiddlers: {}}),
importReport = [];
// Add the tiddlers to the store
importReport.push("The following tiddlers were imported:\n");
$tw.utils.each(importData.tiddlers,function(tiddlerFields) {
var title = tiddlerFields.title;
if(title && importTiddler.fields["selection-" + title] !== "unchecked") {
self.wiki.addTiddler(new $tw.Tiddler(tiddlerFields));
importReport.push("# [[" + tiddlerFields.title + "]]");
}
});
// Replace the $:/Import tiddler with an import report
this.wiki.addTiddler(new $tw.Tiddler({title: IMPORT_TITLE, text: importReport.join("\n")}));
// Navigate to the $:/Import tiddler
this.addToHistory([IMPORT_TITLE]);
};
exports.navigator = NavigatorWidget;
})();
+27
View File
@@ -1125,4 +1125,31 @@ exports.addToHistory = function(title,fromPageRect,historyTitle) {
this.setTiddlerData(historyTitle,historyList,{"current-tiddler": titles[titles.length-1]});
};
/*
Invoke the available upgrader modules
titles: array of tiddler titles to be processed
tiddlers: hashmap by title of tiddler fields of pending import tiddlers. These can be modified by the upgraders. An entry with no fields indicates a tiddler that was pending import has been suppressed. When entries are added to the pending import the tiddlers hashmap may have entries that are not present in the titles array
Returns a hashmap of messages keyed by tiddler title.
*/
exports.invokeUpgraders = function(titles,tiddlers) {
// Collect up the available upgrader modules
var self = this;
if(!this.upgraderModules) {
this.upgraderModules = [];
$tw.modules.forEachModuleOfType("upgrader",function(title,module) {
if(module.upgrade) {
self.upgraderModules.push(module);
}
});
}
// Invoke each upgrader in turn
var messages = {};
for(var t=0; t<this.upgraderModules.length; t++) {
var upgrader = this.upgraderModules[t],
upgraderMessages = upgrader.upgrade(this,titles,tiddlers);
$tw.utils.extend(messages,upgraderMessages);
}
return messages;
};
})();
+10
View File
@@ -3,10 +3,20 @@ tags: $:/tags/ViewTemplate
<div class="body">
<$list filter="[all[current]has[plugin-type]] -[all[current]field:plugin-type[import]]">
This tiddler is a plugin
</$list>
<$list filter="[all[current]!has[plugin-type]]">
<$transclude>
<$transclude tiddler="$:/language/MissingTiddler/Hint"/>
</$transclude>
</$list>
</div>
+53
View File
@@ -0,0 +1,53 @@
title: $:/core/ui/ViewTemplate/import
tags: $:/tags/ViewTemplate
\define lingo-base() $:/language/Import/
\define messageField()
message-$(currentTiddler)$
\end
\define selectionField()
selection-$(currentTiddler)$
\end
<$list filter="[all[current]field:plugin-type[import]]">
<div class="tw-import">
<<lingo Listing/Hint>>
<table>
<tbody>
<tr>
<th>
<<lingo Listing/Select/Caption>>
</th>
<th>
<<lingo Listing/Title/Caption>>
</th>
<th>
<<lingo Listing/Status/Caption>>
</th>
</tr>
<$list filter="[all[current]plugintiddlers[]sort[title]]">
<tr>
<td>
<$checkbox tiddler=<<storyTiddler>> field=<<selectionField>> checked="checked" unchecked="unchecked" default="checked"/>
</td>
<td>
<$link to={{!!title}}>
<$view field="title"/>
</$link>
</td>
<td>
<$view tiddler=<<storyTiddler>> field=<<messageField>>/>
</td>
</tr>
</$list>
</tbody>
</table>
<$button message="tw-delete-tiddler" param=<<currentTiddler>>><<lingo Listing/Cancel/Caption>></$button>
<$button message="tw-perform-import" param=<<currentTiddler>>><<lingo Listing/Import/Caption>></$button>
</div>
</$list>