@ocfl/ocfl 0.2.0 → 0.3.0
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/index.js +72 -97
- package/index.mjs +3 -3
- package/lib/digest.js +245 -76
- package/lib/enum.js +73 -75
- package/lib/extension.js +4 -16
- package/lib/extensions/0003-hash-and-id-n-tuple-storage-layout.js +5 -3
- package/lib/extensions/0004-hashed-n-tuple-storage-layout.js +6 -4
- package/lib/extensions/0009-digest-algorithms.js +18 -0
- package/lib/extensions/index.js +1 -1
- package/lib/file.js +129 -0
- package/lib/index.js +0 -12
- package/lib/inventory.js +297 -136
- package/lib/object.js +293 -434
- package/lib/storage.js +26 -15
- package/lib/store.js +54 -16
- package/lib/transaction.js +177 -142
- package/lib/types.d.ts +110 -9
- package/lib/utils.js +52 -21
- package/lib/validation.js +1 -1
- package/package.json +16 -10
- package/lib/extensions/0001-digest-algorithms.js +0 -41
- package/lib/temp.js +0 -264
package/lib/inventory.js
CHANGED
|
@@ -1,55 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
//@ts-check
|
|
4
|
+
/** @typedef {import('./types').Inventory} Inventory */
|
|
5
|
+
/** @typedef {import('./types').InventoryDigests} InventoryDigests */
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
7
|
+
class VersionNumber {
|
|
8
|
+
value;
|
|
9
|
+
zeroPaddingLength;
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {number} num
|
|
13
|
+
* @param {number} [zeroPaddingLength]
|
|
14
|
+
*/
|
|
15
|
+
constructor(num, zeroPaddingLength) {
|
|
16
|
+
this.value = num;
|
|
17
|
+
this.zeroPaddingLength = zeroPaddingLength;
|
|
18
|
+
}
|
|
19
|
+
valueOf() {
|
|
20
|
+
return this.value;
|
|
21
|
+
}
|
|
22
|
+
toString() {
|
|
23
|
+
return 'v' + (this.zeroPaddingLength ? (this.value + '').padStart(this.zeroPaddingLength, '0') : this.value);
|
|
24
|
+
}
|
|
25
|
+
next() {
|
|
26
|
+
return new VersionNumber(this.value + 1, this.zeroPaddingLength);
|
|
27
|
+
}
|
|
28
|
+
previous() {
|
|
29
|
+
const n = this.value - 1;
|
|
30
|
+
if (n > 0) return new VersionNumber(n, this.zeroPaddingLength);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {number} num
|
|
35
|
+
* @param {number} zeroPaddingLength
|
|
36
|
+
*/
|
|
37
|
+
static fromInt(num, zeroPaddingLength) {
|
|
38
|
+
return new VersionNumber(num, zeroPaddingLength);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {string} versionLabel
|
|
43
|
+
*/
|
|
44
|
+
static fromString(versionLabel) {
|
|
45
|
+
const label = versionLabel.replace('v', '');
|
|
46
|
+
let zeroPaddingLength;
|
|
47
|
+
if (label.startsWith('0')) zeroPaddingLength = label.length;
|
|
48
|
+
return new VersionNumber(parseInt(label), zeroPaddingLength);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static next(versionLabel) {
|
|
52
|
+
return VersionNumber.fromString(versionLabel).next();
|
|
53
|
+
}
|
|
15
54
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (currentVer.startsWith('0')) zeroPaddingLength = currentVer.length;
|
|
20
|
-
let prevVer = '' + (parseInt(currentVer) - 1);
|
|
21
|
-
prevVer = 'v' + (zeroPaddingLength ? prevVer.padStart(zeroPaddingLength, '0') : prevVer);
|
|
22
|
-
return prevVer;
|
|
55
|
+
static previous(versionLabel) {
|
|
56
|
+
return VersionNumber.fromString(versionLabel).previous();
|
|
57
|
+
}
|
|
23
58
|
}
|
|
24
59
|
|
|
25
60
|
/**
|
|
26
61
|
* @typedef {{logicalPath?: string; digest?: string; contentPath?: string;}} FileRef
|
|
27
62
|
*/
|
|
28
63
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* @property {string} [created]
|
|
36
|
-
* @property {InventoryState} state
|
|
37
|
-
* @property {string} [message]
|
|
38
|
-
* @property {{name:string, address:string}} [user]
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @typedef {Object} Inventory
|
|
43
|
-
* @property {string} id - A unique identifier for the OCFL Object.
|
|
44
|
-
* @property {string} type - The URI of the inventory section of the specification version matching the object conformance declaration.
|
|
45
|
-
* @property {string} digestAlgorithm - The digest algorithm used for calculating digests for content-addressing within the OCFL Object and for the Inventory Digest.
|
|
46
|
-
* @property {string} head - The version directory name of the most recent version of the object.
|
|
47
|
-
* @property {string} [contentDirectory='content'] - The name of the designated content directory within the version directories.
|
|
48
|
-
* @property {InventoryState} manifest
|
|
49
|
-
* @property {Object.<string, InventoryVersion>} versions
|
|
50
|
-
* @property {Object} [fixity]
|
|
51
|
-
*/
|
|
52
|
-
|
|
64
|
+
function findKey(file, obj) {
|
|
65
|
+
for (let digest in obj) {
|
|
66
|
+
const arr = obj[digest];
|
|
67
|
+
if (arr.includes(file)) return digest;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
53
70
|
|
|
54
71
|
/**
|
|
55
72
|
* Wrapper class for inventory data
|
|
@@ -57,30 +74,121 @@ function prevVersion(currentVersion) {
|
|
|
57
74
|
class OcflObjectInventory {
|
|
58
75
|
/** @type {Inventory} */
|
|
59
76
|
#data;
|
|
60
|
-
/** @type {
|
|
61
|
-
#
|
|
77
|
+
/** @type {{[key: string]: {[key:string]: {[key:string]: string}}}} */
|
|
78
|
+
#fileIndex = {};
|
|
79
|
+
|
|
80
|
+
metadata;
|
|
62
81
|
|
|
63
82
|
/**
|
|
64
|
-
*
|
|
65
|
-
* @param {
|
|
66
|
-
* @param {string}
|
|
67
|
-
* @param {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
* Helper function to find the digest by logical file path and cached it by indexing
|
|
84
|
+
* @param {string} logicalPath
|
|
85
|
+
* @param {string} version
|
|
86
|
+
* @param {boolean} [onlyUsecache]
|
|
87
|
+
*/
|
|
88
|
+
_indexGet(logicalPath, version, onlyUsecache) {
|
|
89
|
+
let digests = this.#fileIndex[version]?.[logicalPath];
|
|
90
|
+
if (!digests && !onlyUsecache) {
|
|
91
|
+
// search and then index
|
|
92
|
+
const fixity = this.fixity;
|
|
93
|
+
const algo = this.digestAlgorithm;
|
|
94
|
+
const digestArr = [];
|
|
95
|
+
const mainDigest = findKey(logicalPath, this.versionState(version));
|
|
96
|
+
if (mainDigest) {
|
|
97
|
+
digestArr.push([algo, mainDigest]);
|
|
98
|
+
if (fixity) {
|
|
99
|
+
const cp = this.getContentPath(mainDigest);
|
|
100
|
+
for (const fixityDigest in fixity) {
|
|
101
|
+
const digest = findKey(cp, fixity[fixityDigest]);
|
|
102
|
+
if (digest) digestArr.push([fixityDigest, digest]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (digestArr.length > 0) {
|
|
107
|
+
const verIndex = this.#fileIndex[version] = this.#fileIndex[version] || {};
|
|
108
|
+
digests = verIndex[logicalPath] = Object.fromEntries(digestArr);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return digests;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_indexAdd(logicalPath, version, digests) {
|
|
115
|
+
this.#fileIndex[version][logicalPath] = digests;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_indexRemove(logicalPath, version = this.head) {
|
|
119
|
+
const val = this.#fileIndex[version][logicalPath];
|
|
120
|
+
delete this.#fileIndex[version][logicalPath];
|
|
121
|
+
return val;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_indexBuild() {
|
|
125
|
+
// index fixity contentPaths
|
|
126
|
+
const fixity = this.fixity;
|
|
127
|
+
const fixityIndex = {};
|
|
128
|
+
if (fixity) {
|
|
129
|
+
for (const fixityAlgo in fixity) {
|
|
130
|
+
const digestBlock = fixity[fixityAlgo];
|
|
131
|
+
const fixityIndexDigest = fixityIndex[fixityAlgo] = {};
|
|
132
|
+
for (const digest in digestBlock) {
|
|
133
|
+
const cps = digestBlock[digest];
|
|
134
|
+
for (const cp of cps) {
|
|
135
|
+
fixityIndexDigest[cp] = digest;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// index logicalPaths
|
|
141
|
+
// { v1: { 'dir/file.txt': { sha512: 'abcdef' } } }
|
|
142
|
+
const index = this.#fileIndex = {};
|
|
143
|
+
const algo = this.digestAlgorithm;
|
|
144
|
+
for (const verName in this.versions) {
|
|
145
|
+
const ver = this.versions[verName];
|
|
146
|
+
const indexVer = index[verName] = {};
|
|
147
|
+
for (const digest in ver.state) {
|
|
148
|
+
for (const lp of ver.state[digest]) {
|
|
149
|
+
const file = indexVer[lp] = { [algo]: digest };
|
|
150
|
+
if (fixity) {
|
|
151
|
+
for (const fixityAlgo in fixityIndex) {
|
|
152
|
+
const fixityIndexDigest = fixityIndex[fixityAlgo];
|
|
153
|
+
const cps = this.manifest[digest] || [];
|
|
154
|
+
for (const cp of cps) {
|
|
155
|
+
if (fixityIndexDigest[cp]) file[fixityAlgo] = fixityIndexDigest[cp];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_indexFindPaths(prefix, version = this.head) {
|
|
165
|
+
const res = [];
|
|
166
|
+
const index = this.#fileIndex[version];
|
|
167
|
+
const prefixWithSlash = prefix.endsWith('/') ? prefix : prefix + '/';
|
|
168
|
+
for (const lp in index) {
|
|
169
|
+
if (index[lp] && (lp === prefix || lp.startsWith(prefixWithSlash))) res.push(lp);
|
|
170
|
+
}
|
|
171
|
+
return res;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create a new mutable OcflObjectInventory from the object config or clone an existing inventory,
|
|
177
|
+
* and increase the inventory version number. If data.head is undefined, starts from v1.
|
|
178
|
+
* @param {Partial<Inventory> | OcflObjectInventory} data
|
|
74
179
|
* @param {boolean} [cleanState]
|
|
75
180
|
*/
|
|
76
181
|
static newVersion(data, cleanState) {
|
|
182
|
+
if (data instanceof OcflObjectInventory) {
|
|
183
|
+
data = data.toJSON();
|
|
184
|
+
}
|
|
77
185
|
if (!data.id || !data.digestAlgorithm || !data.type) throw new Error('Inventory must have an id, digestAlgorithm and type.');
|
|
78
186
|
data.manifest = data.manifest || {};
|
|
79
187
|
data.versions = data.versions || {};
|
|
80
188
|
let prevVer;
|
|
81
189
|
if (data.head) {
|
|
82
190
|
prevVer = data.versions[data.head];
|
|
83
|
-
data.head =
|
|
191
|
+
data.head = VersionNumber.next(data.head).toString();
|
|
84
192
|
} else {
|
|
85
193
|
data.head = 'v1';
|
|
86
194
|
cleanState = true;
|
|
@@ -99,31 +207,39 @@ class OcflObjectInventory {
|
|
|
99
207
|
*/
|
|
100
208
|
constructor(data, version) {
|
|
101
209
|
this.#data = data;
|
|
102
|
-
this
|
|
210
|
+
this._indexBuild();
|
|
103
211
|
}
|
|
104
212
|
|
|
105
213
|
get id() { return this.#data.id }
|
|
106
214
|
get type() { return this.#data.type }
|
|
107
215
|
get digestAlgorithm() { return this.#data.digestAlgorithm }
|
|
108
216
|
get head() { return this.#data.head }
|
|
217
|
+
/** The version number of the current head */
|
|
218
|
+
get versionNumber() { return VersionNumber.fromString(this.head); }
|
|
109
219
|
get contentDirectory() { return this.#data.contentDirectory || 'content' }
|
|
110
220
|
get manifest() { return this.#data.manifest }
|
|
111
221
|
get versions() { return this.#data.versions }
|
|
222
|
+
get version() { return this.versions[this.head] }
|
|
112
223
|
get fixity() { return this.#data.fixity }
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
get
|
|
116
|
-
get
|
|
117
|
-
get
|
|
224
|
+
set fixity(val) { this.#data.fixity = val; }
|
|
225
|
+
/** The state of the current version */
|
|
226
|
+
get state() { return this.version.state }
|
|
227
|
+
get created() { return this.version.created }
|
|
228
|
+
get message() { return this.version.message }
|
|
229
|
+
get user() { return this.version.user }
|
|
230
|
+
//get nextVersion() { return this.versionNumber.next().toString(); }
|
|
231
|
+
//get prevVersion() { return this.versionNumber.previous()?.toString(); }
|
|
118
232
|
|
|
119
233
|
/** Return true if there has been actual change to the state of current version compared to the previous version */
|
|
120
234
|
get isChanged() {
|
|
121
|
-
|
|
235
|
+
const prev = this.versionNumber.previous()?.toString();
|
|
236
|
+
if (!prev) return true;
|
|
237
|
+
let diff = this.diffState(this.head, prev);
|
|
122
238
|
return Object.keys(diff[0]).length > 0 || Object.keys(diff[1]).length > 0;
|
|
123
239
|
}
|
|
124
|
-
set created(val) { this
|
|
125
|
-
set message(val) { this
|
|
126
|
-
set user(val) { this
|
|
240
|
+
set created(val) { this.version.created = val; }
|
|
241
|
+
set message(val) { this.version.message = val; }
|
|
242
|
+
set user(val) { this.version.user = val; }
|
|
127
243
|
|
|
128
244
|
/**
|
|
129
245
|
* Return the state of the specified version
|
|
@@ -138,11 +254,6 @@ class OcflObjectInventory {
|
|
|
138
254
|
return JSON.stringify(this.#data, null, 2);
|
|
139
255
|
}
|
|
140
256
|
|
|
141
|
-
/** Compute digest from current state of this inventory object*/
|
|
142
|
-
digest() {
|
|
143
|
-
return digestSync(this.#data.digestAlgorithm, this.toString());
|
|
144
|
-
}
|
|
145
|
-
|
|
146
257
|
/**
|
|
147
258
|
* @return {Inventory}
|
|
148
259
|
*/
|
|
@@ -170,7 +281,9 @@ class OcflObjectInventory {
|
|
|
170
281
|
logicalPath,
|
|
171
282
|
version,
|
|
172
283
|
digest,
|
|
173
|
-
contentPath
|
|
284
|
+
contentPath,
|
|
285
|
+
fixity: this.getFixity(logicalPath, version),
|
|
286
|
+
...this.metadata?.[contentPath]
|
|
174
287
|
};
|
|
175
288
|
}
|
|
176
289
|
}
|
|
@@ -198,21 +311,64 @@ class OcflObjectInventory {
|
|
|
198
311
|
}
|
|
199
312
|
|
|
200
313
|
/**
|
|
201
|
-
* Get the content
|
|
314
|
+
* Get the digest of a content represented by the logicalPath of the specified version
|
|
202
315
|
* @param {string} logicalPath
|
|
203
316
|
* @param {string} [versionName]
|
|
204
|
-
* @returns
|
|
205
317
|
*/
|
|
206
|
-
getDigest(logicalPath, versionName) {
|
|
207
|
-
|
|
208
|
-
|
|
318
|
+
getDigest(logicalPath, versionName = this.head) {
|
|
319
|
+
return this._indexGet(logicalPath, versionName, true)?.[this.digestAlgorithm];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get the file information by the logicalPath of the specified version
|
|
324
|
+
* @param {string} logicalPath
|
|
325
|
+
* @param {string} [versionName]
|
|
326
|
+
*/
|
|
327
|
+
getFile(logicalPath, versionName = this.head) {
|
|
328
|
+
const digest = this.getDigest(logicalPath, versionName);
|
|
329
|
+
const contentPath = this.getContentPath(digest);
|
|
330
|
+
const version = versionName;
|
|
331
|
+
if (digest) {
|
|
332
|
+
return {
|
|
333
|
+
logicalPath,
|
|
334
|
+
version,
|
|
335
|
+
digest,
|
|
336
|
+
contentPath,
|
|
337
|
+
fixity: this.getFixity(logicalPath, version),
|
|
338
|
+
...this.metadata?.[contentPath]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
getFileByDigest(digest, versionName) {
|
|
344
|
+
if (versionName) return this.getFile(this.versions[versionName].state[digest]?.[0], versionName);
|
|
345
|
+
for (const version in this.versions) {
|
|
346
|
+
const logicalPath = this.versions[version].state[digest]?.[0];
|
|
347
|
+
if (logicalPath) return this.getFile(logicalPath, version);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
getFileByContentPath(contentPath, versionName) {
|
|
352
|
+
for (const digest in this.manifest) {
|
|
353
|
+
if (this.manifest[digest].includes(contentPath)) return this.getFileByDigest(digest, versionName);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get the fixity of a content represented by the logicalPath of the specified version
|
|
359
|
+
* @param {string} logicalPath
|
|
360
|
+
* @param {string} [versionName]
|
|
361
|
+
*/
|
|
362
|
+
getFixity(logicalPath, versionName = this.head) {
|
|
363
|
+
const { [this.digestAlgorithm]: digest, ...fixity } = this._indexGet(logicalPath, versionName, true) || {};
|
|
364
|
+
return fixity;
|
|
209
365
|
}
|
|
210
366
|
|
|
211
367
|
/**
|
|
212
368
|
* Compare the differences between the state of two versions
|
|
213
369
|
* @param {string} version0
|
|
214
370
|
* @param {string} version1
|
|
215
|
-
* @return {[
|
|
371
|
+
* @return {[InventoryDigests, InventoryDigests]} - A tuple of [A, B] where A contains files
|
|
216
372
|
* that are in `version0` but not in `version1` and B contains files
|
|
217
373
|
* that are in `version1` but not in `version0`
|
|
218
374
|
*/
|
|
@@ -237,7 +393,7 @@ class OcflObjectInventory {
|
|
|
237
393
|
return Object.fromEntries(r);
|
|
238
394
|
});
|
|
239
395
|
|
|
240
|
-
return /** @type {[
|
|
396
|
+
return /** @type {[InventoryDigests, InventoryDigests]} */(result);
|
|
241
397
|
}
|
|
242
398
|
}
|
|
243
399
|
|
|
@@ -246,41 +402,27 @@ class OcflObjectInventory {
|
|
|
246
402
|
* Wrapper class with mutable methods for inventory data
|
|
247
403
|
*/
|
|
248
404
|
class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
249
|
-
/** @type {Object.<string,string>} */
|
|
250
|
-
#byPath;
|
|
251
405
|
|
|
252
406
|
constructor(data) {
|
|
253
407
|
super(data);
|
|
254
|
-
//index logicalPaths
|
|
255
|
-
let byPath = this.#byPath = {};
|
|
256
|
-
let state = this.state;
|
|
257
|
-
for (let d in state) {
|
|
258
|
-
let files = state[d];
|
|
259
|
-
for (let lp of files) {
|
|
260
|
-
byPath[lp] = d;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get the digest of a content represented by the logicalPath of the specified version
|
|
267
|
-
* @param {string} logicalPath
|
|
268
|
-
* @param {string} [versionName]
|
|
269
|
-
* @return {string}
|
|
270
|
-
*/
|
|
271
|
-
getDigest(logicalPath, versionName) {
|
|
272
|
-
if (versionName) return super.getDigest(logicalPath, versionName);
|
|
273
|
-
else return this.#byPath[logicalPath];
|
|
274
408
|
}
|
|
275
409
|
|
|
276
410
|
/**
|
|
277
411
|
* Add a new file to the inventory or update the digest of an existing file
|
|
278
412
|
* @param {string} digest
|
|
279
413
|
* @param {string} logicalPath
|
|
414
|
+
* @param {{[x: string]: string}} [fixity]
|
|
280
415
|
* @return {boolean} - true if the state is changed
|
|
281
416
|
*/
|
|
282
|
-
add(logicalPath, digest) {
|
|
283
|
-
|
|
417
|
+
add(logicalPath, digest, fixity) {
|
|
418
|
+
const state = this.state;
|
|
419
|
+
const contentPath = this.head + '/' + this.contentDirectory + '/' + logicalPath;
|
|
420
|
+
// add fixity info
|
|
421
|
+
if (fixity) {
|
|
422
|
+
for (const digestName in fixity) {
|
|
423
|
+
this.addFixity(contentPath, digestName, fixity[digestName]);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
284
426
|
if (state[digest]) {
|
|
285
427
|
// this is a file with identical content but different name
|
|
286
428
|
if (state[digest].indexOf(logicalPath) < 0) state[digest].push(logicalPath);
|
|
@@ -290,19 +432,38 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
290
432
|
if (mf && mf.length > 0) {
|
|
291
433
|
// this is a reinstatement
|
|
292
434
|
} else {
|
|
293
|
-
// normal addition
|
|
294
|
-
this.manifest[digest] = [
|
|
435
|
+
// normal addition of a new file
|
|
436
|
+
this.manifest[digest] = [contentPath];
|
|
295
437
|
}
|
|
296
|
-
if (this
|
|
438
|
+
if (this.getDigest(logicalPath)) {
|
|
297
439
|
// this is an update, remove old digest
|
|
298
440
|
this.delete(logicalPath);
|
|
299
441
|
}
|
|
300
442
|
state[digest] = [logicalPath];
|
|
301
443
|
}
|
|
302
|
-
this
|
|
444
|
+
this._indexAdd(logicalPath, this.head, { [this.digestAlgorithm]: digest, ...fixity });
|
|
303
445
|
return true;
|
|
304
446
|
}
|
|
305
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Low-level method to add a set of digest entries associated to file into the fixity block
|
|
450
|
+
* @param {string} contentPath
|
|
451
|
+
* @param {string} digestName
|
|
452
|
+
* @param {string} digestValue
|
|
453
|
+
*/
|
|
454
|
+
addFixity(contentPath, digestName, digestValue) {
|
|
455
|
+
if (!digestValue || !digestName) return;
|
|
456
|
+
const fixityBlock = this.fixity = this.fixity || {};
|
|
457
|
+
const digestBlock = fixityBlock[digestName] = fixityBlock[digestName] || {};
|
|
458
|
+
if (digestBlock[digestValue]) {
|
|
459
|
+
if (!digestBlock[digestValue].includes(contentPath)) {
|
|
460
|
+
digestBlock[digestValue].push(contentPath);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
digestBlock[digestValue] = [contentPath];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
306
467
|
/**
|
|
307
468
|
* Rename a logical path. This will overwrite dest if exists
|
|
308
469
|
* @param {string} srcLogicalPath
|
|
@@ -311,18 +472,17 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
311
472
|
rename(srcLogicalPath, destLogicalPath) {
|
|
312
473
|
let srcPrefix = srcLogicalPath.endsWith('/') ? srcLogicalPath : srcLogicalPath + '/';
|
|
313
474
|
let destPrefix = destLogicalPath.endsWith('/') ? destLogicalPath : destLogicalPath + '/';
|
|
314
|
-
let srcList =
|
|
475
|
+
let srcList = this._indexFindPaths(srcPrefix);
|
|
315
476
|
let pairs = srcList.map(src => [src, destPrefix + src.slice(srcPrefix.length)]);
|
|
316
477
|
if (pairs.length === 0) pairs.push([srcLogicalPath, destLogicalPath]);
|
|
317
478
|
for (let [src, dest] of pairs) {
|
|
318
|
-
let digest = this
|
|
479
|
+
let digest = this.getDigest(src);
|
|
319
480
|
let files = this.state[digest];
|
|
320
481
|
let index = files?.indexOf(src);
|
|
321
482
|
if (index >= 0) {
|
|
322
483
|
this.delete(dest); //overwrite
|
|
323
484
|
files[index] = dest;
|
|
324
|
-
this
|
|
325
|
-
this.#byPath[src] = null;
|
|
485
|
+
this._indexAdd(dest, this.head, this._indexRemove(src, this.head));
|
|
326
486
|
} else {
|
|
327
487
|
/** @todo decide: throw error or silently ignore non-existant file? */
|
|
328
488
|
}
|
|
@@ -337,21 +497,21 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
337
497
|
copy(srcLogicalPath, destLogicalPath) {
|
|
338
498
|
let srcPrefix = srcLogicalPath.endsWith('/') ? srcLogicalPath : srcLogicalPath + '/';
|
|
339
499
|
let destPrefix = destLogicalPath.endsWith('/') ? destLogicalPath : destLogicalPath + '/';
|
|
340
|
-
let srcList =
|
|
500
|
+
let srcList = this._indexFindPaths(srcPrefix);
|
|
341
501
|
let pairs = srcList.map(src => [src, destPrefix + src.slice(srcPrefix.length)]);
|
|
342
502
|
if (pairs.length === 0) pairs.push([srcLogicalPath, destLogicalPath]);
|
|
343
503
|
for (let [src, dest] of pairs) {
|
|
344
504
|
if (!dest) throw new Error(`Target logical path cannot be empty`);
|
|
345
|
-
let digest = this
|
|
505
|
+
let digest = this._indexGet(src, this.head, true);
|
|
346
506
|
if (!digest) throw new Error(`Source logical path "${src}" does not exist in object [${this.id}]`);
|
|
347
507
|
//console.log(this.state);
|
|
348
|
-
let files = this.state[digest];
|
|
508
|
+
let files = this.state[digest[this.digestAlgorithm]];
|
|
349
509
|
let index = files?.indexOf(dest);
|
|
350
510
|
if (index >= 0) {
|
|
351
511
|
/** @todo decide: throw error or silently ignore existing dest file? */
|
|
352
512
|
} else {
|
|
353
513
|
files.push(dest);
|
|
354
|
-
this
|
|
514
|
+
this._indexAdd(dest, this.head, digest);
|
|
355
515
|
}
|
|
356
516
|
}
|
|
357
517
|
}
|
|
@@ -359,20 +519,23 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
359
519
|
/**
|
|
360
520
|
* Delete a file
|
|
361
521
|
* @param {string} logicalPath
|
|
522
|
+
* @param {string} [version]
|
|
362
523
|
*/
|
|
363
|
-
delete(logicalPath) {
|
|
364
|
-
|
|
365
|
-
let names = this.getDigest(logicalPath) ? [logicalPath] :
|
|
366
|
-
Object.keys(this.#byPath).filter(p => p.startsWith(prefix));
|
|
524
|
+
delete(logicalPath, version = this.head) {
|
|
525
|
+
const names = this._indexFindPaths(logicalPath, version);
|
|
367
526
|
let count = 0;
|
|
527
|
+
const state = this.versionState(version);
|
|
368
528
|
for (let lp of names) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
529
|
+
const digest = this.getDigest(lp, version);
|
|
530
|
+
const files = state[digest];
|
|
531
|
+
const index = files?.indexOf(lp);
|
|
372
532
|
if (index >= 0) {
|
|
373
|
-
files.
|
|
374
|
-
|
|
375
|
-
|
|
533
|
+
if (files.length === 1) {
|
|
534
|
+
delete state[digest];
|
|
535
|
+
} else {
|
|
536
|
+
files.splice(index, 1);
|
|
537
|
+
}
|
|
538
|
+
this._indexRemove(lp, version);
|
|
376
539
|
count++;
|
|
377
540
|
}
|
|
378
541
|
}
|
|
@@ -380,19 +543,17 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
380
543
|
}
|
|
381
544
|
|
|
382
545
|
reinstate(logicalPath, versionName) {
|
|
383
|
-
let
|
|
384
|
-
let digest = this.getDigest(logicalPath, versionName);
|
|
385
|
-
/** @type {FileRef[]} */
|
|
386
|
-
let files = digest ? [{ logicalPath, digest }] :
|
|
387
|
-
[...this.find(f => f.logicalPath.startsWith(prefix), versionName)];
|
|
546
|
+
let names = this._indexFindPaths(logicalPath, versionName);
|
|
547
|
+
//let digest = this.getDigest(logicalPath, versionName);
|
|
388
548
|
let state = this.state;
|
|
389
549
|
let count = 0;
|
|
390
|
-
for (let
|
|
391
|
-
|
|
392
|
-
|
|
550
|
+
for (let lp of names) {
|
|
551
|
+
const digests = this._indexGet(lp, versionName);
|
|
552
|
+
if (digests) {
|
|
553
|
+
let c = this.delete(lp);
|
|
393
554
|
}
|
|
394
|
-
state[
|
|
395
|
-
this
|
|
555
|
+
state[digests[this.digestAlgorithm]] = [lp];
|
|
556
|
+
this._indexAdd(lp, this.head, digests);
|
|
396
557
|
count++;
|
|
397
558
|
}
|
|
398
559
|
return count;
|
|
@@ -400,4 +561,4 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
400
561
|
|
|
401
562
|
}
|
|
402
563
|
|
|
403
|
-
module.exports = { OcflObjectInventory, OcflObjectInventoryMut };
|
|
564
|
+
module.exports = { OcflObjectInventory, OcflObjectInventoryMut, VersionNumber };
|