Joe Duncan is correct (and thanks for the tip!)
But you need to open the writeable file in 'append' mode.
I have need to close/flush the file after every line is written, and then re-open, seek and append to that file (but only 1 click to the SaveFile button)
import {} from "wicg-file-system-access"
/**
* Supply a button-id in HTML, when user clicks the file is opened for write-append.
*
* Other code can: new LogWriter().writeLine("First line...")
* to queue writes before user clicks the button.
*
* file is flushed/closed & re-opened after every writeLine.
* (so log is already saved if browser crashes...)
*/
export class LogWriter {
fileHandle: FileSystemFileHandle;
writeablePromise: Promise<FileSystemWritableFileStream> = this.newWriterPromise;
writerReady: (value: FileSystemWritableFileStream | PromiseLike<FileSystemWritableFileStream>) => void
writerFailed: (reason?: any) => void
contents: string
get newOpenPromise () { return new Promise<FileSystemWritableFileStream>((fil, rej)=>{
this.writerReady = fil;
this.writerFailed = rej
})}
constructor(name = 'logFile', public buttonId = "fsOpenFileButton") {
const options = {
id: 'logWriter',
startIn: 'downloads', // documents, desktop, music, pictures, videos
suggestedName: name,
types: [{
description: 'Text Files',
accept: { 'text/plain': ['.txt'], },
}, ],
};
this.setButton('showSaveFilePicker', options, (value) => {
this.fileHandle = value as FileSystemFileHandle
this.openWriteable()
})
}
async openWriteable(fileHandle: FileSystemFileHandle = this.fileHandle,
options: FileSystemCreateWritableOptions = { keepExistingData: true }) {
let writeable = await fileHandle.createWritable(options)
let offset = (await fileHandle.getFile()).size
writeable.seek(offset)
this.writerReady(writeable)
}
async writeLine(text: string) {
try {
let line = `${text}\n`
let stream = (await this.openPromise) // indicates writeable is ready
await stream.seek((await this.fileHandle.getFile()).size)
await stream.write({type: 'write', data: line});
let closePromise = this.closeFile() // flush to real-file
this.openPromise = this.newOpenPromise // new Promise for next cycle:
await closePromise
while (!this.openPromise.value) await this.openWriteable()
} catch (err) {
console.warn(stime(this, `.writeLine failed:`), err)
throw err
}
}
async closeFile() {
try {
return (await this.writeablePromise).close();
} catch (err) {
console.warn(stime(this, `.closeFile failed:`), err)
throw err
}
}
/** multi-purpose picker button: (callback arg-type changes) */
setButton(method: 'showOpenFilePicker' | 'showSaveFilePicker' | 'showDirectoryPicker',
options: OpenFilePickerOptions & { multiple?: false; } & SaveFilePickerOptions & DirectoryPickerOptions,
cb: (fileHandleAry: any) => void) {
const picker = window[method] // showSaveFilePicker showDirectoryPicker
const fsOpenButton = document.getElementById(this.buttonId)
fsOpenButton.innerText = method.substring(4, method.length - 6)
fsOpenButton.onclick = () => {
picker(options).then((value: any) => cb(value), (rej: any) => {
console.warn(`showOpenFilePicker failed: `, rej)
});
}
return fsOpenButton
}
}