初めてのAIRアプリケーションの作成

AIRアプリケーションに挑戦してみました。

javascript + html

actionscript + mxmlでも出来るみたいなんですが、
javascript + htmlで作成してみました。
だからって、こっちも十分な経験あるわけではありません。

仕様+ポイント

簡単なZipファイル解凍アプリケーションを作ることにしました。

  • Drag&Drop対応
  • オシャレというか、リッチな感じに
  • メディアごとに解凍

これは、個人的に気に入っているアイデアですが、
指定した拡張子のファイルのみを解凍する、といったものです。
使いどころは微妙かもしれませんが。


zipファイルが階層構造を持つ場合は解凍に失敗します。
かつ、日本語ファイルは文字化けしたり、上手く解凍できない可能性があります。
この辺は以下に挙げるAdobe AIRのByteArrayのページを参照。

こんなのが出来ました

airファイルを以下にうpしました。
興味のある方は以下のリンクからダウンロードどうぞ。
http://firestorage.jp/download/ac7e3af2f823ea38db9df52556d1863e4d72113f

  • ファイルは7-Zip圧縮されています。7-Zip Portableなどで解凍できます。
  • Adobe AIRランタイムがインストールされている必要があります。Adobe AIRAdobe AIRを今すぐインストールからインストールできます。


黒い部分をクリックすると、

例えば、musicなら、mp3,wav,ogg,m4aの拡張子を持つもののみ解凍します。
といっても、正規表現でチェックしているだけですが。
Zip部分にドラッグドロップすると、zipなら同じディレクトリに解凍します。
Zip部分をダブルクリックするとアプリケーションを終了します。

Zipファイルの解凍に関して

http://livedocs.adobe.com/air/1/devappshtml/help.html?content=ByteArrays_3.htmlが参考になりました。
というか、ほぼそのまま使ってます。


もっと、簡単にZipファイルを扱えればよかったのですが。
でも、Zipファイルのヘッダーのフォーマットについてもわかりやすく書かれているので、勉強になりました。

必要な分だけバイトを読み込み、必要な情報の位置にポジションをセットして読み込んでいるだけだとわかると楽しいものですね!


ただ、このページに載っている箇所で、uncompressに渡しているアルゴリズムオブジェクトはjavascriptから使う場合は、
CompressionAlgorithm.DEFLATE
ではなく、
air.CompressionAlgorithm.DEFLATE

とすることは注意です><

インターフェース

見た目はGIMPで適当に作りました。
application.xmlを設定してやることでガジェットのようなものを作ることができるようです。
このような見た目のアプリケーションのための設定はapplication.xmlで設定できます。

SimpleZipExtract.html

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
    <body>
      <div id="ctype">
        <img  src="type-all.png">
      </div>
      <div id="sze">
        <img src="SimpleZipExtractUI.png">
      </div>
      <script type="text/javascript" src="AIRAliases.js"></script>
      <script type="text/javascript" src="ZipExtract.js"></script>
      <script>      
        function $(name){ return document.getElementById(name); }

        var type = {
            all  : "app:/type-all.png",
            music: "app:/type-music.png",
            image: "app:/type-image.png",
            movie: "app:/type-movie.png",
            ptext: "app:/type-plain_text.png" };

        var re = /\..+$/i;

        var ctype = $("ctype");
        // add event listener
        ctype.addEventListener('mousedown',
          function changeType(){
            var cur = ctype.getElementsByTagName('img').item(0);
            if (cur.src == type.all) {
              cur.src = type.music;
              ctype.replaceChild(cur, ctype.lastChild);
              re = /\.(?:mp3|wav|ogg|m4a)$/i;
            }else if (cur.src == type.music) {
              cur.src = type.image;
              ctype.replaceChild(cur, ctype.lastChild);
              re = /\.(?:jpe?g|png|tiff?|gif|bmp|dvi|pdf)$/i;
            }else if (cur.src == type.image) {
              cur.src = type.movie;
              ctype.replaceChild(cur, ctype.lastChild);
              re = /\.(?:wmv|rm|avi|mp4)$/i;
            }else if (cur.src == type.movie) {
              cur.src = type.ptext;
              ctype.replaceChild(cur, ctype.lastChild);
              re = /\.(?:txt|csv|html?|m?a?xml|js|as|rb|py|hs|scm|pl|pm|java|php|cp{,2}|sh|ml|css|tex)$/i;
            }else if(cur.src == type.ptext){
              cur.src = type.all;
              ctype.replaceChild(cur, ctype.lastChild);
              re = /\..+$/i;
            }
          });

        var sze = $("sze");
        // register events
        sze.addEventListener("dragenter", onDragOver);
        sze.addEventListener("dragover", onDragOver);
        sze.addEventListener("drop", onDrop);
        // event handler
        function onDragOver(e){ e.preventDefault(); }
        function onDrop(e){
          var cb = e.dataTransfer;
          // check if file is dropped
          var typeFileList = "application/x-vnd.adobe.air.file-list";
          var file_ary = cb.getData(typeFileList);

          for (var i=0; i < file_ary.length; i++){
            var resource = decodeURI(file_ary[i].url);
            resource.match(/(.+\/)(.+?\.(.+?))$/);
            if (RegExp.$3 == 'zip') {
              var dir = RegExp.$1;
              var basename = RegExp.$2;
              zipExtract(dir, basename, re);
            }
          }
          alert("Complete!");
        }

        // allow drag
        sze.onmousedown = function(){
          window.nativeWindow.startMove();
        };

        // allow exit
        sze.ondblclick = function(){
          window.close();
        };
      </script>
    </body>
