From 9c5b6e64a7c930f5c5713718edb1fd91eb4023c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Mon, 22 Nov 2021 22:29:37 +0100 Subject: [PATCH 1/9] support old hls versions compatible with the requested ghc version --- src/hlsBinaries.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index 02769261..9942c8cb 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -37,7 +37,7 @@ const releaseValidator: validate.Validator = validate.object({ const githubReleaseApiValidator: validate.Validator = validate.array(releaseValidator); -const cachedReleaseValidator: validate.Validator = validate.optional(releaseValidator); +const cachedReleaseValidator: validate.Validator = validate.optional(githubReleaseApiValidator); // On Windows the executable needs to be stored somewhere with an .exe extension const exeExt = process.platform === 'win32' ? '.exe' : ''; @@ -205,7 +205,7 @@ async function getProjectGhcVersion( return callWrapper(downloadedWrapper); } -async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: string): Promise { +async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: string): Promise { const releasesUrl = workspace.getConfiguration('haskell').releasesURL ? url.parse(workspace.getConfiguration('haskell').releasesURL) : undefined; @@ -221,7 +221,7 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: const offlineCache = path.join(storagePath, 'latestApprovedRelease.cache.json'); - async function readCachedReleaseData(): Promise { + async function readCachedReleaseData(): Promise { try { const cachedInfo = await promisify(fs.readFile)(offlineCache, { encoding: 'utf-8' }); return validate.parseAndValidate(cachedInfo, cachedReleaseValidator); @@ -243,14 +243,14 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: try { const releaseInfo = await httpsGetSilently(opts); const latestInfoParsed = - validate.parseAndValidate(releaseInfo, githubReleaseApiValidator).find((x) => !x.prerelease) || null; + validate.parseAndValidate(releaseInfo, githubReleaseApiValidator).filter((x) => !x.prerelease) || null; if (updateBehaviour === 'prompt') { const cachedInfoParsed = await readCachedReleaseData(); if ( latestInfoParsed !== null && - (cachedInfoParsed === null || latestInfoParsed.tag_name !== cachedInfoParsed.tag_name) + (cachedInfoParsed === null || latestInfoParsed[0].tag_name !== cachedInfoParsed[0].tag_name) ) { const promptMessage = cachedInfoParsed === null @@ -316,8 +316,8 @@ export async function downloadHaskellLanguageServer( } logger.info('Fetching the latest release from GitHub or from cache'); - const release = await getLatestReleaseMetadata(context, storagePath); - if (!release) { + const releases = await getLatestReleaseMetadata(context, storagePath); + if (!releases) { let message = "Couldn't find any pre-built haskell-language-server binaries"; const updateBehaviour = workspace.getConfiguration('haskell').get('updateBehavior') as UpdateBehaviour; if (updateBehaviour === 'never-check') { @@ -326,12 +326,12 @@ export async function downloadHaskellLanguageServer( window.showErrorMessage(message); return null; } - logger.info(`The latest release is ${release.tag_name}`); + logger.info(`The latest release is ${releases[0].tag_name}`); logger.info('Figure out the ghc version to use or advertise an installation link for missing components'); const dir: string = folder?.uri?.fsPath ?? path.dirname(resource.fsPath); let ghcVersion: string; try { - ghcVersion = await getProjectGhcVersion(context, logger, dir, release, storagePath); + ghcVersion = await getProjectGhcVersion(context, logger, dir, releases[0], storagePath); } catch (error) { if (error instanceof MissingToolError) { const link = error.installLink(); @@ -354,20 +354,21 @@ export async function downloadHaskellLanguageServer( // When searching for binaries, use startsWith because the compression may differ // between .zip and .gz const assetName = `haskell-language-server-${githubOS}-${ghcVersion}${exeExt}`; - logger.info(`Search for binary ${assetName} in release assests`); + logger.info(`Search for binary ${assetName} in release assets`); + const release = releases?.find(r => r.assets.find((x) => x.name.startsWith(assetName))); const asset = release?.assets.find((x) => x.name.startsWith(assetName)); if (!asset) { logger.error( - `No binary ${assetName} found in the release assets: ${release?.assets.map((value) => value.name).join(',')}` + `No binary ${assetName} found in the release assets` ); - window.showInformationMessage(new NoBinariesError(release.tag_name, ghcVersion).message); + window.showInformationMessage(new NoBinariesError(releases[0].tag_name, ghcVersion).message); return null; } - const serverName = `haskell-language-server-${release.tag_name}-${process.platform}-${ghcVersion}${exeExt}`; + const serverName = `haskell-language-server-${release?.tag_name}-${process.platform}-${ghcVersion}${exeExt}`; const binaryDest = path.join(storagePath, serverName); - const title = `Downloading haskell-language-server ${release.tag_name} for GHC ${ghcVersion}`; + const title = `Downloading haskell-language-server ${release?.tag_name} for GHC ${ghcVersion}`; logger.info(title); await downloadFile(title, asset.browser_download_url, binaryDest); if (ghcVersion.startsWith('9.')) { From 5178ed206940e7dfd7270e10a79f1102abd38d5b Mon Sep 17 00:00:00 2001 From: jneira Date: Tue, 23 Nov 2021 12:11:06 +0100 Subject: [PATCH 2/9] Test ghc deprecated version --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d8e7588..5af4cd59 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,11 @@ jobs: strategy: matrix: os: [macos-11, ubuntu-latest, windows-latest] + ghc: [9.0.1] + include: + # To test a ghc version deprecated in newer hls versions + - os: ubuntu-latest + ghc: 8.10.5 runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -22,7 +27,7 @@ jobs: - name: Ensure there is a supported ghc versions uses: haskell/actions/setup@v1 with: - ghc-version: 9.0.1 + ghc-version: ${{ matrix.ghc }} - run: npm ci - run: npm run webpack - run: xvfb-run -s '-screen 0 640x480x16' -a npm test From 98e43da034ef7a3a712436a25f871e9e97906da8 Mon Sep 17 00:00:00 2001 From: jneira Date: Tue, 23 Nov 2021 12:16:10 +0100 Subject: [PATCH 3/9] Use a real deprecated version --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5af4cd59..bbcea430 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: include: # To test a ghc version deprecated in newer hls versions - os: ubuntu-latest - ghc: 8.10.5 + ghc: 8.10.4 runs-on: ${{ matrix.os }} steps: - name: Checkout From 73d7e0ef691ae92f972269874b5999fd99d4c199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Tue, 23 Nov 2021 23:06:45 +0100 Subject: [PATCH 4/9] Address review comments: * Align identifier names with actual behaviour * rename latestApprovedRelease.cache.json to approvedRelease.cache.json * check for empty array of releases * alert and log when falling back to and older hls version --- src/hlsBinaries.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index 9942c8cb..715839cb 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -85,7 +85,7 @@ class NoBinariesError extends Error { const supportedReleasesLink = '[See the list of supported versions here](https://github.com/haskell/vscode-haskell#supported-ghc-versions)'; if (ghcVersion) { - super(`haskell-language-server ${hlsVersion} for GHC ${ghcVersion} is not available on ${os.type()}. + super(`haskell-language-server ${hlsVersion} or earlier for GHC ${ghcVersion} is not available on ${os.type()}. ${supportedReleasesLink}`); } else { super(`haskell-language-server ${hlsVersion} is not available on ${os.type()}. @@ -205,7 +205,7 @@ async function getProjectGhcVersion( return callWrapper(downloadedWrapper); } -async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: string): Promise { +async function getReleaseMetadata(context: ExtensionContext, storagePath: string): Promise { const releasesUrl = workspace.getConfiguration('haskell').releasesURL ? url.parse(workspace.getConfiguration('haskell').releasesURL) : undefined; @@ -219,7 +219,7 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: path: '/repos/haskell/haskell-language-server/releases', }; - const offlineCache = path.join(storagePath, 'latestApprovedRelease.cache.json'); + const offlineCache = path.join(storagePath, 'approvedReleases.cache.json'); async function readCachedReleaseData(): Promise { try { @@ -242,15 +242,16 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: try { const releaseInfo = await httpsGetSilently(opts); - const latestInfoParsed = + const releaseInfoParsed = validate.parseAndValidate(releaseInfo, githubReleaseApiValidator).filter((x) => !x.prerelease) || null; if (updateBehaviour === 'prompt') { const cachedInfoParsed = await readCachedReleaseData(); if ( - latestInfoParsed !== null && - (cachedInfoParsed === null || latestInfoParsed[0].tag_name !== cachedInfoParsed[0].tag_name) + releaseInfoParsed !== null && releaseInfoParsed.length > 0 && + (cachedInfoParsed === null || cachedInfoParsed.length == 0 + || releaseInfoParsed[0].tag_name !== cachedInfoParsed[0].tag_name) ) { const promptMessage = cachedInfoParsed === null @@ -266,8 +267,8 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath: } // Cache the latest successfully fetched release information - await promisify(fs.writeFile)(offlineCache, JSON.stringify(latestInfoParsed), { encoding: 'utf-8' }); - return latestInfoParsed; + await promisify(fs.writeFile)(offlineCache, JSON.stringify(releaseInfoParsed), { encoding: 'utf-8' }); + return releaseInfoParsed; } catch (githubError: any) { // Attempt to read from the latest cached file try { @@ -316,7 +317,7 @@ export async function downloadHaskellLanguageServer( } logger.info('Fetching the latest release from GitHub or from cache'); - const releases = await getLatestReleaseMetadata(context, storagePath); + const releases = await getReleaseMetadata(context, storagePath); if (!releases) { let message = "Couldn't find any pre-built haskell-language-server binaries"; const updateBehaviour = workspace.getConfiguration('haskell').get('updateBehavior') as UpdateBehaviour; @@ -364,6 +365,11 @@ export async function downloadHaskellLanguageServer( window.showInformationMessage(new NoBinariesError(releases[0].tag_name, ghcVersion).message); return null; } + if (release?.tag_name != releases[0].tag_name) { + const message = `haskell-language-server ${releases[0].tag_name} for GHC ${ghcVersion} is not available on ${os.type()}. Falling back to haskell-language-server ${release?.tag_name}` + logger.warn(message) + window.showInformationMessage(message) + } const serverName = `haskell-language-server-${release?.tag_name}-${process.platform}-${ghcVersion}${exeExt}`; const binaryDest = path.join(storagePath, serverName); From db4a6910d6ec96fac02cfd5b4c3c07e2f61c4f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Wed, 24 Nov 2021 08:50:01 +0100 Subject: [PATCH 5/9] Inform on fallback to outdated hls only on first download --- src/hlsBinaries.ts | 14 ++++++++------ src/utils.ts | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index 715839cb..55a9f7ef 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -365,18 +365,13 @@ export async function downloadHaskellLanguageServer( window.showInformationMessage(new NoBinariesError(releases[0].tag_name, ghcVersion).message); return null; } - if (release?.tag_name != releases[0].tag_name) { - const message = `haskell-language-server ${releases[0].tag_name} for GHC ${ghcVersion} is not available on ${os.type()}. Falling back to haskell-language-server ${release?.tag_name}` - logger.warn(message) - window.showInformationMessage(message) - } const serverName = `haskell-language-server-${release?.tag_name}-${process.platform}-${ghcVersion}${exeExt}`; const binaryDest = path.join(storagePath, serverName); const title = `Downloading haskell-language-server ${release?.tag_name} for GHC ${ghcVersion}`; logger.info(title); - await downloadFile(title, asset.browser_download_url, binaryDest); + const downloaded = await downloadFile(title, asset.browser_download_url, binaryDest); if (ghcVersion.startsWith('9.')) { const warning = 'Currently, HLS supports GHC 9 only partially. ' + @@ -384,6 +379,13 @@ export async function downloadHaskellLanguageServer( logger.warn(warning); window.showWarningMessage(warning); } + if (release?.tag_name != releases[0].tag_name) { + const warning = `haskell-language-server ${releases[0].tag_name} for GHC ${ghcVersion} is not available on ${os.type()}. Falling back to haskell-language-server ${release?.tag_name}` + logger.warn(warning) + if (downloaded) { + window.showInformationMessage(warning) + } + } return binaryDest; } diff --git a/src/utils.ts b/src/utils.ts index 20a0ee0d..924cbaa0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -95,7 +95,7 @@ const userAgentHeader = { 'User-Agent': 'vscode-haskell' }; * equality is by reference, not value in Map. And we are using a tuple of * [src, dest] as the key. */ -const inFlightDownloads = new Map>>(); +const inFlightDownloads = new Map>>(); export async function httpsGetSilently(options: https.RequestOptions): Promise { const opts: https.RequestOptions = { @@ -141,7 +141,7 @@ async function ignoreFileNotExists(err: NodeJS.ErrnoException): Promise { throw err; } -export async function downloadFile(titleMsg: string, src: string, dest: string): Promise { +export async function downloadFile(titleMsg: string, src: string, dest: string): Promise { // Check to see if we're already in the process of downloading the same thing const inFlightDownload = inFlightDownloads.get(src)?.get(dest); if (inFlightDownload) { @@ -150,7 +150,7 @@ export async function downloadFile(titleMsg: string, src: string, dest: string): // If it already is downloaded just use that if (fs.existsSync(dest)) { - return; + return false; } // Download it to a .tmp location first, then rename it! @@ -241,7 +241,7 @@ export async function downloadFile(titleMsg: string, src: string, dest: string): inFlightDownloads.get(src)?.delete(dest); } } - ); + ).then(_ => true); try { if (inFlightDownloads.has(src)) { From a3504255affb41479119d18b3c931f28714e9740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Wed, 24 Nov 2021 09:00:07 +0100 Subject: [PATCH 6/9] Silence warnings --- src/hlsBinaries.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index 55a9f7ef..abd7d319 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -250,7 +250,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string if ( releaseInfoParsed !== null && releaseInfoParsed.length > 0 && - (cachedInfoParsed === null || cachedInfoParsed.length == 0 + (cachedInfoParsed === null || cachedInfoParsed.length === 0 || releaseInfoParsed[0].tag_name !== cachedInfoParsed[0].tag_name) ) { const promptMessage = @@ -379,11 +379,11 @@ export async function downloadHaskellLanguageServer( logger.warn(warning); window.showWarningMessage(warning); } - if (release?.tag_name != releases[0].tag_name) { - const warning = `haskell-language-server ${releases[0].tag_name} for GHC ${ghcVersion} is not available on ${os.type()}. Falling back to haskell-language-server ${release?.tag_name}` - logger.warn(warning) + if (release?.tag_name !== releases[0].tag_name) { + const warning = `haskell-language-server ${releases[0].tag_name} for GHC ${ghcVersion} is not available on ${os.type()}. Falling back to haskell-language-server ${release?.tag_name}`; + logger.warn(warning); if (downloaded) { - window.showInformationMessage(warning) + window.showInformationMessage(warning); } } return binaryDest; From 15c89512dfe48c99f91cd0eb8f69155d22e5565d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Wed, 24 Nov 2021 11:56:43 +0100 Subject: [PATCH 7/9] Fall back to old cached releases if user doesn't want to update or downloading the releases fails --- src/hlsBinaries.ts | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index abd7d319..476c7990 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -39,6 +39,8 @@ const githubReleaseApiValidator: validate.Validator = validate.array const cachedReleaseValidator: validate.Validator = validate.optional(githubReleaseApiValidator); +const cachedReleaseValidatorOld: validate.Validator = validate.optional(releaseValidator); + // On Windows the executable needs to be stored somewhere with an .exe extension const exeExt = process.platform === 'win32' ? '.exe' : ''; @@ -221,10 +223,36 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string const offlineCache = path.join(storagePath, 'approvedReleases.cache.json'); - async function readCachedReleaseData(): Promise { + async function readCachedReleaseData(fallBack: boolean): Promise { try { const cachedInfo = await promisify(fs.readFile)(offlineCache, { encoding: 'utf-8' }); return validate.parseAndValidate(cachedInfo, cachedReleaseValidator); + } catch (err: any) { + // If file doesn't exist, return null unless fallBack is true. In that case try to + // read from the older cache file format (1.7.1 and earlier). + // Consider everything else it a failure + if (err.code === 'ENOENT') { + if (fallBack) { + return readOldCachedReleaseData(); + } else { + return null; + } + } + throw err; + } + } + + const offlineCacheOld = path.join(storagePath, 'latestApprovedRelease.cache.json'); + + async function readOldCachedReleaseData(): Promise { + try { + const cachedInfo = await promisify(fs.readFile)(offlineCacheOld, { encoding: 'utf-8' }); + const cached = validate.parseAndValidate(cachedInfo, cachedReleaseValidatorOld); + if (cached) { + return [cached]; + } else { + return null; + } } catch (err: any) { // If file doesn't exist, return null, otherwise consider it a failure if (err.code === 'ENOENT') { @@ -233,11 +261,12 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string throw err; } } + // Not all users want to upgrade right away, in that case prompt const updateBehaviour = workspace.getConfiguration('haskell').get('updateBehavior') as UpdateBehaviour; if (updateBehaviour === 'never-check') { - return readCachedReleaseData(); + return readCachedReleaseData(true) } try { @@ -246,7 +275,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string validate.parseAndValidate(releaseInfo, githubReleaseApiValidator).filter((x) => !x.prerelease) || null; if (updateBehaviour === 'prompt') { - const cachedInfoParsed = await readCachedReleaseData(); + const cachedInfoParsed = await readCachedReleaseData(false); if ( releaseInfoParsed !== null && releaseInfoParsed.length > 0 && @@ -261,7 +290,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string const decision = await window.showInformationMessage(promptMessage, 'Download', 'Nevermind'); if (decision !== 'Download') { // If not upgrade, bail and don't overwrite cached version information - return cachedInfoParsed; + return readCachedReleaseData(true); } } } @@ -272,7 +301,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string } catch (githubError: any) { // Attempt to read from the latest cached file try { - const cachedInfoParsed = await readCachedReleaseData(); + const cachedInfoParsed = await readCachedReleaseData(true); window.showWarningMessage( `Couldn't get the latest haskell-language-server releases from GitHub, used local cache instead:\n${githubError.message}` From 7753c4d9bdf6beb0d8bf37d177ea5d0a6a2f738e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Wed, 24 Nov 2021 16:59:18 +0100 Subject: [PATCH 8/9] Revert "Fall back to old cached releases if user doesn't want to update or downloading the releases fails" This reverts commit 15c89512dfe48c99f91cd0eb8f69155d22e5565d. --- src/hlsBinaries.ts | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index 476c7990..abd7d319 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -39,8 +39,6 @@ const githubReleaseApiValidator: validate.Validator = validate.array const cachedReleaseValidator: validate.Validator = validate.optional(githubReleaseApiValidator); -const cachedReleaseValidatorOld: validate.Validator = validate.optional(releaseValidator); - // On Windows the executable needs to be stored somewhere with an .exe extension const exeExt = process.platform === 'win32' ? '.exe' : ''; @@ -223,36 +221,10 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string const offlineCache = path.join(storagePath, 'approvedReleases.cache.json'); - async function readCachedReleaseData(fallBack: boolean): Promise { + async function readCachedReleaseData(): Promise { try { const cachedInfo = await promisify(fs.readFile)(offlineCache, { encoding: 'utf-8' }); return validate.parseAndValidate(cachedInfo, cachedReleaseValidator); - } catch (err: any) { - // If file doesn't exist, return null unless fallBack is true. In that case try to - // read from the older cache file format (1.7.1 and earlier). - // Consider everything else it a failure - if (err.code === 'ENOENT') { - if (fallBack) { - return readOldCachedReleaseData(); - } else { - return null; - } - } - throw err; - } - } - - const offlineCacheOld = path.join(storagePath, 'latestApprovedRelease.cache.json'); - - async function readOldCachedReleaseData(): Promise { - try { - const cachedInfo = await promisify(fs.readFile)(offlineCacheOld, { encoding: 'utf-8' }); - const cached = validate.parseAndValidate(cachedInfo, cachedReleaseValidatorOld); - if (cached) { - return [cached]; - } else { - return null; - } } catch (err: any) { // If file doesn't exist, return null, otherwise consider it a failure if (err.code === 'ENOENT') { @@ -261,12 +233,11 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string throw err; } } - // Not all users want to upgrade right away, in that case prompt const updateBehaviour = workspace.getConfiguration('haskell').get('updateBehavior') as UpdateBehaviour; if (updateBehaviour === 'never-check') { - return readCachedReleaseData(true) + return readCachedReleaseData(); } try { @@ -275,7 +246,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string validate.parseAndValidate(releaseInfo, githubReleaseApiValidator).filter((x) => !x.prerelease) || null; if (updateBehaviour === 'prompt') { - const cachedInfoParsed = await readCachedReleaseData(false); + const cachedInfoParsed = await readCachedReleaseData(); if ( releaseInfoParsed !== null && releaseInfoParsed.length > 0 && @@ -290,7 +261,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string const decision = await window.showInformationMessage(promptMessage, 'Download', 'Nevermind'); if (decision !== 'Download') { // If not upgrade, bail and don't overwrite cached version information - return readCachedReleaseData(true); + return cachedInfoParsed; } } } @@ -301,7 +272,7 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string } catch (githubError: any) { // Attempt to read from the latest cached file try { - const cachedInfoParsed = await readCachedReleaseData(true); + const cachedInfoParsed = await readCachedReleaseData(); window.showWarningMessage( `Couldn't get the latest haskell-language-server releases from GitHub, used local cache instead:\n${githubError.message}` From 560b8e4e70008f74c575ea53b41e6a1e399876b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20D=C3=BCrig?= Date: Wed, 24 Nov 2021 17:45:34 +0100 Subject: [PATCH 9/9] Migrate existing old cache file to new file format --- src/hlsBinaries.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index abd7d319..d262e62e 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -205,7 +205,11 @@ async function getProjectGhcVersion( return callWrapper(downloadedWrapper); } -async function getReleaseMetadata(context: ExtensionContext, storagePath: string): Promise { +async function getReleaseMetadata( + context: ExtensionContext, + storagePath: string, + logger: Logger +): Promise { const releasesUrl = workspace.getConfiguration('haskell').releasesURL ? url.parse(workspace.getConfiguration('haskell').releasesURL) : undefined; @@ -220,6 +224,25 @@ async function getReleaseMetadata(context: ExtensionContext, storagePath: string }; const offlineCache = path.join(storagePath, 'approvedReleases.cache.json'); + const offlineCacheOldFormat = path.join(storagePath, 'latestApprovedRelease.cache.json'); + + // Migrate existing old cache file latestApprovedRelease.cache.json to the new cache file + // approvedReleases.cache.json if no such file exists yet. + if (!fs.existsSync(offlineCache)) { + try { + const oldCachedInfo = await promisify(fs.readFile)(offlineCacheOldFormat, { encoding: 'utf-8' }); + const oldCachedInfoParsed = validate.parseAndValidate(oldCachedInfo, validate.optional(releaseValidator)); + if (oldCachedInfoParsed !== null) { + await promisify(fs.writeFile)(offlineCache, JSON.stringify([oldCachedInfoParsed]), { encoding: 'utf-8' }); + } + logger.info(`Successfully migrated ${offlineCacheOldFormat} to ${offlineCache}`); + } catch (err: any) { + // Ignore if old cache file does not exist + if (err.code !== 'ENOENT') { + logger.error(`Failed to migrate ${offlineCacheOldFormat} to ${offlineCache}: ${err}`); + } + } + } async function readCachedReleaseData(): Promise { try { @@ -317,7 +340,7 @@ export async function downloadHaskellLanguageServer( } logger.info('Fetching the latest release from GitHub or from cache'); - const releases = await getReleaseMetadata(context, storagePath); + const releases = await getReleaseMetadata(context, storagePath, logger); if (!releases) { let message = "Couldn't find any pre-built haskell-language-server binaries"; const updateBehaviour = workspace.getConfiguration('haskell').get('updateBehavior') as UpdateBehaviour;