--- title: Monitoring downloads slug: Monitoring_downloads tags: - Download Manager - Firefox 3 translation_of: Archive/Mozilla/Monitoring_downloads ---

Firefox 3 では、ダウンロード状況の監視がかつてなく簡単になりました。これまでのバージョンの Firefox でも実装は可能でしたが、ひとつのオブザーバを一度に登録することしかできませんでした。Firefox 3 では、ダウンロードの監視にいくつでもリスナーを利用できる新しい API が導入されました。

この記事では、Firefox 3 のダウンロードマネージャを利用してダウンロードを監視する方法を実演します。また、親切なおまけとして、Storage API を利用してデータベースに sqlite コマンドを発行する方法も実演します。その結果は、[ツール] メニューの [Download log] を選択することで開けるウィンドウ上で見ることができます。このウィンドウでは、サンプル用の拡張機能をインストールしてから行われたすべてのダウンロードが一覧表示されます。一覧には、ファイル名、ダウンロード開始時刻と終了時刻、ダウンロード速度、ダウンロード状況が表示されます。ファイルの取得元 URL を示すツールチップも含まれています。

完全なサンプルをダウンロード

セットアップ

拡張機能の読み込みが完了すると、すぐにいくつかの処理が行われます。具体的には、ダウンロードマネージャの nsIDownloadManager インタフェースのインスタンスを取得し、データを保存するためのデータベースを作成するのに、これらの処理が必要となります。

 onLoad: function() {
   // 初期化コード
   this.initialized = true;
   this.strings = document.getElementById("downloadlogger-strings");

   this.dlMgr = Components.classes["@mozilla.org/download-manager;1"]
                          .getService(Components.interfaces.nsIDownloadManager);

   this.dlMgr.addListener(downloadlogger);

   // データベースを開き、そのファイルをプロファイルディレクトリに保存します

   this.dbFile = Components.classes["@mozilla.org/file/directory_service;1"]
                    .getService(Components.interfaces.nsIProperties)
                    .get("ProfD", Components.interfaces.nsIFile);
   this.dbFile.append("downloadlogger.sqlite");

   // ストレージサービスへのアクセスを取得し、データベースを開きます

   this.storageService = Components.classes["@mozilla.org/storage/service;1"]
                       .getService(Components.interfaces.mozIStorageService);

   var dbConn = this.storageService.openDatabase(this.dbFile);

   // テーブルを作成します。既に存在する場合は失敗しますが、気にしません。

   dbConn.executeSimpleSQL("CREATE TABLE items (source TEXT, size INTEGER," +
                           " startTime INTEGER, endTime INTEGER," +
                           " speed REAL, status INTEGER)");
   dbConn.close();
 },

これは非常に簡単な例です。ダウンロードマネージャのインスタンスは、後で再利用できるよう downloadlogger オブジェクトのメンバー変数にキャッシュされ、addListener() メソッドが呼び出されてダウンロード状況の監視が開始されます。データベースファイルが開かれ、sqlite の CREATE TABLE コマンドが実行されてテーブルが作成されます。

最後に、データベースが閉じられます。

注意: mozIStorageConnectionclose() メソッドは Firefox 3 Alpha 8 で追加されました。Firefox の以前のバージョンでは、データベースを明示的に閉じる方法がありません。その代わり、ガベージコレクタが接続オブジェクトを破棄したときに閉じられます。

ダウンロード状況変更時の処理

上記のコードが実行されたら、ダウンロードの状況が変わるたびに onDownloadStateChange() メソッドが呼び出されます。これは nsIDownloadProgressListener インタフェースの一部です。

