Skip to content

Commit f0f3d26

Browse files
committed
rf: Consolidate file openers
1 parent 4b3bca8 commit f0f3d26

File tree

3 files changed

+134
-94
lines changed

3 files changed

+134
-94
lines changed

src/files/browser.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
1-
import { BIDSFile, type FileOpener, FileTree } from '../types/filetree.ts'
1+
import { BIDSFile, FileTree } from '../types/filetree.ts'
22
import { filesToTree } from './filetree.ts'
33
import { FileIgnoreRules, readBidsIgnore } from './ignore.ts'
4-
import { parse, SEPARATOR_PATTERN } from '@std/path'
5-
import * as posix from '@std/path/posix'
6-
7-
class BrowserFileOpener implements FileOpener {
8-
file: File
9-
constructor(file: File) {
10-
this.file = file
11-
}
12-
13-
get size(): number {
14-
return this.file.size
15-
}
16-
17-
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
18-
return Promise.resolve(this.file.stream() as ReadableStream<Uint8Array<ArrayBuffer>>)
19-
}
20-
21-
async text(): Promise<string> {
22-
return this.file.text()
23-
}
24-
25-
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
26-
return new Uint8Array(await this.file.slice(offset, size).arrayBuffer())
27-
}
28-
}
4+
import { BrowserFileOpener } from './openers.ts'
295

306
/**
317
* Browser implement of BIDSFile wrapping native File/FileList types

src/files/deno.ts

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,14 @@
33
*/
44
import { basename, join } from '@std/path'
55
import * as posix from '@std/path/posix'
6-
import { BIDSFile, type FileOpener, FileTree } from '../types/filetree.ts'
6+
import { BIDSFile, FileTree } from '../types/filetree.ts'
77
import { requestReadPermission } from '../setup/requestPermissions.ts'
88
import { FileIgnoreRules, readBidsIgnore } from './ignore.ts'
9-
import { logger } from '../utils/logger.ts'
10-
import { createUTF8Stream } from './streams.ts'
11-
12-
class DenoOpener implements FileOpener {
13-
path: string
14-
#fileInfo!: Deno.FileInfo
15-
16-
constructor(datasetPath: string, path: string) {
17-
this.path = join(datasetPath, path)
18-
try {
19-
this.#fileInfo = Deno.statSync(this.path)
20-
} catch (error) {
21-
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
22-
this.#fileInfo = Deno.lstatSync(this.path)
23-
}
24-
}
25-
}
26-
27-
get size(): number {
28-
return this.#fileInfo ? this.#fileInfo.size : -1
29-
}
30-
31-
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
32-
const handle = await this.#openHandle()
33-
return handle.readable
34-
}
35-
36-
/**
37-
* Read the entire file and decode as utf-8 text
38-
*/
39-
async text(): Promise<string> {
40-
const stream = await this.stream()
41-
const reader = stream.pipeThrough(createUTF8Stream()).getReader()
42-
const chunks: string[] = []
43-
try {
44-
while (true) {
45-
const { done, value } = await reader.read()
46-
if (done) break
47-
chunks.push(value)
48-
}
49-
return chunks.join('')
50-
} finally {
51-
reader.releaseLock()
52-
}
53-
}
54-
55-
/**
56-
* Read bytes in a range efficiently from a given file
57-
*
58-
* Reads up to size bytes, starting at offset.
59-
* If EOF is encountered, the resulting array may be smaller.
60-
*/
61-
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
62-
const handle = await this.#openHandle()
63-
const buf = new Uint8Array(size)
64-
await handle.seek(offset, Deno.SeekMode.Start)
65-
const nbytes = await handle.read(buf) ?? 0
66-
await handle.close()
67-
return buf.subarray(0, nbytes)
68-
}
69-
70-
async #openHandle(): Promise<Deno.FsFile> {
71-
return Deno.open(this.path, { read: true, write: false })
72-
}
73-
}
9+
import { FsFileOpener } from './openers.ts'
7410

7511
export class BIDSFileDeno extends BIDSFile {
7612
constructor(datasetPath: string, path: string, ignore?: FileIgnoreRules, parent?: FileTree) {
77-
super(path, new DenoOpener(datasetPath, path), ignore, parent)
13+
super(path, new FsFileOpener(datasetPath, path), ignore, parent)
7814
}
7915
}
8016

