Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association

/*** |''Name:''|ArchivePlugin| |''Version:''|2.4.0 (2 Jun 2008)| |''Source''|http://jackparke.googlepages.com/jtw.html#ArchivePlugin ([[del.icio.us|http://del.icio.us/post?url=http://jackparke.googlepages.com/jtw.html%23ArchivePlugin]])| |''Author:''|[[Jack]]| |''Type:''|Plugin| !Description The archive plugin allows you to store tiddler text outside of the tiddlywiki file. Typically you would tag bulky tiddlers or those with infrequently needed content as "Archive" and these would then be archived as separate html files in the sub folder called "archive". !Usage #Create a folder "archive" in the same folder as your tiddlywiki file. #Install the Archive Plugin and reload your tiddlywiki #Tag your bulky tiddlers with "Archive" #Save your tiddlywiki file !To Do * Synchronize tiddler renames/deletions with file system * Lazy loading of archived files via HTTP !Code ***/ //{{{ version.extensions.ArchivePlugin = {major: 2, minor: 4, revision: 0, date: new Date("Jun 6, 2008")}; config.macros.ArchivePlugin = {}; config.macros.ArchivePlugin.init = function () { this.archivePath = getWikiPath('archive'); } // Hijacking the built-in functions TW21Saver.prototype.externalizeTiddler = function(store,tiddler) { try { var extendedAttributes = ""; var usePre = config.options.chkUsePreForStorage; store.forEachField(tiddler, function(tiddler,fieldName,value) { // don't store stuff from the temp namespace if(typeof value != "string") value = ""; if (!fieldName.match(/^temp\./)) extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]); },true); var created = tiddler.created.convertToYYYYMMDDHHMM(); var modified = tiddler.modified.convertToYYYYMMDDHHMM(); var vdate = version.date.convertToYYYYMMDDHHMM(); var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : ""; attributes += (usePre && modified == created) ? "" : ' modified="' + modified +'"'; attributes += (usePre && created == vdate) ? "" :' created="' + created + '"'; var tags = tiddler.getTags(); if(!usePre || tags) attributes += ' tags="' + tags.htmlEncode() + '"'; return ('<div %0="%1"%2%3>%4</'+'div>').format([ usePre ? "title" : "tiddler", tiddler.title.htmlEncode(), attributes, extendedAttributes, usePre ? "

<pre>" + tiddler.saveMe() + "</pre>

" : tiddler.escapeLineBreaks().htmlEncode() ]); } catch (ex) { throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title])); } }; Tiddler.prototype.saveMe = function() { if (this.tags.indexOf('Archive') != -1) { // Save tiddler body to a file in the archive folder if (this.text) saveFile(config.macros.ArchivePlugin.archivePath + this.title.filenameEncode() + '.html', this.text) return ''; } else return this.text.htmlEncode(); } // This hijack ensures plugins can also be archived var archivePlugin_getPluginInfo = getPluginInfo; getPluginInfo = function(tiddler) { alert(tiddler.title) tiddler.text = store.getValue(tiddler, 'text'); return archivePlugin_getPluginInfo(tiddler); } TiddlyWiki.prototype.getValue = function(tiddler, fieldName) { var t = this.resolveTiddler(tiddler); if (!t) return undefined; fieldName = fieldName.toLowerCase(); if (t.tags.indexOf('Archive')!=-1 && fieldName=='text' && t['text']=='') { try { var tmp = loadFile(config.macros.ArchivePlugin.archivePath + t.title.filenameEncode() + '.html'); tmp = (tmp.charCodeAt(0) == 239 ? manualConvertUTF8ToUnicode(tmp) : tmp); } catch (e) { return ''; //alert("{{{Error: Unable to load file '" + config.macros.ArchivePlugin.archivePath + t.title.filenameEncode() + '.html' + "'}}}"); } return tmp; } else { var accessor = TiddlyWiki.standardFieldAccess[fieldName]; if (accessor) { return accessor.get(t); } } return t.fields ? t.fields[fieldName] : undefined; } String.prototype.filenameEncode = function() { return(this.toLowerCase().replace(/[^a-z0-9_-]/g ,"_")); } function getWikiPath(folderName) { var originalPath = document.location.toString(); if(originalPath.substr(0,5) != 'file:') { if(store.tiddlerExists(config.messages.saveInstructions)) story.displayTiddler(null,config.messages.saveInstructions); return; } var localPath = getLocalPath(originalPath); var backSlash = localPath.lastIndexOf('\\') == -1 ? '/' : '\\'; var dirPathPos = localPath.lastIndexOf(backSlash); var subPath = localPath.substr(0,dirPathPos) + backSlash + (folderName ? folderName + backSlash : ''); return subPath; } // Deleting archive files TiddlyWiki.prototype.archivePlugin_removeTiddler = TiddlyWiki.prototype.removeTiddler; TiddlyWiki.prototype.removeTiddler = function(title) { var tiddler = store.getTiddler(title); var filePath = config.macros.ArchivePlugin.archivePath + title.filenameEncode() + '.html'; if (tiddler.tags.indexOf('Archive') != -1) ieDeleteFile(filePath); this.archivePlugin_removeTiddler(title); } function ieDeleteFile(filePath) { // IE Support only if (!config.browser.isIE) return false; try { var fso = new ActiveXObject("Scripting.FileSystemObject"); } catch(ex) {return null;} try { var file = fso.GetFile(filePath); file.Delete(); } catch(ex) {return null;} return true; } //}}}

<<attach inline>>

text/plain .txt .text .js .vbs .asp .cgi .pl ---- text/html .htm .html .hta .htx .mht ---- text/comma-separated-values .csv ---- text/javascript .js ---- text/css .css ---- text/xml .xml .xsl .xslt ---- image/gif .gif ---- image/jpeg .jpg .jpe .jpeg ---- image/png .png ---- image/bmp .bmp ---- image/tiff .tif .tiff ---- audio/basic .au .snd ---- audio/wav .wav ---- audio/x-pn-realaudio .ra .rm .ram ---- audio/x-midi .mid .midi ---- audio/mp3 .mp3 ---- audio/m3u .m3u ---- video/x-ms-asf .asf ---- video/avi .avi ---- video/mpeg .mpg .mpeg ---- video/quicktime .qt .mov .qtvr ---- application/pdf .pdf ---- application/rtf .rtf ---- application/postscript .ai .eps .ps ---- application/wordperfect .wpd ---- application/mswrite .wri ---- application/msexcel .xls .xls3 .xls4 .xls5 .xlw ---- application/msword .doc ---- application/mspowerpoint .ppt .pps ---- application/x-director .swa ---- application/x-shockwave-flash .swf ---- application/x-zip-compressed .zip ---- application/x-gzip .gz ---- application/x-rar-compressed .rar ---- application/octet-stream .com .exe .dll .ocx ---- application/java-archive .jar

/*** |Name|AttachFilePlugin| |Source|http://www.TiddlyTools.com/#AttachFilePlugin| |Documentation|http://www.TiddlyTools.com/#AttachFilePluginInfo| |Version|4.0.0| |Author|Eric Shulman| |License|http://www.TiddlyTools.com/#LegalStatements| |~CoreVersion|2.1| |Type|plugin| |Requires|AttachFilePluginFormatters, AttachFileMIMETypes| |Description|Store binary files as base64-encoded tiddlers with fallback links for separate local and/or remote file storage| Store or link binary files (such as jpg, gif, pdf or even mp3) within your TiddlyWiki document and then use them as images or links from within your tiddler content. > Important note: As of version 3.6.0, in order to //render// images and other binary attachments created with this plugin, you must also install [[AttachFilePluginFormatters]], which extends the behavior of the TiddlyWiki core formatters for embedded images ({{{[img[tooltip|image]]}}}), linked embedded images ({{{[img[tooltip|image][link]]}}}), and external/"pretty" links ({{{[[label|link]]}}}), so that these formatter will process references to attachment tiddlers as if a normal file reference had been provided. | !!!!!Documentation >see [[AttachFilePluginInfo]] !!!!!Inline interface (live) >see [[AttachFile]] (shadow tiddler) ><<tiddler AttachFile>> !!!!!Revisions <<< 2009.06.04 [4.0.0] changed attachment storage format to use //sections// instead of embedded substring markers. |please see [[AttachFilePluginInfo]] for additional revision details| 2005.07.20 [1.0.0] Initial Release <<< !!!!!Code ***/ // // version //{{{ version.extensions.AttachFilePlugin= {major: 4, minor: 0, revision: 0, date: new Date(2009,6,4)}; // shadow tiddler config.shadowTiddlers.AttachFile="<<attach inline>>"; // add 'attach' backstage task (insert before built-in 'importTask') if (config.tasks) { // for TW2.2b or above config.tasks.attachTask = { text: "attach", tooltip: "Attach a binary file as a tiddler", content: "<<attach inline>>" } config.backstageTasks.splice(config.backstageTasks.indexOf("importTask"),0,"attachTask"); } config.macros.attach = { // // lingo //{{{ label: "attach file", tooltip: "Attach a file to this document", linkTooltip: "Attachment: ", typeList: "AttachFileMIMETypes", titlePrompt: " enter tiddler title...", MIMEPrompt: "<option value=''>select MIME type...</option><option value='editlist'>[edit list...]</option>", localPrompt: " enter local path/filename...", URLPrompt: " enter remote URL...", tiddlerErr: "Please enter a tiddler title", sourceErr: "Please enter a source path/filename", storageErr: "Please select a storage method: embedded, local or remote", MIMEErr: "Unrecognized file format. Please select a MIME type", localErr: "Please enter a local path/filename", URLErr: "Please enter a remote URL", fileErr: "Invalid path/file or file not found", tiddlerFormat: '!usage

{{{%0}}}

%0

!notes

%1

!type

%2

!file

%3

!url

%4

!data

%5

', //}}} // // macro definition //{{{ handler: function(place,macroName,params) { if (params && !params[0]) { createTiddlyButton(place,this.label,this.tooltip,this.toggleAttachPanel); return; } var id=params.shift(); this.createAttachPanel(place,id+"_attachPanel",params); document.getElementById(id+"_attachPanel").style.position="static"; document.getElementById(id+"_attachPanel").style.display="block"; }, //}}} //{{{ createAttachPanel: function(place,panel_id,params) { if (!panel_id || !panel_id.length) var panel_id="_attachPanel"; // remove existing panel (if any) var panel=document.getElementById(panel_id); if (panel) panel.parentNode.removeChild(panel); // set styles for this panel setStylesheet(this.css,"attachPanel"); // create new panel var title=""; if (params && params[0]) title=params.shift(); var types=this.MIMEPrompt+this.formatListOptions(store.getTiddlerText(this.typeList)); // get MIME types panel=createTiddlyElement(place,"span",panel_id,"attachPanel",null); var html=this.html.replace(/%id%/g,panel_id); html=html.replace(/%title%/g,title); html=html.replace(/%disabled%/g,title.length?"disabled":""); html=html.replace(/%IEdisabled%/g,config.browser.isIE?"disabled":""); html=html.replace(/%types%/g,types); panel.innerHTML=html; if (config.browser.isGecko) { // FF3 FIXUP document.getElementById("attachSource").style.display="none"; document.getElementById("attachFixPanel").style.display="block"; } return panel; }, //}}} //{{{ toggleAttachPanel: function (e) { if (!e) var e = window.event; var parent=resolveTarget(e).parentNode; var panel = document.getElementById("_attachPanel"); if (panel==undefined || panel.parentNode!=parent) panel=config.macros.attach.createAttachPanel(parent,"_attachPanel"); var isOpen = panel.style.display=="block"; if(config.options.chkAnimate) anim.startAnimating(new Slider(panel,!isOpen,e.shiftKey || e.altKey,"none")); else panel.style.display = isOpen ? "none" : "block" ; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return(false); }, //}}} //{{{ formatListOptions: function(text) { if (!text || !text.trim().length) return ""; // get MIME list content from text var parts=text.split("

----

"); var out=""; for (var p=0; p<parts.length; p++) { var lines=parts[p].split("

