Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial File Pickers: 'showOpenFilePicker', 'showSaveFilePicker', 'showDirectoryPicker' #765

Merged
merged 16 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 109 additions & 89 deletions api/README.md

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion api/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import ipc, { primordials } from './ipc.js'
import ApplicationWindow, { formatURL } from './window.js'
import { isValidPercentageValue } from './util.js'
import os from './os.js'

import * as exports from './application.js'

Expand Down Expand Up @@ -90,9 +91,15 @@ export async function createWindow (opts) {

/**
* Returns the current screen size.
* @returns {Promise<ipc.Result>}
* @returns {Promise<{ width: number, height: number }>}
*/
export async function getScreenSize () {
if (os.platform() === 'ios') {
return {
width: globalThis.screen.availWidth,
height: globalThis.screen.availHeight
}
}
const { data, err } = await ipc.send('application.getScreenSize', { index: globalThis.__args.index })
if (err) {
throw err
Expand All @@ -113,6 +120,17 @@ function throwOnInvalidIndex (index) {
* @throws {Error} - if indices is not an array of integer numbers
*/
export async function getWindows (indices) {
if (os.platform() === 'ios') {
return {
0: new ApplicationWindow({
index: 0,
width: globalThis.screen.availWidth,
height: globalThis.screen.availHeight,
title: document.title,
status: 31
})
}
}
// TODO: create a local registry and return from it when possible
const resultIndices = indices ?? []
if (!Array.isArray(resultIndices)) {
Expand Down
59 changes: 47 additions & 12 deletions api/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import * as exports from './errors.js'

export default exports

export const ABORT_ERR = 20
export const ENCODING_ERR = 32
export const INVALID_ACCESS_ERR = 15
export const INDEX_SIZE_ERR = 1
export const NETWORK_ERR = 19
export const NOT_ALLOWED_ERR = 31
export const NOT_FOUND_ERR = 8
export const NOT_SUPPORTED_ERR = 9
export const OPERATION_ERR = 30
export const TIMEOUT_ERR = 23
const DOMException = globalThis.DOMException ?? class DOMException extends Error {}

export const ABORT_ERR = DOMException?.ABORT_ERR ?? 20
export const ENCODING_ERR = DOMException?.ENCODING_ERR ?? 32
export const INVALID_ACCESS_ERR = DOMException?.INVALID_ACCESS_ERR ?? 15
export const INDEX_SIZE_ERR = DOMException?.INDEX_SIZE_ERR ?? 1
export const NETWORK_ERR = DOMException?.NETWORK_ERR ?? 19
export const NOT_ALLOWED_ERR = DOMException?.NOT_ALLOWED_ERR ?? 31
export const NOT_FOUND_ERR = DOMException?.NOT_FOUND_ERR ?? 8
export const NOT_SUPPORTED_ERR = DOMException?.NOT_SUPPORTED_ERR ?? 9
export const OPERATION_ERR = DOMException?.OPERATION_ERR ?? 30
export const SECURITY_ERR = DOMException?.SECURITY_ERR ?? 18
export const TIMEOUT_ERR = DOMException?.TIMEOUT_ERR ?? 23

/**
* An `AbortError` is an error type thrown in an `onabort()` level 0
Expand Down Expand Up @@ -187,7 +190,7 @@ export class IllegalConstructorError extends TypeError {
*/
export class IndexSizeError extends Error {
/**
* The code given to an `NOT_FOUND_ERR` `DOMException`
* The code given to an `INDEX_SIZE_ERR` `DOMException`
*/
static get code () { return INDEX_SIZE_ERR }

Expand Down Expand Up @@ -462,7 +465,7 @@ export class ModuleNotFoundError extends NotFoundError {
*/
export class OperationError extends Error {
/**
* The code given to an `NOT_FOUND_ERR` `DOMException`
* The code given to an `OPERATION_ERR` `DOMException`
*/
static get code () { return OPERATION_ERR }

Expand All @@ -488,6 +491,38 @@ export class OperationError extends Error {
}
}

/**
* An `SecurityError` is an error type thrown when an internal exception
* has occurred, such as in the native IPC layer.
*/
export class SecurityError extends Error {
/**
* The code given to an `SECURITY_ERR` `DOMException`
*/
static get code () { return SECURITY_ERR }

/**
* `SecurityError` class constructor.
* @param {string} message
* @param {number} [code]
*/
constructor (message, ...args) {
super(message, ...args)

if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, SecurityError)
}
}

get name () {
return 'SecurityError'
}

get code () {
return 'SECURITY_ERR'
}
}

/**
* An `TimeoutError` is an error type thrown when an operation timesout.
*/
Expand Down
8 changes: 4 additions & 4 deletions api/fs/dir.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ export class Dir {
} catch (err) {
if (typeof callback === 'function') {
callback(err)
return
return null
} else {
throw err
}

throw err
}

results = results.map((result) => {
Expand Down Expand Up @@ -170,7 +170,7 @@ export class Dir {
while (true) {
const results = await this.read(options)

if (results === null) {
if (results === null || results?.length === 0) {
break
}

Expand Down
66 changes: 33 additions & 33 deletions api/fs/handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export class FileHandle extends EventEmitter {
* @param {string=} [flags = 'r']
* @param {string|number=} [mode = 0o666]
* @param {object=} [options]
* @return {Promise<FileHandle>}
*/
static async open (path, flags, mode, options) {
if (flags === undefined) {
Expand Down Expand Up @@ -208,7 +209,7 @@ export class FileHandle extends EventEmitter {
async handle (id) {
if (fds.has(id)) {
console.warn('Closing fs.FileHandle on garbage collection')
await ipc.send('fs.close', { id }, options)
await ipc.request('fs.close', { id }, options)
fds.release(id, false)
}
}
Expand Down Expand Up @@ -274,7 +275,7 @@ export class FileHandle extends EventEmitter {

this[kClosing] = new InvertedPromise()

const result = await ipc.send('fs.close', { id: this.id }, options)
const result = await ipc.request('fs.close', { id: this.id }, options)

if (result.err) {
return this[kClosing].reject(result.err)
Expand Down Expand Up @@ -493,8 +494,8 @@ export class FileHandle extends EventEmitter {
)
}

if (isTypedArray(buffer)) {
buffer = Buffer.from(buffer.buffer) // from ArrayBuffer
if (isBufferLike(buffer)) {
buffer = buffer.buffer ?? buffer // ArrayBuffer
}

if (length > buffer.byteLength - offset) {
Expand Down Expand Up @@ -582,18 +583,10 @@ export class FileHandle extends EventEmitter {
return buffer
}

/**
* @param {object=} [options]
*/
async readv (buffers, position) {
if (this.closing || this.closed) {
throw new Error('FileHandle is not opened')
}
}

/**
* Returns the stats of the underlying file.
* @param {object=} [options]
* @return {Promise<Stats>}
*/
async stat (options) {
if (this.closing || this.closed) {
Expand All @@ -612,30 +605,35 @@ export class FileHandle extends EventEmitter {
}

/**
* @param {object=} [options]
* Synchronize a file's in-core state with storage device
* @return {Promise}
*/
async sync () {
if (this.closing || this.closed) {
throw new Error('FileHandle is not opened')
}
}

/**
* @param {object=} [options]
*/
async truncate (length) {
if (this.closing || this.closed) {
throw new Error('FileHandle is not opened')
const result = await ipc.request('fs.fsync', { id: this.id })

if (result.err) {
throw result.err
}
}

/**
* @param {object=} [options]
* @param {number} [offset = 0]
* @return {Promise}
*/
async utimes (atime, mtime) {
async truncate (offset = 0) {
if (this.closing || this.closed) {
throw new Error('FileHandle is not opened')
}

const result = await ipc.request('fs.ftruncate', { offset, id: this.id })

if (result.err) {
throw result.err
}
}

/**
Expand Down Expand Up @@ -769,12 +767,6 @@ export class FileHandle extends EventEmitter {
stream.once('error', reject)
})
}

/**
* @param {object=} [options]
*/
async writev (buffers, position) {
}
}

/**
Expand Down Expand Up @@ -818,6 +810,7 @@ export class DirectoryHandle extends EventEmitter {
* Asynchronously open a directory.
* @param {string | Buffer | URL} path
* @param {object=} [options]
* @return {Promise<DirectoryHandle>}
*/
static async open (path, options) {
const handle = new this({ path })
Expand Down Expand Up @@ -915,7 +908,7 @@ export class DirectoryHandle extends EventEmitter {
async handle (id) {
if (fds.has(id)) {
console.warn('Closing fs.DirectoryHandle on garbage collection')
await ipc.send('fs.closedir', { id }, options)
await ipc.request('fs.closedir', { id }, options)
fds.release(id, false)
}
}
Expand All @@ -925,6 +918,7 @@ export class DirectoryHandle extends EventEmitter {
/**
* Opens the underlying handle for a directory.
* @param {object=} options
* @return {Promise<boolean>}
*/
async open (options) {
if (this.opened) {
Expand Down Expand Up @@ -988,7 +982,7 @@ export class DirectoryHandle extends EventEmitter {

this[kClosing] = new InvertedPromise()

const result = await ipc.send('fs.closedir', { id }, options)
const result = await ipc.request('fs.closedir', { id }, options)

if (result.err) {
return this[kClosing].reject(result.err)
Expand All @@ -1010,8 +1004,9 @@ export class DirectoryHandle extends EventEmitter {
}

/**
* Reads
* Reads directory entries
* @param {object=} [options]
* @param {number=} [options.entries = DirectoryHandle.MAX_ENTRIES]
*/
async read (options) {
if (this[kOpening]) {
Expand All @@ -1026,7 +1021,12 @@ export class DirectoryHandle extends EventEmitter {
throw new AbortError(options.signal)
}

const entries = clamp(options.entries, 1, DirectoryHandle.MAX_ENTRIES)
const entries = clamp(
options?.entries || DirectoryHandle.MAX_ENTRIES,
1, // MIN_ENTRIES
DirectoryHandle.MAX_ENTRIES
)

const { id } = this

const result = await ipc.request('fs.readdir', {
Expand Down
51 changes: 50 additions & 1 deletion api/fs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ export function createWriteStream (path, options) {
*
* @param {number} fd - A file descriptor.
* @param {object?|function?} [options] - An options object.
* @param {function?} [callback] - The function to call after completion.
* @param {function?} callback - The function to call after completion.
*/
export function fstat (fd, options, callback) {
if (typeof options === 'function') {
Expand All @@ -330,6 +330,55 @@ export function fstat (fd, options, callback) {
}
}

/**
* Request that all data for the open file descriptor is flushed
* to the storage device.
* @param {number} fd - A file descriptor.
* @param {function} callback - The function to call after completion.
*/
export function fsync (fd, callback) {
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function.')
}

try {
FileHandle
.from(fd)
.sync()
.then(() => callback(null))
.catch((err) => callback(err))
} catch (err) {
callback(err)
}
}

/**
* Truncates the file up to `offset` bytes.
* @param {number} fd - A file descriptor.
* @param {number=|function} [offset = 0]
* @param {function?} callback - The function to call after completion.
*/
export function ftruncate (fd, offset, callback) {
if (typeof offset === 'function') {
callback = offset
offset = {}
}

if (typeof callback !== 'function') {
throw new TypeError('callback must be a function.')
}

try {
FileHandle
.from(fd)
.truncate(offset)
.then(() => callback(null))
.catch((err) => callback(err))
} catch (err) {
callback(err)
}
}

/**
* Chages ownership of link at `path` with `uid` and `gid.
* @param {string} path
Expand Down
Loading