forked from coder/code-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupdate.ts
More file actions
141 lines (130 loc) · 5.19 KB
/
update.ts
File metadata and controls
141 lines (130 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import * as cp from "child_process";
import * as os from "os";
import * as path from "path";
import * as util from "util";
import { CancellationToken } from "vs/base/common/cancellation";
import { URI } from "vs/base/common/uri";
import * as pfs from "vs/base/node/pfs";
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
import { IFileService } from "vs/platform/files/common/files";
import { ILogService } from "vs/platform/log/common/log";
import product from "vs/platform/product/common/product";
import { asJson, IRequestService } from "vs/platform/request/common/request";
import { AvailableForDownload, State, UpdateType, StateType } from "vs/platform/update/common/update";
import { AbstractUpdateService } from "vs/platform/update/electron-main/abstractUpdateService";
import { ipcMain } from "vs/server/src/node/ipc";
import { extract } from "vs/server/src/node/marketplace";
import { tmpdir } from "vs/server/src/node/util";
interface IUpdate {
name: string;
}
export class UpdateService extends AbstractUpdateService {
_serviceBrand: any;
constructor(
@IConfigurationService configurationService: IConfigurationService,
@IEnvironmentService environmentService: IEnvironmentService,
@IRequestService requestService: IRequestService,
@ILogService logService: ILogService,
@IFileService private readonly fileService: IFileService,
) {
super(null, configurationService, environmentService, requestService, logService);
}
/**
* Return true if the currently installed version is the latest.
*/
public async isLatestVersion(latest?: IUpdate | null): Promise<boolean | undefined> {
if (!latest) {
latest = await this.getLatestVersion();
}
if (latest) {
const latestMajor = parseInt(latest.name);
const currentMajor = parseInt(product.codeServerVersion);
// If these are invalid versions we can't compare meaningfully.
return isNaN(latestMajor) || isNaN(currentMajor) ||
// This can happen when there is a pre-release for a new major version.
currentMajor > latestMajor ||
// Otherwise assume that if it's not the same then we're out of date.
latest.name === product.codeServerVersion;
}
return true;
}
protected buildUpdateFeedUrl(quality: string): string {
return `${product.updateUrl}/${quality}`;
}
public async doQuitAndInstall(): Promise<void> {
if (this.state.type === StateType.Ready) {
ipcMain.relaunch(this.state.update.version);
}
}
protected async doCheckForUpdates(context: any): Promise<void> {
this.setState(State.CheckingForUpdates(context));
try {
const update = await this.getLatestVersion();
if (!update || await this.isLatestVersion(update)) {
this.setState(State.Idle(UpdateType.Archive));
} else {
this.setState(State.AvailableForDownload({
version: update.name,
productVersion: update.name,
}));
}
} catch (error) {
this.onRequestError(error, !!context);
}
}
private async getLatestVersion(): Promise<IUpdate | null> {
const data = await this.requestService.request({
url: this.url,
headers: { "User-Agent": "code-server" },
}, CancellationToken.None);
return asJson(data);
}
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
this.setState(State.Downloading(state.update));
const target = os.platform();
const releaseName = await this.buildReleaseName(state.update.version);
const url = "https://github.com/cdr/code-server/releases/download/"
+ `${state.update.version}/${releaseName}`
+ `.${target === "darwin" ? "zip" : "tar.gz"}`;
const downloadPath = path.join(tmpdir, `${state.update.version}-archive`);
const extractPath = path.join(tmpdir, state.update.version);
try {
await pfs.mkdirp(tmpdir);
const context = await this.requestService.request({ url }, CancellationToken.None, true);
await this.fileService.writeFile(URI.file(downloadPath), context.stream);
await extract(downloadPath, extractPath, undefined, CancellationToken.None);
const newBinary = path.join(extractPath, releaseName, "code-server");
if (!pfs.exists(newBinary)) {
throw new Error("No code-server binary in extracted archive");
}
await pfs.unlink(process.argv[0]); // Must unlink first to avoid ETXTBSY.
await pfs.move(newBinary, process.argv[0]);
this.setState(State.Ready(state.update));
} catch (error) {
this.onRequestError(error, true);
}
await Promise.all([downloadPath, extractPath].map((p) => pfs.rimraf(p)));
}
private onRequestError(error: Error, showNotification?: boolean): void {
this.logService.error(error);
this.setState(State.Idle(UpdateType.Archive, showNotification ? (error.message || error.toString()) : undefined));
}
private async buildReleaseName(release: string): Promise<string> {
let target: string = os.platform();
if (target === "linux") {
const result = await util.promisify(cp.exec)("ldd --version").catch((error) => ({
stderr: error.message,
stdout: "",
}));
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
target = "alpine";
}
}
let arch = os.arch();
if (arch === "x64") {
arch = "x86_64";
}
return `code-server${release}-${target}-${arch}`;
}
}