"); var label=lines.shift(); // 1st line=display text var value=lines.shift(); // 2nd line=item value out +='<option value="%1">%0</option>'.format([label,value]); } return out; }, //}}} // // interface definition //{{{ css: ".attachPanel { display: none; position:absolute; z-index:10; width:35em; right:105%; top:0em;\ background-color: #eee; color:#000; font-size: 8pt; line-height:110%;\ border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;\ padding: 0.5em; margin:0em; -moz-border-radius:1em;-webkit-border-radius:1em; text-align:left }\ .attachPanel form { display:inline;border:0;padding:0;margin:0; }\ .attachPanel select { width:99%;margin:0px;font-size:8pt;line-height:110%;}\ .attachPanel input { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%}\ .attachPanel textarea { width:98%;margin:0px;height:2em;font-size:8pt;line-height:110%}\ .attachPanel table { width:100%;border:0;margin:0;padding:0;color:inherit; }\ .attachPanel tbody, .attachPanel tr, .attachPanel td { border:0;margin:0;padding:0;color:#000; }\ .attachPanel .box { border:1px solid black; padding:.3em; margin:.3em 0px; background:#f8f8f8; \ -moz-border-radius:5px;-webkit-border-radius:5px; }\ .attachPanel .chk { width:auto;border:0; }\ .attachPanel .btn { width:auto; }\ .attachPanel .btn2 { width:49%; }\ ", //}}} //{{{ html: '<form>\ attach from source file\ <input type="file" id="attachSource" name="source" size="56"\ onChange="config.macros.attach.onChangeSource(this)">\ <div id="attachFixPanel" style="display:none"><!-- FF3 FIXUP -->\ <input type="text" id="attachFixSource" style="width:90%"\ title="Enter a path/file to attach"\ onChange="config.macros.attach.onChangeSource(this);">\ <input type="button" style="width:7%" value="..."\ title="Enter a path/file to attach"\ onClick="config.macros.attach.askForFilename(document.getElementById(\'attachFixSource\'));">\ </div><!--end FF3 FIXUP-->\ <div class="box">\ <table style="border:0"><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\ embed data <input type=checkbox class=chk name="useData" %IEdisabled% \ onclick="if (!this.form.MIMEType.value.length)\ this.form.MIMEType.selectedIndex=this.checked?1:0; "> \ </td><td style="border:0">\ <select size=1 name="MIMEType" \ onchange="this.title=this.value; if (this.value==\'editlist\')\ { this.selectedIndex=this.form.useData.checked?1:0; story.displayTiddler(null,config.macros.attach.typeList,2); return; }">\ <option value=""></option>\ %types%\ </select>\ </td></tr><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\ local link <input type=checkbox class=chk name="useLocal"\ onclick="this.form.local.value=this.form.local.defaultValue=this.checked?config.macros.attach.localPrompt:\'\';"> \ </td><td style="border:0">\ <input type=text name="local" size=15 autocomplete=off value=""\ onchange="this.form.useLocal.checked=this.value.length" \ onkeyup="this.form.useLocal.checked=this.value.length" \ onfocus="if (!this.value.length) this.value=config.macros.attach.localPrompt; this.select()">\ </td></tr><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\ remote link <input type=checkbox class=chk name="useURL"\ onclick="this.form.URL.value=this.form.URL.defaultValue=this.checked?config.macros.attach.URLPrompt:\'\';\"> \ </td><td style="border:0">\ <input type=text name="URL" size=15 autocomplete=off value=""\ onfocus="if (!this.value.length) this.value=config.macros.attach.URLPrompt; this.select()"\ onchange="this.form.useURL.checked=this.value.length;"\ onkeyup="this.form.useURL.checked=this.value.length;">\ </td></tr></table>\ </div>\ <table style="border:0"><tr style="border:0"><td style="border:0;text-align:right;vertical-align:top;width:1%;white-space:nowrap">\ notes \ </td><td style="border:0" colspan=2>\ <textarea name="notes" style="width:98%;height:3.5em;margin-bottom:2px"></textarea>\ </td><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\ attach as \ </td><td style="border:0" colspan=2>\ <input type=text name="tiddlertitle" size=15 autocomplete=off value="%title%"\ onkeyup="if (!this.value.length) { this.value=config.macros.attach.titlePrompt; this.select(); }"\ onfocus="if (!this.value.length) this.value=config.macros.attach.titlePrompt; this.select()" %disabled%>\ </td></tr></tr><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\ add tags \ </td><td style="border:0">\ <input type=text name="tags" size=15 autocomplete=off value="" onfocus="this.select()">\ </td><td style="width:40%;text-align:right;border:0">\ <input type=button class=btn2 value="attach"\ onclick="config.macros.attach.onClickAttach(this)"><!--\ --><input type=button class=btn2 value="close"\ onclick="var panel=document.getElementById(\'%id%\'); if (panel) panel.parentNode.removeChild(panel);">\ </td></tr></table>\ </form>', //}}} // // control processing //{{{ onChangeSource: function(here) { var form=here.form; var list=form.MIMEType; var theFilename = here.value; var theExtension = theFilename.substr(theFilename.lastIndexOf('.')).toLowerCase(); // if theFilename is in current document folder, remove path prefix and use relative reference var h=document.location.href; folder=getLocalPath(decodeURIComponent(h.substr(0,h.lastIndexOf("/")+1))); if (theFilename.substr(0,folder.length)==folder) theFilename='./'+theFilename.substr(folder.length); else theFilename='file:///'+theFilename; // otherwise, use absolute reference theFilename=theFilename.replace(/\\/g,"/"); // fixup: change \ to / form.useLocal.checked = true; form.local.value = theFilename; form.useData.checked = !form.useData.disabled; list.selectedIndex=1; for (var i=0; i<list.options.length; i++) // find matching MIME type if (list.options[i].value.indexOf(theExtension)!=-1) { list.selectedIndex = i; break; } if (!form.tiddlertitle.disabled) form.tiddlertitle.value=theFilename.substr(theFilename.lastIndexOf('/')+1); // get tiddlername from filename }, //}}} //{{{ onClickAttach: function (here) { clearMessage(); // get input values var form=here.form; var src=form.source; if (config.browser.isGecko) src=document.getElementById("attachFixSource"); src=src.value!=src.defaultValue?src.value:""; var when=(new Date()).formatString(config.macros.timeline.dateFormat); var title=form.tiddlertitle.value; var local = form.local.value!=form.local.defaultValue?form.local.value:""; var url = form.URL.value!=form.URL.defaultValue?form.URL.value:""; var notes = form.notes.value; var tags = "attachment excludeMissing "+form.tags.value; var useData=form.useData.checked; var useLocal=form.useLocal.checked; var useURL=form.useURL.checked; var mimetype = form.MIMEType.value.length?form.MIMEType.options[form.MIMEType.selectedIndex].text:""; // validate checkboxes and get filename if (useData) { if (src.length) { if (!theLocation) var theLocation=src; } else { alert(this.sourceErr); src.focus(); return false; } } if (useLocal) { if (local.length) { if (!theLocation) var theLocation = local; } else { alert(this.localErr); form.local.focus(); return false; } } if (useURL) { if (url.length) { if (!theLocation) var theLocation = url; } else { alert(this.URLErr); form.URL.focus(); return false; } } if (!(useData||useLocal||useURL)) { form.useData.focus(); alert(this.storageErr); return false; } if (!theLocation) { src.focus(); alert(this.sourceErr); return false; } if (!title || !title.trim().length || title==this.titlePrompt) { form.tiddlertitle.focus(); alert(this.tiddlerErr); return false; } // if not already selected, determine MIME type based on filename extension (if any) if (useData && !mimetype.length && theLocation.lastIndexOf('.')!=-1) { var theExt = theLocation.substr(theLocation.lastIndexOf('.')).toLowerCase(); var theList=form.MIMEType; for (var i=0; i<theList.options.length; i++) if (theList.options[i].value.indexOf(theExt)!=-1) { var mimetype=theList.options[i].text; theList.selectedIndex=i; break; } } // attach the file return this.createAttachmentTiddler(src, when, notes, tags, title, useData, useLocal, useURL, local, url, mimetype); }, getMIMEType: function(src,def) { var ext = src.substr(src.lastIndexOf('.')).toLowerCase(); var list=store.getTiddlerText(this.typeList); if (!list || !list.trim().length) return def; // get MIME list content from tiddler var parts=list.split("

----

"); for (var p=0; p<parts.length; p++) { var lines=parts[p].split("

"); var mime=lines.shift(); // 1st line=MIME type var match=lines.shift(); // 2nd line=matching extensions if (match.indexOf(ext)!=-1) return mime; } return def; }, createAttachmentTiddler: function (src, when, notes, tags, title, useData, useLocal, useURL, local, url, mimetype, noshow) { if (useData) { // encode the data if (!mimetype.length) { alert(this.MIMEErr); form.MIMEType.selectedIndex=1; form.MIMEType.focus(); return false; } var d = this.readFile(src); if (!d) { return false; } displayMessage('encoding '+src); var encoded = this.encodeBase64(d); displayMessage('file size='+d.length+' bytes, encoded size='+encoded.length+' bytes'); } var usage=(mimetype.substr(0,5)=="image"?'[img[%0]]':'[[%0|%0]]').format([title]); var theText=this.tiddlerFormat.format([ usage, notes.length?notes:'//none//', mimetype, useLocal?local.replace(/\\/g,'/'):'', useURL?url:'', useData?('data:'+mimetype+';base64,'+encoded):'' ]); store.saveTiddler(title,title,theText,config.options.txtUserName,new Date(),tags); var panel=document.getElementById("attachPanel"); if (panel) panel.style.display="none"; if (!noshow) { story.displayTiddler(null,title); story.refreshTiddler(title,null,true); } displayMessage('attached "'+title+'"'); return true; }, //}}} // // base64 conversion //{{{ encodeBase64: function (d) { if (!d) return null; // encode as base64 var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var out=""; var chr1,chr2,chr3=""; var enc1,enc2,enc3,enc4=""; for (var count=0,i=0; i<d.length; ) { chr1=d.charCodeAt(i++); chr2=d.charCodeAt(i++); chr3=d.charCodeAt(i++); enc1=chr1 >> 2; enc2=((chr1 & 3) << 4) | (chr2 >> 4); enc3=((chr2 & 15) << 2) | (chr3 >> 6); enc4=chr3 & 63; if (isNaN(chr2)) enc3=enc4=64; else if (isNaN(chr3)) enc4=64; out+=keyStr.charAt(enc1)+keyStr.charAt(enc2)+keyStr.charAt(enc3)+keyStr.charAt(enc4); chr1=chr2=chr3=enc1=enc2=enc3=enc4=""; } return out; }, decodeBase64: function(input) { var out=""; var chr1,chr2,chr3; var enc1,enc2,enc3,enc4; var i = 0; // remove all characters that are not A-Z, a-z, 0-9, +, /, or = input=input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); do { enc1=keyStr.indexOf(input.charAt(i++)); enc2=keyStr.indexOf(input.charAt(i++)); enc3=keyStr.indexOf(input.charAt(i++)); enc4=keyStr.indexOf(input.charAt(i++)); chr1=(enc1 << 2) | (enc2 >> 4); chr2=((enc2 & 15) << 4) | (enc3 >> 2); chr3=((enc3 & 3) << 6) | enc4; out=out+String.fromCharCode(chr1); if (enc3!=64) out=out+String.fromCharCode(chr2); if (enc4!=64) out=out+String.fromCharCode(chr3); } while (i<input.length); return out; }, //}}} // // I/O functions //{{{ readFile: // read local BINARY file data function(filePath) { if(!window.Components) { return null; } try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); } catch(e) { alert("access denied: "+filePath); return null; } var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); try { file.initWithPath(filePath); } catch(e) { alert("cannot read file - invalid path: "+filePath); return null; } if (!file.exists()) { alert("cannot read file - not found: "+filePath); return null; } var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream); inputStream.init(file, 0x01, 00004, null); var bInputStream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream); bInputStream.setInputStream(inputStream); return(bInputStream.readBytes(inputStream.available())); }, //}}} //{{{ writeFile: function(filepath,data) { // TBD: decode base64 and write BINARY data to specified local path/filename return(false); }, //}}} //{{{ askForFilename: // for FF3 fixup function(target) { var msg=config.messages.selectFile; if (target && target.title) msg=target.title; // use target field tooltip (if any) as dialog prompt text // get local path for current document var path=getLocalPath(document.location.href); var p=path.lastIndexOf("/"); if (p==-1) p=path.lastIndexOf("\\"); // Unix or Windows if (p!=-1) path=path.substr(0,p+1); // remove filename, leave trailing slash var file="" var result=window.mozAskForFilename(msg,path,file,true); // FF3 FIXUP ONLY if (target && result.length) // set target field and trigger handling { target.value=result; target.onchange(); } return result; } }; //}}} //{{{ if (window.mozAskForFilename===undefined) { // also defined by CoreTweaks (for ticket #604) window.mozAskForFilename=function(msg,path,file,mustExist) { if(!window.Components) return false; try { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var nsIFilePicker = window.Components.interfaces.nsIFilePicker; var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker); picker.init(window, msg, mustExist?nsIFilePicker.modeOpen:nsIFilePicker.modeSave); var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); thispath.initWithPath(path); picker.displayDirectory=thispath; picker.defaultExtension=''; picker.defaultString=file; picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML); if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.persistentDescriptor; } catch(ex) { displayMessage(ex.toString()); } return result; } } //}}}

/*** |Name|AttachFilePluginFormatters| |Source|http://www.TiddlyTools.com/#AttachFilePluginFormatters| |Version|4.0.1| |Author|Eric Shulman| |License|http://www.TiddlyTools.com/#LegalStatements| |~CoreVersion|2.1.3| |Type|plugin| |Description|run-time library for displaying attachment tiddlers| Runtime processing for //rendering// attachment tiddlers created by [[AttachFilePlugin]]. Attachment tiddlers are tagged with<<tag attachment>>and contain binary file content (e.g., jpg, gif, pdf, mp3, etc.) that has been stored directly as base64 text-encoded data or can be loaded from external files stored on a local filesystem or remote web server. Note: after creating new attachment tiddlers, you can remove [[AttachFilePlugin]], as long as you retain //this// tiddler (so that images can be rendered later on). !!!!!Formatters <<< This plugin extends the behavior of the following TiddlyWiki core "wikify()" formatters: * embedded images: {{{[img[tooltip|image]]}}} * linked embedded images: {{{[img[tooltip|image][link]]}}} * external/"pretty" links: {{{[[label|link]]}}} ''Please refer to AttachFilePlugin (source: http://www.TiddlyTools.com/#AttachFilePlugin) for additional information.'' <<< !!!!!Revisions <<< 2009.10.10 [4.0.1] in fileExists(), check for IE to avoid hanging Chrome during startup 2009.06.04 [4.0.0] changed attachment storage format to use //sections// instead of embedded substring markers. 2008.01.08 [*.*.*] plugin size reduction: documentation moved to ...Info 2007.12.04 [*.*.*] update for TW2.3.0: replaced deprecated core functions, regexps, and macros 2007.10.29 [3.7.0] more code reduction: removed upload handling from AttachFilePlugin (saves ~7K!) 2007.10.28 [3.6.0] removed duplicate formatter code from AttachFilePlugin (saves ~10K!) and updated documentation accordingly. This plugin ([[AttachFilePluginFormatters]]) is now //''required''// in order to display attached images/binary files within tiddler content. 2006.05.20 [3.4.0] through 2007.03.01 [3.5.3] sync with AttachFilePlugin 2006.05.13 [3.2.0] created from AttachFilePlugin v3.2.0 <<< !!!!!Code ***/ // // version //{{{ version.extensions.AttachFilePluginFormatters= {major: 4, minor: 0, revision: 1, date: new Date(2009,10,10)}; //}}} //{{{ if (config.macros.attach==undefined) config.macros.attach= { }; //}}} //{{{ if (config.macros.attach.isAttachment==undefined) config.macros.attach.isAttachment=function (title) { var tiddler = store.getTiddler(title); if (tiddler==undefined || tiddler.tags==undefined) return false; return (tiddler.tags.indexOf("attachment")!=-1); } //}}} //{{{ // test for local file existence - returns true/false without visible error display if (config.macros.attach.fileExists==undefined) config.macros.attach.fileExists=function(f) { if(window.Components) { // MOZ try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); } catch(e) { return false; } // security access denied var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); try { file.initWithPath(f); } catch(e) { return false; } // invalid directory return file.exists(); } else if (config.browser.isIE) { // IE var fso = new ActiveXObject("Scripting.FileSystemObject"); return fso.FileExists(f); } else return true; // other browsers: assume file exists } //}}} //{{{ if (config.macros.attach.getAttachment==undefined) config.macros.attach.getAttachment=function(title) { // extract embedded data, local and remote links (if any) var text=store.getTiddlerText(title,''); var embedded=store.getTiddlerText(title+'##data','').trim(); var locallink=store.getTiddlerText(title+'##file','').trim(); var remotelink=store.getTiddlerText(title+'##url','').trim(); // backward-compatibility for older attachments (pre 4.0.0) var startmarker="---BEGIN_DATA---

"; var endmarker="

---END_DATA---"; var pos=0; var endpos=0; if ((pos=text.indexOf(startmarker))!=-1 && (endpos=text.indexOf(endmarker))!=-1) embedded="data:"+(text.substring(pos+startmarker.length,endpos)).replace(/

/g,''); if ((pos=text.indexOf("/%LOCAL_LINK%/"))!=-1) locallink=text.substring(text.indexOf("|",pos)+1,text.indexOf("]]",pos)); if ((pos=text.indexOf("/%REMOTE_LINK%/"))!=-1) remotelink=text.substring(text.indexOf("|",pos)+1,text.indexOf("]]",pos)); // if there is a data: URI defined (not supported by IE) if (embedded.length && !config.browser.isIE) return embedded; // document is being served remotely... use remote URL (if any) (avoids security alert) if (remotelink.length && document.location.protocol!="file:") return remotelink; // local link only... return link without checking file existence (avoids security alert) if (locallink.length && !remotelink.length) return locallink; // local link, check for file exist... use local link if found if (locallink.length) { locallink=locallink.replace(/^\.[\/\\]/,''); // strip leading './' or '.\' (if any) if (this.fileExists(getLocalPath(locallink))) return locallink; // maybe local link is relative... add path from current document and try again var pathPrefix=document.location.href; // get current document path and trim off filename var slashpos=pathPrefix.lastIndexOf("/"); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf("\\"); if (slashpos!=-1 && slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1); if (this.fileExists(getLocalPath(pathPrefix+locallink))) return locallink; } // no embedded data, no local (or not found), fallback to remote URL (if any) if (remotelink.length) return remotelink; // attachment URL doesn't resolve, just return input as is return title; } //}}} //{{{ if (config.macros.attach.init_formatters==undefined) config.macros.attach.init_formatters=function() { if (this.initialized) return; // find the formatter for "image" and replace the handler for (var i=0; i<config.formatters.length && config.formatters[i].name!="image"; i++); if (i<config.formatters.length) config.formatters[i].handler=function(w) { this.lookaheadRegExp.lastIndex = w.matchStart; var lookaheadMatch = this.lookaheadRegExp.exec(w.source) if(lookaheadMatch && lookaheadMatch.index == w.matchStart) // Simple bracketted link { var e = w.output; if(lookaheadMatch[5]) { var link = lookaheadMatch[5]; // ELS ------------- var external=config.formatterHelpers.isExternalLink(link); if (external) { if (config.macros.attach.isAttachment(link)) { e = createExternalLink(w.output,link); e.href=config.macros.attach.getAttachment(link); e.title = config.macros.attach.linkTooltip + link; } else e = createExternalLink(w.output,link); } else e = createTiddlyLink(w.output,link,false,null,w.isStatic); // ELS ------------- addClass(e,"imageLink"); } var img = createTiddlyElement(e,"img"); if(lookaheadMatch[1]) img.align = "left"; else if(lookaheadMatch[2]) img.align = "right"; if(lookaheadMatch[3]) img.title = lookaheadMatch[3]; img.src = lookaheadMatch[4]; // ELS ------------- if (config.macros.attach.isAttachment(lookaheadMatch[4])) img.src=config.macros.attach.getAttachment(lookaheadMatch[4]); // ELS ------------- w.nextMatch = this.lookaheadRegExp.lastIndex; } } //}}} //{{{ // find the formatter for "prettyLink" and replace the handler for (var i=0; i<config.formatters.length && config.formatters[i].name!="prettyLink"; i++); if (i<config.formatters.length) { config.formatters[i].handler=function(w) { this.lookaheadRegExp.lastIndex = w.matchStart; var lookaheadMatch = this.lookaheadRegExp.exec(w.source); if(lookaheadMatch && lookaheadMatch.index == w.matchStart) { var e; var text = lookaheadMatch[1]; if(lookaheadMatch[3]) { // Pretty bracketted link var link = lookaheadMatch[3]; if (config.macros.attach.isAttachment(link)) { e = createExternalLink(w.output,link); e.href=config.macros.attach.getAttachment(link); e.title=config.macros.attach.linkTooltip+link; } else e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic); } else { e = createTiddlyLink(w.output,text,false,null,w.isStatic); } createTiddlyText(e,text); w.nextMatch = this.lookaheadRegExp.lastIndex; } } } // if "prettyLink" formatter found this.initialized=true; } //}}} //{{{ config.macros.attach.init_formatters(); // load time init //}}} //{{{ if (TiddlyWiki.prototype.coreGetRecursiveTiddlerText==undefined) { TiddlyWiki.prototype.coreGetRecursiveTiddlerText = TiddlyWiki.prototype.getRecursiveTiddlerText; TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth) { return config.macros.attach.isAttachment(title)? config.macros.attach.getAttachment(title):this.coreGetRecursiveTiddlerText.apply(this,arguments); } } //}}}

