@@ -15,6 +15,7 @@ import {
1515} from "fs-extra" ;
1616import { basename , join } from "path" ;
1717import type { Octokit } from "@octokit/rest" ;
18+ import { nanoid } from "nanoid" ;
1819
1920import type { DatabaseManager , DatabaseItem } from "./local-databases" ;
2021import { tmpDir } from "../tmp-dir" ;
@@ -365,7 +366,11 @@ async function databaseArchiveFetcher(
365366 throw new Error ( "No storage path specified." ) ;
366367 }
367368 await ensureDir ( storagePath ) ;
368- const unzipPath = await getStorageFolder ( storagePath , databaseUrl ) ;
369+ const unzipPath = await getStorageFolder (
370+ storagePath ,
371+ databaseUrl ,
372+ nameOverride ,
373+ ) ;
369374
370375 if ( isFile ( databaseUrl ) ) {
371376 await readAndUnzip ( databaseUrl , unzipPath , cli , progress ) ;
@@ -408,16 +413,41 @@ async function databaseArchiveFetcher(
408413 }
409414}
410415
411- async function getStorageFolder ( storagePath : string , urlStr : string ) {
412- // we need to generate a folder name for the unzipped archive,
413- // this needs to be human readable since we may use this name as the initial
414- // name for the database
415- const url = Uri . parse ( urlStr ) ;
416- // MacOS has a max filename length of 255
417- // and remove a few extra chars in case we need to add a counter at the end.
418- let lastName = basename ( url . path ) . substring ( 0 , 250 ) ;
419- if ( lastName . endsWith ( ".zip" ) ) {
420- lastName = lastName . substring ( 0 , lastName . length - 4 ) ;
416+ async function getStorageFolder (
417+ storagePath : string ,
418+ urlStr : string ,
419+ nameOverrride ?: string ,
420+ ) {
421+ let lastName : string ;
422+
423+ if ( nameOverrride ) {
424+ // Lowercase everything
425+ let name = nameOverrride . toLowerCase ( ) ;
426+
427+ // Replace all spaces, dots, underscores, and forward slashes with hyphens
428+ name = name . replaceAll ( / [ \s . _ / ] + / g, "-" ) ;
429+
430+ // Replace all characters which are not allowed by empty strings
431+ name = name . replaceAll ( / [ ^ a - z 0 - 9 - ] / g, "" ) ;
432+
433+ // Remove any leading or trailing hyphens
434+ name = name . replaceAll ( / ^ - | - $ / g, "" ) ;
435+
436+ // Remove any duplicate hyphens
437+ name = name . replaceAll ( / - { 2 , } / g, "-" ) ;
438+
439+ lastName = name ;
440+ } else {
441+ // we need to generate a folder name for the unzipped archive,
442+ // this needs to be human readable since we may use this name as the initial
443+ // name for the database
444+ const url = Uri . parse ( urlStr ) ;
445+ // MacOS has a max filename length of 255
446+ // and remove a few extra chars in case we need to add a counter at the end.
447+ lastName = basename ( url . path ) . substring ( 0 , 250 ) ;
448+ if ( lastName . endsWith ( ".zip" ) ) {
449+ lastName = lastName . substring ( 0 , lastName . length - 4 ) ;
450+ }
421451 }
422452
423453 const realpath = await fs_realpath ( storagePath ) ;
@@ -429,6 +459,11 @@ async function getStorageFolder(storagePath: string, urlStr: string) {
429459 counter ++ ;
430460 folderName = join ( realpath , `${ lastName } -${ counter } ` ) ;
431461 if ( counter > 100 ) {
462+ // If there are more than 100 similarly named databases,
463+ // give up on using a counter and use a random string instead.
464+ folderName = join ( realpath , `${ lastName } -${ nanoid ( ) } ` ) ;
465+ }
466+ if ( counter > 200 ) {
432467 throw new Error ( "Could not find a unique name for downloaded database." ) ;
433468 }
434469 }
0 commit comments