Skip to content

Commit add1568

Browse files
authored
Merge pull request #287 from effigies/fix/s3
feat: Fall back to an empty file for absent symlinks with no public S3 URL
2 parents 91dcc3d + b8750e2 commit add1568

File tree

5 files changed

+45
-36
lines changed

5 files changed

+45
-36
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Changed
2+
3+
- [Annexed](https://git-annex.branchable.com/) files that are not local are treated as
4+
empty instead of erroring if a remote URL could not be constructed. The size of missing
5+
files are included in the summary estimate of dataset size.

src/files/deno.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import * as posix from '@std/path/posix'
66
import { BIDSFile, type FileOpener, FileTree } from '../types/filetree.ts'
77
import { requestReadPermission } from '../setup/requestPermissions.ts'
88
import { FileIgnoreRules, readBidsIgnore } from './ignore.ts'
9-
import { FsFileOpener, HTTPOpener } from './openers.ts'
10-
import { resolveAnnexedFile } from './repo.ts'
9+
import { FsFileOpener, HTTPOpener, NullFileOpener } from './openers.ts'
10+
import { parseAnnexedFile, resolveAnnexedFile } from './repo.ts'
1111
import fs from 'node:fs'
1212

1313
export class BIDSFileDeno extends BIDSFile {
@@ -51,12 +51,13 @@ async function _readFileTree({
5151
try {
5252
const fileInfo = await Deno.stat(fullPath)
5353
opener = new FsFileOpener(rootPath, thisPath, fileInfo)
54-
} catch (error) {
54+
} catch (_) {
55+
const { key, size, gitdir } = await parseAnnexedFile(fullPath)
5556
try {
56-
const { url, size } = await resolveAnnexedFile(fullPath, preferredRemote, { cache, fs })
57+
const { url } = await resolveAnnexedFile(key, preferredRemote, { cache, fs, gitdir })
5758
opener = new HTTPOpener(url, size)
5859
} catch (_) {
59-
throw error
60+
opener = new NullFileOpener(size)
6061
}
6162
}
6263
tree.files.push(new BIDSFile(thisPath, opener, ignore, tree))

src/files/openers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,18 @@ export class HTTPOpener implements FileOpener {
126126
return new Uint8Array(await response.arrayBuffer())
127127
}
128128
}
129+
130+
export class NullFileOpener implements FileOpener {
131+
size: number
132+
constructor(size = 0) {
133+
this.size = size
134+
}
135+
stream = async () =>
136+
new ReadableStream({
137+
start(controller) {
138+
controller.close()
139+
},
140+
})
141+
text = async () => ''
142+
readBytes = async (size: number, offset?: number) => new Uint8Array()
143+
}

src/files/repo.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,29 @@ export async function readRemotes(options: any): Promise<Record<string, Record<s
8484
return byUUID
8585
}
8686

87-
/**
88-
* Resolve an annexed file location to an HTTP URL, if a public S3 remote is available
89-
*/
90-
export async function resolveAnnexedFile(
87+
export async function parseAnnexedFile(
9188
path: string,
92-
remote?: string,
93-
options?: any,
94-
): Promise<{ url: string; size: number }> {
95-
// path is known to be a symlink
89+
): Promise<{ key: string; size: number; gitdir: string }> {
9690
const target = await Deno.readLink(path)
9791
const { dir, base } = parse(target)
9892

99-
if (!options?.gitdir) {
100-
const dirs = dir.split(SEPARATOR_PATTERN)
101-
const gitdir = join(dirname(path), ...dirs.slice(0, dirs.indexOf('.git') + 1))
102-
options = { ...options, gitdir }
103-
}
93+
const dirs = dir.split(SEPARATOR_PATTERN)
94+
const gitdir = join(dirname(path), ...dirs.slice(0, dirs.indexOf('.git') + 1))
10495

10596
const size = +base.match(annexKeyRegex)?.groups?.size!
10697

107-
const rmet = await readRmet(base, options)
98+
return { key: base, size, gitdir }
99+
}
100+
101+
/**
102+
* Resolve an annexed file location to an HTTP URL, if a public S3 remote is available
103+
*/
104+
export async function resolveAnnexedFile(
105+
key: string,
106+
remote?: string,
107+
options?: any,
108+
): Promise<{ url: string }> {
109+
const rmet = await readRmet(key, options)
108110
const remotes = await readRemotes(options)
109111
let uuid: string
110112
if (remote) {
@@ -137,5 +139,5 @@ export async function resolveAnnexedFile(
137139
const metadata = rmet[uuid]
138140
const url = `${publicurl}/${metadata.path}?versionId=${metadata.version}`
139141

140-
return { url, size }
142+
return { url }
141143
}

src/schema/walk.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BIDSContext, type BIDSContextDataset } from './context.ts'
2-
import { BIDSFile, FileOpener, FileTree } from '../types/filetree.ts'
2+
import { BIDSFile, type FileTree } from '../types/filetree.ts'
33
import type { DatasetIssues } from '../issues/datasetIssues.ts'
4+
import { NullFileOpener } from '../files/openers.ts'
45
import { loadTSV } from '../files/tsv.ts'
56
import { loadJSON } from '../files/json.ts'
67
import { queuedAsyncIterator } from '../utils/queue.ts'
@@ -14,21 +15,6 @@ function* quickWalk(dir: FileTree): Generator<BIDSFile> {
1415
}
1516
}
1617

17-
class NullFileOpener implements FileOpener {
18-
size: number
19-
constructor(size = 0) {
20-
this.size = size
21-
}
22-
stream = async () =>
23-
new ReadableStream({
24-
start(controller) {
25-
controller.close()
26-
},
27-
})
28-
text = async () => ''
29-
readBytes = async (size: number, offset?: number) => new Uint8Array()
30-
}
31-
3218
function pseudoFile(dir: FileTree, opaque: boolean): BIDSFile {
3319
return new BIDSFile(
3420
// Use a trailing slash to indicate directory

0 commit comments

Comments
 (0)