/*** |Name|AttachFilePluginInfo| |Source|http://www.TiddlyTools.com/#AttachFilePlugin| |Documentation|http://www.TiddlyTools.com/#AttachFilePluginInfo| |Version|4.0.0| |Author|Eric Shulman| |License|http://www.TiddlyTools.com/#LegalStatements| |~CoreVersion|2.1| |Type|plugin| |Description|Documentation for AttachFilePlugin| Store or link binary files (such as jpg, gif, pdf or even mp3) within your TiddlyWiki document and then use them as images or links from within your tiddler content. !!!!!Inline interface (live) >see [[AttachFile]] (shadow tiddler) ><<tiddler AttachFile>> !!!!!Syntax <<< ''To display the attach file control panel, simply view the [[AttachFile]] shadow tiddler that is automatically created by the plugin, and contains an instance of the inline control panel.''. Or, you can write: {{{ <<attach inline>> }}} in any tiddler to display the control panel embedded within that tiddler. Note: you can actually use any unique identifier in place of the "inline" keyword. Each unique id creates a separate instance of the controls. If the same ID is used in more than one tiddler, then the control panel is automatically moved to the most recently rendered location. Or, you can write: {{{ <<attach>> }}} (with no ID parameter) in SidebarOptions. This adds a command link that opens the controls as a floating panel, positioned directly to the left of the sidebar. <<< !!!!!Usage <<< Binary file content can be stored in three different locations: #embedded in the attachment tiddler (encoded as base64) #on your filesystem (a 'local link' path/filename) #on a web server (a 'remote link' URL) The plugin creates an "attachment tiddler" for each file you attach. Regardless of where you store the binary content, your document can refer to the attachment tiddler rather than using a direct file or URL reference in your embedded image or external links, so that changing document locations will not require updating numerous tiddlers or copying files from one system to another. > Important note: As of version 3.6.0, in order to //render// images and other binary attachments created with this plugin, you must also install [[AttachFilePluginFormatters]], which extends the behavior of the TiddlyWiki core formatters for embedded images ({{{[img[tooltip|image]]}}}), linked embedded images ({{{[img[tooltip|image][link]]}}}), and external/"pretty" links ({{{[[label|link]]}}}), so that these formatter will process references to attachment tiddlers as if a normal file reference had been provided. | When you attach a file, a tiddler (tagged with<<tag attachment>>) is generated (using the source filename as the tiddler's title). The tiddler contains //''base64 text-encoded binary data''//, surrounded by {{{/%...%/}}} comment markers (so they are not visible when viewing the tiddler). The tiddler also includes summary details about the file: when it was attached, by whom, etc. and, if the attachment is an image file (jpg, gif, or png), the image is automatically displayed below the summary information. >Note: although you can edit an attachment tiddler, ''don't change any of the encoded content below the attachment header'', as it has been prepared for use in the rest of your document, and even changing a single character can make the attachment unusable. //If needed, you ''can'' edit the header information or even the MIME type declaration in the attachment data, but be very careful not to change any of the base64-encoded binary data.// Unfortunately, embedding just a few moderately-sized binary files using base64 text-encoding can dramatically increase the size of your document. To avoid this problem, you can create attachment tiddlers that define external local filesystem (file://) and/or remote web server (http://) 'reference' links, without embedding the binary data directly in the tiddler (i.e., uncheck "embed data" in the 'control panel'). These links provide an alternative source for the binary data: if embedded data is not found (or you are running on Internet Explorer, which does not currently support using embedded data), then the plugin tries the local filesystem reference. If a local file is not found, then the remote reference (if any) is used. This "fallback" approach also lets you 'virtualize' the external links in your document, so that you can access very large binary content such as PDFs, MP3's, and even *video* files, by using just a 'remote reference link' without embedding any data or downloading huge files to your hard disk. Of course, when you //do// download an attached file, the local copy will be used instead of accessing a remote server each time, thereby saving bandwidth and allowing you to 'go mobile' without having to edit any tiddlers to alter the link locations... <<< !!!!!Syntax / Examples <<< To embed attached files as images or link to them from other tiddlers, use the standard ~TiddlyWiki image syntax ({{{[img[tooltip|filename]]}}}), linked image syntax ({{{[img[tooltip|filename][tiddlername]]}}}) , or "external link" syntax ({{{[[text|URL]]}}}), replacing the filename or URL that is normally entered with the title of an attachment tiddler. embedded image data: >{{{[img[Meow|AttachFileSample]]}}} >[img[Meow|AttachFileSample]] embedded image data with link to larger remote image: >{{{[img[click for larger view|AttachFileSample][AttachFileSample2]]}}} >[img[click for larger view|AttachFileSample][AttachFileSample2]] 'external' link to embedded image data: >{{{[[click to view attachment|AttachFileSample]]}}} >[[click to view attachment|AttachFileSample]] 'external' link to remote image: >{{{[[click to view attachment|AttachFileSample2]]}}} >[[click to view attachment|AttachFileSample2]] regular ~TiddlyWiki links to attachment tiddlers: >{{{[[AttachFileSample]]}}} [[AttachFileSample]] >{{{[[AttachFileSample2]]}}} [[AttachFileSample2]] <<< !!!!!Defining MIME types <<< When you select a source file, a ''[[MIME|http://en.wikipedia.org/wiki/MIME]]'' file type is automatically suggested, based on filename extension. The AttachFileMIMETypes tiddler defines the list of MIME types that will be recognized by the plugin. Each MIME type definition consists of exactly two lines of text: the official MIME type designator (e.g., "text/plain", "image/gif", etc.), and a space-separated list of file extensions associated with that type. List entries are separated by "----" (horizontal rules). <<< !!!!!Known Limitations <<< Internet Explorer does not support the data: URI scheme, and cannot use the //embedded// data to render images or links. However, you can still use the local/remote link definitions to create file attachments that are stored externally. In addition, while it is relatively easy to read local //text// files, reading binary files is not directly supported by IE's FileSystemObject (FSO) methods, and other file I/O techniques are subject to security barriers or require additional MS proprietary technologies (like ASP or VB) that make implementation more difficult. As a result, you cannot //create// new attachment tiddlers using IE. <<< !!!!!Installation <<< Import (or copy/paste) the following tiddlers into your document: * [[AttachFilePlugin]] (tagged with <<tag systemConfig>>) * [[AttachFilePluginFormatters]] ("runtime distribution library") (tagged with <<tag systemConfig>>) * [[AttachFileSample]] and [[AttachFileSample2]] //(tagged with <<tag attachment>>)// * [[AttachFileMIMETypes]] //(defines binary file types)// > Important note: As of version 3.6.0, in order to //render// images and other binary attachments created with this plugin, you must also install [[AttachFilePluginFormatters]], which extends the behavior of the TiddlyWiki core formatters for embedded images ({{{[img[tooltip|image]]}}}), linked embedded images ({{{[img[tooltip|image][link]]}}}), and external/"pretty" links ({{{[[label|link]]}}}), so that these formatter will process references to attachment tiddlers as if a normal file reference had been provided. | <<< !!!!!Revisions <<< 2009.06.04 4.0.0 changed attachment storage format to use //sections// instead of embedded substring markers. 2008.07.21 3.9.0 Fixup for FireFox 3: use HTML with separate text+button control instead of type='file' control 2008.05.12 3.8.1 automatically add 'attach' task to backstage (moved from BackstageTweaks) 2008.04.09 3.8.0 in onChangeSource(), if source matches current document folder, use relative reference for local link. Also, disable 'embed' when using IE (which //still// doesn't support data: URI) 2008.04.07 3.7.3 fixed typo in HTML for 'local file link' so that clicking in input field doesn't erase current path/file (if any) 2008.04.07 3.7.2 auto-create AttachFile shadow tiddler for inline interface 2008.01.08 [*.*.*] plugin size reduction: documentation moved to ...Info 2007.12.04 [*.*.*] update for TW2.3.0: replaced deprecated core functions, regexps, and macros 2007.12.03 3.7.1 in createAttachmentTiddler(), added optional "noshow" flag to suppress display of newly created tiddlers. 2007.10.29 3.7.0 code reduction: removed support for built-in upload to server... on-line hosting of binary attachments is left to the document author, who can upload/host files using 3rd-party web-based services (e.g. www.flickr.com, ) or stand-alone applications (e.g., FTP). 2007.10.28 3.6.0 code reduction: removed duplicate definition of image and prettyLink formatters. Rendering of attachment tiddlers now //requires// installation of AttachFilePluginFormatters 2007.03.01 3.5.3 use apply() to invoke hijacked function 2007.02.25 3.5.2 in hijack of "prettyLink", fix version check for TW2.2 compatibility (prevent incorrect use of fallback handler) 2007.01.09 3.5.1 onClickAttach() refactored to create separate createAttachmentTiddler() API for use with FileDropPluginHandlers 2006.11.30 3.5.0 in getAttachment(), for local references, add check for file existence and fallback to remote URL if local file not found. Added fileExists() to encapsulate FF vs. IE local file test function (IE FSO object code is TBD). 2006.11.29 3.4.8 in hijack for PrettyLink, 'simple bracketed link' opens tiddler instead of external link to attachment 2006.11.29 3.4.7 in readFile(), added try..catch around initWithPath() to handle invalid/non-existent paths better. 2006.11.09 3.4.6 REAL FIX for TWv2.1.3: incorporate new TW2.1.3 core "prettyLink" formatter regexp handling logic and check for version < 2.1.3 with fallback to old plugin code. Also, cleanup table layout in HTML (added "border:0" directly to table elements to override stylesheet) 2006.11.08 3.4.5 TEMPORARY FIX for TWv2.1.3: disable hijack of wikiLink formatter due to changes in core wikiLink regexp definition. //Links to attachments are broken, but you can still use {{{[img[TiddlerName]]}}} to render attachments as images, as well as {{{background:url('[[TiddlerName]]')}}} in CSS declarations for background images.// 2006.09.10 3.4.4 update formatters for 2.1 compatibility (use this.lookaheadRegExp instead of temp variable) 2006.07.24 3.4.3 in prettyLink formatter, added check for isShadowTiddler() to fix problem where shadow links became external links. 2006.07.13 3.4.2 in getAttachment(), fixed stripping of newlines so data: used in CSS will work 2006.05.21 3.4.1 in getAttachment(), fixed substring() to extract data: URI (was losing last character, which broken rendering of SOME images) 2006.05.20 3.4.0 hijack core getRecursiveTiddlerText() to support rendering attachments in stylesheets (e.g. {{{url([[AttachFileSample]])}}}) 2006.05.20 3.3.6 add "description" feature to easily include notes in attachment tiddler (you can always edit to add them later... but...) 2006.05.19 3.3.5 add "attach as" feature to change default name for attachment tiddlers. Also, new optional param to specify tiddler name (disables editing) 2006.05.16 3.3.0 completed XMLHttpRequest handling for GET or POST to configurable server scripts 2006.05.13 3.2.0 added interface for upload feature. Major rewrite of code for clean object definitions. Major improvements in UI interaction and validation. 2006.05.09 3.1.1 add wikifer support for using attachments in links from "linked image" syntax: {{{[img[tip|attachment1][attachment2]]}}} 2006.05.09 3.1.0 lots of code changes: new options for attachments that use embedded data and/or links to external files (local or remote) 2006.05.03 3.0.2 added {{{/%...%/}}} comments around attachment data to hide it when viewing attachment tiddler. 2006.02.05 3.0.1 wrapped wikifier hijacks in initAttachmentFormatters() function to eliminate globals and avoid FireFox 1.5.0.1 crash bug when referencing globals 2005.12.27 3.0.0 Update for TW2.0. Automatically add 'excludeMissing' tag to attachments 2005.12.16 2.2.0 Dynamically create/remove attachPanel as needed to ensure only one instance of interface elements exists, even if there are multiple instances of macro embedding. 2005.11.20 2.1.0 added wikifier handler extensions for "image" and "prettyLink" to render tiddler attachments 2005.11.09 2.0.0 begin port from old ELS Design adaptation based on ~TW1.2.33 2005.07.20 1.0.0 Initial release (as adaptation) <<<

AutoPlayer is a tool designed to keep your place when watching a TV series without having to update playlists or remember where you left off. Written primarily for watching anime, it is designed to replicate the similar functionality on cloud services like Netflix and Crunchyroll. To use it, make sure your video file names follow this format: ["""NameOfSeries"""]_ep["""UnpaddedNumber"""]. After that, you can put a link or copy of the script in the video directory, or pass it as an argument at runtime. After the script starts, it will wait 5 seconds, then start playing either the first episode, or the next one you haven't watched. You can click the Select Episode to choose another one or reset the settings and exit. After the player is done or is closed, a message will appear that will play the next episode unless the Go Back or Quit button is pressed within 5 seconds. Should that button be pressed, you can exit (which saves your place) or you can go back (if you didn't intent to close the player or want to resume later). This script requires Zenity and mpv. {{{ #!/bin/bash if [[ -d "$1" ]] then cd "$1" else cd "`dirname "$0"`" fi ls | grep _ep > /dev/null if [[ "$?" != "0" ]] then echo "No video files found in Title_ep# format." exit 1 fi title=`ls | grep _ep | awk 'BEGIN { FS = "_" } ; { print $1 }' | head -n 1` max=`ls -v | grep _ep | awk 'BEGIN { FS = "_" } ; { print $2 }' | sed 's/ep//g' | awk 'BEGIN { FS = "." } ; { print $1 }' | tail -n 1` if [[ ! -e ".current" ]] then echo "1" > .current fi current=`cat .current` for i in {1..100}; do echo $i; sleep 0.05; done | zenity --progress --auto-close --text "About to play episode $current." --title="$title" --cancel-label="Select Episode" if [[ $? != 0 ]]; then current=$(zenity --scale --title="$title" --text "Select Episode" --min-value=1 --max-value=$max --value=$current --step 1 --cancel-label="Default and Quit") fi if [[ $? != 0 ]]; then echo "1" > .current exit fi skip=0 while true do if [[ "$skip" == "0" ]] then mpv --fs "${title}_ep${current}."* current=$((current+1)) fi skip=0 if [[ $current != $((max+1)) ]]; then for i in {1..100}; do echo $i; sleep 0.05; done | zenity --progress --auto-close --text "About to play episode $current..." --title="$title" --cancel-label="Go Back or Quit" else break fi if [[ $? != 0 ]]; then zenity --question --title="$title" --text "Go back one episode or quit?" --cancel-label="Quit" --ok-label="Go Back" if [[ "$?" == "0" ]] then current=$((current-1)) skip=1 else break fi fi done if [[ $current == $((max+1)) ]]; then current=1 fi echo "$current" > .current }}}

@@While this worked as of 2015-08-12, I am not maintaining this setup anymore. Use at your own risk.@@ This project auto-starts XFCE daemons and other software: Why these scripts? * You like awesome WM, but want features that a basic DE would have. * Allows you to use the XFCE desktop. * Uses XFCE GTK theme and icon theme (looks better). * Auto mounts drives. * Starts network manager and other daemons. * Does not break XFCE itself (you can switch between both). Note: This fix starts all of the XFCE parts manually, since I could not find a way to make awesome the default WM in XFCE itself. Copy {{{/etc/xdg/awesome/rc.lua}}} to {{{.config/awesome/rc.lua}}}. Add {{{awful.util.spawn_with_shell("~/autostart.sh")}}} to your {{{.config/awesome/rc.lua}}}. Save this as autostart.sh in your home folder: {{{ #!/bin/bash ps -A | grep xfdesktop; if [ $? = 0 ]; then exit; fi export GTK_PATH="$GTK_PATH:/usr/lib/gtk-2.0" Thunar --daemon & xfdesktop & xfce4-settings-helper & xfsettingsd & xfce4-power-manager & /usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1 & system-config-printer-applet & kerneloops-applet & update-notifier & xfce4-power-manager & wicd-gtk & xscreensaver -no-splash & }}} Run {{{chmod +x autostart.sh}}}. Add any other programs you want auto started to this file, like: {{{ xfce4-clipman & skype & xfce4-volumed & }}} You can now use the awesome setting in your login screen and still have basic XFCE functions but use awesome. Selecting XFCE will still load XFCE like it always has. <html><img border="0" src="http://i.imgur.com/6Zb6j.png" alt="Pulpit rock" width="600" height="300" /></HTML> Adds a shutdown, suspend, restart, and lock button to awesome: Why? * You don't need to log out, then shut down to shut off your computer. * Allows you to lock your computer without a hotkey. * Allows you to suspend. Run {{{sudo visudo}}}. Add these lines, replacing {{{<name>}}} with your username: {{{ <name> ALL = NOPASSWD: /opt/shutdown/restart.sh <name> ALL = NOPASSWD: /opt/shutdown/suspend.sh <name> ALL = NOPASSWD: /opt/shutdown/shutdown.sh }}} Save these files in {{{/opt/shutdown/}}}: {{{restart.sh}}}: {{{ zenity --question --text="Do you want to restart your computer?" --title="Restart?" && reboot }}} {{{suspend.sh}}}: {{{ zenity --question --text="Do you want to suspend your computer?" --title="Suspend?" && pm-suspend }}} {{{shutdown.sh}}}: {{{ zenity --question --text="Do you want to shutdown your computer?" --title="Shutdown?" && halt }}} Run: {{{sudo chmod +x /opt/shutdown/restart.sh}}} {{{sudo chmod +x /opt/shutdown/suspend.sh}}} {{{sudo chmod +x /opt/shutdown/shutdown.sh}}} Change this: {{{ mymainmenu = awful.menu({ items = { { "awesome", myawesomemenu, beautiful.awesome_icon }, { "Debian", debian.menu.Debian_menu.Debian }, { "open terminal", terminal } } }) }}} to this: {{{ mymainmenu = awful.menu({ items = { { "awesome", myawesomemenu, beautiful.awesome_icon }, { "Debian", debian.menu.Debian_menu.Debian }, { "open terminal", terminal }, {"Restart", 'sudo /opt/shutdown/restart.sh'}, {"Shutdown", 'sudo /opt/shutdown/shutdown.sh'}, {"Suspend", 'sudo /opt/shutdown/suspend.sh'}, {"Lock", 'xscreensaver-command -lock'} } }) }}} in your {{{.config/awesome/rc.lua}}}. After restarting Awesome, you will see this: [img[http://i.imgur.com/2IMns.png]]

This page contains the three generations of backup scripts I have used for my laptop. My reasoning for sharing these is that they were annoying to develop and completely automate the backup process. While customizing one of these scripts to your needs might take a while, it is significantly easier than redeveloping the scripts from scratch. The first, most recent script uses a deduplicating archiver called Borg, which is like Attic. It is extremely fast, deduplicates data, and support perpetual incremental backups and allows deletion of individual backups, as it doesn't depend on a backup chain. The second script uses Rsnapshot and Rdiff-backup. Rsnapshot only deduplicates at the file level, but allows deletion of backups in the middle of a chain. Rdiff-backup uses chains, but duplicates at the block level, which is a must for virtual machines. The last script uses Attic, and is only here for archival purposes. All script are fully automatic and run on a schedule, only bugging you if something goes wrong. !Borg Backup Scripts (Newest) I have recently learned that Attic has been forked into [[Borg|http://borgbackup.readthedocs.org/]]. My hope is that the problems with attic have been eliminated. In short, it is an upgraded version with support for different compression types. The script below uses zlib,1 for compression. !!Backup Script This script backups up the main """SSD""", my hard drive, bootloader, and """VMs""" at different intervals. The """VMs""" and """SSD""" are backed up using an """LVM""" snapshot. It will show an error message and attempt to send a text message if something breaks. (The text messages have been broken for a while.) Put in Cron for every 4 hours. {{{ #!/bin/bash startTime=$(date +%s) hour="$(date +%H)" set -o pipefail function sendError { echo "Backup failure: $1" echo "[`date +%F`] Local backup failure: $1" >> /home/ian/logs/backupfail curl http://textbelt.com/text -d number=<<!PHONE NUMBER!>> -d "message=[`date +%F`] Local backup failure: $1" export DISPLAY=:0 zenity --error --title "System Notification" --text "[`date +%F`] ian-thinkpad: $1" & } #Check for issues. if [[ ! -e "/dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab" ]] then sendError "No drive present." exit fi #if [[ -e "/tmp/rbkuplock" ]] #then # sendError "Lockfile exists." # exit #fi #Wait for the lock to go away. if [[ -e "/tmp/rbkuplock" ]] then if [[ -e "/tmp/rbkupw" ]] then sendError "Only one local backup may wait for lock." exit else touch "/tmp/rbkupw" while [[ -e "/tmp/rbkuplock" ]] do sleep 30 echo "Waiting for unlock..." done /bin/rm -f "/tmp/rbkupw" fi fi #Create Lock touch "/tmp/rbkuplock" currentDate=$(date +%FT%R) #Mount the backup drive echo "<<!ENCRYPTION PASSWORD!>>" | /sbin/cryptsetup luksOpen /dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab sbk mount /dev/mapper/sbk /sbk/ #Ensure the backup is actually there. if [[ ! -e "/sbk/main-backup" ]] then sendError "No folder present." exit fi #Create an LVM snapshot /sbin/pvchange -xy /dev/mapper/sdb5_crypt sync /sbin/lvcreate --size 10g --snapshot --name sfs /dev/ian-thinkpad-vg/root mount -r /dev/ian-thinkpad-vg/sfs /sfs if [[ ! -e "/sfs/home" ]] then sendError "No snapshot present." umount /sfs /sbin/lvremove -f /dev/ian-thinkpad-vg/sfs umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/rbkuplock" exit fi #Prepare the cache. mount -t tmpfs -o size=10g tmpfs /root/.cache/borg rsync -a /root/.cache/borg-main/ /root/.cache/borg/ #Perform the backup. /home/ian/bookmarks/scripts/soft-backup.sh borg prune /sbk/main-backup -vs -p main --keep-within=2d -d 10 -w 4 -m 12 -y 10 2>&1 | tee /home/ian/logs/backupprune #Check for issues. if [[ "$?" != "0" ]] then sendError "Prune errors were reported." fi borg create -vs --compression zlib,1 \ --exclude /sfs/sbk/ \ --exclude /sfs/media/ \ --exclude /sfs/mnt/ \ --exclude /sfs/tmp/ \ --exclude /sfs/proc/ \ --exclude /sfs/home/ian/.cache/ \ --exclude /sfs/root/.cache/ \ --exclude /sfs/run/ \ --exclude /sfs/var/cache/ \ --exclude /sfs/var/tmp/ \ --exclude /sfs/tmp/ \ --exclude /sfs/dev/ \ --exclude /sfs/sys/ \ --exclude /sfs/home/ian/VirtualBox\ VMs/ \ --exclude /sfs/bulk-files/ \ "/sbk/main-backup::main-$currentDate" /sfs/ 2>&1 | tee /home/ian/logs/mainbackup if [[ "$?" != "0" ]] then sendError "Main backup errors." fi #Change the cache. rsync -a --delete /root/.cache/borg/ /root/.cache/borg-main/ umount /root/.cache/borg mount -t tmpfs -o size=10g tmpfs /root/.cache/borg rsync -a /root/.cache/borg-boot/ /root/.cache/borg/ borg prune /sbk/boot-backup -vs -p boot --keep-within=2d -d 10 -w 4 -m 12 -y 10 2>&1 | tee -a /home/ian/logs/backupprune #Check for issues. if [[ "$?" != "0" ]] then sendError "Prune errors were reported." fi borg create -vs --compression zlib,1 "/sbk/boot-backup::boot-$currentDate" /boot/ 2>&1 | tee /home/ian/logs/bootbackup if [[ "$?" != "0" ]] then sendError "Boot backup errors." fi #Unmount the cache. rsync -a --delete /root/.cache/borg/ /root/.cache/borg-boot/ umount /root/.cache/borg if [[ "$hour" == "04" ]] || [[ "$1" == "vmforce" ]] then if [[ "$(date +%w)" == "1" ]] || [[ "$1" == "vmforce" ]] then #Prepare the cache. mount -t tmpfs -o size=10g tmpfs /root/.cache/borg rsync -a /root/.cache/borg-vm/ /root/.cache/borg/ borg prune /sbk/vm-backup -vs -p vm -w 4 -m 12 -y 10 2>&1 | tee -a /home/ian/logs/backupprune #Check for issues. if [[ "$?" != "0" ]] then sendError "Prune errors were reported." fi borg create -vs --compression zlib,1 "/sbk/vm-backup::vm-$currentDate" '/sfs/home/ian/VirtualBox VMs/' 2>&1 | tee /home/ian/logs/vmbackup #Check for issues. if [[ "$?" != "0" ]] then sendError "VM backup errors" fi #Unmount the cache. rsync -a --delete /root/.cache/borg/ /root/.cache/borg-vm/ umount /root/.cache/borg fi fi #Delete the snapshot. umount /sfs /sbin/lvremove -f /dev/ian-thinkpad-vg/sfs if [[ "$hour" == "04" ]] || [[ "$1" == "bulkforce" ]] #Run the backup at 04 only. then #Prepare the cache. mount -t tmpfs -o size=10g tmpfs /root/.cache/borg rsync -a /root/.cache/borg-bulk/ /root/.cache/borg/ borg prune /sbk/bulk-backup -vs -p bulk -d 10 -w 4 -m 12 -y 10 2>&1 | tee -a /home/ian/logs/backupprune #Check for issues. if [[ "$?" != "0" ]] then sendError "Prune errors were reported." fi borg create -vs --compression zlib,1 "/sbk/bulk-backup::bulk-$currentDate" /bulk-files/ 2>&1 | tee /home/ian/logs/bulkbackup if [[ "$?" != "0" ]] then sendError "Bulk backup errors." fi #Unmount the cache. rsync -a --delete /root/.cache/borg/ /root/.cache/borg-bulk/ umount /root/.cache/borg fi if [[ "`df | grep /dev/mapper/sbk | awk '{ print $4 }'`" -lt "104857600" ]] then sendError "Less than 100GB on drive." fi #Make sure the drive is set for sleep. if [[ -e "/dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab" ]] then hdparm -S 60 /dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab fi #Unmount the backup drive. umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/rbkuplock" endTime=$(date +%s) echo "Backup took $(((endTime-startTime)/3600)):$((((endTime-startTime)%3600)/60))." | tee -a /home/ian/logs/mainbackup }}} !!Backup Drive Mounter Use this to mount the backup drive and open a file manager for looking at the repo and copying regular files to the drive for some reason. {{{ #!/bin/bash #Check for issues. if [[ ! -e "/dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab" ]] then echo "Access failure: No drive present." echo "[`date +%F`] Local access failure: No drive present." >> /home/ian/logs/backupfail exit fi if [[ -e "/tmp/rbkuplock" ]] then echo "Access failure: Lockfile exists." echo "[`date +%F`] Local access failure: Lockfile exists." >> /home/ian/logs/backupfail exit fi #Mount the backup drive touch "/tmp/rbkuplock" echo "<<!ENCRYPTION PASSWORD!>>" | cryptsetup luksOpen /dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab sbk mount /dev/mapper/sbk /sbk/ #Perform the access. pcmanfm /sbk #Check for issues. if [[ "`df | grep /dev/mapper/sbk | awk '{ print $4 }'`" -lt "104857600" ]] then echo "Backup warning: Less than 100GB on drive." echo "[`date +%F`] Local backup warning: Less than 100GB on drive." >> /home/ian/logs/backupfail curl http://textbelt.com/text -d number=<<!PHONE NUMBER!>> -d "message=[`date +%F`] Local backup warning: Less than 100GB on drive." fi #Unmount the backup drive. umount /sbk cryptsetup luksClose sbk rm -f "/tmp/rbkuplock" }}} !!Backup Volume Mounter This allows you to mount an actual backup and open a file manager to that backup so you can inspect and copy files from a backup. It will ask you to select the backup chain type and actual backup to mount. {{{ #!/bin/bash #Mount a borg repo. backupDir="/sbk/" destLocation="/mnt" #Mount Code #Check for issues. if [[ ! -e "/dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab" ]] then echo "Access failure: No drive present." echo "[`date +%F`] Local access failure: No drive present." >> /home/ian/logs/backupfail exit fi if [[ -e "/tmp/rbkuplock" ]] then echo "Access failure: Lockfile exists." echo "[`date +%F`] Local access failure: Lockfile exists." >> /home/ian/logs/backupfail exit fi #Mount the backup drive touch "/tmp/rbkuplock" echo "<<!ENCRYPTION PASSWORD!>>" | cryptsetup luksOpen /dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab sbk mount /dev/mapper/sbk /sbk/ echo "Select backup type. Enter nothing to show all." echo -e 'main

bulk

vm

boot' | while read line; do i=$((i+1)); echo "$i) $line"; done echo read -p "Type? " typeNum type=$(echo -e 'main

bulk

vm

boot' | sed -n "${typeNum}p" | sed 's/ .*//g') if [[ "$type" == "" ]] then type=".*" fi borg list "${backupDir}${type}-backup" | grep "$type" | tac > ~/.restoreTemp echo "Select a backup to restore from. Enter nothing to cancel." cat ~/.restoreTemp | while read line; do i=$((i+1)); echo "$i) $line"; done echo read -p "Backup? " backup if [[ "$((backup+1-1))" == "$backup" ]] then backupName=$(cat ~/.restoreTemp | sed -n "${backup}p" | sed 's/ .*//g') fi if [[ "$backupName" != "" ]] then borg mount "${backupDir}${type}-backup::$backupName" "$destLocation" else echo "Abort." fi pcmanfm "$destLocation" fusermount -u "$destLocation" #Unmount code umount /sbk cryptsetup luksClose sbk rm -f "/tmp/rbkuplock" }}} !!File Restore Tool Use this toll to restore files directly to the desktop interactively. This is great for when you make a mistake and are in a hurry. {{{ #!/bin/bash #Restore files from a borg repo. backupDir="/sbk/" destLocation="/home/ian/Desktop/" input=$(readlink -f "$1") #Mount Code #Check for issues. if [[ ! -e "/dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab" ]] then echo "Access failure: No drive present." echo "[`date +%F`] Local access failure: No drive present." >> /home/ian/logs/backupfail exit fi if [[ -e "/tmp/rbkuplock" ]] then echo "Access failure: Lockfile exists." echo "[`date +%F`] Local access failure: Lockfile exists." >> /home/ian/logs/backupfail exit fi #Mount the backup drive touch "/tmp/rbkuplock" echo ""<<!ENCRYPTION PASSWORD!>> | cryptsetup luksOpen /dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab sbk mount /dev/mapper/sbk /sbk/ if [[ "$input" =~ ^/home/ian/VirtualBox\ VMs ]] then type="vm" prefix="sfs" elif [[ "$input" =~ ^/bulk-files ]] then type="bulk" prefix="" input="${input:1}" elif [[ "$input" =~ ^/boot ]] then type="boot" prefix="" input="${input:1}" else type="main" prefix="sfs" fi borg list "${backupDir}${type}-backup" | grep "$type" | tac > ~/.restoreTemp echo "Select a backup to restore from. Enter nothing to cancel." cat ~/.restoreTemp | while read line; do i=$((i+1)); echo "$i) $line"; done echo read -p "Backup? " backup if [[ "$((backup+1-1))" == "$backup" ]] then backupName=$(cat ~/.restoreTemp | sed -n "${backup}p" | sed 's/ .*//g') fi if [[ "$backupName" != "" ]] then cd "$destLocation" mkdir temp_delete cd temp_delete borg extract -v "${backupDir}${type}-backup::$backupName" "$prefix$input" cd ../ mv "temp_delete/$prefix$input" "./" rm -r "temp_delete" else echo "Abort." fi #Unmount code umount /sbk cryptsetup luksClose sbk rm -f "/tmp/rbkuplock" }}} !Rsnapshot/Rdiff-backup Backup Scripts These scripts should be very reliable. They rely on LVM snapshots, rsnapshot, and rdiff-backup. It also uses curl to send error texts. Make sure to replace anything in """<<!!!!>>""" with the corresponding value. !!Backup Script (Put in Cron for every 4 hours.) {{{ #!/bin/bash startTime=$(date +%s) set -o pipefail function sendError { echo "Backup failure: $1" echo "[`date +%F`] Local backup failure: $1" >> /home/<<!!Home Folder!!>>/logs/backupfail curl http://textbelt.com/text -d number=<<!!Phone Number!!>> -d "message=[`date +%F`] Local backup failure: $1" } #Check for issues. if [[ ! -e "/dev/disk/by-uuid/<<!!Backup Drive UUID!!>>" ]] then sendError "No drive present." exit fi if [[ -e "/tmp/rbkuplock" ]] then sendError "Lockfile exists." exit fi #Create Lock touch "/tmp/rbkuplock" date +%FT%R > /backup-date #Mount the backup drive echo "<<!!Drive Passphrase!!>>" | /sbin/cryptsetup luksOpen /dev/disk/by-uuid/<<!!Backup Drive UUID!!>> sbk mount /dev/mapper/sbk /sbk/ #Ensure the backup is actually there. if [[ ! -e "/sbk/backup" ]] then sendError "No folder present." exit fi #Create an LVM snapshot /sbin/pvchange -xy /dev/mapper/<<!!Name of Logical Volume to Snapshot!!>> sync /sbin/lvcreate --size 10g --snapshot --name sfs /dev/main/root mount -r /dev/main/sfs /sfs if [[ "$(cat /sfs/backup-date)" == "" ]] then sendError "No snapshot present." umount /sfs /sbin/lvremove -f /dev/main/sfs umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/rbkuplock" exit fi hour="$(date +%H)" #Perform the backup. /home/ian/bookmarks/scripts/soft-backup.sh /usr/bin/rsnapshot sync | tee /home/<<!!Home Folder!!>>/logs/synclog if [[ "$?" != "0" ]] then sendError "Static file backup sync errors" else e=0 if [[ "$hour" == "04" ]] then rm /home/<<!!Home Folder!!>>/logs/rotatelog if [[ "$(date +%d)" == "01" ]] then /usr/bin/rsnapshot monthly | tee -a /home/<<!!Home Folder!!>>/logs/rotatelog error="$?"; e=$((e+error)) fi if [[ "$(date +%w)" == "1" ]] then /usr/bin/rsnapshot weekly | tee -a /home/<<!!Home Folder!!>>/logs/rotatelog error="$?"; e=$((e+error)) fi /usr/bin/rsnapshot daily | tee -a /home/<<!!Home Folder!!>>/logs/rotatelog error="$?"; e=$((e+error)) fi /usr/bin/rsnapshot hourly | tee -a /home/<<!!Home Folder!!>>/logs/rotatelog error="$?"; e=$((e+error)) #Check for issues. if [[ "$e" != "0" ]] then sendError "Backup rotation errors" fi fi if [[ "$hour" == "04" ]] || [[ "$1" == "vmforce" ]] then if [[ "$(date +%w)" == "1" ]] || [[ "$1" == "vmforce" ]] then rdiff-backup --force --remove-older-than 8W /sbk/vmbackup/ rdiff-backup -v6 '/sfs/home/<<!!Home Folder!!>>/VirtualBox VMs/' /sbk/vmbackup/ | tee /home/<<!!Home Folder!!>>/logs/weeklyvmlog fi #Check for issues. if [[ "$?" != "0" ]] then sendError "VM backup errors" fi fi if [[ "`df | grep /dev/mapper/sbk | awk '{ print $4 }'`" -lt "104857600" ]] then sendError "Less than 100GB on drive." fi #Delete the snapshot. umount /sfs /sbin/lvremove -f /dev/main/sfs #Unmount the backup drive. umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/rbkuplock" endTime=$(date +%s) echo "Backup took $(((endTime-startTime)/3600)):$((((endTime-startTime)%3600)/60))." | tee -a /home/<<!!Home Folder!!>>/logs/synclog }}} !!Restore Script for Individual Files/Folders This script will take the file/folder you want to restore as an argument, and let you select the backup to restore from by date. {{{ #!/bin/bash #Check for issues. if [[ ! -e "/dev/disk/by-uuid/<<!!Backup Drive UUID!!>>" ]] then echo "Access failure: No drive present." echo "[`date +%F`] Local access failure: No drive present." >> /home/<<!!Home Folder!!>>/logs/backupfail exit fi if [[ -e "/tmp/rbkuplock" ]] then echo "Access failure: Lockfile exists." echo "[`date +%F`] Local access failure: Lockfile exists." >> /home/<<!!Home Folder!!>>/logs/backupfail exit fi #Mount the backup drive touch "/tmp/rbkuplock" echo <<!!Drive Passphrase!!>> | cryptsetup luksOpen /dev/disk/by-uuid/<<!!Backup Drive UUID!!>> sbk mount /dev/mapper/sbk /sbk/ #Perform the restore. for i in `ls /sbk/backup` do echo "$(cat /sbk/backup/$i/localhost/sfs/backup-date): $i" done | sort read -p "What backup do you wish to restore from? " resBk if [[ -e "/sbk/backup/$resBk/" ]] then echo "Restoring folder to desktop." cp -rfav "/sbk/backup/$resBk/localhost/sfs/$1" "/<<!!Place to put restored files!!>>/" fi #Check for issues. if [[ "`df | grep /dev/mapper/sbk | awk '{ print $4 }'`" -lt "104857600" ]] then echo "Backup warning: Less than 100GB on drive." echo "[`date +%F`] Local backup warning: Less than 100GB on drive." >> /home/<<!!Home Folder!!>>/logs/backupfail curl http://textbelt.com/text -d number=<<!!Phone Number!!>> -d "message=[`date +%F`] Local backup warning: Less than 100GB on drive." fi #Unmount the backup drive. umount /sbk cryptsetup luksClose sbk rm -f "/tmp/rbkuplock" }}} !!Access Script This script simply mounts the backup and opens """PCManFm""". {{{ #!/bin/bash #Check for issues. if [[ ! -e "/dev/disk/by-uuid/<<!!Backup Drive UUID!!>>" ]] then echo "Access failure: No drive present." echo "[`date +%F`] Local access failure: No drive present." >> /home/<<!!Home Folder!!>>/logs/backupfail exit fi if [[ -e "/tmp/rbkuplock" ]] then echo "Access failure: Lockfile exists." echo "[`date +%F`] Local access failure: Lockfile exists." >> /home/<<!!Home Folder!!>>/logs/backupfail exit fi #Mount the backup drive touch "/tmp/rbkuplock" echo <<!!Drive Passphrase!!>> | cryptsetup luksOpen /dev/disk/by-uuid/<<!!Backup Drive UUID!!>> sbk mount /dev/mapper/sbk /sbk/ #Perform the access. pcmanfm /sbk #Check for issues. if [[ "`df | grep /dev/mapper/sbk | awk '{ print $4 }'`" -lt "104857600" ]] then echo "Backup warning: Less than 100GB on drive." echo "[`date +%F`] Local backup warning: Less than 100GB on drive." >> /home/<<!!Home Folder!!>>/logs/backupfail curl http://textbelt.com/text -d number=<<!!Phone Number!!>> -d "message=[`date +%F`] Local backup warning: Less than 100GB on drive." fi #Unmount the backup drive. umount /sbk cryptsetup luksClose sbk rm -f "/tmp/rbkuplock" }}} !Attic Backup Scripts Update from 2014-10-30: Attic appears to be unstable for extremely large (500GB or more) backups. Use rsnapshot for static files and rdiff-backup for VMs. To prevent people from re-inventing the wheel, here are the backup scripts I use for my computer. They both use [[Attic|https://attic-backup.org/]]. You'll need cURL installed for SMS support. !!Local Backup Script This script backs up to an external hard drive encrypted with LUKS. I start it as root using this line in the crontab: {{{0 1 * * * /opt/shutdown/rbkup.sh}}} {{{ #!/bin/bash set -o pipefail function sendError { echo "Backup failure: $1" echo "[`date +%F`] Local backup failure: $1" >> /home/ian/logs/backupfail curl http://textbelt.com/text -d number=<<PHONE>> -d "message=[`date +%F`] Local backup failure: $1" } #Check for issues. if [[ ! -e "/dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab" ]] then sendError "No drive present." exit fi if [[ -e "/tmp/rbkuplock" ]] then sendError "Lockfile exists." exit fi #Wait for the cache touch "/tmp/rbkuplock" while [[ -e "/tmp/atticcachelock" ]] do sleep 10 echo "Waiting for unlock..." done #Mount the backup drive touch "/tmp/atticcachelock" echo "<<PASSPHRASE>>" | /sbin/cryptsetup luksOpen /dev/disk/by-uuid/f3ec445b-5ef2-48e4-bd7d-4661542e1dab sbk mount /dev/mapper/sbk /sbk/ #Ensure the backup is actually there. if [[ ! -e "/sbk/backup.attic" ]] then sendError "No folder present." exit fi #Prepare the cache. mount -t tmpfs -o size=6g tmpfs /root/.cache/attic rsync -a /root/.cache/attic-save/ /root/.cache/attic/ #Check for issues. if [[ "$?" != "0" ]] then sendError "Cache copy errors were reported." echo "[`date +%F`] `df -h | grep "/root/.cache/attic"`" >> /home/ian/logs/backupfail umount /root/.cache/attic umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/rbkuplock" /bin/rm -f "/tmp/atticcachelock" exit fi #Perform the backup. attic prune /sbk/backup.attic -v --keep-within=10d -w 4 -m 24 attic create -vs --exclude /dev --exclude /sbk --exclude /media --exclude /mnt --exclude /tmp --exclude /proc --exclude /home/ian/.cache --exclude /root/.cache/ --exclude /run --exclude /var/cache --exclude /var/tmp --exclude /tmp --exclude /sys /sbk/backup.attic::`date +%FT%R` / | tee /home/ian/logs/dailybackup #Check for issues. if [[ "$?" != "0" ]] then sendError "Errors were reported." fi #Unmount the cache. sync rsync -a --delete /root/.cache/attic/ /root/.cache/attic-save/ umount /root/.cache/attic if [[ "`df | grep /dev/mapper/sbk | awk '{ print $4 }'`" -lt "104857600" ]] then sendError "Less than 100GB on drive." fi #Unmount the backup drive. umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/rbkuplock" /bin/rm -f "/tmp/atticcachelock" }}} !!Remote Backup Script This backup backs up to an cryptographically untrusted server using an ssh key. Encryption in Attic is enabled. It must be started with ssh-agent in front of it. I use this line in the crontab as root: {{{0 2 * * 5,1 ssh-agent /opt/shutdown/sbkup.sh}}} {{{ #!/bin/bash set -o pipefail function sendError { echo "Backup failure: $1" echo "[`date +%F`] Remote backup failure: $1" >> /home/ian/logs/backupfail curl http://textbelt.com/text -d number=<<PHONE>> -d "message=[`date +%F`] Remote backup failure: $1" } #Check for issues. if [[ "$(ip addr show wlan0 | grep inet | awk '{print $2}' | tr "

