From a9ba201bb2546d75a17f2df8dfb42cb0ff66591b Mon Sep 17 00:00:00 2001 From: jaxoncreed Date: Thu, 14 Sep 2023 17:25:24 -0400 Subject: [PATCH] Added methods for the REsource Class --- .../src/dashboard/BuildRootContainer.ts | 10 + .../demo-react/src/dashboard/Dashboard.tsx | 36 +--- packages/solid/src/index.ts | 2 +- packages/solid/src/requester/LeafRequester.ts | 33 +++- .../requester/requestResults/BinaryResult.ts | 4 +- .../requester/requests/createDataResource.ts | 8 +- .../src/requester/requests/deleteResource.ts | 3 +- .../src/requester/requests/readResource.ts | 13 +- .../requester/requests/updateDataResource.ts | 3 +- .../src/requester/requests/uploadResource.ts | 10 +- packages/solid/src/resource/Leaf.ts | 86 -------- packages/solid/src/resource/Resource.ts | 184 +++++++++++++++++- packages/solid/src/util/RequestBatcher.ts | 24 ++- 13 files changed, 276 insertions(+), 140 deletions(-) delete mode 100644 packages/solid/src/resource/Leaf.ts diff --git a/packages/demo-react/src/dashboard/BuildRootContainer.ts b/packages/demo-react/src/dashboard/BuildRootContainer.ts index e69de29..72044a7 100644 --- a/packages/demo-react/src/dashboard/BuildRootContainer.ts +++ b/packages/demo-react/src/dashboard/BuildRootContainer.ts @@ -0,0 +1,10 @@ +import type { FunctionComponent, PropsWithChildren, ReactNode } from "react"; +import {} from "@ldo/solid"; + +interface BuildRootContainerChildProps { + rootContainer: ContainerResource +} + +export const BuildRootContainer: FunctionComponent<{ children: FunctionComponent }> = () => { + +}; diff --git a/packages/demo-react/src/dashboard/Dashboard.tsx b/packages/demo-react/src/dashboard/Dashboard.tsx index 12a4d6a..bb08f53 100644 --- a/packages/demo-react/src/dashboard/Dashboard.tsx +++ b/packages/demo-react/src/dashboard/Dashboard.tsx @@ -7,39 +7,15 @@ import { AccessRules, ContainerResource } from "@ldo/solid"; export const Dashboard: FunctionComponent = () => { const { session } = useSolidAuth(); - const containerUri = useMemo(() => { - if (!session.webId) return ""; - // HACK: this is a hard coded hack to find the root container. Solid doesn't - // have an official way of doing this. - const rootContainer = session.webId.replace("profile/card#me", ""); - return `${rootContainer}demo-ldo/`; - }, [session.webId]); - - const mainContainer = useDataResource(containerUri); - - if (mainContainer instanceof AccessRules) { - console.log("here"); - } + const rootContainer = useRootContainer(session.webId); + const mainContainer = useContainerResource() + // useParentContainer useEffect(() => { - // Upon load check to see if the root folder exists - mainContainer.checkExists().then(async (doesExist) => { - console.log(doesExist); - - - const error: DocumentError = await mainContainer.create(); - if (error) { - // hanldle - return; - } + if (rootContainer) { - // // If not, create it - // if (!doesExist) { - // await mainContainer.create(); - // const accessRules = mainContainer.accessRules; - // } - }); - }, [mainContainer]); + } + }, [rootContainer]); return (
diff --git a/packages/solid/src/index.ts b/packages/solid/src/index.ts index f08a2f1..7a7d1b4 100644 --- a/packages/solid/src/index.ts +++ b/packages/solid/src/index.ts @@ -2,5 +2,5 @@ export * from "./createSolidLdoDataset"; export * from "./SolidLdoDataset"; export * from "./SolidLdoDatasetContext"; -export * from "./resource/Leaf"; +export * from "./resource/Resource"; export * from "./resource/Container"; diff --git a/packages/solid/src/requester/LeafRequester.ts b/packages/solid/src/requester/LeafRequester.ts index 28985c4..9a87b4e 100644 --- a/packages/solid/src/requester/LeafRequester.ts +++ b/packages/solid/src/requester/LeafRequester.ts @@ -1,5 +1,5 @@ import type { LeafUri } from "../util/uriTypes"; -import { RequestBatcher } from "../util/RequestBatcher"; +import { ANY_KEY, RequestBatcher } from "../util/RequestBatcher"; import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; import type { DatasetChanges } from "@ldo/rdf-utils"; import type { @@ -18,8 +18,14 @@ import type { DeleteResult } from "./requests/deleteResource"; import { deleteResource } from "./requests/deleteResource"; import type { UpdateResult } from "./requests/updateDataResource"; +const READ_KEY = "read"; +const CREATE_KEY = "createDataResource"; +const UPLOAD_KEY = "upload"; +const UPDATE_KEY = "updateDataREsource"; +const DELETE_KEY = "delete"; + export class LeafRequester { - private requestBatcher = new RequestBatcher(); + private readonly requestBatcher = new RequestBatcher(); // All intance variables readonly uri: LeafUri; @@ -30,11 +36,29 @@ export class LeafRequester { this.context = context; } + isLoading(): boolean { + return this.requestBatcher.isLoading(ANY_KEY); + } + isCreating(): boolean { + return this.requestBatcher.isLoading(CREATE_KEY); + } + isUploading(): boolean { + return this.requestBatcher.isLoading(UPLOAD_KEY); + } + isReading(): boolean { + return this.requestBatcher.isLoading(READ_KEY); + } + isUpdating(): boolean { + return this.requestBatcher.isLoading(UPDATE_KEY); + } + isDeletinng(): boolean { + return this.requestBatcher.isLoading(DELETE_KEY); + } + /** * Read this resource. */ async read(): Promise { - const READ_KEY = "read"; const transaction = this.context.solidLdoDataset.startTransaction(); const result = await this.requestBatcher.queueProcess({ name: READ_KEY, @@ -69,7 +93,6 @@ export class LeafRequester { async createDataResource( overwrite?: boolean, ): Promise { - const CREATE_KEY = "createDataResource"; const transaction = this.context.solidLdoDataset.startTransaction(); const result = await this.requestBatcher.queueProcess({ name: CREATE_KEY, @@ -119,7 +142,6 @@ export class LeafRequester { mimeType: string, overwrite?: boolean, ): Promise { - const UPLOAD_KEY = "upload"; const transaction = this.context.solidLdoDataset.startTransaction(); const result = await this.requestBatcher.queueProcess({ name: UPLOAD_KEY, @@ -157,7 +179,6 @@ export class LeafRequester { * Delete this resource */ async delete(): Promise { - const DELETE_KEY = "delete"; const transaction = this.context.solidLdoDataset.startTransaction(); const result = await this.requestBatcher.queueProcess({ name: DELETE_KEY, diff --git a/packages/solid/src/requester/requestResults/BinaryResult.ts b/packages/solid/src/requester/requestResults/BinaryResult.ts index 68ddc24..3444122 100644 --- a/packages/solid/src/requester/requestResults/BinaryResult.ts +++ b/packages/solid/src/requester/requestResults/BinaryResult.ts @@ -3,10 +3,12 @@ import { RequesterResult } from "./RequesterResult"; export class BinaryResult extends RequesterResult { type = "binary" as const; readonly blob: Blob; + readonly mimeType: string; - constructor(uri: string, blob: Blob) { + constructor(uri: string, blob: Blob, mimeType: string) { super(uri); this.blob = blob; + this.mimeType = mimeType; } static is(response: Response): boolean { diff --git a/packages/solid/src/requester/requests/createDataResource.ts b/packages/solid/src/requester/requests/createDataResource.ts index 6ffb76d..8df298c 100644 --- a/packages/solid/src/requester/requests/createDataResource.ts +++ b/packages/solid/src/requester/requests/createDataResource.ts @@ -13,11 +13,15 @@ import { deleteResource } from "./deleteResource"; import { readResource } from "./readResource"; import type { RequestParams } from "./requestParams"; -export type CreateResult = DataResult | HttpErrorResultType | UnexpectedError; +export type CreateResult = DataResult | CreateResultErrors; +export type CreateResultErrors = HttpErrorResultType | UnexpectedError; export type CreateResultWithoutOverwrite = | CreateResult - | TurtleFormattingError + | CreateResultWithoutOverwriteErrors | BinaryResult; +export type CreateResultWithoutOverwriteErrors = + | TurtleFormattingError + | CreateResultErrors; export function createDataResource( params: RequestParams, diff --git a/packages/solid/src/requester/requests/deleteResource.ts b/packages/solid/src/requester/requests/deleteResource.ts index efb6904..6cdabcf 100644 --- a/packages/solid/src/requester/requests/deleteResource.ts +++ b/packages/solid/src/requester/requests/deleteResource.ts @@ -6,7 +6,8 @@ import { UnexpectedHttpError } from "../requestResults/HttpErrorResult"; import type { RequestParams } from "./requestParams"; import { deleteResourceRdfFromContainer } from "../../util/rdfUtils"; -export type DeleteResult = AbsentResult | HttpErrorResultType | UnexpectedError; +export type DeleteResult = AbsentResult | DeleteResultError; +export type DeleteResultError = HttpErrorResultType | UnexpectedError; export async function deleteResource({ uri, diff --git a/packages/solid/src/requester/requests/readResource.ts b/packages/solid/src/requester/requests/readResource.ts index e999f6c..d46aa49 100644 --- a/packages/solid/src/requester/requests/readResource.ts +++ b/packages/solid/src/requester/requests/readResource.ts @@ -2,6 +2,7 @@ import { DataResult } from "../requestResults/DataResult"; import type { TurtleFormattingError } from "../requestResults/DataResult"; import { HttpErrorResult, + ServerHttpError, type HttpErrorResultType, } from "../requestResults/HttpErrorResult"; import { UnexpectedError } from "../requestResults/ErrorResult"; @@ -17,6 +18,8 @@ export type ReadResult = | AbsentResult | DataResult | BinaryResult + | ReadResultError; +export type ReadResultError = | HttpErrorResultType | TurtleFormattingError | UnexpectedError; @@ -44,8 +47,16 @@ export async function readResource({ return addRawTurtleToDataset(rawTurtle, transaction, uri); } else { // Load Blob + const contentType = response.headers.get("content-type"); + if (!contentType) { + return new ServerHttpError( + uri, + response, + "Server provided no content-type", + ); + } const blob = await response.blob(); - return new BinaryResult(uri, blob); + return new BinaryResult(uri, blob, contentType); } } catch (err) { return UnexpectedError.fromThrown(uri, err); diff --git a/packages/solid/src/requester/requests/updateDataResource.ts b/packages/solid/src/requester/requests/updateDataResource.ts index 61ce76d..2e345fb 100644 --- a/packages/solid/src/requester/requests/updateDataResource.ts +++ b/packages/solid/src/requester/requests/updateDataResource.ts @@ -4,7 +4,8 @@ import type { HttpErrorResultType } from "../requestResults/HttpErrorResult"; import type { UnexpectedError } from "../requestResults/ErrorResult"; import type { RequestParams } from "./requestParams"; -export type UpdateResult = DataResult | HttpErrorResultType | UnexpectedError; +export type UpdateResult = DataResult | UpdateResultError; +export type UpdateResultError = HttpErrorResultType | UnexpectedError; export async function updateDataResource( _params: RequestParams, diff --git a/packages/solid/src/requester/requests/uploadResource.ts b/packages/solid/src/requester/requests/uploadResource.ts index 099616a..50fd4c3 100644 --- a/packages/solid/src/requester/requests/uploadResource.ts +++ b/packages/solid/src/requester/requests/uploadResource.ts @@ -17,11 +17,15 @@ import { deleteResource } from "./deleteResource"; import { readResource } from "./readResource"; import type { RequestParams } from "./requestParams"; -export type UploadResult = BinaryResult | HttpErrorResultType | UnexpectedError; +export type UploadResult = BinaryResult | UploadResultError; +export type UploadResultError = HttpErrorResultType | UnexpectedError; export type UploadResultWithoutOverwrite = | UploadResult - | TurtleFormattingError + | UploadResultWithoutOverwriteError | DataResult; +export type UploadResultWithoutOverwriteError = + | UploadResultError + | TurtleFormattingError; export function uploadResource( params: RequestParams, @@ -78,7 +82,7 @@ export async function uploadResource( if (httpError) return httpError; addResourceRdfToContainer(uri, transaction); - return new BinaryResult(uri, blob); + return new BinaryResult(uri, blob, mimeType); } catch (err) { return UnexpectedError.fromThrown(uri, err); } diff --git a/packages/solid/src/resource/Leaf.ts b/packages/solid/src/resource/Leaf.ts deleted file mode 100644 index 98a58f7..0000000 --- a/packages/solid/src/resource/Leaf.ts +++ /dev/null @@ -1,86 +0,0 @@ -// import type { LdoDataset } from "@ldo/ldo"; -// import type { LeafMethodNotAllowedError } from "./error/MethodNotAllowedError"; -// import type { DatasetChanges } from "@ldo/rdf-utils"; -// import type { PresentContainer } from "./abstract/container/PresentContainer"; -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import type { LeafUri } from "../uriTypes"; -import { Resource } from "./Resource"; - -export interface ConcreteInstance { - uri: LeafUri; - context: SolidLdoDatasetContext; - // methods: typeof AbstractLeaf; -} - -// REMEMBER: This file should be replaced with non abstract methods -export class Leaf extends Resource { - // All intance variables - private readonly i: SolidLdoDatasetContext; - uri: string; - abstract type(): LeafType["type"]; - // Loading Methods - isLoading(): boolean { - return ( - this.isCreating() || - this.isReading() || - this.isUpdating() || - this.isDeletinng() - ); - } - abstract isCreating(): boolean; - abstract isReading(): boolean; - abstract isUpdating(): boolean; - abstract isDeletinng(): boolean; - isDoingInitialFetch(): boolean { - return this.isReading() && !this.didInitialFetch(); - } - // Checkers - abstract didInitialFetch(): boolean; - abstract isFetched(): boolean; - abstract isUnfetched(): boolean; - abstract isBinary: boolean | undefined; - abstract isDataResource(): boolean | undefined; - // Read Methods - abstract read(): Promise; - abstract readIfUnfetched(): Promise; - // Create Methods - abstract createOrOverwrite(): Promise; - abstract createOrOverwrite(blob: Blob): Promise; - abstract createIfAbsent(): Promise; - abstract createIfAbsent(blob: Blob): Promise; - // Delete Method - abstract delete(): Promise; - // Parent Container Methods -- Remember to change for Container - abstract getCachedParentContainer(): ContainerType | LdoSolidError; - abstract getParentContainer(): Promise; - abstract reloadParentContainer(): Promise; - abstract getRootContainerFromCache(): - | ContainerType - | undefined - | LdoSolidError; - abstract getRootContainer(): Promise< - FetchedContainerType | undefined | LdoSolidError - >; - abstract getRootContainerFromPod(): Promise< - FetchedContainerType | undefined | LdoSolidError - >; - // Exclusing Methods ========================================================= - // Data Methods (Data Leaf Only) - abstract getLdoDataset(): LdoDataset | LeafMethodNotAllowedError; - abstract reloadLdoDataset(): Promise; - abstract hasData(): boolean | LeafMethodNotAllowedError; - abstract reloadHasData(): Promise; - abstract update( - changes: DatasetChanges, - ): Promise; - // Binary Methods (Binary Only) - abstract getMimeType(): string | LeafMethodNotAllowedError; - abstract reloadMimeType(): Promise; - // Create Methods (AbsentLeaf Only) - abstract create(): Promise< - DataLeaf | LdoSolidError | LeafMethodNotAllowedError - >; - abstract create( - blob: Blob, - ): Promise; -} diff --git a/packages/solid/src/resource/Resource.ts b/packages/solid/src/resource/Resource.ts index be3a3a1..449de57 100644 --- a/packages/solid/src/resource/Resource.ts +++ b/packages/solid/src/resource/Resource.ts @@ -1 +1,183 @@ -export class Resource {} +// import type { LdoDataset } from "@ldo/ldo"; +// import type { LeafMethodNotAllowedError } from "./error/MethodNotAllowedError"; +// import type { DatasetChanges } from "@ldo/rdf-utils"; +// import type { PresentContainer } from "./abstract/container/PresentContainer"; +import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; +import { LeafRequester } from "../requester/LeafRequester"; +import type { AbsentResult } from "../requester/requestResults/AbsentResult"; +import type { BinaryResult } from "../requester/requestResults/BinaryResult"; +import type { DataResult } from "../requester/requestResults/DataResult"; +import { + UnexpectedError, + type ErrorResult, +} from "../requester/requestResults/ErrorResult"; +import type { + CreateResultErrors, + CreateResultWithoutOverwriteErrors, +} from "../requester/requests/createDataResource"; +import type { ReadResultError } from "../requester/requests/readResource"; +import type { UploadResultError } from "../requester/requests/uploadResource"; +import type { LeafUri } from "../uriTypes"; + +export interface ConcreteInstance { + uri: LeafUri; + context: SolidLdoDatasetContext; + // methods: typeof AbstractLeaf; +} + +// REMEMBER: This file should be replaced with non abstract methods +export class Resource { + // All intance variables + private readonly context: SolidLdoDatasetContext; + readonly uri: string; + private readonly requester: LeafRequester; + private didInitialFetch: boolean = false; + private absent: boolean | undefined; + private binaryData: { data: Blob; mimeType: string } | undefined; + + constructor(uri: string, context: SolidLdoDatasetContext) { + this.uri = uri; + this.context = context; + this.requester = new LeafRequester(uri as LeafUri, context); + } + + // Loading Methods + isLoading(): boolean { + return this.requester.isLoading(); + } + isCreating(): boolean { + return this.requester.isCreating(); + } + isUploading(): boolean { + return this.requester.isUploading(); + } + isReading(): boolean { + return this.requester.isReading(); + } + isUpdating(): boolean { + return this.requester.isUpdating(); + } + isDeleting(): boolean { + return this.requester.isDeletinng(); + } + isDoingInitialFetch(): boolean { + return this.isReading() && !this.isFetched(); + } + + // Checkers + isFetched(): boolean { + return this.didInitialFetch; + } + isUnfetched(): boolean { + return !this.didInitialFetch; + } + isBinary(): boolean | undefined { + if (!this.didInitialFetch) { + return undefined; + } + return !!this.binaryData; + } + isDataResource(): boolean | undefined { + if (!this.didInitialFetch) { + return undefined; + } + return !this.binaryData; + } + + private parseResult( + result: AbsentResult | BinaryResult | DataResult | ErrorResult, + ) { + switch (result.type) { + case "error": + return result; + case "absent": + this.didInitialFetch = true; + this.absent = true; + delete this.binaryData; + return this; + case "data": + this.didInitialFetch = true; + this.absent = false; + delete this.binaryData; + return this; + case "binary": + this.didInitialFetch = true; + this.absent = false; + this.binaryData = { + data: result.blob, + mimeType: result.mimeType, + }; + default: + return new UnexpectedError( + this.uri, + new Error("Unknown request result"), + ); + } + } + + // Read Methods + async read(): Promise { + return this.parseResult(await this.requester.read()) as + | Resource + | ReadResultError; + } + async readIfUnfetched(): Promise { + if (this.didInitialFetch) { + return this; + } + return this.read(); + } + + // Create Methods + async createAndOverwrite(): Promise { + return this.parseResult(await this.requester.createDataResource(true)) as + | Resource + | CreateResultErrors; + } + + async createIfAbsent(): Promise< + Resource | CreateResultWithoutOverwriteErrors + > { + return this.parseResult(await this.requester.createDataResource()) as + | Resource + | CreateResultWithoutOverwriteErrors; + } + + async uploadAndOverwrite( + blob: Blob, + mimeType: string, + ): Promise { + return this.parseResult(await this.requester.upload(blob, mimeType)) as + | Resource + | UploadResultError; + } + abstract createIfAbsent(blob: Blob): Promise; + // Delete Method + abstract delete(): Promise; + // Parent Container Methods -- Remember to change for Container + abstract getCachedParentContainer(): ContainerType | LdoSolidError; + abstract getParentContainer(): Promise; + abstract reloadParentContainer(): Promise; + abstract getRootContainerFromCache(): + | ContainerType + | undefined + | LdoSolidError; + abstract getRootContainer(): Promise< + FetchedContainerType | undefined | LdoSolidError + >; + abstract getRootContainerFromPod(): Promise< + FetchedContainerType | undefined | LdoSolidError + >; + // Exclusing Methods ========================================================= + // Data Methods (Data Leaf Only) + abstract getLdoDataset(): LdoDataset | LeafMethodNotAllowedError; + abstract reloadLdoDataset(): Promise; + abstract hasData(): boolean | LeafMethodNotAllowedError; + abstract reloadHasData(): Promise; + abstract update( + changes: DatasetChanges, + ): Promise; + // Binary Methods (Binary Only) + abstract getMimeType(): string | LeafMethodNotAllowedError; + abstract reloadMimeType(): Promise; +} diff --git a/packages/solid/src/util/RequestBatcher.ts b/packages/solid/src/util/RequestBatcher.ts index b23c2fb..084b47b 100644 --- a/packages/solid/src/util/RequestBatcher.ts +++ b/packages/solid/src/util/RequestBatcher.ts @@ -7,6 +7,8 @@ export interface WaitingProcess { awaitingRejections: ((err: any) => void)[]; } +export const ANY_KEY = "any"; + export interface WaitingProcessOptions { name: string; args: Args; @@ -27,7 +29,7 @@ export interface WaitingProcessOptions { export class RequestBatcher { private lastRequestTimestampMap: Record = {}; - private isLoading: Record = {}; + private loadingMap: Record = {}; private isWaiting: boolean = false; private processQueue: WaitingProcess[] = []; public shouldBatchAllRequests: boolean; @@ -43,12 +45,16 @@ export class RequestBatcher { this.batchMillis = options?.batchMillis || 1000; } + public isLoading(key: string): boolean { + return !!this.loadingMap[key]; + } + private triggerOrWaitProcess() { if (!this.processQueue[0]) { return; } const processName = this.shouldBatchAllRequests - ? "any" + ? ANY_KEY : this.processQueue[0].name; // Set last request timestamp if not available @@ -62,7 +68,7 @@ export class RequestBatcher { const triggerProcess = async () => { this.isWaiting = false; this.lastRequestTimestampMap[processName] = Date.now(); - this.lastRequestTimestampMap["any"] = Date.now(); + this.lastRequestTimestampMap[ANY_KEY] = Date.now(); const processToTrigger = this.processQueue.shift(); if (processToTrigger) { try { @@ -84,13 +90,13 @@ export class RequestBatcher { (process) => process.name === processToTrigger.name, ) ) { - this.isLoading[processToTrigger.name] = false; + this.loadingMap[processToTrigger.name] = false; } if (this.processQueue.length > 0) { this.triggerOrWaitProcess(); } else { - this.isLoading["any"] = false; + this.loadingMap[ANY_KEY] = false; } } }; @@ -111,7 +117,11 @@ export class RequestBatcher { this.processQueue[this.processQueue.length - 1]; if (lastProcessInQueue) { const didModifyLast = lastProcessInQueue - ? options.modifyQueue(this.processQueue, this.isLoading, options.args) + ? options.modifyQueue( + this.processQueue, + this.loadingMap, + options.args, + ) : false; if (didModifyLast) { lastProcessInQueue.awaitingResolutions.push(resolve); @@ -130,7 +140,7 @@ export class RequestBatcher { this.processQueue.push( waitingProcess as unknown as WaitingProcess, ); - this.isLoading[waitingProcess.name] = true; + this.loadingMap[waitingProcess.name] = true; this.triggerOrWaitProcess(); }); }