Skip to content

Commit b8557d3

Browse files
authored
Merge pull request #2445 from github/koesie10/move-local-databases
Split up `local-databases.ts` file into multiple files
2 parents ff88a8d + 0707155 commit b8557d3

9 files changed

Lines changed: 578 additions & 558 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import vscode from "vscode";
2+
3+
/**
4+
* The layout of the database.
5+
*/
6+
export enum DatabaseKind {
7+
/** A CodeQL database */
8+
Database,
9+
/** A raw QL dataset */
10+
RawDataset,
11+
}
12+
13+
export interface DatabaseContents {
14+
/** The layout of the database */
15+
kind: DatabaseKind;
16+
/**
17+
* The name of the database.
18+
*/
19+
name: string;
20+
/** The URI of the QL dataset within the database. */
21+
datasetUri: vscode.Uri;
22+
/** The URI of the source archive within the database, if one exists. */
23+
sourceArchiveUri?: vscode.Uri;
24+
/** The URI of the CodeQL database scheme within the database, if exactly one exists. */
25+
dbSchemeUri?: vscode.Uri;
26+
}
27+
28+
export interface DatabaseContentsWithDbScheme extends DatabaseContents {
29+
dbSchemeUri: vscode.Uri; // Always present
30+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { DatabaseItem } from "./database-item";
2+
3+
export enum DatabaseEventKind {
4+
Add = "Add",
5+
Remove = "Remove",
6+
7+
// Fired when databases are refreshed from persisted state
8+
Refresh = "Refresh",
9+
10+
// Fired when the current database changes
11+
Change = "Change",
12+
13+
Rename = "Rename",
14+
}
15+
16+
export interface DatabaseChangedEvent {
17+
kind: DatabaseEventKind;
18+
item: DatabaseItem | undefined;
19+
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Exported for testing
2+
import * as cli from "../../codeql-cli/cli";
3+
import vscode from "vscode";
4+
import { FullDatabaseOptions } from "./database-options";
5+
import { basename, dirname, join, relative } from "path";
6+
import { asError } from "../../pure/helpers-pure";
7+
import {
8+
decodeSourceArchiveUri,
9+
encodeArchiveBasePath,
10+
encodeSourceArchiveUri,
11+
zipArchiveScheme,
12+
} from "../../common/vscode/archive-filesystem-provider";
13+
import { DatabaseItem, PersistedDatabaseItem } from "./database-item";
14+
import { isLikelyDatabaseRoot } from "../../helpers";
15+
import { stat } from "fs-extra";
16+
import { pathsEqual } from "../../pure/files";
17+
import { DatabaseContents } from "./database-contents";
18+
import { DatabaseResolver } from "./database-resolver";
19+
import { DatabaseChangedEvent, DatabaseEventKind } from "./database-events";
20+
21+
export class DatabaseItemImpl implements DatabaseItem {
22+
private _error: Error | undefined = undefined;
23+
private _contents: DatabaseContents | undefined;
24+
/** A cache of database info */
25+
private _dbinfo: cli.DbInfo | undefined;
26+
27+
public constructor(
28+
public readonly databaseUri: vscode.Uri,
29+
contents: DatabaseContents | undefined,
30+
private options: FullDatabaseOptions,
31+
private readonly onChanged: (event: DatabaseChangedEvent) => void,
32+
) {
33+
this._contents = contents;
34+
}
35+
36+
public get name(): string {
37+
if (this.options.displayName) {
38+
return this.options.displayName;
39+
} else if (this._contents) {
40+
return this._contents.name;
41+
} else {
42+
return basename(this.databaseUri.fsPath);
43+
}
44+
}
45+
46+
public set name(newName: string) {
47+
this.options.displayName = newName;
48+
}
49+
50+
public get sourceArchive(): vscode.Uri | undefined {
51+
if (this.options.ignoreSourceArchive || this._contents === undefined) {
52+
return undefined;
53+
} else {
54+
return this._contents.sourceArchiveUri;
55+
}
56+
}
57+
58+
public get contents(): DatabaseContents | undefined {
59+
return this._contents;
60+
}
61+
62+
public get dateAdded(): number | undefined {
63+
return this.options.dateAdded;
64+
}
65+
66+
public get error(): Error | undefined {
67+
return this._error;
68+
}
69+
70+
public async refresh(): Promise<void> {
71+
try {
72+
try {
73+
this._contents = await DatabaseResolver.resolveDatabaseContents(
74+
this.databaseUri,
75+
);
76+
this._error = undefined;
77+
} catch (e) {
78+
this._contents = undefined;
79+
this._error = asError(e);
80+
throw e;
81+
}
82+
} finally {
83+
this.onChanged({
84+
kind: DatabaseEventKind.Refresh,
85+
item: this,
86+
});
87+
}
88+
}
89+
90+
public resolveSourceFile(uriStr: string | undefined): vscode.Uri {
91+
const sourceArchive = this.sourceArchive;
92+
const uri = uriStr ? vscode.Uri.parse(uriStr, true) : undefined;
93+
if (uri && uri.scheme !== "file") {
94+
throw new Error(
95+
`Invalid uri scheme in ${uriStr}. Only 'file' is allowed.`,
96+
);
97+
}
98+
if (!sourceArchive) {
99+
if (uri) {
100+
return uri;
101+
} else {
102+
return this.databaseUri;
103+
}
104+
}
105+
106+
if (uri) {
107+
const relativeFilePath = decodeURI(uri.path)
108+
.replace(":", "_")
109+
.replace(/^\/*/, "");
110+
if (sourceArchive.scheme === zipArchiveScheme) {
111+
const zipRef = decodeSourceArchiveUri(sourceArchive);
112+
const pathWithinSourceArchive =
113+
zipRef.pathWithinSourceArchive === "/"
114+
? relativeFilePath
115+
: `${zipRef.pathWithinSourceArchive}/${relativeFilePath}`;
116+
return encodeSourceArchiveUri({
117+
pathWithinSourceArchive,
118+
sourceArchiveZipPath: zipRef.sourceArchiveZipPath,
119+
});
120+
} else {
121+
let newPath = sourceArchive.path;
122+
if (!newPath.endsWith("/")) {
123+
// Ensure a trailing slash.
124+
newPath += "/";
125+
}
126+
newPath += relativeFilePath;
127+
128+
return sourceArchive.with({ path: newPath });
129+
}
130+
} else {
131+
return sourceArchive;
132+
}
133+
}
134+
135+
/**
136+
* Gets the state of this database, to be persisted in the workspace state.
137+
*/
138+
public getPersistedState(): PersistedDatabaseItem {
139+
return {
140+
uri: this.databaseUri.toString(true),
141+
options: this.options,
142+
};
143+
}
144+
145+
/**
146+
* Holds if the database item refers to an exported snapshot
147+
*/
148+
public async hasMetadataFile(): Promise<boolean> {
149+
return await isLikelyDatabaseRoot(this.databaseUri.fsPath);
150+
}
151+
152+
/**
153+
* Returns information about a database.
154+
*/
155+
private async getDbInfo(server: cli.CodeQLCliServer): Promise<cli.DbInfo> {
156+
if (this._dbinfo === undefined) {
157+
this._dbinfo = await server.resolveDatabase(this.databaseUri.fsPath);
158+
}
159+
return this._dbinfo;
160+
}
161+
162+
/**
163+
* Returns `sourceLocationPrefix` of database. Requires that the database
164+
* has a `.dbinfo` file, which is the source of the prefix.
165+
*/
166+
public async getSourceLocationPrefix(
167+
server: cli.CodeQLCliServer,
168+
): Promise<string> {
169+
const dbInfo = await this.getDbInfo(server);
170+
return dbInfo.sourceLocationPrefix;
171+
}
172+
173+
/**
174+
* Returns path to dataset folder of database.
175+
*/
176+
public async getDatasetFolder(server: cli.CodeQLCliServer): Promise<string> {
177+
const dbInfo = await this.getDbInfo(server);
178+
return dbInfo.datasetFolder;
179+
}
180+
181+
public get language() {
182+
return this.options.language || "";
183+
}
184+
185+
/**
186+
* Returns the root uri of the virtual filesystem for this database's source archive.
187+
*/
188+
public getSourceArchiveExplorerUri(): vscode.Uri {
189+
const sourceArchive = this.sourceArchive;
190+
if (sourceArchive === undefined || !sourceArchive.fsPath.endsWith(".zip")) {
191+
throw new Error(this.verifyZippedSources());
192+
}
193+
return encodeArchiveBasePath(sourceArchive.fsPath);
194+
}
195+
196+
public verifyZippedSources(): string | undefined {
197+
const sourceArchive = this.sourceArchive;
198+
if (sourceArchive === undefined) {
199+
return `${this.name} has no source archive.`;
200+
}
201+
202+
if (!sourceArchive.fsPath.endsWith(".zip")) {
203+
return `${this.name} has a source folder that is unzipped.`;
204+
}
205+
return;
206+
}
207+
208+
/**
209+
* Holds if `uri` belongs to this database's source archive.
210+
*/
211+
public belongsToSourceArchiveExplorerUri(uri: vscode.Uri): boolean {
212+
if (this.sourceArchive === undefined) return false;
213+
return (
214+
uri.scheme === zipArchiveScheme &&
215+
decodeSourceArchiveUri(uri).sourceArchiveZipPath ===
216+
this.sourceArchive.fsPath
217+
);
218+
}
219+
220+
public async isAffectedByTest(testPath: string): Promise<boolean> {
221+
const databasePath = this.databaseUri.fsPath;
222+
if (!databasePath.endsWith(".testproj")) {
223+
return false;
224+
}
225+
try {
226+
const stats = await stat(testPath);
227+
if (stats.isDirectory()) {
228+
return !relative(testPath, databasePath).startsWith("..");
229+
} else {
230+
// database for /one/two/three/test.ql is at /one/two/three/three.testproj
231+
const testdir = dirname(testPath);
232+
const testdirbase = basename(testdir);
233+
return pathsEqual(
234+
databasePath,
235+
join(testdir, `${testdirbase}.testproj`),
236+
process.platform,
237+
);
238+
}
239+
} catch {
240+
// No information available for test path - assume database is unaffected.
241+
return false;
242+
}
243+
}
244+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import vscode from "vscode";
2+
import * as cli from "../../codeql-cli/cli";
3+
import { DatabaseContents } from "./database-contents";
4+
import { DatabaseOptions } from "./database-options";
5+
6+
/** An item in the list of available databases */
7+
export interface DatabaseItem {
8+
/** The URI of the database */
9+
readonly databaseUri: vscode.Uri;
10+
/** The name of the database to be displayed in the UI */
11+
name: string;
12+
13+
/** The primary language of the database or empty string if unknown */
14+
readonly language: string;
15+
/** The URI of the database's source archive, or `undefined` if no source archive is to be used. */
16+
readonly sourceArchive: vscode.Uri | undefined;
17+
/**
18+
* The contents of the database.
19+
* Will be `undefined` if the database is invalid. Can be updated by calling `refresh()`.
20+
*/
21+
readonly contents: DatabaseContents | undefined;
22+
23+
/**
24+
* The date this database was added as a unix timestamp. Or undefined if we don't know.
25+
*/
26+
readonly dateAdded: number | undefined;
27+
28+
/** If the database is invalid, describes why. */
29+
readonly error: Error | undefined;
30+
/**
31+
* Resolves the contents of the database.
32+
*
33+
* @remarks
34+
* The contents include the database directory, source archive, and metadata about the database.
35+
* If the database is invalid, `this.error` is updated with the error object that describes why
36+
* the database is invalid. This error is also thrown.
37+
*/
38+
refresh(): Promise<void>;
39+
/**
40+
* Resolves a filename to its URI in the source archive.
41+
*
42+
* @param file Filename within the source archive. May be `undefined` to return a dummy file path.
43+
*/
44+
resolveSourceFile(file: string | undefined): vscode.Uri;
45+
46+
/**
47+
* Holds if the database item has a `.dbinfo` or `codeql-database.yml` file.
48+
*/
49+
hasMetadataFile(): Promise<boolean>;
50+
51+
/**
52+
* Returns `sourceLocationPrefix` of exported database.
53+
*/
54+
getSourceLocationPrefix(server: cli.CodeQLCliServer): Promise<string>;
55+
56+
/**
57+
* Returns dataset folder of exported database.
58+
*/
59+
getDatasetFolder(server: cli.CodeQLCliServer): Promise<string>;
60+
61+
/**
62+
* Returns the root uri of the virtual filesystem for this database's source archive,
63+
* as displayed in the filesystem explorer.
64+
*/
65+
getSourceArchiveExplorerUri(): vscode.Uri;
66+
67+
/**
68+
* Holds if `uri` belongs to this database's source archive.
69+
*/
70+
belongsToSourceArchiveExplorerUri(uri: vscode.Uri): boolean;
71+
72+
/**
73+
* Whether the database may be affected by test execution for the given path.
74+
*/
75+
isAffectedByTest(testPath: string): Promise<boolean>;
76+
77+
/**
78+
* Gets the state of this database, to be persisted in the workspace state.
79+
*/
80+
getPersistedState(): PersistedDatabaseItem;
81+
82+
/**
83+
* Verifies that this database item has a zipped source folder. Returns an error message if it does not.
84+
*/
85+
verifyZippedSources(): string | undefined;
86+
}
87+
88+
export interface PersistedDatabaseItem {
89+
uri: string;
90+
options?: DatabaseOptions;
91+
}

0 commit comments

Comments
 (0)