@@ -97,7 +33,7 @@ async function _readFileTree(
9733
if (dirEntry.isFile || dirEntry.isSymlink) {
9834
const file = new BIDSFile(
9935
thisPath,
100-
new DenoOpener(rootPath, thisPath),
36+
new FsFileOpener(rootPath, thisPath),
10137
ignore,
10238
tree,
10339
)

src/files/openers.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* BIDS file openers
3+
*
4+
* These classes implement stream, text and random bytes access to BIDS resources.
5+
*/
6+
import { join } from '@std/path'
7+
import { type FileOpener } from '../types/filetree.ts'
8+
import { createUTF8Stream } from './streams.ts'
9+
10+
export class FsFileOpener implements FileOpener {
11+
path: string
12+
fileInfo!: Deno.FileInfo
13+
14+
constructor(datasetPath: string, path: string, fileInfo?: Deno.FileInfo) {
15+
this.path = join(datasetPath, path)
16+
if (fileInfo) {
17+
this.fileInfo = fileInfo
18+
} else {
19+
try {
20+
this.fileInfo = Deno.statSync(this.path)
21+
} catch (error) {
22+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
23+
this.fileInfo = Deno.lstatSync(this.path)
24+
}
25+
}
26+
}
27+
}
28+
29+
get size(): number {
30+
return this.fileInfo.size
31+
}
32+
33+
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
34+
const handle = await this.open()
35+
return handle.readable
36+
}
37+
38+
/**
39+
* Read the entire file and decode as utf-8 text
40+
*/
41+
async text(): Promise<string> {
42+
const stream = await this.stream()
43+
const reader = stream.pipeThrough(createUTF8Stream()).getReader()
44+
const chunks: string[] = []
45+
try {
46+
while (true) {
47+
const { done, value } = await reader.read()
48+
if (done) break
49+
chunks.push(value)
50+
}
51+
return chunks.join('')
52+
} finally {
53+
reader.releaseLock()
54+
}
55+
}
56+
57+
/**
58+
* Read bytes in a range efficiently from a given file
59+
*
60+
* Reads up to size bytes, starting at offset.
61+
* If EOF is encountered, the resulting array may be smaller.
62+
*/
63+
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
64+
const handle = await this.open()
65+
const buf = new Uint8Array(size)
66+
await handle.seek(offset, Deno.SeekMode.Start)
67+
const nbytes = await handle.read(buf) ?? 0
68+
await handle.close()
69+
return buf.subarray(0, nbytes)
70+
}
71+
72+
async open(): Promise<Deno.FsFile> {
73+
return Deno.open(this.path, { read: true, write: false })
74+
}
75+
}
76+
77+
export class BrowserFileOpener implements FileOpener {
78+
file: File
79+
constructor(file: File) {
80+
this.file = file
81+
}
82+
83+
get size(): number {
84+
return this.file.size
85+
}
86+
87+
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
88+
return Promise.resolve(this.file.stream() as ReadableStream<Uint8Array<ArrayBuffer>>)
89+
}
90+
91+
async text(): Promise<string> {
92+
return this.file.text()
93+
}
94+
95+
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
96+
return new Uint8Array(await this.file.slice(offset, size).arrayBuffer())
97+
}
98+
}
99+
100+
export class HTTPOpener implements FileOpener {
101+
url: string
102+
size: number
103+
104+
constructor(url: string, size: number = -1) {
105+
this.url = url
106+
this.size = size
107+
}
108+
109+
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
110+
const response = await fetch(this.url)
111+
if (!response.ok || !response.body) {
112+
throw new Error(`Failed to fetch ${this.url}: ${response.status} ${response.statusText}`)
113+
}
114+
return response.body
115+
}
116+
117+
async text(): Promise<string> {
118+
const response = await fetch(this.url)
119+
return response.text()
120+
}
121+
122+
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
123+
const headers = new Headers()
124+
headers.append('Range', `bytes=${offset}-${offset + size - 1}`)
125+
const response = await fetch(this.url, { headers })
126+
return new Uint8Array(await response.arrayBuffer())
127+
}
128+
}

0 commit comments

Comments
 (0)