3

I'm trying to copy a sqlite database from the data folder in my extension directory, to the profile folder, in order to use it.

So for now, I'm trying with that:

const {Cc, Ci, Cu} = require("chrome"); const {NetUtils} = Cu.import("resource://gre/modules/NetUtil.jsm"); const data = require('sdk/self').data; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); var file = Cc["@mozilla.org/file/directory_service;1"]. getService(Ci.nsIProperties). get("TmpD", Ci.nsIFile); file.append("searchEngines.sqlite"); file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); // Then, we need an output stream to our output file. var ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); ostream.init(file, -1, -1, 0); // Finally, we need an input stream to take data from. var iStreamData = NetUtil.ioService.newChannel(data.url("searchEngines.sqlite"), null, null).open(); let istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); istream.setData(iStreamData, iStreamData.length); NetUtil.asyncCopy(istream, ostream, function(aResult) { console.log(aResult); // return 0 }) console.log(FileUtils.getFile("ProfD", ["searchEngines.sqlite"]).exists()); // return false let dbConn = Services.storage.openDatabase(file); 

The file seems to exist since the console.log(file.exists()) return FALSE and is not populated (the console.log(aResult) return 0).

Where is my mistake, and is there a better way to do that?

3
  • You define file twice. I assume this is an error, and only the latter TmpD instance is to be considered? Commented Jul 20, 2014 at 12:27
  • Well, you're right, that's why the file.exists() return true. It's an error after several copy/paste. Should I edit my question? Commented Jul 20, 2014 at 12:34
  • Please do edit your question with corrected code. Commented Jul 20, 2014 at 12:35

2 Answers 2

3

Besides that it uses sync I/O (opening the channel with .open instead of .asyncOpen), the NetUtil.asyncCopy operation is still async, meaning the code

NetUtil.asyncCopy(istream, ostream, function(aResult) { console.log(aResult); // return 0 }) console.log(FileUtils.getFile("ProfD", ["searchEngines.sqlite"]).exists()); // return false let dbConn = Services.storage.openDatabase(file); 

will try to open the file before the copy likely finishes! However, file.exists() will be likely true, because you already opened the file for writing. It's just that the file is still blank because the data copy isn't done (or even started) yet. (Actually, it is true, because you're checking searchEngines.sqlite in ProfD and not TmpD, but if you correct that the previous statement would apply).

You can only use the file when/after your callback to .asyncCopy is done, e.g.

NetUtil.asyncCopy(istream, ostream, function(aResult) { console.log(aResult); console.log(FileUtils.getFile("ProfD", ["searchEngines.sqlite"]).exists()); // return false let dbConn = Services.storage.openDatabase(file); // ... }); 

PS: You might want to .asyncOpen the channel, then use NetUtil.asyncFetch and pass the resulting stream to .asyncCopy to be truly async for smallish files, since this caches the contents in memory first.

For large files you could create a variant of the NetUtil.asyncFetch implementation that feeds the .outputStream end directly to NetUtils.asyncCopy. That is a bit more complicated, so I won't be writing this up in detail until somebody is truly interested in this and ask the corresponding question.

Edit, so here is how I'd write it:

const data = require('sdk/self').data; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); function copyDataURLToFile(url, file, callback) { NetUtil.asyncFetch(url, function(istream) { var ostream = Cc["@mozilla.org/network/file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); ostream.init(file, -1, -1, Ci.nsIFileOutputStream.DEFER_OPEN); NetUtil.asyncCopy(istream, ostream, function(result) { callback && callback(file, result); }); }); } var file = Services.dirsvc.get("TmpD", Ci.nsIFile); file.append("searchEngines.sqlite"); copyDataURLToFile(data.url("searchEngine.sqlite"), file, function(file, result) { console.log(result); console.log(file.exists()); console.log(file.fileSize); }); 
Sign up to request clarification or add additional context in comments.

8 Comments

After my correction, the file.exists() return false every time (if I fire it in the asyncCopy(..) or not).
My database size will not be up to 700ko, is that to be considered as a large file ?
Added test code that works for me (well, I did only copy a stub file, so I did not open the thing as an sqlite DB...)
Thanks a lot, that is working for me ! However, is that writing the file in the profile folder, or just in a temporary folder ? I just need to copy the database in the profile folder the first time the extension is loaded.
Feel free to modify the file path as you please. Right now it uses TmpD, because that is what your question used. However, please note that messing with search engines (the filename suggests this ;) might violate the Add-on Guidelines and get your add-on blocklisted.
|
0

Try using OS.File it's much more straight forward.

Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/osfile.jsm") var fromPath = FileUtils.getFile("ProfD", ["searchEngines.sqlite"]).path; var toPath = FileUtils.getFile("TmpD", ["searchEngines.sqlite"]).path;; var promise = OS.File.copy(fromPath, toPath); var dbConn; promise.then( function(aStat) { alert('success will now open connection'); dbConn = Services.storage.openDatabase(toPath); }, function(aReason) { console.log('promise rejected', aReason); alert('copy failed, see console for details'); } ); 

3 Comments

Except that they read from a self.data.url() (second initialization of file = ), which in all likelihood is a resource: URI pointing into an XPI (i.e. underlying jar: URI)...
Oh, to read from xpi you can use NetUtils, is it only if its not compressed? I thought it's zipped so you have to use nsIZipReader no?
You can use jar: channels to create channels into any JAR/ZIP/XPI... And once you have a channel, you can read from it and decompression is performed transparent and as-required.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.