Skip to content

Commit

Permalink
WIP Fetcher & UpdateManager linkeddata#355
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Nov 4, 2019
1 parent 428cc0a commit 847c4fb
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 100 deletions.
91 changes: 75 additions & 16 deletions src/fetcher.js → src/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import serialize from './serialize'

import { fetch as solidAuthCli } from 'solid-auth-cli'
import { fetch as solidAuthClient } from 'solid-auth-client'
import { TFBlankNode } from './types'
import { Formula } from './index'

// This is a special fetch which does OIDC auth, catching 401 errors
const fetch = typeof window === 'undefined' ? solidAuthCli : solidAuthClient
Expand Down Expand Up @@ -81,13 +83,21 @@ const ns = {
}

class Handler {
// TODO: Document, type
response: any
// TODO: Document, type
dom: any
static pattern: RegExp

constructor (response, dom) {
this.response = response
this.dom = dom
}
}

class RDFXMLHandler extends Handler {
static pattern: RegExp

static toString () {
return 'RDFXMLHandler'
}
Expand All @@ -98,7 +108,13 @@ class RDFXMLHandler extends Handler {
}
}

parse (fetcher, responseText, options, response) {
parse (
fetcher: Fetcher,
/** An XML String */
responseText: String,
options,
response
) {
let kb = fetcher.store
if (!this.dom) {
this.dom = Util.parseXML(responseText)
Expand Down Expand Up @@ -427,6 +443,37 @@ function isXMLNS (responseText) {
return responseText.match(/[^(<html)]*<html\s+[^<]*xmlns=['"]http:\/\/www.w3.org\/1999\/xhtml["'][^<]*>/)
}

interface Options {
fetch?: any
timeout?: number
}

type RequestedValues =
/** No record of web access or record reset */
undefined |
/** Has been requested, fetch in progress */
true |
/** Received, OK */
'done' |
/** Not logged in */
401 |
/** HTTP status unauthorized */
403 |
/** Not found, resource does not exist */
404 |
/** In attempt to counter CORS problems retried */
'redirected' |
'parse_error' |
/**
* URI is not a protocol Fetcher can deal with
* other strings mean various other errors.
*/
'unsupported_protocol'

interface Requested {
[uri: string]: RequestedValues
}

/** Fetcher
*
* The Fetcher object is a helper object for a quadstore
Expand All @@ -436,10 +483,34 @@ function isXMLNS (responseText) {
* and put back the fata to the web.
*/
export default class Fetcher {
store: Formula
timeout: number
_fetch: any
mediatypes: {
[id: string]: {
'q': number
};
}
appNode: TFBlankNode
/**
* @constructor
*/
constructor (store, options = {}) {
* this.requested[uri] states:
* undefined no record of web access or records reset
* true has been requested, fetch in progress
* 'done' received, Ok
* 401 Not logged in
* 403 HTTP status unauthorized
* 404 Resource does not exist. Can be created etc.
* 'redirected' In attempt to counter CORS problems retried.
* 'parse_error' Parse error
* 'unsupported_protocol' URI is not a protocol Fetcher can deal with
* other strings mean various other errors.
*/
requested: Requested

constructor (
store: Formula,
options: Options = {}
) {
this.store = store || new IndexedFormula()
this.timeout = options.timeout || 30000

Expand All @@ -452,18 +523,6 @@ export default class Fetcher {
this.appNode = this.store.bnode() // Denoting this session
this.store.fetcher = this // Bi-linked
this.requested = {}
// this.requested[uri] states:
// undefined no record of web access or records reset
// true has been requested, fetch in progress
// 'done' received, Ok
// 401 Not logged in
// 403 HTTP status unauthorized
// 404 Resource does not exist. Can be created etc.
// 'redirected' In attempt to counter CORS problems retried.
// 'parse_error' Parse error
// 'unsupported_protocol' URI is not a protocol Fetcher can deal with
// other strings mean various other errors.
//
this.timeouts = {} // list of timeouts associated with a requested URL
this.redirectedTo = {} // When 'redirected'
this.fetchQueue = {}
Expand Down
3 changes: 3 additions & 0 deletions src/formula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Variable from './variable'
import Literal from './literal'
import { IdentityFactory, Indexable } from './data-factory-type'
import IndexedFormula from './store'
import Fetcher from './fetcher'

export function isFormula<T>(value: T | TFTerm): value is Formula {
return (value as Node).termType === TermType.Graph
Expand All @@ -40,6 +41,8 @@ interface FormulaOpts {
* A formula, or store of RDF statements
*/
export default class Formula extends Node {
/** Is created by the Fetcher on an IndexedFormula */
fetcher?: Fetcher

static termType: TermType.Graph;

Expand Down
90 changes: 47 additions & 43 deletions src/update-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,31 @@ import DataFactory from './data-factory'
import Namespace from './namespace'
import Serializer from './serializer'
import { join as uriJoin } from './uri'
import { isStore } from './utils'
import { isStore, isNamedNode } from './utils'
import * as Util from './util'
import Statement from './statement';
import { NamedNode } from './index'

/** Update Manager
*
* The update manager is a helper object for a store.
interface UpdateManagerFormula extends IndexedFormula {
fetcher: Fetcher
}

/**
* The UpdateManager is a helper object for a store.
* Just as a Fetcher provides the store with the ability to read and write,
* the Update Manager provides functionality for making small patches in real time,
* and also looking out for concurrent updates from other agents
*/

export default class UpdateManager {

store: IndexedFormula
store: UpdateManagerFormula

ifps: {}

fps: {}

/** Index of objects for coordinating incoming and outgoing patches */
patchControl: []

/** Object of namespaces */
ns: any
Expand All @@ -35,14 +44,14 @@ export default class UpdateManager {
* @param store - The quadstore to store data and metadata. Created if not passed.
*/
constructor (store?: IndexedFormula) {
store = store || new IndexedFormula() // If none provided make a store
this.store = (store as IndexedFormula)
store = store || new IndexedFormula()
if (store.updater) {
throw new Error("You can't have two UpdateManagers for the same store")
}
if (!store.fetcher) { // The store must also/already have a fetcher
store.fetcher = new Fetcher(store)
if (!(store as UpdateManagerFormula).fetcher) {
(store as UpdateManagerFormula).fetcher = new Fetcher(store)
}
this.store = store as UpdateManagerFormula
store.updater = this
this.ifps = {}
this.fps = {}
Expand All @@ -56,7 +65,7 @@ export default class UpdateManager {
this.ns.rdf = Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
this.ns.owl = Namespace('http://www.w3.org/2002/07/owl#')

this.patchControl = [] // index of objects fro coordinating incomng and outgoing patches
this.patchControl = []
}

patchControlFor (doc) {
Expand All @@ -72,20 +81,19 @@ export default class UpdateManager {
* for safety.
* We don't actually check for write access on files.
*
* @param uri {string}
* @param kb {IndexedFormula}
*
* @returns {string|boolean|undefined} The method string SPARQL or DAV or
* @returns The method string SPARQL or DAV or
* LOCALFILE or false if known, undefined if not known.
*/
editable (uri, kb) {
editable (uri: string | NamedNode, kb: IndexedFormula): string | boolean | undefined {
if (!uri) {
return false // Eg subject is bnode, no known doc to write to
}
if (!kb) {
kb = this.store
}
uri = uri.uri || uri // Allow Named Node to be passed
if (isNamedNode(uri)) {
uri = uri.uri
}

if (uri.slice(0, 8) === 'file:///') {
if (kb.holds(
Expand Down Expand Up @@ -121,7 +129,7 @@ export default class UpdateManager {
if (kb.holds(DataFactory.namedNode(uri), this.ns.rdf('type'), this.ns.ldp('Resource'))) {
return 'SPARQL'
}
var method
var method: string
for (var r = 0; r < requests.length; r++) {
request = requests[r]
if (request !== undefined) {
Expand Down Expand Up @@ -656,25 +664,21 @@ export default class UpdateManager {
return true
}

/** Update
*
* This high-level function updates the local store iff the web is changed
* successfully.
*
* Deletions, insertions may be undefined or single statements or lists or formulae
* (may contain bnodes which can be indirectly identified by a where clause).
* The `why` property of each statement must be the same and give the web document to be updated
*
* @param deletions - Statement or statments to be deleted.
* @param insertions - Statement or statements to be inserted
*
* @param callbackFunction {Function} called as callbackFunction(uri, success, errorbody)
* OR returns a promise
*
* @returns {*}
/**
* This high-level function updates the local store iff the web is changed successfully.
* Deletions, insertions may be undefined or single statements or lists or formulae (may contain bnodes which can be indirectly identified by a where clause).
* The `why` property of each statement must be the same and give the web document to be updated.
* @param deletions - Statement or statements to be deleted.
* @param insertions - Statement or statements to be inserted.
* @param callback - called as callbackFunction(uri, success, errorbody)
* OR returns a promise
*/
update (deletions, insertions, callbackFunction, secondTry) {
if (!callbackFunction) {
update(
deletions: ReadonlyArray<Statement>,
insertions: ReadonlyArray<Statement>,
callback?: (uri: string | undefined | null, success: boolean, errorBody?: string) => void
): void | Promise<void> {
if (!callback) {
var thisUpdater = this
return new Promise(function (resolve, reject) { // Promise version
thisUpdater.update(deletions, insertions, function (uri, ok, errorBody) {
Expand Down Expand Up @@ -702,7 +706,7 @@ export default class UpdateManager {
throw new Error('Type Error ' + (typeof is) + ': ' + is)
}
if (ds.length === 0 && is.length === 0) {
return callbackFunction(null, true) // success -- nothing needed to be done.
return callback(null, true) // success -- nothing needed to be done.
}
var doc = ds.length ? ds[0].why : is[0].why
if (!doc) {
Expand Down Expand Up @@ -740,7 +744,7 @@ export default class UpdateManager {
}
console.log(`Update: have not loaded ${doc} before: loading now...`)
this.store.fetcher.load(doc).then(response => {
this.update(deletions, insertions, callbackFunction, true) // secondTry
this.update(deletions, insertions, callback, true) // secondTry
}, err => {
throw new Error(`Update: Can't read ${doc} before patching: ${err}`)
})
Expand Down Expand Up @@ -809,7 +813,7 @@ export default class UpdateManager {
}
}

callbackFunction(uri, success, body, response)
callback(uri, success, body, response)
control.pendingUpstream -= 1
// When upstream patches have been sent, reload state if downstream waiting
if (control.pendingUpstream === 0 && control.downstreamAction) {
Expand All @@ -820,13 +824,13 @@ export default class UpdateManager {
}
})
} else if (protocol.indexOf('DAV') >= 0) {
this.updateDav(doc, ds, is, callbackFunction)
this.updateDav(doc, ds, is, callback)
} else {
if (protocol.indexOf('LOCALFILE') >= 0) {
try {
this.updateLocalFile(doc, ds, is, callbackFunction)
this.updateLocalFile(doc, ds, is, callback)
} catch (e) {
callbackFunction(doc.uri, false,
callback(doc.uri, false,
'Exception trying to write back file <' + doc.uri + '>\n'
// + tabulator.Util.stackString(e))
)
Expand All @@ -836,7 +840,7 @@ export default class UpdateManager {
}
}
} catch (e) {
callbackFunction(undefined, false, 'Exception in update: ' + e + '\n' +
callback(undefined, false, 'Exception in update: ' + e + '\n' +
Util.stackString(e))
}
}
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"strict": true,
"noImplicitAny": false,
"lib": [
"es2019"
"es2019",
"dom"
],
"outDir": "./temp_ts_output"
},
Expand Down
Loading

0 comments on commit 847c4fb

Please sign in to comment.