@ocfl/ocfl 0.2.2 → 0.4.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 -77
- 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 +147 -0
- package/lib/index.js +0 -12
- package/lib/inventory.js +294 -101
- package/lib/object.js +332 -489
- package/lib/storage.js +23 -14
- package/lib/store.js +26 -18
- package/lib/transaction.js +177 -142
- package/lib/types.d.ts +75 -16
- package/lib/utils.js +54 -24
- package/lib/validation.js +1 -1
- package/package.json +15 -11
- package/lib/extensions/0001-digest-algorithms.js +0 -46
- package/lib/temp.js +0 -264
package/lib/inventory.js
CHANGED
|
@@ -1,53 +1,194 @@
|
|
|
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
|
+
}
|
|
15
50
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
51
|
+
static next(versionLabel) {
|
|
52
|
+
return VersionNumber.fromString(versionLabel).next();
|
|
53
|
+
}
|
|
54
|
+
|
|
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
|
|
|
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
|
+
}
|
|
70
|
+
|
|
29
71
|
/**
|
|
30
72
|
* Wrapper class for inventory data
|
|
31
73
|
*/
|
|
32
74
|
class OcflObjectInventory {
|
|
33
75
|
/** @type {Inventory} */
|
|
34
76
|
#data;
|
|
35
|
-
/** @type {
|
|
36
|
-
#
|
|
77
|
+
/** @type {{[key: string]: {[key:string]: {[key:string]: string}}}} */
|
|
78
|
+
#fileIndex = {};
|
|
79
|
+
|
|
37
80
|
metadata;
|
|
81
|
+
|
|
82
|
+
/**
|
|
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
|
+
|
|
38
175
|
/**
|
|
39
|
-
* Create a mutable
|
|
40
|
-
*
|
|
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
|
|
41
179
|
* @param {boolean} [cleanState]
|
|
42
180
|
*/
|
|
43
181
|
static newVersion(data, cleanState) {
|
|
182
|
+
if (data instanceof OcflObjectInventory) {
|
|
183
|
+
data = data.toJSON();
|
|
184
|
+
}
|
|
44
185
|
if (!data.id || !data.digestAlgorithm || !data.type) throw new Error('Inventory must have an id, digestAlgorithm and type.');
|
|
45
186
|
data.manifest = data.manifest || {};
|
|
46
187
|
data.versions = data.versions || {};
|
|
47
188
|
let prevVer;
|
|
48
189
|
if (data.head) {
|
|
49
190
|
prevVer = data.versions[data.head];
|
|
50
|
-
data.head =
|
|
191
|
+
data.head = VersionNumber.next(data.head).toString();
|
|
51
192
|
} else {
|
|
52
193
|
data.head = 'v1';
|
|
53
194
|
cleanState = true;
|
|
@@ -66,31 +207,39 @@ class OcflObjectInventory {
|
|
|
66
207
|
*/
|
|
67
208
|
constructor(data, version) {
|
|
68
209
|
this.#data = data;
|
|
69
|
-
this
|
|
210
|
+
this._indexBuild();
|
|
70
211
|
}
|
|
71
212
|
|
|
72
213
|
get id() { return this.#data.id }
|
|
73
214
|
get type() { return this.#data.type }
|
|
74
215
|
get digestAlgorithm() { return this.#data.digestAlgorithm }
|
|
75
216
|
get head() { return this.#data.head }
|
|
217
|
+
/** The version number of the current head */
|
|
218
|
+
get versionNumber() { return VersionNumber.fromString(this.head); }
|
|
76
219
|
get contentDirectory() { return this.#data.contentDirectory || 'content' }
|
|
77
220
|
get manifest() { return this.#data.manifest }
|
|
78
221
|
get versions() { return this.#data.versions }
|
|
222
|
+
get version() { return this.versions[this.head] }
|
|
79
223
|
get fixity() { return this.#data.fixity }
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
get
|
|
83
|
-
get
|
|
84
|
-
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(); }
|
|
85
232
|
|
|
86
233
|
/** Return true if there has been actual change to the state of current version compared to the previous version */
|
|
87
234
|
get isChanged() {
|
|
88
|
-
|
|
235
|
+
const prev = this.versionNumber.previous()?.toString();
|
|
236
|
+
if (!prev) return true;
|
|
237
|
+
let diff = this.diffState(this.head, prev);
|
|
89
238
|
return Object.keys(diff[0]).length > 0 || Object.keys(diff[1]).length > 0;
|
|
90
239
|
}
|
|
91
|
-
set created(val) { this
|
|
92
|
-
set message(val) { this
|
|
93
|
-
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; }
|
|
94
243
|
|
|
95
244
|
/**
|
|
96
245
|
* Return the state of the specified version
|
|
@@ -105,11 +254,6 @@ class OcflObjectInventory {
|
|
|
105
254
|
return JSON.stringify(this.#data, null, 2);
|
|
106
255
|
}
|
|
107
256
|
|
|
108
|
-
/** Compute digest from current state of this inventory object*/
|
|
109
|
-
digest() {
|
|
110
|
-
return digestSync(this.#data.digestAlgorithm, this.toString());
|
|
111
|
-
}
|
|
112
|
-
|
|
113
257
|
/**
|
|
114
258
|
* @return {Inventory}
|
|
115
259
|
*/
|
|
@@ -138,6 +282,7 @@ class OcflObjectInventory {
|
|
|
138
282
|
version,
|
|
139
283
|
digest,
|
|
140
284
|
contentPath,
|
|
285
|
+
fixity: this.getFixity(logicalPath, version),
|
|
141
286
|
...this.metadata?.[contentPath]
|
|
142
287
|
};
|
|
143
288
|
}
|
|
@@ -166,14 +311,57 @@ class OcflObjectInventory {
|
|
|
166
311
|
}
|
|
167
312
|
|
|
168
313
|
/**
|
|
169
|
-
* Get the content
|
|
314
|
+
* Get the digest of a content represented by the logicalPath of the specified version
|
|
315
|
+
* @param {string} logicalPath
|
|
316
|
+
* @param {string} [versionName]
|
|
317
|
+
*/
|
|
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
|
|
170
359
|
* @param {string} logicalPath
|
|
171
360
|
* @param {string} [versionName]
|
|
172
|
-
* @returns
|
|
173
361
|
*/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return
|
|
362
|
+
getFixity(logicalPath, versionName = this.head) {
|
|
363
|
+
const { [this.digestAlgorithm]: digest, ...fixity } = this._indexGet(logicalPath, versionName, true) || {};
|
|
364
|
+
return fixity;
|
|
177
365
|
}
|
|
178
366
|
|
|
179
367
|
/**
|
|
@@ -214,41 +402,27 @@ class OcflObjectInventory {
|
|
|
214
402
|
* Wrapper class with mutable methods for inventory data
|
|
215
403
|
*/
|
|
216
404
|
class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
217
|
-
/** @type {Object.<string,string>} */
|
|
218
|
-
#byPath;
|
|
219
405
|
|
|
220
406
|
constructor(data) {
|
|
221
407
|
super(data);
|
|
222
|
-
//index logicalPaths
|
|
223
|
-
let byPath = this.#byPath = {};
|
|
224
|
-
let state = this.state;
|
|
225
|
-
for (let d in state) {
|
|
226
|
-
let files = state[d];
|
|
227
|
-
for (let lp of files) {
|
|
228
|
-
byPath[lp] = d;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get the digest of a content represented by the logicalPath of the specified version
|
|
235
|
-
* @param {string} logicalPath
|
|
236
|
-
* @param {string} [versionName]
|
|
237
|
-
* @return {string}
|
|
238
|
-
*/
|
|
239
|
-
getDigest(logicalPath, versionName) {
|
|
240
|
-
if (versionName) return super.getDigest(logicalPath, versionName);
|
|
241
|
-
else return this.#byPath[logicalPath];
|
|
242
408
|
}
|
|
243
409
|
|
|
244
410
|
/**
|
|
245
411
|
* Add a new file to the inventory or update the digest of an existing file
|
|
246
412
|
* @param {string} digest
|
|
247
413
|
* @param {string} logicalPath
|
|
414
|
+
* @param {{[x: string]: string}} [fixity]
|
|
248
415
|
* @return {boolean} - true if the state is changed
|
|
249
416
|
*/
|
|
250
|
-
add(logicalPath, digest) {
|
|
251
|
-
|
|
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
|
+
}
|
|
252
426
|
if (state[digest]) {
|
|
253
427
|
// this is a file with identical content but different name
|
|
254
428
|
if (state[digest].indexOf(logicalPath) < 0) state[digest].push(logicalPath);
|
|
@@ -258,19 +432,38 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
258
432
|
if (mf && mf.length > 0) {
|
|
259
433
|
// this is a reinstatement
|
|
260
434
|
} else {
|
|
261
|
-
// normal addition
|
|
262
|
-
this.manifest[digest] = [
|
|
435
|
+
// normal addition of a new file
|
|
436
|
+
this.manifest[digest] = [contentPath];
|
|
263
437
|
}
|
|
264
|
-
if (this
|
|
438
|
+
if (this.getDigest(logicalPath)) {
|
|
265
439
|
// this is an update, remove old digest
|
|
266
440
|
this.delete(logicalPath);
|
|
267
441
|
}
|
|
268
442
|
state[digest] = [logicalPath];
|
|
269
443
|
}
|
|
270
|
-
this
|
|
444
|
+
this._indexAdd(logicalPath, this.head, { [this.digestAlgorithm]: digest, ...fixity });
|
|
271
445
|
return true;
|
|
272
446
|
}
|
|
273
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
|
+
|
|
274
467
|
/**
|
|
275
468
|
* Rename a logical path. This will overwrite dest if exists
|
|
276
469
|
* @param {string} srcLogicalPath
|
|
@@ -279,18 +472,17 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
279
472
|
rename(srcLogicalPath, destLogicalPath) {
|
|
280
473
|
let srcPrefix = srcLogicalPath.endsWith('/') ? srcLogicalPath : srcLogicalPath + '/';
|
|
281
474
|
let destPrefix = destLogicalPath.endsWith('/') ? destLogicalPath : destLogicalPath + '/';
|
|
282
|
-
let srcList =
|
|
475
|
+
let srcList = this._indexFindPaths(srcPrefix);
|
|
283
476
|
let pairs = srcList.map(src => [src, destPrefix + src.slice(srcPrefix.length)]);
|
|
284
477
|
if (pairs.length === 0) pairs.push([srcLogicalPath, destLogicalPath]);
|
|
285
478
|
for (let [src, dest] of pairs) {
|
|
286
|
-
let digest = this
|
|
479
|
+
let digest = this.getDigest(src);
|
|
287
480
|
let files = this.state[digest];
|
|
288
481
|
let index = files?.indexOf(src);
|
|
289
482
|
if (index >= 0) {
|
|
290
483
|
this.delete(dest); //overwrite
|
|
291
484
|
files[index] = dest;
|
|
292
|
-
this
|
|
293
|
-
this.#byPath[src] = null;
|
|
485
|
+
this._indexAdd(dest, this.head, this._indexRemove(src, this.head));
|
|
294
486
|
} else {
|
|
295
487
|
/** @todo decide: throw error or silently ignore non-existant file? */
|
|
296
488
|
}
|
|
@@ -305,21 +497,21 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
305
497
|
copy(srcLogicalPath, destLogicalPath) {
|
|
306
498
|
let srcPrefix = srcLogicalPath.endsWith('/') ? srcLogicalPath : srcLogicalPath + '/';
|
|
307
499
|
let destPrefix = destLogicalPath.endsWith('/') ? destLogicalPath : destLogicalPath + '/';
|
|
308
|
-
let srcList =
|
|
500
|
+
let srcList = this._indexFindPaths(srcPrefix);
|
|
309
501
|
let pairs = srcList.map(src => [src, destPrefix + src.slice(srcPrefix.length)]);
|
|
310
502
|
if (pairs.length === 0) pairs.push([srcLogicalPath, destLogicalPath]);
|
|
311
503
|
for (let [src, dest] of pairs) {
|
|
312
504
|
if (!dest) throw new Error(`Target logical path cannot be empty`);
|
|
313
|
-
let digest = this
|
|
505
|
+
let digest = this._indexGet(src, this.head, true);
|
|
314
506
|
if (!digest) throw new Error(`Source logical path "${src}" does not exist in object [${this.id}]`);
|
|
315
507
|
//console.log(this.state);
|
|
316
|
-
let files = this.state[digest];
|
|
508
|
+
let files = this.state[digest[this.digestAlgorithm]];
|
|
317
509
|
let index = files?.indexOf(dest);
|
|
318
510
|
if (index >= 0) {
|
|
319
511
|
/** @todo decide: throw error or silently ignore existing dest file? */
|
|
320
512
|
} else {
|
|
321
513
|
files.push(dest);
|
|
322
|
-
this
|
|
514
|
+
this._indexAdd(dest, this.head, digest);
|
|
323
515
|
}
|
|
324
516
|
}
|
|
325
517
|
}
|
|
@@ -327,20 +519,23 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
327
519
|
/**
|
|
328
520
|
* Delete a file
|
|
329
521
|
* @param {string} logicalPath
|
|
522
|
+
* @param {string} [version]
|
|
330
523
|
*/
|
|
331
|
-
delete(logicalPath) {
|
|
332
|
-
|
|
333
|
-
let names = this.getDigest(logicalPath) ? [logicalPath] :
|
|
334
|
-
Object.keys(this.#byPath).filter(p => p.startsWith(prefix));
|
|
524
|
+
delete(logicalPath, version = this.head) {
|
|
525
|
+
const names = this._indexFindPaths(logicalPath, version);
|
|
335
526
|
let count = 0;
|
|
527
|
+
const state = this.versionState(version);
|
|
336
528
|
for (let lp of names) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
529
|
+
const digest = this.getDigest(lp, version);
|
|
530
|
+
const files = state[digest];
|
|
531
|
+
const index = files?.indexOf(lp);
|
|
340
532
|
if (index >= 0) {
|
|
341
|
-
files.
|
|
342
|
-
|
|
343
|
-
|
|
533
|
+
if (files.length === 1) {
|
|
534
|
+
delete state[digest];
|
|
535
|
+
} else {
|
|
536
|
+
files.splice(index, 1);
|
|
537
|
+
}
|
|
538
|
+
this._indexRemove(lp, version);
|
|
344
539
|
count++;
|
|
345
540
|
}
|
|
346
541
|
}
|
|
@@ -348,19 +543,17 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
348
543
|
}
|
|
349
544
|
|
|
350
545
|
reinstate(logicalPath, versionName) {
|
|
351
|
-
let
|
|
352
|
-
let digest = this.getDigest(logicalPath, versionName);
|
|
353
|
-
/** @type {FileRef[]} */
|
|
354
|
-
let files = digest ? [{ logicalPath, digest }] :
|
|
355
|
-
[...this.find(f => f.logicalPath.startsWith(prefix), versionName)];
|
|
546
|
+
let names = this._indexFindPaths(logicalPath, versionName);
|
|
547
|
+
//let digest = this.getDigest(logicalPath, versionName);
|
|
356
548
|
let state = this.state;
|
|
357
549
|
let count = 0;
|
|
358
|
-
for (let
|
|
359
|
-
|
|
360
|
-
|
|
550
|
+
for (let lp of names) {
|
|
551
|
+
const digests = this._indexGet(lp, versionName);
|
|
552
|
+
if (digests) {
|
|
553
|
+
let c = this.delete(lp);
|
|
361
554
|
}
|
|
362
|
-
state[
|
|
363
|
-
this
|
|
555
|
+
state[digests[this.digestAlgorithm]] = [lp];
|
|
556
|
+
this._indexAdd(lp, this.head, digests);
|
|
364
557
|
count++;
|
|
365
558
|
}
|
|
366
559
|
return count;
|
|
@@ -368,4 +561,4 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
368
561
|
|
|
369
562
|
}
|
|
370
563
|
|
|
371
|
-
module.exports = { OcflObjectInventory, OcflObjectInventoryMut };
|
|
564
|
+
module.exports = { OcflObjectInventory, OcflObjectInventoryMut, VersionNumber };
|