</html>

ZipExtract.js

var bytes = new air.ByteArray();

// variables for reading fixed portion of file header
var fileName = new String();
var flNameLength;
var xfldLength;
var offset;
var compSize;
var uncompSize;
var compMethod;
var signature;

var output;

// File variables for accessing .zip file
var zStream = new air.FileStream();

function zipExtract(dir, basename, re)
{
  var zfile = new air.File(dir + basename);
  zStream.open(zfile, air.FileMode.READ);
  bytes.endian = air.Endian.LITTLE_ENDIAN;
  while (zStream.position < zfile.size) {
    // read fixed metadata portion of local file header
    zStream.readBytes(bytes, 0, 30);

    bytes.position = 0;
    signature = bytes.readInt();
    // if no longer reading data files, quit
    if (signature != 0x04034b50) {
      break;
    }

    bytes.position = 8;
    compMethod = bytes.readByte(); // store compression method (8 == Deflate)
    offset = 0; // stores length of variable portion of metadata
    bytes.position = 26; //offset to file name length
    flNameLength = bytes.readShort(); // store file name
    offset += flNameLength; //add length of file name
    bytes.position = 28; //offset to extra field length
    xfldLength = bytes.readShort();
    offset += xfldLength; // add length of extra field
    // read variable length bytes between fixed-length header and compressed file data
    zStream.readBytes(bytes, 30, offset);

    bytes.position = 30;
    fileName = bytes.readUTFBytes(flNameLength); // read file name
    //output += fileName + "<br />"; // write file name to text area
    bytes.position = 18;
    compSize = bytes.readUnsignedInt(); // store size of compressed portion
    //output += "\tCompressed size is: " + compSize + '<br />';
    bytes.position = 22; // offset to uncompressed size
    uncompSize = bytes.readUnsignedInt(); // store uncompressed size
    //output += "\tUncompressed size is:" + uncompSize + '<br />';

    // read compressed file to offset 0 of bytes; for uncompressed files
    // the compressed and uncompresed size is the same
    zStream.readBytes(bytes, 0, compSize);
    if (fileName.search(re) == -1)  // break if file type doesn't match
    {
      continue;
    }
    if (compMethod == 8) // if file is compressed, uncompress
    {
      bytes.uncompress(air.CompressionAlgorithm.DEFLATE);
    }
    var fullpath = dir + fileName;
    outFile(fullpath, bytes); // call outFile() to write out the file
  }  // end of while loop
}  // end of init() method

function outFile(fullpath, data)
{
  var outFile = new air.File(fullpath);
  var outStream = new air.FileStream();
  // open output file stream in WRITE mode
  outStream.open(outFile, air.FileMode.WRITE);
  // write out the file
  outStream.writeBytes(data, 0, data.length);
  // close it
  outStream.close();
}

突っ込みは歓迎です><!

P.S.

血、止まりました。
一晩中止まらなくて、正直焦りました;


結果、大橋アナ似のポニテ歯科衛生士のお姉さんに惚れました///