この部分のコードは以下のようになります:

 onDownloadStateChange: function(aState, aDownload) {
   var statement;

   switch(aDownload.state) {
     case Components.interfaces.nsIDownloadManager.DOWNLOAD_DOWNLOADING:

       // 開始されたダウンロードのために新しい列を追加します。各列には取得元 URI、
       // サイズ、開始時刻が含まれます。終了時刻とダウンロード速度は、まだ分からないため、
       // 初めはいずれも 0 に設定します。

       // 状況は、ダウンロードマネージャから提供されるものと同じ、状況を示す値になります。

       var dbConn = this.storageService.openDatabase(this.dbFile);
       statement = dbConn.createStatement("REPLACE INTO items VALUES " +
                                          "(?1, ?2, ?3, 0, 0.0, 0)");

       statement.bindStringParameter(0, aDownload.source.spec);
       statement.bindInt64Parameter(1, aDownload.size);
       statement.bindInt64Parameter(2, aDownload.startTime);
       statement.execute();
       statement.reset();
       dbConn.close();
       break;

     // ダウンロードの完了 (失敗もしくは成功) を記録します

     case Components.interfaces.nsIDownloadManager.DOWNLOAD_FINISHED:
     case Components.interfaces.nsIDownloadManager.DOWNLOAD_FAILED:
     case Components.interfaces.nsIDownloadManager.DOWNLOAD_CANCELED:
       this.logTransferCompleted(aDownload);
       break;
   }
 },

ここで 4 つの進捗状況に注目してみましょう。aDownload.state フィールドで示されるダウンロード状況が Components.interfaces.nsIDownloadManager.DOWNLOAD_DOWNLOADING の場合、ファイルのダウンロードが開始されています。aDownload オブジェクトは nsIDownload オブジェクトです。

その場合、新しいファイルのために、データベースを開いて REPLACE INTO sqlite コマンドを作成することで、データベースに新しい列を作成します。最初の 3 列は、ダウンロードオブジェクトから提供された、取得元 URI、ファイルサイズ、開始時刻フィールドの値に設定されます。残りの列の情報は、この時点では分からないため、ゼロに設定されます。

ダウンロードの進捗状況が、ダウンロードが完了、キャンセル、あるいは失敗したことを示した場合、logTransferCompleted ルーチンを呼び出して、その進捗状況の変更を示すようログを更新します。この部分のコードは以下のようになります。

 logTransferCompleted: function(aDownload) {
     var endTime = new Date();                // 現在時刻が終了時刻になります

     // REPLACE sqlite コマンドを発行して記録を更新します。同じ取得元 URI と開始時刻の
     // 記録を見つけたら、その記録内の終了時刻、サイズ、速度のエントリーを更新します。
     // 取得元 URI と開始時刻の両方が一致することを確認することで、同じファイルについて
     // 複数ダウンロードがあっても、それぞれに記録を取ることができます。

     var dbConn = this.storageService.openDatabase(this.dbFile);
     var statement = dbConn.createStatement("UPDATE items SET size=?1, " +
         "endTime=?2, speed=?3, status=?4 WHERE source=?5 and startTime=?6");
     statement.bindInt64Parameter(0, aDownload.size);
     statement.bindInt64Parameter(1, endTime.getTime());
     statement.bindDoubleParameter(2, aDownload.speed);
     statement.bindInt32Parameter(3, aDownload.state);
     statement.bindStringParameter(4, aDownload.source.spec);
     statement.bindInt64Parameter(5, aDownload.startTime);
     statement.execute();
     statement.reset();
     dbConn.close();
 },

ここでは単純に、データベースを開いて、UPDATE sqlite コマンドを作成、実行することで、完了したダウンロードと取得元 URI と開始時刻が一致するダウンロード項目を検索し、その情報を更新しています。同じ URI と開始時刻の記録を探すことで、ユーザが同じファイルを何度ダウンロードした場合も、正しく処理を行うことができます。

ダウンロードログの表示

ダウンロードログウィンドウのコードは、downloadlogger_dlwindow と呼ばれるオブジェクトの中で完結しています。これはシンプルな例なので、1 回限りのログウィンドウとなっており、それ以降のログの変更は監視していません。ウィンドウが開かれた時点でのダウンロードの状況を単純に表示するだけです。

