Skip to content

Commit cd9e903

Browse files
authored
fix: convert date to mtime in glob source (#106)
The UnixFS importer requires mtimes to follow the `Mtime` interface so convert files/folders read from glob source.
1 parent 2421ee2 commit cd9e903

File tree

9 files changed

+147
-20
lines changed

9 files changed

+147
-20
lines changed

packages/interop/.aegir.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default {
1212
host: '127.0.0.1',
1313
port: ipfsdPort
1414
}, {
15-
ipfsBin: (await import('go-ipfs')).default.path(),
15+
ipfsBin: (await import('kubo')).default.path(),
1616
kuboRpcModule: kuboRpcClient,
1717
ipfsOptions: {
1818
config: {

packages/interop/package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"test:chrome": "aegir test -t browser --cov",
4545
"test:chrome-webworker": "aegir test -t webworker",
4646
"test:firefox": "aegir test -t browser -- --browser firefox",
47-
"test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
4847
"test:node": "aegir test -t node --cov",
4948
"test:electron-main": "aegir test -t electron-main"
5049
},
@@ -58,7 +57,7 @@
5857
"aegir": "^41.0.0",
5958
"blockstore-core": "^4.0.1",
6059
"datastore-core": "^9.0.3",
61-
"go-ipfs": "^0.22.0",
60+
"kubo": "^0.24.0",
6261
"helia": "^2.0.1",
6362
"ipfs-core-types": "^0.14.0",
6463
"ipfs-unixfs-importer": "^15.1.0",
@@ -72,7 +71,7 @@
7271
},
7372
"browser": {
7473
"./dist/test/fixtures/create-helia.js": "./dist/test/fixtures/create-helia.browser.js",
75-
"go-ipfs": false
74+
"kubo": false
7675
},
7776
"private": true
7877
}

packages/interop/test/fixtures/create-helia.browser.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { all } from '@libp2p/websockets/filters'
55
import { MemoryBlockstore } from 'blockstore-core'
66
import { MemoryDatastore } from 'datastore-core'
77
import { createHelia } from 'helia'
8+
import { bitswap } from 'helia/block-brokers'
89
import { createLibp2p, type Libp2pOptions } from 'libp2p'
910
import { identifyService } from 'libp2p/identify'
1011
import type { Helia } from '@helia/interface'
@@ -39,7 +40,10 @@ export async function createHeliaNode (config: Libp2pOptions = {}): Promise<Heli
3940
const helia = await createHelia({
4041
libp2p,
4142
blockstore,
42-
datastore
43+
datastore,
44+
blockBrokers: [
45+
bitswap()
46+
]
4347
})
4448

4549
return helia

packages/interop/test/fixtures/create-helia.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { tcp } from '@libp2p/tcp'
44
import { MemoryBlockstore } from 'blockstore-core'
55
import { MemoryDatastore } from 'datastore-core'
66
import { createHelia } from 'helia'
7+
import { bitswap } from 'helia/block-brokers'
78
import { createLibp2p, type Libp2pOptions } from 'libp2p'
89
import { identifyService } from 'libp2p/identify'
910
import type { Helia } from '@helia/interface'
@@ -32,7 +33,10 @@ export async function createHeliaNode (config: Libp2pOptions = {}): Promise<Heli
3233
const helia = await createHelia({
3334
libp2p,
3435
blockstore,
35-
datastore
36+
datastore,
37+
blockBrokers: [
38+
bitswap()
39+
]
3640
})
3741

3842
return helia

packages/interop/test/fixtures/create-kubo.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/prefer-ts-expect-error */
2-
// @ts-ignore no types - TODO: remove me once the next version of npm-go-ipfs has shipped
3-
import * as goIpfs from 'go-ipfs'
42
import { type Controller, type ControllerOptions, createController } from 'ipfsd-ctl'
3+
import * as goIpfs from 'kubo'
54
import * as kuboRpcClient from 'kubo-rpc-client'
65
import mergeOptions from 'merge-options'
76
import { isElectronMain, isNode } from 'wherearewe'

packages/unixfs/src/utils/glob-source.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fsp from 'fs/promises'
33
import Path from 'path'
44
import glob from 'it-glob'
55
import { InvalidParametersError } from '../errors.js'
6+
import { toMtime } from './to-mtime.js'
67
import type { MtimeLike } from 'ipfs-unixfs'
78
import type { ImportCandidateStream } from 'ipfs-unixfs-importer'
89

@@ -84,7 +85,7 @@ export async function * globSource (cwd: string, pattern: string, options: GlobS
8485
path: toPosix(p.replace(cwd, '')),
8586
content: stat.isFile() ? fs.createReadStream(p) : undefined,
8687
mode,
87-
mtime
88+
mtime: toMtime(mtime)
8889
}
8990
}
9091
}

