1 /* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software. It comes without any warranty, to
5 * the extent permitted by applicable law. You can redistribute it
6 * and/or modify it under the terms of the Do What The Fuck You Want
7 * To Public License, Version 2, as published by Sam Hocevar. See
8 * http://www.wtfpl.net/txt/copying/ for more details.
10 ////////////////////////////////////////////////////////////////////////////////
11 function PackageDB () {
17 PackageDB.prototype.STATE_OK = 0;
18 PackageDB.prototype.STATE_INACTIVE = 1;
19 PackageDB.prototype.STATE_DISABLED = 2; // due to some errors
22 PackageDB.prototype.__defineGetter__("db", function () {
23 if (this.dbObj == null) {
25 this.dbFile = getUserPkgDBDir();
26 //this.dbFile.append("database");
27 if (!this.dbFile.exists()) this.dbFile.create(this.dbFile.DIRECTORY_TYPE, 0700);
28 this.dbFile.append("packages.db");
30 this.dbObj = Services.storage.openDatabase(this.dbFile);
31 // the auto_vacuum pragma has to be set before the table is created
32 this.dbObj.executeSimpleSQL("PRAGMA auto_vacuum = NONE;");
33 this.dbObj.executeSimpleSQL("PRAGMA journal_mode = DELETE;");
35 this.dbObj.executeSimpleSQL(
36 "CREATE TABLE IF NOT EXISTS packages (\n"+
37 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"+ /* unique package id */
38 " name TEXT NOT NULL UNIQUE,\n"+ /* package name */
39 " dirname TEXT NOT NULL UNIQUE,\n"+ /* package dir name, valid disk file name inside package dir */
40 " version TEXT NOT NULL,\n"+ /* package version */
41 " downurl TEXT NOT NULL,\n"+ /* package update url */
42 " state INTEGER NOT NULL DEFAULT 0\n"+ /* see PackageDB.STATE_* */
45 this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packages_by_name ON packages (name);");
46 this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packages_by_name_and_state ON packages (name, state);");
48 this.dbObj.executeSimpleSQL(
49 "CREATE TABLE IF NOT EXISTS resources (\n"+
50 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"+ /* unique file id */
51 " pkgid INTEGER NOT NULL,\n"+ /* unique package id */
52 " url TEXT NOT NULL UNIQUE,\n"+ /* file url */
53 " diskname TEXT NOT NULL,\n"+ /* disk file name; ""' means "main package file" */
54 " sha512 TEXT NOT NULL,\n"+ /* sha-512 of the disk file */
55 " resname TEXT NOT NULL DEFAULT ''\n"+ /* resource name for named resources */
58 this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS rsrcs_by_pkgid ON resources (pkgid);");
59 this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS rsrcs_by_pkgid_and_name ON resources (pkgid, resname);");
61 this.dbObj.executeSimpleSQL(
62 "CREATE TABLE IF NOT EXISTS requires (\n"+
63 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"+ /* unique file id; first one is main js file */
64 " pkgid INTEGER NOT NULL,\n"+ /* unique package id */
65 " url TEXT NOT NULL UNIQUE,\n"+ /* file url */
66 " diskname TEXT NOT NULL,\n"+ /* disk file name */
67 " sha512 TEXT NOT NULL\n"+ /* sha-512 of the disk file */
70 this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS reqs_by_pkgid ON requires (pkgid);");
71 this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS reqs_by_pkgid_and_id ON requires (pkgid, id);");
72 // run vacuum once manually to switch to the correct auto_vacuum mode for
73 // databases that were created with incorrect auto_vacuum
74 this.dbObj.executeSimpleSQL("VACUUM;");
80 PackageDB.prototype.__defineGetter__("opened", function () {
81 return (this.dbObj != null);
85 PackageDB.prototype.close = function () {
94 PackageDB.prototype.getActivePackageByName = function (name) {
95 if (typeof(name) !== "string" || !name) return null;
96 let stmt = this.db.createStatement("SELECT id,name,dirname,version,downurl FROM packages WHERE state=0 AND name=:name LIMIT 1");
98 stmt.params.name = name;
99 if (stmt.executeStep()) {
101 res.id = stmt.row.id;
102 res.name = stmt.row.name;
103 res.dirname = stmt.row.dirname;
104 res.version = stmt.row.version;
105 res.url = stmt.row.downurl;
115 PackageDB.prototype.getActivePackages = function () {
116 let res = new Array();
117 let stmt = this.db.createStatement("SELECT id,name,version,downurl FROM packages WHERE state=0 ORDER BY name ASC");
119 while (stmt.executeStep()) {
122 rr.name = stmt.row.name;
123 rr.version = stmt.row.version;
124 rr.url = stmt.row.downurl;
135 PackageDB.prototype.getPackageJSFiles = function (pkgid) {
136 let res = new Array();
137 let stmt = this.db.createStatement("SELECT diskname FROM requires WHERE pkgid=:pkgid ORDER BY id ASC");
139 stmt.params.pkgid = pkgid;
140 while (stmt.executeStep()) {
141 res.push(stmt.row.diskname);
151 ////////////////////////////////////////////////////////////////////////////////
152 // normalize string, so it can be used as disk file name
153 function normDiskStr (s) {
154 const validRE = /^[-A-Za-z_0-9.]$/;
157 if (!validRE.test(ch)) ch = "_";
160 if (res.length && res[0] == "_") res = "pkg"+res;
167 // bool ignoreVersion
169 // returns true if package should be updated
170 PackageDB.prototype.checkAndUpdate = function (pkgname, dnpkg, options) {
171 if (typeof(pkgname) !== "string" || !pkgname) throw new Error("invalid package name");
172 if (typeof(dnpkg) !== "object") throw new Error("package info object expected");
173 if (typeof(options) !== "object") options = {};
177 function getPkgInfo () {
178 let stmt = pdb.createStatement("SELECT id,version,dirname FROM packages WHERE name=:name LIMIT 1");
179 stmt.params.name = pkgname;
180 if (stmt.step()) return {id:stmt.row.id, version:stmt.row.version, dirname:stmt.row.dirname};
184 let pi = getPkgInfo();
185 if (!options.forceUpdate && !options.ignoreVersion && pi) {
186 if (pi.version == dnpkg.version) return false; // no need to do anything
190 if (options.checkOnly) return true;
193 pdb.executeSimpleSQL("BEGIN TRANSACTION;");
195 // build package directory, and create it if necessary
196 let pkdir = getUserPkgDir();
198 // we want new directory
200 pi.version = ""+dnpkg.version;
201 pi.dirname = normDiskStr(pkgname);
202 pkdir.append(pi.dirname);
203 pkdir.createUnique(pkdir.DIRECTORY_TYPE, 0700);
204 pi.dirname = pkdir.leafName;
205 // create database record, so we can get package id from it
207 let stmt = pdb.createStatement("INSERT OR REPLACE INTO packages (name, dirname, version, downurl, state) VALUES (:name, :dirname, :version, :downurl, 0)");
209 stmt.params.name = pkgname;
210 stmt.params.dirname = pi.dirname;
211 stmt.params.version = pi.version;
212 stmt.params.downurl = dnpkg.downurl;
219 if (!pi) throw new Error("something is wrong with package database");
221 // we want existing directory
222 pkdir.append(pi.dirname);
223 if (pkdir.exists() && !pkdir.isDirectory()) throw new Error("something is wrong with package storage");
226 // clear package directory
227 try { pkdir.remove(true); } catch (e) {}
228 try { if (!pkdir.exists()) pkdir.create(pkdir.DIRECTORY_TYPE, 0700); } catch (e) {}
230 function copyFile (srcpath) {
231 let fl = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
232 fl.initWithPath(srcpath);
233 //conlog("copying ["+srcpath+"] to ["+pkdir.path+"/]");
234 fl.copyTo(pkdir, fl.leafName);
235 let nn = pkdir.clone();
236 nn.append(fl.leafName);
240 //TODO: make this async?
241 // copy package files
242 dnpkg.diskname = copyFile(dnpkg.diskname);
243 for (let res of dnpkg.resources) res.diskname = copyFile(res.diskname);
246 // ah, id is always integer here
247 pdb.executeSimpleSQL("DELETE FROM resources WHERE pkgid="+pi.id);
248 pdb.executeSimpleSQL("DELETE FROM requires WHERE pkgid="+pi.id);
252 let stmt = pdb.createStatement("INSERT OR REPLACE INTO requires (pkgid, url, diskname, sha512) VALUES (:pkgid, :url, :diskname, :sha512)");
254 stmt.params.pkgid = pi.id;
255 stmt.params.url = dnpkg.downurl;
256 stmt.params.diskname = dnpkg.diskname;
257 stmt.params.sha512 = dnpkg.sha512;
260 // insert other js files
261 for (let res of dnpkg.resources) {
262 if (res.resname !== null) continue;
264 stmt.params.pkgid = pi.id;
265 stmt.params.url = res.url;
266 stmt.params.diskname = res.diskname;
267 stmt.params.sha512 = res.sha512;
275 let stmt = pdb.createStatement("INSERT OR REPLACE INTO resources (pkgid, url, diskname, sha512, resname) VALUES (:pkgid, :url, :diskname, :sha512, :resname)");
276 for (let res of dnpkg.resources) {
277 if (res.resname === null) continue;
279 stmt.params.pkgid = pi.id;
280 stmt.params.url = res.url;
281 stmt.params.diskname = res.diskname;
282 stmt.params.sha512 = res.sha512;
283 stmt.params.resname = res.resname;
289 pdb.executeSimpleSQL("COMMIT;");
292 pdb.executeSimpleSQL("ROLLBACK;");
298 ////////////////////////////////////////////////////////////////////////////////
299 // this returns array of xnfos:
300 // string path: path to main script
301 // string[] reqs: list of required files
302 // {string name} rsrc: resources dict, keyed by resource name, value is string path
303 PackageDB.prototype.getActivePackagesForCache = function () {
304 let res = new Array();
308 function getPackageIds () {
309 let stmt = pdb.createStatement("SELECT id FROM packages WHERE state=0 ORDER BY name ASC");
311 while (stmt.executeStep()) res.push({id:stmt.row.id});
319 //conlog("packages: "+res.length);
321 let reqmt = pdb.createStatement("SELECT diskname FROM requires WHERE pkgid=:pkgid ORDER BY id ASC");
322 let resmt = pdb.createStatement("SELECT diskname,resname FROM resources WHERE pkgid=:pkgid");
323 for (let pi of res) {
324 //conlog("package: "+pi.id);
327 reqmt.params.pkgid = pi.id;
328 while (reqmt.executeStep()) {
329 //conlog("req: ", reqmt.row.diskname);
331 pi.path = reqmt.row.diskname;
332 pi.reqs = new Array();
334 pi.reqs.push(reqmt.row.diskname);
340 resmt.params.pkgid = pi.id;
342 while (resmt.executeStep()) {
343 //conlog("res: ", resmt.row.resname, " : ", resmt.row.diskname);
344 pi.rsrc[resmt.row.resname] = resmt.row.diskname;
354 ////////////////////////////////////////////////////////////////////////////////
355 PackageDB.prototype.removePackage = function (name) {
356 if (typeof(name) !== "string" || !name) throw new Error("invalid package name");
357 let pi = this.getActivePackageByName(name);
358 if (!pi) throw new Error("unknown package '"+name+"'");
361 pdb.executeSimpleSQL("BEGIN TRANSACTION;");
362 pdb.executeSimpleSQL("DELETE FROM packages WHERE id="+pi.id);
363 pdb.executeSimpleSQL("DELETE FROM resources WHERE pkgid="+pi.id);
364 pdb.executeSimpleSQL("DELETE FROM requires WHERE pkgid="+pi.id);
365 pdb.executeSimpleSQL("COMMIT;");
366 // clear package directory
368 pdb.executeSimpleSQL("ROLLBACK;");
371 let pkdir = getUserPkgDir();
372 pkdir.append(pi.dirname);
373 if (pkdir.exists() && pkdir.isDirectory()) {
374 try { pkdir.remove(true); } catch (e) {}
379 ////////////////////////////////////////////////////////////////////////////////
380 exports.PackageDB = PackageDB;