0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cordova(Android)でファイルを扱うクラスを作成した

Last updated at Posted at 2025-01-03

Cordovaでファイルを扱うためのクラスを作成しました。

ファイルは、私の場合、以下のプラグインが関連しました。

・cordova-plugin-camera
・cordova-plugin-file

File System Access API

ファイルアクセスのための仕様が少し特殊で、File System Access APIというAPI仕様のようのようです。
ちょっとわかりにくいなあと思ったら、以下の方がまとめてくださっていました。(早く見つけたかった。。。)

https://developer.chrome.com/docs/capabilities/web-apis/file-system-access?hl=ja
https://doc.applican.com/FileSystem/index.html
https://github.com/apache/cordova-plugin-file

ソースコード

lib_file.js
class LibFile{
  constructor(){
  }

  error_message(code){
    switch(code){
        case 1: return "NOT_FOUND_ERR";
        case 2: return "SECURITY_ERR";
        case 3: return "ABORT_ERR";
        case 4: return "NOT_READABLE_ERR";
        case 5: return "ENCODING_ERR";
        case 6: return "NO_MODIFICATION_ALLOWED_ERR";
        case 7: return "INVALID_STATE_ERR";
        case 8: return "SYNTAX_ERR";
        case 9: return "INVALID_MODIFICATION_ERR";
        case 10: return "QUOTA_EXCEEDED_ERR";
        case 11: return "TYPE_MISMATCH_ERR";
        case 12: return "PATH_EXISTS_ERR";
        default: "Unknown error";
    }
  }

  static async newInstance(target){
    var lib = new LibFile();
    await lib.initialize(target);
    return lib;
  }

