11import { directoryHandleStoreKey } from "./contract" ;
22import { openDB } from "./browserConfig" ;
3+ import { createFakePath , FakePath , isFakePath } from "./fakePath" ;
34import { SandboxKey } from "@/type/preload" ;
45import { failure , success } from "@/type/result" ;
56import { createLogger } from "@/domain/frontend/log" ;
6- import { uuid4 } from "@/helpers/random" ;
77import { normalizeError } from "@/helpers/normalizeError" ;
88import path from "@/helpers/path" ;
9+ import { ExhaustiveError } from "@/type/utility" ;
910
1011const log = createLogger ( "fileImpl" ) ;
1112
@@ -113,45 +114,81 @@ const getDirectoryHandleFromDirectoryPath = async (
113114 }
114115} ;
115116
117+ export type WritableFilePath =
118+ | {
119+ // ファイル名のみ。ダウンロードとして扱われます。
120+ type : "nameOnly" ;
121+ path : string ;
122+ }
123+ | {
124+ // ディレクトリ内への書き込み。
125+ type : "child" ;
126+ path : string ;
127+ }
128+ | {
129+ // 疑似パス。
130+ type : "fake" ;
131+ path : FakePath ;
132+ } ;
133+
116134// NOTE: fixedExportEnabled が有効になっている GENERATE_AND_SAVE_AUDIO action では、ファイル名に加えディレクトリ名も指定された状態でfilePathが渡ってくる
117135// また GENERATE_AND_SAVE_ALL_AUDIO action では fixedExportEnabled の有効の有無に関わらず、ディレクトリ名も指定された状態でfilePathが渡ってくる
118- export const writeFileImpl : ( typeof window ) [ typeof SandboxKey ] [ "writeFile" ] =
119- async ( obj : { filePath : string ; buffer : ArrayBuffer } ) => {
120- const filePath = obj . filePath ;
136+ // showExportFilePicker での疑似パスが渡ってくる可能性もある。
137+ export const writeFileImpl = async ( obj : {
138+ filePath : WritableFilePath ;
139+ buffer : ArrayBuffer ;
140+ } ) => {
141+ const filePath = obj . filePath ;
121142
122- if ( ! filePath . includes ( path . SEPARATOR ) ) {
143+ switch ( filePath . type ) {
144+ case "fake" : {
145+ const fileHandle = fileHandleMap . get ( filePath . path ) ;
146+ if ( fileHandle == undefined ) {
147+ return failure ( new Error ( `ファイルが見つかりません: ${ filePath . path } ` ) ) ;
148+ }
149+ const writable = await fileHandle . createWritable ( ) ;
150+ await writable . write ( obj . buffer ) ;
151+ return writable . close ( ) . then ( ( ) => success ( undefined ) ) ;
152+ }
153+
154+ case "nameOnly" : {
123155 const aTag = document . createElement ( "a" ) ;
124156 const blob = URL . createObjectURL ( new Blob ( [ obj . buffer ] ) ) ;
125157 aTag . href = blob ;
126- aTag . download = filePath ;
158+ aTag . download = filePath . path ;
127159 document . body . appendChild ( aTag ) ;
128160 aTag . click ( ) ;
129161 document . body . removeChild ( aTag ) ;
130162 URL . revokeObjectURL ( blob ) ;
131163 return success ( undefined ) ;
132164 }
133165
134- const fileName = resolveFileName ( filePath ) ;
135- const maybeDirectoryHandleName = resolveDirectoryName ( filePath ) ;
166+ case "child" : {
167+ const fileName = resolveFileName ( filePath . path ) ;
168+ const maybeDirectoryHandleName = resolveDirectoryName ( filePath . path ) ;
136169
137- const directoryHandle = await getDirectoryHandleFromDirectoryPath (
138- maybeDirectoryHandleName ,
139- ) ;
170+ const directoryHandle = await getDirectoryHandleFromDirectoryPath (
171+ maybeDirectoryHandleName ,
172+ ) ;
140173
141- directoryHandleMap . set ( maybeDirectoryHandleName , directoryHandle ) ;
174+ directoryHandleMap . set ( maybeDirectoryHandleName , directoryHandle ) ;
142175
143- return directoryHandle
144- . getFileHandle ( fileName , { create : true } )
145- . then ( async ( fileHandle ) => {
146- const writable = await fileHandle . createWritable ( ) ;
147- await writable . write ( obj . buffer ) ;
148- return writable . close ( ) ;
149- } )
150- . then ( ( ) => success ( undefined ) )
151- . catch ( ( e ) => {
152- return failure ( normalizeError ( e ) ) ;
153- } ) ;
154- } ;
176+ return directoryHandle
177+ . getFileHandle ( fileName , { create : true } )
178+ . then ( async ( fileHandle ) => {
179+ const writable = await fileHandle . createWritable ( ) ;
180+ await writable . write ( obj . buffer ) ;
181+ return writable . close ( ) ;
182+ } )
183+ . then ( ( ) => success ( undefined ) )
184+ . catch ( ( e ) => {
185+ return failure ( normalizeError ( e ) ) ;
186+ } ) ;
187+ }
188+ default :
189+ throw new ExhaustiveError ( filePath ) ;
190+ }
191+ } ;
155192
156193export const checkFileExistsImpl : ( typeof window ) [ typeof SandboxKey ] [ "checkFileExists" ] =
157194 async ( filePath ) => {
@@ -182,7 +219,7 @@ export const checkFileExistsImpl: (typeof window)[typeof SandboxKey]["checkFileE
182219 } ;
183220
184221// FileSystemFileHandleを保持するMap。キーは生成した疑似パス。
185- const fileHandleMap : Map < string , FileSystemFileHandle > = new Map ( ) ;
222+ const fileHandleMap : Map < FakePath , FileSystemFileHandle > = new Map ( ) ;
186223
187224// ファイル選択ダイアログを開く
188225// 返り値はファイルパスではなく、疑似パスを返す
@@ -201,7 +238,7 @@ export const showOpenFilePickerImpl = async (options: {
201238 } ) ;
202239 const paths = [ ] ;
203240 for ( const handle of handles ) {
204- const fakePath = `<browser-dummy- ${ uuid4 ( ) } >- ${ handle . name } ` ;
241+ const fakePath = createFakePath ( handle . name ) ;
205242 fileHandleMap . set ( fakePath , handle ) ;
206243 paths . push ( fakePath ) ;
207244 }
@@ -214,6 +251,9 @@ export const showOpenFilePickerImpl = async (options: {
214251
215252// 指定した疑似パスのファイルを読み込む
216253export const readFileImpl = async ( filePath : string ) => {
254+ if ( ! isFakePath ( filePath ) ) {
255+ return failure ( new Error ( `疑似パスではありません: ${ filePath } ` ) ) ;
256+ }
217257 const fileHandle = fileHandleMap . get ( filePath ) ;
218258 if ( fileHandle == undefined ) {
219259 return failure ( new Error ( `ファイルが見つかりません: ${ filePath } ` ) ) ;
@@ -222,3 +262,29 @@ export const readFileImpl = async (filePath: string) => {
222262 const buffer = await file . arrayBuffer ( ) ;
223263 return success ( buffer ) ;
224264} ;
265+
266+ // ファイル選択ダイアログを開く
267+ // 返り値はファイルパスではなく、疑似パスを返す
268+ export const showExportFilePickerImpl : ( typeof window ) [ typeof SandboxKey ] [ "showExportFileDialog" ] =
269+ async ( obj : {
270+ defaultPath ?: string ;
271+ extensionName : string ;
272+ extensions : string [ ] ;
273+ title : string ;
274+ } ) => {
275+ const handle = await showSaveFilePicker ( {
276+ suggestedName : obj . defaultPath ,
277+ types : [
278+ {
279+ description : obj . extensions . join ( "、" ) ,
280+ accept : {
281+ "application/octet-stream" : obj . extensions . map ( ( ext ) => `.${ ext } ` ) ,
282+ } ,
283+ } ,
284+ ] ,
285+ } ) ;
286+ const fakePath = createFakePath ( handle . name ) ;
287+ fileHandleMap . set ( fakePath , handle ) ;
288+
289+ return fakePath ;
290+ } ;
0 commit comments