diff --git a/src/common/SolidDataDriver.ts b/src/common/SolidDataDriver.ts index a3f15dc..ea674e8 100644 --- a/src/common/SolidDataDriver.ts +++ b/src/common/SolidDataDriver.ts @@ -94,6 +94,9 @@ export class SolidDataDriver extends SPARQLDat return this.service.getDataset(session, query.uri); }) .then((dataset) => { + if (!dataset) { + return undefined; + } const subjects = Object.values(dataset.graphs.default); const quads = RDFSerializer.subjectsToQuads(subjects); const store = new Store(quads); @@ -111,6 +114,9 @@ export class SolidDataDriver extends SPARQLDat return this.service.getDataset(session, query.uri); }) .then((dataset) => { + if (!dataset) { + return []; + } const subjects = Object.values(dataset.graphs.default); const quads = RDFSerializer.subjectsToQuads(subjects); const store = new Store(quads); @@ -134,6 +140,9 @@ export class SolidDataDriver extends SPARQLDat return this.service.getDataset(session, query.uri); }) .then((dataset) => { + if (!dataset) { + return 0; + } const subjects = Object.values(dataset.graphs.default); const quads = RDFSerializer.subjectsToQuads(subjects); const store = new Store(quads); @@ -178,6 +187,9 @@ export class SolidDataDriver extends SPARQLDat this.service .getDataset(session, documentURL.href) .then((dataset) => { + if (!dataset) { + dataset = this.service.createDataset(); + } let promise = Promise.resolve(dataset); for (let i = 0; i < items.length; i++) { promise = promise.then( diff --git a/src/common/SolidPropertyService.ts b/src/common/SolidPropertyService.ts index cbef0ce..43ff440 100644 --- a/src/common/SolidPropertyService.ts +++ b/src/common/SolidPropertyService.ts @@ -2,7 +2,6 @@ import { DataService } from '@openhps/core'; import { Property, RDFSerializer, - dcterms, rdfs, sosa, ssn, @@ -20,10 +19,13 @@ import { Node } from '../models/tree'; import { tree } from '../terms'; import { GreaterThanOrEqualToRelation } from '../models/tree/Relation'; import { Collection } from '../models/tree/Collection'; +import { isContainer } from '@inrupt/solid-client'; /** - * - * @param node + * Default filter function for nodes + * This is the function that splits data over nodes based on the amount of members, + * date or other properties. + * @param node Tree node to filter */ function defaultFilter(node: Node): boolean { // Filter false if node has 50 or more children @@ -185,8 +187,7 @@ export class SolidPropertyService extends DataService { return new Promise((resolve, reject) => { const nodeURL = new URL(node.id); nodeURL.hash = ''; - const isContainer = !nodeURL.href.endsWith('.ttl'); - const datasetURL = `${nodeURL.href}${isContainer ? `${nodeURL.href.endsWith('/') ? '' : '/'}.meta` : ''}`; + const datasetURL = `${nodeURL.href}${isContainer(nodeURL.href) ? `${nodeURL.href.endsWith('/') ? '' : '/'}.meta` : ''}`; this.service .getDatasetStore(session, datasetURL) .then((dataset) => { @@ -205,8 +206,7 @@ export class SolidPropertyService extends DataService { return new Promise((resolve, reject) => { const nodeURL = new URL(node.id); nodeURL.hash = ''; - const isContainer = !nodeURL.href.endsWith('.ttl'); - const datasetURL = `${nodeURL.href}${isContainer ? `${nodeURL.href.endsWith('/') ? '' : '/'}.meta` : ''}`; + const datasetURL = `${nodeURL.href}${isContainer(nodeURL.href) ? `${nodeURL.href.endsWith('/') ? '' : '/'}.meta` : ''}`; this.service .getDatasetStore(session, datasetURL) .then((dataset) => { @@ -234,13 +234,12 @@ export class SolidPropertyService extends DataService { } /** - * Add an observation to a property - * @param session - * @param property - * @param observation - * @returns + * Find the root node of a property + * @param session Solid session + * @param property Property + * @returns {Promise} Root node */ - addObservation(session: SolidSession, property: Property, observation: Observation): Promise { + findRootNode(session: SolidSession, property: Property): Promise { return new Promise((resolve, reject) => { Promise.all([ this.service.getDatasetStore(session, `${property.id}/property.ttl`), @@ -259,7 +258,8 @@ export class SolidPropertyService extends DataService { ); if (bindings.length === 0) { // No root node - return reject(new Error('Root node not found')); + resolve(undefined); + return; } const rootNode: Node = RDFSerializer.deserializeFromStore( DataFactory.namedNode(bindings[0].get('node').value as IriString), @@ -267,7 +267,29 @@ export class SolidPropertyService extends DataService { ); if (!rootNode) { // Root node not found - return reject(new Error('Root node not found, but it was in the query result')); + resolve(undefined); + return; + } + resolve(rootNode); + }) + .catch(reject); + }); + } + + /** + * Add an observation to a property + * @param session + * @param property + * @param observation + * @returns + */ + addObservation(session: SolidSession, property: Property, observation: Observation): Promise { + return new Promise((resolve, reject) => { + this.findRootNode(session, property) + .then(async (rootNode) => { + // Create/repair root node if it does not exist + if (!rootNode) { + await this.createProperty(session, property); } // Check relations to make sure there is no issue diff --git a/src/common/SolidService.ts b/src/common/SolidService.ts index f0ad6f7..076e1ea 100644 --- a/src/common/SolidService.ts +++ b/src/common/SolidService.ts @@ -23,6 +23,7 @@ import { getThing, saveSolidDatasetAt, createContainerAt, + isContainer, deleteSolidDataset, setStringNoLocale, setThing, @@ -234,6 +235,9 @@ export abstract class SolidService extends RemoteService { return new Promise((resolve, reject) => { this.getDataset(session, uri) .then((dataset) => { + if (!dataset) { + dataset = createSolidDataset(); + } const quads: Quad[] = Object.keys(dataset.graphs) .map((key) => { const graph = dataset.graphs[key]; @@ -247,6 +251,10 @@ export abstract class SolidService extends RemoteService { }); } + createDataset(): SolidDataset { + return createSolidDataset(); + } + /** * Get a Solid dataset * @param {SolidSession} session Solid session to get a thing from @@ -269,7 +277,7 @@ export abstract class SolidService extends RemoteService { .catch((ex: FetchError) => { if (ex.response.status === 404) { // Create dataset when 404 (not found) - resolve(createSolidDataset()); + resolve(undefined); } else { reject(ex); } @@ -313,14 +321,23 @@ export abstract class SolidService extends RemoteService { */ createContainer(session: SolidSession, url: IriString): Promise { return new Promise((resolve, reject) => { - createContainerAt( - url, - session - ? { - fetch: session.fetch, - } - : undefined, - ) + // First check if the container does not exist yet + this.getDataset(session, url) + .then((dataset: SolidDataset & WithResourceInfo) => { + if (dataset && isContainer(dataset)) { + resolve(dataset); + return; + } + // Create container (can still fail based on permissions) + return createContainerAt( + url, + session + ? { + fetch: session.fetch, + } + : undefined, + ); + }) .then(resolve) .catch(reject); }); @@ -361,6 +378,9 @@ export abstract class SolidService extends RemoteService { ): Promise { if (typeof dataset === 'string') { const fetchedDataset = await this.getDataset(session, dataset); + if (!fetchedDataset) { + return Promise.resolve(); + } return await this.deleteRecursively(session, fetchedDataset as SolidDataset & WithResourceInfo); } if (!dataset) { @@ -467,6 +487,9 @@ export abstract class SolidService extends RemoteService { }; this.getDataset(session, documentURL.href) .then((dataset: SolidDataset & WithResourceInfo) => { + if (!dataset) { + dataset = createSolidDataset() as SolidDataset & WithResourceInfo; + } dummyDataset.internal_resourceInfo = dataset.internal_resourceInfo; return saveSolidDatasetAt( documentURL.href, @@ -605,6 +628,9 @@ export abstract class SolidService extends RemoteService { } else { this.getDataset(session, documentURL.href) .then((dataset) => { + if (!dataset) { + dataset = createSolidDataset(); + } setThingInDataset.bind(this)(dataset); }) .catch(reject); diff --git a/test/specs/solid.service.spec.ts b/test/specs/solid.service.spec.ts index 59849a1..69d9512 100644 --- a/test/specs/solid.service.spec.ts +++ b/test/specs/solid.service.spec.ts @@ -123,5 +123,12 @@ describe('SolidService', () => { }).catch(done); }); + it('should not throw an error when creating an existing container', (done) => { + service.getDocumentURL(session, "/test/abc/").then(url => { + return service.createContainer(session, url.href as IriString); + }).then(() => { + done(); + }).catch(done); + }); }); });