masalibの日記

システム開発、運用と猫の写真ブログです

React Firebase入門 Storageのファイルの移動

f:id:masalib:20201208031125j:plain

Firestorageでファイルをアップロード(新規、更新)はできたのですが ファイルの移動ができず・・・後回しにしていました。そちらをなんとかしたいと思いましてがんばりました

この記事はReact Firebase入門シリーズです 1-1・ Firebase初期設定とFirebaseAuthのSignUp
1-2・ 「react-hook-form」を入れてみた
1-3・ AuthのSignUpの通信エラー対応
2 ・ FirebaseAuthのログイン処理
3 ・ FirebaseAuthのログイン認証とログアウト処理
4 ・ FirebaseAuthのパスワード初期化処理
5 ・ FirebaseAuthのメールアドレスの有効化
6 ・ FirebaseAuthのメールアドレスとパスワード変更
7 ・ FirebaseAuthの表示名変更
8-1・ FirebaseStorageのファイルアップ:基礎
8-2・ FirebaseStorageのファイルアップ前に画像の切り抜き
8-3・ FirebaseStorageのファイルアップの移動 今ここ
9-1・FirestoreのCRUD
9-2・Firestoreのデータ取得補足
9-3・Firestoreのページネーション処理
9-4・Firestoreのコレクション(テーブル)のJOIN

やりたい事

簡単にいうとファイル移動です。

ファイル移動がほしかった理由
ファイルアップする時に作業用のファイルにアップして問題なけば 公開用のファイルに切り替える。作業用ファイルは削除する形にしたかった。 小さい時のアプリならこの作業用ファイルは消さなくてもいいだけど、大きくなると膨大量になる。事前につぶせると思ったのでやりたかった。

事前作業

javascriptでファイルを移動(ダウンロード+削除)するためにはをするためにはサーバー側にCORSという設定が必要です。

gsutil のインストール

firebaseのサーバーというかバケットにCORSを設定するにはgsutilで操作するしかできないようなのでインストールします

cloud.google.com

公式だとPowerShellでとか書いてあるのですが普通にインストラーが起動してインストールしてくれます。

インストール後に使用するユーザーに認証がでてくるので、使用しているユーザーで認証する

gsutil を使ってCORSの設定ファイルをアップする

下記のファイルを作成する

[
    {
      "origin": ["*"],
      "method": ["GET"],
      "maxAgeSeconds": 3600
    }
  ]

作成後に設定ファイルをアップする

gsutil cors set cors.json gs://<your-cloud-storage-bucket>

gsutil cors set cors.json gs://learn-firebase-masalib.appspot.com  
// => Setting CORS on gs://learn-firebase-masalib.appspot.com/...  

確認は以下のコマンド

gsutil cors get gs://<your-cloud-storage-bucket>

gsutil cors get gs://learn-firebase-masalib.appspot.com  
// => [{"maxAgeSeconds": 3600, "method": ["GET"], "origin": ["*"]}]    

プログラム

//更新ボタンが押された時を想定
const sourcePath   = "images/users/"+ currentUser.uid + "/tempprofilePicture.png" 
const destinationPath  = "images/users/"+ currentUser.uid + "/filePicture.png" 
let statusData = moveFirebaseFile(sourcePath, destinationPath)
console.log(statusData)
//・
//・
//・
//・
function moveFirebaseFile(currentPath, destinationPath) {
    let oldRef = storage.ref().child(currentPath)

    oldRef.getDownloadURL().then(url => {
        fetch(url).then(htmlReturn => {
            let fileArray = new Uint8Array()
            const reader = htmlReturn.body.getReader()

            //get the reader that reads the readable stream of data
            reader
                .read()
                .then(function appendStreamChunk({ done, value }) {
                    //If the reader doesn't return "done = true" append the chunk that was returned to us
                    // rinse and repeat until it is done.
                    if (value) {
                        fileArray = mergeTypedArrays(fileArray, value)
                    }
                    if (done) {
                        //console.log(fileArray)
                        return fileArray
                    } else {
                        // "Readout not complete, reading next chunk"
                        return reader.read().then(appendStreamChunk)
                    }
                })
                .then(file => {
                    //Write the file to the new storage place
                    let status = storage
                        .ref()
                        .child(destinationPath)
                        .put(file)
                    //Remove the old reference
                    oldRef.delete()

                    return status
                })
        })
    })
}

function mergeTypedArrays(a, b) {
    // Checks for truthy values on both arrays
    //if(!a && !b) throw 'Please specify valid arguments for parameters a and b.';  
        // Checks for truthy values or empty arrays on each argument
    // to avoid the unnecessary construction of a new array and
    // the type comparison
    if(!b || b.length === 0) return a;
    if(!a || a.length === 0) return b;

    // Make sure that both typed arrays are of the same type
    if(Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)){
        console.log(Error,'The types of the two arguments passed for parameters a and b do not match.')
    }

    var c = new a.constructor(a.length + b.length);
    c.set(a);
    c.set(b, a.length);

    return c;
}

解説

oldRef.getDownloadURL().thenは具体的なURLのパスを取得しています

source:images/users/9f5oVBzuPuZrKibnLgWPM61UYcL2/tempprofilePicture.pngの場合は
 ↓
https://firebasestorage.googleapis.com/v0/b/learn-firebase-masalib.appspot.com/o/images%2Fusers%2F9f5oVBzuPuZrKibnLgWPM61UYcL2%2FtempprofilePicture.png?alt=media&token=fe155cf4-5da4-4e56-861f-72dbf3d1eed5

になります

let fileArray = new Uint8Array()はバイナリーデータを扱うために 準備しています。

Uint8Arrayとは

Uint8Array は型付き配列であり、 8 ビット符号なし整数値の配列を表します。中身は 0 で初期化されます。生成されると、配列の中の要素はオブジェクトのメソッドを使用するか、配列の標準的な添字の構文を使用するか (すなわち、ブラケット構文を使用するか) して参照することができます。

developer.mozilla.org

元ファイルを読み込んで型付き配列に突っ込んでいます。

if (value) {
    fileArray = mergeTypedArrays(fileArray, value)
}
if (done) {
    //console.log(fileArray)
    return fileArray
} 

mergeTypedArraysは結合しています。

結果

ファイルの移動はできたが 種類(mimetype)が

image/jpeg or image/png
=>  application/octet-streamに変更になってしまった

表示されるから問題ないがFirebaeの管理画面からは見れなくなった・・・・

あとで調べてみたら mimetypeを指定してアップしたら管理画面からも見れた。

 var metadata = {
      contentType: 'image/jpeg',
 };
let status = storage
    .ref()
    .child(destinationPath)
    .put(file,metadata)

感想

  • ファイル移動のために2時間近く悩んだ。関数を用意してほしい

参考URL

stackoverflow.com

www.it-swarm-ja.tech