Skip to content

Commit dd95f59

Browse files
committed
with cache
1 parent 1349cb2 commit dd95f59

File tree

2 files changed

+101
-20
lines changed

2 files changed

+101
-20
lines changed

src/common/cache.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Gitpod. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
8+
const CACHE_KEY = 'gitpod.cache';
9+
10+
interface CacheObject {
11+
value: any;
12+
expiration?: number;
13+
}
14+
15+
interface CacheMap { [key: string]: CacheObject }
16+
17+
export class CacheHelper {
18+
constructor(private readonly context: vscode.ExtensionContext) { }
19+
20+
set(key: string, value: any, expiration?: number) {
21+
let obj = this.context.globalState.get<CacheMap>(CACHE_KEY);
22+
if (!obj) {
23+
obj = {};
24+
}
25+
const exp = expiration ? ((+ new Date()) / 1000 + expiration) : undefined;
26+
obj[key] = { value, expiration: exp };
27+
return this.context.globalState.update(CACHE_KEY, obj);
28+
}
29+
30+
get(key: string) {
31+
const value = this.context.globalState.get<CacheMap>(CACHE_KEY);
32+
if (!value || !value[key]) {
33+
return undefined;
34+
}
35+
const data = value[key];
36+
if (!data.expiration) {
37+
return data.value;
38+
}
39+
const now = (+ new Date()) / 1000;
40+
return now > data.expiration ? undefined : data.value;
41+
}
42+
43+
async handy<T>(key: string, cb: () => Thenable<{ value: T; ttl?: number }>) {
44+
let d = this.get(key);
45+
if (d === undefined) {
46+
const tmp = await cb();
47+
await this.set(key, tmp.value, tmp.ttl);
48+
d = tmp.value;
49+
}
50+
return d as T;
51+
}
52+
}

src/releaseNotes.ts

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import fetch from 'node-fetch';
6+
import fetch, { Response } from 'node-fetch';
77
import * as vscode from 'vscode';
88
import { load } from 'js-yaml';
9+
import { CacheHelper } from './common/cache';
910

1011
const LAST_READ_RELEASE_NOTES_ID = 'gitpod.lastReadReleaseNotesId';
1112

