@@ -16,6 +16,7 @@ import {
1616 ThemeIcon ,
1717 ThemeColor ,
1818 workspace ,
19+ FileType ,
1920} from "vscode" ;
2021import { pathExists , stat , readdir , remove } from "fs-extra" ;
2122
@@ -36,6 +37,7 @@ import {
3637import {
3738 showAndLogExceptionWithTelemetry ,
3839 showAndLogErrorMessage ,
40+ showAndLogInformationMessage ,
3941} from "../common/logging" ;
4042import type { DatabaseFetcher } from "./database-fetcher" ;
4143import { asError , asyncFilter , getErrorMessage } from "../common/helpers-pure" ;
@@ -267,6 +269,8 @@ export class DatabaseUI extends DisposableObject {
267269 "codeQL.getCurrentDatabase" : this . handleGetCurrentDatabase . bind ( this ) ,
268270 "codeQL.chooseDatabaseFolder" :
269271 this . handleChooseDatabaseFolderFromPalette . bind ( this ) ,
272+ "codeQL.chooseDatabaseFoldersParent" :
273+ this . handleChooseDatabaseFoldersParentFromPalette . bind ( this ) ,
270274 "codeQL.chooseDatabaseArchive" :
271275 this . handleChooseDatabaseArchiveFromPalette . bind ( this ) ,
272276 "codeQL.chooseDatabaseInternet" :
@@ -359,6 +363,12 @@ export class DatabaseUI extends DisposableObject {
359363 ) ;
360364 }
361365
366+ private async handleChooseDatabaseFoldersParentFromPalette ( ) : Promise < void > {
367+ return withProgress ( async ( progress ) => {
368+ await this . chooseDatabasesParentFolder ( progress ) ;
369+ } ) ;
370+ }
371+
362372 private async handleSetDefaultTourDatabase ( ) : Promise < void > {
363373 return withProgress (
364374 async ( ) => {
@@ -957,26 +967,22 @@ export class DatabaseUI extends DisposableObject {
957967 }
958968
959969 /**
960- * Ask the user for a database directory . Returns the chosen database, or `undefined` if the
961- * operation was canceled.
970+ * Import database from uri . Returns the imported database, or `undefined` if the
971+ * operation was unsuccessful or canceled.
962972 */
963- private async chooseAndSetDatabase (
973+ private async importDatabase (
974+ uri : Uri ,
964975 byFolder : boolean ,
965976 progress : ProgressCallback ,
966977 ) : Promise < DatabaseItem | undefined > {
967- const uri = await chooseDatabaseDir ( byFolder ) ;
968- if ( ! uri ) {
969- return undefined ;
970- }
971-
972- if ( byFolder && ! uri . fsPath . endsWith ( "testproj" ) ) {
978+ if ( byFolder && ! uri . fsPath . endsWith ( ".testproj" ) ) {
973979 const fixedUri = await this . fixDbUri ( uri ) ;
974980 // we are selecting a database folder
975981 return await this . databaseManager . openDatabase ( fixedUri , {
976982 type : "folder" ,
977983 } ) ;
978984 } else {
979- // we are selecting a database archive or a testproj.
985+ // we are selecting a database archive or a . testproj.
980986 // Unzip archives (if an archive) and copy into a workspace-controlled area
981987 // before importing.
982988 return await this . databaseFetcher . importLocalDatabase (
@@ -986,6 +992,104 @@ export class DatabaseUI extends DisposableObject {
986992 }
987993 }
988994
995+ /**
996+ * Ask the user for a database directory. Returns the chosen database, or `undefined` if the
997+ * operation was canceled.
998+ */
999+ private async chooseAndSetDatabase (
1000+ byFolder : boolean ,
1001+ progress : ProgressCallback ,
1002+ ) : Promise < DatabaseItem | undefined > {
1003+ const uri = await chooseDatabaseDir ( byFolder ) ;
1004+ if ( ! uri ) {
1005+ return undefined ;
1006+ }
1007+
1008+ return await this . importDatabase ( uri , byFolder , progress ) ;
1009+ }
1010+
1011+ /**
1012+ * Ask the user for a parent directory that contains all databases.
1013+ * Returns all valid databases, or `undefined` if the operation was canceled.
1014+ */
1015+ private async chooseDatabasesParentFolder (
1016+ progress : ProgressCallback ,
1017+ ) : Promise < DatabaseItem [ ] | undefined > {
1018+ const uri = await chooseDatabaseDir ( true ) ;
1019+ if ( ! uri ) {
1020+ return undefined ;
1021+ }
1022+
1023+ const databases : DatabaseItem [ ] = [ ] ;
1024+ const failures : string [ ] = [ ] ;
1025+ const entries = await workspace . fs . readDirectory ( uri ) ;
1026+ const validFileTypes = [ FileType . File , FileType . Directory ] ;
1027+
1028+ for ( const [ index , entry ] of entries . entries ( ) ) {
1029+ progress ( {
1030+ step : index + 1 ,
1031+ maxStep : entries . length ,
1032+ message : `Importing '${ entry [ 0 ] } '` ,
1033+ } ) ;
1034+
1035+ const subProgress : ProgressCallback = ( p ) => {
1036+ progress ( {
1037+ step : index + 1 ,
1038+ maxStep : entries . length ,
1039+ message : `Importing '${ entry [ 0 ] } ': (${ p . step } /${ p . maxStep } ) ${ p . message } ` ,
1040+ } ) ;
1041+ } ;
1042+
1043+ if ( ! validFileTypes . includes ( entry [ 1 ] ) ) {
1044+ void this . app . logger . log (
1045+ `Skipping import for '${ entry } ', invalid file type: ${ entry [ 1 ] } ` ,
1046+ ) ;
1047+ continue ;
1048+ }
1049+
1050+ try {
1051+ const databaseUri = Uri . joinPath ( uri , entry [ 0 ] ) ;
1052+ void this . app . logger . log ( `Importing from ${ databaseUri } ` ) ;
1053+
1054+ const database = await this . importDatabase (
1055+ databaseUri ,
1056+ entry [ 1 ] === FileType . Directory ,
1057+ subProgress ,
1058+ ) ;
1059+ if ( database ) {
1060+ databases . push ( database ) ;
1061+ } else {
1062+ failures . push ( entry [ 0 ] ) ;
1063+ }
1064+ } catch ( e ) {
1065+ failures . push ( `${ entry [ 0 ] } : ${ getErrorMessage ( e ) } ` . trim ( ) ) ;
1066+ }
1067+ }
1068+
1069+ if ( failures . length ) {
1070+ void showAndLogErrorMessage (
1071+ this . app . logger ,
1072+ `Failed to import ${ failures . length } database(s), successfully imported ${ databases . length } database(s).` ,
1073+ {
1074+ fullMessage : `Failed to import ${ failures . length } database(s), successfully imported ${ databases . length } database(s).\nFailed databases:\n - ${ failures . join ( "\n - " ) } ` ,
1075+ } ,
1076+ ) ;
1077+ } else if ( databases . length === 0 ) {
1078+ void showAndLogErrorMessage (
1079+ this . app . logger ,
1080+ `No database folder to import.` ,
1081+ ) ;
1082+ return undefined ;
1083+ } else {
1084+ void showAndLogInformationMessage (
1085+ this . app . logger ,
1086+ `Successfully imported ${ databases . length } database(s).` ,
1087+ ) ;
1088+ }
1089+
1090+ return databases ;
1091+ }
1092+
9891093 /**
9901094 * Perform some heuristics to ensure a proper database location is chosen.
9911095 *
0 commit comments