" " " | cut -f1 -d"/")" != "192.168.3.129" ]] then sendError "Not on local network." exit fi if [[ -e "/tmp/sbkuplock" ]] then sendError "Lockfile exists." exit fi while [[ -e "/tmp/atticcachelock" ]] do sleep 10 echo "Waiting for unlock..." done #Mount the backup drive touch "/tmp/sbkuplock" touch "/tmp/atticcachelock" #Prepare the cache. mount -t tmpfs -o size=6g tmpfs /root/.cache/attic rsync -a /root/.cache/attic-remote/ /root/.cache/attic/ #Check for issues. if [[ "$?" != "0" ]] then sendError "Cache copy errors were reported." echo "[`date +%F`] `df -h | grep "/root/.cache/attic"`" >> /home/ian/logs/backupfail umount /root/.cache/attic umount /sbk /sbin/cryptsetup luksClose sbk /bin/rm -f "/tmp/sbkuplock" /bin/rm -f "/tmp/atticcachelock" exit fi #Perform the backup. ssh-add /home/ian/.ssh/bkupkey ATTIC_PASSPHRASE=<<PASSPHRASE>> attic prune ian@192.168.3.133:backup.attic -v --keep-within=10d -w 4 -m 24 ATTIC_PASSPHRASE=<<PASSPHRASE>> attic create -vs --exclude /dev --exclude /sbk --exclude /media --exclude /mnt --exclude /tmp --exclude /proc --exclude /home/ian/.cache --exclude /root/.cache/ --exclude /run --exclude /var/cache --exclude /var/tmp --exclude /tmp --exclude /sys ian@192.168.3.133:backup.attic::`date +%FT%R` / | tee /home/ian/logs/remotebackup #Check for issues. if [[ "$?" != "0" ]] then sendError "Errors were reported." fi #Unmount the cache. sync rsync -a --delete /root/.cache/attic/ /root/.cache/attic-remote/ umount /root/.cache/attic #Unmount the backup drive. /bin/rm -f "/tmp/sbkuplock" /bin/rm -f "/tmp/atticcachelock" }}}

