From d72595a7974a308a7cb383005d382443f2d28295 Mon Sep 17 00:00:00 2001 From: ketmar Date: Mon, 2 Nov 2015 20:33:52 +0000 Subject: [PATCH] added alert notifications to package downloader FossilOrigin-Name: 965c806df627b0d4436fd2c0efee371df11b609071dda45f0a6706ccbde00e8e --- install.rdf | 2 +- main/modules/pkg/downpkg.js | 26 ++++++++++-- main/modules/pkg/packagedb.js | 63 ++++++++++++++++++++++++++- main/modules/pkg/pkgman.js | 35 +++++++++++++++ main/modules/signals.jsm | 99 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 main/modules/signals.jsm diff --git a/install.rdf b/install.rdf index 43f3074..550e0fd 100644 --- a/install.rdf +++ b/install.rdf @@ -2,7 +2,7 @@ guerilla@ketmar.no-ip.org - 0.0.3.6rc5 + 0.0.3.6.1 2 Guerilla Scripting Userscript injecting engine for power users. diff --git a/main/modules/pkg/downpkg.js b/main/modules/pkg/downpkg.js index 6b759b3..a51ecb7 100644 --- a/main/modules/pkg/downpkg.js +++ b/main/modules/pkg/downpkg.js @@ -13,6 +13,10 @@ let {parseMeta} = require("utils/metaparser"); //////////////////////////////////////////////////////////////////////////////// +Components.utils.import("chrome://guerilla-script-jscode/content/modules/signals.jsm"); + + +//////////////////////////////////////////////////////////////////////////////// function genFileObj (fname) { if (typeof(fname) !== "string") throw new Error("non-string file name in `genFileObj()`"); if (fname.length == 0) throw new Error("empty file name in `genFileObj()`"); @@ -150,6 +154,7 @@ PkgDownloader.prototype.getExtFromUrl = function (url) { // bool cancelled PkgDownloader.prototype.downloadFile = function (destfname, aurl, callback) { conlog("downloading: ", aurl); + emitSignal("package-downloader", {url:aurl, phase:"preparing-url"}); let ioSrv = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService) let done = false, cancelled = false; let fl = genFileObj(destfname); @@ -235,17 +240,27 @@ PkgDownloader.prototype.downCBMain = function (chan, success, filename, url, can //conlog("downCBMain: [", filename, "] : ", success, " : ", chan.responseStatus, " : ", chan.requestSucceeded); this.curdown = null; try { - if (!success || !chan.requestSucceeded) { this._abort(filename, cancelled); return; } // failed to download main file + if (!success || !chan.requestSucceeded) { + // failed to download main file + emitSignal("package-downloader", {url:this.mainURL, phase:"error-main-file"}); + this._abort(filename, cancelled); + return; + } // main file downloaded let ct = (chan.getResponseHeader("Content-Type")||""); if (!(/^text\/javascript(?:;|$)/.test(ct)) && !(/^text\/plain(?:;|$)/.test(ct))) { // invalid content type + emitSignal("package-downloader", {url:this.mainURL, phase:"error-main-content-type"}); this._abort(filename, cancelled); return; } // parse metadata let meta = parseMeta(fileReadText(genFileObj(filename))); - if (meta.length == 0) { this._abort(filename, cancelled); return; } + if (meta.length == 0) { + emitSignal("package-downloader", {url:this.mainURL, phase:"error-main-metadata"}); + this._abort(filename, cancelled); + return; + } this.pkg = {}; this.pkg.downurl = this.mainURL; this.pkg.meta = meta; @@ -302,6 +317,7 @@ PkgDownloader.prototype.downloadNextResource = function () { this.curdown = null; if (this.reslist.length == 0) { conlog("package download complete"); + emitSignal("package-downloader", {url:this.mainURL, phase:"complete"}); this.state = 3; try { this.onComplete(); } catch (e) { logException("DOWN", e); } return; @@ -311,7 +327,11 @@ PkgDownloader.prototype.downloadNextResource = function () { let res = this.reslist[0]; conlog("downloading resource '", res.name, "' from: ", res.url); let ext = this.getExtFromUrl(res.url); - if (!ext) { this._abort(filename, cancelled); return; } + if (!ext) { + emitSignal("package-downloader", {url:this.mainURL, phase:"error-resource-file"}); + this._abort(filename, cancelled); + return; + } this.curres = res; this.reslist = this.reslist.slice(1); let fname = this.genFileName(ext); diff --git a/main/modules/pkg/packagedb.js b/main/modules/pkg/packagedb.js index 66c043f..5f00f5b 100644 --- a/main/modules/pkg/packagedb.js +++ b/main/modules/pkg/packagedb.js @@ -8,6 +8,10 @@ * http://www.wtfpl.net/txt/copying/ for more details. */ //////////////////////////////////////////////////////////////////////////////// +function nowSeconds () Math.floor(Date.now()/1000); + + +//////////////////////////////////////////////////////////////////////////////// function PackageDB () { this.dbObj = null; return this; @@ -46,6 +50,14 @@ PackageDB.prototype.__defineGetter__("db", function () { ); this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packages_by_name ON packages (name);"); this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packages_by_name_and_state ON packages (name, state);"); + // i don't want to alter `packages` table, so... + this.dbObj.executeSimpleSQL( + "CREATE TABLE IF NOT EXISTS packagetimes (\n"+ + " pkgid INTEGER NOT NULL,\n"+ /* unique package id */ + " lastuptime INTEGER NOT NULL DEFAULT 0\n"+ /* last successfull update unixtime, in seconds */ + ");" + ); + this.dbObj.executeSimpleSQL("CREATE INDEX IF NOT EXISTS packageups_by_pkgid ON packagetimes (pkgid);"); // this.dbObj.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS resources (\n"+ @@ -115,6 +127,45 @@ PackageDB.prototype.getActivePackageByName = function (name) { }; +// -1: unknown +PackageDB.prototype.getPackageUpdateTimeById = function (pkgid) { + if (typeof(pkgid) !== "number") return -1; + let stmt = this.db.createStatement("SELECT lastuptime FROM packagetimes WHERE pkgid=:pid LIMIT 1"); + try { + stmt.params.pkgid = pkgid; + if (stmt.executeStep()) { + let res = stmt.row.lastuptime; + stmt.reset(); + return res; + } + } catch (e) {} + stmt.reset(); + return -1; +}; + + +// -1: unknown +PackageDB.prototype.getPackageUpdateTimeByName = function (name) { + let nfo = this.getActivePackageByName(name); + if (!nfo) return -1; + return this.getPackageUpdateTimeById(nfo.id); +}; + + +PackageDB.prototype.setPackageUpdateTimeById = function (pkgid, luptime) { + if (typeof(pkgid) !== "number") return false; + if (typeof(luptime) !== "number") return false; + let stmt = this.db.createStatement("UPDATE packagetimes SET lastuptime=:luptime WHERE pkgid=:pid"); + try { + stmt.params.pkgid = pkgid; + stmt.params.luptime = luptime; + stmt.execute(); + } catch (e) {} + stmt.reset(); + return true; +}; + + PackageDB.prototype.getActivePackages = function () { let res = new Array(); let stmt = this.db.createStatement("SELECT id,name,dirname,version,downurl FROM packages WHERE state=0 ORDER BY name ASC"); @@ -187,7 +238,14 @@ PackageDB.prototype.checkAndUpdate = function (pkgname, dnpkg, options) { let pi = getPkgInfo(); if (!options.forceUpdate && !options.ignoreVersion && pi) { - if (pi.version == dnpkg.version) return false; // no need to do anything + if (pi.version == dnpkg.version) { + // no need to do anything + if (!options.checkOnly) { + // update update time ;-) + this.setPackageUpdateTimeById(pi.id, nowSeconds()); + } + return false; + } } // need to update @@ -292,6 +350,8 @@ PackageDB.prototype.checkAndUpdate = function (pkgname, dnpkg, options) { } pdb.executeSimpleSQL("COMMIT;"); + // update update time ;-) + this.setPackageUpdateTimeById(pi.id, nowSeconds()); return true; } catch (e) { pdb.executeSimpleSQL("ROLLBACK;"); @@ -365,6 +425,7 @@ PackageDB.prototype.removePackage = function (name) { try { pdb.executeSimpleSQL("BEGIN TRANSACTION;"); pdb.executeSimpleSQL("DELETE FROM packages WHERE id="+pi.id); + pdb.executeSimpleSQL("DELETE FROM packagetimes WHERE pkgid="+pi.id); pdb.executeSimpleSQL("DELETE FROM resources WHERE pkgid="+pi.id); pdb.executeSimpleSQL("DELETE FROM requires WHERE pkgid="+pi.id); pdb.executeSimpleSQL("COMMIT;"); diff --git a/main/modules/pkg/pkgman.js b/main/modules/pkg/pkgman.js index edd8a67..b064f6a 100644 --- a/main/modules/pkg/pkgman.js +++ b/main/modules/pkg/pkgman.js @@ -8,6 +8,38 @@ * http://www.wtfpl.net/txt/copying/ for more details. */ //////////////////////////////////////////////////////////////////////////////// +Components.utils.import("chrome://guerilla-script-jscode/content/modules/signals.jsm"); +let asvc = Components.classes["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); + + +addSignalListener("package-downloader", function (signame, state) { + conlog("package-downloader: phase=", state.phase, "; url=", state.url); + + if (state.phase.substr(0, 6) === "error-") { + // show notification + asvc.showAlertNotification( + "chrome://guerilla-script-misc/content/icon.png", + "GuerillaScript package manager", + "DOWNLOAD FAILED! ("+state.phase+")\n"+state.url, + false + ); + //error-main-file + //error-main-content-type + //error-main-metadata + //error-resource-file + } else if (state.phase === "complete") { + // show notification + asvc.showAlertNotification( + "chrome://guerilla-script-misc/content/icon.png", + "GuerillaScript package manager", + "DOWNLOAD COMPLETE!\n"+state.url, + false + ); + } +}); + + +//////////////////////////////////////////////////////////////////////////////// let dldr = null; let lastUpdateCount = 0; let pkgList = {lmod:-1}; @@ -82,11 +114,13 @@ function pingQueue () { dldr.clearTempDir(); dldr.onError = function () { /*++lastUpdateCount;*/ + logError("package '", qo.name, "': download failed!"); dldr = null; pingQueue(); }; dldr.onCancel = function () { /*++lastUpdateCount;*/ + logError("package '", qo.name, "': download cancelled!"); dldr = null; pingQueue(); }; @@ -101,6 +135,7 @@ function pingQueue () { pingQueue(); }; dldr.onMainReceived = function () { + conlog("package '", qo.name, "': received main file"); let res = pkgDB.checkAndUpdate(qo.name, this.pkg, {checkOnly:true}); if (!res) conlog("package '", qo.name, "' is up-to-date"); return res; diff --git a/main/modules/signals.jsm b/main/modules/signals.jsm new file mode 100644 index 0000000..34b0bfa --- /dev/null +++ b/main/modules/signals.jsm @@ -0,0 +1,99 @@ +/* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar) + * Understanding is not required. Only obedience. + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://www.wtfpl.net/txt/copying/ for more details. + */ +//////////////////////////////////////////////////////////////////////////////// +this.EXPORTED_SYMBOLS = [ + "addSignalListener", + "removeSignalListener", + "emitSignal", + "signalNamePrefix" +]; + +//////////////////////////////////////////////////////////////////////////////// +const {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components; + + +function genUUID () { + let uuidgen = Cc["@mozilla.org/uuid-generator;1"].createInstance(Ci.nsIUUIDGenerator); + if (!uuidgen) throw new Error("no UUID generator available!"); + return uuidgen.generateUUID().toString().replace(/[^-a-z0-9]/ig, ""); +} + + +//////////////////////////////////////////////////////////////////////////////// +const signalNamePrefix = "signal-"+genUUID()+"-"; +const obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + + +//////////////////////////////////////////////////////////////////////////////// +// string key: name +// object value: +// object observer +// array[] callbacks +let observers = {}; + +function addSignalListener (name, cback) { + if (typeof(name) !== "string" || !name) throw new Error("invalid signal name"); + if (typeof(cback) !== "function") throw new Error("callback function expected"); + // check if already here + let nfo = observers[name]; + if (nfo !== undefined) { + for (let cb of nfo.callbacks) if (cb === cback) return; // nothing to do + } else { + // no observer, create one + nfo = {callbacks:[]}; + nfo.observer = { + observe: function (subject, topic, data) { + topic = topic.substr(signalNamePrefix.length); // remove prefix + if (data && data.length) { + try { data = JSON.parse(data); } catch (e) { Cu.reportError(e); return; } + } else { + data = null; + } + for (let cb of nfo.callbacks) { + try { cb(topic, data); } catch (e) { Cu.reportError(e); } + } + }, + }; + obs.addObserver(nfo.observer, signalNamePrefix+name, false); + observers[name] = nfo; + } + nfo.callbacks.push(cback); +} + + +function removeSignalListener (name, cback) { + if (typeof(name) !== "string" || !name) throw new Error("invalid signal name"); + if (typeof(cback) !== "function") throw new Error("callback function expected"); + // find observer + let nfo = observers[name]; + if (nfo !== undefined) { + for (let [idx, cb] of Iterator(nfo.callbacks)) { + if (cb === cback) { + // remove callback + nfo.callbacks.splice(idx, 1); + // if there's no more callbacks, remove observer + if (nfo.callbacks.length === 0) { + conlog("removing observer for '", signalNamePrefix+name, "'"); + obs.removeObserver(nfo.observer, signalNamePrefix+name); + delete observers[name]; + } + return; + } + } + } +} + + +function emitSignal (name, data) { + if (typeof(name) !== "string" || !name) throw new Error("invalid signal name"); + if (!(name in observers)) return; // no listeners -- no need to send anything + data = (typeof(data) === "undefined" ? null : (data !== null ? JSON.stringify(data) : null)); + obs.notifyObservers(null, signalNamePrefix+name, data); +} -- 2.11.4.GIT