const Cc=Components.classes;const Ci=Components.interfaces;const Cr=Components.results;const Cu=Components.utils;const debugMode=false;const WRITE_DELAY_MS=(debugMode?3:30)*1000;const XULSTORE_CONTRACTID="@mozilla.org/xul/xulstore;1";const XULSTORE_CID=Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");const STOREDB_FILENAME="xulstore.json";Cu.import("resource://gre/modules/Services.jsm");Cu.import("resource://gre/modules/Task.jsm");Cu.import('resource://gre/modules/XPCOMUtils.jsm');XPCOMUtils.defineLazyModuleGetter(this,"NetUtil","resource://gre/modules/NetUtil.jsm");XPCOMUtils.defineLazyModuleGetter(this,"OS","resource://gre/modules/osfile.jsm");function XULStore(){if(!Services.appinfo.inSafeMode)
this.load();}
XULStore.prototype={classID:XULSTORE_CID,classInfo:XPCOMUtils.generateCI({classID:XULSTORE_CID,contractID:XULSTORE_CONTRACTID,classDescription:"XULStore",interfaces:[Ci.nsIXULStore]}),QueryInterface:XPCOMUtils.generateQI([Ci.nsIObserver,Ci.nsIXULStore,Ci.nsISupportsWeakReference]),_xpcom_factory:XPCOMUtils.generateSingletonFactory(XULStore),_data:{},_storeFile:null,_needsSaving:false,_saveAllowed:true,_writeTimer:Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),load:function(){Services.obs.addObserver(this,"profile-before-change",true);this._storeFile=Services.dirsvc.get("ProfD",Ci.nsIFile);this._storeFile.append(STOREDB_FILENAME);if(!this._storeFile.exists()){this.import();}else{this.readFile();}},observe:function(subject,topic,data){this.writeFile();if(topic=="profile-before-change"){this._saveAllowed=false;}},log:function(message){if(!debugMode)
return;dump("XULStore: "+message+"\n");Services.console.logStringMessage("XULStore: "+message);},import:function(){let localStoreFile=Services.dirsvc.get("ProfD",Ci.nsIFile);localStoreFile.append("localstore.rdf");if(!localStoreFile.exists()){return;}
const RDF=Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);const persistKey=RDF.GetResource("http://home.netscape.com/NC-rdf#persist");this.log("Import localstore from "+localStoreFile.path);let localStoreURI=Services.io.newFileURI(localStoreFile).spec;let localStore=RDF.GetDataSourceBlocking(localStoreURI);let resources=localStore.GetAllResources();while(resources.hasMoreElements()){let resource=resources.getNext().QueryInterface(Ci.nsIRDFResource);let uri;try{uri=NetUtil.newURI(resource.ValueUTF8);}catch(ex){continue;}
if(!uri.hasRef)
continue;let docURI=uri.specIgnoringRef;if(!localStore.HasAssertion(RDF.GetResource(docURI),persistKey,resource,true))
continue;let id=uri.ref;let attrs=localStore.ArcLabelsOut(resource);while(attrs.hasMoreElements()){let attr=attrs.getNext().QueryInterface(Ci.nsIRDFResource);let value=localStore.GetTarget(resource,attr,true);if(value instanceof Ci.nsIRDFLiteral){this.setValue(docURI,id,attr.ValueUTF8,value.Value);}}}},readFile:function(){const MODE_RDONLY=0x01;const FILE_PERMS=0600;let stream=Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);let json=Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);try{stream.init(this._storeFile,MODE_RDONLY,FILE_PERMS,0);this._data=json.decodeFromStream(stream,stream.available());}catch(e){this.log("Error reading JSON: "+e);}finally{stream.close();}},writeFile:Task.async(function*(){if(!this._needsSaving)
return;this._needsSaving=false;this.log("Writing to xulstore.json");try{let data=JSON.stringify(this._data);let encoder=new TextEncoder();data=encoder.encode(data);yield OS.File.writeAtomic(this._storeFile.path,data,{tmpPath:this._storeFile.path+".tmp"});}catch(e){this.log("Failed to write xulstore.json: "+e);throw e;}}),markAsChanged:function(){if(this._needsSaving||!this._storeFile)
return;this._needsSaving=true;this._writeTimer.init(this,WRITE_DELAY_MS,Ci.nsITimer.TYPE_ONE_SHOT);},setValue:function(docURI,id,attr,value){this.log("Saving "+attr+"="+value+" for id="+id+", doc="+docURI);if(!this._saveAllowed){Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");return;}
if(id.length>512||attr.length>512){throw Components.Exception("id or attribute name too long",Cr.NS_ERROR_ILLEGAL_VALUE);}
if(value.length>4096){Services.console.logStringMessage("XULStore: Warning, truncating long attribute value")
value=value.substr(0,4096);}
let obj=this._data;if(!(docURI in obj)){obj[docURI]={};}
obj=obj[docURI];if(!(id in obj)){obj[id]={};}
obj=obj[id];if(attr in obj&&obj[attr]==value)
return;obj[attr]=value;this.markAsChanged();},hasValue:function(docURI,id,attr){this.log("has store value for id="+id+", attr="+attr+", doc="+docURI);let ids=this._data[docURI];if(ids){let attrs=ids[id];if(attrs){return attr in attrs;}}
return false;},getValue:function(docURI,id,attr){this.log("get store value for id="+id+", attr="+attr+", doc="+docURI);let ids=this._data[docURI];if(ids){let attrs=ids[id];if(attrs){return attrs[attr]||"";}}
return"";},removeValue:function(docURI,id,attr){this.log("remove store value for id="+id+", attr="+attr+", doc="+docURI);if(!this._saveAllowed){Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");return;}
let ids=this._data[docURI];if(ids){let attrs=ids[id];if(attrs&&attr in attrs){delete attrs[attr];if(Object.getOwnPropertyNames(attrs).length==0){delete ids[id];if(Object.getOwnPropertyNames(ids).length==0){delete this._data[docURI];}}
this.markAsChanged();}}},getIDsEnumerator:function(docURI){this.log("Getting ID enumerator for doc="+docURI);if(!(docURI in this._data))
return new nsStringEnumerator([]);let result=[];let ids=this._data[docURI];if(ids){for(let id in this._data[docURI]){result.push(id);}}
return new nsStringEnumerator(result);},getAttributeEnumerator:function(docURI,id){this.log("Getting attribute enumerator for id="+id+", doc="+docURI);if(!(docURI in this._data)||!(id in this._data[docURI]))
return new nsStringEnumerator([]);let attrs=[];for(let attr in this._data[docURI][id]){attrs.push(attr);}
return new nsStringEnumerator(attrs);}};function nsStringEnumerator(items){this._items=items;}
nsStringEnumerator.prototype={QueryInterface:XPCOMUtils.generateQI([Ci.nsIStringEnumerator]),_nextIndex:0,hasMore:function(){return this._nextIndex<this._items.length;},getNext:function(){if(!this.hasMore())
throw Cr.NS_ERROR_NOT_AVAILABLE;return this._items[this._nextIndex++];},};this.NSGetFactory=XPCOMUtils.generateNSGetFactory([XULStore]);