@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/object.js
CHANGED
|
@@ -3,207 +3,262 @@
|
|
|
3
3
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const validation = require('./validation.js');
|
|
6
|
-
const {
|
|
7
|
-
const { OcflObjectInventory, OcflObjectInventoryMut } = require('./inventory.js');
|
|
6
|
+
const { enumeration } = require('./enum.js');
|
|
7
|
+
const { OcflObjectInventory, OcflObjectInventoryMut, VersionNumber } = require('./inventory.js');
|
|
8
8
|
const { OcflDigest } = require('./digest.js');
|
|
9
|
-
const {
|
|
9
|
+
const { OcflObjectTransaction, OcflObjectTransactionImpl } = require('./transaction.js');
|
|
10
10
|
const { OcflStore } = require('./store.js');
|
|
11
11
|
const { OcflExtension } = require('./extension.js');
|
|
12
12
|
const { NotImplementedError } = require('./error.js');
|
|
13
|
-
const { parallelize,
|
|
13
|
+
const { parallelize, isDirEmpty, findNamasteVersion } = require('./utils');
|
|
14
|
+
const { OcflObjectFile } = require('./file.js');
|
|
14
15
|
const { OCFL_VERSION, OCFL_VERSIONS, INVENTORY_NAME, NAMASTE_PREFIX_OBJECT, NAMASTE_T } = require('./constants').OcflConstants;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
static MERGE = new this();
|
|
19
|
-
/** New version will assume that all existing files are removed first before adding new ones */
|
|
20
|
-
static REPLACE = new this();
|
|
21
|
-
};
|
|
17
|
+
const UPDATE_MODES = /** @type { const } */(['MERGE', 'REPLACE']);
|
|
18
|
+
const UPDATE_MODE = enumeration(UPDATE_MODES);
|
|
22
19
|
const DIGEST = OcflDigest.CONTENT;
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
|
-
* @typedef {
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
28
|
-
* @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256.
|
|
29
|
-
* @property {string} [id] - Identifier for the object. Only be used in a newly created object.
|
|
30
|
-
* @property {string} [contentDirectory='content'] - Content directory name. Only applies to a newly created object.
|
|
31
|
-
* @property {string} [ocflVersion=c.OCFL_VERSION] - Ocfl version. Only applies to a newly created object.
|
|
32
|
-
* @property {OcflExtension[]} [extensions] - Reference to existing extensions defined outside of the object, such as in the storage root.
|
|
33
|
-
*/
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {UPDATE_MODE | ('MERGE' | 'REPLACE') } ObjectUpdateMode
|
|
36
|
-
*
|
|
37
|
-
* @typedef {Object} FileRef
|
|
38
|
-
* @property {string} [contentPath] - Actual path to the file location relative to the ocfl object root.
|
|
39
|
-
* @property {string} [logicalPath] - A path that represents a file’s location in the logical state of an object.
|
|
40
|
-
* @property {string} [digest] - An algorithmic characterization of the contents of a file conforming to a standard digest algorithm.
|
|
41
|
-
* @property {string} [version] - Version of the OCFL object.
|
|
42
|
-
* @property {string} [lastModified] - Last modified date of the file.
|
|
43
|
-
* @property {string} [size] - Size of the file in bytes.
|
|
22
|
+
* @typedef {(typeof UPDATE_MODES)[number]} UpdateModeStr
|
|
23
|
+
* @typedef { InstanceType<typeof UPDATE_MODE> | UpdateModeStr} UpdateMode
|
|
24
|
+
* @typedef {string} LogicalPath
|
|
44
25
|
* @typedef {{logicalPath: string, version?: string}} FileRefLogical
|
|
45
|
-
* @typedef {{digest: string}} FileRefDigest
|
|
46
|
-
* @typedef {{contentPath: string}} FileRefContent
|
|
26
|
+
* @typedef {{digest: string, version?: string}} FileRefDigest
|
|
27
|
+
* @typedef {{contentPath: string, version?: string}} FileRefContent
|
|
28
|
+
* @typedef {import('./types').Inventory} Inventory
|
|
29
|
+
* @typedef {import('./types').OcflVersion} OcflVersion
|
|
30
|
+
* @typedef {import('./types').OcflObjectConfig} OcflObjectConfig
|
|
31
|
+
*
|
|
47
32
|
*/
|
|
48
33
|
|
|
49
34
|
/**
|
|
50
|
-
*
|
|
35
|
+
* A class representing an OCFL Object allowing to read and write the content of an OCFL Object in the underlying store.
|
|
51
36
|
*/
|
|
52
|
-
class
|
|
53
|
-
#ocflObject;
|
|
54
|
-
#lastModified;
|
|
55
|
-
#size;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
59
|
-
* @param {OcflObjectImpl} ocflObject
|
|
60
|
-
* @param {FileRef} fileRef
|
|
61
|
-
*/
|
|
62
|
-
constructor(ocflObject, fileRef) {
|
|
63
|
-
this.#ocflObject = ocflObject;
|
|
64
|
-
// for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
65
|
-
// if (fileRef[key]) this[key] = fileRef[key];
|
|
66
|
-
// }
|
|
67
|
-
if (fileRef.logicalPath) this.logicalPath = fileRef.logicalPath;
|
|
68
|
-
if (fileRef.contentPath) this.contentPath = fileRef.contentPath;
|
|
69
|
-
if (fileRef.digest) this.digest = fileRef.digest;
|
|
70
|
-
if (fileRef.version) this.version = fileRef.version;
|
|
71
|
-
if (fileRef.lastModified != null) this.#lastModified = fileRef.lastModified;
|
|
72
|
-
if (fileRef.size != null) this.#size = fileRef.size;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Resolve contentPath given either the `logicalPath` and `version`, `digest`, or `contentPath`.
|
|
77
|
-
*/
|
|
78
|
-
async #resolveContentPath() {
|
|
79
|
-
let contentPath = this.contentPath;
|
|
80
|
-
if (!contentPath) {
|
|
81
|
-
let inv = await this.#ocflObject.getInventory();
|
|
82
|
-
if (!inv) throw new Error(`OCFL Object "${this.#ocflObject.id}" does not exist`);
|
|
83
|
-
let version = !this.version || this.version === 'latest' ? inv.head : this.version;
|
|
84
|
-
let digest = this.digest || inv.getDigest(this.logicalPath, version);
|
|
85
|
-
contentPath = inv.getContentPath(digest);
|
|
86
|
-
if (!contentPath) throw new Error(`Cannot find content "${this.toString()}" in the OCFL Object "${this.#ocflObject.id}" version "${this.version || 'latest'}"`);
|
|
87
|
-
}
|
|
88
|
-
return contentPath;
|
|
89
|
-
}
|
|
37
|
+
class OcflObject {
|
|
90
38
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
39
|
+
/** @type {OcflVersion} */
|
|
40
|
+
#ocflVersion;
|
|
41
|
+
/** @type {string} */
|
|
42
|
+
#root;
|
|
43
|
+
/** @type {string} */
|
|
44
|
+
#workspace;
|
|
45
|
+
/** @type {string} */
|
|
46
|
+
#id;
|
|
47
|
+
/** @type {('sha256' | 'sha512')} */
|
|
48
|
+
#digestAlgorithm;
|
|
49
|
+
/** @type {OcflExtension[]} */
|
|
50
|
+
#extensions;
|
|
51
|
+
/** @type {OcflStore} */
|
|
52
|
+
#store;
|
|
53
|
+
/**
|
|
54
|
+
* A default list of digest algorithms to be calculated for each file in this object and added to the fixity block.
|
|
55
|
+
* @type {string[]}
|
|
98
56
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
toString() {
|
|
102
|
-
let props = [];
|
|
103
|
-
for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
104
|
-
if (this[key]) props.push(key + ": '" + this[key] + "'");
|
|
105
|
-
}
|
|
106
|
-
return `{ ${props.join(', ')} }`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get the file content as a buffer
|
|
111
|
-
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
57
|
+
fixityAlgorithms;
|
|
112
58
|
|
|
113
|
-
* @return {Promise<Buffer>}
|
|
114
|
-
*/
|
|
115
|
-
async buffer(options) {
|
|
116
|
-
return this.#ocflObject.readFile(await this.#resolveContentPath(), options);
|
|
117
|
-
}
|
|
118
59
|
/**
|
|
119
|
-
*
|
|
60
|
+
* Create a new OCFL Object
|
|
61
|
+
* @param {OcflObjectConfig} config
|
|
62
|
+
* @param {OcflStore} store
|
|
120
63
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
64
|
+
constructor(config, store) {
|
|
65
|
+
if (!store) throw new TypeError('[OcflObject] store is required.');
|
|
66
|
+
this.#store = store;
|
|
67
|
+
if (!config.root) throw new TypeError('[OcflObject] config.root is required.');
|
|
68
|
+
if (config.root === config.workspace) throw new Error('[OcflObject] config.root and config.workspace must be different.');
|
|
69
|
+
this.#root = path.resolve(config.root);
|
|
70
|
+
this.#ocflVersion = config.ocflVersion || /** @type {OcflVersion} */(OCFL_VERSION);
|
|
71
|
+
//this.contentVersion = null; // No content yet
|
|
72
|
+
this.#id = config.id;
|
|
73
|
+
this.#extensions = config.extensions;
|
|
74
|
+
//this.fixityDigest = config.fixityDigest ?? this.digestAlgorithm;
|
|
75
|
+
this.digestAlgorithm = config.digestAlgorithm || 'sha512';
|
|
76
|
+
this.#workspace = config.workspace ? path.resolve(config.workspace) : undefined;
|
|
77
|
+
this.contentDirectory = config.contentDirectory ?? 'content';
|
|
78
|
+
/** @type Object.<string, OcflObjectInventory> */
|
|
79
|
+
this._inventory = {};
|
|
80
|
+
this.fixityAlgorithms = config.fixityAlgorithms;
|
|
123
81
|
}
|
|
124
82
|
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
*/
|
|
130
|
-
async text(encoding = 'utf8') {
|
|
83
|
+
/** the digest algorithm used to identify files within the OCFL object */
|
|
84
|
+
get digestAlgorithm() { return this.#digestAlgorithm; }
|
|
85
|
+
set digestAlgorithm(algorithm) {
|
|
86
|
+
this.#digestAlgorithm = algorithm;
|
|
131
87
|
// @ts-ignore
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @deprecated
|
|
136
|
-
*/
|
|
137
|
-
async asString(encoding = 'utf8') {
|
|
138
|
-
// @ts-ignore
|
|
139
|
-
return this.text(encoding);
|
|
88
|
+
this.#digestAlgorithm = enumeration.of(DIGEST, algorithm || 'sha512')?.name;
|
|
89
|
+
if (!this.#digestAlgorithm) throw new Error('Invalid digest algorithm. Must be one of `sha256` or `sha512`.');
|
|
140
90
|
}
|
|
141
91
|
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
*/
|
|
148
|
-
async asStream(options) {
|
|
149
|
-
return this.#ocflObject.createReadStream(await this.#resolveContentPath(), options);
|
|
150
|
-
}
|
|
92
|
+
/**
|
|
93
|
+
* Identifier of the OCFL Object
|
|
94
|
+
* @return {string}
|
|
95
|
+
*/
|
|
96
|
+
get id() { return this.inventory?.id || this.#id; }
|
|
151
97
|
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
98
|
+
/**
|
|
99
|
+
* Latest version number of the OCFL Object
|
|
100
|
+
* @return {VersionNumber}
|
|
101
|
+
*/
|
|
102
|
+
get headVersion() {
|
|
103
|
+
const head = this.inventory?.head;
|
|
104
|
+
if (head) return VersionNumber.fromString(head);
|
|
159
105
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Interface class representing OCFL Object
|
|
165
|
-
* @interface
|
|
166
|
-
*/
|
|
167
|
-
class OcflObject {
|
|
168
106
|
|
|
169
107
|
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
get
|
|
108
|
+
* All available version numbers in the OCFL Object
|
|
109
|
+
* @return {VersionNumber[]}
|
|
110
|
+
*/
|
|
111
|
+
get versions() {
|
|
112
|
+
return Object.keys(this.inventory?.versions || {}).map(v => VersionNumber.fromString(v));
|
|
113
|
+
}
|
|
174
114
|
|
|
175
115
|
/**
|
|
176
116
|
* The absolute path to the root of this OCFL Object
|
|
177
117
|
* @return {string}
|
|
178
118
|
*/
|
|
179
|
-
get root() {
|
|
119
|
+
get root() { return this.#root; }
|
|
180
120
|
|
|
181
121
|
/**
|
|
182
122
|
* The absolute path to the temporary workspace of this OCFL Object
|
|
183
123
|
* @return {string}
|
|
184
124
|
*/
|
|
185
|
-
get workspace() {
|
|
125
|
+
get workspace() { return this.#workspace; }
|
|
186
126
|
|
|
187
|
-
|
|
127
|
+
/** The raw OCFL version number the object adheres to , eg 1.0, 1.1) */
|
|
128
|
+
get ocflVersion() { return this.#ocflVersion; }
|
|
188
129
|
|
|
189
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Create a new backend-specific update transaction
|
|
132
|
+
* @param {OcflObjectInventoryMut} inventory
|
|
133
|
+
* @return {Promise<OcflObjectTransaction>}
|
|
134
|
+
*/
|
|
135
|
+
async #createTransaction(inventory) {
|
|
136
|
+
await this._validateObjectPath();
|
|
137
|
+
if (!this.workspace) await this._ensureNamaste();
|
|
138
|
+
let workspacePath = this.workspace || this.root;
|
|
139
|
+
return OcflObjectTransactionImpl.create(this, this.#store, inventory, workspacePath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async _ensureNamaste() {
|
|
143
|
+
let prefix = NAMASTE_PREFIX_OBJECT;
|
|
144
|
+
let nv = await findNamasteVersion(this.#store, prefix, this.root);
|
|
145
|
+
if (nv === '') throw validation.createError(6, this.root);
|
|
146
|
+
if (nv) {
|
|
147
|
+
if (!this.ocflVersion) this.#ocflVersion = /** @type {OcflVersion} */(nv);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// namaste not found, check if object root is not empty
|
|
151
|
+
let files;
|
|
152
|
+
try {
|
|
153
|
+
files = await this.#store.readdir(this.root);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
}
|
|
156
|
+
if (files && files.length > 0) throw new Error('Cannot create an OCFL Object in a non-empty directory');
|
|
157
|
+
//create the namaste file
|
|
158
|
+
let filePath = path.join(this.root, NAMASTE_T + prefix + this.ocflVersion);
|
|
159
|
+
await this.#store.writeFile(filePath, prefix + this.ocflVersion + '\n', 'utf8');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read and parse the inventory.json file
|
|
164
|
+
* @param {string} [version] - Full name of the version, eg: v1, v2, v3
|
|
165
|
+
* @returns {Promise<OcflObjectInventory>}
|
|
166
|
+
*/
|
|
167
|
+
async _readInventory(version = 'latest') {
|
|
168
|
+
let invPath = version === 'latest' ? INVENTORY_NAME : path.join(version, INVENTORY_NAME);
|
|
169
|
+
let datastr;
|
|
170
|
+
try {
|
|
171
|
+
datastr = /** @type string */(await this.readFile(invPath, 'utf8'));
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (err.code !== 'ENOENT') throw err;
|
|
174
|
+
//inventory does not exist yet
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
let data = /** @type Inventory */(JSON.parse(datastr));
|
|
178
|
+
let [digest, actualDigest] = /** @type [string,string] */ (await Promise.all([
|
|
179
|
+
this.readFile(invPath + '.' + data.digestAlgorithm, 'utf8'),
|
|
180
|
+
OcflDigest.digest(data.digestAlgorithm, datastr)
|
|
181
|
+
]));
|
|
182
|
+
digest = digest.match(/[^\s]+/)?.[0];
|
|
183
|
+
if (digest !== actualDigest) throw new Error(`Inventory file ${this.root}/${invPath} digest mismatch: recorded=${digest} actual=${actualDigest}`);
|
|
184
|
+
//return new OcflObjectInventory({ data, digest });
|
|
185
|
+
return new OcflObjectInventory(data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Set the latest version of inventory in the cache
|
|
190
|
+
* @param {*} inventory
|
|
191
|
+
* @param {*} version
|
|
192
|
+
*/
|
|
193
|
+
_setInventory(inventory, version = 'latest') {
|
|
194
|
+
//this._inventory['latest'] = this._inventory[inventory.head] = inventory;
|
|
195
|
+
this._inventory[version] = inventory;
|
|
196
|
+
if (version === 'latest') this._inventory[inventory.head] = inventory;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// all the parent directories cannot be an ocfl object
|
|
200
|
+
async _validateObjectPath() {
|
|
201
|
+
var p = this.root;
|
|
202
|
+
while (p !== '.' && p !== '/') {
|
|
203
|
+
p = path.dirname(p);
|
|
204
|
+
let nv = await findNamasteVersion(this.#store, NAMASTE_PREFIX_OBJECT, p);
|
|
205
|
+
if (nv) throw new Error(`Object root cannot be nested under another object ${p}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
190
208
|
|
|
191
209
|
/**
|
|
192
210
|
* If a cache exists, return the cached inventory. Otherwise read it from the storage and cache it.
|
|
211
|
+
* If the inventory file does not exist, eg in a newly created object, returns undefined.
|
|
193
212
|
* @param {string} [version] - Full name of the version, eg: v1, v2, v3
|
|
194
213
|
* @returns {Promise<OcflObjectInventory>}
|
|
195
214
|
*/
|
|
196
|
-
async getInventory(version = 'latest') {
|
|
215
|
+
async getInventory(version = 'latest') {
|
|
216
|
+
if (!this._inventory[version]) {
|
|
217
|
+
let inv = await this._readInventory(version);
|
|
218
|
+
if (inv) this._setInventory(inv, version);
|
|
219
|
+
}
|
|
220
|
+
return this._inventory[version];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_baseInventory() {
|
|
224
|
+
return {
|
|
225
|
+
id: this.id,
|
|
226
|
+
digestAlgorithm: this.digestAlgorithm || 'sha512',
|
|
227
|
+
type: /** @type {Inventory["type"]} */(`https://ocfl.io/${this.ocflVersion}/spec/#inventory`),
|
|
228
|
+
contentDirectory: this.contentDirectory || 'content'
|
|
229
|
+
};
|
|
230
|
+
}
|
|
197
231
|
|
|
198
232
|
/**
|
|
199
233
|
* Update the content files or directories as one transaction and commit the changes as a new version.
|
|
234
|
+
* After obtaining the OcflObjectTransaction instance, make sure to either call the
|
|
235
|
+
* {@link OcflObjectTransaction#commit} or {@link OcflObjectTransaction#rollback} method to complete the transaction.
|
|
200
236
|
* If callback function `cb` is provided, all update operations can be done in the callback function
|
|
201
237
|
* and all changes are automatically commited at the end of the function.
|
|
202
|
-
* @param {function(OcflObjectTransaction)
|
|
203
|
-
* @param {
|
|
238
|
+
* @param {function(OcflObjectTransaction):Promise<*>} [updater]
|
|
239
|
+
* @param {UpdateMode} [mode_]
|
|
204
240
|
* @return {Promise<?OcflObjectTransaction>}
|
|
205
241
|
*/
|
|
206
|
-
async update(updater,
|
|
242
|
+
async update(updater, mode_ = UPDATE_MODE.MERGE) {
|
|
243
|
+
const mode = enumeration.of(UPDATE_MODE, mode_);
|
|
244
|
+
if (!mode) throw new TypeError(`Invalid mode '${mode.toString()}'`);
|
|
245
|
+
const inv = await this.getInventory() ?? this._baseInventory();
|
|
246
|
+
const newInv = await OcflObjectInventory.newVersion(inv, mode === UPDATE_MODE.REPLACE);
|
|
247
|
+
const t = await this.#createTransaction(newInv);
|
|
248
|
+
if (typeof updater === 'function') {
|
|
249
|
+
try {
|
|
250
|
+
await updater(t);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
// console.log(error);
|
|
253
|
+
await t.rollback();
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
await t.commit();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
return t;
|
|
260
|
+
}
|
|
261
|
+
|
|
207
262
|
|
|
208
263
|
/**
|
|
209
264
|
* Import one or more content files or directories to the object
|
|
@@ -211,7 +266,7 @@ class OcflObject {
|
|
|
211
266
|
* Use this method to simplify copying all contents of a directory
|
|
212
267
|
* in local filesystem to the OCFL Object content directory
|
|
213
268
|
* @param {string|string[]|string[][]} content - The source path(s) or an array of a tuple [source, logical path]
|
|
214
|
-
* @param {
|
|
269
|
+
* @param {UpdateMode} [mode=UPDATE_MODE.MERGE] - Update mode.
|
|
215
270
|
*/
|
|
216
271
|
async import(content, mode = UPDATE_MODE.MERGE) {
|
|
217
272
|
//let entries = /**@type {string[][]}*/(content);
|
|
@@ -243,44 +298,17 @@ class OcflObject {
|
|
|
243
298
|
await this.import(sourceDir, mode);
|
|
244
299
|
}
|
|
245
300
|
|
|
246
|
-
async load() { throw new NotImplementedError(); }
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Iterate through the content files contained in the specified version.
|
|
250
|
-
* Returns an Iterator that contains FileRef which consists of logical path, content path, and digest, in no particular order.
|
|
251
|
-
* The iterator is implemented as a generator.
|
|
252
|
-
* @param {string} [version]
|
|
253
|
-
* @return {Promise<Generator<OcflObjectFile, void, unknown>>}
|
|
254
|
-
*/
|
|
255
|
-
async files(version) { throw new Error('Not Implemented'); }
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get a file by its logical path and version. If version is omitted, it defaults to the last version.
|
|
259
|
-
* @callback GetFileByLogical
|
|
260
|
-
* @param {string} logicalPath Logical path of the file, eg: 'test'
|
|
261
|
-
* @param {string} [version='latest'] The object version name, eg: 'v1', 'v2'. Default to 'latest'
|
|
262
|
-
* @returns {OcflObjectFile}
|
|
263
|
-
*/
|
|
264
|
-
/**
|
|
265
|
-
* Get a file by either the [logical path and version], digest, or content path.
|
|
266
|
-
* @callback GetFileByOpt
|
|
267
|
-
* @param { FileRefLogical | FileRefDigest | FileRefContent } opt A choice of logical path (eg: 'test') and version (eg: 'v1'),
|
|
268
|
-
* digest of the file content, or content path (eg: 'v1/content/test')
|
|
269
|
-
* @returns {OcflObjectFile}
|
|
270
|
-
*/
|
|
271
|
-
/**
|
|
272
|
-
* Retrieve the content of a file either as string, buffer, or stream.
|
|
273
|
-
* @type { GetFileByLogical & GetFileByOpt }
|
|
274
|
-
*/
|
|
275
|
-
// @ts-ignore
|
|
276
|
-
getFile = function (opt, version) { };
|
|
277
|
-
|
|
278
301
|
/**
|
|
279
302
|
* Count the number of files contained in this object
|
|
280
303
|
* @param {string} [version]
|
|
281
304
|
*/
|
|
282
305
|
async count(version) {
|
|
283
|
-
|
|
306
|
+
let inv = await this.getInventory();
|
|
307
|
+
return [...inv.files(version)].length;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async createReadable(filePath, options) {
|
|
311
|
+
return this.#store.createReadable(path.join(this.root, filePath), options);
|
|
284
312
|
}
|
|
285
313
|
|
|
286
314
|
/**
|
|
@@ -289,172 +317,157 @@ class OcflObject {
|
|
|
289
317
|
* The existing directory may or may not be a valid OCFL Object.
|
|
290
318
|
* @return {Promise<boolean>}
|
|
291
319
|
*/
|
|
292
|
-
async exists() {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Abstract class implementing {@link OcflObject}.
|
|
301
|
-
* This class provides common functionalities for the subclasses and
|
|
302
|
-
* at the same provide encapsulation emulating private and protected methods.
|
|
303
|
-
* @implements {OcflObject}
|
|
304
|
-
*/
|
|
305
|
-
class OcflObjectImpl extends OcflObject {
|
|
306
|
-
|
|
307
|
-
/** @type {string} */
|
|
308
|
-
#root;
|
|
309
|
-
/** @type {string} */
|
|
310
|
-
#workspace;
|
|
311
|
-
/** @type {string} */
|
|
312
|
-
#id;
|
|
313
|
-
/** @type {OcflExtension[]} */
|
|
314
|
-
#extensions;
|
|
315
|
-
/** @type {OcflStore} */
|
|
316
|
-
#store;
|
|
320
|
+
async exists() {
|
|
321
|
+
return !(await isDirEmpty(this.#store, this.root));
|
|
322
|
+
}
|
|
317
323
|
|
|
318
324
|
/**
|
|
319
|
-
*
|
|
320
|
-
* @param {
|
|
321
|
-
* @param {
|
|
325
|
+
* Copy all content of this Object to a directory in local filesystem
|
|
326
|
+
* @param {string} targetDir
|
|
327
|
+
* @param {string} version
|
|
322
328
|
*/
|
|
323
|
-
|
|
324
|
-
super();
|
|
325
|
-
if (!store) throw new TypeError('[OcflObject] store is required.');
|
|
326
|
-
this.#store = store;
|
|
327
|
-
if (!config.root) throw new TypeError('[OcflObject] config.root is required.');
|
|
328
|
-
if (config.root === config.workspace) throw new Error('[OcflObject] config.root and config.workspace must be different.');
|
|
329
|
-
this.#root = path.resolve(config.root);
|
|
330
|
-
this.ocflVersion = config.ocflVersion || OCFL_VERSION;
|
|
331
|
-
//this.contentVersion = null; // No content yet
|
|
332
|
-
this.#id = config.id;
|
|
333
|
-
this.#extensions = config.extensions;
|
|
334
|
-
this.digestAlgorithm = DIGEST.of(config.digestAlgorithm || 'sha512');
|
|
335
|
-
if (!this.digestAlgorithm) throw new Error('Invalid digest algorithm. Must be one of `sha256` or `sha512`.');
|
|
336
|
-
//this.fixityDigest = config.fixityDigest ?? this.digestAlgorithm;
|
|
337
|
-
this.#workspace = config.workspace ? path.resolve(config.workspace) : undefined;
|
|
338
|
-
this.contentDirectory = config.contentDirectory ?? 'content';
|
|
339
|
-
/** @type Object.<string, OcflObjectInventory> */
|
|
340
|
-
this._inventory = {};
|
|
341
|
-
}
|
|
342
|
-
get id() { return this._inventory?.latest?.id || this.#id; }
|
|
343
|
-
get root() { return this.#root; }
|
|
344
|
-
get workspace() { return this.#workspace; }
|
|
329
|
+
async export(targetDir, version) { throw new Error('Not Implemented'); }
|
|
345
330
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
331
|
+
/**
|
|
332
|
+
* Iterate through the content files contained in the specified version.
|
|
333
|
+
* Returns an Iterator that contains FileRef which consists of logical path, content path, and digest, in no particular order.
|
|
334
|
+
* The iterator is implemented as a generator.
|
|
335
|
+
* @param {string} [version]
|
|
336
|
+
* @return {Promise<Generator<OcflObjectFile, void, unknown>>}
|
|
337
|
+
*/
|
|
338
|
+
async files(version) {
|
|
339
|
+
let inv = await this.getInventory();
|
|
340
|
+
let o = this;
|
|
341
|
+
function* files() {
|
|
342
|
+
for (const f of inv.files(version)) {
|
|
343
|
+
yield new OcflObjectFile(o, f);
|
|
344
|
+
}
|
|
350
345
|
}
|
|
351
|
-
return
|
|
346
|
+
return files();
|
|
352
347
|
}
|
|
353
348
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Return a file representation by the content path and version,
|
|
351
|
+
* @param { string } contentPath
|
|
352
|
+
* @param { string } [version='latest']
|
|
353
|
+
*/
|
|
354
|
+
async fileByContentPath(contentPath, version) {
|
|
355
|
+
const inv = await this.getInventory();
|
|
356
|
+
const fr = inv?.getFileByContentPath(contentPath, version);
|
|
357
|
+
if (fr) return new OcflObjectFile(this, fr);
|
|
358
|
+
};
|
|
357
359
|
|
|
358
360
|
/**
|
|
359
|
-
*
|
|
360
|
-
* @param {string}
|
|
361
|
-
* @
|
|
361
|
+
* Return a file representation by the digest and version,
|
|
362
|
+
* @param { string } digest
|
|
363
|
+
* @param { string } [version='latest']
|
|
362
364
|
*/
|
|
363
|
-
async
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
} catch (err) {
|
|
369
|
-
if (err.code !== 'ENOENT') throw err;
|
|
370
|
-
//inventory does not exist yet
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
let data = /** @type Inventory */(JSON.parse(datastr));
|
|
374
|
-
let [digest, actualDigest] = /** @type [string,string] */ (await Promise.all([
|
|
375
|
-
this.readFile(invPath + '.' + data.digestAlgorithm, 'utf8'),
|
|
376
|
-
OcflDigest.digestAsync(data.digestAlgorithm, datastr)
|
|
377
|
-
]));
|
|
378
|
-
digest = digest.match(/[^\s]+/)?.[0];
|
|
379
|
-
if (digest !== actualDigest) throw new Error(`Inventory file ${this.root}/${invPath} digest mismatch: recorded=${digest} actual=${actualDigest}`);
|
|
380
|
-
//return new OcflObjectInventory({ data, digest });
|
|
381
|
-
return new OcflObjectInventory(data);
|
|
382
|
-
}
|
|
365
|
+
async fileByDigest(digest, version) {
|
|
366
|
+
const inv = await this.getInventory();
|
|
367
|
+
const fr = inv?.getFileByDigest(digest, version);
|
|
368
|
+
if (fr) return new OcflObjectFile(this, fr);
|
|
369
|
+
};
|
|
383
370
|
|
|
384
|
-
|
|
385
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Return a file representation by the logical path and version,
|
|
373
|
+
* @param { string } logicalPath
|
|
374
|
+
* @param { string } [version='latest']
|
|
375
|
+
*/
|
|
376
|
+
async fileByLogicalPath(logicalPath, version) {
|
|
386
377
|
const inv = await this.getInventory();
|
|
387
|
-
|
|
378
|
+
const fr = inv?.getFile(logicalPath, version);
|
|
379
|
+
if (fr) return new OcflObjectFile(this, fr);
|
|
380
|
+
};
|
|
388
381
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Return a file representation by either the [logical path and version], digest, or content path.
|
|
384
|
+
* The returned file object can be used to retrieve the content of a file either as string, buffer, or stream.
|
|
385
|
+
* If the object inventory file is not loaded, this file object will not include the full file metadata information.
|
|
386
|
+
* If version is omitted, it defaults to the last version.
|
|
387
|
+
* @param { LogicalPath | FileRefLogical | FileRefDigest | FileRefContent } opt A choice of logical path (eg: 'test') and version (eg: 'v1'),
|
|
388
|
+
* digest of the file content, or content path (eg: 'v1/content/test')
|
|
389
|
+
* @param {string} [version='latest'] The object version name, eg: 'v1', 'v2'. Default to 'latest'. Only used if opt is a LogicalPath.
|
|
390
|
+
* @param {string} [lazy] If true, always return a file instance even if the object inventory file is not loaded.
|
|
391
|
+
*/
|
|
392
|
+
getFile(opt, version, lazy) {
|
|
393
|
+
const inv = this.inventory;
|
|
394
|
+
/** @type {any} */
|
|
395
|
+
const file = typeof opt === 'string' ? { logicalPath: opt, version: version } : { ...opt };
|
|
396
|
+
if (inv) {
|
|
397
|
+
let fr;
|
|
398
|
+
if (file.logicalPath) {
|
|
399
|
+
fr = inv?.getFile(file.logicalPath, file.version);
|
|
400
|
+
} else if (file.digest) {
|
|
401
|
+
fr = inv?.getFileByDigest(file.digest, file.version);
|
|
402
|
+
} else if (file.contentPath) {
|
|
403
|
+
fr = inv?.getFileByContentPath(file.contentPath, file.version);
|
|
396
404
|
}
|
|
405
|
+
if (fr) return new OcflObjectFile(this, fr);
|
|
406
|
+
} else if (lazy) {
|
|
407
|
+
return new OcflObjectFile(this, file);
|
|
397
408
|
}
|
|
398
|
-
|
|
399
|
-
}
|
|
409
|
+
};
|
|
400
410
|
|
|
401
411
|
/**
|
|
402
|
-
* @
|
|
403
|
-
* @param {ObjectUpdateMode} [mode=UPDATE_MODE.MERGE]
|
|
412
|
+
* Return the latest version of the cached data from inventory.json which can be read using {@link getInventory()}
|
|
404
413
|
*/
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (!mode || !UPDATE_MODE.has(mode)) throw new TypeError(`Invalid mode '${mode.toString()}'`);
|
|
408
|
-
let inv = await this.getInventory();
|
|
409
|
-
let newInv = await this._createInventory(inv, mode === UPDATE_MODE.REPLACE);
|
|
410
|
-
let t = await this.#createTransaction(newInv);
|
|
411
|
-
if (typeof updater === 'function') {
|
|
412
|
-
try {
|
|
413
|
-
await updater(t);
|
|
414
|
-
} catch (error) {
|
|
415
|
-
// console.log(error);
|
|
416
|
-
await t.rollback();
|
|
417
|
-
throw error;
|
|
418
|
-
}
|
|
419
|
-
await t.commit();
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
return t;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** @deprecated */
|
|
426
|
-
async createReadStream(filePath, options) {
|
|
427
|
-
return this.#store.createReadStream(path.join(this.root, filePath), options);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
async createReadable(filePath, options) {
|
|
431
|
-
return this.#store.createReadable(path.join(this.root, filePath), options);
|
|
414
|
+
get inventory() {
|
|
415
|
+
return this._inventory.latest;
|
|
432
416
|
}
|
|
433
417
|
|
|
434
418
|
/**
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
(relPath: string, options:({encoding: BufferEncoding, flag?: OpenMode | undefined} & Abortable)|BufferEncoding): Promise<string>
|
|
440
|
-
}} ReadFileFn
|
|
419
|
+
* Reads the object inventory file and cache it in the memory.
|
|
420
|
+
* Optionally, preloads and reads all files metadata from the underlying storage.
|
|
421
|
+
* @param {object} [options]
|
|
422
|
+
* @param {boolean} [options.filesMetadata] Whether to read file metadata from the underlying storage.
|
|
441
423
|
*/
|
|
424
|
+
async load({ filesMetadata } = {}) {
|
|
425
|
+
this._inventory = {}; // reset inventory cache
|
|
426
|
+
const inv = await this.getInventory();
|
|
427
|
+
//await this.loadExtensions();
|
|
428
|
+
if (inv && filesMetadata) {
|
|
429
|
+
//load file metadata from underlying storage
|
|
430
|
+
const metadata = inv.metadata = {};
|
|
431
|
+
for await (const file of await this.#store.list(this.root, { recursive: true })) {
|
|
432
|
+
const contentPath = file.path;
|
|
433
|
+
metadata[contentPath] = {
|
|
434
|
+
size: file.size,
|
|
435
|
+
lastModified: file.lastModified.getTime()
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
442
441
|
/**
|
|
443
|
-
* Read a file inside an object.
|
|
444
|
-
* @type {ReadFileFn} abc
|
|
442
|
+
* Read the content of a file inside an object.
|
|
445
443
|
* @param {string} relPath - The file path relative to the object root.
|
|
446
|
-
* @param options - Options to be passed to the underlying method
|
|
444
|
+
* @param {*} options - Options to be passed to the underlying method
|
|
447
445
|
*/
|
|
448
|
-
|
|
446
|
+
async readFile(relPath, options) {
|
|
449
447
|
return this.#store.readFile(path.join(this.root, relPath), options);
|
|
450
448
|
};
|
|
451
449
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Get the file metadata such as size and modification time.
|
|
452
|
+
* @param {string} relPath - The file path relative to the object root.
|
|
453
|
+
*/
|
|
454
|
+
async stat(relPath) {
|
|
455
|
+
return this.#store.stat(path.join(this.root, relPath));
|
|
456
456
|
};
|
|
457
457
|
|
|
458
|
+
toString() { return `OcflObject { root: ${this.root}, id: ${this.id} }`; }
|
|
459
|
+
|
|
460
|
+
toJSON() { return { root: this.root, id: this.id }; }
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @implements {OcflObject}
|
|
468
|
+
*/
|
|
469
|
+
class OcflObjectImpl extends OcflObject {
|
|
470
|
+
|
|
458
471
|
// async _resolveContentPath({ logicalPath = '', contentPath = '', digest = '', version = '' }) {
|
|
459
472
|
// if (!contentPath) {
|
|
460
473
|
// let inv = await this.getInventory();
|
|
@@ -464,138 +477,7 @@ class OcflObjectImpl extends OcflObject {
|
|
|
464
477
|
// return contentPath;
|
|
465
478
|
// }
|
|
466
479
|
|
|
467
|
-
// async getAsBuffer(opt) {
|
|
468
|
-
// let p = await this._resolveContentPath(opt);
|
|
469
|
-
// if (!p) throw new Error(`Cannot find content "${opt.logicalPath || opt.contentPath || opt.digest}" in the OCFL Object "${this.#id}" version "${opt.version || 'latest'}"`);
|
|
470
|
-
// return this.readFile(p, opt.options);
|
|
471
|
-
// }
|
|
472
|
-
// async getAsString(opt) {
|
|
473
|
-
// // @ts-ignore
|
|
474
|
-
// return this.getAsBuffer({ ...opt, options: opt.encoding || 'utf8' });
|
|
475
|
-
// }
|
|
476
|
-
// async getAsStream(opt) {
|
|
477
|
-
// let p = await this._resolveContentPath(opt);
|
|
478
|
-
// if (!p) throw new Error(`Cannot find content "${opt.logicalPath || opt.contentPath || opt.digest}" in the OCFL Object "${this.#id}" version "${opt.version || 'latest'}"`);
|
|
479
|
-
// return this.createReadStream(p, opt.options);
|
|
480
|
-
// }
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Copy all content of this Object to a directory in local filesystem
|
|
484
|
-
* @param {*} targetDir
|
|
485
|
-
*/
|
|
486
|
-
async export(targetDir, version) { }
|
|
487
|
-
|
|
488
|
-
async exists() {
|
|
489
|
-
return !(await isDirEmpty(this.#store, this.root));
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* @param {string} [version]
|
|
494
|
-
*/
|
|
495
|
-
async files(version) {
|
|
496
|
-
let inv = await this.getInventory();
|
|
497
|
-
let o = this;
|
|
498
|
-
function* files() {
|
|
499
|
-
for (const f of inv.files(version)) {
|
|
500
|
-
yield new OcflObjectFile(o, f);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return files();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Create a new inventory from the object config or clone an existing inventory.
|
|
509
|
-
* @param {OcflObjectInventory} [inventory]
|
|
510
|
-
* @param {boolean} [cleanState]
|
|
511
|
-
* @return {OcflObjectInventoryMut}
|
|
512
|
-
*/
|
|
513
|
-
_createInventory(inventory, cleanState) {
|
|
514
|
-
/** @type {Object} */
|
|
515
|
-
let data;
|
|
516
|
-
if (inventory) {
|
|
517
|
-
data = inventory.toJSON();
|
|
518
|
-
} else {
|
|
519
|
-
data = {
|
|
520
|
-
id: this.id,
|
|
521
|
-
digestAlgorithm: this.digestAlgorithm.value,
|
|
522
|
-
type: `https://ocfl.io/${this.ocflVersion}/spec/#inventory`
|
|
523
|
-
};
|
|
524
|
-
if (this.contentDirectory !== 'content') data.contentDirectory = this.contentDirectory;
|
|
525
|
-
}
|
|
526
|
-
return OcflObjectInventory.newVersion(data, cleanState);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
*
|
|
531
|
-
* @param {string} absOrRelPath
|
|
532
|
-
* @return {string}
|
|
533
|
-
*/
|
|
534
|
-
toAbsPath(absOrRelPath = '') {
|
|
535
|
-
if (!path.isAbsolute(absOrRelPath)) return path.join(this.root, absOrRelPath);
|
|
536
|
-
return absOrRelPath;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Create a new backend-specific update transaction
|
|
541
|
-
* @param {OcflObjectInventoryMut} inventory
|
|
542
|
-
* @return {Promise<OcflObjectTransaction>}
|
|
543
|
-
*/
|
|
544
|
-
async #createTransaction(inventory) {
|
|
545
|
-
// @todo: validate existing object
|
|
546
|
-
// check existing
|
|
547
|
-
let workspacePath = this.workspace || this.root;
|
|
548
|
-
let workspaceVersionPath = path.join(workspacePath, inventory.head);
|
|
549
|
-
|
|
550
|
-
if (await this.#store.exists(workspaceVersionPath)) {
|
|
551
|
-
// workspace dir exists, abort update
|
|
552
|
-
throw new Error('Uncommitted changes detected. Object is being updated by other process or there has been a failed update attempt');
|
|
553
|
-
}
|
|
554
|
-
await this._validateObjectPath();
|
|
555
|
-
let createdDir = await this.#store.mkdir(workspacePath);
|
|
556
|
-
if (!this.workspace) await this._ensureNamaste();
|
|
557
|
-
return createTransactionProxy(new OcflObjectTransactionImpl(this, this.#store, inventory, workspaceVersionPath, createdDir));
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
async _ensureNamaste() {
|
|
561
|
-
let prefix = NAMASTE_PREFIX_OBJECT;
|
|
562
|
-
let nv = await findNamasteVersion(this.#store, prefix, this.root);
|
|
563
|
-
if (nv === '') throw validation.createError(6, this.root);
|
|
564
|
-
if (nv) {
|
|
565
|
-
if (!this.ocflVersion) this.ocflVersion = nv;
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
// namaste not found, check if object root is not empty
|
|
569
|
-
let files;
|
|
570
|
-
try {
|
|
571
|
-
files = await this.#store.readdir(this.root);
|
|
572
|
-
} catch (error) {
|
|
573
|
-
}
|
|
574
|
-
if (files && files.length > 0) throw new Error('Cannot create an OCFL Object in a non-empty directory');
|
|
575
|
-
//create the namaste file
|
|
576
|
-
let filePath = path.join(this.root, NAMASTE_T + prefix + this.ocflVersion);
|
|
577
|
-
await this.#store.writeFile(filePath, prefix + this.ocflVersion + '\n', 'utf8');
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// all the parent directories cannot be an ocfl object
|
|
581
|
-
async _validateObjectPath() {
|
|
582
|
-
var p = this.root;
|
|
583
|
-
while (p !== '.' && p !== '/' ) {
|
|
584
|
-
p = path.dirname(p);
|
|
585
|
-
let nv = await findNamasteVersion(this.#store, NAMASTE_PREFIX_OBJECT, p);
|
|
586
|
-
if (nv) throw new Error(`Object root cannot be nested under another object ${p}`);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
480
|
async isObject(aPath) { }
|
|
591
|
-
getVersionString(i) { }
|
|
592
|
-
async determineVersion() { }
|
|
593
|
-
nameVersion(version) {
|
|
594
|
-
return 'ocfl_object_' + version;
|
|
595
|
-
}
|
|
596
|
-
async digest_dir(dir) { }
|
|
597
|
-
async hash_file(p) { }
|
|
598
|
-
async removeEmptyDirectories(folder) { }
|
|
599
481
|
static status(rootPath) { }
|
|
600
482
|
static exists(rootPath) { }
|
|
601
483
|
|
|
@@ -609,48 +491,9 @@ class OcflObjectImpl extends OcflObject {
|
|
|
609
491
|
};
|
|
610
492
|
|
|
611
493
|
|
|
612
|
-
const objectInterface = new OcflObject();
|
|
613
|
-
const proxyHandler = {
|
|
614
|
-
/**
|
|
615
|
-
* @param {OcflObject} target
|
|
616
|
-
* @param {string} prop
|
|
617
|
-
*/
|
|
618
|
-
get(target, prop, receiver) {
|
|
619
|
-
if (prop in objectInterface) {
|
|
620
|
-
let p = target[prop];
|
|
621
|
-
if (typeof p === 'function') {
|
|
622
|
-
return p.bind(target);
|
|
623
|
-
} else {
|
|
624
|
-
return p;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
/**
|
|
631
|
-
* Return any OcflObject implementation as OcflObject.
|
|
632
|
-
* @param {OcflObject} impl
|
|
633
|
-
*/
|
|
634
|
-
function createObjectProxy(impl) {
|
|
635
|
-
return new Proxy(impl, proxyHandler);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Create an OCFL Object
|
|
640
|
-
* @param {OcflObjectConfig} config
|
|
641
|
-
* @return {OcflObject}
|
|
642
|
-
*/
|
|
643
|
-
function createObject(config) {
|
|
644
|
-
throw new Error('Not Implemented');
|
|
645
|
-
}
|
|
646
|
-
|
|
647
494
|
module.exports = {
|
|
648
495
|
OcflObject,
|
|
649
|
-
OcflObjectImpl,
|
|
650
496
|
OcflObjectInventory,
|
|
651
497
|
//OcflObjectTransaction,
|
|
652
|
-
OcflObjectTransactionImpl
|
|
653
|
-
createTransactionProxy,
|
|
654
|
-
createObjectProxy,
|
|
655
|
-
createObject
|
|
498
|
+
OcflObjectTransactionImpl
|
|
656
499
|
};
|