Skip to content

Commit 71e36d1

Browse files
committed
Cache source archive list inside DataBaseItemImpl
1 parent 2e384c3 commit 71e36d1

4 files changed

Lines changed: 95 additions & 61 deletions

File tree

extensions/ql-vscode/src/databases/local-databases/database-item-impl.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Exported for testing
22
import type { CodeQLCliServer, DbInfo } from "../../codeql-cli/cli";
3-
import { Uri, workspace } from "vscode";
3+
import { FileType, Uri, workspace } from "vscode";
44
import type { FullDatabaseOptions } from "./database-options";
55
import { basename, dirname, extname, join } from "path";
66
import {
@@ -9,7 +9,11 @@ import {
99
encodeSourceArchiveUri,
1010
zipArchiveScheme,
1111
} from "../../common/vscode/archive-filesystem-provider";
12-
import type { DatabaseItem, PersistedDatabaseItem } from "./database-item";
12+
import type {
13+
DatabaseItem,
14+
PersistedDatabaseItem,
15+
SourceArchiveFile,
16+
} from "./database-item";
1317
import { isLikelyDatabaseRoot } from "./db-contents-heuristics";
1418
import { stat } from "fs-extra";
1519
import { containsPath, pathsEqual } from "../../common/files";
@@ -22,6 +26,8 @@ export class DatabaseItemImpl implements DatabaseItem {
2226
public contents: DatabaseContents | undefined;
2327
/** A cache of database info */
2428
private _dbinfo: DbInfo | undefined;
29+
/** A cache of source archive files */
30+
private _sourceArchiveFiles: SourceArchiveFile[] | undefined;
2531

2632
public constructor(
2733
public readonly databaseUri: Uri,
@@ -234,4 +240,66 @@ export class DatabaseItemImpl implements DatabaseItem {
234240
return false;
235241
}
236242
}
243+
244+
public async getSourceArchiveFiles(): Promise<SourceArchiveFile[]> {
245+
if (this._sourceArchiveFiles === undefined) {
246+
this._sourceArchiveFiles = await this.collectSourceArchiveFiles();
247+
}
248+
return this._sourceArchiveFiles;
249+
}
250+
251+
private async collectSourceArchiveFiles(): Promise<SourceArchiveFile[]> {
252+
const explorerUri = this.getSourceArchiveExplorerUri();
253+
const sourceArchiveZipPath =
254+
decodeSourceArchiveUri(explorerUri).sourceArchiveZipPath;
255+
256+
const items: SourceArchiveFile[] = [];
257+
await this.collectFilesRecursive(
258+
explorerUri,
259+
sourceArchiveZipPath,
260+
"",
261+
items,
262+
);
263+
// Sort by file name, then by path
264+
items.sort((a, b) => {
265+
const nameCmp = a.name.localeCompare(b.name);
266+
if (nameCmp !== 0) {
267+
return nameCmp;
268+
}
269+
return a.path.localeCompare(b.path);
270+
});
271+
return items;
272+
}
273+
274+
private async collectFilesRecursive(
275+
dirUri: Uri,
276+
sourceArchiveZipPath: string,
277+
prefix: string,
278+
items: SourceArchiveFile[],
279+
): Promise<void> {
280+
const entries = await workspace.fs.readDirectory(dirUri);
281+
282+
for (const [name, type] of entries) {
283+
const childPath = prefix ? `${prefix}/${name}` : name;
284+
const childUri = encodeSourceArchiveUri({
285+
sourceArchiveZipPath,
286+
pathWithinSourceArchive: `${decodeSourceArchiveUri(dirUri).pathWithinSourceArchive}/${name}`,
287+
});
288+
289+
if (type === FileType.File) {
290+
items.push({
291+
name,
292+
path: prefix,
293+
uri: childUri,
294+
});
295+
} else if (type === FileType.Directory) {
296+
await this.collectFilesRecursive(
297+
childUri,
298+
sourceArchiveZipPath,
299+
childPath,
300+
items,
301+
);
302+
}
303+
}
304+
}
237305
}

extensions/ql-vscode/src/databases/local-databases/database-item.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ import type { DatabaseContents } from "./database-contents";
44
import type { DatabaseOptions } from "./database-options";
55
import type { DatabaseOrigin } from "./database-origin";
66

7+
/** A file entry from the database's source archive. */
8+
export interface SourceArchiveFile {
9+
/** The file name (basename). */
10+
name: string;
11+
/** The path prefix (directory path relative to the source archive root). */
12+
path: string;
13+
/** The URI that can be used to open the file. */
14+
uri: Uri;
15+
}
16+
717
/** An item in the list of available databases */
818
export interface DatabaseItem {
919
/** The URI of the database */
@@ -92,6 +102,12 @@ export interface DatabaseItem {
92102
* Verifies that this database item has a zipped source folder. Returns an error message if it does not.
93103
*/
94104
verifyZippedSources(): string | undefined;
105+
106+
/**
107+
* Returns all files in the database's source archive.
108+
* The result is lazily computed and cached.
109+
*/
110+
getSourceArchiveFiles(): Promise<SourceArchiveFile[]>;
95111
}
96112

97113
export interface PersistedDatabaseItem {

extensions/ql-vscode/src/databases/local-databases/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export {
44
DatabaseKind,
55
} from "./database-contents";
66
export { DatabaseChangedEvent, DatabaseEventKind } from "./database-events";
7-
export { DatabaseItem } from "./database-item";
7+
export { DatabaseItem, SourceArchiveFile } from "./database-item";
88
export { DatabaseItemImpl } from "./database-item-impl";
99
export { DatabaseManager } from "./database-manager";
1010
export { DatabaseResolver } from "./database-resolver";

extensions/ql-vscode/src/databases/source-archive-file-search.ts

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,19 @@
11
import type { QuickPickItem, Uri } from "vscode";
2-
import { FileType, window, workspace } from "vscode";
2+
import { window, workspace } from "vscode";
33
import type { DatabaseItem } from "./local-databases";
4-
import {
5-
encodeSourceArchiveUri,
6-
decodeSourceArchiveUri,
7-
} from "../common/vscode/archive-filesystem-provider";
84

95
interface SourceArchiveFileQuickPickItem extends QuickPickItem {
106
uri: Uri;
117
}
128

13-
/**
14-
* Recursively collects all file URIs from a source archive directory.
15-
*/
16-
async function collectFiles(
17-
dirUri: Uri,
18-
sourceArchiveZipPath: string,
19-
prefix: string,
20-
items: SourceArchiveFileQuickPickItem[] = [],
21-
): Promise<SourceArchiveFileQuickPickItem[]> {
22-
const entries = await workspace.fs.readDirectory(dirUri);
23-
24-
for (const [name, type] of entries) {
25-
const childPath = prefix ? `${prefix}/${name}` : name;
26-
const childUri = encodeSourceArchiveUri({
27-
sourceArchiveZipPath,
28-
pathWithinSourceArchive: `${decodeSourceArchiveUri(dirUri).pathWithinSourceArchive}/${name}`,
29-
});
30-
31-
if (type === FileType.File) {
32-
items.push({
33-
label: name,
34-
description: prefix,
35-
uri: childUri,
36-
});
37-
} else if (type === FileType.Directory) {
38-
await collectFiles(childUri, sourceArchiveZipPath, childPath, items);
39-
}
40-
}
41-
42-
return items;
43-
}
44-
459
/**
4610
* Shows a Quick Pick to search for and open a file from the source archive
4711
* of the given database.
4812
*/
4913
export async function searchSourceArchiveFiles(
5014
databaseItem: DatabaseItem,
5115
): Promise<void> {
52-
let explorerUri: Uri;
53-
try {
54-
explorerUri = databaseItem.getSourceArchiveExplorerUri();
55-
} catch (e) {
56-
void window.showErrorMessage(e instanceof Error ? e.message : String(e));
57-
return;
58-
}
59-
const sourceArchiveZipPath =
60-
decodeSourceArchiveUri(explorerUri).sourceArchiveZipPath;
61-
62-
const filesPromise = collectFiles(explorerUri, sourceArchiveZipPath, "");
16+
const filesPromise = databaseItem.getSourceArchiveFiles();
6317

6418
const quickPick = window.createQuickPick<SourceArchiveFileQuickPickItem>();
6519
quickPick.placeholder = "Go to File in Selected Database...";
@@ -68,16 +22,12 @@ export async function searchSourceArchiveFiles(
6822
quickPick.show();
6923

7024
try {
71-
const items = await filesPromise;
72-
// Sort items by file name, then by path
73-
items.sort((a, b) => {
74-
const nameCmp = a.label.localeCompare(b.label);
75-
if (nameCmp !== 0) {
76-
return nameCmp;
77-
}
78-
return (a.description ?? "").localeCompare(b.description ?? "");
79-
});
80-
quickPick.items = items;
25+
const files = await filesPromise;
26+
quickPick.items = files.map((f) => ({
27+
label: f.name,
28+
description: f.path,
29+
uri: f.uri,
30+
}));
8131
quickPick.busy = false;
8232
} catch (e) {
8333
quickPick.dispose();

0 commit comments

Comments
 (0)