!Converting decimal to binary: First you start with a number, 22 in this case. The first number in the chart greater than 22 is 32 (06 +32), so we use the one before it 16 (05 +16). Repeat these steps until you run out of numbers in the chart (place values 05-01 in this case): Subtract the number on the chart from your number (your number-chart number=number returned). If: *The number returned is a negative number, don't keep the value as your next number, and add a 0 to your binary number (ex: problem:6-8=-2 binary number:1__0__xxx). *The number returned is 0 or a positive number, make the number returned your next number, and add a 1 to your binary number (ex: problem:22-16=6 binary number:__1__xxxx). Here is an example of these rules |!problem|!number returned|!binary number| |22-16|6|__1__xxxx| |6-8|-2|1__0__xxx| |6-4|2|10__1__xx| |2-2|0|101__1__x| |0-1|-1|1011__0__| 10110 is your result. !Converting binary to decimal: First you start with a number, 10110 in this case. Count out the digits in the number like this: |binary number|1|0|1|1|0| |2^X|4|3|2|1|0| |Power of 2|8|4|2|1|0| Add all the powers of 2 representing place values under the 1's on the chart like this: 2+4+16=22 22 is your result. !Hexadecimal Hexadecimal is used because it is much easier to convert between binary and hexadecimal. Here is a chart: |!Decimal|!Hexadecimal|!Binary| |0|0|0000| |1|1|0001| |2|2|0010| |3|3|0011| |4|4|0100| |5|5|0101| |6|6|0110| |7|7|0111| |8|8|1000| |9|9|1001| |10|A|1010| |11|B|1011| |12|C|1100| |13|D|1101| |14|E|1110| |15|F|1111| Converting is as easy as this: |!Hexadecimal|!Binary| |__1__"""C"""__5__F|__0001__1100__0101__1111| (The 4 bit groups are called a nibble. 8 bit groups are called a byte.)

