UNPKG

@coedl/ocfl

Version:

Oxford Common File Layout JS library

235 lines (209 loc) 7.11 kB
const { pathExists, stat, readdir, writeFile, readJSON } = require("fs-extra"); const path = require("path"); const pairtree = require("pairtree"); const EventEmitter = require("events"); const { Bucket } = require("./s3"); const OcflObjectFilesystemBackend = require("./ocflObject-filesystem-backend"); const OcflObjectS3Backend = require("./ocflObject-s3-backend"); const DEPOSIT_DIR = "deposit"; const DIGEST_ALGORITHM = "sha512"; const OCFL_VERSION = "1.0"; const NAMASTE = `0=ocfl_${OCFL_VERSION}`; const OBJECT_ID_TO_PATH = pairtree.path; const INVENTORY_TYPE = `https://ocfl.io/${OCFL_VERSION}/spec/#inventory`; const allowedRepositoryBackends = ["filesystem", "s3"]; class Repository extends EventEmitter { constructor({ type = "filesystem", ocflRoot, ocflScratch, s3 }) { super(); this.ocflVersion = "1.0"; if (!allowedRepositoryBackends.includes(type.toLowerCase())) { throw new Error( `Unsupported repository type: options are '${allowedRepositoryBackends.join( ", " )}'` ); } this.backend = type.toLowerCase(); if (this.backend === "filesystem") { if (!ocflRoot) { throw new Error(`You must define 'ocflRoot'`); } if (!ocflScratch) { console.warn( `You haven't defined 'ocflScratch' so you won't be able to operate on objects.` ); } this.ocflRoot = ocflRoot; this.ocflScratch = ocflScratch; this.namaste = path.join(this.ocflRoot, NAMASTE); } else if (this.backend === "s3") { if (!ocflScratch) { console.warn( `You haven't defined 'ocflScratch' so you won't be able to operate on objects.` ); } if (!s3.bucket) { throw new Error(`You must define a 'bucket' to use this backend`); } this.ocflScratch = ocflScratch; this.bucket = new Bucket({ ...s3 }); this.namaste = NAMASTE; } } // // PUBLIC API // object({ id }) { let object; if (this.backend === "filesystem") { object = new OcflObjectFilesystemBackend({ ocflRoot: this.ocflRoot, ocflScratch: this.ocflScratch, ocflVersion: OCFL_VERSION, digestAlgorithm: DIGEST_ALGORITHM, namaste: `0=ocfl_object_${OCFL_VERSION}`, inventoryType: INVENTORY_TYPE, objectIdToPath: OBJECT_ID_TO_PATH, }); } else if (this.backend === "s3") { object = new OcflObjectS3Backend({ bucket: this.bucket, ocflScratch: this.ocflScratch, ocflVersion: OCFL_VERSION, digestAlgorithm: DIGEST_ALGORITHM, namaste: `0=ocfl_object_${OCFL_VERSION}`, inventoryType: INVENTORY_TYPE, }); } return object.init({ id }); } async create() { if ( this.ocflRoot && this.ocflScratch && this.ocflScratch.match(this.ocflRoot) ) { throw new Error(`'ocflScratch' cannot be a subpath of 'ocflRoot'`); } if (!(await pathExists(this.ocflScratch))) { throw new Error( `The OCFL scratch directory '${this.ocflScratch}' doesn't exist.` ); } if (this.backend === "filesystem") await this.__createFilesystemRepository(); if (this.backend === "s3") await this.__createS3Repository(); } async isRepository() { if (this.backend === "filesystem") { return await pathExists(this.namaste); } else if (this.backend === "s3") { let response = await this.bucket.listObjects({ prefix: this.namaste }); return response?.Contents?.length !== undefined ? true : false; } } async findObjects() { if (this.backend === "filesystem") { await this.__findObjectsFilesystemRepository({}); } else if (this.backend === "s3") { await this.__findObjectsS3Repository(); } } // // PRIVATE API // __nameVersion(version) { return `ocfl__${version}`; } async __createFilesystemRepository() { if (!(await pathExists(this.ocflRoot))) { throw new Error( `The OCFL root directory '${this.ocflRoot}' doesn't exist.` ); } const stats = await stat(this.ocflRoot); if (stats.isDirectory()) { if (await pathExists(this.namaste)) { throw new Error("This repository has already been initialized."); } const content = await readdir(this.ocflRoot); if (content.length !== 0) { throw new Error( "Can't initialise a repository as there are already files." ); } // empty - initialise a repo here await writeFile(this.namaste, this.ocflVersion); } } async __createS3Repository() { // is there a namaste file in S3? let namaste = await this.bucket.listObjects({ prefix: this.namaste }); if (namaste?.length) { throw new Error(`This repository has already been initialized.`); } // is there any content in the bucket? let content = await this.bucket.listObjects({}); if (content?.length) { throw new Error( "Can't initialise a repository as there are already files." ); } // empty - initialise a repo here await this.bucket.upload({ target: this.namaste, content: this.namaste }); } async __findObjectsFilesystemRepository({ root }) { if (!root) root = this.ocflRoot; // Recursive function to find OCFL objects const dirs = await readdir(root); for (let d of dirs) { const potentialObject = path.join(root, d); const stats = await stat(potentialObject); if (stats.isDirectory()) { // Looks like an object if (potentialObject.match(DEPOSIT_DIR)) continue; if ( await pathExists( path.join(potentialObject, `0=ocfl_object_${this.ocflVersion}`) ) ) { let files = await readdir(potentialObject); if (files.includes("inventory.json")) { let inventory = await readJSON( path.join(potentialObject, "inventory.json") ); let object = this.object({ id: inventory.id }); this.emit("object", object); } } else { this.__findObjectsFilesystemRepository({ root: potentialObject }); } } } } async __findObjectsS3Repository() { // console.log(this.bucket); let walk = true; let continuationToken = undefined; while (walk) { let objects = await this.bucket.listObjects({ continuationToken }); objects.Contents.forEach(async (entry) => { if (entry.Key.match(/.*\/0=ocfl_object_1.0/)) { let objectPath = path.dirname(entry.Key); let inventory = await this.bucket.readJSON({ target: path.join(objectPath, "inventory.json"), }); // this.emit("object", { objectPath: entry.Key.split("/")[0] }); let object = this.object({ id: inventory.id }); this.emit("object", object); } }); if (objects.NextContinuationToken) { continuationToken = objects.NextContinuationToken; } else { walk = false; } } } } module.exports = Repository;