You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
8.9 KiB
297 lines
8.9 KiB
import { namedNode } from "@rdfjs/data-model";
|
|
import { ContainerRequester } from "../requester/ContainerRequester";
|
|
import type {
|
|
CheckRootResult,
|
|
CheckRootResultError,
|
|
} from "../requester/requests/checkRootContainer";
|
|
import type {
|
|
ContainerCreateAndOverwriteResult,
|
|
ContainerCreateIfAbsentResult,
|
|
LeafCreateAndOverwriteResult,
|
|
LeafCreateIfAbsentResult,
|
|
} from "../requester/requests/createDataResource";
|
|
import type {
|
|
DeleteResult,
|
|
DeleteResultError,
|
|
} from "../requester/requests/deleteResource";
|
|
import type {
|
|
ReadContainerResult,
|
|
ReadResultError,
|
|
} from "../requester/requests/readResource";
|
|
import { AggregateError } from "../requester/results/error/ErrorResult";
|
|
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
|
|
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
|
|
import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess";
|
|
import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess";
|
|
import type { AggregateSuccess } from "../requester/results/success/SuccessResult";
|
|
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
|
|
import { getParentUri, ldpContains } from "../util/rdfUtils";
|
|
import type { ContainerUri, LeafUri } from "../util/uriTypes";
|
|
import type { Leaf } from "./Leaf";
|
|
import type { SharedStatuses } from "./Resource";
|
|
import { Resource } from "./Resource";
|
|
import type { ResourceResult } from "./resourceResult/ResourceResult";
|
|
|
|
export class Container extends Resource {
|
|
readonly uri: ContainerUri;
|
|
protected requester: ContainerRequester;
|
|
protected rootContainer: boolean | undefined;
|
|
readonly type = "container" as const;
|
|
readonly isError = false as const;
|
|
status:
|
|
| SharedStatuses
|
|
| ReadContainerResult
|
|
| ContainerCreateAndOverwriteResult
|
|
| ContainerCreateIfAbsentResult
|
|
| CheckRootResult;
|
|
|
|
constructor(uri: ContainerUri, context: SolidLdoDatasetContext) {
|
|
super(context);
|
|
this.uri = uri;
|
|
this.requester = new ContainerRequester(uri, context);
|
|
this.status = { isError: false, type: "unfetched", uri };
|
|
}
|
|
|
|
isRootContainer(): boolean | undefined {
|
|
return this.rootContainer;
|
|
}
|
|
|
|
// Read Methods
|
|
protected updateWithReadSuccess(
|
|
result: ContainerReadSuccess | AbsentReadSuccess,
|
|
): void {
|
|
if (result.type === "containerReadSuccess") {
|
|
this.rootContainer = result.isRootContainer;
|
|
}
|
|
}
|
|
|
|
async read(): Promise<ResourceResult<ReadContainerResult, Container>> {
|
|
const result = (await this.handleRead()) as ReadContainerResult;
|
|
if (result.isError) return result;
|
|
return { ...result, resource: this };
|
|
}
|
|
|
|
protected toReadResult(): ResourceResult<ReadContainerResult, Container> {
|
|
if (this.isAbsent()) {
|
|
return {
|
|
isError: false,
|
|
type: "absentReadSuccess",
|
|
uri: this.uri,
|
|
recalledFromMemory: true,
|
|
resource: this,
|
|
};
|
|
} else {
|
|
return {
|
|
isError: false,
|
|
type: "containerReadSuccess",
|
|
uri: this.uri,
|
|
recalledFromMemory: true,
|
|
isRootContainer: this.isRootContainer()!,
|
|
resource: this,
|
|
};
|
|
}
|
|
}
|
|
|
|
async readIfUnfetched(): Promise<
|
|
ResourceResult<ReadContainerResult, Container>
|
|
> {
|
|
return super.readIfUnfetched() as Promise<
|
|
ResourceResult<ReadContainerResult, Container>
|
|
>;
|
|
}
|
|
|
|
// Parent Container Methods
|
|
private async checkIfIsRootContainer(): Promise<
|
|
ResourceResult<CheckRootResult, Container>
|
|
> {
|
|
const rootContainerResult = await this.requester.isRootContainer();
|
|
this.status = rootContainerResult;
|
|
if (rootContainerResult.isError) return rootContainerResult;
|
|
this.rootContainer = rootContainerResult.isRootContainer;
|
|
this.emit("update");
|
|
return { ...rootContainerResult, resource: this };
|
|
}
|
|
|
|
async getRootContainer(): Promise<Container | CheckRootResultError> {
|
|
const checkResult = await this.checkIfIsRootContainer();
|
|
if (checkResult.isError) return checkResult;
|
|
if (this.rootContainer) {
|
|
return this;
|
|
}
|
|
const parentUri = getParentUri(this.uri);
|
|
if (!parentUri) {
|
|
return new NoncompliantPodError(
|
|
this.uri,
|
|
"Resource does not have a root container",
|
|
);
|
|
}
|
|
return this.context.resourceStore.get(parentUri).getRootContainer();
|
|
}
|
|
|
|
async getParentContainer(): Promise<
|
|
Container | CheckRootResultError | undefined
|
|
> {
|
|
const checkResult = await this.checkIfIsRootContainer();
|
|
if (checkResult.isError) return checkResult;
|
|
if (this.rootContainer) return undefined;
|
|
const parentUri = getParentUri(this.uri);
|
|
if (!parentUri) {
|
|
return new NoncompliantPodError(
|
|
this.uri,
|
|
`${this.uri} is not root does not have a parent container`,
|
|
);
|
|
}
|
|
return this.context.resourceStore.get(parentUri);
|
|
}
|
|
|
|
children(): (Leaf | Container)[] {
|
|
const childQuads = this.context.solidLdoDataset.match(
|
|
namedNode(this.uri),
|
|
ldpContains,
|
|
null,
|
|
namedNode(this.uri),
|
|
);
|
|
return childQuads.toArray().map((childQuad) => {
|
|
return this.context.resourceStore.get(childQuad.object.value);
|
|
});
|
|
}
|
|
|
|
child(slug: ContainerUri): Container;
|
|
child(slug: LeafUri): Leaf;
|
|
child(slug: string): Leaf | Container;
|
|
child(slug: string): Leaf | Container {
|
|
return this.context.resourceStore.get(`${this.uri}${slug}`);
|
|
}
|
|
|
|
// Child Creators
|
|
createChildAndOverwrite(
|
|
slug: ContainerUri,
|
|
): Promise<ResourceResult<ContainerCreateAndOverwriteResult, Container>>;
|
|
createChildAndOverwrite(
|
|
slug: LeafUri,
|
|
): Promise<ResourceResult<LeafCreateAndOverwriteResult, Leaf>>;
|
|
createChildAndOverwrite(
|
|
slug: string,
|
|
): Promise<
|
|
ResourceResult<
|
|
ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult,
|
|
Leaf | Container
|
|
>
|
|
>;
|
|
createChildAndOverwrite(
|
|
slug: string,
|
|
): Promise<
|
|
ResourceResult<
|
|
ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult,
|
|
Leaf | Container
|
|
>
|
|
> {
|
|
return this.child(slug).createAndOverwrite();
|
|
}
|
|
|
|
createChildIfAbsent(
|
|
slug: ContainerUri,
|
|
): Promise<ResourceResult<ContainerCreateIfAbsentResult, Container>>;
|
|
createChildIfAbsent(
|
|
slug: LeafUri,
|
|
): Promise<ResourceResult<LeafCreateIfAbsentResult, Leaf>>;
|
|
createChildIfAbsent(
|
|
slug: string,
|
|
): Promise<
|
|
ResourceResult<
|
|
ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult,
|
|
Leaf | Container
|
|
>
|
|
>;
|
|
createChildIfAbsent(
|
|
slug: string,
|
|
): Promise<
|
|
ResourceResult<
|
|
ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult,
|
|
Leaf | Container
|
|
>
|
|
> {
|
|
return this.child(slug).createIfAbsent();
|
|
}
|
|
|
|
async uploadChildAndOverwrite(
|
|
slug: LeafUri,
|
|
blob: Blob,
|
|
mimeType: string,
|
|
): Promise<ResourceResult<LeafCreateAndOverwriteResult, Leaf>> {
|
|
return this.child(slug).uploadAndOverwrite(blob, mimeType);
|
|
}
|
|
|
|
async uploadChildIfAbsent(
|
|
slug: LeafUri,
|
|
blob: Blob,
|
|
mimeType: string,
|
|
): Promise<ResourceResult<LeafCreateIfAbsentResult, Leaf>> {
|
|
return this.child(slug).uploadIfAbsent(blob, mimeType);
|
|
}
|
|
|
|
async clear(): Promise<
|
|
ResourceResult<
|
|
| AggregateSuccess<ResourceResult<DeleteSuccess, Container | Leaf>>
|
|
| AggregateError<DeleteResultError | ReadResultError>,
|
|
Container
|
|
>
|
|
> {
|
|
const readResult = await this.read();
|
|
if (readResult.isError) return new AggregateError([readResult]);
|
|
const results = (
|
|
await Promise.all(
|
|
this.children().map(async (child) => {
|
|
return child.delete();
|
|
}),
|
|
)
|
|
).flat();
|
|
const errors = results.filter(
|
|
(
|
|
value,
|
|
): value is
|
|
| DeleteResultError
|
|
| AggregateError<DeleteResultError | ReadResultError> => value.isError,
|
|
);
|
|
if (errors.length > 0) {
|
|
return new AggregateError(errors);
|
|
}
|
|
return {
|
|
isError: false,
|
|
type: "aggregateSuccess",
|
|
results: results as ResourceResult<DeleteSuccess, Container | Leaf>[],
|
|
resource: this,
|
|
};
|
|
}
|
|
|
|
async delete(): Promise<
|
|
ResourceResult<
|
|
DeleteResult | AggregateError<DeleteResultError | ReadResultError>,
|
|
Container
|
|
>
|
|
> {
|
|
const clearResult = await this.clear();
|
|
if (clearResult.isError) return clearResult;
|
|
const deleteResult = await this.handleDelete();
|
|
if (deleteResult.isError) return deleteResult;
|
|
return { ...deleteResult, resource: this };
|
|
}
|
|
|
|
async createAndOverwrite(): Promise<
|
|
ResourceResult<ContainerCreateAndOverwriteResult, Container>
|
|
> {
|
|
const createResult =
|
|
(await this.handleCreateAndOverwrite()) as ContainerCreateAndOverwriteResult;
|
|
if (createResult.isError) return createResult;
|
|
return { ...createResult, resource: this };
|
|
}
|
|
|
|
async createIfAbsent(): Promise<
|
|
ResourceResult<ContainerCreateIfAbsentResult, Container>
|
|
> {
|
|
const createResult =
|
|
(await this.handleCreateIfAbsent()) as ContainerCreateIfAbsentResult;
|
|
if (createResult.isError) return createResult;
|
|
return { ...createResult, resource: this };
|
|
}
|
|
}
|
|
|