/*** |Name|CalendarPlugin| |Source|http://www.TiddlyTools.com/#CalendarPlugin| |Version|1.5.0| |Author|Eric Shulman| |Original Author|SteveRumsby| |License|unknown| |~CoreVersion|2.1| |Type|plugin| |Description|display monthly and yearly calendars| NOTE: For //enhanced// date popup display, optionally install [[DatePlugin]] and [[ReminderMacros]] !!!Usage: <<< |{{{<<calendar>>}}}|full-year calendar for the current year| |{{{<<calendar year>>}}}|full-year calendar for the specified year| |{{{<<calendar year month>>}}}|one month calendar for the specified month and year| |{{{<<calendar thismonth>>}}}|one month calendar for the current month| |{{{<<calendar lastmonth>>}}}|one month calendar for last month| |{{{<<calendar nextmonth>>}}}|one month calendar for next month| |{{{<<calendar +n>>}}}<br>{{{<<calendar -n>>}}}|one month calendar for a month +/- 'n' months from now| <<< !!!Configuration: <<< |''First day of week:''<br>{{{config.options.txtCalFirstDay}}}|<<option txtCalFirstDay>>|(Monday = 0, Sunday = 6)| |''First day of weekend:''<br>{{{config.options.txtCalStartOfWeekend}}}|<<option txtCalStartOfWeekend>>|(Monday = 0, Sunday = 6)| <<option chkDisplayWeekNumbers>> Display week numbers //(note: Monday will be used as the start of the week)// |''Week number display format:''<br>{{{config.options.txtWeekNumberDisplayFormat }}}|<<option txtWeekNumberDisplayFormat >>| |''Week number link format:''<br>{{{config.options.txtWeekNumberLinkFormat }}}|<<option txtWeekNumberLinkFormat >>| <<< !!!Revisions <<< 2009.04.31 [1.5.0] rewrote onClickCalendarDate() (popup handler) and added config.options.txtCalendarReminderTags. Partial code reduction/cleanup. Assigned true version number (1.5.0) 2008.09.10 added '+n' (and '-n') param to permit display of relative months (e.g., '+6' means 'six months from now', '-3' means 'three months ago'. Based on suggestion from Jean. 2008.06.17 added support for config.macros.calendar.todaybg 2008.02.27 in handler(), DON'T set hard-coded default date format, so that *customized* value (pre-defined in config.macros.calendar.journalDateFmt is used. 2008.02.17 in createCalendarYear(), fix next/previous year calculation (use parseInt() to convert to numeric value). Also, use journalDateFmt for date linking when NOT using [[DatePlugin]]. 2008.02.16 in createCalendarDay(), week numbers now created as TiddlyLinks, allowing quick creation/navigation to 'weekly' journals (based on request from Kashgarinn) 2008.01.08 in createCalendarMonthHeader(), 'month year' heading is now created as TiddlyLink, allowing quick creation/navigation to 'month-at-a-time' journals 2007.11.30 added 'return false' to onclick handlers (prevent IE from opening blank pages) 2006.08.23 added handling for weeknumbers (code supplied by Martin Budden (see 'wn**' comment marks). Also, incorporated updated by Jeremy Sheeley to add caching for reminders (see [[ReminderMacros]], if installed) 2005.10.30 in config.macros.calendar.handler(), use 'tbody' element for IE compatibility. Also, fix year calculation for IE's getYear() function (which returns '2005' instead of '105'). Also, in createCalendarDays(), use showDate() function (see [[DatePlugin]], if installed) to render autostyled date with linked popup. Updated calendar stylesheet definition: use .calendar class-specific selectors, add text centering and margin settings 2006.05.29 added journalDateFmt handling <<< !!!Code ***/ //{{{ version.extensions.CalendarPlugin= { major: 1, minor: 5, revision: 0, date: new Date(2009,5,31)}; //}}} //{{{ if(config.options.txtCalFirstDay == undefined) config.options.txtCalFirstDay = 0; if(config.options.txtCalStartOfWeekend == undefined) config.options.txtCalStartOfWeekend = 5; if(config.options.chkDisplayWeekNumbers == undefined) config.options.chkDisplayWeekNumbers = false; if(config.options.chkDisplayWeekNumbers) config.options.txtCalFirstDay = 0; if(config.options.txtWeekNumberDisplayFormat == undefined) config.options.txtWeekNumberDisplayFormat = 'w0WW'; if(config.options.txtWeekNumberLinkFormat == undefined) config.options.txtWeekNumberLinkFormat = 'YYYY-w0WW'; if(config.options.txtCalendarReminderTags == undefined) config.options.txtCalendarReminderTags = 'reminder'; config.macros.calendar = { monthnames:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], daynames:['M','T','W','T','F','S','S'], todaybg:'#ccccff', weekendbg:'#c0c0c0', monthbg:'#e0e0e0', holidaybg:'#ffc0c0', journalDateFmt:'DD MMM YYYY', monthdays:[31,28,31,30,31,30,31,31,30,31,30,31], holidays:[ ] // for customization see [[CalendarPluginConfig]] }; //}}} //{{{ function calendarIsHoliday(date) { var longHoliday = date.formatString('0DD/0MM/YYYY'); var shortHoliday = date.formatString('0DD/0MM'); for(var i = 0; i < config.macros.calendar.holidays.length; i++) { if( config.macros.calendar.holidays[i]==longHoliday || config.macros.calendar.holidays[i]==shortHoliday) return true; } return false; } //}}} //{{{ config.macros.calendar.handler = function(place,macroName,params) { var calendar = createTiddlyElement(place, 'table', null, 'calendar', null); var tbody = createTiddlyElement(calendar, 'tbody'); var today = new Date(); var year = today.getYear(); if (year<1900) year+=1900; // get journal format from SideBarOptions (ELS 5/29/06 - suggested by MartinBudden) var text = store.getTiddlerText('SideBarOptions'); var re = new RegExp('<<(?:newJournal)([^>]*)>>','mg'); var fm = re.exec(text); if (fm && fm[1]!=null) { var pa=fm[1].readMacroParams(); if (pa[0]) this.journalDateFmt = pa[0]; } var month=-1; if (params[0] == 'thismonth') { var month=today.getMonth(); } else if (params[0] == 'lastmonth') { var month = today.getMonth()-1; if (month==-1) { month=11; year--; } } else if (params[0] == 'nextmonth') { var month = today.getMonth()+1; if (month>11) { month=0; year++; } } else if (params[0]&&'+-'.indexOf(params[0].substr(0,1))!=-1) { var month = today.getMonth()+parseInt(params[0]); if (month>11) { year+=Math.floor(month/12); month%=12; }; if (month<0) { year+=Math.floor(month/12); month=12+month%12; } } else if (params[0]) { year = params[0]; if(params[1]) month=parseInt(params[1])-1; if (month>11) month=11; if (month<0) month=0; } if (month!=-1) { cacheReminders(new Date(year, month, 1, 0, 0), 31); createCalendarOneMonth(tbody, year, month); } else { cacheReminders(new Date(year, 0, 1, 0, 0), 366); createCalendarYear(tbody, year); } window.reminderCacheForCalendar = null; } //}}} //{{{ // cache used to store reminders while the calendar is being rendered // it will be renulled after the calendar is fully rendered. window.reminderCacheForCalendar = null; //}}} //{{{ function cacheReminders(date, leadtime) { if (window.findTiddlersWithReminders == null) return; window.reminderCacheForCalendar = {}; var leadtimeHash = []; leadtimeHash [0] = 0; leadtimeHash [1] = leadtime; var t = findTiddlersWithReminders(date, leadtimeHash, null, 1); for(var i = 0; i < t.length; i++) { //just tag it in the cache, so that when we're drawing days, we can bold this one. window.reminderCacheForCalendar[t[i]['matchedDate']] = 'reminder:' + t[i]['params']['title']; } } //}}} //{{{ function createCalendarOneMonth(calendar, year, mon) { var row = createTiddlyElement(calendar, 'tr'); createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon]+' '+year, true, year, mon); row = createTiddlyElement(calendar, 'tr'); createCalendarDayHeader(row, 1); createCalendarDayRowsSingle(calendar, year, mon); } //}}} //{{{ function createCalendarMonth(calendar, year, mon) { var row = createTiddlyElement(calendar, 'tr'); createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon]+' '+ year, false, year, mon); row = createTiddlyElement(calendar, 'tr'); createCalendarDayHeader(row, 1); createCalendarDayRowsSingle(calendar, year, mon); } //}}} //{{{ function createCalendarYear(calendar, year) { var row; row = createTiddlyElement(calendar, 'tr'); var back = createTiddlyElement(row, 'td'); var backHandler = function() { removeChildren(calendar); createCalendarYear(calendar, parseInt(year)-1); return false; // consume click }; createTiddlyButton(back, '<', 'Previous year', backHandler); back.align = 'center'; var yearHeader = createTiddlyElement(row, 'td', null, 'calendarYear', year); yearHeader.align = 'center'; yearHeader.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?22:19);//wn** var fwd = createTiddlyElement(row, 'td'); var fwdHandler = function() { removeChildren(calendar); createCalendarYear(calendar, parseInt(year)+1); return false; // consume click }; createTiddlyButton(fwd, '>', 'Next year', fwdHandler); fwd.align = 'center'; createCalendarMonthRow(calendar, year, 0); createCalendarMonthRow(calendar, year, 3); createCalendarMonthRow(calendar, year, 6); createCalendarMonthRow(calendar, year, 9); } //}}} //{{{ function createCalendarMonthRow(cal, year, mon) { var row = createTiddlyElement(cal, 'tr'); createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon], false, year, mon); createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+1], false, year, mon); createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+2], false, year, mon); row = createTiddlyElement(cal, 'tr'); createCalendarDayHeader(row, 3); createCalendarDayRows(cal, year, mon); } //}}} //{{{ function createCalendarMonthHeader(cal, row, name, nav, year, mon) { var month; if (nav) { var back = createTiddlyElement(row, 'td'); back.align = 'center'; back.style.background = config.macros.calendar.monthbg; var backMonHandler = function() { var newyear = year; var newmon = mon-1; if(newmon == -1) { newmon = 11; newyear = newyear-1;} removeChildren(cal); cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31); createCalendarOneMonth(cal, newyear, newmon); return false; // consume click }; createTiddlyButton(back, '<', 'Previous month', backMonHandler); month = createTiddlyElement(row, 'td', null, 'calendarMonthname') createTiddlyLink(month,name,true); month.setAttribute('colSpan', config.options.chkDisplayWeekNumbers?6:5);//wn** var fwd = createTiddlyElement(row, 'td'); fwd.align = 'center'; fwd.style.background = config.macros.calendar.monthbg; var fwdMonHandler = function() { var newyear = year; var newmon = mon+1; if(newmon == 12) { newmon = 0; newyear = newyear+1;} removeChildren(cal); cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31); createCalendarOneMonth(cal, newyear, newmon); return false; // consume click }; createTiddlyButton(fwd, '>', 'Next month', fwdMonHandler); } else { month = createTiddlyElement(row, 'td', null, 'calendarMonthname', name) month.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?8:7);//wn** } month.align = 'center'; month.style.background = config.macros.calendar.monthbg; } //}}} //{{{ function createCalendarDayHeader(row, num) { var cell; for(var i = 0; i < num; i++) { if (config.options.chkDisplayWeekNumbers) createTiddlyElement(row, 'td');//wn** for(var j = 0; j < 7; j++) { var d = j + (config.options.txtCalFirstDay - 0); if(d > 6) d = d - 7; cell = createTiddlyElement(row, 'td', null, null, config.macros.calendar.daynames[d]); if(d == (config.options.txtCalStartOfWeekend-0) || d == (config.options.txtCalStartOfWeekend-0+1)) cell.style.background = config.macros.calendar.weekendbg; } } } //}}} //{{{ function createCalendarDays(row, col, first, max, year, mon) { var i; if (config.options.chkDisplayWeekNumbers){ if (first<=max) { var ww = new Date(year,mon,first); var td=createTiddlyElement(row, 'td');//wn** var link=createTiddlyLink(td,ww.formatString(config.options.txtWeekNumberLinkFormat),false); link.appendChild(document.createTextNode( ww.formatString(config.options.txtWeekNumberDisplayFormat))); } else createTiddlyElement(row, 'td');//wn** } for(i = 0; i < col; i++) createTiddlyElement(row, 'td'); var day = first; for(i = col; i < 7; i++) { var d = i + (config.options.txtCalFirstDay - 0); if(d > 6) d = d - 7; var daycell = createTiddlyElement(row, 'td'); var isaWeekend=((d==(config.options.txtCalStartOfWeekend-0) || d==(config.options.txtCalStartOfWeekend-0+1))?true:false); if(day > 0 && day <= max) { var celldate = new Date(year, mon, day); // ELS 10/30/05 - use <<date>> macro's showDate() function to create popup // ELS 05/29/06 - use journalDateFmt if (window.showDate) showDate(daycell,celldate,'popup','DD', config.macros.calendar.journalDateFmt,true, isaWeekend); else { if(isaWeekend) daycell.style.background = config.macros.calendar.weekendbg; var title = celldate.formatString(config.macros.calendar.journalDateFmt); if(calendarIsHoliday(celldate)) daycell.style.background = config.macros.calendar.holidaybg; var now=new Date(); if ((now-celldate>=0) && (now-celldate<86400000)) // is today? daycell.style.background = config.macros.calendar.todaybg; if(window.findTiddlersWithReminders == null) { var link = createTiddlyLink(daycell, title, false); link.appendChild(document.createTextNode(day)); } else var button = createTiddlyButton(daycell, day, title, onClickCalendarDate); } } day++; } } //}}} //{{{ // Create a pop-up containing: // * a link to a tiddler for this date // * a 'new tiddler' link to add a reminder for this date // * links to current reminders for this date // NOTE: this code is only used if [[ReminderMacros]] is installed AND [[DatePlugin]] is //not// installed. function onClickCalendarDate(ev) { ev=ev||window.event; var d=new Date(this.getAttribute('title')); var date=d.formatString(config.macros.calendar.journalDateFmt); var p=Popup.create(this); if (!p) return; createTiddlyLink(createTiddlyElement(p,'li'),date,true); var rem='\

