Skip to content

Commit 178f9a9

Browse files
committed
wip: windows fast updates
1 parent 12b1973 commit 178f9a9

7 files changed

Lines changed: 238 additions & 51 deletions

File tree

src/typings/windows-mutex.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ declare module 'windows-mutex' {
99
isActive(): boolean;
1010
release(): void;
1111
}
12+
13+
export function isActive(name: string): boolean;
1214
}

src/vs/code/electron-main/menus.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,17 @@ export class CodeMenu {
10451045
return [];
10461046

10471047
case UpdateState.UpdateDownloaded:
1048+
return [new MenuItem({
1049+
label: nls.localize('miInstallUpdate', "Install Update..."), click: () => {
1050+
this.reportMenuActionTelemetry('InstallUpdate');
1051+
this.updateService.applyUpdate();
1052+
}
1053+
})];
1054+
1055+
case UpdateState.UpdateInstalling:
1056+
return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })];
1057+
1058+
case UpdateState.UpdateReady:
10481059
return [new MenuItem({
10491060
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
10501061
this.reportMenuActionTelemetry('RestartToUpdate');

src/vs/platform/update/common/update.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export enum State {
1414
Idle,
1515
CheckingForUpdate,
1616
UpdateAvailable,
17-
UpdateDownloaded
17+
UpdateDownloaded,
18+
UpdateInstalling,
19+
UpdateReady
1820
}
1921

2022
export enum ExplicitState {
@@ -26,18 +28,21 @@ export interface IRawUpdate {
2628
releaseNotes: string;
2729
version: string;
2830
date: Date;
31+
supportsFastUpdate?: boolean;
2932
}
3033

3134
export interface IUpdate {
3235
version: string;
3336
date?: Date;
3437
releaseNotes?: string;
3538
url?: string;
39+
supportsFastUpdate?: boolean;
3640
}
3741

3842
export interface IAutoUpdater extends NodeEventEmitter {
3943
setFeedURL(url: string): void;
4044
checkForUpdates(): void;
45+
applyUpdate?(): TPromise<void>;
4146
quitAndInstall(): void;
4247
}
4348

@@ -49,10 +54,13 @@ export interface IUpdateService {
4954
readonly onError: Event<any>;
5055
readonly onUpdateAvailable: Event<{ url: string; version: string; }>;
5156
readonly onUpdateNotAvailable: Event<boolean>;
57+
readonly onUpdateDownloaded: Event<IRawUpdate>;
58+
readonly onUpdateInstalling: Event<IRawUpdate>;
5259
readonly onUpdateReady: Event<IRawUpdate>;
5360
readonly onStateChange: Event<State>;
5461
readonly state: State;
5562

5663
checkForUpdates(explicit: boolean): TPromise<IUpdate>;
64+
applyUpdate(): TPromise<void>;
5765
quitAndInstall(): TPromise<void>;
5866
}

src/vs/platform/update/common/updateIpc.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ export interface IUpdateChannel extends IChannel {
1515
call(command: 'event:onError'): TPromise<void>;
1616
call(command: 'event:onUpdateAvailable'): TPromise<void>;
1717
call(command: 'event:onUpdateNotAvailable'): TPromise<void>;
18+
call(command: 'event:onUpdateDownloaded'): TPromise<void>;
19+
call(command: 'event:onUpdateInstalling'): TPromise<void>;
1820
call(command: 'event:onUpdateReady'): TPromise<void>;
1921
call(command: 'event:onStateChange'): TPromise<void>;
2022
call(command: 'checkForUpdates', arg: boolean): TPromise<IUpdate>;
23+
call(command: 'applyUpdate'): TPromise<void>;
2124
call(command: 'quitAndInstall'): TPromise<void>;
2225
call(command: '_getInitialState'): TPromise<State>;
2326
call(command: string, arg?: any): TPromise<any>;
@@ -32,9 +35,12 @@ export class UpdateChannel implements IUpdateChannel {
3235
case 'event:onError': return eventToCall(this.service.onError);
3336
case 'event:onUpdateAvailable': return eventToCall(this.service.onUpdateAvailable);
3437
case 'event:onUpdateNotAvailable': return eventToCall(this.service.onUpdateNotAvailable);
38+
case 'event:onUpdateDownloaded': return eventToCall(this.service.onUpdateDownloaded);
39+
case 'event:onUpdateInstalling': return eventToCall(this.service.onUpdateInstalling);
3540
case 'event:onUpdateReady': return eventToCall(this.service.onUpdateReady);
3641
case 'event:onStateChange': return eventToCall(this.service.onStateChange);
3742
case 'checkForUpdates': return this.service.checkForUpdates(arg);
43+
case 'applyUpdate': return this.service.applyUpdate();
3844
case 'quitAndInstall': return this.service.quitAndInstall();
3945
case '_getInitialState': return TPromise.as(this.service.state);
4046
}
@@ -55,6 +61,12 @@ export class UpdateChannelClient implements IUpdateService {
5561
private _onUpdateNotAvailable = eventFromCall<boolean>(this.channel, 'event:onUpdateNotAvailable');
5662
get onUpdateNotAvailable(): Event<boolean> { return this._onUpdateNotAvailable; }
5763

64+
private _onUpdateDownloaded = eventFromCall<IRawUpdate>(this.channel, 'event:onUpdateDownloaded');
65+
get onUpdateDownloaded(): Event<IRawUpdate> { return this._onUpdateDownloaded; }
66+
67+
private _onUpdateInstalling = eventFromCall<IRawUpdate>(this.channel, 'event:onUpdateInstalling');
68+
get onUpdateInstalling(): Event<IRawUpdate> { return this._onUpdateInstalling; }
69+
5870
private _onUpdateReady = eventFromCall<IRawUpdate>(this.channel, 'event:onUpdateReady');
5971
get onUpdateReady(): Event<IRawUpdate> { return this._onUpdateReady; }
6072

@@ -82,6 +94,10 @@ export class UpdateChannelClient implements IUpdateService {
8294
return this.channel.call('checkForUpdates', explicit);
8395
}
8496

97+
applyUpdate(): TPromise<void> {
98+
return this.channel.call('applyUpdate');
99+
}
100+
85101
quitAndInstall(): TPromise<void> {
86102
return this.channel.call('quitAndInstall');
87103
}

src/vs/platform/update/electron-main/auto-updater.win32.ts

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'use strict';
77

88
import * as path from 'path';
9+
import * as fs from 'fs';
910
import * as pfs from 'vs/base/node/pfs';
1011
import { checksum } from 'vs/base/node/crypto';
1112
import { EventEmitter } from 'events';
@@ -17,6 +18,7 @@ import { download, asJson } from 'vs/base/node/request';
1718
import { IRequestService } from 'vs/platform/request/node/request';
1819
import { IAutoUpdater } from 'vs/platform/update/common/update';
1920
import product from 'vs/platform/node/product';
21+
import { isActive } from 'windows-mutex';
2022

2123
interface IUpdate {
2224
url: string;
@@ -25,13 +27,35 @@ interface IUpdate {
2527
version: string;
2628
productVersion: string;
2729
hash: string;
30+
supportsFastUpdate?: boolean;
31+
}
32+
33+
function pollUntil(fn: () => boolean, timeout = 1000): TPromise<void> {
34+
return new TPromise<void>(c => {
35+
const poll = () => {
36+
if (fn()) {
37+
c(null);
38+
} else {
39+
setTimeout(poll, timeout);
40+
}
41+
};
42+
43+
poll();
44+
});
45+
}
46+
47+
interface IAvailableUpdate {
48+
packagePath: string;
49+
version: string;
50+
supportsFastUpdate: boolean;
51+
updateFilePath?: string;
2852
}
2953

3054
export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
3155

3256
private url: string = null;
3357
private currentRequest: Promise = null;
34-
private updatePackagePath: string = null;
58+
private currentUpdate: IAvailableUpdate = null;
3559

3660
constructor(
3761
@IRequestService private requestService: IRequestService
@@ -87,14 +111,21 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
87111
.then(() => updatePackagePath);
88112
});
89113
}).then(updatePackagePath => {
90-
this.updatePackagePath = updatePackagePath;
114+
const supportsFastUpdate = !!update.supportsFastUpdate;
115+
116+
this.currentUpdate = {
117+
packagePath: updatePackagePath,
118+
version: update.version,
119+
supportsFastUpdate
120+
};
91121

92122
this.emit('update-downloaded',
93123
{},
94124
update.releaseNotes,
95125
update.productVersion,
96126
new Date(),
97-
this.url
127+
this.url,
128+
supportsFastUpdate
98129
);
99130
});
100131
});
@@ -126,12 +157,45 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
126157
);
127158
}
128159

