Files
arduino-ide/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts
Akos Kitta 0ab28266df feat: introduced cloud state in sketchbook view
Closes #1879
Closes #1876
Closes #1899
Closes #1878

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2023-03-16 10:00:17 +01:00

196 lines
6.2 KiB
TypeScript

import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { CompositeTreeNode, TreeNode } from '@theia/core/lib/browser/tree';
import { posixSegments, splitSketchPath } from '../../create/create-paths';
import { CreateApi } from '../../create/create-api';
import { CloudSketchbookTree } from './cloud-sketchbook-tree';
import { AuthenticationClientService } from '../../auth/authentication-client-service';
import { SketchbookTreeModel } from '../sketchbook/sketchbook-tree-model';
import { WorkspaceNode } from '@theia/navigator/lib/browser/navigator-tree';
import { CreateUri } from '../../create/create-uri';
import { FileChangesEvent, FileStat } from '@theia/filesystem/lib/common/files';
import {
LocalCacheFsProvider,
LocalCacheUri,
} from '../../local-cache/local-cache-fs-provider';
import URI from '@theia/core/lib/common/uri';
import { Create } from '../../create/typings';
import { nls } from '@theia/core/lib/common/nls';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { ApplicationConnectionStatusContribution } from '../../theia/core/connection-status-service';
function sketchBaseDir(sketch: Create.Sketch): FileStat {
// extract the sketch path
const [, path] = splitSketchPath(sketch.path);
const dirs = posixSegments(path);
const mtime = Date.parse(sketch.modified_at);
const ctime = Date.parse(sketch.created_at);
const createPath = CreateUri.toUri(dirs[0]);
const baseDir: FileStat = {
name: dirs[0],
isDirectory: true,
isFile: false,
isSymbolicLink: false,
resource: createPath,
mtime,
ctime,
};
return baseDir;
}
function sketchesToFileStats(sketches: Create.Sketch[]): FileStat[] {
const sketchesBaseDirs: Record<string, FileStat> = {};
for (const sketch of sketches) {
const sketchBaseDirFileStat = sketchBaseDir(sketch);
sketchesBaseDirs[sketchBaseDirFileStat.resource.toString()] =
sketchBaseDirFileStat;
}
return Object.keys(sketchesBaseDirs).map(
(dirUri) => sketchesBaseDirs[dirUri]
);
}
@injectable()
export class CloudSketchbookTreeModel extends SketchbookTreeModel {
@inject(CreateApi)
private readonly createApi: CreateApi;
@inject(AuthenticationClientService)
private readonly authenticationService: AuthenticationClientService;
@inject(LocalCacheFsProvider)
private readonly localCacheFsProvider: LocalCacheFsProvider;
@inject(ApplicationConnectionStatusContribution)
private readonly connectionStatus: ApplicationConnectionStatusContribution;
private _localCacheFsProviderReady: Deferred<void> | undefined;
@postConstruct()
protected override init(): void {
super.init();
this.toDispose.pushAll([
this.authenticationService.onSessionDidChange(() => this.updateRoot()),
this.connectionStatus.onOfflineStatusDidChange((offlineStatus) => {
if (!offlineStatus) {
this.updateRoot();
}
}),
]);
}
override *getNodesByUri(uri: URI): IterableIterator<TreeNode> {
if (uri.scheme === LocalCacheUri.scheme) {
const workspace = this.root;
const { session } = this.authenticationService;
if (session && WorkspaceNode.is(workspace)) {
const currentUri = this.localCacheFsProvider.to(uri);
if (currentUri) {
const rootPath = this.localCacheFsProvider
.toUri(session)
.path.toString();
const currentPath = currentUri.path.toString();
if (rootPath === currentPath) {
return workspace;
}
if (currentPath.startsWith(rootPath)) {
const id = currentPath.substring(rootPath.length);
const node = this.getNode(id);
if (node) {
yield node;
}
}
}
}
}
}
protected override isRootAffected(changes: FileChangesEvent): boolean {
return changes.changes
.map(({ resource }) => resource)
.some(
(uri) => uri.parent.toString().startsWith(LocalCacheUri.root.toString()) // all files under the root might affect the tree
);
}
override async refresh(
parent?: Readonly<CompositeTreeNode>
): Promise<CompositeTreeNode | undefined> {
if (parent) {
return super.refresh(parent);
}
await this.updateRoot();
return super.refresh();
}
override async createRoot(): Promise<TreeNode | undefined> {
const { session } = this.authenticationService;
if (!session) {
this.tree.root = undefined;
return;
}
this.createApi.sketchCache.init();
const [sketches] = await Promise.all([
this.createApi.sketches(),
this.ensureLocalFsProviderReady(),
]);
const rootFileStats = sketchesToFileStats(sketches);
if (this.workspaceService.opened) {
const workspaceNode = WorkspaceNode.createRoot(
nls.localize('arduino/cloud/remote', 'Remote')
);
for await (const stat of rootFileStats) {
workspaceNode.children.push(
await this.tree.createWorkspaceRoot(stat, workspaceNode)
);
}
return workspaceNode;
}
}
sketchbookTree(): CloudSketchbookTree {
return this.tree as CloudSketchbookTree;
}
protected override recursivelyFindSketchRoot(
node: TreeNode
): TreeNode | false {
if (node && CloudSketchbookTree.CloudSketchDirNode.is(node)) {
return node;
}
if (node && node.parent) {
return this.recursivelyFindSketchRoot(node.parent);
}
// can't find a root, return false
return false;
}
override async revealFile(uri: URI): Promise<TreeNode | undefined> {
await this.localCacheFsProvider.ready.promise;
// we use remote uris as keys for the tree
// convert local URIs
const remoteUri = this.localCacheFsProvider.from(uri);
if (remoteUri) {
return super.revealFile(remoteUri);
} else {
return super.revealFile(uri);
}
}
private async ensureLocalFsProviderReady(): Promise<void> {
if (this._localCacheFsProviderReady) {
return this._localCacheFsProviderReady.promise;
}
this._localCacheFsProviderReady = new Deferred();
this.fileService
.access(LocalCacheUri.root)
.then(() => this._localCacheFsProviderReady?.resolve());
return this._localCacheFsProviderReady.promise;
}
}