\\<\\<reminder day:%0 month:%1 year:%2 title: \\>\\>'; rem=rem.format([d.getDate(),d.getMonth()+1,d.getYear()+1900]); var cmd="<<newTiddler label:[[new reminder...]] prompt:[[add a new reminder to '%0']]" +" title:[[%0]] text:{{store.getTiddlerText('%0','')+'%1'}} tag:%2>>"; wikify(cmd.format([date,rem,config.options.txtCalendarReminderTags]),p); createTiddlyElement(p,'hr'); var t=findTiddlersWithReminders(d,[0,31],null,1); for(var i=0; i<t.length; i++) { var link=createTiddlyLink(createTiddlyElement(p,'li'), t[i].tiddler, false); link.appendChild(document.createTextNode(t[i]['params']['title'])); } Popup.show(); ev.cancelBubble=true; if (ev.stopPropagation) ev.stopPropagation(); return false; } //}}} //{{{ function calendarMaxDays(year, mon) { var max = config.macros.calendar.monthdays[mon]; if(mon == 1 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) max++; return max; } //}}} //{{{ function createCalendarDayRows(cal, year, mon) { var row = createTiddlyElement(cal, 'tr'); var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0); if(first1 < 0) first1 = first1 + 7; var day1 = -first1 + 1; var first2 = (new Date(year, mon+1, 1)).getDay() -1 - (config.options.txtCalFirstDay-0); if(first2 < 0) first2 = first2 + 7; var day2 = -first2 + 1; var first3 = (new Date(year, mon+2, 1)).getDay() -1 - (config.options.txtCalFirstDay-0); if(first3 < 0) first3 = first3 + 7; var day3 = -first3 + 1; var max1 = calendarMaxDays(year, mon); var max2 = calendarMaxDays(year, mon+1); var max3 = calendarMaxDays(year, mon+2); while(day1 <= max1 || day2 <= max2 || day3 <= max3) { row = createTiddlyElement(cal, 'tr'); createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7; createCalendarDays(row, 0, day2, max2, year, mon+1); day2 += 7; createCalendarDays(row, 0, day3, max3, year, mon+2); day3 += 7; } } //}}} //{{{ function createCalendarDayRowsSingle(cal, year, mon) { var row = createTiddlyElement(cal, 'tr'); var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0); if(first1 < 0) first1 = first1+ 7; var day1 = -first1 + 1; var max1 = calendarMaxDays(year, mon); while(day1 <= max1) { row = createTiddlyElement(cal, 'tr'); createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7; } } //}}} //{{{ setStylesheet('.calendar, .calendar table, .calendar th, .calendar tr, .calendar td { text-align:center; } .calendar, .calendar a { margin:0px !important; padding:0px !important; }', 'calendarStyles'); //}}}

