refactor: Simplify resource metadata generation

This commit is contained in:
Joachim Van Herwegen 2021-05-11 14:55:21 +02:00
parent cae9d54fac
commit 96a07e4853
6 changed files with 26 additions and 78 deletions

View File

@ -1,4 +1,3 @@
import { DataFactory } from 'n3';
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation'; import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata'; import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
@ -6,10 +5,9 @@ import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore'; import type { ResourceStore } from '../storage/ResourceStore';
import { TEXT_TURTLE } from '../util/ContentTypes'; import { TEXT_TURTLE } from '../util/ContentTypes';
import { ensureTrailingSlash } from '../util/PathUtil'; import { ensureTrailingSlash } from '../util/PathUtil';
import { generateResourceQuads } from '../util/ResourceUtil'; import { addResourceMetadata } from '../util/ResourceUtil';
import { PIM, RDF } from '../util/Vocabularies'; import { PIM, RDF } from '../util/Vocabularies';
import { Initializer } from './Initializer'; import { Initializer } from './Initializer';
import namedNode = DataFactory.namedNode;
/** /**
* Initializes ResourceStores by creating a root container if it didn't exist yet. * Initializes ResourceStores by creating a root container if it didn't exist yet.
@ -43,7 +41,7 @@ export class RootContainerInitializer extends Initializer {
*/ */
protected async createRootContainer(): Promise<void> { protected async createRootContainer(): Promise<void> {
const metadata = new RepresentationMetadata(this.baseId, TEXT_TURTLE); const metadata = new RepresentationMetadata(this.baseId, TEXT_TURTLE);
metadata.addQuads(generateResourceQuads(namedNode(this.baseId.path), true)); addResourceMetadata(metadata, true);
// Make sure the root container is a pim:Storage // Make sure the root container is a pim:Storage
// This prevents deletion of the root container as storage root containers can not be deleted // This prevents deletion of the root container as storage root containers can not be deleted

View File

@ -25,7 +25,7 @@ import {
toCanonicalUriPath, toCanonicalUriPath,
} from '../util/PathUtil'; } from '../util/PathUtil';
import { parseQuads } from '../util/QuadUtil'; import { parseQuads } from '../util/QuadUtil';
import { generateResourceQuads } from '../util/ResourceUtil'; import { addResourceMetadata } from '../util/ResourceUtil';
import { CONTENT_TYPE, DC, HTTP, LDP, POSIX, PIM, RDF, VANN, XSD } from '../util/Vocabularies'; import { CONTENT_TYPE, DC, HTTP, LDP, POSIX, PIM, RDF, VANN, XSD } from '../util/Vocabularies';
import type { DataAccessor } from './accessors/DataAccessor'; import type { DataAccessor } from './accessors/DataAccessor';
import type { ResourceStore } from './ResourceStore'; import type { ResourceStore } from './ResourceStore';
@ -293,7 +293,7 @@ export class DataAccessorBasedStore implements ResourceStore {
// Need to do this before handling container data to have the correct identifier // Need to do this before handling container data to have the correct identifier
const { metadata } = representation; const { metadata } = representation;
metadata.identifier = DataFactory.namedNode(identifier.path); metadata.identifier = DataFactory.namedNode(identifier.path);
metadata.addQuads(generateResourceQuads(metadata.identifier, isContainer)); addResourceMetadata(metadata, isContainer);
// Validate container data // Validate container data
if (isContainer) { if (isContainer) {

View File

@ -1,8 +1,7 @@
import type { Stats } from 'fs'; import type { Stats } from 'fs';
import { createWriteStream, createReadStream, promises as fsPromises } from 'fs'; import { createWriteStream, createReadStream, promises as fsPromises } from 'fs';
import type { Readable } from 'stream'; import type { Readable } from 'stream';
import { DataFactory } from 'n3'; import type { Quad } from 'rdf-js';
import type { NamedNode, Quad } from 'rdf-js';
import type { Representation } from '../../ldp/representation/Representation'; import type { Representation } from '../../ldp/representation/Representation';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata'; import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
@ -13,8 +12,8 @@ import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMedi
import { guardStream } from '../../util/GuardedStream'; import { guardStream } from '../../util/GuardedStream';
import type { Guarded } from '../../util/GuardedStream'; import type { Guarded } from '../../util/GuardedStream';
import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil'; import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
import { parseQuads, pushQuad, serializeQuads } from '../../util/QuadUtil'; import { parseQuads, serializeQuads } from '../../util/QuadUtil';
import { generateResourceQuads } from '../../util/ResourceUtil'; import { addResourceMetadata } from '../../util/ResourceUtil';
import { toLiteral } from '../../util/TermUtil'; import { toLiteral } from '../../util/TermUtil';
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, XSD } from '../../util/Vocabularies'; import { CONTENT_TYPE, DC, LDP, POSIX, RDF, XSD } from '../../util/Vocabularies';
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper'; import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
@ -251,8 +250,8 @@ export class FileDataAccessor implements DataAccessor {
Promise<RepresentationMetadata> { Promise<RepresentationMetadata> {
const metadata = new RepresentationMetadata(link.identifier) const metadata = new RepresentationMetadata(link.identifier)
.addQuads(await this.getRawMetadata(link.identifier)); .addQuads(await this.getRawMetadata(link.identifier));
metadata.addQuads(generateResourceQuads(metadata.identifier as NamedNode, isContainer)); addResourceMetadata(metadata, isContainer);
metadata.addQuads(this.generatePosixQuads(metadata.identifier as NamedNode, stats)); this.addPosixMetadata(metadata, stats);
return metadata; return metadata;
} }
@ -306,30 +305,25 @@ export class FileDataAccessor implements DataAccessor {
.mapFilePathToUrl(joinFilePath(link.filePath, childName), entry.isDirectory()); .mapFilePathToUrl(joinFilePath(link.filePath, childName), entry.isDirectory());
// Generate metadata of this specific child // Generate metadata of this specific child
const subject = DataFactory.namedNode(childLink.identifier.path);
const childStats = await fsPromises.lstat(joinFilePath(link.filePath, childName)); const childStats = await fsPromises.lstat(joinFilePath(link.filePath, childName));
const quads: Quad[] = []; const metadata = new RepresentationMetadata(childLink.identifier);
quads.push(...generateResourceQuads(subject, childStats.isDirectory())); addResourceMetadata(metadata, childStats.isDirectory());
quads.push(...this.generatePosixQuads(subject, childStats)); this.addPosixMetadata(metadata, childStats);
yield new RepresentationMetadata(subject).addQuads(quads); yield metadata;
} }
} }
/** /**
* Helper function to add file system related metadata. * Helper function to add file system related metadata.
* @param subject - Subject for the new quads. * @param metadata - metadata object to add to
* @param stats - Stats of the file/directory corresponding to the resource. * @param stats - Stats of the file/directory corresponding to the resource.
*/ */
private generatePosixQuads(subject: NamedNode, stats: Stats): Quad[] { private addPosixMetadata(metadata: RepresentationMetadata, stats: Stats): void {
const quads: Quad[] = []; metadata.add(DC.terms.modified, toLiteral(stats.mtime.toISOString(), XSD.terms.dateTime));
pushQuad(quads, subject, DC.terms.modified, toLiteral(stats.mtime.toISOString(), XSD.terms.dateTime)); metadata.add(POSIX.terms.mtime, toLiteral(Math.floor(stats.mtime.getTime() / 1000), XSD.terms.integer));
pushQuad(quads, subject, POSIX.terms.mtime, toLiteral(
Math.floor(stats.mtime.getTime() / 1000), XSD.terms.integer,
));
if (!stats.isDirectory()) { if (!stats.isDirectory()) {
pushQuad(quads, subject, POSIX.terms.size, toLiteral(stats.size, XSD.terms.integer)); metadata.add(POSIX.terms.size, toLiteral(stats.size, XSD.terms.integer));
} }
return quads;
} }
/** /**

View File

@ -1,32 +1,12 @@
import type { Readable } from 'stream'; import type { Readable } from 'stream';
import arrayifyStream from 'arrayify-stream'; import arrayifyStream from 'arrayify-stream';
import type { ParserOptions } from 'n3'; import type { ParserOptions } from 'n3';
import { DataFactory, StreamParser, StreamWriter } from 'n3'; import { StreamParser, StreamWriter } from 'n3';
import type { Literal, NamedNode, Quad } from 'rdf-js'; import type { Quad } from 'rdf-js';
import streamifyArray from 'streamify-array'; import streamifyArray from 'streamify-array';
import type { Guarded } from './GuardedStream'; import type { Guarded } from './GuardedStream';
import { pipeSafely } from './StreamUtil'; import { pipeSafely } from './StreamUtil';
import { toSubjectTerm, toPredicateTerm, toObjectTerm } from './TermUtil';
/**
* Generates a quad with the given subject/predicate/object and pushes it to the given array.
*/
export function pushQuad(
quads: Quad[] | Readable,
subject: string | NamedNode,
predicate: string | NamedNode,
object: string | NamedNode | Literal,
): void {
quads.push(DataFactory.quad(toSubjectTerm(subject), toPredicateTerm(predicate), toObjectTerm(object)));
}
/**
* Helper function for serializing an array of quads, with as result a Readable object.
* @param quads - The array of quads.
* @param contentType - The content-type to serialize to.
*
* @returns The Readable object.
*/
export function serializeQuads(quads: Quad[], contentType?: string): Guarded<Readable> { export function serializeQuads(quads: Quad[], contentType?: string): Guarded<Readable> {
return pipeSafely(streamifyArray(quads), new StreamWriter({ format: contentType })); return pipeSafely(streamifyArray(quads), new StreamWriter({ format: contentType }));
} }

View File

@ -1,29 +1,24 @@
import arrayifyStream from 'arrayify-stream'; import arrayifyStream from 'arrayify-stream';
import type { NamedNode, Quad } from 'rdf-js';
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation'; import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
import type { Representation } from '../ldp/representation/Representation'; import type { Representation } from '../ldp/representation/Representation';
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata'; import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
import { pushQuad } from './QuadUtil';
import { guardedStreamFrom } from './StreamUtil'; import { guardedStreamFrom } from './StreamUtil';
import { LDP, RDF } from './Vocabularies'; import { LDP, RDF } from './Vocabularies';
/** /**
* Helper function to generate type quads for a Container or Resource. * Helper function to generate type quads for a Container or Resource.
* @param subject - Subject for the new quads. * @param metadata - Metadata to add to.
* @param isContainer - If the identifier corresponds to a container. * @param isContainer - If the identifier corresponds to a container.
* *
* @returns The generated quads. * @returns The generated quads.
*/ */
export function generateResourceQuads(subject: NamedNode, isContainer: boolean): Quad[] { export function addResourceMetadata(metadata: RepresentationMetadata, isContainer: boolean): void {
const quads: Quad[] = [];
if (isContainer) { if (isContainer) {
pushQuad(quads, subject, RDF.terms.type, LDP.terms.Container); metadata.add(RDF.terms.type, LDP.terms.Container);
pushQuad(quads, subject, RDF.terms.type, LDP.terms.BasicContainer); metadata.add(RDF.terms.type, LDP.terms.BasicContainer);
} }
pushQuad(quads, subject, RDF.terms.type, LDP.terms.Resource); metadata.add(RDF.terms.type, LDP.terms.Resource);
return quads;
} }
/** /**

View File

@ -1,28 +1,9 @@
import 'jest-rdf'; import 'jest-rdf';
import { literal, namedNode, quad } from '@rdfjs/data-model'; import { literal, namedNode, quad } from '@rdfjs/data-model';
import type { Quad } from 'rdf-js'; import { parseQuads, serializeQuads } from '../../../src/util/QuadUtil';
import { parseQuads, pushQuad, serializeQuads } from '../../../src/util/QuadUtil';
import { guardedStreamFrom, readableToString } from '../../../src/util/StreamUtil'; import { guardedStreamFrom, readableToString } from '../../../src/util/StreamUtil';
describe('QuadUtil', (): void => { describe('QuadUtil', (): void => {
describe('#pushQuad', (): void => {
it('creates a quad and adds it to the given array.', async(): Promise<void> => {
const quads: Quad[] = [];
pushQuad(quads, namedNode('sub'), namedNode('pred'), literal('obj'));
expect(quads).toEqualRdfQuadArray([
quad(namedNode('sub'), namedNode('pred'), literal('obj')),
]);
});
it('creates a quad from strings and adds it to the given array.', async(): Promise<void> => {
const quads: Quad[] = [];
pushQuad(quads, 'sub', 'pred', 'obj');
expect(quads).toEqualRdfQuadArray([
quad(namedNode('sub'), namedNode('pred'), namedNode('obj')),
]);
});
});
describe('#serializeQuads', (): void => { describe('#serializeQuads', (): void => {
it('converts quads to the requested format.', async(): Promise<void> => { it('converts quads to the requested format.', async(): Promise<void> => {
const quads = [ quad( const quads = [ quad(