@comapeo/core 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blob-store/entries-stream.d.ts.map +1 -1
- package/dist/blob-store/index.d.ts +27 -15
- package/dist/blob-store/index.d.ts.map +1 -1
- package/dist/core-manager/index.d.ts +4 -4
- package/dist/core-manager/index.d.ts.map +1 -1
- package/dist/generated/extensions.d.ts +7 -0
- package/dist/generated/extensions.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/sync/core-sync-state.d.ts +14 -6
- package/dist/sync/core-sync-state.d.ts.map +1 -1
- package/dist/sync/namespace-sync-state.d.ts +3 -13
- package/dist/sync/namespace-sync-state.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts +17 -25
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/sync/sync-state.d.ts +3 -13
- package/dist/sync/sync-state.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/blob-store/entries-stream.js +43 -18
- package/src/blob-store/index.js +161 -19
- package/src/core-manager/index.js +14 -7
- package/src/generated/extensions.d.ts +7 -0
- package/src/generated/extensions.js +12 -2
- package/src/generated/extensions.ts +19 -1
- package/src/mapeo-project.js +7 -76
- package/src/sync/core-sync-state.js +41 -14
- package/src/sync/namespace-sync-state.js +25 -22
- package/src/sync/sync-api.js +12 -43
- package/src/sync/sync-state.js +9 -19
- package/src/types.ts +1 -1
|
@@ -2,6 +2,7 @@ import SubEncoder from 'sub-encoder'
|
|
|
2
2
|
import mergeStreams from '@sindresorhus/merge-streams'
|
|
3
3
|
import { Transform, pipeline } from 'node:stream'
|
|
4
4
|
import { noop } from '../utils.js'
|
|
5
|
+
import ensureError from 'ensure-error'
|
|
5
6
|
|
|
6
7
|
/** @import Hyperdrive from 'hyperdrive' */
|
|
7
8
|
/** @import { BlobStoreEntriesStream } from '../types.js' */
|
|
@@ -18,7 +19,7 @@ const keyEncoding = new SubEncoder('files', 'utf-8')
|
|
|
18
19
|
*/
|
|
19
20
|
export function createEntriesStream(driveIndex, { live = false } = {}) {
|
|
20
21
|
const mergedEntriesStreams = mergeStreams(
|
|
21
|
-
[...driveIndex].map((drive) => getHistoryStream(drive
|
|
22
|
+
[...driveIndex].map((drive) => getHistoryStream(drive, { live }))
|
|
22
23
|
)
|
|
23
24
|
driveIndex.on('add-drive', addDrive)
|
|
24
25
|
// Close is always emitted, so we can use it to remove the listener
|
|
@@ -29,53 +30,77 @@ export function createEntriesStream(driveIndex, { live = false } = {}) {
|
|
|
29
30
|
|
|
30
31
|
/** @param {Hyperdrive} drive */
|
|
31
32
|
function addDrive(drive) {
|
|
32
|
-
mergedEntriesStreams.add(getHistoryStream(drive
|
|
33
|
+
mergedEntriesStreams.add(getHistoryStream(drive, { live }))
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
*
|
|
38
|
-
* @param {
|
|
39
|
+
* @param {Hyperdrive} drive
|
|
39
40
|
* @param {object} opts
|
|
40
41
|
* @param {boolean} opts.live
|
|
41
42
|
*/
|
|
42
|
-
function getHistoryStream(
|
|
43
|
+
function getHistoryStream(drive, { live }) {
|
|
43
44
|
// This will also include old versions of files, but it is the only way to
|
|
44
45
|
// get a live stream from a Hyperbee, however we currently do not support
|
|
45
46
|
// edits of blobs, so this should not be an issue, and the consequence is
|
|
46
47
|
// that old versions are downloaded too, which is acceptable.
|
|
47
|
-
const historyStream =
|
|
48
|
+
const historyStream = drive.db.createHistoryStream({
|
|
48
49
|
live,
|
|
49
50
|
// `keyEncoding` is necessary because hyperdrive stores file index data
|
|
50
51
|
// under the `files` sub-encoding key
|
|
51
52
|
keyEncoding,
|
|
52
53
|
})
|
|
53
|
-
return pipeline(historyStream, new AddDriveIds(
|
|
54
|
+
return pipeline(historyStream, new AddDriveIds(drive), noop)
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
class AddDriveIds extends Transform {
|
|
57
|
-
#
|
|
58
|
+
#drive
|
|
59
|
+
/** @type {string | undefined} */
|
|
58
60
|
#cachedDriveId
|
|
61
|
+
/** @type {string | undefined} */
|
|
62
|
+
#cachedBlobCoreId
|
|
59
63
|
|
|
60
|
-
/** @param {
|
|
61
|
-
constructor(
|
|
64
|
+
/** @param {Hyperdrive} drive */
|
|
65
|
+
constructor(drive) {
|
|
62
66
|
super({ objectMode: true })
|
|
63
|
-
this.#
|
|
64
|
-
this.#cachedDriveId = core.discoveryKey?.toString('hex')
|
|
67
|
+
this.#drive = drive
|
|
68
|
+
this.#cachedDriveId = drive.db.core.discoveryKey?.toString('hex')
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
_transform(entry, _, callback) {
|
|
71
|
+
get #driveId() {
|
|
69
72
|
// Minimal performance optimization to only call toString() once.
|
|
70
73
|
// core.discoveryKey will always be defined by the time it starts
|
|
71
74
|
// streaming, but could be null when the instance is first created.
|
|
72
|
-
let driveId
|
|
73
75
|
if (this.#cachedDriveId) {
|
|
74
|
-
|
|
76
|
+
return this.#cachedDriveId
|
|
75
77
|
} else {
|
|
76
|
-
|
|
77
|
-
this.#cachedDriveId
|
|
78
|
+
this.#cachedDriveId = this.#drive.db.core.discoveryKey?.toString('hex')
|
|
79
|
+
return this.#cachedDriveId
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async #getBlobCoreId() {
|
|
84
|
+
if (this.#cachedBlobCoreId) return this.#cachedBlobCoreId
|
|
85
|
+
const blobs = await this.#drive.getBlobs()
|
|
86
|
+
this.#cachedBlobCoreId = blobs.core.discoveryKey?.toString('hex')
|
|
87
|
+
return this.#cachedBlobCoreId
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @type {Transform['_transform']} */
|
|
91
|
+
_transform(entry, _, callback) {
|
|
92
|
+
if (!this.#driveId) {
|
|
93
|
+
return callback(new Error('Drive discovery key unexpectedly missing'))
|
|
78
94
|
}
|
|
79
|
-
|
|
95
|
+
this.#getBlobCoreId()
|
|
96
|
+
.then((blobCoreId) => {
|
|
97
|
+
if (!blobCoreId) {
|
|
98
|
+
return callback(
|
|
99
|
+
new Error('Blob core discovery key unexpectedly missing')
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
callback(null, { ...entry, driveId: this.#driveId, blobCoreId })
|
|
103
|
+
})
|
|
104
|
+
.catch((reason) => callback(ensureError(reason)))
|
|
80
105
|
}
|
|
81
106
|
}
|
package/src/blob-store/index.js
CHANGED
|
@@ -6,12 +6,26 @@ import { FilterEntriesStream } from './utils.js'
|
|
|
6
6
|
import { noop } from '../utils.js'
|
|
7
7
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
8
8
|
import { HyperdriveIndexImpl as HyperdriveIndex } from './hyperdrive-index.js'
|
|
9
|
+
import { Logger } from '../logger.js'
|
|
10
|
+
import { getErrorCode, getErrorMessage } from '../lib/error.js'
|
|
9
11
|
|
|
10
12
|
/** @import Hyperdrive from 'hyperdrive' */
|
|
11
13
|
/** @import { JsonObject } from 'type-fest' */
|
|
12
14
|
/** @import { Readable as NodeReadable } from 'node:stream' */
|
|
13
15
|
/** @import { Readable as StreamxReadable, Writable } from 'streamx' */
|
|
14
|
-
/** @import { BlobFilter, BlobId, BlobStoreEntriesStream } from '../types.js' */
|
|
16
|
+
/** @import { GenericBlobFilter, BlobFilter, BlobId, BlobStoreEntriesStream } from '../types.js' */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} BlobStoreEvents
|
|
20
|
+
* @prop {(peerId: string, blobFilter: GenericBlobFilter | null) => void} blob-filter
|
|
21
|
+
* @prop {(opts: {
|
|
22
|
+
* peerId: string
|
|
23
|
+
* start: number
|
|
24
|
+
* length: number
|
|
25
|
+
* blobCoreId: string
|
|
26
|
+
* }) => void} want-blob-range
|
|
27
|
+
* @prop {(error: Error) => void} error
|
|
28
|
+
*/
|
|
15
29
|
|
|
16
30
|
/**
|
|
17
31
|
* @internal
|
|
@@ -31,6 +45,13 @@ const SUPPORTED_BLOB_VARIANTS = /** @type {const} */ ({
|
|
|
31
45
|
// the type with JSDoc
|
|
32
46
|
export { SUPPORTED_BLOB_VARIANTS }
|
|
33
47
|
|
|
48
|
+
/** @type {import('../types.js').BlobFilter} */
|
|
49
|
+
const NON_ARCHIVE_DEVICE_DOWNLOAD_FILTER = {
|
|
50
|
+
photo: ['preview', 'thumbnail'],
|
|
51
|
+
// Don't download any audio of video files, since previews and
|
|
52
|
+
// thumbnails aren't supported yet.
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
class ErrNotFound extends Error {
|
|
35
56
|
constructor(message = 'NotFound') {
|
|
36
57
|
super(message)
|
|
@@ -38,24 +59,117 @@ class ErrNotFound extends Error {
|
|
|
38
59
|
}
|
|
39
60
|
}
|
|
40
61
|
|
|
41
|
-
/** @extends {TypedEmitter<
|
|
62
|
+
/** @extends {TypedEmitter<BlobStoreEvents>} */
|
|
42
63
|
export class BlobStore extends TypedEmitter {
|
|
43
64
|
#driveIndex
|
|
44
65
|
/** @type {Downloader} */
|
|
45
66
|
#downloader
|
|
67
|
+
/** @type {Map<string, GenericBlobFilter | null>} */
|
|
68
|
+
#blobFilters = new Map()
|
|
69
|
+
#l
|
|
70
|
+
/** @type {Map<string, BlobStoreEntriesStream>} */
|
|
71
|
+
#entriesStreams = new Map()
|
|
72
|
+
#isArchiveDevice
|
|
73
|
+
#deviceId
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Bound function for handling download intents for both peers and self
|
|
77
|
+
* @param {GenericBlobFilter | null} filter
|
|
78
|
+
* @param {string} peerId
|
|
79
|
+
*/
|
|
80
|
+
#handleDownloadIntent = async (filter, peerId) => {
|
|
81
|
+
this.#l.log('Download intent %o for peer %S', filter, peerId)
|
|
82
|
+
try {
|
|
83
|
+
this.#entriesStreams.get(peerId)?.destroy()
|
|
84
|
+
this.emit('blob-filter', peerId, filter)
|
|
85
|
+
this.#blobFilters.set(peerId, filter)
|
|
86
|
+
|
|
87
|
+
if (filter === null) return
|
|
88
|
+
|
|
89
|
+
const entriesReadStream = this.createEntriesReadStream({
|
|
90
|
+
live: true,
|
|
91
|
+
filter,
|
|
92
|
+
})
|
|
93
|
+
this.#entriesStreams.set(peerId, entriesReadStream)
|
|
94
|
+
|
|
95
|
+
entriesReadStream.once('close', () => {
|
|
96
|
+
if (this.#entriesStreams.get(peerId) === entriesReadStream) {
|
|
97
|
+
this.#entriesStreams.delete(peerId)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
for await (const {
|
|
102
|
+
blobCoreId,
|
|
103
|
+
value: { blob },
|
|
104
|
+
} of entriesReadStream) {
|
|
105
|
+
const { blockOffset: start, blockLength: length } = blob
|
|
106
|
+
this.emit('want-blob-range', {
|
|
107
|
+
peerId,
|
|
108
|
+
start,
|
|
109
|
+
length,
|
|
110
|
+
blobCoreId,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (getErrorCode(err) === 'ERR_STREAM_PREMATURE_CLOSE') return
|
|
115
|
+
this.#l.log(
|
|
116
|
+
'Error getting blob entries stream for peer %h: %s',
|
|
117
|
+
peerId,
|
|
118
|
+
getErrorMessage(err)
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Bound to `this`
|
|
125
|
+
* This will be called whenever a peer is successfully added to the creatorcore
|
|
126
|
+
* @param {import('../types.js').HypercorePeer & { protomux: import('protomux')<import('../lib/noise-secret-stream-helpers.js').OpenedNoiseStream> }} peer
|
|
127
|
+
*/
|
|
128
|
+
#handlePeerAdd = (peer) => {
|
|
129
|
+
const downloadFilter = getBlobDownloadFilter(this.#isArchiveDevice)
|
|
130
|
+
this.#coreManager.sendDownloadIntents(downloadFilter, peer)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Bound to `this`
|
|
135
|
+
* @param {import('../types.js').HypercorePeer & { protomux: import('protomux')<import('../lib/noise-secret-stream-helpers.js').OpenedNoiseStream> }} peer
|
|
136
|
+
*/
|
|
137
|
+
#handlePeerRemove = (peer) => {
|
|
138
|
+
const peerKey = peer.protomux.stream.remotePublicKey
|
|
139
|
+
const peerId = peerKey.toString('hex')
|
|
140
|
+
this.#entriesStreams.get(peerId)?.destroy()
|
|
141
|
+
this.#entriesStreams.delete(peerId)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#coreManager
|
|
145
|
+
#logger
|
|
46
146
|
|
|
47
147
|
/**
|
|
48
148
|
* @param {object} options
|
|
49
149
|
* @param {import('../core-manager/index.js').CoreManager} options.coreManager
|
|
50
|
-
* @param {
|
|
150
|
+
* @param {boolean} [options.isArchiveDevice] Set to `true` if this is an archive device which should download all blobs, or just a selection of blobs
|
|
151
|
+
* @param {import('../logger.js').Logger} [options.logger]
|
|
51
152
|
*/
|
|
52
|
-
constructor({ coreManager,
|
|
153
|
+
constructor({ coreManager, isArchiveDevice = true, logger }) {
|
|
53
154
|
super()
|
|
155
|
+
this.#logger = logger
|
|
156
|
+
this.#l = Logger.create('blobStore', logger)
|
|
157
|
+
this.#isArchiveDevice = isArchiveDevice
|
|
54
158
|
this.#driveIndex = new HyperdriveIndex(coreManager)
|
|
159
|
+
this.#coreManager = coreManager
|
|
160
|
+
this.#deviceId = coreManager.deviceId
|
|
161
|
+
const downloadFilter = getBlobDownloadFilter(isArchiveDevice)
|
|
162
|
+
if (downloadFilter) {
|
|
163
|
+
this.#handleDownloadIntent(downloadFilter, this.#deviceId)
|
|
164
|
+
}
|
|
55
165
|
this.#downloader = new Downloader(this.#driveIndex, {
|
|
56
166
|
filter: downloadFilter,
|
|
57
167
|
})
|
|
58
168
|
this.#downloader.on('error', (error) => this.emit('error', error))
|
|
169
|
+
|
|
170
|
+
coreManager.on('peer-download-intent', this.#handleDownloadIntent)
|
|
171
|
+
coreManager.creatorCore.on('peer-add', this.#handlePeerAdd)
|
|
172
|
+
coreManager.creatorCore.on('peer-remove', this.#handlePeerRemove)
|
|
59
173
|
}
|
|
60
174
|
|
|
61
175
|
/**
|
|
@@ -65,6 +179,38 @@ export class BlobStore extends TypedEmitter {
|
|
|
65
179
|
return getDiscoveryId(this.#driveIndex.writerKey)
|
|
66
180
|
}
|
|
67
181
|
|
|
182
|
+
get isArchiveDevice() {
|
|
183
|
+
return this.#isArchiveDevice
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {string} peerId
|
|
188
|
+
* @returns {GenericBlobFilter | null}
|
|
189
|
+
*/
|
|
190
|
+
getBlobFilter(peerId) {
|
|
191
|
+
return this.#blobFilters.get(peerId) ?? null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** @param {boolean} isArchiveDevice */
|
|
195
|
+
async setIsArchiveDevice(isArchiveDevice) {
|
|
196
|
+
this.#l.log('Setting isArchiveDevice to %s', isArchiveDevice)
|
|
197
|
+
if (this.#isArchiveDevice === isArchiveDevice) return
|
|
198
|
+
this.#isArchiveDevice = isArchiveDevice
|
|
199
|
+
const blobDownloadFilter = getBlobDownloadFilter(isArchiveDevice)
|
|
200
|
+
this.#downloader.removeAllListeners()
|
|
201
|
+
this.#downloader.destroy()
|
|
202
|
+
this.#downloader = new Downloader(this.#driveIndex, {
|
|
203
|
+
filter: blobDownloadFilter,
|
|
204
|
+
})
|
|
205
|
+
this.#downloader.on('error', (error) => this.emit('error', error))
|
|
206
|
+
// Even if blobFilter is null, e.g. we plan to download everything, we still
|
|
207
|
+
// need to inform connected peers of the change.
|
|
208
|
+
for (const peer of this.#coreManager.creatorCore.peers) {
|
|
209
|
+
this.#coreManager.sendDownloadIntents(blobDownloadFilter, peer)
|
|
210
|
+
}
|
|
211
|
+
this.#handleDownloadIntent(blobDownloadFilter, this.#deviceId)
|
|
212
|
+
}
|
|
213
|
+
|
|
68
214
|
/**
|
|
69
215
|
* @param {string} driveId hex-encoded discovery key
|
|
70
216
|
* @returns {Hyperdrive}
|
|
@@ -90,21 +236,6 @@ export class BlobStore extends TypedEmitter {
|
|
|
90
236
|
return blob
|
|
91
237
|
}
|
|
92
238
|
|
|
93
|
-
/**
|
|
94
|
-
* Set the filter for downloading blobs.
|
|
95
|
-
*
|
|
96
|
-
* @param {import('../types.js').BlobFilter | null} filter Filter blob types and/or variants to download. Filter is { [BlobType]: BlobVariants[] }. At least one blob variant must be specified for each blob type.
|
|
97
|
-
* @returns {void}
|
|
98
|
-
*/
|
|
99
|
-
setDownloadFilter(filter) {
|
|
100
|
-
this.#downloader.removeAllListeners()
|
|
101
|
-
this.#downloader.destroy()
|
|
102
|
-
this.#downloader = new Downloader(this.#driveIndex, {
|
|
103
|
-
filter,
|
|
104
|
-
})
|
|
105
|
-
this.#downloader.on('error', (error) => this.emit('error', error))
|
|
106
|
-
}
|
|
107
|
-
|
|
108
239
|
/**
|
|
109
240
|
* @param {BlobId} blobId
|
|
110
241
|
* @param {object} [options]
|
|
@@ -245,6 +376,9 @@ export class BlobStore extends TypedEmitter {
|
|
|
245
376
|
close() {
|
|
246
377
|
this.#downloader.removeAllListeners()
|
|
247
378
|
this.#downloader.destroy()
|
|
379
|
+
this.#coreManager.off('peer-download-intent', this.#handleDownloadIntent)
|
|
380
|
+
this.#coreManager.creatorCore.off('peer-add', this.#handlePeerAdd)
|
|
381
|
+
this.#coreManager.creatorCore.off('peer-remove', this.#handlePeerRemove)
|
|
248
382
|
}
|
|
249
383
|
}
|
|
250
384
|
|
|
@@ -280,3 +414,11 @@ function makePath({ type, variant, name }) {
|
|
|
280
414
|
function getDiscoveryId(key) {
|
|
281
415
|
return discoveryKey(key).toString('hex')
|
|
282
416
|
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* @param {boolean} isArchiveDevice
|
|
420
|
+
* @returns {null | BlobFilter}
|
|
421
|
+
*/
|
|
422
|
+
function getBlobDownloadFilter(isArchiveDevice) {
|
|
423
|
+
return isArchiveDevice ? null : NON_ARCHIVE_DEVICE_DOWNLOAD_FILTER
|
|
424
|
+
}
|
|
@@ -30,7 +30,7 @@ export const kCoreManagerReplicate = Symbol('replicate core manager')
|
|
|
30
30
|
* @typedef {Object} Events
|
|
31
31
|
* @property {(coreRecord: CoreRecord) => void} add-core
|
|
32
32
|
* @property {(namespace: Namespace, msg: { coreDiscoveryId: string, peerId: string, start: number, bitfield: Uint32Array }) => void} peer-have
|
|
33
|
-
* @property {(blobFilter: GenericBlobFilter, peerId: string) => void} peer-download-intent
|
|
33
|
+
* @property {(blobFilter: GenericBlobFilter | null, peerId: string) => void} peer-download-intent
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -412,7 +412,7 @@ export class CoreManager extends TypedEmitter {
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
/**
|
|
415
|
-
* @param {GenericBlobFilter} blobFilter
|
|
415
|
+
* @param {GenericBlobFilter | null} blobFilter
|
|
416
416
|
* @param {HypercorePeer} peer
|
|
417
417
|
*/
|
|
418
418
|
#handleDownloadIntentMessage(blobFilter, peer) {
|
|
@@ -421,7 +421,7 @@ export class CoreManager extends TypedEmitter {
|
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
/**
|
|
424
|
-
* @param {BlobFilter} blobFilter
|
|
424
|
+
* @param {BlobFilter | null} blobFilter
|
|
425
425
|
* @param {HypercorePeer} peer
|
|
426
426
|
*/
|
|
427
427
|
sendDownloadIntents(blobFilter, peer) {
|
|
@@ -540,20 +540,27 @@ const HaveExtensionCodec = {
|
|
|
540
540
|
}
|
|
541
541
|
|
|
542
542
|
const DownloadIntentCodec = {
|
|
543
|
-
/** @param {BlobFilter} filter */
|
|
543
|
+
/** @param {BlobFilter | null} filter */
|
|
544
544
|
encode(filter) {
|
|
545
|
-
const downloadIntents = mapObject(filter, (key, value) => [
|
|
545
|
+
const downloadIntents = mapObject(filter || {}, (key, value) => [
|
|
546
546
|
key,
|
|
547
547
|
{ variants: value || [] },
|
|
548
548
|
])
|
|
549
|
-
|
|
549
|
+
// If filter is null, we want to download everything
|
|
550
|
+
const everything = !filter
|
|
551
|
+
return DownloadIntentExtension.encode({
|
|
552
|
+
downloadIntents,
|
|
553
|
+
everything,
|
|
554
|
+
}).finish()
|
|
550
555
|
},
|
|
551
556
|
/**
|
|
552
557
|
* @param {Buffer | Uint8Array} buf
|
|
553
|
-
* @returns {GenericBlobFilter}
|
|
558
|
+
* @returns {GenericBlobFilter | null}
|
|
554
559
|
*/
|
|
555
560
|
decode(buf) {
|
|
556
561
|
const msg = DownloadIntentExtension.decode(buf)
|
|
562
|
+
// If everything is true, we ignore the downloadIntents and return null, which means download everything
|
|
563
|
+
if (msg.everything) return null
|
|
557
564
|
return mapObject(msg.downloadIntents, (key, value) => [
|
|
558
565
|
key + '', // keep TS happy
|
|
559
566
|
value.variants,
|
|
@@ -24,6 +24,13 @@ export interface DownloadIntentExtension {
|
|
|
24
24
|
downloadIntents: {
|
|
25
25
|
[key: string]: DownloadIntentExtension_DownloadIntent;
|
|
26
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* If true, the peer intends to download all blobs - this is the default
|
|
29
|
+
* assumption when a peer has not sent a download intent, but if a peer
|
|
30
|
+
* changes their intent while connected, we need to send the new intent to
|
|
31
|
+
* download everything.
|
|
32
|
+
*/
|
|
33
|
+
everything: boolean;
|
|
27
34
|
}
|
|
28
35
|
export interface DownloadIntentExtension_DownloadIntent {
|
|
29
36
|
variants: string[];
|
|
@@ -170,7 +170,7 @@ export var HaveExtension = {
|
|
|
170
170
|
},
|
|
171
171
|
};
|
|
172
172
|
function createBaseDownloadIntentExtension() {
|
|
173
|
-
return { downloadIntents: {} };
|
|
173
|
+
return { downloadIntents: {}, everything: false };
|
|
174
174
|
}
|
|
175
175
|
export var DownloadIntentExtension = {
|
|
176
176
|
encode: function (message, writer) {
|
|
@@ -180,6 +180,9 @@ export var DownloadIntentExtension = {
|
|
|
180
180
|
DownloadIntentExtension_DownloadIntentsEntry.encode({ key: key, value: value }, writer.uint32(10).fork())
|
|
181
181
|
.ldelim();
|
|
182
182
|
});
|
|
183
|
+
if (message.everything === true) {
|
|
184
|
+
writer.uint32(16).bool(message.everything);
|
|
185
|
+
}
|
|
183
186
|
return writer;
|
|
184
187
|
},
|
|
185
188
|
decode: function (input, length) {
|
|
@@ -198,6 +201,12 @@ export var DownloadIntentExtension = {
|
|
|
198
201
|
message.downloadIntents[entry1.key] = entry1.value;
|
|
199
202
|
}
|
|
200
203
|
continue;
|
|
204
|
+
case 2:
|
|
205
|
+
if (tag !== 16) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
message.everything = reader.bool();
|
|
209
|
+
continue;
|
|
201
210
|
}
|
|
202
211
|
if ((tag & 7) === 4 || tag === 0) {
|
|
203
212
|
break;
|
|
@@ -210,7 +219,7 @@ export var DownloadIntentExtension = {
|
|
|
210
219
|
return DownloadIntentExtension.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
211
220
|
},
|
|
212
221
|
fromPartial: function (object) {
|
|
213
|
-
var _a;
|
|
222
|
+
var _a, _b;
|
|
214
223
|
var message = createBaseDownloadIntentExtension();
|
|
215
224
|
message.downloadIntents = Object.entries((_a = object.downloadIntents) !== null && _a !== void 0 ? _a : {}).reduce(function (acc, _a) {
|
|
216
225
|
var key = _a[0], value = _a[1];
|
|
@@ -219,6 +228,7 @@ export var DownloadIntentExtension = {
|
|
|
219
228
|
}
|
|
220
229
|
return acc;
|
|
221
230
|
}, {});
|
|
231
|
+
message.everything = (_b = object.everything) !== null && _b !== void 0 ? _b : false;
|
|
222
232
|
return message;
|
|
223
233
|
},
|
|
224
234
|
};
|
|
@@ -69,6 +69,13 @@ export function haveExtension_NamespaceToNumber(object: HaveExtension_Namespace)
|
|
|
69
69
|
/** A map of blob types and variants that a peer intends to download */
|
|
70
70
|
export interface DownloadIntentExtension {
|
|
71
71
|
downloadIntents: { [key: string]: DownloadIntentExtension_DownloadIntent };
|
|
72
|
+
/**
|
|
73
|
+
* If true, the peer intends to download all blobs - this is the default
|
|
74
|
+
* assumption when a peer has not sent a download intent, but if a peer
|
|
75
|
+
* changes their intent while connected, we need to send the new intent to
|
|
76
|
+
* download everything.
|
|
77
|
+
*/
|
|
78
|
+
everything: boolean;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
export interface DownloadIntentExtension_DownloadIntent {
|
|
@@ -209,7 +216,7 @@ export const HaveExtension = {
|
|
|
209
216
|
};
|
|
210
217
|
|
|
211
218
|
function createBaseDownloadIntentExtension(): DownloadIntentExtension {
|
|
212
|
-
return { downloadIntents: {} };
|
|
219
|
+
return { downloadIntents: {}, everything: false };
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
export const DownloadIntentExtension = {
|
|
@@ -218,6 +225,9 @@ export const DownloadIntentExtension = {
|
|
|
218
225
|
DownloadIntentExtension_DownloadIntentsEntry.encode({ key: key as any, value }, writer.uint32(10).fork())
|
|
219
226
|
.ldelim();
|
|
220
227
|
});
|
|
228
|
+
if (message.everything === true) {
|
|
229
|
+
writer.uint32(16).bool(message.everything);
|
|
230
|
+
}
|
|
221
231
|
return writer;
|
|
222
232
|
},
|
|
223
233
|
|
|
@@ -238,6 +248,13 @@ export const DownloadIntentExtension = {
|
|
|
238
248
|
message.downloadIntents[entry1.key] = entry1.value;
|
|
239
249
|
}
|
|
240
250
|
continue;
|
|
251
|
+
case 2:
|
|
252
|
+
if (tag !== 16) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
message.everything = reader.bool();
|
|
257
|
+
continue;
|
|
241
258
|
}
|
|
242
259
|
if ((tag & 7) === 4 || tag === 0) {
|
|
243
260
|
break;
|
|
@@ -260,6 +277,7 @@ export const DownloadIntentExtension = {
|
|
|
260
277
|
}
|
|
261
278
|
return acc;
|
|
262
279
|
}, {});
|
|
280
|
+
message.everything = object.everything ?? false;
|
|
263
281
|
return message;
|
|
264
282
|
},
|
|
265
283
|
};
|