How to add “Auto-Update” function to Userscript

這篇主要來說明一下, VIL 的 Auto-Update 是怎麼實作的. 要實作Userscript 的 Auto-Update 功能, 要解決的問題有以下兩個:

  1. 如何比較遠端版本和已安裝的版本?
  2. 如何下載並更新本機的Userscript?

首先來看第一個問題. 在 greasemonkey userscript 中, 要取得遠端網站上的資訊可以透過 GM_xmlhttpRequest 來進行, 因此只要遠端的 server 可以提供版本資訊, 或者 Last-Modified, 我們便可以拿來做為判斷依據. 不過以 Last-Modified 來判斷, 比較容易產生誤差. 所幸 VIL 目前是放在 Opensvn上, 而 SVN 會在 HTTP Response Header 的 ETag 中包含 SVN revision number:

---response begin---
HTTP/1.1 200 OK
Date: Sat, 19 Apr 2008 06:24:47 GMT
Server: Apache
Last-Modified: Sat, 19 Apr 2008 05:38:26 GMT
ETag: "55//userscripts/view.image.links.user.js"
Accept-Ranges: bytes
Content-Length: 44055
Connection: close
Content-Type: text/plain
X-Pad: avoid browser bug

所以我們可以比較這個值即可. 但是另一個問題是, 要如何取得已安裝程式的版本呢? 我原本有想過利用 SVN Keyword 的方式, 自動 replace 程式中的 $Revision$ keyword, 不過後來發現 SVN 是由 client 來進行 Keyword replace, 所以使用 HTTP 方式取得的內容Keyword並不會被代換掉, 因此只好用 GM_getValue/GM_setValue 來處理了. 以下便是 check update 的程式碼片斷:

    if('200' != rspDtls.status){
        GM_log('check failed, response HTTP status: '+rspDtls.status);
        return;
    }else{
        var myrev = parseInt(GM_getValue(revtag,'0'));
        var remoterev = parseInt(rspDtls.responseHeaders.match(/Etag:\s+"([0-9]+).*"/)[1]);
        GM_log('remote rev is ' + remoterev + ', current rev is ' + myrev);
        if(myrev < remoterev){
            return updateFunction(rspDtls, remoterev);
        }else{
            if(showOptMessage){
                alert('remote rev is ' + remoterev + ', my rev is ' + myrev + ', no need to update.');
            }
            return true;
        }
    }

至於第二點, "如何下載及更新本機Userscript", 我一開始的想法是直接 window.open() 到 userscript 的 URL, Greasemonkey 便會自動出現安裝提示. 但後來發現, 這樣的安裝方法會清掉現有的 Include/Exclude page 設定, 所以便改用直接覆蓋已安裝檔案的方法. 而問題又來了: 要如何取得現有的安裝路徑及檔名呢? 這時候就要直接去挖 greasemonkey extension 的 source 了. 在 greasemonkey 的 util.js 中可以看到這一段:


function getScriptDir() {
  var dir = getNewScriptDir();

  if (dir.exists()) {
    return dir;
  } else {
    var oldDir = getOldScriptDir();
    if (oldDir.exists()) {
      return oldDir;
    } else {
      // if we called this function, we want a script dir.
      // but, at this branch, neither the old nor new exists, so create one
      return GM_createScriptsDir(dir);
    }
  }
}
function getNewScriptDir() {
  var file = Components.classes["@mozilla.org/file/directory_service;1"]
                       .getService(Components.interfaces.nsIProperties)
                       .get("ProfD", Components.interfaces.nsILocalFile);
  file.append("gm_scripts");
  return file;
}

function getOldScriptDir() {
  var file = getContentDir();
  file.append("scripts");
  return file;
}

直接 copy paste 到 VIL 中就 ok 了. 取得 script install dir, 再用 DOMParser 取得安裝後的檔名(修改自 greasemonkey 的 config.js):

function getInstalledFileName(){
    var installedFilename = '';
    var configContents = getContents(getScriptFileURI("config.xml"));
    var domParser = new DOMParser();
    var doc = domParser.parseFromString(configContents, "text/xml");
    var nodes = doc.evaluate("/UserScriptConfig/Script", doc, null, 0, null);
  for (var node = null; (node = nodes.iterateNext()); ) {
        var fname = node.getAttribute("filename");
    var name = node.getAttribute("name");
    var namespace = node.getAttribute("namespace");
        if(name == MY_NAME && namespace == MY_NAMESPACE){
            installedFilename = fname;
            break;
        }
  }
  return installedFilename;
}

之後就直接 overwite 掉現有的 userscript, 再 alert 提示使用者重新整理頁面即可.

解決這兩個問題之後, userscript 便可以享有 auto-update 的功能了. 有在寫 userscript 的朋友可以參考看看.

2 Responses to “How to add “Auto-Update” function to Userscript”


  1. 1 convil Jun 15th, 2008 at 01:15:17 UTC

    Hi Horance,

    Just a little bug report.

    I got the following error from auto-updating the scripts at diggirl:

    update failed! reason:Error: Permission denied to get property XPCComponents.classes

    I am using Firefox 3 RC2, greasemonkey 0.8.20080609 and refcontrol 0.8.11. I have already clicked “Allow” when it asked for permission.

    Also, although I have already set “signed.applets.codebase_principal_support” = true, I am getting the same “Permission denied” error from launching PicLens from VIL. The exception apparently was thrown from the following lines:

    // get home directory
    fileObj= Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get(“Home”, Components.interfaces.nsILocalFile);

    Sorry that my only knowledge about javascript is that it is not java. Can’t help you trace the problem further.

    BTW, I didn’t encounter those problems with Firefox 2.

    That’s all. Keep up the great work! :)

  2. 2 Horance Chou Jun 16th, 2008 at 18:16:08 UTC

    Hi Convil,

    Thanks a lot for your report.

    Actually, It’s a known-issue for VIL on FF3.0, and seems no solution for now. I guess we might need to stay on 2.0 for a while.

    I’m working one a extension version of VIL, I believe that will solve the security-related issue. But I’m quite busy recently, so it will take more time to complete. please wait for the good news :)

    Thanks again!

Leave a Reply




Google Friend Connect