つまり、ここでの処理はすべて load イベントハンドラだけで行うことができます。コードは以下の通りです。

 onLoad: function() {
   // データベースを開きます

   this.dbFile = Components.classes["@mozilla.org/file/directory_service;1"]
                    .getService(Components.interfaces.nsIProperties)
                    .get("ProfD", Components.interfaces.nsIFile);
   this.dbFile.append("downloadlogger.sqlite");

   // ストレージサービスへのアクセスを取得し、データベースを開きます

   this.storageService = Components.classes["@mozilla.org/storage/service;1"]
                       .getService(Components.interfaces.mozIStorageService);

   var dbConn = this.storageService.openDatabase(this.dbFile);

   var loglist = document.getElementById("loglist");

   var statement = dbConn.createStatement("SELECT * FROM items");   // テーブル内のすべての項目を取得します
   try {
     while (statement.executeStep()) {
       var row = document.createElement('listitem');

       // 列にセルを追加します

       var cell = document.createElement('listcell');
       var sourceStr = statement.getString(0);
       row.setAttribute("tooltiptext", sourceStr);
       sourceStr = sourceStr.slice(sourceStr.lastIndexOf("/")+1, sourceStr.length);
       cell.setAttribute("label", sourceStr);   // 取得元
       row.appendChild(cell);

       cell = document.createElement('listcell');
       cell.setAttribute("label", (statement.getInt64(1) / 1024).toFixed(1) + "KB");    // サイズ
       cell.setAttribute("style", "text-align:right");
       row.appendChild(cell);

       var theDate = new Date(statement.getInt64(2) / 1000);        // 開始時刻
       cell = document.createElement('listcell');
       var dateStr = theDate.toLocaleString();
       cell.setAttribute("label", dateStr);
       row.appendChild(cell);

       theDate = new Date(statement.getInt64(3));            // 終了時刻
       cell = document.createElement('listcell');
       dateStr = theDate.toLocaleString();
       cell.setAttribute("label", dateStr);
       row.appendChild(cell);

       var speed = statement.getDouble(4) / 1024.0;
       cell = document.createElement('listcell');
       cell.setAttribute("label", speed.toFixed(1) + "KB/sec");
       cell.setAttribute("style", "text-align:right");
       row.appendChild(cell);

       var status = statement.getInt32(5);
       var style = "color:black";
       cell = document.createElement('listcell');

       var statusStr;

       switch(status) {
         case 0:
           statusStr = "Downloading";
           break;
         case 1:
           statusStr = "Complete";
           style = "color:green";
           break;
         case 2:
           statusStr = "Failed";
           style = "color:red";
           break;
         case 3:
           statusStr = "Canceled";
           style = "color:purple";
           break;
         case 4:
           statusStr = "Paused";
           style = "color:blue";
           break;
         case 5:
           statusStr = "Queued";
           style = "color:teal";
           break;
         case 6:
           statusStr = "Blocked";
           style = "color:white background-color:red";
           break;
         case 7:
           statusStr = "Scanning";
           style = "color:silver";
           break;
         default:
           statusStr = "Unknown";
           break;
       }
       cell.setAttribute("label", statusStr);
       cell.setAttribute("style", style);
       row.appendChild(cell);

       loglist.appendChild(row);
     }
   } finally {
     statement.reset();
     dbConn = null;
   }
 }

このコードは至ってシンプルです。初めに、ログ情報が含まれる sqlite データベースを開いた後、SELECT SQL 構文を作成し、データベースからすべてのエントリーを取得します。

複数の結果を繰り返し処理するために、mozIStorageStatement オブジェクトの executeStep() メソッドを呼び出す while ループを使っています。このメソッドが呼び出されるたびに、結果から 1 つの列が取得されます。

その後、リスト列オブジェクトが作成され、検索結果の各エントリーが取得されて適切なリストセルに挿入されます。

上記のコードからいくつか興味深い点を取り上げてみましょう。

読者への課題

この拡張機能を改良するためにできる、一見して分かることがいくつかあります。ダウンロードマネージャやストレージ API の使い方を学んでいるなら、以下のようなことを、練習のために調べてみると良いでしょう。

関連資料

Storage, nsIDownloadManager, nsIDownload, nsIDownloadProgressListener