mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Keep track of last modified date of resources
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
||||
toCanonicalUriPath,
|
||||
} from '../util/PathUtil';
|
||||
import { parseQuads } from '../util/QuadUtil';
|
||||
import { addResourceMetadata } from '../util/ResourceUtil';
|
||||
import { addResourceMetadata, updateModifiedDate } from '../util/ResourceUtil';
|
||||
import {
|
||||
CONTENT_TYPE,
|
||||
DC,
|
||||
@@ -164,7 +164,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
const newID = await this.createSafeUri(container, representation.metadata);
|
||||
|
||||
// Write the data. New containers should never be made for a POST request.
|
||||
await this.writeData(newID, representation, isContainerIdentifier(newID), false);
|
||||
await this.writeData(newID, representation, isContainerIdentifier(newID), false, false);
|
||||
|
||||
return newID;
|
||||
}
|
||||
@@ -197,7 +197,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
}
|
||||
|
||||
// Potentially have to create containers if it didn't exist yet
|
||||
return this.writeData(identifier, representation, isContainer, !oldMetadata);
|
||||
return this.writeData(identifier, representation, isContainer, !oldMetadata, Boolean(oldMetadata));
|
||||
}
|
||||
|
||||
public async modifyResource(): Promise<ResourceIdentifier[]> {
|
||||
@@ -237,6 +237,17 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
const auxiliaries = this.auxiliaryStrategy.getAuxiliaryIdentifiers(identifier);
|
||||
deleted.push(...await this.safelyDeleteAuxiliaryResources(auxiliaries));
|
||||
}
|
||||
|
||||
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||
deleted.push(container);
|
||||
|
||||
// Update modified date of parent
|
||||
const parentMetadata = await this.accessor.getMetadata(container);
|
||||
updateModifiedDate(parentMetadata);
|
||||
await this.accessor.writeContainer(container, parentMetadata);
|
||||
}
|
||||
|
||||
await this.accessor.deleteResource(identifier);
|
||||
return deleted;
|
||||
}
|
||||
@@ -300,11 +311,12 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
* @param representation - Corresponding Representation.
|
||||
* @param isContainer - Is the incoming resource a container?
|
||||
* @param createContainers - Should parent containers (potentially) be created?
|
||||
* @param exists - If the resource already exists.
|
||||
*
|
||||
* @returns Identifiers of resources that were possibly modified.
|
||||
*/
|
||||
protected async writeData(identifier: ResourceIdentifier, representation: Representation, isContainer: boolean,
|
||||
createContainers?: boolean): Promise<ResourceIdentifier[]> {
|
||||
createContainers: boolean, exists: boolean): Promise<ResourceIdentifier[]> {
|
||||
// Make sure the metadata has the correct identifier and correct type quads
|
||||
// Need to do this before handling container data to have the correct identifier
|
||||
representation.metadata.identifier = DataFactory.namedNode(identifier.path);
|
||||
@@ -320,12 +332,15 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
await this.auxiliaryStrategy.validate(representation);
|
||||
}
|
||||
|
||||
// Add date modified metadata
|
||||
updateModifiedDate(representation.metadata);
|
||||
|
||||
// Root container should not have a parent container
|
||||
// Solid, §5.3: "Servers MUST create intermediate containers and include corresponding containment triples
|
||||
// in container representations derived from the URI path component of PUT and PATCH requests."
|
||||
// https://solid.github.io/specification/protocol#writing-resources
|
||||
const modified = [];
|
||||
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||
if (!this.identifierStrategy.isRootContainer(identifier) && !exists) {
|
||||
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||
if (!createContainers) {
|
||||
modified.push(container);
|
||||
@@ -333,6 +348,11 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
const created = await this.createRecursiveContainers(container);
|
||||
modified.push(...created.length === 0 ? [ container ] : created);
|
||||
}
|
||||
|
||||
// Parent container is also modified
|
||||
const parentMetadata = await this.accessor.getMetadata(container);
|
||||
updateModifiedDate(parentMetadata);
|
||||
await this.accessor.writeContainer(container, parentMetadata);
|
||||
}
|
||||
|
||||
// Remove all generated metadata to prevent it from being stored permanently
|
||||
@@ -534,7 +554,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
const ancestors = this.identifierStrategy.isRootContainer(container) ?
|
||||
[] :
|
||||
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
|
||||
await this.writeData(container, new BasicRepresentation([], container), true);
|
||||
await this.writeData(container, new BasicRepresentation([], container), true, false, false);
|
||||
return [ ...ancestors, container ];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { guardStream } from '../../util/GuardedStream';
|
||||
import type { Guarded } from '../../util/GuardedStream';
|
||||
import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
|
||||
import { parseQuads, serializeQuads } from '../../util/QuadUtil';
|
||||
import { addResourceMetadata } from '../../util/ResourceUtil';
|
||||
import { addResourceMetadata, updateModifiedDate } from '../../util/ResourceUtil';
|
||||
import { toLiteral } from '../../util/TermUtil';
|
||||
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, SOLID_META, XSD } from '../../util/Vocabularies';
|
||||
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
|
||||
@@ -193,9 +193,10 @@ export class FileDataAccessor implements DataAccessor {
|
||||
*/
|
||||
private async writeMetadata(link: ResourceLink, metadata: RepresentationMetadata): Promise<boolean> {
|
||||
// These are stored by file system conventions
|
||||
metadata.remove(RDF.type, LDP.terms.Resource);
|
||||
metadata.remove(RDF.type, LDP.terms.Container);
|
||||
metadata.remove(RDF.type, LDP.terms.BasicContainer);
|
||||
metadata.remove(RDF.terms.type, LDP.terms.Resource);
|
||||
metadata.remove(RDF.terms.type, LDP.terms.Container);
|
||||
metadata.remove(RDF.terms.type, LDP.terms.BasicContainer);
|
||||
metadata.removeAll(DC.terms.modified);
|
||||
metadata.removeAll(CONTENT_TYPE);
|
||||
const quads = metadata.quads();
|
||||
const metadataLink = await this.resourceMapper.mapUrlToFilePath(link.identifier, true);
|
||||
@@ -303,9 +304,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
* @param stats - Stats of the file/directory corresponding to the resource.
|
||||
*/
|
||||
private addPosixMetadata(metadata: RepresentationMetadata, stats: Stats): void {
|
||||
metadata.add(DC.terms.modified,
|
||||
toLiteral(stats.mtime.toISOString(), XSD.terms.dateTime),
|
||||
SOLID_META.terms.ResponseMetadata);
|
||||
updateModifiedDate(metadata, stats.mtime);
|
||||
metadata.add(POSIX.terms.mtime,
|
||||
toLiteral(Math.floor(stats.mtime.getTime() / 1000), XSD.terms.integer),
|
||||
SOLID_META.terms.ResponseMetadata);
|
||||
|
||||
@@ -3,8 +3,9 @@ import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
||||
import type { Representation } from '../ldp/representation/Representation';
|
||||
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
|
||||
import { guardedStreamFrom } from './StreamUtil';
|
||||
import { toLiteral } from './TermUtil';
|
||||
|
||||
import { LDP, RDF } from './Vocabularies';
|
||||
import { DC, LDP, RDF, XSD } from './Vocabularies';
|
||||
|
||||
/**
|
||||
* Helper function to generate type quads for a Container or Resource.
|
||||
@@ -21,6 +22,18 @@ export function addResourceMetadata(metadata: RepresentationMetadata, isContaine
|
||||
metadata.add(RDF.terms.type, LDP.terms.Resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dc:modified time to the given time.
|
||||
* @param metadata - Metadata to update.
|
||||
* @param date - Last modified date. Defaults to current time.
|
||||
*/
|
||||
export function updateModifiedDate(metadata: RepresentationMetadata, date = new Date()): void {
|
||||
// Milliseconds get lost in some serializations, potentially causing mismatches
|
||||
const lastModified = new Date(date);
|
||||
lastModified.setMilliseconds(0);
|
||||
metadata.set(DC.terms.modified, toLiteral(lastModified.toISOString(), XSD.terms.dateTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to clone a representation, the original representation can still be used.
|
||||
* This function loads the entire stream in memory.
|
||||
|
||||
Reference in New Issue
Block a user