160+
applyUpdate(): TPromise<void> {
161+
if (!this.currentUpdate) {
162+
return TPromise.as(null);
163+
}
164+
165+
return this.cachePath.then(cachePath => {
166+
this.currentUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${this.currentUpdate.version}.flag`);
167+
168+
return pfs.touch(this.currentUpdate.updateFilePath).then(() => {
169+
spawn(this.currentUpdate.packagePath, ['/verysilent', '/update=FILENAME', '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
170+
detached: true,
171+
stdio: ['ignore', 'ignore', 'ignore']
172+
});
173+
174+
const readyMutexName = `${product.win32MutexName}-ready`;
175+
176+
// poll for mutex-ready
177+
pollUntil(() => isActive(readyMutexName)).then(() => {
178+
179+
// now we're ready for `quitAndInstall`
180+
this.emit('update-ready');
181+
});
182+
});
183+
});
184+
}
185+
129186
quitAndInstall(): void {
130-
if (!this.updatePackagePath) {
187+
if (!this.currentUpdate) {
188+
return;
189+
}
190+
191+
if (this.currentUpdate.supportsFastUpdate && this.currentUpdate.updateFilePath) {
192+
// let's delete the file, to signal inno setup that we want Code to start
193+
// after the update is applied. after that, just die
194+
fs.unlinkSync(this.currentUpdate.updateFilePath);
131195
return;
132196
}
133197

134-
spawn(this.updatePackagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
198+
spawn(this.currentUpdate.packagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
135199
detached: true,
136200
stdio: ['ignore', 'ignore', 'ignore']
137201
});

src/vs/platform/update/electron-main/updateService.ts

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export class UpdateService implements IUpdateService {
4545
private _onUpdateNotAvailable = new Emitter<boolean>();
4646
get onUpdateNotAvailable(): Event<boolean> { return this._onUpdateNotAvailable.event; }
4747

48+
private _onUpdateDownloaded = new Emitter<IRawUpdate>();
49+
get onUpdateDownloaded(): Event<IRawUpdate> { return this._onUpdateDownloaded.event; }
50+
51+
private _onUpdateInstalling = new Emitter<IRawUpdate>();
52+
get onUpdateInstalling(): Event<IRawUpdate> { return this._onUpdateInstalling.event; }
53+
4854
private _onUpdateReady = new Emitter<IRawUpdate>();
4955
get onUpdateReady(): Event<IRawUpdate> { return this._onUpdateReady.event; }
5056

@@ -68,14 +74,19 @@ export class UpdateService implements IUpdateService {
6874

6975
@memoize
7076
private get onRawUpdateDownloaded(): Event<IRawUpdate> {
71-
return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url) => ({ releaseNotes, version, date }));
77+
return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url, supportsFastUpdate) => ({ releaseNotes, version, date, supportsFastUpdate }));
78+
}
79+
80+
@memoize
81+
private get onRawUpdateReady(): Event<IRawUpdate> {
82+
return fromNodeEventEmitter(this.raw, 'update-ready');
7283
}
7384

7485
get state(): State {
7586
return this._state;
7687
}
7788

78-
set state(state: State) {
89+
private updateState(state: State): void {
7990
this._state = state;
8091
this._onStateChange.fire(state);
8192
}
@@ -119,7 +130,7 @@ export class UpdateService implements IUpdateService {
119130
return; // application not signed
120131
}
121132

122-
this.state = State.Idle;
133+
this.updateState(State.Idle);
123134

124135
// Start checking for updates after 30 seconds
125136
this.scheduleCheckForUpdates(30 * 1000)
@@ -157,27 +168,28 @@ export class UpdateService implements IUpdateService {
157168
}
158169

159170
this._onCheckForUpdate.fire();
160-
this.state = State.CheckingForUpdate;
171+
this.updateState(State.CheckingForUpdate);
161172

162173
const listeners: IDisposable[] = [];
163174
const result = new TPromise<IUpdate>((c, e) => {
164175
once(this.onRawError)(e, null, listeners);
165176
once(this.onRawUpdateNotAvailable)(() => c(null), null, listeners);
166177
once(this.onRawUpdateAvailable)(({ url, version }) => url && c({ url, version }), null, listeners);
167-
once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes }) => c({ version, date, releaseNotes }), null, listeners);
178+
once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes, supportsFastUpdate }) => c({ version, date, releaseNotes, supportsFastUpdate }), null, listeners);
168179

169180
this.raw.checkForUpdates();
170181
}).then(update => {
171182
if (!update) {
172183
this._onUpdateNotAvailable.fire(explicit);
173-
this.state = State.Idle;
184+
this.updateState(State.Idle);
174185
/* __GDPR__
175186
"update:notAvailable" : {
176187
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
177188
}
178189
*/
179190
this.telemetryService.publicLog('update:notAvailable', { explicit });
180191

192+
// LINUX
181193
} else if (update.url) {
182194
const data: IUpdate = {
183195
url: update.url,
@@ -188,7 +200,7 @@ export class UpdateService implements IUpdateService {
188200

189201
this._availableUpdate = data;
190202
this._onUpdateAvailable.fire({ url: update.url, version: update.version });
191-
this.state = State.UpdateAvailable;
203+
this.updateState(State.UpdateAvailable);
192204
/* __GDPR__
193205
"update:available" : {
194206
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
@@ -202,12 +214,20 @@ export class UpdateService implements IUpdateService {
202214
const data: IRawUpdate = {
203215
releaseNotes: update.releaseNotes,
204216
version: update.version,
205-
date: update.date
217+
date: update.date,
218+
supportsFastUpdate: update.supportsFastUpdate
206219
};
207220

208221
this._availableUpdate = data;
209-
this._onUpdateReady.fire(data);
210-
this.state = State.UpdateDownloaded;
222+
223+
if (update.supportsFastUpdate) {
224+
this._onUpdateDownloaded.fire(data);
225+
this.updateState(State.UpdateDownloaded);
226+
} else {
227+
this._onUpdateReady.fire(data);
228+
this.updateState(State.UpdateReady);
229+
}
230+
211231
/* __GDPR__
212232
"update:downloaded" : {
213233
"version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -218,7 +238,7 @@ export class UpdateService implements IUpdateService {
218238

219239
return update;
220240
}, err => {
221-
this.state = State.Idle;
241+
this.updateState(State.Idle);
222242
return TPromise.wrapError<IUpdate>(err);
223243
});
224244

@@ -260,6 +280,26 @@ export class UpdateService implements IUpdateService {
260280
return process.platform;
261281
}
262282

283+
// for windows fast updates
284+
applyUpdate(): TPromise<void> {
285+
if (this.state !== State.UpdateDownloaded) {
286+
return TPromise.as(null);
287+
}
288+
289+
if (!this.raw.applyUpdate) {
290+
return TPromise.as(null);
291+
}
292+
293+
once(this.onRawUpdateReady)(() => {
294+
this._onUpdateReady.fire(this._availableUpdate as IRawUpdate);
295+
this.updateState(State.UpdateReady);
296+
});
297+
298+
this._onUpdateInstalling.fire(this._availableUpdate as IRawUpdate);
299+
this.updateState(State.UpdateInstalling);
300+
return this.raw.applyUpdate();
301+
}
302+
263303
quitAndInstall(): TPromise<void> {
264304
if (!this._availableUpdate) {
265305
return TPromise.as(null);

0 commit comments

Comments
 (0)