getTablets now uses the new backend
[taboo.git] / components / oyTablets.js
blob396923a6115a3e373308e60b2ed32c75c97c8975
1 /*
2  * Copyright 2007 Jesse Andrews and Manish Singh
3  *  
4  * This file may be used under the terms of of the
5  * GNU General Public License Version 2 or later (the "GPL"),
6  * http://www.gnu.org/licenses/gpl.html
7  *  
8  * Software distributed under the License is distributed on an "AS IS" basis,
9  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10  * for the specific language governing rights and limitations under the
11  * License.
12  */
14 const TB_CONTRACTID = '@oy/tablets;1';
15 const TB_CLASSID    = Components.ID('{962a9516-b177-4083-bbe8-e10f47cf8570}');
16 const TB_CLASSNAME  = 'Tablets Service';
19 const Cc = Components.classes;
20 const Ci = Components.interfaces; 
21 const Cr = Components.results;
22 const Cu = Components.utils;
24 /* from nspr's prio.h */
25 const PR_RDONLY      = 0x01;
26 const PR_WRONLY      = 0x02;
27 const PR_RDWR        = 0x04;
28 const PR_CREATE_FILE = 0x08;
29 const PR_APPEND      = 0x10;
30 const PR_TRUNCATE    = 0x20;
31 const PR_SYNC        = 0x40;
32 const PR_EXCL        = 0x80;
35 function getObserverService() {
36   return Cc['@mozilla.org/observer-service;1']
37     .getService(Ci.nsIObserverService);
41 /* MD5 wrapper */
42 function hex_md5_stream(stream) {   
43   var hasher = Components.classes["@mozilla.org/security/hash;1"]
44     .createInstance(Components.interfaces.nsICryptoHash);
45   hasher.init(hasher.MD5);
47   hasher.updateFromStream(stream, stream.available());
48   var hash = hasher.finish(false);
50   var ret = '';
51   for (var i = 0; i < hash.length; ++i) {
52     var hexChar = hash.charCodeAt(i).toString(16);
53     if (hexChar.length == 1)
54       ret += '0';
55     ret += hexChar;
56   }
58   return ret;
61 function hex_md5(s) {
62   var stream = Components.classes["@mozilla.org/io/string-input-stream;1"]
63     .createInstance(Components.interfaces.nsIStringInputStream);
64   stream.setData(s, s.length);
66   return hex_md5_stream(stream);
71  * Tablet Info Instance
72  */
74 function TabletInfo() {
77 TabletInfo.prototype = {
78   title: "",
79   url: "",
80   description: null,
81   imageURL: "",
82   created: null,
83   updated: null,
84   QueryInterface: function(iid) {
85     if (!iid.equals(Ci.nsISupports) &&
86         !iid.equals(Ci.oyITabletInfo)) {
87       throw Cr.NS_ERROR_NO_INTERFACE;
88     }
89     return this;
90   }
94  * Tablets Service Component
95  */
98 function snapshot() {
99   var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
100   var win = wm.getMostRecentWindow('navigator:browser');
101   var content = win.content;
103   var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
105   var realW = content.innerWidth;
106   var realH = content.innerHeight;
108   var pW = 125.0/realW;
109   var pH = 125.0/realH;
111   var p = pW;
113   if (pH < pW) {
114     p = pH;
115   }
117   var w = p * realW;
118   var h = p * realH;
120   canvas.setAttribute("width", Math.floor(w));
121   canvas.setAttribute("height", Math.floor(h));
122   
123   var ctx = canvas.getContext("2d");
124   ctx.scale(p, p);
125   ctx.drawWindow(content, content.scrollX, content.scrollY, content.innerWidth, content.innerHeight, "rgb(0,0,0)");
127   return [win, canvas];
130 function TabletStorageFS() {
131   this._tabletsDir = Cc['@mozilla.org/file/directory_service;1']
132     .getService(Ci.nsIProperties).get('ProfD', Ci.nsILocalFile);
133   this._tabletsDir.append('tablets');
135   if (!this._tabletsDir.exists())
136     this._tabletsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
138   this._tabletsFile = this._tabletsDir.clone();
139   this._tabletsFile.append('tablets.js');
141   this._loadState();
144 TabletStorageFS.prototype = {
145   save: function TSFS_save(url, data, preview) {
146     this._data[url] = data;
147     this._saveState();
149     try {
150       var file = this._getPreviewFile(url);
152       var ostream = Cc['@mozilla.org/network/file-output-stream;1']
153         .createInstance(Ci.nsIFileOutputStream);
154       ostream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, 0);
156       ostream.write(preview, preview.length);
157       ostream.close();
158     }
159     catch (e) { }
160   },
161   delete: function TSFS_delete(url) {
162     try {
163       var file = this._getPreviewFile(url);
164       file.remove(false);
165     }
166     catch (e) { }
168     delete this._data[url];
169     this._saveState();
170   },
171   retrieve: function TSFS_retrieve(url) {
172     var file = this._getPreviewFile(url);
174     var ios = Cc['@mozilla.org/network/io-service;1']
175       .getService(Ci.nsIIOService);
176     var fileHandler = ios.getProtocolHandler('file')
177       .QueryInterface(Ci.nsIFileProtocolHandler);
178     var previewURL = fileHandler.getURLSpecFromFile(file);
180     return [this._data[url], previewURL];
181   },
182   getURLs: function TSFS_getURLs() {
183     var urls = [];
184     for (var url in this._data)
185       urls.push(url);
186     return urls;
187   },
188   _getPreviewFile: function TSFS__getPreviewFile(url) {
189     var id = hex_md5(url);
190     var file = this._tabletsDir.clone();
191     file.append(id + '.png');
192     return file;
193   },
194   _saveState: function TSFS__saveState() {
195     try {
196       var file = this._tabletsFile;
198       var ostream = Cc['@mozilla.org/network/safe-file-output-stream;1']
199         .createInstance(Ci.nsIFileOutputStream);
200       ostream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, 0);
202       var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
203         .createInstance(Ci.nsIScriptableUnicodeConverter);
204       converter.charset = 'UTF-8';
206       var data = this._data.toSource();
207       var convdata = converter.ConvertFromUnicode(data) + converter.Finish();
209       ostream.write(convdata, convdata.length);
211       if (ostream instanceof Ci.nsISafeOutputStream) {
212         ostream.finish();
213       } else {
214         ostream.close();
215       }
216     }
217     catch (e) { }
218   },
219   _loadState: function TSFS__loadState() {
220     try { 
221       var file = this._tabletsFile;
223       var stream = Cc['@mozilla.org/network/file-input-stream;1']
224           .createInstance(Ci.nsIFileInputStream);
225       stream.init(file, PR_RDONLY, 0, 0);
227       var cvstream = Cc['@mozilla.org/intl/converter-input-stream;1']
228         .createInstance(Ci.nsIConverterInputStream);
229       cvstream.init(stream, 'UTF-8', 1024,
230                     Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
232       var content = '';
233       var data = {};
234       while (cvstream.readString(4096, data)) {
235         content += data.value;
236       }
238       cvstream.close();
240       content = content.replace(/\r\n?/g, '\n');
242       var sandbox = new Cu.Sandbox('about:blank');
243       this._data = Cu.evalInSandbox(content, sandbox);
244     }
245     catch (e) {
246       this._data = {};
247     }
248   },
252 function TabletsService() {
253   var obs = getObserverService();
254   obs.addObserver(this, 'profile-after-change', false);
257 TabletsService.prototype = {
258   _init: function TB__init() {
259     this._storage = new TabletStorageFS();
260   },
261   observe: function TB_observe(subject, topic, state) {
262     var obs = getObserverService();
264     switch (topic) {
265       case 'profile-after-change':
266         obs.removeObserver(this, 'profile-after-change');
267         this._init();
268         break;
269     }
270   },
272   save: function TB_save(aDescription) {
273     var win, canvas;
274     [win, canvas] = snapshot();
276     var tabbrowser = win.getBrowser();
277     var selectedBrowser = tabbrowser.selectedBrowser;
279     var currentTab = -1;
280     var browsers = tabbrowser.browsers;
281     for (var i = 0; i < browsers.length; i++) {
282       if (browsers[i] == selectedBrowser)
283         currentTab = i;
284     }
286     if (currentTab == -1)
287       return false;
289     var ss = Cc['@mozilla.org/browser/sessionstore;1']
290       .getService(Ci.nsISessionStore);
291     var winJSON = "(" + ss.getWindowState(win) + ")";
293     var sandbox = new Cu.Sandbox('about:blank');
294     var winState = Cu.evalInSandbox(winJSON, sandbox);
296     var state = winState.windows[0].tabs[currentTab];
298     var previewData = canvas.toDataURL();
299     var preview = win.atob(previewData.substr('data:image/png;base64,'.length));
301     var url = selectedBrowser.currentURI.spec;
303     this._storage.save(url, state, preview);
305     return true;
306   },
307   delete: function TB_delete(aURL) {
308     this._storage.delete(aURL);
309   },
310   open: function TB_open(aURL, aWhere) {
311   },
312   getTablets: function TB_getTablets() {
313     var urls = this._storage.getURLs();
315     var enumerator = {
316       _urls: urls,
317       _storage: this._storage,
318       getNext: function() {
319         var url = this._urls.shift();
321         var data, imageURL;
322         [data, imageURL] = this._storage.retrieve(url);
324         var tab = new TabletInfo();
325         tab.url = url;
326         tab.title = data.entries[data.index - 1].title;
327         tab.imageURL = imageURL;
328         return tab;
329       },
330       hasMoreElements: function() {
331         return this._urls.length > 0;
332       }
333     }
335     return enumerator;
336   },
338   getInterfaces: function TB_getInterfaces(countRef) {
339     var interfaces = [Ci.oyITablets, Ci.nsIObserver, Ci.nsISupports];
340     countRef.value = interfaces.length;
341     return interfaces;
342   },
343   getHelperForLanguage: function TB_getHelperForLanguage(language) {
344     return null;
345   },
346   contractID: TB_CONTRACTID,
347   classDescription: TB_CLASSNAME,
348   classID: TB_CLASSID,
349   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
350   flags: Ci.nsIClassInfo.SINGLETON,
352   QueryInterface: function TB_QueryInterface(iid) {
353     if (iid.equals(Ci.oyITablets) ||
354         iid.equals(Ci.nsIObserver) ||
355         iid.equals(Ci.nsISupports))
356       return this;
357     throw Cr.NS_ERROR_NO_INTERFACE;
358   }
361 function GenericComponentFactory(ctor) {
362   this._ctor = ctor;
365 GenericComponentFactory.prototype = {
367   _ctor: null,
369   // nsIFactory
370   createInstance: function(outer, iid) {
371     if (outer != null)
372       throw Cr.NS_ERROR_NO_AGGREGATION;
373     return (new this._ctor()).QueryInterface(iid);
374   },
376   // nsISupports
377   QueryInterface: function(iid) {
378     if (iid.equals(Ci.nsIFactory) ||
379         iid.equals(Ci.nsISupports))
380       return this;
381     throw Cr.NS_ERROR_NO_INTERFACE;
382   },
385 var Module = {
386   QueryInterface: function(iid) {
387     if (iid.equals(Ci.nsIModule) ||
388         iid.equals(Ci.nsISupports))
389       return this;
391     throw Cr.NS_ERROR_NO_INTERFACE;
392   },
394   getClassObject: function(cm, cid, iid) {
395     if (!iid.equals(Ci.nsIFactory))
396       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
398     if (cid.equals(TB_CLASSID))
399       return new GenericComponentFactory(TabletsService)
401     throw Cr.NS_ERROR_NO_INTERFACE;
402   },
404   registerSelf: function(cm, file, location, type) {
405     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
406     cr.registerFactoryLocation(TB_CLASSID, TB_CLASSNAME, TB_CONTRACTID,
407                                file, location, type);
409     var catman = Cc['@mozilla.org/categorymanager;1']
410       .getService(Ci.nsICategoryManager);
411     catman.addCategoryEntry('app-startup', TB_CLASSNAME,
412                             'service,' + TB_CONTRACTID,
413                             true, true);
414   },
416   unregisterSelf: function(cm, location, type) {
417     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
418     cr.unregisterFactoryLocation(TB_CLASSID, location);
419   },
421   canUnload: function(cm) {
422     return true;
423   },
426 function NSGetModule(compMgr, fileSpec)
428   return Module;