ぽよメモ

レガシーシステム考古学専攻

monacaでCordova Fileプラグインを使う

ちょっとしたハッカソンAndroidアプリケーションを作成する際,ローカルのファイルをちょっといじれて,APIをちょっと叩けて,くらいの能力があれば良いと言うことで,monacaを利用したハイブリッドアプリケーションを作りました.

その際,CordovaFileプラグインを使うことを考えついたのですが,web上のリファレンスを見ても,TEMPORARYな部分や,PERSISTENTであっても自分のアプリケーションに割り当てられる領域のようなものばかりを扱っていて,いわゆる/mnt/sdcard/0/直下みたいなところをどう覗いたら良いんだろうとかなり悩みました.

今回はmonacaにおいて,OnsenUI 2.x,AngularJS 1.xを用いることとします.AngularJSの記法に則って書いていきます.

権限の設定

まず,Fileプラグインを導入しただけではダメです.config.xmlの適当な箇所に

<preference name="AndroidPersistentFileLocation" value="Compatibility"/>

これを追記しておきます.これが無ければ権限ではじかれます.
Content-Security-Policyは以下のように設定していました.

<meta http-equiv="Content-Security-Policy" content="default-src * data:; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">

PERSISTENTのルートを取得

ファイルシステムのルートディレクトリを取得するため,factoryを追加します.

let app = angular.module("myApp", ["onsen"]);

app.factory("FileSystem", ["$q", function($q){
    return {
        getFileSystem: function(){
            let deferred = $q.defer();
            window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(filesystem){
                deferred.resolve(filesystem);
            }, function(error){
                deferred.reject(error);
            });
            return deferred.promise;
        }
    };
}]);

$qは非同期処理のためのAngularJSのライブラリです.詳しくはググってください.
今後,追加していくサービスはできる限り非同期で書いていきます.

適当なcontrollerを作成し,先ほどのメソッドを読んでみます.

<ons-template ng-controller="SampleController">
  <ons-page>
    <ons-toolbar>
      <div class="center">Test</div>
    </ons-toolbar>
    <h1>てすと</h1>
    <span>{{ROOT_PATH}}</span>
  </ons-page>
</ons-template>
app.controller("SampleController", ["$scope", "FileSystem", function($scope, FileSystem){
  FileSystem.getFileSystem()
  .then(function(fs){
    $scope.ROOT_PATH = fs.root.toURL(); /* ルートへのパスを取得 */
  }, 
  function(error){
    alert("エラーです");
  });
}]);

これを動かしてダメだったら僕には何も言えませんお手上げです.頑張ってリファレンスを読んでください…

特定のディレクトリを取得

ディレクトリ名を指定して,先ほどのルート以下にあるディレクトリを1つ取得します.
ここでは指定しませんが後でMusicを引数として渡し,Musicディレクトリを取得します.中に何か入っているディレクトリにすると今後がやりやすいです.

app.factory("DirectoryMethod", ["$q", "FileSystem", function($q, FS) {
    return {
        getTargetDirectory: function(dirPath) {
            let deferred = $q.defer();
            FS.getFileSystem()
            .then(function(filesystem){
                filesystem.root.getDirectory(dirPath, {create: false}, function(dirEntry){
                    deferred.resolve(dirEntry);
                },
                function(erroe){
                    deferred.reject(error);
                });
            });
            return deferred.promise;
        }
    };
}]);

ルートの取得まで含めましたが分離したい人はその部分だけ削ってしまえば良いと思います.
もし読み取ろうとするディレクトリが存在しない場合,{create: true}を指定すると勝手に作成してくれます.

このときgetTargetDirectoryメソッドの成功時の返り値はDirectoryEntryオブジェクトです.これに対しcreateReader()してReaderを作成します.

ディレクトリ一覧を返す

先ほどのメソッドにチェーンさせて中身の一覧を返すメソッドを作ります.

/* DirectoryMethodに追記 */
        returnDirList: function(Entry) {
            let deferred = $q.defer();
            Entry.createReader().readEntries(function(entries){
                    deferred.resolve(entries);
                }, 
                function(error){
                    deferred.reject(error);
                }
            );
            return deferred.promise;
        }

このメソッドの返り値はDirectoryEntryまたはFileEntryのリストになります.readEntries()以下でfor文回してentries[i].isDirectoryとかで判定してディレクトリのみを抜き出せばディレクトリのみを返させることが出来ます.
ファイルのみを抜き出したい場合は.isFileを使えば良いです.

ここまででディレクトリとその中身を見るファイラーモドキを作れます.

ディレクトリを閲覧する何かを作る

<ons-template id="sample_main.html">
  <ons-navigator var="SampleNavi">
    <ons-page ng-controller="SampleController">
      <ons-toolbar>
        <div class="center">
          ファイラーっぽい何か
        </div>
      </ons-toolbar>
      <ons-list ng-show="dir_load" class="animated">
        <ons-list-header>Directory</ons-list-header>
        <ons-list-item ng-repeat="dir in dirs | orderBy: 'name'" ng-click="load(dir)" tappable>
          <div class="left"><ons-icon size="20px" icon="md-folder-outline" class="list__item__icon"></div>
          <div class="center">{{::dir.name}}</div>
        </ons-list-item>
      </ons-list>
    </ons-page>
  </ons-navigator>
</ons-template>

<ons-template id="sample_child.html">
    <ons-page ng-controller="SampleController">
      <ons-toolbar>
          <div class="left"><ons-back-button>Back</ons-back-button></div>
          <div class="center">{{ SampleNavi.topPage.data.name }}</div>
      </ons-toolbar>
      <ons-list ng-show="dir_load" class="animated">
        <ons-list-header>{{SampleNavi.topPage.data.name}}</ons-list-header>
        <ons-list-item ng-repeat="dir in dirs | orderBy: 'name'" ng-click="load(dir)" tappable>
          <div class="left">
            <ons-icon size="20px" icon="md-folder-outline" class="list__item__icon" ng-if="dir.isDirectory"></ons-icon>
            <ons-icon size="20px" icon="md-file" class="list__item__icon" ng-if="!dir.isDirectory"></ons-icon>
          </div>
          <div class="center">{{::dir.name}}</div>
        </ons-list-item>
      </ons-list>
    </ons-page>
</ons-template>
app.controller("SampleController", ["$scope", "DirectoryMethod", function($scope, DirMethod){
  let abort_error = function(error){
    alert("エラーだよ");
  }
  DirMethod.getTargetDirectory("Music")
  .then(DirMethod.returnDirList)
  .then(function(dirList){
    $scope.dirs = dirList;
    $timeout(function(){
      $scope.dir_load = true;
    });
  }).catch(abort_error);
  $scope.dir_load = false;
  $scope.load = function(dirEntry) {
    let options = {data: dirEntry, animation: "slide"};
    DirNavi.pushPage("sample_child.html", options);
  };
}]);

こんな感じのメソッドを利用して以下のような感じのものにしてみました.
f:id:pudding_info:20161105013731p:plain

これでファイルをタップして開こうとするとエラーを吐きます.例えば画像を表示したいとかなら,<img ng-src="{{hoge.path}}">とかで画像ファイルへのパスを渡してやれば表示できます.
簡単なとこまでしか書いてませんが疲れました.そのうち気が向いたら上記のファイラーモドキとMediaプラグインを組み合わせて簡易音楽プレーヤーを作る記事を書きます.