packages/unixfs/src/utils/to-mtime.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { Mtime, MtimeLike } from 'ipfs-unixfs'
2+
3+
export function toMtime (mtimeLike: MtimeLike): Mtime
4+
export function toMtime (mtimeLike?: MtimeLike): Mtime | undefined
5+
export function toMtime (mtimeLike?: MtimeLike): Mtime | undefined {
6+
if (mtimeLike == null) {
7+
return undefined
8+
}
9+
10+
if (isMtime(mtimeLike)) {
11+
return mtimeLike
12+
}
13+
14+
if (mtimeLike instanceof Date) {
15+
return dateToTimespec(mtimeLike)
16+
}
17+
18+
if (Array.isArray(mtimeLike)) {
19+
const output: Mtime = {
20+
secs: BigInt(mtimeLike[0])
21+
}
22+
23+
if (mtimeLike.length > 1) {
24+
output.nsecs = mtimeLike[1]
25+
}
26+
27+
return output
28+
}
29+
30+
if (typeof mtimeLike.Seconds === 'number') {
31+
const output: Mtime = {
32+
secs: BigInt(mtimeLike.Seconds)
33+
}
34+
35+
if (mtimeLike.FractionalNanoseconds != null) {
36+
output.nsecs = mtimeLike.FractionalNanoseconds
37+
}
38+
39+
return output
40+
}
41+
42+
throw new Error('Cannot convert object to mtime')
43+
}
44+
45+
function dateToTimespec (date: Date): Mtime {
46+
const ms = date.getTime()
47+
const secs = Math.floor(ms / 1000)
48+
49+
return {
50+
secs: BigInt(secs),
51+
nsecs: (ms - (secs * 1000)) * 1000
52+
}
53+
}
54+
55+
function isMtime (obj: any): obj is Mtime {
56+
return typeof obj.secs === 'bigint'
57+
}

packages/unixfs/test/utils/glob-source.spec.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { expect } from 'aegir/chai'
77
import all from 'it-all'
88
import { isNode } from 'wherearewe'
99
import { globSource } from '../../src/utils/glob-source.js'
10+
import { toMtime } from '../../src/utils/to-mtime.js'
11+
import type { Mtime } from 'ipfs-unixfs'
1012

