2010年6月30日水曜日

ZipファイルをUnzip(解凍)するクラス

Androidアプリを開発していて、Zipファイルを解凍するクラスを作ったので下記に公開しておく。

public class UnzipFiler
{
    private final static int CHUNK_SIZE = 32 * 1024;
    byte[] _fileIOBuffer = new byte[CHUNK_SIZE];
    
    public void unzipFile(File zipFile, String directory)
  throws IOException
    { 
 ZipInputStream in = null; 
 FileOutputStream os = null; 
 try 
 {
  in = new ZipInputStream (new FileInputStream(zipFile)); 
  ZipEntry entry = null; 
  while ((entry = in.getNextEntry ())!= null) 
  { 
   String entryName = entry.getName();     
   if (entry.isDirectory ()) { 
    File file = new File (directory, entryName); 
    file.mkdirs(); 
   } 
   else { 
    File file = new File(directory, entryName);
    if (file.exists()){
     file.delete();
    }
    os = new FileOutputStream (file); 
    
    int unzippedSize = 0;
    int reportedProgress = 100;
    int bytesRead = 0; 
    while ((bytesRead = in.read (_fileIOBuffer))!= -1) {
     os.write(_fileIOBuffer, 0, bytesRead);
     unzippedSize += bytesRead;
    }
    os.close();
   }
  }
 }
 catch (FileNotFoundException e) {    
  Log.v("unzip", e.getMessage());
 } 
 catch (IOException e) {
  Log.v("unzip", e.getMessage());
 } 
 finally{
  if (in != null ){
   in.close();
  }
  if (os != null ){
   os.close();
  }
 }
    }  
}

Stack overflowに投稿したのと同じものだけれど、こうした方が良い、などの意見があったらコメントお願いします。

How should I decompress a large data file in AsyncTask.doInBackground?

2010年6月18日金曜日

存在しないファイルを定期的にリクエストされたら

My-ClipではELMAHを使用しているので、何かしらエラーが起きるとメールが飛んでくる仕組みになっている。で、ELMAHを組み込んでからFileNotFoundExceptionがちょこちょこと報告されるようになり、それによると、いかにクラッカーさん達が日夜何かしらアカウント的なものにアクセスしようと試みているかがよく分かる。そういう場合は大抵、/account/hogehogeだったり/admin/hogehogeなどがリクエストされている。

が、本日受け取ったリクエストはいささか趣がことなり、下記のようなパスだった。

/SlurpConfirm404/booksontheweb.htm

このほかにも4つほど似たようなリクエストがあったのでGoogleしたら、下記のブログにたどりついた。

Inktomi Slurp Confirm 404

この記事によると、あのリクエストはYahooに登録されているWebサイトが正しく404を返却するかをYahooが確認するためのものらしい。ということは、いずれこのサイトも今は皆無であるYahooからの流入者がある、ということ、・・・なのかな?

2010年6月10日木曜日

Entity Frameworkを使ってSQLiteにAuto Incrementのデータを挿入する方法

SQLiteと.NETをつなぐ夢の架け橋であるSystem.Data.SQLiteを使い、VS2010でEntity Framework経由でSQLiteにデータを流し込もうと画策したのだがAuto Increment周りで大変四苦八苦したのでここに後続のために記しておく。

そもそもはじめから嫌な感じがした。SQLiteのことがよく分からなかったのでOfficial Siteをざっくりと読んでみたけど、公式サイトでは特にGUIのサポートなどはなく、UIがコマンドラインしかない。おいおいまじかよ、Foreign Keyとか多量にあるからいちいち記述すんのめんどいんですけど、とか気持ちがなえかけていたらSQLite Database Browserを見つけた。これで勝つると思ったのもつかの間、このGUIが思いのほかしょぼいし使いづらい。

で、くだんのSystem.Data.SQLite君を使うとVSのServer Explorerからテーブルを作ってー外部キー制約作ってー、というのがぽちぽちとできるっぽい(詳しくはここを観て欲しい)。SQL ServerのDBダイアグラムに比べるべくもないが、SQLite Database Browserさんよりは多分に使いやすいので、これでぽちぽちと作業に入った。

