@treecg/curation
Version:
This library implements a class (Curator) with methods to curate an announcement LDES in LDP.
853 lines (852 loc) • 48.9 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Curator = void 0;
var ldes_announcements_1 = require("@treecg/ldes-announcements");
var ldes_orchestrator_1 = require("@treecg/ldes-orchestrator");
var tree_metadata_extraction_1 = require("@treecg/tree-metadata-extraction");
var Logger_Browser_1 = require("@treecg/types/dist/lib/utils/Logger-Browser");
var n3_1 = require("n3");
var Conversion_1 = require("./util/Conversion");
var SolidCommunication_1 = require("./util/SolidCommunication");
var Vocabularies_1 = require("./util/Vocabularies");
var namedNode = n3_1.DataFactory.namedNode;
var literal = n3_1.DataFactory.literal;
var Curator = /** @class */ (function () {
// this field allows waiting for the synchronization process.
function Curator(config, session) {
this.logger = new Logger_Browser_1.LoggerBrowser(this);
this.curatedIRI = config.curatedIRI;
this.ldesIRI = config.ldesIRI;
this.synchronizedIRI = config.synchronizedIRI;
this.session = session;
}
/**
* Checks whether the user is logged in
* Load the curatedLDES in Solid
* Creates the curatedLDES in Solid if it does not exist (requires certain access control permissions on the curated IRI)
* Furthermore verifies that the LDES exists as well.
* @param privateCuration When true, the curated LDES in LDP is only visible to the webId from the session. (Default: True)
* @return {Promise<void>}
*/
Curator.prototype.init = function (privateCuration) {
return __awaiter(this, void 0, void 0, function () {
var config, e_1, ldesStore, e_2, treeShape, relations, bn, relationType, treePath;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
privateCuration = privateCuration !== undefined ? privateCuration : true;
if (!this.session.info.isLoggedIn) {
this.logger.error("Contents of the session: ".concat(JSON.stringify(this.session.info)));
throw Error("Session is not logged in");
}
config = undefined;
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, ldes_orchestrator_1.LDESinSolid.getConfig(this.curatedIRI, this.session)];
case 2:
config = _a.sent();
return [3 /*break*/, 4];
case 3:
e_1 = _a.sent();
this.logger.info("No curated LDES in LDP exist yet at ".concat(this.curatedIRI));
return [3 /*break*/, 4];
case 4:
_a.trys.push([4, 6, , 7]);
return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)("".concat(this.ldesIRI, "root.ttl"), this.session)];
case 5:
ldesStore = _a.sent();
return [3 /*break*/, 7];
case 6:
e_2 = _a.sent();
this.logger.error("LDES in LDP does not exist at ".concat(this.ldesIRI));
throw Error("LDES in LDP does not exist");
case 7:
if (!config) return [3 /*break*/, 8];
this.curatedLDESinSolid = new ldes_orchestrator_1.LDESinSolid(config.ldesConfig, config.aclConfig, this.session);
return [3 /*break*/, 13];
case 8:
treeShape = ldesStore.getObjects(null, Vocabularies_1.TREE.shape, null);
relations = ldesStore.getObjects(null, Vocabularies_1.TREE.relation, null);
if (relations.length === 0) {
throw Error('Original LDES root node currently no relations.');
}
if (treeShape.length === 0) {
throw Error('Original LDES has currently no shape.');
}
bn = relations[0].id;
relationType = ldesStore.getObjects(bn, Vocabularies_1.RDF.type, null);
treePath = ldesStore.getObjects(null, Vocabularies_1.TREE.path, null);
config = {
aclConfig: {
agent: this.session.info.webId // I know it exists
},
ldesConfig: {
base: this.curatedIRI,
treePath: treePath[0].id,
shape: 'https://tree.linkeddatafragments.org/datasets/shape',
relationType: relationType[0].id,
}
};
this.curatedLDESinSolid = new ldes_orchestrator_1.LDESinSolid(config.ldesConfig, config.aclConfig, this.session);
if (!privateCuration) return [3 /*break*/, 10];
return [4 /*yield*/, this.curatedLDESinSolid.createLDESinLDP(ldes_orchestrator_1.AccessSubject.Agent)];
case 9:
_a.sent();
return [3 /*break*/, 12];
case 10: return [4 /*yield*/, this.curatedLDESinSolid.createLDESinLDP()];
case 11:
_a.sent();
_a.label = 12;
case 12:
this.logger.info("Created curated LDES in Solid at ".concat(this.curatedIRI));
_a.label = 13;
case 13: return [2 /*return*/];
}
});
});
};
Curator.prototype.accept = function (memberIRI, member, timestamp) {
return __awaiter(this, void 0, void 0, function () {
var extracted, containerIRI, text, response, e_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.curatedLDESinSolid) {
throw Error("First execute function init() as the curated LDES was not initialised yet");
}
if (!!timestamp) return [3 /*break*/, 2];
return [4 /*yield*/, this.getTimestamp(memberIRI)];
case 1:
timestamp = _a.sent();
_a.label = 2;
case 2:
if (!!member) return [3 /*break*/, 4];
return [4 /*yield*/, this.extractMember(memberIRI)];
case 3:
extracted = _a.sent();
member = extracted.value;
_a.label = 4;
case 4: return [4 /*yield*/, this.curatedLDESinSolid.getCurrentContainer()];
case 5:
containerIRI = _a.sent();
this.logger.debug("Posting contents of ".concat(member["@id"], " to ").concat(containerIRI, "."));
return [4 /*yield*/, (0, Conversion_1.memberToString)(member, memberIRI)];
case 6:
text = _a.sent();
_a.label = 7;
case 7:
_a.trys.push([7, 10, , 11]);
this.logger.info("Accepted ".concat(memberIRI, " to the curated ldes located at ").concat(this.curatedIRI));
return [4 /*yield*/, (0, SolidCommunication_1.postResource)(containerIRI, this.session, text, 'text/turtle')];
case 8:
response = _a.sent();
return [4 /*yield*/, this.removeFromSyncedCollection(memberIRI, timestamp)];
case 9:
_a.sent();
return [2 /*return*/, response];
case 10:
e_3 = _a.sent();
this.logger.error('Something failed');
console.log(e_3);
throw Error("Could not add the member to ".concat(this.curatedIRI));
case 11: return [2 /*return*/];
}
});
});
};
/**
* Curation action reject: Which means that a member is removed from the synced collection
* @param memberIRI
* @param timestamp
* @returns {Promise<Response>}
*/
Curator.prototype.reject = function (memberIRI, timestamp) {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!timestamp) return [3 /*break*/, 2];
return [4 /*yield*/, this.getTimestamp(memberIRI)];
case 1:
timestamp = _a.sent();
_a.label = 2;
case 2: return [4 /*yield*/, this.removeFromSyncedCollection(memberIRI, timestamp)];
case 3:
response = _a.sent();
return [2 /*return*/, response];
}
});
});
};
/**
* Fetch the timestamp of an IRI by reading the Last-modified header.
* For a member in an LDES, this will be the same as the creation time (as member are immutable
* , indicating that they MUST NOT be changed after creation)
* @param iri IRI of the LDP Resource
* @returns {Promise<number>}
*/
Curator.prototype.getTimestamp = function (iri) {
return __awaiter(this, void 0, void 0, function () {
var response, lastModifiedHeader, dateLastModified;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.session.fetch(iri, {
method: "HEAD"
})];
case 1:
response = _a.sent();
lastModifiedHeader = response.headers.get('Last-modified');
if (!lastModifiedHeader)
throw Error("Resource ".concat(iri, " has no Last-modified header."));
dateLastModified = new Date(lastModifiedHeader);
return [2 /*return*/, dateLastModified.getTime()];
}
});
});
};
/**
* Removes a member from the synced collection stored at the syncedURI
* @param memberIRI IRI of the member
* @param timestamp timestamp of creation of the member in the LDES in LDP
* @returns {Promise<Response>}
*/
Curator.prototype.removeFromSyncedCollection = function (memberIRI, timestamp) {
return __awaiter(this, void 0, void 0, function () {
var syncedLocation, memberQuad, timeQuad, response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
syncedLocation = memberIRI.replace(this.ldesIRI, this.synchronizedIRI).split('/').slice(0, -1).join('/');
memberQuad = new n3_1.Quad(namedNode("".concat(this.ldesIRI, "root.ttl#Collection")), namedNode(Vocabularies_1.TREE.member), namedNode(memberIRI));
timeQuad = new n3_1.Quad(namedNode(memberIRI), namedNode(Vocabularies_1.DCT.modified), this.timestampToLiteral(timestamp));
return [4 /*yield*/, (0, SolidCommunication_1.patchQuads)(syncedLocation, this.session, [memberQuad, timeQuad], SolidCommunication_1.SPARQL.DELETE)];
case 1:
response = _a.sent();
this.logger.info("Removed ".concat(memberIRI, " from the synced collection at ").concat(this.synchronizedIRI));
return [2 /*return*/, response];
}
});
});
};
/**
* Synchronize the Synced collection with the LDES in LDP
*
* The synced collection is a TREE collection with as members the URIs of the LDES in LDP. Each member also has a timestamp.
* A synchronize operations looks at the last synced time and adds all members which were added to the LDES to the synced collection.
*
* @returns {Promise<void>}
*/
Curator.prototype.synchronize = function () {
return __awaiter(this, void 0, void 0, function () {
var LDESRootNode, LDESRootCollectionIRI, syncedRootNode, response, contentType, body, store;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
LDESRootNode = "".concat(this.ldesIRI, "root.ttl");
LDESRootCollectionIRI = "".concat(LDESRootNode, "#Collection");
syncedRootNode = "".concat(this.synchronizedIRI, "root.ttl");
return [4 /*yield*/, this.session.fetch(syncedRootNode)];
case 1:
response = _a.sent();
contentType = response.headers.get('content-type');
if (!contentType) {
throw Error("No content-type known of ".concat(syncedRootNode));
}
if (!(response.status === 200)) return [3 /*break*/, 5];
return [4 /*yield*/, response.text()];
case 2:
body = _a.sent();
return [4 /*yield*/, (0, Conversion_1.stringToStore)(body, { contentType: contentType, baseIRI: syncedRootNode })];
case 3:
store = _a.sent();
return [4 /*yield*/, this.otherTimesSync(LDESRootNode, syncedRootNode, LDESRootCollectionIRI, store)];
case 4:
_a.sent();
return [3 /*break*/, 8];
case 5:
// create container
return [4 /*yield*/, (0, SolidCommunication_1.putContainer)(this.synchronizedIRI, this.session)];
case 6:
// create container
_a.sent();
return [4 /*yield*/, this.firstTimeSync(LDESRootNode, syncedRootNode, LDESRootCollectionIRI)];
case 7:
_a.sent();
_a.label = 8;
case 8: return [2 /*return*/];
}
});
});
};
/**
* Extract the member and its metadata from an LDES in LDP. Also extract the announcement itself
* Currently, only View, DataSet and DataService can be parsed (interface can be found in LDES-Announcements)
* @param announcementIRI
* @returns {Promise<{iri: string, type: string, value: View, announcement: Announce} | {iri: string, type: string, value: DataSet, announcement: Announce} | {iri: string, type: string, value: DataService, announcement: Announce}>}
*/
Curator.prototype.extractMember = function (announcementIRI) {
return __awaiter(this, void 0, void 0, function () {
var memberStore, metadata, announcementIRIs, iri, announcement, valueIRI, type, content, content, content;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)(announcementIRI, this.session)];
case 1:
memberStore = _a.sent();
return [4 /*yield*/, (0, ldes_announcements_1.extractAnnouncementsMetadata)(memberStore)];
case 2:
metadata = _a.sent();
announcementIRIs = [];
metadata.announcements.forEach(function (announcement) {
announcementIRIs.push(announcement["@id"]);
});
if (announcementIRIs.length !== 1) {
throw Error("There is more than one announcement in ".concat(announcementIRI));
}
iri = announcementIRIs[0];
announcement = metadata.announcements.get(iri);
if (!announcement)
throw Error("Announcement was not correct ".concat(iri));
valueIRI = announcement.object["@id"];
type = memberStore.getObjects(valueIRI, Vocabularies_1.RDF.type, null).map(function (object) { return object.id; });
if (type.includes(Vocabularies_1.TREE.Node)) {
this.logger.debug("View from ".concat(announcementIRI, " extracted."));
content = metadata.views.get(valueIRI);
return [2 /*return*/, { type: Vocabularies_1.TREE.Node, value: content, iri: announcementIRI, announcement: announcement }];
}
if (type.includes(Vocabularies_1.DCAT.Dataset)) {
this.logger.debug("DCAT dataset from ".concat(announcementIRI, " extracted."));
content = metadata.datasets.get(valueIRI);
return [2 /*return*/, { type: Vocabularies_1.DCAT.Dataset, value: content, iri: announcementIRI, announcement: announcement }];
}
if (type.includes(Vocabularies_1.DCAT.DataService)) {
this.logger.debug("DCAT Dataservice ".concat(announcementIRI, " extracted."));
content = metadata.dataServices.get(valueIRI);
return [2 /*return*/, { type: Vocabularies_1.DCAT.DataService, value: content, iri: announcementIRI, announcement: announcement }];
}
throw Error("Could not extract member from ".concat(announcementIRI));
}
});
});
};
/**
* Flow for the first time an LDES in LDP has to be synchronized
*
* @param LDESRootNode IRI of the LDES in LDP root node
* @param syncedRootNode IRI of the root node of the synced collection
* @param LDESRootCollectionIRI IRI of the LDES in LDP collection
*/
Curator.prototype.firstTimeSync = function (LDESRootNode, syncedRootNode, LDESRootCollectionIRI) {
return __awaiter(this, void 0, void 0, function () {
var LDESRootStore, metadata, metadataRelations, collection, view, now, relations, body, bodyStore, turtleBody, error_1, relationNodeIds, _i, relationNodeIds_1, relationNodeId;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)(LDESRootNode, this.session)];
case 1:
LDESRootStore = _a.sent();
return [4 /*yield*/, (0, tree_metadata_extraction_1.extractMetadata)(LDESRootStore.getQuads(null, null, null, null))];
case 2:
metadata = _a.sent();
metadataRelations = metadata.nodes.get(LDESRootNode).relation;
collection = {
"@context": { "@vocab": Vocabularies_1.TREE.namespace },
"@id": "".concat(syncedRootNode, "#Collection"),
"@type": [Vocabularies_1.TREE.Collection],
view: [{ "@id": syncedRootNode }]
};
view = {
"@context": { "@vocab": Vocabularies_1.TREE.namespace },
"@id": syncedRootNode,
"@type": [Vocabularies_1.TREE.Node],
relation: []
};
now = new Date();
view[Vocabularies_1.DCT.issued] = { '@value': now.toISOString(), '@type': Vocabularies_1.XSD.dateTime };
relations = [];
metadataRelations.forEach(function (relationIRI) {
var _a;
// clone the relation -> I don't want to update it
var relation = __assign({}, metadata.relations.get(relationIRI['@id']));
// In practice this if holds always
if (relation.node && relation.node[0]) {
// change relation of node -> again clone it otherwise the original node is changed
relation.node = __spreadArray([], relation.node, true);
relation.node[0] = {
"@id": _this.ldesRelationToSyncedRelationIRI(relation.node[0]['@id'])
};
}
(_a = view.relation) === null || _a === void 0 ? void 0 : _a.push(relationIRI); // I just defined it, believe me it's there
relations.push(relation);
});
body = __spreadArray([collection, view], relations, true);
return [4 /*yield*/, (0, Conversion_1.ldjsonToStore)(JSON.stringify(body))];
case 3:
bodyStore = _a.sent();
turtleBody = (0, Conversion_1.storeToString)(bodyStore);
_a.label = 4;
case 4:
_a.trys.push([4, 6, , 7]);
return [4 /*yield*/, (0, SolidCommunication_1.putTurtle)(syncedRootNode, this.session, turtleBody)];
case 5:
_a.sent();
return [3 /*break*/, 7];
case 6:
error_1 = _a.sent();
console.log(error_1);
this.logger.error("Could not create root at ".concat(syncedRootNode));
return [3 /*break*/, 7];
case 7:
relationNodeIds = [];
LDESRootStore.getObjects(LDESRootNode, Vocabularies_1.TREE.relation, null).forEach(function (object) {
var relationNodeId = LDESRootStore.getObjects(object, Vocabularies_1.TREE.node, null)[0].id;
relationNodeIds.push(relationNodeId);
});
_i = 0, relationNodeIds_1 = relationNodeIds;
_a.label = 8;
case 8:
if (!(_i < relationNodeIds_1.length)) return [3 /*break*/, 11];
relationNodeId = relationNodeIds_1[_i];
return [4 /*yield*/, this.synchronizeMembersFromRelation(relationNodeId, LDESRootCollectionIRI)];
case 9:
_a.sent();
_a.label = 10;
case 10:
_i++;
return [3 /*break*/, 8];
case 11: return [2 /*return*/];
}
});
});
};
/**
* Flow for the subsequent times an LDES in LDP has to be synchronized
*
* @param LDESRootNode IRI of the LDES in LDP root node
* @param syncedRootNode IRI of the root node of the synced collection
* @param LDESRootCollectionIRI IRI of the LDES in LDP collection
* @param syncedStore The store of the synced root node
*/
Curator.prototype.otherTimesSync = function (LDESRootNode, syncedRootNode, LDESRootCollectionIRI, syncedStore) {
return __awaiter(this, void 0, void 0, function () {
var syncQuads, lastSynced, nowQuad, newRelations, _i, newRelations_1, relation, iri, error_2, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
syncQuads = syncedStore.getQuads(syncedRootNode, Vocabularies_1.DCT.issued, null, null);
if (syncQuads.length !== 1) {
throw Error("Can't find last time synced at ".concat(syncedRootNode, "."));
}
lastSynced = this.extractTimeFromLiteral(syncQuads[0].object);
nowQuad = new n3_1.Quad(namedNode(syncedRootNode), namedNode(Vocabularies_1.DCT.issued), this.timestampToLiteral(Date.now()));
return [4 /*yield*/, this.updateRootNode(LDESRootNode, syncedStore, syncedRootNode)];
case 1:
newRelations = _a.sent();
_i = 0, newRelations_1 = newRelations;
_a.label = 2;
case 2:
if (!(_i < newRelations_1.length)) return [3 /*break*/, 5];
relation = newRelations_1[_i];
iri = this.syncedRelationtoLDESRelationIRI(relation);
return [4 /*yield*/, this.synchronizeMembersFromRelation(iri, LDESRootCollectionIRI)];
case 3:
_a.sent();
_a.label = 4;
case 4:
_i++;
return [3 /*break*/, 2];
case 5: return [4 /*yield*/, this.updateCurrentMostRecentRelation(syncedStore, lastSynced, LDESRootCollectionIRI)];
case 6:
_a.sent();
_a.label = 7;
case 7:
_a.trys.push([7, 9, , 10]);
return [4 /*yield*/, (0, SolidCommunication_1.patchQuads)(syncedRootNode, this.session, [nowQuad], SolidCommunication_1.SPARQL.INSERT)];
case 8:
_a.sent();
return [3 /*break*/, 10];
case 9:
error_2 = _a.sent();
console.log(error_2);
this.logger.error("Could not add new DCTERMS issued at ".concat(syncedRootNode));
return [3 /*break*/, 10];
case 10:
_a.trys.push([10, 12, , 13]);
return [4 /*yield*/, (0, SolidCommunication_1.patchQuads)(syncedRootNode, this.session, [syncQuads[0]], SolidCommunication_1.SPARQL.DELETE)];
case 11:
_a.sent();
return [3 /*break*/, 13];
case 12:
error_3 = _a.sent();
console.log(error_3);
this.logger.error("Could not delete old DCTERMS issued at ".concat(syncedRootNode));
return [3 /*break*/, 13];
case 13:
this.logger.info("Sync time updated in Synced root at ".concat(syncedRootNode));
return [2 /*return*/];
}
});
});
};
Curator.prototype.updateCurrentMostRecentRelation = function (syncedStore, lastSynced, LDESRootCollectionIRI) {
return __awaiter(this, void 0, void 0, function () {
var syncedMostRecentRelation, mostRecentRelation, store, potentialMembers, members, collection, collectionIRI, error_4;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
syncedMostRecentRelation = this.calculateMostRecentRelation(syncedStore);
mostRecentRelation = this.syncedRelationtoLDESRelationIRI(syncedMostRecentRelation);
return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)(mostRecentRelation, this.session)];
case 1:
store = _a.sent();
potentialMembers = store.getObjects(null, Vocabularies_1.LDP.contains, null).map(function (object) { return object.id; });
members = [];
potentialMembers.forEach(function (memberID) {
var memberTimeLiteral = store.getQuads(memberID, Vocabularies_1.DCT.modified, null, null)[0].object;
var memberDateTime = _this.extractTimeFromLiteral(memberTimeLiteral);
if (memberDateTime > lastSynced) {
members.push(memberID);
}
});
collection = new n3_1.Store();
collection.addQuad(namedNode(LDESRootCollectionIRI), namedNode(Vocabularies_1.RDF.type), namedNode(Vocabularies_1.TREE.Collection));
members.forEach(function (member) {
var dateTimeLiteral = store.getObjects(member, Vocabularies_1.DCT.modified, null)[0];
if (!dateTimeLiteral)
throw Error("Announcement has no dc:modified ".concat(member));
collection.addQuad(namedNode(LDESRootCollectionIRI), namedNode(Vocabularies_1.TREE.member), namedNode(member));
collection.addQuad(namedNode(member), namedNode(Vocabularies_1.DCT.modified), dateTimeLiteral); // Also add time to curated IRI
});
collectionIRI = this.ldesRelationToSyncedRelationIRI(mostRecentRelation);
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, (0, SolidCommunication_1.patchQuads)(collectionIRI, this.session, collection.getQuads(null, null, null, null), SolidCommunication_1.SPARQL.INSERT)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
error_4 = _a.sent();
console.log(error_4);
this.logger.error("Could not update part of collection at ".concat(collectionIRI));
return [3 /*break*/, 5];
case 5:
this.logger.info("".concat(collectionIRI, " was updated with ").concat(collection.getQuads(null, Vocabularies_1.TREE.member, null, null).length, " members."));
return [2 /*return*/];
}
});
});
};
/**
* Updates the synced root node with the new relations since last sync time.
* Also returns the new relation nodes which members should be added to the synced collection as well.
*
* @param LDESRootNode
* @param syncedStore
* @param syncedRootNode
* @returns {Promise<string[]>}
*/
Curator.prototype.updateRootNode = function (LDESRootNode, syncedStore, syncedRootNode) {
return __awaiter(this, void 0, void 0, function () {
var LDESRootStore, metadata, newRelationsStore, newRelations, metadataRelations, error_5;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)(LDESRootNode, this.session)];
case 1:
LDESRootStore = _a.sent();
return [4 /*yield*/, (0, tree_metadata_extraction_1.extractMetadata)(LDESRootStore.getQuads(null, null, null, null))];
case 2:
metadata = _a.sent();
newRelationsStore = new n3_1.Store();
newRelations = [];
metadataRelations = metadata.nodes.get(LDESRootNode).relation;
metadataRelations.forEach(function (relationIRI) {
var relation = metadata.relations.get(relationIRI["@id"]);
// They should exist, but still a check?
if (!(relation.node && relation.node[0] && relation.path && relation.path[0]
&& relation.value && relation.value[0] && relation["@type"] && relation["@type"][0])) {
throw Error('relation parts are not in this relation');
}
var node = __assign({}, relation.node[0]);
node = {
"@id": _this.ldesRelationToSyncedRelationIRI(node['@id'])
};
var exists = syncedStore.getQuads(null, Vocabularies_1.TREE.node, node["@id"], null).length === 1;
// only if it doesn't exist yet, should the root be patched
if (!exists) {
var relationNode = newRelationsStore.createBlankNode();
newRelationsStore.addQuad(namedNode(syncedRootNode), namedNode(Vocabularies_1.TREE.relation), relationNode);
newRelationsStore.addQuad(relationNode, namedNode(Vocabularies_1.RDF.type), namedNode(relation["@type"][0]));
newRelationsStore.addQuad(relationNode, namedNode(Vocabularies_1.TREE.node), namedNode(node["@id"]));
newRelationsStore.addQuad(relationNode, namedNode(Vocabularies_1.TREE.path), namedNode(relation.path[0]["@id"]));
var dateTime = new Date(relation.value[0]['@value']);
newRelationsStore.addQuad(relationNode, namedNode(Vocabularies_1.TREE.value), _this.timestampToLiteral(dateTime.getTime()));
newRelations.push(node["@id"]);
}
});
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, (0, SolidCommunication_1.patchQuads)(syncedRootNode, this.session, newRelationsStore.getQuads(null, null, null, null), SolidCommunication_1.SPARQL.INSERT)];
case 4:
_a.sent();
return [3 /*break*/, 6];
case 5:
error_5 = _a.sent();
console.log(error_5);
this.logger.error("Could not patch root at ".concat(syncedRootNode));
return [3 /*break*/, 6];
case 6:
this.logger.info("Root patched with new relations at ".concat(syncedRootNode));
return [2 /*return*/, newRelations];
}
});
});
};
/**
* Retrieve the URIs of the most recent members of the synced collection.
* This method can be further optimised as currently the whole collection is fetched even though a part is wanted
* @param amount number of members that are needed
* @param startPoint Startpoint in the synced collection
* @returns {Promise<{timestamp: number, memberIRI: string}[]>} sorted list (newest members first)
*/
Curator.prototype.getRecentMembers = function (amount, startPoint) {
return __awaiter(this, void 0, void 0, function () {
var syncedRootIRI, memberStore, e_4, syncedMetadata, relationIRIs, promiseStores, _i, relationIRIs_1, iri, timeSortedMembers;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
syncedRootIRI = "".concat(this.synchronizedIRI, "root.ttl");
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)(syncedRootIRI, this.session)];
case 2:
memberStore = _a.sent();
return [3 /*break*/, 4];
case 3:
e_4 = _a.sent();
this.logger.error('Synchronized root does not exist yet. Call the synchronize() method first before members of the LDES can be retrieved');
return [2 /*return*/, []];
case 4: return [4 /*yield*/, (0, tree_metadata_extraction_1.extractMetadata)(memberStore.getQuads(null, null, null, null))];
case 5:
syncedMetadata = _a.sent();
relationIRIs = [];
syncedMetadata.relations.forEach(function (relation) { return relationIRIs.push(relation.node[0]["@id"]); });
promiseStores = [];
for (_i = 0, relationIRIs_1 = relationIRIs; _i < relationIRIs_1.length; _i++) {
iri = relationIRIs_1[_i];
promiseStores.push((0, SolidCommunication_1.fetchResourceAsStore)(iri, this.session));
}
return [4 /*yield*/, Promise.all(promiseStores).then(function (stores) {
stores.forEach(function (store) {
memberStore.addQuads(store.getQuads(null, null, null, null));
});
})];
case 6:
_a.sent();
timeSortedMembers = [];
memberStore.getQuads("".concat(this.ldesIRI, "root.ttl#Collection"), Vocabularies_1.TREE.member, null, null).forEach(function (member) {
var memberIRI = member.object.id;
var timeliteral = memberStore.getObjects(memberIRI, Vocabularies_1.DCT.modified, null)[0];
timeSortedMembers.push({
timestamp: _this.extractTimeFromLiteral(timeliteral),
memberIRI: memberIRI
});
});
this.logger.info("Members extracted from ".concat(syncedRootIRI));
// sort them
timeSortedMembers.sort(function (first, second) {
return second.timestamp - first.timestamp;
});
// extract the ones asked in the function and return them
return [2 /*return*/, timeSortedMembers.slice(startPoint, amount)];
}
});
});
};
/**
* Convert resources in a container of the LDES in LDP to members of the collection and return as a store
* @param store store of the LDP Container
* @param iri iri of the LDP Container
* @param LDESRootCollectionIRI iri of the LDES in LDP EventStream
* @returns {Store}
*/
Curator.prototype.extractMembers = function (store, iri, LDESRootCollectionIRI) {
var members = store.getObjects(null, Vocabularies_1.LDP.contains, null)
.map(function (object) { return object.id; });
var collection = new n3_1.Store();
collection.addQuad(namedNode(LDESRootCollectionIRI), namedNode(Vocabularies_1.RDF.type), namedNode(Vocabularies_1.TREE.Collection));
members.forEach(function (member) {
var dateTimeLiteral = store.getObjects(member, Vocabularies_1.DCT.modified, null)[0];
if (!dateTimeLiteral)
throw Error("Announcement has no dc:modified ".concat(iri).concat(member));
collection.addQuad(namedNode(LDESRootCollectionIRI), namedNode(Vocabularies_1.TREE.member), namedNode(member));
collection.addQuad(namedNode(member), namedNode(Vocabularies_1.DCT.modified), dateTimeLiteral); // Also add time to curated IRI
});
return collection;
};
/**
* Extract the members of a relation node in the LDES in LDP (where the relation node is an LDP Container)
* And Put all those members together with their created time in the synchronized collection
* @param iri IRI of an LDP container (which is the relation node)
* @param LDESCollectionIRI The IRI of the Event Stream (=Collection) of the LDES in LDP
* @returns {Promise<void>}
*/
Curator.prototype.synchronizeMembersFromRelation = function (iri, LDESCollectionIRI) {
return __awaiter(this, void 0, void 0, function () {
var store, collection, body, collectionIRI, error_6;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, SolidCommunication_1.fetchResourceAsStore)(iri, this.session)];
case 1:
store = _a.sent();
collection = this.extractMembers(store, iri, LDESCollectionIRI);
body = (0, Conversion_1.storeToString)(collection);
collectionIRI = this.ldesRelationToSyncedRelationIRI(iri);
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, (0, SolidCommunication_1.putTurtle)(collectionIRI, this.session, body)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
error_6 = _a.sent();
console.log(error_6);
this.logger.error("Could not update part of collection at ".concat(collectionIRI));
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
});
};
/**
* Using a store containing several relations, calculate the most recent relation
* @param syncedStore
* @returns {any}
*/
Curator.prototype.calculateMostRecentRelation = function (syncedStore) {
var _this = this;
var relationValues = syncedStore.getQuads(null, Vocabularies_1.TREE.value, null, null);
var maxValue = 0;
var relationMap = new Map();
relationValues.forEach(function (quad) {
var time = _this.extractTimeFromLiteral(quad.object);
if (time >= maxValue) {
maxValue = time;
}
var nodeId = syncedStore.getQuads(quad.subject, Vocabularies_1.TREE.node, null, null)[0].object.id;
relationMap.set(time, nodeId);
});
var mostRecentRelation = relationMap.get(maxValue);
if (!mostRecentRelation) {
throw Error('not possible');
}
return mostRecentRelation;
};
/**
* Extract a timestamp (ms) from an RDF Literal
* @param dateTimeLiteral
* @returns {number}
*/
Curator.prototype.extractTimeFromLiteral = function (dateTimeLiteral) {
var value = dateTimeLiteral.value;
if (!(dateTimeLiteral.datatype && dateTimeLiteral.datatype.id === Vocabularies_1.XSD.dateTime)) {
throw Error("Could not interpret ".concat(dateTimeLiteral, " as it was not ").concat(Vocabularies_1.XSD.dateTime));
}
var dateTime = new Date(value);
return dateTime.getTime();
};
/**
* Convert a timestamp (ms) to an RDF Literal
* @param timestamp
* @returns {Literal}
*/
Curator.prototype.timestampToLiteral = function (timestamp) {
var dateTime = new Date(timestamp);
return literal(dateTime.toISOString(), namedNode(Vocabularies_1.XSD.dateTime));
};
/**
* Transforms an ldes relation iri to a synchronized relation iri
* E.g. ldesIRI is "https://tree.linkeddatafragments.org/announcements/"
* and synchronizedIRI is "https://tree.linkeddatafragments.org/datasets/synced/"
* Then "https://tree.linkeddatafragments.org/announcements/1636985640000/" becomes
* "https://tree.linkeddatafragments.org/datasets/synced/1636985640000"
* @param iri
* @returns {string}
*/
Curator.prototype.ldesRelationToSyncedRelationIRI = function (iri) {
return iri.replace(this.ldesIRI, this.synchronizedIRI).slice(0, -1);
};
/**
* Transforms a synchronized relation iri to an ldes relation iri
* @param iri
* @returns {string}
*/
Curator.prototype.syncedRelationtoLDESRelationIRI = function (iri) {
return "".concat(iri.replace(this.synchronizedIRI, this.ldesIRI), "/");
};
return Curator;
}());
exports.Curator = Curator;