mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-08 19:08:33 +00:00

Closes #1879 Closes #1876 Closes #1899 Closes #1878 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
196 lines
6.2 KiB
TypeScript
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;
|
|
}
|
|
}
|