@@ -3,8 +3,9 @@ import * as fs from 'fs';
3
3
import * as https from 'https' ;
4
4
import * as os from 'os' ;
5
5
import * as path from 'path' ;
6
+ import { promisify } from 'util' ;
6
7
import { env , ExtensionContext , ProgressLocation , Uri , window , WorkspaceFolder } from 'vscode' ;
7
- import { downloadFile , executableExists , userAgentHeader } from './utils' ;
8
+ import { downloadFile , executableExists , httpsGetSilently } from './utils' ;
8
9
9
10
/** GitHub API release */
10
11
interface IRelease {
@@ -140,6 +141,35 @@ async function getProjectGhcVersion(context: ExtensionContext, dir: string, rele
140
141
return callWrapper ( downloadedWrapper ) ;
141
142
}
142
143
144
+ async function getLatestReleaseMetadata ( context : ExtensionContext ) : Promise < IRelease | undefined > {
145
+ const opts : https . RequestOptions = {
146
+ host : 'api.github.com' ,
147
+ path : '/repos/haskell/haskell-language-server/releases' ,
148
+ } ;
149
+ const offlineCache = path . join ( context . globalStoragePath , 'latestRelease.cache.json' ) ;
150
+
151
+ try {
152
+ const releaseInfo = await httpsGetSilently ( opts ) ;
153
+ const latestInfoParsed = ( JSON . parse ( releaseInfo ) as IRelease [ ] ) . find ( ( x ) => ! x . prerelease ) ;
154
+
155
+ // Cache the latest successfully fetched release information
156
+ await promisify ( fs . writeFile ) ( offlineCache , JSON . stringify ( latestInfoParsed ) , { encoding : 'utf-8' } ) ;
157
+ return latestInfoParsed ;
158
+ } catch ( githubError ) {
159
+ // Attempt to read from the latest cached file
160
+ try {
161
+ const cachedInfo = await promisify ( fs . readFile ) ( offlineCache , { encoding : 'utf-8' } ) ;
162
+
163
+ const cachedInfoParsed = JSON . parse ( cachedInfo ) ;
164
+ window . showWarningMessage (
165
+ `Couldn't get the latest haskell-language-server releases from GitHub, used local cache instead:\n${ githubError . message } `
166
+ ) ;
167
+ return cachedInfoParsed ;
168
+ } catch ( fileError ) {
169
+ throw new Error ( `Couldn't get the latest haskell-language-server releases from GitHub:\n${ githubError . message } ` ) ;
170
+ }
171
+ }
172
+ }
143
173
/**
144
174
* Downloads the latest haskell-language-server binaries from GitHub releases.
145
175
* Returns null if it can't find any that match.
@@ -149,27 +179,6 @@ export async function downloadHaskellLanguageServer(
149
179
resource : Uri ,
150
180
folder ?: WorkspaceFolder
151
181
) : Promise < string | null > {
152
- // Fetch the latest release from GitHub
153
- const releases : IRelease [ ] = await new Promise ( ( resolve , reject ) => {
154
- let data : string = '' ;
155
- const opts : https . RequestOptions = {
156
- host : 'api.github.com' ,
157
- path : '/repos/haskell/haskell-language-server/releases' ,
158
- headers : userAgentHeader ,
159
- } ;
160
- https
161
- . get ( opts , ( res ) => {
162
- res . on ( 'data' , ( d ) => ( data += d ) ) ;
163
- res . on ( 'error' , reject ) ;
164
- res . on ( 'close' , ( ) => {
165
- resolve ( JSON . parse ( data ) ) ;
166
- } ) ;
167
- } )
168
- . on ( 'error' , ( e ) => {
169
- reject ( new Error ( `Couldn't get the latest haskell-language-server releases from GitHub:\n${ e . message } ` ) ) ;
170
- } ) ;
171
- } ) ;
172
-
173
182
// Make sure to create this before getProjectGhcVersion
174
183
if ( ! fs . existsSync ( context . globalStoragePath ) ) {
175
184
fs . mkdirSync ( context . globalStoragePath ) ;
@@ -182,13 +191,15 @@ export async function downloadHaskellLanguageServer(
182
191
return null ;
183
192
}
184
193
185
- const release = releases . find ( ( x ) => ! x . prerelease ) ;
194
+ // Fetch the latest release from GitHub or from cache
195
+ const release = await getLatestReleaseMetadata ( context ) ;
186
196
if ( ! release ) {
187
197
window . showErrorMessage ( "Couldn't find any pre-built haskell-language-server binaries" ) ;
188
198
return null ;
189
199
}
190
- const dir : string = folder ?. uri ?. fsPath ?? path . dirname ( resource . fsPath ) ;
191
200
201
+ // Figure out the ghc version to use or advertise an installation link for missing components
202
+ const dir : string = folder ?. uri ?. fsPath ?? path . dirname ( resource . fsPath ) ;
192
203
let ghcVersion : string ;
193
204
try {
194
205
ghcVersion = await getProjectGhcVersion ( context , dir , release ) ;
@@ -224,15 +235,8 @@ export async function downloadHaskellLanguageServer(
224
235
const binaryDest = path . join ( context . globalStoragePath , serverName ) ;
225
236
226
237
const title = `Downloading haskell-language-server ${ release . tag_name } for GHC ${ ghcVersion } ` ;
227
- try {
228
- await downloadFile ( title , asset . browser_download_url , binaryDest ) ;
229
- return binaryDest ;
230
- } catch ( e ) {
231
- if ( e instanceof Error ) {
232
- window . showErrorMessage ( e . message ) ;
233
- }
234
- return null ;
235
- }
238
+ await downloadFile ( title , asset . browser_download_url , binaryDest ) ;
239
+ return binaryDest ;
236
240
}
237
241
238
242
/** Get the OS label used by GitHub for the current platform */
0 commit comments