1 /* See license.txt for terms of usage */
  2 
  3 // ************************************************************************************************
  4 // Constants
  5 
  6 const CLASS_ID = Components.ID("{9589DC0D-9709-4578-883E-D393452B3611}");
  7 const CLASS_NAME = "Firebug Annotation Service";
  8 const CONTRACT_ID = "@joehewitt.com/firebug-annotation-service;1";
  9 
 10 const Cc = Components.classes;
 11 const Ci = Components.interfaces;
 12 const Cr = Components.results;
 13 
 14 const dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
 15 
 16 // ************************************************************************************************
 17 // Annotaion service implementation
 18 
 19 var FBTrace = null;
 20 
 21 /**
 22  * @class Represents an internal Firebug annotation service. This service is used to
 23  * annotate sites with an info whether Firebug should be activated for them or not.
 24  */
 25 function AnnotationService()
 26 {
 27     this.wrappedJSObject = this;
 28 
 29     FBTrace = Cc["@joehewitt.com/firebug-trace-service;1"]
 30        .getService(Ci.nsISupports).wrappedJSObject.getTracer("extensions.firebug");
 31 
 32     // Get annotation file stored within the profile directory.
 33     this.file = dirService.get("ProfD", Ci.nsIFile);
 34     this.file.append("firebug");
 35     this.file.append("annotations.json");
 36 
 37     // Load annotaions.
 38     this.initialize();
 39 }
 40 
 41 AnnotationService.prototype =
 42 {
 43     annotations: [],
 44 
 45     setPageAnnotation: function(uri, value)
 46     {
 47         if (FBTrace.DBG_ANNOTATIONS)
 48             FBTrace.sysout("AnnotationService.setPageAnnotation; " + value + ", " + uri.spec);
 49 
 50         this.annotations[uri.spec] = value;
 51     },
 52 
 53     getPageAnnotation: function(uri)
 54     {
 55         return this.annotations[uri.spec];
 56     },
 57 
 58     pageHasAnnotation: function(uri)
 59     {
 60         return this.annotations[uri.spec] ? true : false;
 61     },
 62 
 63     removePageAnnotation: function(uri)
 64     {
 65         if (FBTrace.DBG_ANNOTATIONS)
 66             FBTrace.sysout("AnnotationService.removePageAnnotation; " + uri.spec);
 67 
 68         delete this.annotations[uri.spec];
 69     },
 70 
 71     getAnnotations: function()
 72     {
 73         return this.annotations;
 74     },
 75 
 76     clear: function()
 77     {
 78         this.annotations = [];
 79     },
 80 
 81     // Persistence
 82     initialize: function()
 83     {
 84         try
 85         {
 86             this.clear();
 87 
 88             if (!this.file.exists())
 89             {
 90                 this.file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
 91                 if (FBTrace.DBG_ANNOTATIONS)
 92                     FBTrace.sysout("AnnotationService.initialize; Annotaions file created " +
 93                         this.file.path);
 94                 return;
 95             }
 96 
 97             var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
 98                 .createInstance(Ci.nsIFileInputStream);
 99             var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
100                 .createInstance(Ci.nsIConverterInputStream);
101 
102             // Initialize input stream.
103             inputStream.init(this.file, 0x01 | 0x08, 0666, 0); // read, create
104             cstream.init(inputStream, "UTF-8", 0, 0);
105 
106             // Load annotations.
107             var data = {};
108             cstream.readString(-1, data);
109             if (!data.value.length)
110                 return;
111 
112             var nativeJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
113             var arr = nativeJSON.decode(data.value);
114             if (!arr)
115                 return;
116 
117             // Convert to map for faster lookup.
118             for (var i=0; i<arr.length; i++)
119                 this.annotations[arr[i].uri] = arr[i].value;
120 
121             if (FBTrace.DBG_ANNOTATIONS)
122                 FBTrace.sysout("AnnotationService.initialize; Annotations loaded from " +
123                     this.file.path, arr);
124         }
125         catch (err)
126         {
127             if (FBTrace.DBG_ERRORS || FBTrace.DBG_ANNOTATIONS)
128                 FBTrace.sysout("AnnotationService.initialize; EXCEPTION", err);
129         }
130     },
131 
132     flush: function()
133     {
134         try
135         {
136             // Initialize output stream.
137             var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
138                 .createInstance(Ci.nsIFileOutputStream);
139             outputStream.init(this.file, 0x02 | 0x08 | 0x20, 0666, 0); // write, create, truncate
140 
141             // Convert data to JSON.
142             var arr = [];
143             for (var uri in this.annotations)
144                 arr.push({
145                     uri: uri,
146                     value: this.annotations[uri]
147                 });
148 
149             var nativeJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
150             var jsonString = nativeJSON.encode(arr);
151 
152             // Store annotations.
153             outputStream.write(jsonString, jsonString.length);
154             outputStream.close();
155 
156             if (FBTrace.DBG_ANNOTATIONS)
157                 FBTrace.sysout("AnnotationService.initialize; Annotations stored to " +
158                     this.file.path, jsonString);
159         }
160         catch (err)
161         {
162             if (FBTrace.DBG_ERRORS || FBTrace.DBG_ANNOTATIONS)
163                 FBTrace.sysout("AnnotationService.flush; EXCEPTION", err);
164         }
165     },
166 
167     /* nsISupports */
168     QueryInterface: function(iid)
169     {
170         if (iid.equals(Ci.nsISupports))
171             return this;
172 
173         throw Cr.NS_ERROR_NO_INTERFACE;
174     }
175 };
176 
177 // ************************************************************************************************
178 // Service factory
179 
180 var gServiceSingleton = null;
181 var AnnotationsFactory =
182 {
183     createInstance: function (outer, iid)
184     {
185         if (outer != null)
186             throw Cr.NS_ERROR_NO_AGGREGATION;
187 
188         if (iid.equals(Ci.nsISupports))
189         {
190             if (!gServiceSingleton)
191                 gServiceSingleton = new AnnotationService();
192             return gServiceSingleton.QueryInterface(iid);
193         }
194 
195         throw Cr.NS_ERROR_NO_INTERFACE;
196     },
197 
198     QueryInterface: function(iid)
199     {
200         if (iid.equals(Ci.nsISupports) ||
201             iid.equals(Ci.nsISupportsWeakReference) ||
202             iid.equals(Ci.nsIFactory))
203             return this;
204 
205         throw Cr.NS_ERROR_NO_INTERFACE;
206     }
207 };
208 
209 // ************************************************************************************************
210 // Module implementation
211 
212 var AnnotationsModule =
213 {
214     registerSelf: function (compMgr, fileSpec, location, type)
215     {
216         compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
217         compMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME,
218             CONTRACT_ID, fileSpec, location, type);
219     },
220 
221     unregisterSelf: function(compMgr, fileSpec, location)
222     {
223         compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
224         compMgr.unregisterFactoryLocation(CLASS_ID, location);
225     },
226 
227     getClassObject: function (compMgr, cid, iid)
228     {
229         if (!iid.equals(Ci.nsIFactory))
230             throw Cr.NS_ERROR_NOT_IMPLEMENTED;
231 
232         if (cid.equals(CLASS_ID))
233             return AnnotationsFactory;
234 
235         throw Cr.NS_ERROR_NO_INTERFACE;
236     },
237 
238     canUnload: function(compMgr)
239     {
240         return true;
241     }
242 };
243 
244 // ************************************************************************************************
245 
246 function NSGetModule(compMgr, fileSpec)
247 {
248     return AnnotationsModule;
249 }
250