This is a simple script that allows for the creation of interactive stories. There are multiple files that are each named a number. Each numbered file is a chapter in the story. The chapter can have a question at the bottom and a list of options. The user types the number of the option (a different chapter number) and they are directed to that chapter. The script also checks for the end of the story and offers options when you reach the end. [[>>Download<<|https://www.dropbox.com/s/jts4j4t017oxf7l/choose%20your%20own%20adventure.zip]]

<<clock2 120>>

/*** | Name:|Clock2| | Author:|Simon Baird| | Description:|A skinnable, sizeable analog clock| | Source:|http://tiddlyspot.com/mptw/#Clock2| | Requires:|Firefox 1.5.x or maybe Safari| | Version:|1.0.6| | Date:|8-Jul-2008| !!Note * Does not work in IE or Opera due to lack of canvas support. * If you make a nice skin send it to me and I will include it here. *I'm not actively maintaining this plugin * See also http://randomibis.com/coolclock/ !!Ideas * Can we support IE with this? http://sourceforge.net/projects/excanvas * Skin should specify order of drawing so things can be on top of other things * Fix it so we can have filled and/or stroked elements * Skin should allow any number of moving and static elements * Make download and example for non-TW use * Make floating draggable? !!Examples {{{ <<clock2 fancy>><<clock2 120>> <<clock2 chunkySwiss>> <<clock2 60 chunkySwiss noSeconds>><<clock2 '{ outerBorder: { lineWidth: 60, radius:55, color: "#dd8877", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 80, endAt: 95, color: "white", alpha: 1 }, largeIndicator: { lineWidth: 12, startAt: 77, endAt: 89, color: "#dd8877", alpha: 1 }, hourHand: { lineWidth: 15, startAt: -15, endAt: 50, color: "white", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: 24, endAt: 200, color: "#771100", alpha: 0.6 }, secondHand: { lineWidth: 3, startAt: 22, endAt: 83, color: "green", alpha: 0 }, secondDecoration: { lineWidth: 1, startAt: 52, radius: 26, fillColor: "white", color: "red", alpha: 0.2 } }'>> }}} <<clock2 fancy>><<clock2 120>> <<clock2 chunkySwiss>> <<clock2 60 chunkySwiss noSeconds>><<clock2 '{ outerBorder: { lineWidth: 60, radius:55, color: "#dd8877", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 80, endAt: 95, color: "white", alpha: 1 }, largeIndicator: { lineWidth: 12, startAt: 77, endAt: 89, color: "#dd8877", alpha: 1 }, hourHand: { lineWidth: 15, startAt: -15, endAt: 50, color: "white", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: 24, endAt: 200, color: "#771100", alpha: 0.6 }, secondHand: { lineWidth: 3, startAt: 22, endAt: 83, color: "green", alpha: 0 }, secondDecoration: { lineWidth: 1, startAt: 52, radius: 26, fillColor: "white", color: "red", alpha: 0.2 } }'>> See also BigClock. !!Code ***/ //{{{ window.CoolClock = function(canvasId,displayRadius,skinId,showSecondHand) { return this.init(canvasId,displayRadius,skinId,showSecondHand); } CoolClock.config = { clockTracker: {}, tickDelay: 1000, longTickDelay: 15000, defaultRadius: 85, renderRadius: 100, defaultSkin: "swissRail", skins: { // try making your own... swissRail: { outerBorder: { lineWidth: 1, radius:95, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 2, startAt: 89, endAt: 93, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 4, startAt: 80, endAt: 93, color: "black", alpha: 1 }, hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 }, minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 } }, chunkySwiss: { outerBorder: { lineWidth: 5, radius:97, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } }, fancy: { outerBorder: { lineWidth: 5, radius:95, color: "green", alpha: 0.7 }, smallIndicator: { lineWidth: 1, startAt: 80, endAt: 93, color: "black", alpha: 0.4 }, largeIndicator: { lineWidth: 1, startAt: 30, endAt: 93, color: "black", alpha: 0.5 }, hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "blue", alpha: 0.7 }, minuteHand: { lineWidth: 7, startAt: -15, endAt: 92, color: "red", alpha: 0.7 }, secondHand: { lineWidth: 10, startAt: 80, endAt: 85, color: "blue", alpha: 0.3 }, secondDecoration: { lineWidth: 1, startAt: 30, radius: 50, fillColor: "blue", color: "red", alpha: 0.15 } } } }; CoolClock.prototype = { init: function(canvasId,displayRadius,skinId,showSecondHand) { this.canvasId = canvasId; this.displayRadius = displayRadius || CoolClock.config.defaultRadius; this.skinId = skinId || CoolClock.config.defaultSkin; this.showSecondHand = typeof showSecondHand == "boolean" ? showSecondHand : true; this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay"]; this.canvas = document.getElementById(canvasId); this.canvas.setAttribute("width",this.displayRadius*2); this.canvas.setAttribute("height",this.displayRadius*2); this.renderRadius = CoolClock.config.renderRadius; var scale = this.displayRadius / this.renderRadius; this.ctx = this.canvas.getContext("2d"); this.ctx.scale(scale,scale); CoolClock.config.clockTracker[canvasId] = this; this.tick(); return this; }, fullCircle: function(skin) { this.fullCircleAt(this.renderRadius,this.renderRadius,skin); }, fullCircleAt: function(x,y,skin) { with (this.ctx) { save(); globalAlpha = skin.alpha; lineWidth = skin.lineWidth; if (!docu