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 //conlog("** package db dir: [", this.dbFile.path, "]");
27 //this.dbFile.append("database");
28 if (!this.dbFile
.exists()) this.dbFile
.create(this.dbFile
.DIRECTORY_TYPE
, 0700);
29 this.dbFile
.append("packages.db");
30 //conlog("package db: [", this.dbFile.path, "]");
32 this.dbObj
= Services
.storage
.openDatabase(this.dbFile
);
33 // the auto_vacuum pragma has to be set before the table is created
34 this.dbObj
.executeSimpleSQL("PRAGMA auto_vacuum = NONE;");
35 this.dbObj
.executeSimpleSQL("PRAGMA journal_mode = DELETE;");
37 this.dbObj
.executeSimpleSQL(
38 "CREATE TABLE IF NOT EXISTS packages (\n"+
39 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"+ /* unique package id */
40 " name TEXT NOT NULL UNIQUE,\n"+ /* package name */
41 " dirname TEXT NOT NULL UNIQUE,\n"+ /* package dir name, valid disk file name inside package dir */
42 " version TEXT NOT NULL,\n"+ /* package version */
43 " downurl TEXT NOT NULL,\n"+ /* package update url */
44 " state INTEGER NOT NULL DEFAULT 0\n"+ /* see PackageDB.STATE_* */
47 this.dbObj
.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packages_by_name ON packages (name);");
48 this.dbObj
.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packages_by_name_and_state ON packages (name, state);");
50 this.dbObj
.executeSimpleSQL(
51 "CREATE TABLE IF NOT EXISTS resources (\n"+
52 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"+ /* unique file id */
53 " pkgid INTEGER NOT NULL,\n"+ /* unique package id */
54 " url TEXT NOT NULL UNIQUE,\n"+ /* file url */
55 " diskname TEXT NOT NULL,\n"+ /* disk file name; ""' means "main package file" */
56 " sha512 TEXT NOT NULL,\n"+ /* sha-512 of the disk file */
57 " resname TEXT NOT NULL DEFAULT '',\n"+ /* resource name for named resources */
58 " contenttype TEXT NOT NULL DEFAULT 'application/octet-stream'\n"+ /* resource content type */
61 this.dbObj
.executeSimpleSQL("CREATE INDEX IF NOT EXISTS rsrcs_by_pkgid ON resources (pkgid);");
62 this.dbObj
.executeSimpleSQL("CREATE INDEX IF NOT EXISTS rsrcs_by_pkgid_and_name ON resources (pkgid, resname);");
64 this.dbObj
.executeSimpleSQL(
65 "CREATE TABLE IF NOT EXISTS requires (\n"+
66 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"+ /* unique file id; first one is main js file */
67 " pkgid INTEGER NOT NULL,\n"+ /* unique package id */
68 " url TEXT NOT NULL UNIQUE,\n"+ /* file url */
69 " diskname TEXT NOT NULL,\n"+ /* disk file name */
70 " sha512 TEXT NOT NULL\n"+ /* sha-512 of the disk file */
73 this.dbObj
.executeSimpleSQL("CREATE INDEX IF NOT EXISTS reqs_by_pkgid ON requires (pkgid);");
74 this.dbObj
.executeSimpleSQL("CREATE INDEX IF NOT EXISTS reqs_by_pkgid_and_id ON requires (pkgid, id);");
75 // run vacuum once manually to switch to the correct auto_vacuum mode for
76 // databases that were created with incorrect auto_vacuum
77 this.dbObj
.executeSimpleSQL("VACUUM;");
83 PackageDB
.prototype.__defineGetter__("opened", function () {
84 return (this.dbObj
!= null);
88 PackageDB
.prototype.close = function () {
97 PackageDB
.prototype.getActivePackageByName = function (name
) {
98 if (typeof(name
) !== "string" || !name
) return null;
99 let stmt
= this.db
.createStatement("SELECT id,name,dirname,version,downurl FROM packages WHERE state=0 AND name=:name LIMIT 1");
101 stmt
.params
.name
= name
;
102 if (stmt
.executeStep()) {
104 res
.id
= stmt
.row
.id
;
105 res
.name
= stmt
.row
.name
;
106 res
.dirname
= stmt
.row
.dirname
;
107 res
.version
= stmt
.row
.version
;
108 res
.url
= stmt
.row
.downurl
;
118 PackageDB
.prototype.getActivePackages = function () {
119 let res
= new Array();
120 let stmt
= this.db
.createStatement("SELECT id,name,dirname,version,downurl FROM packages WHERE state=0 ORDER BY name ASC");
122 while (stmt
.executeStep()) {
125 rr
.name
= stmt
.row
.name
;
126 rr
.dirname
= stmt
.row
.dirname
;
127 rr
.version
= stmt
.row
.version
;
128 rr
.url
= stmt
.row
.downurl
;
139 PackageDB
.prototype.getPackageJSFiles = function (pkgid
) {
140 let res
= new Array();
141 let stmt
= this.db
.createStatement("SELECT diskname FROM requires WHERE pkgid=:pkgid ORDER BY id ASC");
143 stmt
.params
.pkgid
= pkgid
;
144 while (stmt
.executeStep()) {
145 res
.push(stmt
.row
.diskname
);
155 ////////////////////////////////////////////////////////////////////////////////
156 // normalize string, so it can be used as disk file name
157 function normDiskStr (s
) {
158 const validRE
= /^[-A-Za-z_0-9.]$/;
161 if (!validRE
.test(ch
)) ch
= "_";
164 if (res
.length
&& res
[0] == "_") res
= "pkg"+res
;
171 // bool ignoreVersion
173 // returns true if package should be updated
174 PackageDB
.prototype.checkAndUpdate = function (pkgname
, dnpkg
, options
) {
175 if (typeof(pkgname
) !== "string" || !pkgname
) throw new Error("invalid package name");
176 if (typeof(dnpkg
) !== "object") throw new Error("package info object expected");
177 if (typeof(options
) !== "object") options
= {};
181 function getPkgInfo () {
182 let stmt
= pdb
.createStatement("SELECT id,version,dirname FROM packages WHERE name=:name LIMIT 1");
183 stmt
.params
.name
= pkgname
;
184 if (stmt
.step()) return {id
:stmt
.row
.id
, version
:stmt
.row
.version
, dirname
:stmt
.row
.dirname
};
188 let pi
= getPkgInfo();
189 if (!options
.forceUpdate
&& !options
.ignoreVersion
&& pi
) {
190 if (pi
.version
== dnpkg
.version
) return false; // no need to do anything
194 if (options
.checkOnly
) return true;
197 pdb
.executeSimpleSQL("BEGIN TRANSACTION;");
199 // build package directory, and create it if necessary
200 let pkdir
= getUserPkgDir();
202 // we want new directory
204 pi
.version
= ""+dnpkg
.version
;
205 pi
.dirname
= normDiskStr(pkgname
);
206 pkdir
.append(pi
.dirname
);
207 pkdir
.createUnique(pkdir
.DIRECTORY_TYPE
, 0700);
208 pi
.dirname
= pkdir
.leafName
;
209 // create database record, so we can get package id from it
211 let stmt
= pdb
.createStatement("INSERT OR REPLACE INTO packages (name, dirname, version, downurl, state) VALUES (:name, :dirname, :version, :downurl, 0)");
213 stmt
.params
.name
= pkgname
;
214 stmt
.params
.dirname
= pi
.dirname
;
215 stmt
.params
.version
= pi
.version
;
216 stmt
.params
.downurl
= dnpkg
.downurl
;
223 if (!pi
) throw new Error("something is wrong with package database");
225 // we want existing directory
226 pkdir
.append(pi
.dirname
);
227 if (pkdir
.exists() && !pkdir
.isDirectory()) throw new Error("something is wrong with package storage");
230 // clear package directory
231 try { pkdir
.remove(true); } catch (e
) {}
232 try { if (!pkdir
.exists()) pkdir
.create(pkdir
.DIRECTORY_TYPE
, 0700); } catch (e
) {}
234 function copyFile (srcpath
) {
235 let fl
= Cc
["@mozilla.org/file/local;1"].createInstance(Ci
.nsILocalFile
);
236 fl
.initWithPath(srcpath
);
237 //conlog("copying ["+srcpath+"] to ["+pkdir.path+"/]");
238 fl
.copyTo(pkdir
, fl
.leafName
);
239 let nn
= pkdir
.clone();
240 nn
.append(fl
.leafName
);
244 //TODO: make this async?
245 // copy package files
246 dnpkg
.diskname
= copyFile(dnpkg
.diskname
);
247 for (let res
of dnpkg
.resources
) res
.diskname
= copyFile(res
.diskname
);
250 // ah, id is always integer here
251 pdb
.executeSimpleSQL("DELETE FROM resources WHERE pkgid="+pi
.id
);
252 pdb
.executeSimpleSQL("DELETE FROM requires WHERE pkgid="+pi
.id
);
256 let stmt
= pdb
.createStatement("INSERT OR REPLACE INTO requires (pkgid, url, diskname, sha512) VALUES (:pkgid, :url, :diskname, :sha512)");
258 stmt
.params
.pkgid
= pi
.id
;
259 stmt
.params
.url
= dnpkg
.downurl
;
260 stmt
.params
.diskname
= dnpkg
.diskname
;
261 stmt
.params
.sha512
= dnpkg
.sha512
;
264 // insert other js files
265 for (let res
of dnpkg
.resources
) {
266 if (res
.resname
!== null) continue;
268 stmt
.params
.pkgid
= pi
.id
;
269 stmt
.params
.url
= res
.url
;
270 stmt
.params
.diskname
= res
.diskname
;
271 stmt
.params
.sha512
= res
.sha512
;
279 let stmt
= pdb
.createStatement("INSERT OR REPLACE INTO resources (pkgid, url, diskname, sha512, resname, contenttype) VALUES (:pkgid, :url, :diskname, :sha512, :resname, :ct)");
280 for (let res
of dnpkg
.resources
) {
281 if (res
.resname
=== null) continue;
283 stmt
.params
.pkgid
= pi
.id
;
284 stmt
.params
.url
= res
.url
;
285 stmt
.params
.diskname
= res
.diskname
;
286 stmt
.params
.sha512
= res
.sha512
;
287 stmt
.params
.resname
= res
.resname
;
288 stmt
.params
.ct
= res
.contenttype
||"application/octet-stream";
294 pdb
.executeSimpleSQL("COMMIT;");
297 pdb
.executeSimpleSQL("ROLLBACK;");
303 ////////////////////////////////////////////////////////////////////////////////
304 // this returns array of xnfos:
305 // string path: path to main script
306 // string[] reqs: list of required files
307 // {string name} rsrc: resources dict, keyed by resource name, value is string path
308 PackageDB
.prototype.getActivePackagesForCache = function () {
309 let res
= new Array();
313 function getPackageIds () {
314 let stmt
= pdb
.createStatement("SELECT id FROM packages WHERE state=0 ORDER BY name ASC");
316 while (stmt
.executeStep()) res
.push({id
:stmt
.row
.id
});
324 //conlog("packages: "+res.length);
326 let reqmt
= pdb
.createStatement("SELECT diskname FROM requires WHERE pkgid=:pkgid ORDER BY id ASC");
327 let resmt
= pdb
.createStatement("SELECT diskname,resname,contenttype FROM resources WHERE pkgid=:pkgid");
328 for (let pi
of res
) {
329 //conlog("package: "+pi.id);
332 reqmt
.params
.pkgid
= pi
.id
;
333 while (reqmt
.executeStep()) {
334 //conlog("req: ", reqmt.row.diskname);
336 pi
.path
= reqmt
.row
.diskname
;
337 pi
.reqs
= new Array();
339 pi
.reqs
.push(reqmt
.row
.diskname
);
345 resmt
.params
.pkgid
= pi
.id
;
347 while (resmt
.executeStep()) {
348 //conlog("res: ", resmt.row.resname, " : ", resmt.row.diskname);
349 pi
.rsrc
[resmt
.row
.resname
] = {path
:resmt
.row
.diskname
, contenttype
:resmt
.row
.contenttype
};
359 ////////////////////////////////////////////////////////////////////////////////
360 PackageDB
.prototype.removePackage = function (name
) {
361 if (typeof(name
) !== "string" || !name
) throw new Error("invalid package name");
362 let pi
= this.getActivePackageByName(name
);
363 if (!pi
) throw new Error("unknown package '"+name
+"'");
366 pdb
.executeSimpleSQL("BEGIN TRANSACTION;");
367 pdb
.executeSimpleSQL("DELETE FROM packages WHERE id="+pi
.id
);
368 pdb
.executeSimpleSQL("DELETE FROM resources WHERE pkgid="+pi
.id
);
369 pdb
.executeSimpleSQL("DELETE FROM requires WHERE pkgid="+pi
.id
);
370 pdb
.executeSimpleSQL("COMMIT;");
371 // clear package directory
373 pdb
.executeSimpleSQL("ROLLBACK;");
376 let pkdir
= getUserPkgDir();
377 pkdir
.append(pi
.dirname
);
378 if (pkdir
.exists() && pkdir
.isDirectory()) {
379 try { pkdir
.remove(true); } catch (e
) {}
384 ////////////////////////////////////////////////////////////////////////////////
385 exports
.PackageDB
= PackageDB
;