が、しかし、ここで問題が一点。主キーをAuto Incrementにしたいのにその設定がない。SQL Serverならば下図のように項目があるのだが、SQLiteのほうにはその項目がない。

(優秀なSQL Server君の図)

(ちょっと残念なSQLite氏の図)

おうおうおう?ということでまたぞろReferenceをざっくりと読んでみるに、Primary Keyと設定されていればId値がNullだったり0の場合は勝手に値を自動生成しますよ?とか書いてある。おぉ、すげー、ということでそのまま作業をすすめ、いざ準備が整ったのでささっとプログラミングして動かしたらうごかねー。

普通にPrimary KeyがUniqueではありませんと例外を投げ出されてしまった。なんでなんで、と色々ぐぐったけどあまり有効な情報がヒットしない。edmxファイル上でStoreGeneratedPatterをIdentityにしなさい、とかの指示はあったけど、それはedmxファイルを作ったときにやってあったのでまだ他の問題があるようだ。ふむ?と色々試行錯誤の結果、最終的にはSQLite Database Browserさんを使い、テーブルの編集→フィールドの編集→フィールドタイプの入力、で「INTEGER PRIMARY KEY AUTOINCREMENT」と入力し、edmx上の対応するテーブルを消去してから追加して実行したら正常に動作した。

なんだよ!AUTOINCREMENTって記述いるんじゃん!!

まぁEFでSQLiteにAuto Incrementなデータを流し込むためにはAUTOINCREMENTって記述が必要ってことなのかな、よく分からんけど。


追記:System.Data.SQLiteのAdd-in(なのか?)からもAuto Incrementの記述を追加できるの図

2010年6月6日日曜日

StreetViewのReferenceにないイベントを拾う ~ Google Maps API v3

Google Maps API v3に変わり、以前のようにStreetViewを使うために自前でゴリゴリとPegmanの挙動やら何やらを制御してやる必要がなくなったわけだが、それにあわせてイベントの種類も減ってしまったように見える(あくまでReference的な意味で)。

というのも、StreetViewPanoramのEvents項目を見ると、そこにはcloseclickしか載っていない。

これではいつMapがStreetViewモードに切り替わったかなどのイベントが拾えないじゃないか、と思いつつもデバッグ時にStreetViewPanoramのインスタンスを掘り下げてみたらReferenceに載っていないイベントがたくさん見つかった。なので、今回はそのうちの一例を紹介しようと思う。下記の例はgoogle.maps.StreetViewPanoramaの表示・非表示が切り替わったイベントをハンドルしている。これは、MapのほうでPegmanを地図上に落とした場合などにも呼ばれるイベントなので、Mapの動作に併せてStreetViewPanoramaの挙動を変えたいときに重宝するイベントだ。

var map = new google.maps.Map("省略");
var point = new google.maps.LatLng("省略");
var panoramaOptions = {
        position: point,
        pov: {
            heading: 34,
            pitch: 10,
            zoom: 1
        }};
var panorama = new google.maps.StreetViewPanorama(document.getElementById("streetview"), panoramaOptions);
map.setStreetView(panorama);
google.maps.event.addListener(panorama, "visible_changed", function () {
  alert(panorama.getVisible() ? "StreetView見えてる" : "StreetView見えてない");
});

IE8でStreetViewを表示すると描画がおかしくなる ~ Google Maps API v3

ASP.NETでホストしているサイトで、Google Maps API v3を使用し、IE8でStreetViewを表示すると下図のように描画が崩れてしまう。


一見分からないかもしれないが、画面中央に表示されるべき矢印が画面左側に寄ってしまっている。ちなみにFF、Chromeで表示すると下図のように正しく描画される。


この現象はgoogle.maps.Mapとgoogle.maps.StreetViewPanoramaに分けてStreetViewを実装しているのでまだこの程度で収まっているが、google.maps.MapにStreetViewの描画を任せている場合はその崩れ具合が悪化する。

