UXPにおけるローカルファイルへのアクセス
ExtendScriptおよびCEPではローカルへのファイルの保存、特定のファイルのアクセスは 当たり前のようにできました。しかしUXPからローカルファイルへのアクセスがセキュリティの観点から 厳しくなりました。
Photoshop2023より一部緩和されたのでmanifestの許可をfullAccessにすれば fsモジュールというNode.jsチックなファイルアクセスモジュールでNode.jsのようにアクセスできます。 fsモジュールについては以下の記事を参照。 Photoshop 24.2よりgetEntryWithUrlメソッドメソッドも実装されてほぼ完全な形でローカルファイルへのアクセスが 緩和されました。
fsという名称に関して
またfsというファイルシステムの略称はfileSystemの頭文字をとった変数名ですがこれがuxpモジュールのfileSystemと Node.jsチックなファイルシステムと混ざりやすい名称です。公式のサンプルコードでも両サンプルコードで同じfsが使われているので 注意してください。このページではstorage.localFileSystemはuxpfs、Node.jsチックなファイルシステム、fsモジュールは fsを変数名として使用します。また、このNode.jsチックなファイルシステムは Node.jsのfsモジュールと似てるようで別物なので注意してください。詳しくはこちらの記事参照
uxpモジュールは具体的にはユーザーの許可無しにアクセスができなくなったので 最低でも一度はダイアログを通じてローカルへのアクセスを許可をユーザーに貰わない限り プラグインが無断でファイルの保存をしたり開くといったことができなくりました。例えば以下の コードはMac上で取得したtiffファイルの保存のbatchPlayのコードです。alchemistなり「アクション記録」機能なり を使うと似たようなコードを取得できます。
filePath変数に「/Users/user/Desktop/file」のようにstring型のファイルパスを渡せば従来のExtendScriptでは 保存処理ができました。しかしUXPでstring型のパスを渡すと以下のようなエラーが出ます。
Error: invalid file token usedこちらのフォーラムでも話題に上がっているようにUXPからstring型のファイルパスではなくentry型のファイルパスが必要です。これはstring型と全く異なる型の Objectのみ受け取ります。これはUXPモジュールのstorage.localFileSystemメソッドより取得できます。以下サンプルコードになります。
manifest V5からmanifest上localFileSystemの設定をrequestにしないとstorage.localFileSystemの機能が制限されます。詳しくは公式もしくは当方ブログ参照。
注意公式のサンプルではuxp.storage.localFileSystemをfsとしてimportしていますがNode.jsのfsモジュールとは全く関係ありませんし完全に別物です。
上記コードを実行するとフォルダーを選ぶダイアログが開かれて特定のフォルダーを選択します。選択後にentry型のフォルダーファイルパスが取得できます。 実際にアクティブなドキュメントをjpsファイルで保存する場合は以下のようになります。
これで「app.jpg」というファイルが選択したフォルダー内に保存できます。既存のapiによる保存メソッドでもbatchPlayで取得できる保存メソッドでもこれは 必須となります。しかし上記メソッドはダイアログを呼び出すため一々呼び出すのが億劫になるでしょう。それではダイアログを通さずにローカルファイルにアクセスする 方法があるのかというと残念ながらCC2022の時点ではありません。それではこのentry型の中身はどうなっているのかというとこれはSymbolのキーを持つ 独自のオブジェクトです。つまり通常の型のオブジェクトでは無いのですが現状string型のパスをこの型のオブジェクトに変換するメソッドは 今の所ありません。なぜこのような仕様なのかというと何度も触れている通りセキュリティの関連からユーザーに無断でローカルファイルにアクセス できないようにするためのようです。
特定のファイルを開く
保存するためのファイルパスを取得するメソッドもあります。勿論保存メソッドのように必ずダイアログからアクセスする必要があります。 以下サンプルコード
fs.getFileForOpeningメソッドでファイルパスをentry型のパスとして取得しています。オプションallowMultipleで 複数のファイルを選択できるようになっているので複数のパスを開くようにPromise.allで非同期で全てのファイルを開いています。
応用 複数のファイルパスを一括で取得する
流石にダイアログで何度もファイルパスを選択するのは苦悩なのでもう少しこの過程を省略してみます。まず特定にディレクトリーから ファイルの一覧を取得します。サンプルコードは以下になります。
取得したentry型のフォルダーパスオブジェクトはgetEntriesというメソッドを所持しています。これは自身のフォルダー直下のファイルを 全て取得するメソッドです。これで従来のExtendScriptやNode.jsのようにファイルを再帰的に取得する事も可能です。
ダイアログから取得したentryフォルダーから再帰的にファイルを取得する関数です。 非同期処理に慣れたからたならなんてことのない再帰的関数ですね。 entryオブジェクト自身、isFileとisFolderでファイルかフォルダーかbooleanを返してくれるので簡単に分けることが可能です。 その他取得したentryフォルダーに指定のファイル名で画像を書き出す事も可能です。
entryfolderオブジェクトが保持するcreateEntryメソッドはstring型のファイル名を渡す事により entryフォルダーパスとファイル名を結合したパス(要するにnodeでいうpath.joinメソッドのようなもの) をもったentryファイルオブジェクトを返してくれます。 ダイアログで指定のフォルダーさえ取得すればscript側で決まったファイル名の保存が可能になります。
取得したEntryファイルパスを一時的に保存する。
ファイルパスを取得した後に何度も利用しようとする場合どこかに一時的に保存しなければいけません。 流石に同じパスファイルを何度もダイアログで選択して取得、というのは面倒でしょう。 しかし前述の通りこのEntry型のファイルパスはSymbolキーを持つ独自のオブジェクトです。 おそらく通常の方法ではオブジェクトとして保存できないでしょう。試しにlocalStorageにセットして みます。
結果やはりSymbol型のキーを持つオブジェクトがstorage保存の時にただのオブジェクトに変換されるため UXPシステム上で使えそうにはありません。
Persistent Token
しかしUXPにはこのファイルパスにアクセスするためのアクセスTokenを発行する機能があります。 UXPではこのTokenをstorageに保存して再度取り出せば既に取得したEntry型のファイルパスにアクセスできます。 以下サンプルコード。
createPersistentTokenメソッドにファイルパスを渡す事により該当のファイルパスにアクセスするためのTokenを取得できます。この TokenをlocalStorageに保存することにより後から目的のEntry型ファイルパスに再度アクセスできます。以下サンプルコード。
localStorageに保存したTokenを取り出してgetEntryForPersistentTokenメソッドでTokenから再度ファイルパスにアクセスできます。 同じファイルパスを何度も扱う時に一時的に保存するのに必須となります。 おそらくUXPではこのentry型のパスは一度取得した時にどこかに一時的に保持されているらしく、このTokenを使えばTokenが有効な限りいつでも一度取得したパスを取り出せます。 このTokenが具体的にいつまで有効なのかは、ちょっとわかっていません。
UXPのLocalFileSyetemに触れて
このUXPのLocalFileSyetemは他のファイルシステムと全く異なるとっつきにくいものではあります。 何よりセキュリティの厳しさから出来ることが限られる以上CEPでできていた事を 一部諦める必要もありました。しかし後述のファイルシステムを使うとアクセスの緩和も可能です。
Node.jsのようなファイルシステム
Photoshop24.0、uxp6.3.0~から新しいNode.jsチックなファイルシステムが追加されました。 従来のuxpfsモジュールはファイルアクセスが厳しく規制されていましたがmanifestで緩和を宣言して使用するとこにより 一々ダイアログを出したりしなくてもstring型のパスを渡してローカルディレクトリーにアクセスできるようになりました。 仕様もほとんどNode.jsのfsやpathモジュールと似ています。(ただし同じものではないので注意)
menifestによるfullAccessの宣言
まずローカルアクセスのメソッドを使う前にmanifestでアクセス緩和の宣言が必要になります。
つまり必然的にmanifest ver5を使う必要があります。これが設定できたら実際に使ってみましょう。fsモジュール
まずはfsモジュールです。Node.jsのfsモジュールとほとんど同じです。(ただし全く同じものではない) Node.js同様ファイルシステム関連のモジュールでファイルを移動させたり書き込んだり読み込んだりするモジュールです。実際に指定のファイルを読み込んでみましょう。
index.htmlというhtmlファイルを読み込むコードですがNode.jsに慣れている方は少し違和感があるでしょう。なぜならreadFileメソッドなのに fs.promisesを使わずPromiseが返ってくるからです。UXPのfsモジュールはpromisesを使わなくてもPromiseが返ってくるのpromisesは不要です。 コールバックを使いたい場合は各メソッドの最後の引数にコールバック関数を渡すと発火後にコールバックが動きます。 もちろんNode.js同様同期用のsyncメソッドもあリます。その他statメソッドがlstatだったりややこしい箇所が結構あるので必ず使用前にドキュメントを読む必要が あります。公式ドキュメントも参照。UXP7.0以前のバージョンだとファイルアクセスに(file://)プロトコルが必要でしたがUXP7.0以降はなくても動くようになっています。readFile(path, options, callback)
ファイルの中身を読み込むNode.jsでいうまんまreadFileです。通常Promiseを返しますが三つ目の引数にコールバックを渡すとPromiseでなく コールバックを実行する関数になります。Node.js同様同期タイプのreadFileSyncもあります。writeFile(path, data, options, callback)
Node.jsのまんまwriteFileメソッドです。これも三つ目のコールバック関数を省略するとPromiseを返す関数、省略しない場合は実行後に コールバックが動きます。これも同期タイプのwriteFileSyncがあります。- open(path, [flag], [mode], callback)
後述のファイルを書き込む、読み込むreadとwriteメソッドの前にファイルを開くメソッド。
- close(fd, callback)
後述のファイルを書き込む、読み込むreadとwriteメソッドの後にファイルを閉じるメソッド。
- read(fd, buffer, offset, length, position, callback)
ファイルを少しづつ細切れに読み込むメソッド。Node.jsでいうcreateReadStreamのように大きいファイルを扱うときにreadFileの代わりに使えばいいのでしょうか。 おそらく。
- write(fd, buffer, offset, length, position, callback)
ファイルを少しづつ細切れに書き込むメソッド。これもNode.jsでいうところのcreateWriteStreamのように大きいファイルを扱うときのメソッドだと思います。
- lstat(path, callback)これはNode.jsでいうところのfs.statなんでしょうけどなぜlstatというクラス名になったのでしょうか??? とりあえずNode.js同様ファイルを調べることができて同期タイプのlstatSyncもあります。
- rename(oldPath, newPath, callback)ファイルのリネーム、もしくは移動に使用します。Node.jsのrenameメソッドとほとんど同じです。
- copyFile(srcPath, destPath, flags, callback)ファイルをコピーします。Node.jsのcopyFIleメソッドとほとんど同じです。
- unlink(path, callback)ファイルを消去します。Node.jsのunlinkメソッドとほとんど同じです。
- mkdir(path, callback)フォルダーを作成します。Node.jsのmkdirメソッドとほとんど同じです。
- rmdir(path, callback)フォルダーを削除します。Node.jsのrmdirメソッドとほとんど同じです。
- readdir(path, callback)フォルダー内のファイルを読み込みます。Node.jsのreaddirとほとんど同じです。同期タイプのreaddirSyncもあります。
以下サンプルコードを載せておきます。
ファイルコピー
read and write
osモジュール
オペレートシステムを取得するためのモジュール。Node.jsのosモジュールとほとんど一緒らしいです。 違いがあるかどうかは不明ですがやはり使用には注意した方が良いでしょう。詳細は公式ドキュント参照。
サンプルコード
process
Node.jsのprocessとそっくりなグローバルオブジェクトもありますがこれも似て非なるものだと思った方が良さそうです。 以下のプロパティがあります。
- args
- env
- version
- versions
argsとかあるので引数を渡せそうな感じもありますがコマンドから実行できない以上どうやってUXPのシステムに引数を渡すか不明。 versionはuxpのバージョン。versionsは複数形になっているのでオブジェクトとしてuxp,v8,pluginのバージョンも格納されています。
pathモジュール
Node.jsでいうところのpathモジュールです。uxp ver6.4から使えるらしいので注意。公式ドキュメントも参照。
pathモジュールはグローバルオブジェクト扱いなので必ずrequireせずに使用してください。requireできるpathモジュールも以前からUXP内にデフォルトで ありますがこれはごく一部のメソッドしか使えない代物です。これをrequireしてpathの変数名で上書きすると使えないpathオブジェクトが参照されるので 必ずrequireしないでグローバルオブジェクトとして使用してください。
getEntryWithUrlメソッド
UXP6.5へのアップデート(Photoshopだと24.2以降)からgetEntryWithUrlメソッドが実装されました。 fsモジュールの追加でローカルファイルへのアクセスが緩和されたUXPですがPhotoshop自体のアプリケーションからのローカルへのアクセスは entry型のオブジェクトでの扱い、つまり従来通りダイアログからのアクセスまたはからなり遠回しなやり方でfsモジュールを使ってアクセス する必要がありました。しかしUXP6.5からこのgetEntryWithUrlメソッドを使用すればstring型のパスをentry型のオブジェクトに変換できます。 つまりfsモジュールで取得したローカルパスのstring型のパスをPhotoshopに渡して該当のファイルを開いたり保存先にすることができます。 使い方は簡単でstring型のパスをそのまま渡せばいいだけです。
以下、デスクトップ上のimageフォルダーの画像をダイアログを通さずにそのままPhotoshopで 開くサンプルです。
このようにPhotoshopから直接画像を開くことができます。 以下はPhotoshopで開いた画像をpsdとしてデスクトップのsavefolderフォルダーに保存するサンプル。
getEntryWithUrlメソッドはすでに存在するファイルパスをentry objectに変換するメソッドですが 全く新しいentry obejctパスを生成したい場合はcreateEntryWithUrlメソッドを使用してください。
ちなみにbatchPlayで保存処理を行う場合はentry ObjectをcreateSessionTokenメソッドにさらにを通す必要があります。 現状saveAsメソッドは tiffのフォーマットをサポートしていません。その他どうしても既存のAPIでアクセスできない保存形式で保存したい場合もbatchPlayを使います。
ただstring型とentry型のパスの扱いは常に区別できるようにはしておきたいです。
参考サイト
- 公式サイトリファレンス
- You Tube Davide Barranca
- UXP開発者にとって存在する三つのfs