1113
function fixtureDir (): string {
1214
const filename = fileURLToPath(import.meta.url)
@@ -23,8 +25,8 @@ function findMode (file: string): number {
2325
return fs.statSync(fixture(file)).mode
2426
}
2527

26-
function findMtime (file: string): Date {
27-
return fs.statSync(fixture(file)).mtime
28+
function findMtime (file: string): Mtime {
29+
return toMtime(fs.statSync(fixture(file)).mtime)
2830
}
2931

3032
describe('glob-source', () => {
@@ -228,28 +230,28 @@ describe('glob-source', () => {
228230
}
229231

230232
const result = await all(globSource(fixtureDir(), '{dir,dir/**/*}', {
231-
mtime: new Date(5)
233+
mtime: toMtime(new Date(5))
232234
}))
233235

234236
expect(result).to.have.lengthOf(6)
235237
expect(result).to.containSubset([{
236238
path: '/dir',
237-
mtime: new Date(5)
239+
mtime: toMtime(new Date(5))
238240
}, {
239241
path: '/dir/file-1.txt',
240-
mtime: new Date(5)
242+
mtime: toMtime(new Date(5))
241243
}, {
242244
path: '/dir/file-2.js',
243-
mtime: new Date(5)
245+
mtime: toMtime(new Date(5))
244246
}, {
245247
path: '/dir/file-3.css',
246-
mtime: new Date(5)
248+
mtime: toMtime(new Date(5))
247249
}, {
248250
path: '/dir/nested-dir',
249-
mtime: new Date(5)
251+
mtime: toMtime(new Date(5))
250252
}, {
251253
path: '/dir/nested-dir/other.txt',
252-
mtime: new Date(5)
254+
mtime: toMtime(new Date(5))
253255
}])
254256
})
255257

@@ -274,7 +276,7 @@ describe('glob-source', () => {
274276
mtime: [5, 0]
275277
}))
276278

277-
expect(result).to.have.deep.nested.property('[0].mtime', [5, 0])
279+
expect(result).to.have.deep.nested.property('[0].mtime', toMtime([5, 0]))
278280
})
279281

280282
it('overrides mtime for file with UnixFS timespec', async function () {
@@ -286,6 +288,6 @@ describe('glob-source', () => {
286288
mtime: { Seconds: 5, FractionalNanoseconds: 0 }
287289
}))
288290

289-
expect(result).to.have.deep.nested.property('[0].mtime', { Seconds: 5, FractionalNanoseconds: 0 })
291+
expect(result).to.have.deep.nested.property('[0].mtime', toMtime({ Seconds: 5, FractionalNanoseconds: 0 }))
290292
})
291293
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-env mocha */
2+
3+
import { expect } from 'aegir/chai'
4+
import { toMtime } from '../../src/utils/to-mtime.js'
5+
6+
describe('to-mtime', () => {
7+
it('should survive undefined', async function () {
8+
const result = toMtime()
9+
10+
expect(result).to.equal(undefined)
11+
})
12+
13+
it('should convert a date', async function () {
14+
const input = new Date()
15+
const result = toMtime(input)
16+
17+
expect(result?.secs).to.equal(BigInt(Math.floor(input.getTime() / 1000)))
18+
})
19+
20+
it('should convert a timespec', async function () {
21+
const input = {
22+
Seconds: 100
23+
}
24+
const result = toMtime(input)
25+
26+
expect(result?.secs).to.equal(BigInt(input.Seconds))
27+
expect(result?.nsecs).to.be.undefined()
28+
})
29+
30+
it('should convert a timespec with fractional nanoseconds', async function () {
31+
const input = {
32+
Seconds: 100,
33+
FractionalNanoseconds: 5
34+
}
35+
const result = toMtime(input)
36+
37+
expect(result?.secs).to.equal(BigInt(input.Seconds))
38+
expect(result?.nsecs).to.equal(input.FractionalNanoseconds)
39+
})
40+
41+
it('should convert a mtime', async function () {
42+
const input = {
43+
secs: 100n
44+
}
45+
const result = toMtime(input)
46+
47+
expect(result?.secs).to.equal(input.secs)
48+
expect(result?.nsecs).to.be.undefined()
49+
})
50+
51+
it('should convert a mtime with fractional nanoseconds', async function () {
52+
const input = {
53+
secs: 100n,
54+
nsecs: 5
55+
}
56+
const result = toMtime(input)
57+
58+
expect(result?.secs).to.equal(input.secs)
59+
expect(result?.nsecs).to.equal(input.nsecs)
60+
})
61+
})

0 commit comments

Comments
 (0)