1213
export function registerReleaseNotesView(context: vscode.ExtensionContext) {
14+
const cacheHelper = new CacheHelper(context);
1315

1416
async function shouldShowReleaseNotes(lastReadId: string | undefined) {
15-
const releaseId = await getLastPublish();
17+
const releaseId = await getLastPublish(cacheHelper);
1618
console.log(`gitpod release notes lastReadId: ${lastReadId}, latestReleaseId: ${releaseId}`);
1719
return releaseId !== lastReadId;
1820
}
1921

2022
context.subscriptions.push(
2123
vscode.commands.registerCommand('gitpod.showReleaseNotes', () => {
22-
ReleaseNotesPanel.createOrShow(context);
24+
ReleaseNotesPanel.createOrShow(context, cacheHelper);
2325
})
2426
);
2527

@@ -29,18 +31,37 @@ export function registerReleaseNotesView(context: vscode.ExtensionContext) {
2931
const lastReadId = context.globalState.get<string>(LAST_READ_RELEASE_NOTES_ID);
3032
shouldShowReleaseNotes(lastReadId).then(shouldShow => {
3133
if (shouldShow) {
32-
ReleaseNotesPanel.createOrShow(context);
34+
ReleaseNotesPanel.createOrShow(context, cacheHelper);
3335
}
3436
});
3537
}
3638

37-
async function getLastPublish() {
38-
const resp = await fetch(`${websiteHost}/changelog/latest`);
39-
if (!resp.ok) {
40-
throw new Error(`Getting latest releaseId failed: ${resp.statusText}`);
39+
function getResponseCacheTime(resp: Response) {
40+
const v = resp.headers.get('Cache-Control');
41+
if (!v) {
42+
return undefined;
4143
}
42-
const { releaseId } = JSON.parse(await resp.text());
43-
return releaseId as string;
44+
const t = /max-age=(\d+)/.exec(v);
45+
if (!t) {
46+
return undefined;
47+
}
48+
return Number(t[1]);
49+
}
50+
51+
async function getLastPublish(cacheHelper: CacheHelper) {
52+
const url = `${websiteHost}/changelog/latest`;
53+
return cacheHelper.handy(url, async () => {
54+
const resp = await fetch(url);
55+
if (!resp.ok) {
56+
throw new Error(`Getting latest releaseId failed: ${resp.statusText}`);
57+
}
58+
const { releaseId } = JSON.parse(await resp.text());
59+
return {
60+
value: releaseId as string,
61+
ttl: getResponseCacheTime(resp),
62+
};
63+
});
64+
4465
}
4566

4667
const websiteHost = 'https://www.gitpod.io';
@@ -53,11 +74,18 @@ class ReleaseNotesPanel {
5374
private _disposables: vscode.Disposable[] = [];
5475

5576
private async loadChangelog(releaseId: string) {
56-
const resp = await fetch(`${websiteHost}/changelog/raw-markdown?releaseId=${releaseId}`);
57-
if (!resp.ok) {
58-
throw new Error(`Getting raw markdown content failed: ${resp.statusText}`);
59-
}
60-
const md = await resp.text();
77+
const url = `${websiteHost}/changelog/raw-markdown?releaseId=${releaseId}`;
78+
const md = await this.cacheHelper.handy(url, async () => {
79+
const resp = await fetch(url);
80+
if (!resp.ok) {
81+
throw new Error(`Getting raw markdown content failed: ${resp.statusText}`);
82+
}
83+
const md = await resp.text();
84+
return {
85+
value: md,
86+
ttl: getResponseCacheTime(resp),
87+
};
88+
});
6189

6290
const parseInfo = (md: string) => {
6391
if (!md.startsWith('---')) {
@@ -96,7 +124,7 @@ class ReleaseNotesPanel {
96124

97125
public async updateHtml(releaseId?: string) {
98126
if (!releaseId) {
99-
releaseId = await getLastPublish();
127+
releaseId = await getLastPublish(this.cacheHelper);
100128
}
101129
const mdContent = await this.loadChangelog(releaseId);
102130
const html = await vscode.commands.executeCommand('markdown.api.render', mdContent) as string;
@@ -120,7 +148,7 @@ class ReleaseNotesPanel {
120148
}
121149
}
122150

123-
public static createOrShow(context: vscode.ExtensionContext) {
151+
public static createOrShow(context: vscode.ExtensionContext, cacheHelper: CacheHelper) {
124152
const column = vscode.window.activeTextEditor
125153
? vscode.window.activeTextEditor.viewColumn
126154
: undefined;
@@ -137,15 +165,16 @@ class ReleaseNotesPanel {
137165
{ enableScripts: true },
138166
);
139167

140-
ReleaseNotesPanel.currentPanel = new ReleaseNotesPanel(context, panel);
168+
ReleaseNotesPanel.currentPanel = new ReleaseNotesPanel(context, cacheHelper, panel);
141169
}
142170

143-
public static revive(context: vscode.ExtensionContext, panel: vscode.WebviewPanel) {
144-
ReleaseNotesPanel.currentPanel = new ReleaseNotesPanel(context, panel);
171+
public static revive(context: vscode.ExtensionContext, cacheHelper: CacheHelper, panel: vscode.WebviewPanel) {
172+
ReleaseNotesPanel.currentPanel = new ReleaseNotesPanel(context, cacheHelper, panel);
145173
}
146174

147175
private constructor(
148176
private readonly context: vscode.ExtensionContext,
177+
private readonly cacheHelper: CacheHelper,
149178
panel: vscode.WebviewPanel
150179
) {
151180
this.lastReadId = this.context.globalState.get<string>(LAST_READ_RELEASE_NOTES_ID);

0 commit comments

Comments
 (0)