  async initialize(target = "data"){
    this.target = target;
    if( target == "root" )
      this.target_dir = cordova.file.externalRootDirectory;
    else if( target == "storage" )
      this.target_dir = cordova.file.applicationStorageDirectory;
    else if( target == "data" )
      this.target_dir = cordova.file.externalDataDirectory;
    else
      throw new Error("unknown target");
      
    return new Promise((resolve, reject) =>{
      window.resolveLocalFileSystemURL(this.target_dir, (dirEntry) => {
        this.dir = dirEntry;
        resolve(this);
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async createFile(dirName, fileName, data){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: true}, (dirEntry) =>{
        dirEntry.getFile(fileName, {create: true, exclusive: false}, (fileEntry) =>{
          fileEntry.createWriter((fileWriter) =>{
            fileWriter.write(data);
            fileWriter.onwriteend = () =>{
              resolve(fileEntry);
            };
            fileWriter.onerror = (error) =>{
              reject(this.error_message(error.code));
            };
          }, (error) =>{
            reject(this.error_message(error.code));
          });
        }, (error) =>{
          reject(this.error_message(error.code));
        });
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async createDirectory(dirName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: true, exclusive: true}, (dirEntry) =>{
        resolve(dirEntry);
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async removeDirectory(dirName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        dirEntry.removeRecursively(() =>{
          resolve();
        }, (error) =>{
          reject(error);
        });
      }, (error) =>{
        reject(error);
      });
      //   var dirReader = dirEntry.createReader();
      //   dirReader.readEntries((fileEntries) =>{
      //     for (let fileEntry of fileEntries) {
      //       fileEntry.remove();
      //     }
      //     dirEntry.remove(() =>{
      //       resolve();
      //     }, (error) =>{
      //       reject(this.error_message(error.code));
      //     });
      //   }, error =>{
      //     reject(this.error_message(error.code));
      //   });
      // }, (error) =>{
      //   reject(this.error_message(error.code));
      // });
    });
  }

  async getFileEntry(dirName, fileName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        dirEntry.getFile(fileName, {create: false, reclusive: false}, (fileEntry) =>{
          resolve(fileEntry);
        }, (error) =>{
          resolve(null);
        });
      }, (error) =>{
        resolve(null);
      });
    });
  }

  async isFile(dirName, fileName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        var dirReader = dirEntry.createReader();
        dirReader.readEntries((fileEntries) =>{
          var target = fileEntries.find(item => item.name == fileName && !item.isDirectory);
          resolve(target);
        }, error =>{
          resolve(null);
        });
      }, (error) =>{
        resolve(null);
      });
    });
  }

  async isDirectory(dirName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        resolve(dirEntry);
      }, error =>{
        resolve(null);
      });
    });
  }

  async listFiles(dirName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        var dirReader = dirEntry.createReader();
        dirReader.readEntries((fileEntries) =>{
          resolve(fileEntries);
        }, error =>{
          reject(this.error_message(error.code));
        });
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }
  
  async removeFile(dirName, fileName){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        dirEntry.getFile(fileName, {create: false, reclusive: false}, (fileEntry) =>{
          fileEntry.remove(() =>{
            resolve();
          }, (error) =>{
            reject(this.error_message(error.code));
          });
        }, (error) =>{
          reject(this.error_message(error.code));
        });
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async writeFile(dirName, fileName, data){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        dirEntry.getFile(fileName, {create: false, reclusive: false}, (fileEntry) =>{
          fileEntry.createWriter((fileWriter) =>{
            fileWriter.write(data);
            fileWriter.onwriteend = () =>{
              resolve();
            };
            fileWrite.onerror = (error) =>{
              reject(error);
            };
          }, (error) =>{
            reject(this.error_message(error.code));
          });
        }, (error) =>{
          reject(this.error_message(error.code));
        });
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async readFile(dirName, fileName, response_type = "text"){
    return new Promise((resolve, reject) =>{
      this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
        dirEntry.getFile(fileName, {create: false, reclusive: false}, (fileEntry) =>{
          fileEntry.file((file) => {
            var reader = new FileReader();
            reader.onloadend = () => {
              resolve(reader.result);
            };
            reader.onerror = () =>{
              reject(error);
            }
            if( response_type == "binary" )
              reader.readAsArrayBuffer(file);
            else if( response_type == "text" )
              reader.readAsText(file);
            else if( response_type == "dataurl" )
              reader.readAsDataURL(file);
            else
              reject(new Error("unknown response_type"));
          }, (error) =>{
            reject(this.error_message(error.code));
          });
        }, (error) =>{
          reject(this.error_message(error.code));
        });
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async copyFile(nativeUrl, dirName, fileName){
    return await new Promise((resolve, reject) =>{
        window.resolveLocalFileSystemURL(nativeUrl, (sourceEntry) => {
            this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
                sourceEntry.copyTo(dirEntry, fileName, (newFileEntry) => {
                  resolve(newFileEntry);
                }, (error) => {
                  reject(this.error_message(error.code));
                });
            }, (error) => {
              reject(this.error_message(error.code));
            });
        }, (error) => {
            reject(this.error_message(error.code));
        });
    });
  }

  async moveFile(nativeUrl, dirName, fileName){
    return await new Promise((resolve, reject) =>{
        window.resolveLocalFileSystemURL(nativeUrl, (sourceEntry) => {
          this.dir.getDirectory(dirName, {create: false}, (dirEntry) =>{
            sourceEntry.moveTo(dirEntry, fileName, (newFileEntry) => {
                resolve(newFileEntry);
              }, (error) => {
                reject(this.error_message(error.code));
              });
          }, (error) => {
            reject(this.error_message(error.code));
          });
        }, (error) =>{
            reject(this.error_message(error.code));
        });
    });
  }

  async readFromNativeUrl(nativeURL, response_type = "text"){
    return new Promise((resolve, reject) =>{
      resolveLocalFileSystemURL(nativeURL, (fileEntry) =>{
        fileEntry.file((file) => {
          var reader = new FileReader();
          reader.onloadend = () => {
            resolve(reader.result);
          };
          reader.onerror = (error) => {
            reject(this.error_message(error.code));
          }
          if( response_type == "binary" )
            reader.readAsArrayBuffer(file);
          else if( response_type == "text" )
            reader.readAsText(file);
          else if( response_type == "dataurl" )
            reader.readAsDataURL(file);
          else
            reject(new Error("unknown response_type"));
        }, (error) =>{
          reject(this.error_message(error.code));
        });
      }, (error) =>{
        reject(this.error_message(error.code));
      });
    });
  }

  async getFileEntry(nativeURL){
    return new Promise((resolve, reject) =>{
      resolveLocalFileSystemURL(nativeURL, (fileEntry) =>{
        resolve(fileEntry);
      }, (error) =>{
        reject(error);
      })
    });
  }

  toUrl(dirName, fileName){
    if( this.target == "root" )
      return "https://localhost/__cdvfile_sdcard__/" + dirName + "/" + fileName;
    else if( this.target == "storage" )
      return "https://localhost/__cdvfile_files__/" + dirName + "/" + fileName;
    else if( this.target == "data" )
      return "https://localhost/__cdvfile_files-external__/" + dirName + "/" + fileName;
    else
      throw new Error("unknown target");
  }
}

Cordovaでの注意点

1点だけ、注意があります。

本来であれば、FileEntryで示されるファイルをHTMLでimgタグに使う場合は以下のようにしていたかと思います。

var image_url = fileEntry.toURL()
<img v-bind:src="image_url">

この場合、Webコンテンツ(HTMLやJavascript)をサーバに配置せずアプリケーションにアセットとして埋め込んでいる場合は問題ありません。サーバに配置して読み込んでいる場合、これではうまく動作しません。
なぜならば、fileEntry.toURL()は以下のように展開されるためです。

image_url="https://[ホスティングURL]/cdvfile_sdcard/test.png

ホスティングURLは、Webコンテンツを配置したホスト名です。アセットとして埋め込んでいる場合はlocalhostとなるため問題ないのですが、サーバに配置すると、ローカルにあるファイルをリモートのホスティングサーバからとってこようとします。

そこで、以下の関数を用意しました。これにより、ローカルにあるファイルをローカルからとってきます。

lib_file.js
  toUrl(dirName, fileName){
    if( this.target == "root" )
      return "https://localhost/__cdvfile_sdcard__/" + dirName + "/" + fileName;
    else if( this.target == "storage" )
      return "https://localhost/__cdvfile_files__/" + dirName + "/" + fileName;
    else if( this.target == "data" )
      return "https://localhost/__cdvfile_files-external__/" + dirName + "/" + fileName;
    else
      throw new Error("unknown target");
  }

以下のように使います。

var image_url = libfile.toURL("ディレクトリ名", "ファイル名");
<img v-bind:src="image_url">

Permission

以下の記載がありました。
・適切な READ_EXTERNAL または READ_MEDIA_* 権限による読み取り専用アクセス。
・読み取り専用アクセスはメディア ファイルに限定されますが、ドキュメントには適用されません。

あと、以下も

config.xml
<config-file target="AndroidManifest.xml" parent="/*" xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
</config-file>

参考

以下も参考にしてください。

Cordovaアプリ開発の備忘録
Cordovaアプリ開発の備忘録(プラグイン編)

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?