/*\ title: $:/plugins/tiddlywiki/dropbox/dropbox.js type: application/javascript module-type: browser-startup Main Dropbox integration module. It creates the `$tw.plugins.dropbox` object that includes static methods for various Dropbox operations. It also contains a startup function that kicks off the login process \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; // Obfuscated API key var apiKey = "m+qwjj8wFRA=|1TSoitGS9Nz2RTwv+jrUJnsAj0yy57NhQJ4TkZ/+Hw=="; // Query string marker for forcing authentication var queryLoginMarker = "login=true"; // Require async.js var async = require("./async.js"); $tw.plugins.dropbox = { // State data client: null, // Dropbox.js client object fileInfo: {}, // Hashmap of each filename as retrieved from Dropbox (including .meta files): {versionTag:,title:} titleInfo: {}, // Hashmap of each tiddler title retrieved from Dropbox to filename // Titles of various system tiddlers used by the plugin titleIsLoggedIn: "$:/plugins/dropbox/IsLoggedIn", titleUserName: "$:/plugins/dropbox/UserName", titlePublicAppUrl: "$:/plugins/dropbox/PublicAppUrl", titleAppTemplateHtml: "$:/plugins/dropbox/apptemplate.html", titleTiddlerIndex: "$:/plugins/dropbox/Index", titleAppIndexTemplate: "$:/plugins/dropbox/index.template.html", titleWikiName: "$:/plugins/dropbox/WikiName", titleLoadedWikis: "$:/plugins/dropbox/LoadedWikis" }; /* Startup function that sets up Dropbox and, if the queryLoginMarker is present, logs the user in. After login, any dropbox-startup modules are executed. */ exports.startup = function() { if(!$tw.browser) { return; } // Mark us as not logged in $tw.wiki.addTiddler({title: $tw.plugins.dropbox.titleIsLoggedIn, text: "no"},true); // Initialise Dropbox for sandbox access $tw.plugins.dropbox.client = new Dropbox.Client({key: apiKey, sandbox: true}); // Use the basic redirection authentication driver $tw.plugins.dropbox.client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true})); // Authenticate ourselves if the marker is in the document query string if(document.location.search.indexOf(queryLoginMarker) !== -1) { $tw.plugins.dropbox.login(); } else { $tw.plugins.dropbox.invokeDropboxStartupModules(false); } }; /* Error handling */ $tw.plugins.dropbox.showError = function(error) { alert("Dropbox error: " + error); console.log("Dropbox error: " + error); }; /* Authenticate */ $tw.plugins.dropbox.login = function() { $tw.plugins.dropbox.client.authenticate(function(error, client) { if(error) { return $tw.plugins.dropbox.showError(error); } // Mark us as logged in $tw.wiki.addTiddler({title: $tw.plugins.dropbox.titleIsLoggedIn, text: "yes"},true); // Get user information $tw.plugins.dropbox.getUserInfo(function() { // Invoke any dropbox-startup modules $tw.plugins.dropbox.invokeDropboxStartupModules(true); }); }); }; /* Invoke any dropbox-startup modules */ $tw.plugins.dropbox.invokeDropboxStartupModules = function(loggedIn) { $tw.modules.forEachModuleOfType("dropbox-startup",function(title,module) { module.startup(loggedIn); }); }; /* Get user information */ $tw.plugins.dropbox.getUserInfo = function(callback) { $tw.plugins.dropbox.client.getUserInfo(function(error,userInfo) { if(error) { callback(error); return $tw.plugins.dropbox.showError(error); } $tw.plugins.dropbox.userInfo = userInfo; // Save the username $tw.wiki.addTiddler({title: $tw.plugins.dropbox.titleUserName, text: userInfo.name},true); callback(); }); }; /* Logout */ $tw.plugins.dropbox.logout = function() { $tw.plugins.dropbox.client.signOut(function(error) { if(error) { return $tw.plugins.dropbox.showError(error); } // Mark us as logged out $tw.wiki.deleteTiddler($tw.plugins.dropbox.titleUserName); $tw.wiki.addTiddler({title: $tw.plugins.dropbox.titleIsLoggedIn, text: "no"},true); // Remove any marker from the query string document.location.search = ""; }); }; /* Load tiddlers representing each wiki in a folder */ $tw.plugins.dropbox.loadWikiFiles = function(path,callback) { // First get the list of tiddler files $tw.plugins.dropbox.client.stat(path,{readDir: true},function(error,stat,stats) { if(error) { return $tw.plugins.dropbox.showError(error); } // Create a tiddler for each folder for(var s=0; s> 18; // 16515072 = (2^6 - 1) << 18 b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 d = chunk & 63; // 63 = 2^6 - 1 // Convert the raw binary segments to the appropriate ASCII encoding base64.push(charmap[a],charmap[b],charmap[c],charmap[d]); } // Deal with the remaining bytes and padding if(byteRemainder === 1) { chunk = data[mainLength]; a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 // Set the 4 least significant bits to zero b = (chunk & 3) << 4; // 3 = 2^2 - 1 base64.push(charmap[a],charmap[b],"=="); } else if(byteRemainder === 2) { chunk = (data[mainLength] << 8) | data[mainLength + 1]; a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 // Set the 2 least significant bits to zero c = (chunk & 15) << 2; // 15 = 2^4 - 1 base64.push(charmap[a],charmap[b],charmap[c],"="); } return base64.join(""); }; /* Rewrite the document location to include a force login marker */ $tw.plugins.dropbox.forceLogin = function() { if(document.location.search.indexOf(queryLoginMarker) === -1) { document.location.search = queryLoginMarker; } }; /* Create a new empty TiddlyWiki */ $tw.plugins.dropbox.createWiki = function(wikiName) { // Remove any dodgy characters from the wiki name wikiName = wikiName.replace(/[\!\@\€\£\%\^\*\+\$\:\?\#\/\\\<\>\|\"\'\`\~\=]/g,""); // Check that the name isn't now empty if(wikiName.length === 0) { return alert("Bad wiki name"); } // Create the wiki async.series([ function(callback) { // First create the wiki folder $tw.plugins.dropbox.client.mkdir(wikiName,function(error,stat) { callback(error); }); }, function(callback) { // Second create the tiddlers folder $tw.plugins.dropbox.client.mkdir(wikiName + "/tiddlers",function(error,stat) { callback(error); }); }, function(callback) { // Third save the template app HTML file var tiddler = $tw.wiki.getTiddler($tw.plugins.dropbox.titleAppTemplateHtml); if(!tiddler) { callback("Cannot find app template tiddler"); } else { $tw.plugins.dropbox.client.writeFile(wikiName + "/index.html",tiddler.fields.text,function(error,stat) { callback(error); }); } } ], // optional callback function(error,results) { if(error) { $tw.plugins.dropbox.showError(error); } else { alert("Created wiki " + wikiName); } }); }; /* Save the index file */ $tw.plugins.dropbox.saveTiddlerIndex = function(path,callback) { // Get the tiddler index information var index = {tiddlers: [],systemTiddlers: [], fileInfo: $tw.plugins.dropbox.fileInfo}; // First all the tiddlers $tw.wiki.forEachTiddler(function(title,tiddler) { if(tiddler.isSystem) { index.systemTiddlers.push(tiddler.fields); } else { index.tiddlers.push(tiddler.fields); } }); // Save everything to a tiddler $tw.wiki.addTiddler({title: $tw.plugins.dropbox.titleTiddlerIndex, type: "application/json", text: JSON.stringify(index,null,$tw.config.preferences.jsonSpaces)},true); // Generate the index file var file = $tw.wiki.renderTiddler("text/plain",$tw.plugins.dropbox.titleAppIndexTemplate); // Save the index to Dropbox $tw.plugins.dropbox.client.writeFile(path,file,function(error,stat) { callback(error); }); }; /* Setup synchronisation back to Dropbox */ $tw.plugins.dropbox.setupSyncer = function(wiki) { wiki.addEventListener("change",function(changes) { $tw.plugins.dropbox.syncChanges(changes,wiki); }); }; $tw.plugins.dropbox.syncChanges = function(changes,wiki) { // Create a queue of tasks to save or delete tiddlers var q = async.queue($tw.plugins.dropbox.syncTask,2); // Called when we've processed all the files q.drain = function () { }; // Process each of the changes for(var title in changes) { var tiddler = wiki.getTiddler(title), filename = $tw.plugins.dropbox.titleInfo[title], contentType = tiddler ? tiddler.fields.type : null; contentType = contentType || "text/vnd.tiddlywiki"; var contentTypeInfo = $tw.config.contentTypeInfo[contentType], isNew = false; // Figure out the pathname of the tiddler if(!filename) { var extension = contentTypeInfo ? contentTypeInfo.extension : ""; filename = encodeURIComponent(title) + extension; $tw.plugins.dropbox.titleInfo[title] = filename; isNew = true; } // Push the appropriate task if(tiddler) { if(contentType === "text/vnd.tiddlywiki") { // .tid file q.push({ type: "save", title: title, path: $tw.plugins.dropbox.titleInfo[title], content: wiki.serializeTiddlers([tiddler],"application/x-tiddler"), isNew: isNew }); } else { // main file plus meta file q.push({ type: "save", title: title, path: $tw.plugins.dropbox.titleInfo[title], content: tiddler.fields.text, metadata: tiddler.getFieldStringBlock({exclude: ["text"]}), isNew: isNew }); } } else { q.push({ type: "delete", title: title, path: $tw.plugins.dropbox.titleInfo[title] }); } } }; /* Perform a single sync task */ $tw.plugins.dropbox.syncTask = function(task,callback) { if(task.type === "delete") { console.log("Deleting",task.path); } else if(task.type === "save") { console.log("Saving",task.path,task); } }; })();