ちなみに上記の問題を引き起こしているのは下記のタグだ。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >

このタグがあるとStreetViewが正しく描画されないようだ。が、これがないとIE8ではCSSの描画が崩れるという問題があった。何か解決方法がないものか、と思いつつも面倒なのでとりあえず一旦休止。

Google Maps APIのV2をV3へ移行する

Google Maps APIのv2が先月正式に非推奨になったのでwww.my-clip.netのAPIをv3へと移行した。v3になったことで大幅に変わった箇所がいくつかあるので下記に列挙しておく。

・API Keyはいらない
v2までは必須だったAPI Keyはもう必要ない。下記のようにVersionを含まないUrlをScriptタグのソースに指定すれば常に最新のライブラリが参照できるようになっている。
http://maps.google.com/maps/api/js?sensor=true_or_false

・google.maps名前空間の導入
v2まではグローバル空間にGMap2、GMarker、GPolyLineというクラスが配置されていたが、v3からはそれぞれgoogle.maps.Map, google.maps.Marker, google.maps.Polylineとなっている。これにあわせていくつかメソッドやプロパティも変更されているので注意するように。
例1:GMarker.getLatLng() → Marker.getPosition()
例2:GeocoderClient.getLocations() → Geocoder.geocode()
(GeocoderはCallbackメソッドのシグネチャと返却される値の構造が変わっているので要注意)

・GBrowserIsCompatible()、GUnload()がない
Referenceで明示的に廃止されたとは記述されていないけれど、Referenceに記述がないのとForumなどの他のユーザーからの報告をもとに類推すると現状上記二つのメソッドに対応するものはない

・StreetViewのサポート
StreetViewの導入がしごく簡単になった。google.maps.MapクラスがStreetViewをサポートするので、Mapクラスの初期化時にオプションでstreetViewControl: trueを渡してやるだけでStreetViewが使用できるようになる。詳細はここに載っているサンプルコードを参照してもらいたい。


クラスの配置場所が変わっているので移行作業が大変そうに映るが思ったよりも大変ではなかった。

2010年6月5日土曜日

AndroidのエミュレーターでASP.NET Dev Serverのlocalhostにアクセスする方法

Androidアプリをエミュレーター上で実行しWebアクセスするときに、テスト用にlocalhostを接続先に指定すると「Connection to http:localhost refused」とかなんとか言われて接続できない。

この理由はエミュレーター上でlocalhostとはエミュレーター自身を指すので、テスターが期待するようにはエミュレーターを実行しているマシンをルックアップしてくれない。なので解決方法は指定するURLを下記のようにプライベートIPアドレス(PvIP)へと変更しなければならない。

HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://192.168.0.2/foo/bar"); 
HttpResponse response;
response = httpclient.execute(httpget);
// 以下略
(HttpGetのサンプルはこちらを参考にどうぞ:How-to: Android as a RESTful Client

PvIPはコマンドプロンプトなどからipconfigを実行して取得すればよい。

で、このPvIPを指定する場合にASP.NET Development Serverを指定すると動かない。デブサバとはなんぞや、というのは下図のようなASP.NET開発者にはなじみの開発環境用のお手軽自動サーバーのことだ。


仮にデブサバが起動している状態でブラウザからhttp://192.168.0.2:52267/へとアクセスしても、サイトが見つからないと拒絶されてしまう。そのため、デブサバをPvIPで使うのは早々にあきらめて、IISでWebアプリケーションを仮想ディレクトリに登録する必要がある。

「デブサバのlocalhostにアクセスする方法」とこのポストのタイトルにあるが、それは色々と試したけれど無理だったので、もう普通にIISでテストしたいアプリを仮想ディレクトリにしたほうが早い。設定した仮想ディレクトリは下図のようになる。


一番下の項目のstripmeにアクセスするにはhttp://192.168.0.2:56526/となる。これでAndroidのエミュレーターからもHttpGetが正常に期待したサイトへとリクエストされるようになる。