From 460e00c5756596858d2a2038a4cc225b836562a4 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 18 Jan 2018 08:59:43 +0100 Subject: [PATCH 01/21] update inno --- build/win32/code.iss | 64 +++++++++++++++++++++++++++++++---- build/win32/inno_updater.exe | Bin 0 -> 178176 bytes 2 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 build/win32/inno_updater.exe diff --git a/build/win32/code.iss b/build/win32/code.iss index f4374ee099d51..ed747315c63ff 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -18,7 +18,7 @@ OutputDir={#OutputDir} OutputBaseFilename=VSCodeSetup Compression=lzma SolidCompression=yes -AppMutex={#AppMutex} +AppMutex={code:GetAppMutex} SetupMutex={#AppMutex}setup WizardImageFile={#RepoDir}\resources\win32\inno-big.bmp WizardSmallImageFile={#RepoDir}\resources\win32\inno-small.bmp @@ -47,11 +47,15 @@ Name: "simplifiedChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh Name: "traditionalChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-tw.isl,{#RepoDir}\build\win32\i18n\messages.zh-tw.isl" {#LocalizedLanguageFile("cht")} [InstallDelete] -Type: filesandordirs; Name: {app}\resources\app\out -Type: filesandordirs; Name: {app}\resources\app\plugins -Type: filesandordirs; Name: {app}\resources\app\extensions -Type: filesandordirs; Name: {app}\resources\app\node_modules -Type: files; Name: {app}\resources\app\Credits_45.0.2454.85.html +Type: filesandordirs; Name: "{app}\resources\app\out" +Type: filesandordirs; Name: "{app}\resources\app\plugins" +Type: filesandordirs; Name: "{app}\resources\app\extensions" +Type: filesandordirs; Name: "{app}\resources\app\node_modules" +Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html" + +[UninstallDelete] +Type: filesandordirs; Name: "{app}\_" +Type: filesandordirs; Name: "{app}\old" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked @@ -63,7 +67,8 @@ Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent [Files] -Source: "*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "*"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#RepoDir}\build\win32\inno_updater.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}" @@ -955,6 +960,51 @@ begin Result := not WizardSilent(); end; +// Updates +function IsUpdate(): Boolean; +begin + Result := ExpandConstant('{param:update|false}') = 'true'; +end; + +function GetAppMutex(Value: string): string; +begin + if IsUpdate() then + Result := '' + else + Result := '{#AppMutex}'; +end; + +function GetDestDir(Value: string): string; +begin + if IsUpdate() then + Result := ExpandConstant('{app}\_') + else + Result := ExpandConstant('{app}'); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + UpdateResultCode: Integer; +begin + if (CurStep = ssPostInstall) and (ExpandConstant('{param:update|false}') = 'true') then + begin + CreateMutex('{#AppMutex}-ready'); + + while (CheckForMutexes('{#AppMutex}')) do + begin + Log('Application is still running, waiting'); + Sleep(1000); + end; + + Sleep(1000); + + if Exec(ExpandConstant('{app}\inno_updater.exe'), ExpandConstant('--apply-update _ "{app}\unins000.dat"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode) then + Log('Update applied successfully!') + else + Log('Failed to apply update!'); + end; +end; + // http://stackoverflow.com/a/23838239/261019 procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String); var diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe new file mode 100644 index 0000000000000000000000000000000000000000..3dd7e34c20ba4a75f6dd9b6f593ec183293658fc GIT binary patch literal 178176 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 Date: Thu, 18 Jan 2018 14:56:13 +0100 Subject: [PATCH 02/21] update inno updater, selectively launch code after update --- build/win32/code.iss | 39 ++++++++++++++++++++++------------- build/win32/inno_updater.exe | Bin 178176 -> 180224 bytes 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index ed747315c63ff..7e8326e43134e 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -47,11 +47,11 @@ Name: "simplifiedChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh Name: "traditionalChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-tw.isl,{#RepoDir}\build\win32\i18n\messages.zh-tw.isl" {#LocalizedLanguageFile("cht")} [InstallDelete] -Type: filesandordirs; Name: "{app}\resources\app\out" -Type: filesandordirs; Name: "{app}\resources\app\plugins" -Type: filesandordirs; Name: "{app}\resources\app\extensions" -Type: filesandordirs; Name: "{app}\resources\app\node_modules" -Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html" +Type: filesandordirs; Name: "{app}\resources\app\out"; Check: IsNotUpdate +Type: filesandordirs; Name: "{app}\resources\app\plugins"; Check: IsNotUpdate +Type: filesandordirs; Name: "{app}\resources\app\extensions"; Check: IsNotUpdate +Type: filesandordirs; Name: "{app}\resources\app\node_modules"; Check: IsNotUpdate +Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNotUpdate [UninstallDelete] Type: filesandordirs; Name: "{app}\_" @@ -76,7 +76,7 @@ Name: "{commondesktop}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks: quicklaunchicon; AppUserModelID: "{#AppUserId}" [Run] -Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Tasks: runcode; Flags: nowait postinstall; Check: WizardSilent +Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Tasks: runcode; Flags: nowait postinstall; Check: ShouldRunAfterUpdate Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Flags: nowait postinstall; Check: WizardNotSilent [Registry] @@ -963,7 +963,23 @@ end; // Updates function IsUpdate(): Boolean; begin - Result := ExpandConstant('{param:update|false}') = 'true'; + Result := ExpandConstant('{param:update|false}') <> 'false'; +end; + +function IsNotUpdate(): Boolean; +begin + Result := not IsUpdate(); +end; + +// VS Code will create a flag file before the update starts (/update=C:\foo\bar) +// - if the file exists at this point, the user quit Code before the update finished, so don't start Code after update +// - otherwise, the user has accepted to apply the update and Code should start +function ShouldRunAfterUpdate(): Boolean; +begin + if IsUpdate() then + Result := not FileExists(ExpandConstant('{param:update}')) + else + Result := False; end; function GetAppMutex(Value: string): string; @@ -986,7 +1002,7 @@ procedure CurStepChanged(CurStep: TSetupStep); var UpdateResultCode: Integer; begin - if (CurStep = ssPostInstall) and (ExpandConstant('{param:update|false}') = 'true') then + if IsUpdate() and (CurStep = ssPostInstall) then begin CreateMutex('{#AppMutex}-ready'); @@ -996,12 +1012,7 @@ begin Sleep(1000); end; - Sleep(1000); - - if Exec(ExpandConstant('{app}\inno_updater.exe'), ExpandConstant('--apply-update _ "{app}\unins000.dat"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode) then - Log('Update applied successfully!') - else - Log('Failed to apply update!'); + Exec(ExpandConstant('{app}\inno_updater.exe'), ExpandConstant('--apply-update _ "{app}\unins000.dat"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); end; end; diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index 3dd7e34c20ba4a75f6dd9b6f593ec183293658fc..d82b1430c6407c1bf6a21418629564ddd599956e 100644 GIT binary patch delta 50555 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

_^WXMXCW-uUssGnLfudb3yA@%)fJ%&sWmCHY_U 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 delta 48210 zcmce<3w(@6_Xoc7#7cr>6Xb$K5*CST;vSdElCaT@`>klJ7nio$Dv4XMAqm#nmJS|T zE&6IviV6~nCT^h@x3=yov}#tWf>u*h{@*jtZg$gp-~apjy#LSN=OfQEGiT16Idg7v zW_C9-vwCJ}wMF-gdR^;*qlW8T|4MZYbk&u>8oIWMPM4`w9_+~LvL@9^QBy_M@F!Wj z7NwgMU8$>=e4J2p{Q|ryiWS{@bcy~z(G6N0Q_DYdm7vF}s~>7*JJUBW4| ztuWPACKjP!E-EO;zf!ZIu&19ntBhrYi-{hfQghb9*k_+{jyCeXRazUGd0NVZ(a0B7 ziKscDKd?9~##mE+7={(`Tlu~!&8^28Dmt+je=3ICiGtZaD_*Sgu3fFwCW*z~wF{{B zF{F+601;^&LE5vZTBN-n_a29F=& z3hG@nyJ!Dh1755j-*8k6dgK(Ow@El^&N-T%Qu3lm^8lBL0Sy%X*}+hrQ?-M#Rmb;L zt)sMy=I5%u+F(qy*0Yt?(^T@h7zkt)tQi)=$N4_lY*#tc8LFEd0kJ7y-i7P3WJ@-7 zG}~qvlgze3Ml*4`qlk~_Ub?bz4OM-$A$hsls*}p8p8XJO`At*J-5Y#`~ z5VypbNmX$c;{-!ovT>CCjd-)&6l4b7@dOyhU-WMjy||xFx4R|)QxXbN5{}w#i*BHF zk$Lt(Lio%9zR~|VR>}jay{$yo=O0vS*z7^1qD!+U8(qtFhl;k}EcSF`lDLe5i+Mtv zjpV;n8?0RTiT9}9$I#5vR4R;-e0BAgm2DBcMveAC(<2CDiVNYy2tJ@jH{(}ND7xMC zK!fN%iRsL?!=etr5>A=&!%+!M5%nVYjvBEnkN;3(Q2iTMiF<}veN1QYDwx(rvhGwJ zVjb^S-DGH1uYx#GP>-(;00nhBIWxqo@#hr>CgD5hJnWBpmTQvn%~venJyLc`y{+F z_D$j37}T64^Zr54)qVN03+b8;m5>eyy6BogyLQ?Pq|PLXpKH+yksR|AK_)|IZyaqy_@Y{` zD2288U$xo=EvW51lR33{_u8F}Sc`PR&PvV z1d5(X3`9woJrJeBgo5*;XA<`Fu1V}~U1<`Ng89afZo&I8H7R!%N3yY>XejuD5R-8v zYIhrm*&n~8k@#B8$|O#x$w!4YSNxLsTcK}SbAv0O0ESw~p5L{TAA| zuDrE6r0yGIFvwd>7c+mspcBn(E^H(Dqbvget zv^Hy2kS;wGfq|c>>wzp2wWxpAtXQovTKrYbi-3F8__4^wY%2dfvWf8^S~N8EFrc4U z#{j~ME$l6_8z45hu)YS`nXo;iuYmWBDgLJn^2j~KdaxghHQci z4iiO3`1Sf78{B-0R!!@-0!#Lfem`+jRMQ%=N%re-gind;*dTJ4XFUJM^&2Gn$tKxv zT@m+aYeuAT9JLQ+{m_FM^kV z#;h5}QOxYLJCTvlLw+qwDIMOW9ld$uHu_w5?L?)b4JURC+b2I0!}{akGj&D!z&1`QfEFcZni#%Rp7I=y4A@fQ3^ zGUZcrEuJgohDP=Me|(kNLVkFaw{O&x_i5OX4de3~X0va34P#Tbo_8=di%fn77CH$DeQVyl)bsS@B~L|Efvh^YOZhbu2D1h!_;T zLZ@&PMSusSOjJd|Z0{9hPCPrggW1v7&ulv{nq7i6+WUr^9aZX>ZN0+HK7IWxwmT35 z@o^3QYi$4UrHD@v|JFcMmSnc|^h+oxX(Q&AGd?t~x9H5<^cU0Z_R}C!Z9=U3i}9w-*3>NWCyvp2+wP>H z{<&@ZQnLjigC7!Z;IP=UjnP<7k@}G3&S?HDGXxv3D%8VlyCbTM<~Le2Rr2fRhPG_Z z*lymV)w!Ch%QV7hXG9kG!?N6#@ozDM?hhAI+862n$o(oIfU(;ATp7zPU}DQpotZHshlDGtif{$5;l^bx@{PeskdxT*#s6w-p|b7t2P><$J*s;xi_ zeU|^w(Wca>!?$&4*r6-Tmgu@nxeghs_jC~wB1Tah{9wX^!++X0h?;5O_ zo-_6@EVmYbhl z?Sa_Q!$TkR0uK-KXFD~mQ9@0m5W@dwzO>VPW%hj@)j84d;!Q6Zdg&$~+d0J$Y1?$;QB7#$dpoMcXahrNqv2N4Y{jDCpR{0z z#BYc>{LLC6VZcB6VzC zlKW2Ac-HQ|^B-NWqsr6uGD_k*Hxy~mH%|V?69a<3rBDiNh?NT}4({W_yJfYz3{iA% zm0hHKjVcemgD8nzZYaXHP4JpYE24toi^n7zUJnST7sIW4Sm&-0uF8;xxnCxYXUTK1 z&vxwqO5p^jplGR>*EQ$AUyG}|rG#pt_ydiMuPusFo=dFlHMwrJPud zQ^m(u_|%l<##q86W$p#lG28);15$iHBZ_gb-;GcY)4u`9Q;5l-VwuP z!*0q}K@P-5*?@uPz+BS6^U3r&78*106fKJR?VjHzlwm9D#VaR@9$sBRNu2gTl!@cT z{P$ib68^wm)vK34(bKCKCGounqD&mS%mgx*6~Lc2?q#%YYAXPo*diCP|rGEw6a&wp}7!YkN^dQmqLMNh9mD2Y@L zM49M*k*?*0n>U+!(akgUaXGP3ezNKHX;=hCc-Y+U_ z)=xlVA8Cvwg@n<#N_zF}7ra^jrNbU#H|3gcvG^SY2rV4zHFq^xgC9^>gs6TTqBhe= z(=(=LX$$)NJxmYT7!9ka^}RM*igm>p&cg>JEA4;e&kTsE*%kdg^Aw`nX})+s3*$Zn z23n%R*g~7 z*71vE{cVI;q87bk?+~v`uP4sKZ@Q$nOni$%iEqMDaql?4|5S|i+;OJ++BDQO#5CA6 zXvomP1M}y{V2m$8pE>RzETeU$aRO@SZHaM|>usU2pX)8#m_%;|>mFmg_Ojg=jTf`+ z+f>`JRC_3dJg6w!m`$YPpJ8Z$AuiiE0Y%i#x8h0h#~2irNDwpL7P^2BXB!vc6&s+1 z#uM;{7Cfn^SQ!WM?AwiJQAtq|H8Ml?N1f1i;u~$Tbm&Jm?!`=(FUs~sNJ#M7+y^$rKW%p{B1D^YDJU~1!l(*iN~~yT1oD2Y=J8U zDbxy5#<>Q|q|P)`JS8HB20{qXX4RSUlQ23ISp0dX5{4%U1A@D$G#uipQFq~{VC?1S z9x7-6Q+_6Ji2TY3QY#_QqC~TOlZF9^=L3_K7~w}WdQHp=<(SGS+sj#PG7h62wv{qw zCmLyXIC1bX%pE8PUFf2Ep)m+86rxJ9?F;3gX4^uej(XS^T8Rl5ynhw5W86b~9otCb z46{wL1_NwzA^r=9=MiE@e>LT9a9#I6*u(qxrHh#7h)|M9Jr^qj+!EklrasX^Izq`00Jtl_c@ztOHfX zm<*2!&9fg6KMgqvlu!0rNrH0WzX)SVf;fRPb@-Q0XGJ81-&KeD;a^KbRJLp}>`TVWQoOQU0Iq`JHraw+#?&%mivB;1!0m}r! zClvGZkxGG~=HOmBj}5r&-5_Ow3wmr6lxB7{D@3+&qnluY@QdoHPm#_^?AcWV?{ zpOLK%(Q39MeIj(2DfB((>vlTb>>0)xb--SOBTR}$c7^t9TC86K`%F3@wG5miIi)oT z0;ysvJixAKi8vVx`o|RnWrob_iBMZJ#A7S9PGOly6?V&J5{lw)XBx!YcJOGY;W{|~+X+*oO_ z{pe-iqv_3T1amrMHuf0LB0x4|S+AhcV*3j*MHA7w$P)%oSkV?`7vF=N)7sFSlZnwp zEHQ{C140oQrNZiFS02?D@a4x0d562&0gjl)FfR&KrtNS-0mWT_;e{%*%~p1#yx6oo zsHC@PdrnD;Y5TmAB-8f!C7n&%XP2}$ZI3QVFm2B&X>Qs+r=*E#yS=1=X?t);!p;aKcTe@#}%KR67BMdti8c&g?&$swS8S=CljxpIRzLDng{baC6 zjLepMxsRq>27oKfC>85au-H48?N(UYm2ya#u0#r|Ol&`=Ere7-?7`Qz(ok!zGsh2W zTjBV!3LZ(vl65CR>qOKG>ws%0(J92YP(VZWX==>5kZu%l9xBd?mP9iSBWjYbA`N#& zqIbR&1Spv{zScs@M!HY`DlDj$F}oK5EN&M@-Nk0>9`3w&uVp1xl*gkPLrd#a!3t+Q?5|srMe0u|Ae)Q>nVbuT z>~E+G^ddq;BRO6X_cfwSIapE6iTuvrS+GYN;IS+?Pb>C&y~-@Go&cK+K^g}ro-1&% zjh?Ov=Um}|*0-!D!;U}yB{Ugisn|#MhsjCvN09dV6>0N8HtCv1pzW%rCwDF@U|$^nhJm6f!oHCv8KGUYV43Mjm2F%ZBP(o8)4 z5(+G?;4Hf!y<75vwUQdc&TJzE$%Vy1q}em^>R)KK7G8;l22P_P{Ew4b9wCMpa^{6Y z$XDWzY4)`s80FNgzk)KlLmn))7jcH4y$evHI_6Lz7(p~4a+m)?6Ioa(Yv2!Q1M$`w z2m1%(H~J>bY&7+M5KjIV`H2ICgvhghJcM$p?ND0IO+)sN&|=f!6kX7QY~x(uG0ji$ zHRNFt?4b(a0ZKuIa7mzrWnMu0@otI z4xhvN?_gxh%B%< z?uNzY6z7H#A&BBJoCS+5QQ9t9Z1>Ez%h)oP@52O-;y}WL7^e&c*wX-U5-+yGgo2!- z8G}u>gQK)g;v95NSPi%z&#u~cNC=&YhG8YSE5notWAq(}Txs+nQJ{yt+; z4YLZ&4(oQ8%*uRj{HaPrh-mCwX#I%-=C-1SoOZ1MhHA2{RSY?esfd7p?3USfgaU+q zxISgb4v-DorSZn_WM$2U?6n|}BHDh~hMNO%EJE81*G7XkZ!x@1Q~&@H5uqH%+2(y5 zUNLiG5nhk;TjL|G{}M1bRe?i<)+R|gEPmKSQ}x)JVwBzX=1~hs!~enFSR9SU{tBJ5 zjfO1GF!s}>kE|@=R!zjmctqSRo{Wb-{&&Z{9oLR^U|aN zHK9pk@dp58)|_?mE<%CFqZ@%JnQh~7JpJhNinSD0@drv|kEET0CtVF*kS8TY5$S!= z2$_-Q+GTy1x0zcGs0?4|800>ZHhQ$*cSjAT{9(`sF`D)(a>poTost?@TCLG4%xI-h zqOohDWG6Nm2`|l+Oon^HP7{uZXunR~rSk(2*;<*PA(Kk=Ku^QC#=e0ty<&E*k;Ub3 zRBE%aB8`$0hLaz%yEIGE3~Y7eESAW`kem&I1yerGlX3P!7_%P1$?q|mF>fNMOmYEQ zp>e^r52xWg{hmWVC>D%}+@hf|6Yy3ID;Wk80GvJal<4^b*RG^c!Xx@p=HD8OL2((X z7czC;vYCpqTQ-+A1C9T+Y|iwPLTRPIHU?By7Rw65JF~*@euE=Fw?BvB-G`#*jD9Cd zVxt>^?Cx4>xBX%6jDtb+G4F4j$&_7yet?|CNG7f!Vgs9Ezh)5)hP4B=T7c90?L-x1S2jbe1=mcC(HyowiyP-h3Frfy&H8tEyS}dOD*mb#8+AZ{#LICJi zVMC4{Ht(M@uSuiazdWK-?4CsWr*LI^-;~%X5eenYI zu-!?x=NteSb7!QdH38^n$gW3eA*`f*kg*@Fzgd_7P5B2#E%uehVwyBKEn^|M1MiyW z#h&gc(AtJL^>IW%6{4l54?=eEgD7|BVBl70CRk>}UTJCkCK@)&mlNc00W|(Gmq(SDL2Iiq>#X zYpDpPR$!-fq1cV2lV*I`av zw$qRuhO~k+3V%G-^H1ywO!-za(ImcDhqFA(Amw*&Y4gCY36TD4p7$C;O|e4 zRB#7o4<74l@C%dcD4*2e_a?Wl_QOM*_vbB4hPr;exKaGGjfmWZ#&{4wlCqXmE&eJB zkPKM~mLyRl>4xb~K5t43rRJY}=alHU(z5dM%m+6!8k!ToK{)fhIq^$F_61Z@IwtX( z^u)}Wx>b|gIIX;VO6S1~(LgZ-g2d}GoYmdhB+<#Ff9nK8Pn4lvg=ck zX8#QNJ%FqW-ENYT|R`Mn;mFF4ytsH|p>wUan>x?KKEI-w+f%V%`TO(Zd5#Cc2>T z_mqU&8jD@Y8t-I41uc)SFv;#6)jy&sD|PlnOybkMnDf60r^F&O(j0Ia7h>1?Hxh6v=tU|8QMv_G!>`&Gh-lm z7TW_drybq#RN4hB*rBQzA6I%ETS$I9vHsgAyW^<~(J%#wr>y^7a^Y1iprY(pGDP`t zb`%@gS1aPFT5@O^68ARU3AH)MzTOnD`8z_-rQd~|e++3W}+O^N- zoT28N0&;E|ceG4+MqOt*=Ak*ozqS1*CyAUE$?$3nW~Eqa9x)8I=s&wZ?Yff|&@IJw zimIi_7k;eSAQU}UT`edHlN*ATtP=`Ep0uVQ z&**6^6Oq&i^8Ss(JMjaWrKrm^EVd*tlNkYSRTOdaHARu#Nm0niuC1mGNR`;veEA<@ zJlE1JB8T$=1EmDF@aS%X1H!Ubn8JeeB~f$(`zA7HKu zWS}(%Ed8v$qJahl3SQ4k3&dN($d_uKIk`OAIGtW#}PlVl%($Oj(7}? z5a{6IAC9;c;vLG5u|nJzF+?TtRZ~KgY7fXQHx>6E4+h{5%oCY=}|yER(khXN@xqO`s^}!Za&HBaq3hvN#sI4e^_`Dt^cNekL z3sSsAE#S&HB1GfkD7=aZ-as!A5S;7;T8I>j4Y}kZKRQM8q@Uygf;p?3E#5!-@WU0>M50FtOg#H(TDG5G!T(nMk)HbjUyw)eS)zU@Ho# zS+md>l4@@+UiNN#0R=;>b|1$Ot6k7ZHpJ4E9n2qZ+BF`$Xg7QCqFwf(j|9c&bd~Nh z{hJ8Y9W<6qmJw=6LrOTmjVZYfEF5^nfkmM_udtz@oma$ame<`IeBJi3i)X~wjVO5H z*cdXJ;@E78W06>-EuoaP%`PY{nAPIOrGU!B#U}_KrL;*WCw6xw_&P!SYV|Q>-ykyZ z=@OZ^Hq68JQ76hj^2no;l4iNgd4p_;`$P$tJR{etG_qAjtoJkjIr8(ak%vDn=4fNV zd9GLf{3S0Epc2DT^r*xDlu~VfiDWkrd9ki2K0#ZxJ(g{2%5+S4E_D7TndAkQm zp!CvJ+z3c|{#xtiv$hAN2+YDnx@r8uC7wdjR!#-tA&g%5L?jAWN&^}5 z>J>exgAYog>|Yg9C&XWQ4gQBJkGj^*0p3jBYcip-Fav(#+F z6{D|Xqw02WwZ)bXUA!YJH%xqtVugz{OYK9jLDm`Con>_cQvzlN3KppHgw(>H^{I}Y zQINH66I0WWT`er{mRfkLa~h*p{jO+C%!0(8>*`G-OjLAqqaq3JibAUGU|P;khU^fu zrrEBQ7)*Qo&@UyS+_aZodONCgfQ-1VIk=sl>!7DXO%SP3c3OP-A@q2Ub_X&2bz}fx z0ci0l(gGSGuD?-dJ#&S4-(Dx zAd5Z8`=TVBb;;xvjScTh*?V|U-M_slxflQpHzw&CB|SMkiFmS=^5!I+BU5S*w?53` zorg5)LT!f$=R{D*x9qG%FsP<14Th!B>7NCm1ikMrz=Gma-=cK2mKDrL2a9d3aWw(a z9+tv`^(cr;K*-a)XhK;i=2%f7jYJ|1f`~zjL^W$pkp|h(d>z1XE$lVzq>q9ROr{hM zAaSpRe)LyBg&uOVPMcDYMq~K}Vc?%OQR7*8Z-xolV#dp>5!TkAJvHl4w0kM(J_b$7 z;#eyq7KBkL566%h^CL+6tHm&2K!DHbr`czgn(P^6V=yQMHx<{V5H-^g-beGGjbI~5 z2-wJxx&yTuBqC|h^Cz~867kG4=SvyRfu~iNe*Nkj+PmJd=B4`QR*{lTv;v0gY?>~<6@h$ykE4}s=Yv?xg*(Xi-|_}EkC?&r%S5{%Ok`h&j@8=DUBd+ z)?{BOFUPKk7Ra@E#Ini(p-3d5^smC+AkAq`BjPmawK$B4q(?Hb2|^{olpVoV#%v#C z97X=-<_0VEaAC!TrwF0<&~jE(tDxn~nSao-@;}gWUhK7a)6%>S?I9}Dl1&#Lk#Zt> zU@J*Oa1K6_wjfw9IZs5eMv=bKi~rM5AnxD_mX?5WjyqpL*&glcaRs+D2r@hWgistp-|2 z`G*HWBhgVVftL-WrLtD;NtW=t_q1+CbQ9B1@HTpHqAYD*YqVCfc`zc<=HdBwn};g0 zd3Z=%1)vW38}TRNdH6A`3sr!y`FP4kd`K-i3D6b71!qP;%YT-U=N@Kr?u|d`h_o~x^ z5;n*X%?Af5KH3L@Ery=B)%JrW?y%c#^mcTyGI5K7Z$Obpmpwrb^)$>)A=J1ui-H?_ z)*}x#To=4+i96uN*2mGs7p7FKlK39^NO2+x5E*Fr;(;$2*rgz7bkWA~pM>>w7=7u& z%3v31QjsMpH)$~%Y0dvVX@6Mairl32b+oNw!LsQqNYiqLW*csvX|&YTDSr@v zfz~aDSl@$Hji;1nvG@RGx22Tg4j(6;1rCHh`Zx3GdFv>JcuD-k)7!1vDY~G$;I8ZH zk4S$0<8}3C5S`2T0}h@3;XX5@2x3jw)eSq-bbq^Oj;hL6)FqVbhPu`3CVSdHuc#lw z74`2kHdb6whwRWO?)lNEuJ^C8C4e$*W}xx52-JpdZ!t%lUoD!$6`(g8|b zMH2(E`x7wXAXPVH-^N3Zil-1Zt&1NZDXB1Znaiw7r53NFNPZU|@d*xO& zuBBcU{|pLR-sC_reP$VX6FAM9Vz(QJb^y63JSK6n%nd_x93>nQl_zmB#nbG{o4i60 zGH)`%yNz6c=!=@vEGtqYR^&!Hh)!+@npGR>NP&zY`_N774I|x+G_u*b33U|ZQ$%XO z#n5b8E|ldnP7Cg_`RcF_(FeqW#Ctc^QYV($=~KfMT>*-mzZGd1?MA(Ohk(8{y!#>` z)`{W&=$naZ>MEa}VQ=sC8W#77bRtT2rF@?jQ=`3EioZ!6%PPP__7Wy{uvB0Np_QhsjUTXuNVS44D({9V(TR)& z#3FED6YK;&a_#t>q(}6bBdcN5v$)0T;km z?b=w4iLNT^U0lY&NMGZzTeA8plovF>S;*PCZ9EvG1&9ILK(})aM#m=-Q*wa^+;`LE z4kY9@k1Cx_VE$;$sk7G-Kadl|1zw`wxgLKELB0G8L4CswK|^Ed`?iR$f8RZVGvBy6 zQa|m(@7r*wKn(t`x~tyZ!E>ulO!~j{AMM>=ehX1s*&UR@SXaN1tf{4d133}T#pAQoW7I}i(v72%H?ojekGjCR4uc8yJu=|s`RJDSbh1dVV3TuJz^-%Nw5ixUjde+71k5nmGGJBhwCB7~y zNIiC2iB|doi8+5k4!leuttq;hy-;c2diB&?*Sz8@Fz)>MAM+{@2|)NydHXw$tt3kS zt-O7;!!xhlaQ8T*f%BG3{!KRNzsF;D9cua4OjpZrIRkxXJhTrH+uSWDFafL74>9*u zvPTpljl_|#yvVG{xOT-;R7J8}j&p4*wz2aEyP;dUq2+xkGPx8VoLxxmyQ@GLEr|Q# zo}?mzM!G)}Aqx~B%Pw@4j*f?=F?IJ(Y%E7}!+3#|tdyZOCMY)4sg@J_rNz0ds z7w2e%Wn}qi3ok7nAh4p%U6M*2oTXOWaCr<+xHpjRpO1y9?F~~OF!E*>ji!CzLL2S{ zFR0y=ZV?gLv@&86SZXe`7DtoMb4goG+ybdED9s4CG>d>`xCud_f3!%$KZUM8MW4Z{ za1-v&gH*VQP?x2Y#wi3H#af9=JizK80i}&A6K{@yau<~Z>>Nsr!w0vTU@C=I@ZpXn z#zuJc|I%D|HQK((7=wb@|9c`z=9GY&d>zdkl1vX+ObKKY=0XvVNngv+QCml>(rz!dIq{tlkMm)DA-@$Nj{F{$gVIqy#@)5>0(C~Y)|@2V z>g5wdy@A4TQVlvLLBKV&Id@f?rgVH{?8#~j`ctEfDhZS1->;AbbnRHt^@GQCts}eQ z69bj{cB9h@N5EfLU~SkJTz#drau54M8HW3tv2xDVy3%z9t!wP#y8f$jSF1+yDfE^5 zG@0HUW0+LtSCQd$9y}sxdbtAei7Of_GQox2ZknESA*M>_LExihkU=W=LLnBW)zrmd zDr$j{HAIFxe_t}1oHt+4cayjA(_;{E9>-eJD5Zka;vr5rQG#F*1#a`;hj))jXbxX6 z6?^`6_uT)j?w9}W8Hdh8Dk1pq`nd<}{`bDt3QGQOh};PO*QfP=MeslNw|cF`|34fU zfhFjQ{}%l3b)a#1`Dnbf#Q&G^zYYF(#Q)dve-!@r$NzNv=iwjCRgUHI)gNU=GsH8L zxgSLdrg578NyZ+Oq|TxLv6%Pxc#JaXW4`g@Im+c1b6c$|U@U-_u8(IIdCZ2^HH)D~ zW{3PXJ!!NOxA4&$dMh(?`DYs@)~w@cC66Xv+ML_&lVn!&$Bh-eWO!BUQ*Pflo9TJL zro_-k`0hly4(?|z|HgSy>RL$EBe^3tean=P>bVJTnx|};!{YdnEsd17hw)om z>MO55#SL4J4$WQaZ2c+j?1N~?9;w5@5aU?dt#I-Bi%qRsZ6vAEP9y^hb? z)>L`;G5=&+Hzn~hFWJ^w`DhD|%8gR4{lJrQdj%fS6L(I@uQGh9kT1<`7QB$Eum(xx zr;q^-6m|K@+|A0~Dtz|#xX$?MHJW%sJa#vvi6%6Hxuvh;oxdD5249&HPkm#>Q*I#oX=QAQIn3aa2}Lb)u*Sc zp=F-Yry3g89ATmUi$SSfU&0igo|ove1<$Q{2}-lI{KvdeA!~4ThEC1XN8qoL-Uf{O zc?}Ap}_t&1bfytTq>=tXDu3Aw0;`Vke7MURuFZ3KNyl zD|lvMoYG?j-&j~jF|XilmaSN?U1zqh}R zGT|WKxxcAmSGYO6YAKUz8g0RVfhDhg<>>v1!-O z4vt2Zz1?S}d}+~>UD5#{hGPF|wnq+=z_`IV5|VsaKMF-u>#tBXvD`!~H)MANDiGV? z9UgHcw%%&A8gdxb+4Tn`hJuU)wBP!WKYJv$lY^=p0f%qlQ2m7SLkjgA0r@D2_uz0r z$V$9R5|LBDsB2LqREXV769OJpLZt{cPeiqOWwHdcuzAgR zB8}?^>9>ggdNj7)F+dq|I%~AtMuE;=XvUFic*2*lQJ+(lBjC52L<@MI^9>>}XLorU zvEAvLeCn5}Y4rf(^>rX-n~N$R)Pt%Y=sSbw>7NFQ!}{0om#Dghr#Dr_kV%Xh|27Z& zDz@Hcv>I|YP@P?Wl0aQl{k4ks|0?#W_o&Jd@ZB$xstbmknnYCt=mKi@p;oM^PJP>b zhdg3Y`@^x(38A9p^2*LyF z(}8xf_IAoIe9+f@lx-z^?bmIU872HYO3h1n({I`;_ipe}-}F&FzQMPC(?uC~ga3+B z_znKVu{5R7$sNZmJ$-P;$Pfh$b@A@5lX#bGd!#YXg5qKQ)A&o||A41A^Ma|`5fWwR zQO9HJy^mHy&Uw;tyS{)xUCjICJ^uXh*ysgRI5_i{4f`4+n zk1|s5`^VcVjRlWC(MLIbooAlt*sTiO2uyG!9=quy6rv$qr8L3sz6l2q1FVbTrvS1W zkKHW^MCS(SHG}_gqPcSJUEb(qhmd$1h_I!shdJ8nGDnAU`5Px=l)ARuB`52$pD8m7XO!I(z^RU2_{g3<5|xMbeE>!irO(z$o+RS0l0FOku*~CkPQ`|O z35bj*0dgBJZeBz#=Lz4&hJ8*|j(|Ns$)%hvc^0|(CqCiZKFTvc@q%xUDts!pfA>;M zs~@#-1fJf!KTUlcA#pi8(FY*Star4za&r>@9!<;!@iG=TS8TifTdUEc-H|3 z|8*S>tK?0*Jeq=+iFmm-g>U%2UX%HAiJ2*-yYXpRd<*jpS77}{4mfi$5+flY*{;o1 zUh;htrRfx2`*a88Yi=GMo}R?HotH-Htfn}VDF6;@*@xz_O|cvy60k*Qn||Qi&c`WnbNHq6 zb(EHyc-i@gj^-bjZnW86qtP(AOjQagF1q^$*;Q+1Kh&A_fO3+lotHFn5LkqKIGbB8 zGzp$fRgQo;c$A;8>(5`(#%g5O@4dv=Txh5)zof4Jl*Q=Ly}>2^%Y{f~-)vs}Vx<4} z*^)os&*ljiJBH<=l$v-fBNiY3z=S)Nn1OZGY@T^>h|>NdKYMYcax;^+zm%x#&g5@g zN>x6*!1r9Lugtl?FJ7vvjJd$eE;UmUFYuPd_+-luRh94yyl-(7AN|8S%INc`4foG^ z7rJ8CC!gnaFV|C=oaY@bk5E3G#g|-erK~syY~g+-n5SJo?VQB<^f}IonB}!?1mN&oBT&Z%F55E$toIk^}ui&d{r}G_G8Yy$m@QYVg z1ipRR!@BpT^Q@~)!iQ6pBj8Cq%1@+bJ!F&dwOji1>3sjyX8vzam)wb-&+lD5{kd1ghv(lk*$$^B-b0*tlGnY~O1YcCpS)&fLvxp0 zdx3S+Lz&ICoC4|Xa-@N`FS;U8IWp3Hz}sIpR$D+Vj0o!&Kj*`Zhw1(dmdz74G{4-~C z;7b5-M|A_H@XO9FEQUwisK;`7#~VF@Kl=vPx(f{1D`c)p|NLYge7}J*c_rU^!&dhp zA$K@}$OAi;E=Sm5+9gj(7Vv>3jq9#^Ofxb%I?vH-GKUB6<9+WaY>klBR_!XbR<9Z<&-4T8+V7 zqvx$l`^M}pf+|53MjQdv6om93>uktgCfO3Q@+JOZY0I$MXw7&UV4VpJ>d*-aH+DL| zUYg+l5EvjeAw@jqc8K!mFzf8{~_dXrK>WY*qjEIB?2GF(Qij#!W^rt!58wzS!hOtP?7iUi&C+A$7i z;JC$WvF9wuo3P1t2gwRP<3QJfI`FvURcQ$_GuWia7FYpf@ zEx}i2w<-Ut-X_>^a7kh=$_WPnE-sa1FMX0duY5Uj=@xdE_0IbjV_{5bGECj0u#c6t z&*r`8!(L#@$*1#`F^DOR#g_N23KWq$^=(oKvmXBG0Vb;s)t8lx7$pe9mMJ? zCr4<|F}|#U^1iDu!j~m0AtTf^zO0_obcDLkm(^D;4Of4l^4;O;11f(sTQ~cQhrkok77FA=- zmCZxd-|3k%RBc!t&yhpb{?%CvrS(uXvpRM_)rYE^==swS^=x(4Jz?$;j4%U>leCjz z5FbO%8Dx<#!a_8p(kI{3U~eh}Xz~#C$r`L(cn^2$V%drimc+fq5OsMC);9bvD@YbusSDzbqH_ZZXM&@ zT4S(!l3LFVa`j#0-Fje<8Xbt%Mef#F-mTLIsjpLOFL&!Y@79Dt>ON|HG|+{4s&}gx zs0J8Vhp-)JO||`iDU+_bn6Y}GI>Nx>e5a$)nfV8;r&52Qw$OVFY?{IcXoa*O)<>!4 zD&z)XRp0KfwL}H8hRU%1YH~39t7^TzP{?I@y=t-{Oj(tvuByfA)*NU?<)h0KQEX#| zonMM~^1iCYY>drU2Zum}*Qs+u*m7lvIWIbtJ;9Woz0^@*Y)AE>Z4tBiB4&H5of;j^ zX7lF?BbCNI)#7m0$YAJ+$s$xSI|d6e{TFrnb=HzilN;&@sjdz6XH&HewYsGabm*{J zRELdC+*h;Wf<&48e(FwQBdRF4}oPik4Jc)Jq*-ml@St5v+^Bzq=Rm+Sk-hk!)xn7P$hu7^JR>WK*I}2fFHT zi3q=LP=un_uN&-B+tp{!2M?A&+7~|3m8PNU`uZ%kal}685`FcM!yI9@rQrj}X*jUZ zFHWKuGMUxDqD@O;>gq8pqF$*XHf6OTE~QwU2%r%?x8YBzEuvUd@an%+f2ckk#TqN7 zZX^bE)m2d}zS+$0ToLc~<}ysh7Z5tdxat(BW2{>(Je7CV`%$b}?Rh{-AMC|vK!b6a zsyw5$IHe{=vu>=DIxU(tWQ)}Iqgf&wqMnInDNLx5F|4~1@UHq?42xy0)CDoDKOPrj zSl94xt9dvjzDB_r3v(yVs|gKQ!{Ftz)g(WEM=W|*9o>M%v1in`8?ey8+g&u4R0Ek8 zx~RDgSd*GHT|L6dBB4j^YU(cySpDFu{+0RjlUlDK%%HWi+N&Y!A3F$V=m>uB>k;@y zJ6j5VQ9{pv9k00hm(meN4jQ3f_MBSKkVUt6*jdU!y!a9I+DHENx_(`36w$=0pFldtI15+@6(9!Ovm8HN<8M~$p!z_m^U z^2NaJ->&Ok^1SfFkZ#i;0`z?A!S2=b+PL#?uc}G0ER-!%hsLt2fw|yL1#Es^uQ*nX zF?-&Crp!=<+12M;vaZUd*6Ny;FpEyLxFw|bb2YjZtH*AuU0SjB0iQ@_Im|fq5g(}5 zR%~|bU+`f9H%?s2zJ#L3EPaoX_>v�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 NZ?55h^5Y1S61h(V-22$#)hVb^rOrU#}sbu z8{JLrW_OD_p*+1jvplAc6X`U?e>&6mNz-d8><7=O?9nxk@fBdZ-c)f@@N7Iupf0Eb&1R_V(Wn%cL_3; zxjpX2@<4g8ytO>CBB3I^BD12jGO{Y6D!s~DWvVsTdTX0%1GUYy!P*d-YC4jB#DbB` z!8itv1Tl{Ox Date: Thu, 18 Jan 2018 16:18:25 +0100 Subject: [PATCH 03/21] publish windows builds as supporting fast updates --- build/tfs/common/publish.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build/tfs/common/publish.ts b/build/tfs/common/publish.ts index e4cbdc80f0e7b..8e25b630c3e8b 100644 --- a/build/tfs/common/publish.ts +++ b/build/tfs/common/publish.ts @@ -69,6 +69,7 @@ interface Asset { hash: string; sha256hash: string; size: number; + supportsFastUpdate?: boolean; } function createOrUpdate(commit: string, quality: string, platform: string, type: string, release: NewDocument, asset: Asset, isUpdate: boolean): Promise { @@ -234,6 +235,11 @@ async function publish(commit: string, quality: string, platform: string, type: size }; + // Remove this if we ever need to rollback fast updates for windows + if (/win32/.test(platform)) { + asset.supportsFastUpdate = true; + } + const release = { id: commit, timestamp: (new Date()).getTime(), From 178f9a95b61ade9dbc40e42cf7077fea7514e397 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 11:31:36 +0100 Subject: [PATCH 04/21] wip: windows fast updates --- src/typings/windows-mutex.ts | 2 + src/vs/code/electron-main/menus.ts | 11 ++ src/vs/platform/update/common/update.ts | 10 +- src/vs/platform/update/common/updateIpc.ts | 16 +++ .../electron-main/auto-updater.win32.ts | 74 +++++++++++- .../update/electron-main/updateService.ts | 62 ++++++++-- .../parts/update/electron-browser/update.ts | 114 ++++++++++++------ 7 files changed, 238 insertions(+), 51 deletions(-) diff --git a/src/typings/windows-mutex.ts b/src/typings/windows-mutex.ts index 039dffcc2ad56..a3acc8f4430ce 100644 --- a/src/typings/windows-mutex.ts +++ b/src/typings/windows-mutex.ts @@ -9,4 +9,6 @@ declare module 'windows-mutex' { isActive(): boolean; release(): void; } + + export function isActive(name: string): boolean; } \ No newline at end of file diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 59229fbc73976..ced1253651e25 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -1045,6 +1045,17 @@ export class CodeMenu { return []; case UpdateState.UpdateDownloaded: + return [new MenuItem({ + label: nls.localize('miInstallUpdate', "Install Update..."), click: () => { + this.reportMenuActionTelemetry('InstallUpdate'); + this.updateService.applyUpdate(); + } + })]; + + case UpdateState.UpdateInstalling: + return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })]; + + case UpdateState.UpdateReady: return [new MenuItem({ label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => { this.reportMenuActionTelemetry('RestartToUpdate'); diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 13928e33f1784..eecba4582999e 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -14,7 +14,9 @@ export enum State { Idle, CheckingForUpdate, UpdateAvailable, - UpdateDownloaded + UpdateDownloaded, + UpdateInstalling, + UpdateReady } export enum ExplicitState { @@ -26,6 +28,7 @@ export interface IRawUpdate { releaseNotes: string; version: string; date: Date; + supportsFastUpdate?: boolean; } export interface IUpdate { @@ -33,11 +36,13 @@ export interface IUpdate { date?: Date; releaseNotes?: string; url?: string; + supportsFastUpdate?: boolean; } export interface IAutoUpdater extends NodeEventEmitter { setFeedURL(url: string): void; checkForUpdates(): void; + applyUpdate?(): TPromise; quitAndInstall(): void; } @@ -49,10 +54,13 @@ export interface IUpdateService { readonly onError: Event; readonly onUpdateAvailable: Event<{ url: string; version: string; }>; readonly onUpdateNotAvailable: Event; + readonly onUpdateDownloaded: Event; + readonly onUpdateInstalling: Event; readonly onUpdateReady: Event; readonly onStateChange: Event; readonly state: State; checkForUpdates(explicit: boolean): TPromise; + applyUpdate(): TPromise; quitAndInstall(): TPromise; } \ No newline at end of file diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index bd42c38e018cf..9b8b30041a3ce 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -15,9 +15,12 @@ export interface IUpdateChannel extends IChannel { call(command: 'event:onError'): TPromise; call(command: 'event:onUpdateAvailable'): TPromise; call(command: 'event:onUpdateNotAvailable'): TPromise; + call(command: 'event:onUpdateDownloaded'): TPromise; + call(command: 'event:onUpdateInstalling'): TPromise; call(command: 'event:onUpdateReady'): TPromise; call(command: 'event:onStateChange'): TPromise; call(command: 'checkForUpdates', arg: boolean): TPromise; + call(command: 'applyUpdate'): TPromise; call(command: 'quitAndInstall'): TPromise; call(command: '_getInitialState'): TPromise; call(command: string, arg?: any): TPromise; @@ -32,9 +35,12 @@ export class UpdateChannel implements IUpdateChannel { case 'event:onError': return eventToCall(this.service.onError); case 'event:onUpdateAvailable': return eventToCall(this.service.onUpdateAvailable); case 'event:onUpdateNotAvailable': return eventToCall(this.service.onUpdateNotAvailable); + case 'event:onUpdateDownloaded': return eventToCall(this.service.onUpdateDownloaded); + case 'event:onUpdateInstalling': return eventToCall(this.service.onUpdateInstalling); case 'event:onUpdateReady': return eventToCall(this.service.onUpdateReady); case 'event:onStateChange': return eventToCall(this.service.onStateChange); case 'checkForUpdates': return this.service.checkForUpdates(arg); + case 'applyUpdate': return this.service.applyUpdate(); case 'quitAndInstall': return this.service.quitAndInstall(); case '_getInitialState': return TPromise.as(this.service.state); } @@ -55,6 +61,12 @@ export class UpdateChannelClient implements IUpdateService { private _onUpdateNotAvailable = eventFromCall(this.channel, 'event:onUpdateNotAvailable'); get onUpdateNotAvailable(): Event { return this._onUpdateNotAvailable; } + private _onUpdateDownloaded = eventFromCall(this.channel, 'event:onUpdateDownloaded'); + get onUpdateDownloaded(): Event { return this._onUpdateDownloaded; } + + private _onUpdateInstalling = eventFromCall(this.channel, 'event:onUpdateInstalling'); + get onUpdateInstalling(): Event { return this._onUpdateInstalling; } + private _onUpdateReady = eventFromCall(this.channel, 'event:onUpdateReady'); get onUpdateReady(): Event { return this._onUpdateReady; } @@ -82,6 +94,10 @@ export class UpdateChannelClient implements IUpdateService { return this.channel.call('checkForUpdates', explicit); } + applyUpdate(): TPromise { + return this.channel.call('applyUpdate'); + } + quitAndInstall(): TPromise { return this.channel.call('quitAndInstall'); } diff --git a/src/vs/platform/update/electron-main/auto-updater.win32.ts b/src/vs/platform/update/electron-main/auto-updater.win32.ts index c88880617f32f..401dc510256de 100644 --- a/src/vs/platform/update/electron-main/auto-updater.win32.ts +++ b/src/vs/platform/update/electron-main/auto-updater.win32.ts @@ -6,6 +6,7 @@ 'use strict'; import * as path from 'path'; +import * as fs from 'fs'; import * as pfs from 'vs/base/node/pfs'; import { checksum } from 'vs/base/node/crypto'; import { EventEmitter } from 'events'; @@ -17,6 +18,7 @@ import { download, asJson } from 'vs/base/node/request'; import { IRequestService } from 'vs/platform/request/node/request'; import { IAutoUpdater } from 'vs/platform/update/common/update'; import product from 'vs/platform/node/product'; +import { isActive } from 'windows-mutex'; interface IUpdate { url: string; @@ -25,13 +27,35 @@ interface IUpdate { version: string; productVersion: string; hash: string; + supportsFastUpdate?: boolean; +} + +function pollUntil(fn: () => boolean, timeout = 1000): TPromise { + return new TPromise(c => { + const poll = () => { + if (fn()) { + c(null); + } else { + setTimeout(poll, timeout); + } + }; + + poll(); + }); +} + +interface IAvailableUpdate { + packagePath: string; + version: string; + supportsFastUpdate: boolean; + updateFilePath?: string; } export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater { private url: string = null; private currentRequest: Promise = null; - private updatePackagePath: string = null; + private currentUpdate: IAvailableUpdate = null; constructor( @IRequestService private requestService: IRequestService @@ -87,14 +111,21 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater { .then(() => updatePackagePath); }); }).then(updatePackagePath => { - this.updatePackagePath = updatePackagePath; + const supportsFastUpdate = !!update.supportsFastUpdate; + + this.currentUpdate = { + packagePath: updatePackagePath, + version: update.version, + supportsFastUpdate + }; this.emit('update-downloaded', {}, update.releaseNotes, update.productVersion, new Date(), - this.url + this.url, + supportsFastUpdate ); }); }); @@ -126,12 +157,45 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater { ); } + applyUpdate(): TPromise { + if (!this.currentUpdate) { + return TPromise.as(null); + } + + return this.cachePath.then(cachePath => { + this.currentUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${this.currentUpdate.version}.flag`); + + return pfs.touch(this.currentUpdate.updateFilePath).then(() => { + spawn(this.currentUpdate.packagePath, ['/verysilent', '/update=FILENAME', '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { + detached: true, + stdio: ['ignore', 'ignore', 'ignore'] + }); + + const readyMutexName = `${product.win32MutexName}-ready`; + + // poll for mutex-ready + pollUntil(() => isActive(readyMutexName)).then(() => { + + // now we're ready for `quitAndInstall` + this.emit('update-ready'); + }); + }); + }); + } + quitAndInstall(): void { - if (!this.updatePackagePath) { + if (!this.currentUpdate) { + return; + } + + if (this.currentUpdate.supportsFastUpdate && this.currentUpdate.updateFilePath) { + // let's delete the file, to signal inno setup that we want Code to start + // after the update is applied. after that, just die + fs.unlinkSync(this.currentUpdate.updateFilePath); return; } - spawn(this.updatePackagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { + spawn(this.currentUpdate.packagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { detached: true, stdio: ['ignore', 'ignore', 'ignore'] }); diff --git a/src/vs/platform/update/electron-main/updateService.ts b/src/vs/platform/update/electron-main/updateService.ts index b7f8f5edf69a1..849f57f279dfb 100644 --- a/src/vs/platform/update/electron-main/updateService.ts +++ b/src/vs/platform/update/electron-main/updateService.ts @@ -45,6 +45,12 @@ export class UpdateService implements IUpdateService { private _onUpdateNotAvailable = new Emitter(); get onUpdateNotAvailable(): Event { return this._onUpdateNotAvailable.event; } + private _onUpdateDownloaded = new Emitter(); + get onUpdateDownloaded(): Event { return this._onUpdateDownloaded.event; } + + private _onUpdateInstalling = new Emitter(); + get onUpdateInstalling(): Event { return this._onUpdateInstalling.event; } + private _onUpdateReady = new Emitter(); get onUpdateReady(): Event { return this._onUpdateReady.event; } @@ -68,14 +74,19 @@ export class UpdateService implements IUpdateService { @memoize private get onRawUpdateDownloaded(): Event { - return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url) => ({ releaseNotes, version, date })); + return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url, supportsFastUpdate) => ({ releaseNotes, version, date, supportsFastUpdate })); + } + + @memoize + private get onRawUpdateReady(): Event { + return fromNodeEventEmitter(this.raw, 'update-ready'); } get state(): State { return this._state; } - set state(state: State) { + private updateState(state: State): void { this._state = state; this._onStateChange.fire(state); } @@ -119,7 +130,7 @@ export class UpdateService implements IUpdateService { return; // application not signed } - this.state = State.Idle; + this.updateState(State.Idle); // Start checking for updates after 30 seconds this.scheduleCheckForUpdates(30 * 1000) @@ -157,20 +168,20 @@ export class UpdateService implements IUpdateService { } this._onCheckForUpdate.fire(); - this.state = State.CheckingForUpdate; + this.updateState(State.CheckingForUpdate); const listeners: IDisposable[] = []; const result = new TPromise((c, e) => { once(this.onRawError)(e, null, listeners); once(this.onRawUpdateNotAvailable)(() => c(null), null, listeners); once(this.onRawUpdateAvailable)(({ url, version }) => url && c({ url, version }), null, listeners); - once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes }) => c({ version, date, releaseNotes }), null, listeners); + once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes, supportsFastUpdate }) => c({ version, date, releaseNotes, supportsFastUpdate }), null, listeners); this.raw.checkForUpdates(); }).then(update => { if (!update) { this._onUpdateNotAvailable.fire(explicit); - this.state = State.Idle; + this.updateState(State.Idle); /* __GDPR__ "update:notAvailable" : { "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } @@ -178,6 +189,7 @@ export class UpdateService implements IUpdateService { */ this.telemetryService.publicLog('update:notAvailable', { explicit }); + // LINUX } else if (update.url) { const data: IUpdate = { url: update.url, @@ -188,7 +200,7 @@ export class UpdateService implements IUpdateService { this._availableUpdate = data; this._onUpdateAvailable.fire({ url: update.url, version: update.version }); - this.state = State.UpdateAvailable; + this.updateState(State.UpdateAvailable); /* __GDPR__ "update:available" : { "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -202,12 +214,20 @@ export class UpdateService implements IUpdateService { const data: IRawUpdate = { releaseNotes: update.releaseNotes, version: update.version, - date: update.date + date: update.date, + supportsFastUpdate: update.supportsFastUpdate }; this._availableUpdate = data; - this._onUpdateReady.fire(data); - this.state = State.UpdateDownloaded; + + if (update.supportsFastUpdate) { + this._onUpdateDownloaded.fire(data); + this.updateState(State.UpdateDownloaded); + } else { + this._onUpdateReady.fire(data); + this.updateState(State.UpdateReady); + } + /* __GDPR__ "update:downloaded" : { "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } @@ -218,7 +238,7 @@ export class UpdateService implements IUpdateService { return update; }, err => { - this.state = State.Idle; + this.updateState(State.Idle); return TPromise.wrapError(err); }); @@ -260,6 +280,26 @@ export class UpdateService implements IUpdateService { return process.platform; } + // for windows fast updates + applyUpdate(): TPromise { + if (this.state !== State.UpdateDownloaded) { + return TPromise.as(null); + } + + if (!this.raw.applyUpdate) { + return TPromise.as(null); + } + + once(this.onRawUpdateReady)(() => { + this._onUpdateReady.fire(this._availableUpdate as IRawUpdate); + this.updateState(State.UpdateReady); + }); + + this._onUpdateInstalling.fire(this._availableUpdate as IRawUpdate); + this.updateState(State.UpdateInstalling); + return this.raw.applyUpdate(); + } + quitAndInstall(): TPromise { if (!this._availableUpdate) { return TPromise.as(null); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index fca8ccd73a60b..08349ed005db3 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -9,7 +9,7 @@ import nls = require('vs/nls'); import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction, Action } from 'vs/base/common/actions'; -import { mapEvent } from 'vs/base/common/event'; +import { mapEvent, filterEvent, once } from 'vs/base/common/event'; import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMessageService, CloseAction, Severity } from 'vs/platform/message/common/message'; @@ -34,16 +34,6 @@ import * as semver from 'semver'; import { OS, isLinux, isWindows } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -class ApplyUpdateAction extends Action { - constructor( @IUpdateService private updateService: IUpdateService) { - super('update.applyUpdate', nls.localize('updateNow', "Update Now"), null, true); - } - - run(): TPromise { - return this.updateService.quitAndInstall(); - } -} - const NotNowAction = new Action( 'update.later', nls.localize('later', "Later"), @@ -178,17 +168,6 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio } } -export class DownloadAction extends Action { - - constructor( @IUpdateService private updateService: IUpdateService) { - super('update.download', nls.localize('downloadNow', "Download Now"), null, true); - } - - run(): TPromise { - return this.updateService.quitAndInstall(); - } -} - const LinkAction = (id: string, message: string, licenseUrl: string) => new Action( id, message, null, true, () => { window.open(licenseUrl); return TPromise.as(null); } @@ -328,11 +307,25 @@ export class UpdateContribution implements IGlobalActivity { @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IActivityService private activityService: IActivityService ) { - const onUpdateAvailable = isLinux - ? mapEvent(updateService.onUpdateAvailable, e => e.version) - : mapEvent(updateService.onUpdateReady, e => e.version); + if (isLinux) { + mapEvent(updateService.onUpdateAvailable, e => e.version) + (this.onUpdateAvailable, this, this.disposables); + } else if (isWindows) { + // fast updates + mapEvent(updateService.onUpdateDownloaded, e => e.version) + (this.onUpdateDownloaded, this, this.disposables); + mapEvent(updateService.onUpdateInstalling, e => e.version) + (this.onUpdateInstalling, this, this.disposables); + + // regular old updates + mapEvent(filterEvent(updateService.onUpdateReady, e => !e.supportsFastUpdate), e => e.version) + (this.onUpdateAvailable, this, this.disposables); + + } else { + mapEvent(updateService.onUpdateReady, e => e.version) + (this.onUpdateAvailable, this, this.disposables); + } - onUpdateAvailable(this.onUpdateAvailable, this, this.disposables); updateService.onError(this.onError, this, this.disposables); updateService.onUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables); @@ -370,7 +363,7 @@ export class UpdateContribution implements IGlobalActivity { } } - private onUpdateAvailable(version: string): void { + private shouldShowNotification(): boolean { const currentVersion = product.commit; const currentMillis = new Date().getTime(); const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL); @@ -384,27 +377,73 @@ export class UpdateContribution implements IGlobalActivity { const updateNotificationMillis = this.storageService.getInteger('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis); const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24); - // if 5 days have passed from stored date, show message service - if (diffDays > 5) { - this.showUpdateNotification(version); + return diffDays > 5; + } + + // windows fast updates + private onUpdateDownloaded(version: string): void { + if (!this.shouldShowNotification()) { + return; + } + + const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, version); + const installUpdateAction = new Action('update.applyUpdate', nls.localize('installUpdate', "Install Update"), undefined, true, () => { + once(mapEvent(filterEvent(this.updateService.onUpdateReady, e => e.supportsFastUpdate), e => e.version)) + (this.onWindowsFastUpdateReady, this); + + return this.updateService.applyUpdate(); + }); + + this.messageService.show(severity.Info, { + message: nls.localize('updateAvailable', "There's an available update: {0} {1}", product.nameLong, version), + actions: [installUpdateAction, NotNowAction, releaseNotesAction] + }); + } + + // windows fast updates + private onWindowsFastUpdateReady(version: string): void { + const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, version); + const restartAction = new Action('update.applyUpdate', nls.localize('updateNow', "Update Now"), undefined, true, () => this.updateService.quitAndInstall()); + + this.messageService.show(severity.Info, { + message: nls.localize('updateAvailableAfterRestart', "{0} will be updated after it restarts.", product.nameLong), + actions: [restartAction, NotNowAction, releaseNotesAction] + }); + } + + // windows fast updates + private onUpdateInstalling(version: string): void { + const neverShowAgain = new NeverShowAgain('update/win32-fast-updates', this.storageService); + + if (!neverShowAgain.shouldShow()) { + return; } + + this.messageService.show(severity.Info, { + message: nls.localize('updateInstalling', "{0} {1} is being installed in the background, we'll let you know when it's done.", product.nameLong, version), + actions: [CloseAction, neverShowAgain.action] + }); } - private showUpdateNotification(version: string): void { + private onUpdateAvailable(version: string): void { + if (!this.shouldShowNotification()) { + return; + } + const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, version); if (isLinux) { - const downloadAction = this.instantiationService.createInstance(DownloadAction); + const downloadAction = new Action('update.download', nls.localize('downloadNow', "Download Now"), undefined, true, () => this.updateService.quitAndInstall()); this.messageService.show(severity.Info, { message: nls.localize('thereIsUpdateAvailable', "There is an available update."), actions: [downloadAction, NotNowAction, releaseNotesAction] }); } else { - const applyUpdateAction = this.instantiationService.createInstance(ApplyUpdateAction); + const applyUpdateAction = new Action('update.applyUpdate', nls.localize('updateNow', "Update Now"), undefined, true, () => this.updateService.quitAndInstall()); this.messageService.show(severity.Info, { - message: nls.localize('updateAvailable', "{0} will be updated after it restarts.", product.nameLong), + message: nls.localize('updateAvailableAfterRestart', "{0} will be updated after it restarts.", product.nameLong), actions: [applyUpdateAction, NotNowAction, releaseNotesAction] }); } @@ -461,6 +500,13 @@ export class UpdateContribution implements IGlobalActivity { return new Action('update.available', updateAvailableLabel, undefined, false); case UpdateState.UpdateDownloaded: + return new Action('update.apply', nls.localize('installUpdate...', "Install Update..."), undefined, true, () => + this.updateService.applyUpdate()); + + case UpdateState.UpdateInstalling: + return new Action('update.applying', nls.localize('installingUpdate', "Installing Update..."), undefined, false); + + case UpdateState.UpdateReady: return new Action('update.restart', nls.localize('restartToUpdate', "Restart to Update..."), undefined, true, () => this.updateService.quitAndInstall()); From 079899193e25b7e45f73f6727189f236680a1823 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 11:48:08 +0100 Subject: [PATCH 05/21] load mutex later --- src/vs/platform/update/electron-main/auto-updater.win32.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/update/electron-main/auto-updater.win32.ts b/src/vs/platform/update/electron-main/auto-updater.win32.ts index 401dc510256de..55da62dda4ef9 100644 --- a/src/vs/platform/update/electron-main/auto-updater.win32.ts +++ b/src/vs/platform/update/electron-main/auto-updater.win32.ts @@ -18,7 +18,6 @@ import { download, asJson } from 'vs/base/node/request'; import { IRequestService } from 'vs/platform/request/node/request'; import { IAutoUpdater } from 'vs/platform/update/common/update'; import product from 'vs/platform/node/product'; -import { isActive } from 'windows-mutex'; interface IUpdate { url: string; @@ -172,6 +171,7 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater { }); const readyMutexName = `${product.win32MutexName}-ready`; + const isActive = (require.__$__nodeRequire('windows-mutex') as any).isActive; // poll for mutex-ready pollUntil(() => isActive(readyMutexName)).then(() => { From 7bf655d11a81864ddbdb790ddbd4ecf92cd0cf70 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 12:32:24 +0100 Subject: [PATCH 06/21] fix badge, win32 flag file --- .../update/electron-main/auto-updater.win32.ts | 10 ++++++++-- .../workbench/parts/update/electron-browser/update.ts | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/update/electron-main/auto-updater.win32.ts b/src/vs/platform/update/electron-main/auto-updater.win32.ts index 55da62dda4ef9..3100d0941d230 100644 --- a/src/vs/platform/update/electron-main/auto-updater.win32.ts +++ b/src/vs/platform/update/electron-main/auto-updater.win32.ts @@ -164,12 +164,18 @@ export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater { return this.cachePath.then(cachePath => { this.currentUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${this.currentUpdate.version}.flag`); - return pfs.touch(this.currentUpdate.updateFilePath).then(() => { - spawn(this.currentUpdate.packagePath, ['/verysilent', '/update=FILENAME', '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { + return pfs.writeFile(this.currentUpdate.updateFilePath, 'flag').then(() => { + const child = spawn(this.currentUpdate.packagePath, ['/verysilent', `/update="${this.currentUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { detached: true, stdio: ['ignore', 'ignore', 'ignore'] }); + child.once('exit', () => { + this.emit('update-not-available'); + this.currentRequest = null; + this.currentUpdate = null; + }); + const readyMutexName = `${product.win32MutexName}-ready`; const isActive = (require.__$__nodeRequire('windows-mutex') as any).isActive; diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 08349ed005db3..34e36f495bb54 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -355,7 +355,7 @@ export class UpdateContribution implements IGlobalActivity { const isUpdateAvailable = isLinux ? state === UpdateState.UpdateAvailable - : state === UpdateState.UpdateDownloaded; + : state === UpdateState.UpdateDownloaded || state === UpdateState.UpdateReady; if (isUpdateAvailable) { const badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); From 7032bc2888aac947205e0ad60ecb20d4b9347117 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 12:33:09 +0100 Subject: [PATCH 07/21] rollback update mechanism if user cancels --- src/vs/platform/update/electron-main/updateService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/platform/update/electron-main/updateService.ts b/src/vs/platform/update/electron-main/updateService.ts index 849f57f279dfb..fde9b01d33f68 100644 --- a/src/vs/platform/update/electron-main/updateService.ts +++ b/src/vs/platform/update/electron-main/updateService.ts @@ -295,6 +295,11 @@ export class UpdateService implements IUpdateService { this.updateState(State.UpdateReady); }); + once(this.onRawUpdateNotAvailable)(() => { + this._onUpdateNotAvailable.fire(false); + this.updateState(State.Idle); + }); + this._onUpdateInstalling.fire(this._availableUpdate as IRawUpdate); this.updateState(State.UpdateInstalling); return this.raw.applyUpdate(); From f01d2d2d7d912ea42476e0b4da934ed2e819e1df Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 14:24:56 +0100 Subject: [PATCH 08/21] log asset --- build/tfs/common/publish.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/tfs/common/publish.ts b/build/tfs/common/publish.ts index 8e25b630c3e8b..ca580ce1b4d05 100644 --- a/build/tfs/common/publish.ts +++ b/build/tfs/common/publish.ts @@ -240,6 +240,8 @@ async function publish(commit: string, quality: string, platform: string, type: asset.supportsFastUpdate = true; } + console.log('Asset:', JSON.stringify(asset, null, ' ')); + const release = { id: commit, timestamp: (new Date()).getTime(), From 16306c893a08469857a0156d43688dca4d3190bc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 19:25:43 +0100 Subject: [PATCH 09/21] unify update services across platforms --- src/vs/code/electron-main/app.ts | 13 +- src/vs/code/electron-main/menus.ts | 63 ++-- src/vs/platform/update/common/update.ts | 67 ++-- src/vs/platform/update/common/updateIpc.ts | 38 +- .../electron-main/abstractUpdateService.ts | 137 +++++++ .../electron-main/auto-updater.linux.ts | 77 ---- .../electron-main/auto-updater.win32.ts | 209 ----------- .../electron-main/updateService.darwin.ts | 114 ++++++ .../electron-main/updateService.linux.ts | 77 ++++ .../update/electron-main/updateService.ts | 340 ------------------ .../electron-main/updateService.win32.ts | 207 +++++++++++ .../parts/update/electron-browser/update.ts | 241 +++++++------ 12 files changed, 741 insertions(+), 842 deletions(-) create mode 100644 src/vs/platform/update/electron-main/abstractUpdateService.ts delete mode 100644 src/vs/platform/update/electron-main/auto-updater.linux.ts delete mode 100644 src/vs/platform/update/electron-main/auto-updater.win32.ts create mode 100644 src/vs/platform/update/electron-main/updateService.darwin.ts create mode 100644 src/vs/platform/update/electron-main/updateService.linux.ts delete mode 100644 src/vs/platform/update/electron-main/updateService.ts create mode 100644 src/vs/platform/update/electron-main/updateService.win32.ts diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index ca4a569c0f657..d5a8604b0bb66 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -16,7 +16,6 @@ import { CodeMenu } from 'vs/code/electron-main/menus'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/common/updateIpc'; -import { UpdateService } from 'vs/platform/update/electron-main/updateService'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main'; import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net'; import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; @@ -52,6 +51,9 @@ import URI from 'vs/base/common/uri'; import { WorkspacesChannel } from 'vs/platform/workspaces/common/workspacesIpc'; import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; import { getMachineId } from 'vs/base/node/id'; +import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32'; +import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; +import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; export class CodeApplication { @@ -296,7 +298,14 @@ export class CodeApplication { private initServices(machineId: string): IInstantiationService { const services = new ServiceCollection(); - services.set(IUpdateService, new SyncDescriptor(UpdateService)); + if (process.platform === 'win32') { + services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); + } else if (process.platform === 'linux') { + services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); + } else if (process.platform === 'darwin') { + services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService)); + } + services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, machineId)); services.set(IWindowsService, new SyncDescriptor(WindowsService, this.sharedProcess)); services.set(ILaunchService, new SyncDescriptor(LaunchService)); diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index ced1253651e25..6a3d1c1da5f2a 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -14,7 +14,7 @@ import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/comm import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update'; +import { IUpdateService, StateType } from 'vs/platform/update/common/update'; import product from 'vs/platform/node/product'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -1040,11 +1040,34 @@ export class CodeMenu { } private getUpdateMenuItems(): Electron.MenuItem[] { - switch (this.updateService.state) { - case UpdateState.Uninitialized: + const state = this.updateService.state; + + switch (state.type) { + case StateType.Uninitialized: return []; - case UpdateState.UpdateDownloaded: + case StateType.Idle: + return [new MenuItem({ + label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => { + this.reportMenuActionTelemetry('CheckForUpdate'); + this.updateService.checkForUpdates(true); + }, 0) + })]; + + case StateType.CheckingForUpdates: + return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })]; + + case StateType.Available: + return [new MenuItem({ + label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => { + shell.openExternal(state.update.url); + } + })]; + + case StateType.Downloading: + return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })]; + + case StateType.Downloaded: return [new MenuItem({ label: nls.localize('miInstallUpdate', "Install Update..."), click: () => { this.reportMenuActionTelemetry('InstallUpdate'); @@ -1052,44 +1075,16 @@ export class CodeMenu { } })]; - case UpdateState.UpdateInstalling: + case StateType.Updating: return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })]; - case UpdateState.UpdateReady: + case StateType.Ready: return [new MenuItem({ label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => { this.reportMenuActionTelemetry('RestartToUpdate'); this.updateService.quitAndInstall(); } })]; - - case UpdateState.CheckingForUpdate: - return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })]; - - case UpdateState.UpdateAvailable: - if (isLinux) { - return [new MenuItem({ - label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => { - this.updateService.quitAndInstall(); - } - })]; - } - - const updateAvailableLabel = isWindows - ? nls.localize('miDownloadingUpdate', "Downloading Update...") - : nls.localize('miInstallingUpdate', "Installing Update..."); - - return [new MenuItem({ label: updateAvailableLabel, enabled: false })]; - - default: - const result = [new MenuItem({ - label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => { - this.reportMenuActionTelemetry('CheckForUpdate'); - this.updateService.checkForUpdates(true); - }, 0) - })]; - - return result; } } diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index eecba4582999e..5a397113d9021 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -9,36 +9,49 @@ import Event, { NodeEventEmitter } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; -export enum State { - Uninitialized, - Idle, - CheckingForUpdate, - UpdateAvailable, - UpdateDownloaded, - UpdateInstalling, - UpdateReady -} - -export enum ExplicitState { - Implicit, - Explicit -} - -export interface IRawUpdate { - releaseNotes: string; - version: string; - date: Date; - supportsFastUpdate?: boolean; -} - export interface IUpdate { version: string; + productVersion: string; date?: Date; releaseNotes?: string; - url?: string; supportsFastUpdate?: boolean; + url?: string; + hash?: string; +} + +export enum StateType { + Uninitialized = 'uninitialized', + Idle = 'idle', + Available = 'available', + CheckingForUpdates = 'checking for updates', + Downloading = 'downloading', + Downloaded = 'downloaded', + Updating = 'updating', + Ready = 'ready', } +export type Uninitialized = { type: StateType.Uninitialized }; +export type Idle = { type: StateType.Idle }; +export type CheckingForUpdates = { type: StateType.CheckingForUpdates, explicit: boolean }; +export type Available = { type: StateType.Available, update: IUpdate }; +export type Downloading = { type: StateType.Downloading, update: IUpdate }; +export type Downloaded = { type: StateType.Downloaded, update: IUpdate }; +export type Updating = { type: StateType.Updating, update: IUpdate }; +export type Ready = { type: StateType.Ready, update: IUpdate }; + +export type State = Uninitialized | Idle | CheckingForUpdates | Available | Downloading | Downloaded | Updating | Ready; + +export const State = { + Uninitialized: { type: StateType.Uninitialized } as Uninitialized, + Idle: { type: StateType.Idle } as Idle, + CheckingForUpdates: (explicit: boolean) => ({ type: StateType.CheckingForUpdates, explicit } as CheckingForUpdates), + Available: (update: IUpdate) => ({ type: StateType.Available, update } as Available), + Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading), + Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded), + Updating: (update: IUpdate) => ({ type: StateType.Updating, update } as Updating), + Ready: (update: IUpdate) => ({ type: StateType.Ready, update } as Ready), +}; + export interface IAutoUpdater extends NodeEventEmitter { setFeedURL(url: string): void; checkForUpdates(): void; @@ -51,16 +64,10 @@ export const IUpdateService = createDecorator('updateService'); export interface IUpdateService { _serviceBrand: any; - readonly onError: Event; - readonly onUpdateAvailable: Event<{ url: string; version: string; }>; - readonly onUpdateNotAvailable: Event; - readonly onUpdateDownloaded: Event; - readonly onUpdateInstalling: Event; - readonly onUpdateReady: Event; readonly onStateChange: Event; readonly state: State; - checkForUpdates(explicit: boolean): TPromise; + checkForUpdates(explicit: boolean): TPromise; applyUpdate(): TPromise; quitAndInstall(): TPromise; } \ No newline at end of file diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index 9b8b30041a3ce..9aef4457e9bf0 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -9,17 +9,10 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; import Event, { Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IUpdateService, IRawUpdate, State, IUpdate } from './update'; +import { IUpdateService, State } from './update'; export interface IUpdateChannel extends IChannel { - call(command: 'event:onError'): TPromise; - call(command: 'event:onUpdateAvailable'): TPromise; - call(command: 'event:onUpdateNotAvailable'): TPromise; - call(command: 'event:onUpdateDownloaded'): TPromise; - call(command: 'event:onUpdateInstalling'): TPromise; - call(command: 'event:onUpdateReady'): TPromise; - call(command: 'event:onStateChange'): TPromise; - call(command: 'checkForUpdates', arg: boolean): TPromise; + call(command: 'checkForUpdates', arg: boolean): TPromise; call(command: 'applyUpdate'): TPromise; call(command: 'quitAndInstall'): TPromise; call(command: '_getInitialState'): TPromise; @@ -32,12 +25,6 @@ export class UpdateChannel implements IUpdateChannel { call(command: string, arg?: any): TPromise { switch (command) { - case 'event:onError': return eventToCall(this.service.onError); - case 'event:onUpdateAvailable': return eventToCall(this.service.onUpdateAvailable); - case 'event:onUpdateNotAvailable': return eventToCall(this.service.onUpdateNotAvailable); - case 'event:onUpdateDownloaded': return eventToCall(this.service.onUpdateDownloaded); - case 'event:onUpdateInstalling': return eventToCall(this.service.onUpdateInstalling); - case 'event:onUpdateReady': return eventToCall(this.service.onUpdateReady); case 'event:onStateChange': return eventToCall(this.service.onStateChange); case 'checkForUpdates': return this.service.checkForUpdates(arg); case 'applyUpdate': return this.service.applyUpdate(); @@ -52,25 +39,8 @@ export class UpdateChannelClient implements IUpdateService { _serviceBrand: any; - private _onError = eventFromCall(this.channel, 'event:onError'); - get onError(): Event { return this._onError; } - - private _onUpdateAvailable = eventFromCall<{ url: string; version: string; }>(this.channel, 'event:onUpdateAvailable'); - get onUpdateAvailable(): Event<{ url: string; version: string; }> { return this._onUpdateAvailable; } - - private _onUpdateNotAvailable = eventFromCall(this.channel, 'event:onUpdateNotAvailable'); - get onUpdateNotAvailable(): Event { return this._onUpdateNotAvailable; } - - private _onUpdateDownloaded = eventFromCall(this.channel, 'event:onUpdateDownloaded'); - get onUpdateDownloaded(): Event { return this._onUpdateDownloaded; } - - private _onUpdateInstalling = eventFromCall(this.channel, 'event:onUpdateInstalling'); - get onUpdateInstalling(): Event { return this._onUpdateInstalling; } - - private _onUpdateReady = eventFromCall(this.channel, 'event:onUpdateReady'); - get onUpdateReady(): Event { return this._onUpdateReady; } - private _onRemoteStateChange = eventFromCall(this.channel, 'event:onStateChange'); + private _onStateChange = new Emitter(); get onStateChange(): Event { return this._onStateChange.event; } @@ -90,7 +60,7 @@ export class UpdateChannelClient implements IUpdateService { }, onUnexpectedError); } - checkForUpdates(explicit: boolean): TPromise { + checkForUpdates(explicit: boolean): TPromise { return this.channel.call('checkForUpdates', explicit); } diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts new file mode 100644 index 0000000000000..f7fa618119c49 --- /dev/null +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import Event, { Emitter } from 'vs/base/common/event'; +import { Throttler } from 'vs/base/common/async'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import product from 'vs/platform/node/product'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IUpdateService, State, StateType } from 'vs/platform/update/common/update'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ILogService } from 'vs/platform/log/common/log'; + +export function createUpdateURL(platform: string, quality: string): string { + return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`; +} + +export abstract class AbstractUpdateService implements IUpdateService { + + _serviceBrand: any; + + private _state: State = State.Uninitialized; + private throttler: Throttler = new Throttler(); + + private _onStateChange = new Emitter(); + get onStateChange(): Event { return this._onStateChange.event; } + + get state(): State { + return this._state; + } + + protected setState(state: State): void { + this._state = state; + this._onStateChange.fire(state); + } + + constructor( + @ILifecycleService private lifecycleService: ILifecycleService, + @IConfigurationService private configurationService: IConfigurationService, + @IEnvironmentService private environmentService: IEnvironmentService, + @ILogService protected logService: ILogService + ) { + if (this.environmentService.disableUpdates) { + return; + } + + if (!product.updateUrl || !product.commit) { + return; + } + + const quality = this.getProductQuality(); + + if (!quality) { + return; + } + + if (!this.setUpdateFeedUrl(quality)) { + return; + } + + this.setState({ type: StateType.Idle }); + + // Start checking for updates after 30 seconds + this.scheduleCheckForUpdates(30 * 1000) + .done(null, err => this.logService.error(err)); + } + + private getProductQuality(): string { + const quality = this.configurationService.getValue('update.channel'); + return quality === 'none' ? null : product.quality; + } + + private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise { + return TPromise.timeout(delay) + .then(() => this.checkForUpdates()) + .then(update => { + if (update) { + // Update found, no need to check more + return TPromise.as(null); + } + + // Check again after 1 hour + return this.scheduleCheckForUpdates(60 * 60 * 1000); + }); + } + + checkForUpdates(explicit = false): TPromise { + if (this.state !== State.Idle) { + return TPromise.as(null); + } + + return this.throttler.queue(() => TPromise.as(this.doCheckForUpdates(explicit))); + } + + applyUpdate(): TPromise { + if (this.state.type !== StateType.Ready) { + return TPromise.as(null); + } + + return this.doApplyUpdate(); + } + + protected doApplyUpdate(): TPromise { + return TPromise.as(null); + } + + quitAndInstall(): TPromise { + if (this.state.type !== StateType.Ready) { + return TPromise.as(null); + } + + this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); + + this.lifecycleService.quit(true /* from update */).done(vetod => { + this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); + if (vetod) { + return; + } + + this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); + this.doQuitAndInstall(); + }); + + return TPromise.as(null); + } + + protected doQuitAndInstall(): void { + // noop + } + + protected abstract setUpdateFeedUrl(quality: string): boolean; + protected abstract doCheckForUpdates(explicit: boolean): void; +} diff --git a/src/vs/platform/update/electron-main/auto-updater.linux.ts b/src/vs/platform/update/electron-main/auto-updater.linux.ts deleted file mode 100644 index bdc9c183329c4..0000000000000 --- a/src/vs/platform/update/electron-main/auto-updater.linux.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { EventEmitter } from 'events'; -import { isString } from 'vs/base/common/types'; -import { Promise } from 'vs/base/common/winjs.base'; -import { asJson } from 'vs/base/node/request'; -import { IRequestService } from 'vs/platform/request/node/request'; -import { IAutoUpdater } from 'vs/platform/update/common/update'; -import product from 'vs/platform/node/product'; - -interface IUpdate { - url: string; - name: string; - releaseNotes?: string; - version: string; - productVersion: string; - hash: string; -} - -export class LinuxAutoUpdaterImpl extends EventEmitter implements IAutoUpdater { - - private url: string; - private currentRequest: Promise; - - constructor( - @IRequestService private requestService: IRequestService - ) { - super(); - - this.url = null; - this.currentRequest = null; - } - - setFeedURL(url: string): void { - this.url = url; - } - - checkForUpdates(): void { - if (!this.url) { - throw new Error('No feed url set.'); - } - - if (this.currentRequest) { - return; - } - - this.emit('checking-for-update'); - - this.currentRequest = this.requestService.request({ url: this.url }) - .then(asJson) - .then(update => { - if (!update || !update.url || !update.version || !update.productVersion) { - this.emit('update-not-available'); - } else { - this.emit('update-available', null, product.downloadUrl, update.productVersion); - } - }) - .then(null, e => { - if (isString(e) && /^Server returned/.test(e)) { - return; - } - - this.emit('update-not-available'); - this.emit('error', e); - }) - .then(() => this.currentRequest = null); - } - - quitAndInstall(): void { - // noop - } -} diff --git a/src/vs/platform/update/electron-main/auto-updater.win32.ts b/src/vs/platform/update/electron-main/auto-updater.win32.ts deleted file mode 100644 index 3100d0941d230..0000000000000 --- a/src/vs/platform/update/electron-main/auto-updater.win32.ts +++ /dev/null @@ -1,209 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as path from 'path'; -import * as fs from 'fs'; -import * as pfs from 'vs/base/node/pfs'; -import { checksum } from 'vs/base/node/crypto'; -import { EventEmitter } from 'events'; -import { tmpdir } from 'os'; -import { spawn } from 'child_process'; -import { isString } from 'vs/base/common/types'; -import { Promise, TPromise } from 'vs/base/common/winjs.base'; -import { download, asJson } from 'vs/base/node/request'; -import { IRequestService } from 'vs/platform/request/node/request'; -import { IAutoUpdater } from 'vs/platform/update/common/update'; -import product from 'vs/platform/node/product'; - -interface IUpdate { - url: string; - name: string; - releaseNotes?: string; - version: string; - productVersion: string; - hash: string; - supportsFastUpdate?: boolean; -} - -function pollUntil(fn: () => boolean, timeout = 1000): TPromise { - return new TPromise(c => { - const poll = () => { - if (fn()) { - c(null); - } else { - setTimeout(poll, timeout); - } - }; - - poll(); - }); -} - -interface IAvailableUpdate { - packagePath: string; - version: string; - supportsFastUpdate: boolean; - updateFilePath?: string; -} - -export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater { - - private url: string = null; - private currentRequest: Promise = null; - private currentUpdate: IAvailableUpdate = null; - - constructor( - @IRequestService private requestService: IRequestService - ) { - super(); - } - - get cachePath(): TPromise { - const result = path.join(tmpdir(), `vscode-update-${process.arch}`); - return pfs.mkdirp(result, null).then(() => result); - } - - setFeedURL(url: string): void { - this.url = url; - } - - checkForUpdates(): void { - if (!this.url) { - throw new Error('No feed url set.'); - } - - if (this.currentRequest) { - return; - } - - this.emit('checking-for-update'); - - this.currentRequest = this.requestService.request({ url: this.url }) - .then(asJson) - .then(update => { - if (!update || !update.url || !update.version) { - this.emit('update-not-available'); - return this.cleanup(); - } - - this.emit('update-available'); - - return this.cleanup(update.version).then(() => { - return this.getUpdatePackagePath(update.version).then(updatePackagePath => { - return pfs.exists(updatePackagePath).then(exists => { - if (exists) { - return TPromise.as(updatePackagePath); - } - - const url = update.url; - const hash = update.hash; - const downloadPath = `${updatePackagePath}.tmp`; - - return this.requestService.request({ url }) - .then(context => download(downloadPath, context)) - .then(hash ? () => checksum(downloadPath, update.hash) : () => null) - .then(() => pfs.rename(downloadPath, updatePackagePath)) - .then(() => updatePackagePath); - }); - }).then(updatePackagePath => { - const supportsFastUpdate = !!update.supportsFastUpdate; - - this.currentUpdate = { - packagePath: updatePackagePath, - version: update.version, - supportsFastUpdate - }; - - this.emit('update-downloaded', - {}, - update.releaseNotes, - update.productVersion, - new Date(), - this.url, - supportsFastUpdate - ); - }); - }); - }) - .then(null, e => { - if (isString(e) && /^Server returned/.test(e)) { - return; - } - - this.emit('update-not-available'); - this.emit('error', e); - }) - .then(() => this.currentRequest = null); - } - - private getUpdatePackagePath(version: string): TPromise { - return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`)); - } - - private cleanup(exceptVersion: string = null): Promise { - const filter = exceptVersion ? one => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true; - - return this.cachePath - .then(cachePath => pfs.readdir(cachePath) - .then(all => Promise.join(all - .filter(filter) - .map(one => pfs.unlink(path.join(cachePath, one)).then(null, () => null)) - )) - ); - } - - applyUpdate(): TPromise { - if (!this.currentUpdate) { - return TPromise.as(null); - } - - return this.cachePath.then(cachePath => { - this.currentUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${this.currentUpdate.version}.flag`); - - return pfs.writeFile(this.currentUpdate.updateFilePath, 'flag').then(() => { - const child = spawn(this.currentUpdate.packagePath, ['/verysilent', `/update="${this.currentUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { - detached: true, - stdio: ['ignore', 'ignore', 'ignore'] - }); - - child.once('exit', () => { - this.emit('update-not-available'); - this.currentRequest = null; - this.currentUpdate = null; - }); - - const readyMutexName = `${product.win32MutexName}-ready`; - const isActive = (require.__$__nodeRequire('windows-mutex') as any).isActive; - - // poll for mutex-ready - pollUntil(() => isActive(readyMutexName)).then(() => { - - // now we're ready for `quitAndInstall` - this.emit('update-ready'); - }); - }); - }); - } - - quitAndInstall(): void { - if (!this.currentUpdate) { - return; - } - - if (this.currentUpdate.supportsFastUpdate && this.currentUpdate.updateFilePath) { - // let's delete the file, to signal inno setup that we want Code to start - // after the update is applied. after that, just die - fs.unlinkSync(this.currentUpdate.updateFilePath); - return; - } - - spawn(this.currentUpdate.packagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { - detached: true, - stdio: ['ignore', 'ignore', 'ignore'] - }); - } -} diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts new file mode 100644 index 0000000000000..88b0f19310221 --- /dev/null +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as electron from 'electron'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import Event, { fromNodeEventEmitter } from 'vs/base/common/event'; +import { memoize } from 'vs/base/common/decorators'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { State, IUpdate, StateType } from 'vs/platform/update/common/update'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ILogService } from 'vs/platform/log/common/log'; +import { AbstractUpdateService, createUpdateURL } from 'vs/platform/update/electron-main/abstractUpdateService'; + +export class DarwinUpdateService extends AbstractUpdateService { + + _serviceBrand: any; + + private disposables: IDisposable[] = []; + + @memoize private get onRawError(): Event { return fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); } + @memoize private get onRawUpdateNotAvailable(): Event { return fromNodeEventEmitter(electron.autoUpdater, 'update-not-available'); } + @memoize private get onRawUpdateAvailable(): Event { return fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version) => ({ url, version, productVersion: version })); } + @memoize private get onRawUpdateDownloaded(): Event { return fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); } + + constructor( + @ILifecycleService lifecycleService: ILifecycleService, + @IConfigurationService configurationService: IConfigurationService, + @ITelemetryService private telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, + @ILogService logService: ILogService + ) { + super(lifecycleService, configurationService, environmentService, logService); + this.onRawError(this.logService.error, this.logService, this.disposables); + this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); + this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); + this.onRawUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables); + } + + protected setUpdateFeedUrl(quality: string): boolean { + try { + electron.autoUpdater.setFeedURL(createUpdateURL('darwin', quality)); + } catch (e) { + // application is very likely not signed + this.logService.error('Failed to set update feed URL'); + return false; + } + + return true; + } + + protected doCheckForUpdates(explicit: boolean): void { + this.setState(State.CheckingForUpdates(explicit)); + electron.autoUpdater.checkForUpdates(); + } + + private onUpdateAvailable(update: IUpdate): void { + if (this.state.type !== StateType.CheckingForUpdates) { + return; + } + + this.setState(State.Downloading(update)); + } + + private onUpdateDownloaded(update: IUpdate): void { + if (this.state.type !== StateType.Downloading) { + return; + } + + /* __GDPR__ + "update:downloaded" : { + "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('update:downloaded', { version: update.version }); + + this.setState(State.Ready(update)); + } + + private onUpdateNotAvailable(): void { + if (this.state.type !== StateType.CheckingForUpdates) { + return; + } + + /* __GDPR__ + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('update:notAvailable', { explicit: this.state.explicit }); + + this.setState(State.Idle); + } + + protected doQuitAndInstall(): void { + // for some reason updating on Mac causes the local storage not to be flushed. + // we workaround this issue by forcing an explicit flush of the storage data. + // see also https://github.com/Microsoft/vscode/issues/172 + this.logService.trace('update#quitAndInstall(): calling flushStorageData()'); + electron.session.defaultSession.flushStorageData(); + + this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); + electron.autoUpdater.quitAndInstall(); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts new file mode 100644 index 0000000000000..a8c1ea132b2cd --- /dev/null +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { IRequestService } from 'vs/platform/request/node/request'; +import { State, IUpdate } from 'vs/platform/update/common/update'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ILogService } from 'vs/platform/log/common/log'; +import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/electron-main/abstractUpdateService'; +import { asJson } from 'vs/base/node/request'; + +export class LinuxUpdateService extends AbstractUpdateService { + + _serviceBrand: any; + + private url: string | undefined; + + constructor( + @ILifecycleService lifecycleService: ILifecycleService, + @IConfigurationService configurationService: IConfigurationService, + @ITelemetryService private telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, + @IRequestService private requestService: IRequestService, + @ILogService logService: ILogService + ) { + super(lifecycleService, configurationService, environmentService, logService); + } + + protected setUpdateFeedUrl(quality: string): boolean { + this.url = createUpdateURL(`linux-${process.arch}`, quality); + return true; + } + + protected doCheckForUpdates(explicit: boolean): void { + if (!this.url) { + return; + } + + this.requestService.request({ url: this.url }) + .then(asJson) + .then(update => { + if (!update || !update.url || !update.version || !update.productVersion) { + /* __GDPR__ + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('update:notAvailable', { explicit }); + + this.setState(State.Idle); + } else { + this.setState(State.Available(update)); + } + }) + .then(null, err => { + this.logService.error(err); + + /* __GDPR__ + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('update:notAvailable', { explicit }); + this.setState(State.Idle); + }); + } + + protected doQuitAndInstall(): void { + // not available + } +} diff --git a/src/vs/platform/update/electron-main/updateService.ts b/src/vs/platform/update/electron-main/updateService.ts deleted file mode 100644 index fde9b01d33f68..0000000000000 --- a/src/vs/platform/update/electron-main/updateService.ts +++ /dev/null @@ -1,340 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as fs from 'original-fs'; -import * as path from 'path'; -import * as electron from 'electron'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import Event, { Emitter, once, filterEvent, fromNodeEventEmitter } from 'vs/base/common/event'; -import { always, Throttler } from 'vs/base/common/async'; -import { memoize } from 'vs/base/common/decorators'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Win32AutoUpdaterImpl } from './auto-updater.win32'; -import { LinuxAutoUpdaterImpl } from './auto-updater.linux'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; -import { IRequestService } from 'vs/platform/request/node/request'; -import product from 'vs/platform/node/product'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IUpdateService, State, IAutoUpdater, IUpdate, IRawUpdate } from 'vs/platform/update/common/update'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ILogService } from 'vs/platform/log/common/log'; - -export class UpdateService implements IUpdateService { - - _serviceBrand: any; - - private _state: State = State.Uninitialized; - private _availableUpdate: IUpdate = null; - private raw: IAutoUpdater; - private throttler: Throttler = new Throttler(); - - private _onError = new Emitter(); - get onError(): Event { return this._onError.event; } - - private _onCheckForUpdate = new Emitter(); - get onCheckForUpdate(): Event { return this._onCheckForUpdate.event; } - - private _onUpdateAvailable = new Emitter<{ url: string; version: string; }>(); - get onUpdateAvailable(): Event<{ url: string; version: string; }> { return this._onUpdateAvailable.event; } - - private _onUpdateNotAvailable = new Emitter(); - get onUpdateNotAvailable(): Event { return this._onUpdateNotAvailable.event; } - - private _onUpdateDownloaded = new Emitter(); - get onUpdateDownloaded(): Event { return this._onUpdateDownloaded.event; } - - private _onUpdateInstalling = new Emitter(); - get onUpdateInstalling(): Event { return this._onUpdateInstalling.event; } - - private _onUpdateReady = new Emitter(); - get onUpdateReady(): Event { return this._onUpdateReady.event; } - - private _onStateChange = new Emitter(); - get onStateChange(): Event { return this._onStateChange.event; } - - @memoize - private get onRawError(): Event { - return fromNodeEventEmitter(this.raw, 'error', (_, message) => message); - } - - @memoize - private get onRawUpdateNotAvailable(): Event { - return fromNodeEventEmitter(this.raw, 'update-not-available'); - } - - @memoize - private get onRawUpdateAvailable(): Event<{ url: string; version: string; }> { - return filterEvent(fromNodeEventEmitter(this.raw, 'update-available', (_, url, version) => ({ url, version })), ({ url }) => !!url); - } - - @memoize - private get onRawUpdateDownloaded(): Event { - return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url, supportsFastUpdate) => ({ releaseNotes, version, date, supportsFastUpdate })); - } - - @memoize - private get onRawUpdateReady(): Event { - return fromNodeEventEmitter(this.raw, 'update-ready'); - } - - get state(): State { - return this._state; - } - - private updateState(state: State): void { - this._state = state; - this._onStateChange.fire(state); - } - - get availableUpdate(): IUpdate { - return this._availableUpdate; - } - - constructor( - @IRequestService requestService: IRequestService, - @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService private configurationService: IConfigurationService, - @ITelemetryService private telemetryService: ITelemetryService, - @IEnvironmentService private environmentService: IEnvironmentService, - @ILogService private logService: ILogService - ) { - if (process.platform === 'win32') { - this.raw = new Win32AutoUpdaterImpl(requestService); - } else if (process.platform === 'linux') { - this.raw = new LinuxAutoUpdaterImpl(requestService); - } else if (process.platform === 'darwin') { - this.raw = electron.autoUpdater; - } else { - return; - } - - if (this.environmentService.disableUpdates) { - return; - } - - const channel = this.getUpdateChannel(); - const feedUrl = this.getUpdateFeedUrl(channel); - - if (!feedUrl) { - return; // updates not available - } - - try { - this.raw.setFeedURL(feedUrl); - } catch (e) { - return; // application not signed - } - - this.updateState(State.Idle); - - // Start checking for updates after 30 seconds - this.scheduleCheckForUpdates(30 * 1000) - .done(null, err => this.logService.error(err)); - } - - private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise { - return TPromise.timeout(delay) - .then(() => this.checkForUpdates()) - .then(update => { - if (update) { - // Update found, no need to check more - return TPromise.as(null); - } - - // Check again after 1 hour - return this.scheduleCheckForUpdates(60 * 60 * 1000); - }); - } - - checkForUpdates(explicit = false): TPromise { - return this.throttler.queue(() => this._checkForUpdates(explicit)) - .then(null, err => { - if (explicit) { - this._onError.fire(err); - } - - return null; - }); - } - - private _checkForUpdates(explicit: boolean): TPromise { - if (this.state !== State.Idle) { - return TPromise.as(null); - } - - this._onCheckForUpdate.fire(); - this.updateState(State.CheckingForUpdate); - - const listeners: IDisposable[] = []; - const result = new TPromise((c, e) => { - once(this.onRawError)(e, null, listeners); - once(this.onRawUpdateNotAvailable)(() => c(null), null, listeners); - once(this.onRawUpdateAvailable)(({ url, version }) => url && c({ url, version }), null, listeners); - once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes, supportsFastUpdate }) => c({ version, date, releaseNotes, supportsFastUpdate }), null, listeners); - - this.raw.checkForUpdates(); - }).then(update => { - if (!update) { - this._onUpdateNotAvailable.fire(explicit); - this.updateState(State.Idle); - /* __GDPR__ - "update:notAvailable" : { - "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('update:notAvailable', { explicit }); - - // LINUX - } else if (update.url) { - const data: IUpdate = { - url: update.url, - releaseNotes: '', - version: update.version, - date: new Date() - }; - - this._availableUpdate = data; - this._onUpdateAvailable.fire({ url: update.url, version: update.version }); - this.updateState(State.UpdateAvailable); - /* __GDPR__ - "update:available" : { - "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "currentVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('update:available', { explicit, version: update.version, currentVersion: product.commit }); - - } else { - const data: IRawUpdate = { - releaseNotes: update.releaseNotes, - version: update.version, - date: update.date, - supportsFastUpdate: update.supportsFastUpdate - }; - - this._availableUpdate = data; - - if (update.supportsFastUpdate) { - this._onUpdateDownloaded.fire(data); - this.updateState(State.UpdateDownloaded); - } else { - this._onUpdateReady.fire(data); - this.updateState(State.UpdateReady); - } - - /* __GDPR__ - "update:downloaded" : { - "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('update:downloaded', { version: update.version }); - } - - return update; - }, err => { - this.updateState(State.Idle); - return TPromise.wrapError(err); - }); - - return always(result, () => dispose(listeners)); - } - - private getUpdateChannel(): string { - const channel = this.configurationService.getValue('update.channel'); - return channel === 'none' ? null : product.quality; - } - - private getUpdateFeedUrl(channel: string): string { - if (!channel) { - return null; - } - - if (process.platform === 'win32' && !fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) { - return null; - } - - if (!product.updateUrl || !product.commit) { - return null; - } - - const platform = this.getUpdatePlatform(); - - return `${product.updateUrl}/api/update/${platform}/${channel}/${product.commit}`; - } - - private getUpdatePlatform(): string { - if (process.platform === 'linux') { - return `linux-${process.arch}`; - } - - if (process.platform === 'win32' && process.arch === 'x64') { - return 'win32-x64'; - } - - return process.platform; - } - - // for windows fast updates - applyUpdate(): TPromise { - if (this.state !== State.UpdateDownloaded) { - return TPromise.as(null); - } - - if (!this.raw.applyUpdate) { - return TPromise.as(null); - } - - once(this.onRawUpdateReady)(() => { - this._onUpdateReady.fire(this._availableUpdate as IRawUpdate); - this.updateState(State.UpdateReady); - }); - - once(this.onRawUpdateNotAvailable)(() => { - this._onUpdateNotAvailable.fire(false); - this.updateState(State.Idle); - }); - - this._onUpdateInstalling.fire(this._availableUpdate as IRawUpdate); - this.updateState(State.UpdateInstalling); - return this.raw.applyUpdate(); - } - - quitAndInstall(): TPromise { - if (!this._availableUpdate) { - return TPromise.as(null); - } - - if (this._availableUpdate.url) { - electron.shell.openExternal(this._availableUpdate.url); - return TPromise.as(null); - } - - this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - - this.lifecycleService.quit(true /* from update */).done(vetod => { - this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); - if (vetod) { - return; - } - - // for some reason updating on Mac causes the local storage not to be flushed. - // we workaround this issue by forcing an explicit flush of the storage data. - // see also https://github.com/Microsoft/vscode/issues/172 - if (process.platform === 'darwin') { - this.logService.trace('update#quitAndInstall(): calling flushStorageData()'); - electron.session.defaultSession.flushStorageData(); - } - - this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); - this.raw.quitAndInstall(); - }); - - return TPromise.as(null); - } -} diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts new file mode 100644 index 0000000000000..80562db75bd92 --- /dev/null +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'original-fs'; +import * as path from 'path'; +import * as pfs from 'vs/base/node/pfs'; +import { memoize } from 'vs/base/common/decorators'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { IRequestService } from 'vs/platform/request/node/request'; +import product from 'vs/platform/node/product'; +import { TPromise, Promise } from 'vs/base/common/winjs.base'; +import { State, IUpdate, StateType } from 'vs/platform/update/common/update'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ILogService } from 'vs/platform/log/common/log'; +import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/electron-main/abstractUpdateService'; +import { download, asJson } from 'vs/base/node/request'; +import { checksum } from 'vs/base/node/crypto'; +import { tmpdir } from 'os'; +import { spawn } from 'child_process'; + +function pollUntil(fn: () => boolean, timeout = 1000): TPromise { + return new TPromise(c => { + const poll = () => { + if (fn()) { + c(null); + } else { + setTimeout(poll, timeout); + } + }; + + poll(); + }); +} + +interface IAvailableUpdate { + packagePath: string; + updateFilePath?: string; +} + +export class Win32UpdateService extends AbstractUpdateService { + + _serviceBrand: any; + + private url: string | undefined; + private availableUpdate: IAvailableUpdate | undefined; + + @memoize + get cachePath(): TPromise { + const result = path.join(tmpdir(), `vscode-update-${process.arch}`); + return pfs.mkdirp(result, null).then(() => result); + } + + constructor( + @ILifecycleService lifecycleService: ILifecycleService, + @IConfigurationService configurationService: IConfigurationService, + @ITelemetryService private telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, + @IRequestService private requestService: IRequestService, + @ILogService logService: ILogService + ) { + super(lifecycleService, configurationService, environmentService, logService); + } + + protected setUpdateFeedUrl(quality: string): boolean { + if (!fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) { + return false; + } + + this.url = createUpdateURL(process.arch === 'x64' ? 'win32-x64' : 'win32', quality); + return true; + } + + protected doCheckForUpdates(explicit: boolean): void { + if (!this.url) { + return; + } + + this.setState(State.CheckingForUpdates(explicit)); + + this.requestService.request({ url: this.url }) + .then(asJson) + .then(update => { + if (!update || !update.url || !update.version) { + /* __GDPR__ + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('update:notAvailable', { explicit }); + + this.setState(State.Idle); + return TPromise.as(null); + } + + this.setState(State.Downloading(update)); + + return this.cleanup(update.version).then(() => { + return this.getUpdatePackagePath(update.version).then(updatePackagePath => { + return pfs.exists(updatePackagePath).then(exists => { + if (exists) { + return TPromise.as(updatePackagePath); + } + + const url = update.url; + const hash = update.hash; + const downloadPath = `${updatePackagePath}.tmp`; + + return this.requestService.request({ url }) + .then(context => download(downloadPath, context)) + .then(hash ? () => checksum(downloadPath, update.hash) : () => null) + .then(() => pfs.rename(downloadPath, updatePackagePath)) + .then(() => updatePackagePath); + }); + }).then(packagePath => { + this.availableUpdate = { packagePath }; + + if (update.supportsFastUpdate) { + this.setState(State.Downloaded(update)); + } else { + this.setState(State.Ready(update)); + } + }); + }); + }) + .then(null, err => { + this.logService.error(err); + /* __GDPR__ + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('update:notAvailable', { explicit }); + this.setState(State.Idle); + }); + } + + private getUpdatePackagePath(version: string): TPromise { + return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`)); + } + + private cleanup(exceptVersion: string = null): Promise { + const filter = exceptVersion ? one => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true; + + return this.cachePath + .then(cachePath => pfs.readdir(cachePath) + .then(all => Promise.join(all + .filter(filter) + .map(one => pfs.unlink(path.join(cachePath, one)).then(null, () => null)) + )) + ); + } + + protected doApplyUpdate(): TPromise { + if (this.state.type !== StateType.Downloaded || !this.availableUpdate) { + return TPromise.as(null); + } + + const update = this.state.update; + this.setState(State.Updating(update)); + + return this.cachePath.then(cachePath => { + this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${update.version}.flag`); + + return pfs.writeFile(this.availableUpdate.updateFilePath, 'flag').then(() => { + const child = spawn(this.availableUpdate.packagePath, ['/verysilent', `/update="${this.availableUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { + detached: true, + stdio: ['ignore', 'ignore', 'ignore'] + }); + + child.once('exit', () => { + this.availableUpdate = undefined; + this.setState(State.Idle); + }); + + const readyMutexName = `${product.win32MutexName}-ready`; + const isActive = (require.__$__nodeRequire('windows-mutex') as any).isActive; + + // poll for mutex-ready + pollUntil(() => isActive(readyMutexName)) + .then(() => this.setState(State.Ready(update))); + }); + }); + } + + protected doQuitAndInstall(): void { + if (this.state.type !== StateType.Ready) { + return; + } + + this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); + + if (this.state.update.supportsFastUpdate && this.availableUpdate.updateFilePath) { + fs.unlinkSync(this.availableUpdate.updateFilePath); + } else { + spawn(this.availableUpdate.packagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { + detached: true, + stdio: ['ignore', 'ignore', 'ignore'] + }); + } + } +} diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 34e36f495bb54..edb19e1467050 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -9,7 +9,6 @@ import nls = require('vs/nls'); import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction, Action } from 'vs/base/common/actions'; -import { mapEvent, filterEvent, once } from 'vs/base/common/event'; import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMessageService, CloseAction, Severity } from 'vs/platform/message/common/message'; @@ -17,7 +16,7 @@ import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; import URI from 'vs/base/common/uri'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ReleaseNotesInput } from 'vs/workbench/parts/update/electron-browser/releaseNotesInput'; import { IGlobalActivity } from 'vs/workbench/common/activity'; @@ -29,10 +28,11 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update'; +import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update'; import * as semver from 'semver'; -import { OS, isLinux, isWindows } from 'vs/base/common/platform'; +import { OS } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; const NotNowAction = new Action( 'update.later', @@ -282,6 +282,21 @@ class CommandAction extends Action { } } +export class DownloadNowAction extends Action { + + constructor( + private url: string, + @IWindowsService private windowsService: IWindowsService + ) { + super('update.downloadNow', nls.localize('download now', "Download Now"), null, true); + } + + run(): TPromise { + this.windowsService.openExternal(this.url); + return TPromise.as(null); + } +} + export class UpdateContribution implements IGlobalActivity { private static readonly showCommandsId = 'workbench.action.showCommands'; @@ -295,6 +310,7 @@ export class UpdateContribution implements IGlobalActivity { get name() { return ''; } get cssClass() { return 'update-activity'; } + private state: UpdateState; private badgeDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; @@ -307,27 +323,7 @@ export class UpdateContribution implements IGlobalActivity { @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IActivityService private activityService: IActivityService ) { - if (isLinux) { - mapEvent(updateService.onUpdateAvailable, e => e.version) - (this.onUpdateAvailable, this, this.disposables); - } else if (isWindows) { - // fast updates - mapEvent(updateService.onUpdateDownloaded, e => e.version) - (this.onUpdateDownloaded, this, this.disposables); - mapEvent(updateService.onUpdateInstalling, e => e.version) - (this.onUpdateInstalling, this, this.disposables); - - // regular old updates - mapEvent(filterEvent(updateService.onUpdateReady, e => !e.supportsFastUpdate), e => e.version) - (this.onUpdateAvailable, this, this.disposables); - - } else { - mapEvent(updateService.onUpdateReady, e => e.version) - (this.onUpdateAvailable, this, this.disposables); - } - - updateService.onError(this.onError, this, this.disposables); - updateService.onUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables); + this.state = updateService.state; updateService.onStateChange(this.onUpdateStateChange, this, this.disposables); this.onUpdateStateChange(this.updateService.state); @@ -351,68 +347,84 @@ export class UpdateContribution implements IGlobalActivity { } private onUpdateStateChange(state: UpdateState): void { - this.badgeDisposable.dispose(); + switch (state.type) { + case StateType.Idle: + if (this.state.type === StateType.CheckingForUpdates && this.state.explicit) { + this.onUpdateNotAvailable(); + } + break; - const isUpdateAvailable = isLinux - ? state === UpdateState.UpdateAvailable - : state === UpdateState.UpdateDownloaded || state === UpdateState.UpdateReady; + case StateType.Available: + this.onUpdateAvailable(state.update); + break; - if (isUpdateAvailable) { - const badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); - this.badgeDisposable = this.activityService.showActivity(this.id, badge); + case StateType.Downloaded: + this.onUpdateDownloaded(state.update); + break; + + case StateType.Updating: + this.onUpdateUpdating(state.update); + break; + + case StateType.Ready: + this.onUpdateReady(state.update); + break; } - } - private shouldShowNotification(): boolean { - const currentVersion = product.commit; - const currentMillis = new Date().getTime(); - const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL); + let badge: IBadge | undefined = undefined; - // if version != stored version, save version and date - if (currentVersion !== lastKnownVersion) { - this.storageService.store('update/lastKnownVersion', currentVersion, StorageScope.GLOBAL); - this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL); + if (state.type === StateType.Available || state.type === StateType.Downloaded || state.type === StateType.Ready) { + badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); + } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { + badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); } - const updateNotificationMillis = this.storageService.getInteger('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis); - const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24); + this.badgeDisposable.dispose(); - return diffDays > 5; + if (badge) { + this.badgeDisposable = this.activityService.showActivity(this.id, badge); + } + + this.state = state; } - // windows fast updates - private onUpdateDownloaded(version: string): void { + private onUpdateNotAvailable(): void { + this.messageService.show(severity.Info, nls.localize('noUpdatesAvailable', "There are no updates currently available.")); + } + + // linux + private onUpdateAvailable(update: IUpdate): void { if (!this.shouldShowNotification()) { return; } - const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, version); - const installUpdateAction = new Action('update.applyUpdate', nls.localize('installUpdate', "Install Update"), undefined, true, () => { - once(mapEvent(filterEvent(this.updateService.onUpdateReady, e => e.supportsFastUpdate), e => e.version)) - (this.onWindowsFastUpdateReady, this); - - return this.updateService.applyUpdate(); - }); + const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); + const downloadAction = this.instantiationService.createInstance(DownloadNowAction, update.url); this.messageService.show(severity.Info, { - message: nls.localize('updateAvailable', "There's an available update: {0} {1}", product.nameLong, version), - actions: [installUpdateAction, NotNowAction, releaseNotesAction] + message: nls.localize('thereIsUpdateAvailable', "There is an available update."), + actions: [downloadAction, NotNowAction, releaseNotesAction] }); } // windows fast updates - private onWindowsFastUpdateReady(version: string): void { - const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, version); - const restartAction = new Action('update.applyUpdate', nls.localize('updateNow', "Update Now"), undefined, true, () => this.updateService.quitAndInstall()); + private onUpdateDownloaded(update: IUpdate): void { + if (!this.shouldShowNotification()) { + return; + } + + const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); + const installUpdateAction = new Action('update.applyUpdate', nls.localize('installUpdate', "Install Update"), undefined, true, () => + this.updateService.applyUpdate()); this.messageService.show(severity.Info, { - message: nls.localize('updateAvailableAfterRestart', "{0} will be updated after it restarts.", product.nameLong), - actions: [restartAction, NotNowAction, releaseNotesAction] + message: nls.localize('updateAvailable', "There's an available update: {0} {1}", product.nameLong, update.productVersion), + actions: [installUpdateAction, NotNowAction, releaseNotesAction] }); } // windows fast updates - private onUpdateInstalling(version: string): void { + private onUpdateUpdating(update: IUpdate): void { const neverShowAgain = new NeverShowAgain('update/win32-fast-updates', this.storageService); if (!neverShowAgain.shouldShow()) { @@ -420,51 +432,46 @@ export class UpdateContribution implements IGlobalActivity { } this.messageService.show(severity.Info, { - message: nls.localize('updateInstalling', "{0} {1} is being installed in the background, we'll let you know when it's done.", product.nameLong, version), + message: nls.localize('updateInstalling', "{0} {1} is being installed in the background, we'll let you know when it's done.", product.nameLong, update.productVersion), actions: [CloseAction, neverShowAgain.action] }); } - private onUpdateAvailable(version: string): void { + // windows and mac + private onUpdateReady(update: IUpdate): void { if (!this.shouldShowNotification()) { return; } - const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, version); - - if (isLinux) { - const downloadAction = new Action('update.download', nls.localize('downloadNow', "Download Now"), undefined, true, () => this.updateService.quitAndInstall()); - - this.messageService.show(severity.Info, { - message: nls.localize('thereIsUpdateAvailable', "There is an available update."), - actions: [downloadAction, NotNowAction, releaseNotesAction] - }); - } else { - const applyUpdateAction = new Action('update.applyUpdate', nls.localize('updateNow', "Update Now"), undefined, true, () => this.updateService.quitAndInstall()); + const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); + const applyUpdateAction = new Action('update.applyUpdate', nls.localize('updateNow', "Update Now"), undefined, true, () => + this.updateService.quitAndInstall()); - this.messageService.show(severity.Info, { - message: nls.localize('updateAvailableAfterRestart', "{0} will be updated after it restarts.", product.nameLong), - actions: [applyUpdateAction, NotNowAction, releaseNotesAction] - }); - } + this.messageService.show(severity.Info, { + message: nls.localize('updateAvailableAfterRestart', "{0} will be updated after it restarts.", product.nameLong), + actions: [applyUpdateAction, NotNowAction, releaseNotesAction] + }); } - private onUpdateNotAvailable(explicit: boolean): void { - if (!explicit) { - return; + private shouldShowNotification(): boolean { + const currentVersion = product.commit; + const currentMillis = new Date().getTime(); + const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL); + + // if version != stored version, save version and date + if (currentVersion !== lastKnownVersion) { + this.storageService.store('update/lastKnownVersion', currentVersion, StorageScope.GLOBAL); + this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL); } - this.messageService.show(severity.Info, nls.localize('noUpdatesAvailable', "There are no updates currently available.")); - } + const updateNotificationMillis = this.storageService.getInteger('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis); + const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24); - private onError(err: any): void { - this.messageService.show(severity.Error, err); + return diffDays > 5; } getActions(): IAction[] { - const updateAction = this.getUpdateAction(); - - return [ + const result: IAction[] = [ new CommandAction(UpdateContribution.showCommandsId, nls.localize('commandPalette', "Command Palette..."), this.commandService), new Separator(), new CommandAction(UpdateContribution.openSettingsId, nls.localize('settings', "Settings"), this.commandService), @@ -473,46 +480,48 @@ export class UpdateContribution implements IGlobalActivity { new CommandAction(UpdateContribution.openUserSnippets, nls.localize('userSnippets', "User Snippets"), this.commandService), new Separator(), new CommandAction(UpdateContribution.selectColorThemeId, nls.localize('selectTheme.label', "Color Theme"), this.commandService), - new CommandAction(UpdateContribution.selectIconThemeId, nls.localize('themes.selectIconTheme.label', "File Icon Theme"), this.commandService), - new Separator(), - updateAction + new CommandAction(UpdateContribution.selectIconThemeId, nls.localize('themes.selectIconTheme.label', "File Icon Theme"), this.commandService) ]; + + const updateAction = this.getUpdateAction(); + + if (updateAction) { + result.push(new Separator(), updateAction); + } + + return result; } - private getUpdateAction(): IAction { - switch (this.updateService.state) { - case UpdateState.Uninitialized: - return new Action('update.notavailable', nls.localize('not available', "Updates Not Available"), undefined, false); + private getUpdateAction(): IAction | null { + const state = this.updateService.state; - case UpdateState.CheckingForUpdate: - return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); + switch (state.type) { + case StateType.Uninitialized: + return null; - case UpdateState.UpdateAvailable: - if (isLinux) { - return new Action('update.linux.available', nls.localize('DownloadUpdate', "Download Available Update"), undefined, true, () => - this.updateService.quitAndInstall()); - } + case StateType.Idle: + return new Action('update.check', nls.localize('checkForUpdates', "Check for Updates..."), undefined, true, () => + this.updateService.checkForUpdates(true)); + + case StateType.CheckingForUpdates: + return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); - const updateAvailableLabel = isWindows - ? nls.localize('DownloadingUpdate', "Downloading Update...") - : nls.localize('InstallingUpdate', "Installing Update..."); + case StateType.Available: + return this.instantiationService.createInstance(DownloadNowAction, state.update.url); - return new Action('update.available', updateAvailableLabel, undefined, false); + case StateType.Downloading: + return new Action('update.downloading', nls.localize('DownloadingUpdate', "Downloading Update..."), undefined, false); - case UpdateState.UpdateDownloaded: - return new Action('update.apply', nls.localize('installUpdate...', "Install Update..."), undefined, true, () => + case StateType.Downloaded: + return new Action('update.install', nls.localize('installUpdate...', "Install Update..."), undefined, true, () => this.updateService.applyUpdate()); - case UpdateState.UpdateInstalling: - return new Action('update.applying', nls.localize('installingUpdate', "Installing Update..."), undefined, false); + case StateType.Updating: + return new Action('update.updating', nls.localize('installingUpdate', "Installing Update..."), undefined, false); - case UpdateState.UpdateReady: + case StateType.Ready: return new Action('update.restart', nls.localize('restartToUpdate', "Restart to Update..."), undefined, true, () => this.updateService.quitAndInstall()); - - default: - return new Action('update.check', nls.localize('checkForUpdates', "Check for Updates..."), undefined, this.updateService.state === UpdateState.Idle, () => - this.updateService.checkForUpdates(true)); } } From ca91ad4ee4d3b074dfb8e7a090f00e6eb26b8b8e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 21:14:04 +0100 Subject: [PATCH 10/21] fix state check --- src/vs/platform/update/electron-main/abstractUpdateService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index f7fa618119c49..fcbb2303851f7 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -89,7 +89,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } checkForUpdates(explicit = false): TPromise { - if (this.state !== State.Idle) { + if (this.state.type !== StateType.Idle) { return TPromise.as(null); } From 0192c680eb8c43e31b0dde9e56c3ce3fdc151223 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 22:13:21 +0100 Subject: [PATCH 11/21] linux: state should be idle after download --- src/vs/code/electron-main/menus.ts | 2 +- src/vs/platform/update/common/update.ts | 1 + src/vs/platform/update/common/updateIpc.ts | 6 +++++ .../electron-main/abstractUpdateService.ts | 14 +++++++++++- .../electron-main/updateService.linux.ts | 11 +++++++--- .../parts/update/electron-browser/update.ts | 22 ++++--------------- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 7d419505b8496..5c1daf2bea39d 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -1060,7 +1060,7 @@ export class CodeMenu { case StateType.Available: return [new MenuItem({ label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => { - shell.openExternal(state.update.url); + this.updateService.downloadUpdate(); } })]; diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 5a397113d9021..a80618c9ebbf9 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -68,6 +68,7 @@ export interface IUpdateService { readonly state: State; checkForUpdates(explicit: boolean): TPromise; + downloadUpdate(): TPromise; applyUpdate(): TPromise; quitAndInstall(): TPromise; } \ No newline at end of file diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index 9aef4457e9bf0..441bdd138e139 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -13,6 +13,7 @@ import { IUpdateService, State } from './update'; export interface IUpdateChannel extends IChannel { call(command: 'checkForUpdates', arg: boolean): TPromise; + call(command: 'downloadUpdate'): TPromise; call(command: 'applyUpdate'): TPromise; call(command: 'quitAndInstall'): TPromise; call(command: '_getInitialState'): TPromise; @@ -27,6 +28,7 @@ export class UpdateChannel implements IUpdateChannel { switch (command) { case 'event:onStateChange': return eventToCall(this.service.onStateChange); case 'checkForUpdates': return this.service.checkForUpdates(arg); + case 'downloadUpdate': return this.service.downloadUpdate(); case 'applyUpdate': return this.service.applyUpdate(); case 'quitAndInstall': return this.service.quitAndInstall(); case '_getInitialState': return TPromise.as(this.service.state); @@ -64,6 +66,10 @@ export class UpdateChannelClient implements IUpdateService { return this.channel.call('checkForUpdates', explicit); } + downloadUpdate(): TPromise { + return this.channel.call('downloadUpdate'); + } + applyUpdate(): TPromise { return this.channel.call('applyUpdate'); } diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index fcbb2303851f7..742d09737d19e 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import product from 'vs/platform/node/product'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IUpdateService, State, StateType } from 'vs/platform/update/common/update'; +import { IUpdateService, State, StateType, Available } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -96,6 +96,18 @@ export abstract class AbstractUpdateService implements IUpdateService { return this.throttler.queue(() => TPromise.as(this.doCheckForUpdates(explicit))); } + downloadUpdate(): TPromise { + if (this.state.type !== StateType.Available) { + return TPromise.as(null); + } + + return this.doDownloadUpdate(this.state); + } + + protected doDownloadUpdate(state: Available): TPromise { + return TPromise.as(null); + } + applyUpdate(): TPromise { if (this.state.type !== StateType.Ready) { return TPromise.as(null); diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index a8c1ea132b2cd..a59e24fd7227c 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -8,12 +8,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IRequestService } from 'vs/platform/request/node/request'; -import { State, IUpdate } from 'vs/platform/update/common/update'; +import { State, IUpdate, Available } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/electron-main/abstractUpdateService'; import { asJson } from 'vs/base/node/request'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { shell } from 'electron'; export class LinuxUpdateService extends AbstractUpdateService { @@ -71,7 +73,10 @@ export class LinuxUpdateService extends AbstractUpdateService { }); } - protected doQuitAndInstall(): void { - // not available + protected doDownloadUpdate(state: Available): TPromise { + shell.openExternal(state.update.url); + this.setState(State.Idle); + + return TPromise.as(null); } } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 87fcca32d3cb6..e091ce43563e5 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -32,7 +32,6 @@ import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/pla import * as semver from 'semver'; import { OS } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; const NotNowAction = new Action( 'update.later', @@ -282,21 +281,6 @@ class CommandAction extends Action { } } -export class DownloadNowAction extends Action { - - constructor( - private url: string, - @IWindowsService private windowsService: IWindowsService - ) { - super('update.downloadNow', nls.localize('download now', "Download Now"), null, true); - } - - run(): TPromise { - this.windowsService.openExternal(this.url); - return TPromise.as(null); - } -} - export class UpdateContribution implements IGlobalActivity { private static readonly showCommandsId = 'workbench.action.showCommands'; @@ -399,7 +383,8 @@ export class UpdateContribution implements IGlobalActivity { } const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); - const downloadAction = this.instantiationService.createInstance(DownloadNowAction, update.url); + const downloadAction = new Action('update.downloadNow', nls.localize('download now', "Download Now"), null, true, () => + this.updateService.downloadUpdate()); this.messageService.show(severity.Info, { message: nls.localize('thereIsUpdateAvailable', "There is an available update."), @@ -507,7 +492,8 @@ export class UpdateContribution implements IGlobalActivity { return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); case StateType.Available: - return this.instantiationService.createInstance(DownloadNowAction, state.update.url); + return new Action('update.downloadNow', nls.localize('download now', "Download Now"), null, true, () => + this.updateService.downloadUpdate()); case StateType.Downloading: return new Action('update.downloading', nls.localize('DownloadingUpdate', "Downloading Update..."), undefined, false); From 50064a2501768cd94b39598f83206f25c1d36892 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 22:35:27 +0100 Subject: [PATCH 12/21] enableWindowsBackgroundUpdates --- .../platform/update/electron-main/abstractUpdateService.ts | 2 +- src/vs/platform/update/electron-main/updateService.win32.ts | 4 +++- .../parts/update/electron-browser/update.contribution.ts | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 742d09737d19e..ade88f2e6f5e7 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -40,7 +40,7 @@ export abstract class AbstractUpdateService implements IUpdateService { constructor( @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService private configurationService: IConfigurationService, + @IConfigurationService protected configurationService: IConfigurationService, @IEnvironmentService private environmentService: IEnvironmentService, @ILogService protected logService: ILogService ) { diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 80562db75bd92..b50b3ac33b6f1 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -118,9 +118,11 @@ export class Win32UpdateService extends AbstractUpdateService { .then(() => updatePackagePath); }); }).then(packagePath => { + const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); + this.availableUpdate = { packagePath }; - if (update.supportsFastUpdate) { + if (fastUpdatesEnabled && update.supportsFastUpdate) { this.setState(State.Downloaded(update)); } else { this.setState(State.Ready(update)); diff --git a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts index d93239ac3ca6f..b533e60a755cb 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts @@ -57,6 +57,11 @@ configurationRegistry.registerConfiguration({ 'enum': ['none', 'default'], 'default': 'default', 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change.") + }, + 'update.enableWindowsBackgroundUpdates': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates.") } } }); From 04d38863910098da9224eb3a6d5e9a3ca531192c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 19 Jan 2018 22:47:12 +0100 Subject: [PATCH 13/21] win32: code should run after regular update --- build/win32/code.iss | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index 7e8326e43134e..fc9b79a33c842 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -961,30 +961,30 @@ begin end; // Updates -function IsUpdate(): Boolean; +function IsBackgroundUpdate(): Boolean; begin Result := ExpandConstant('{param:update|false}') <> 'false'; end; function IsNotUpdate(): Boolean; begin - Result := not IsUpdate(); + Result := not IsBackgroundUpdate(); end; -// VS Code will create a flag file before the update starts (/update=C:\foo\bar) -// - if the file exists at this point, the user quit Code before the update finished, so don't start Code after update -// - otherwise, the user has accepted to apply the update and Code should start function ShouldRunAfterUpdate(): Boolean; begin - if IsUpdate() then + if IsBackgroundUpdate() then + // VS Code will create a flag file before the update starts (/update=C:\foo\bar) + // - if the file exists at this point, the user quit Code before the update finished, so don't start Code after update + // - otherwise, the user has accepted to apply the update and Code should start Result := not FileExists(ExpandConstant('{param:update}')) else - Result := False; + Result := True; end; function GetAppMutex(Value: string): string; begin - if IsUpdate() then + if IsBackgroundUpdate() then Result := '' else Result := '{#AppMutex}'; @@ -992,7 +992,7 @@ end; function GetDestDir(Value: string): string; begin - if IsUpdate() then + if IsBackgroundUpdate() then Result := ExpandConstant('{app}\_') else Result := ExpandConstant('{app}'); @@ -1002,7 +1002,7 @@ procedure CurStepChanged(CurStep: TSetupStep); var UpdateResultCode: Integer; begin - if IsUpdate() and (CurStep = ssPostInstall) then + if IsBackgroundUpdate() and (CurStep = ssPostInstall) then begin CreateMutex('{#AppMutex}-ready'); From a43926a61e073d7a5b08ce7d297bd93cadb0fabc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Sat, 20 Jan 2018 15:24:23 +0100 Subject: [PATCH 14/21] document update state machine --- src/vs/code/electron-main/menus.ts | 2 +- src/vs/platform/update/common/update.ts | 26 ++++++++++++++++--- .../electron-main/abstractUpdateService.ts | 6 ++--- .../electron-main/updateService.linux.ts | 6 ++--- .../parts/update/electron-browser/update.ts | 6 ++--- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 5c1daf2bea39d..7e389c637293a 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -1057,7 +1057,7 @@ export class CodeMenu { case StateType.CheckingForUpdates: return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })]; - case StateType.Available: + case StateType.AvailableForDownload: return [new MenuItem({ label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => { this.updateService.downloadUpdate(); diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index a80618c9ebbf9..8ce018cb411f7 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -19,11 +19,29 @@ export interface IUpdate { hash?: string; } +/** + * Updates are run as a state machine: + * + * Uninitialized + * ↓ + * Idle + * ↓ ↑ + * Checking for Updates → Available for Download + * ↓ + * Downloading → Ready + * ↓ ↑ + * Downloaded → Updating + * + * Available: There is an update available for download (linux). + * Ready: Code will be updated as soon as it restarts (win32, darwin). + * Donwloaded: There is an update ready to be installed in the background (win32). + */ + export enum StateType { Uninitialized = 'uninitialized', Idle = 'idle', - Available = 'available', CheckingForUpdates = 'checking for updates', + AvailableForDownload = 'available for download', Downloading = 'downloading', Downloaded = 'downloaded', Updating = 'updating', @@ -33,19 +51,19 @@ export enum StateType { export type Uninitialized = { type: StateType.Uninitialized }; export type Idle = { type: StateType.Idle }; export type CheckingForUpdates = { type: StateType.CheckingForUpdates, explicit: boolean }; -export type Available = { type: StateType.Available, update: IUpdate }; +export type AvailableForDownload = { type: StateType.AvailableForDownload, update: IUpdate }; export type Downloading = { type: StateType.Downloading, update: IUpdate }; export type Downloaded = { type: StateType.Downloaded, update: IUpdate }; export type Updating = { type: StateType.Updating, update: IUpdate }; export type Ready = { type: StateType.Ready, update: IUpdate }; -export type State = Uninitialized | Idle | CheckingForUpdates | Available | Downloading | Downloaded | Updating | Ready; +export type State = Uninitialized | Idle | CheckingForUpdates | AvailableForDownload | Downloading | Downloaded | Updating | Ready; export const State = { Uninitialized: { type: StateType.Uninitialized } as Uninitialized, Idle: { type: StateType.Idle } as Idle, CheckingForUpdates: (explicit: boolean) => ({ type: StateType.CheckingForUpdates, explicit } as CheckingForUpdates), - Available: (update: IUpdate) => ({ type: StateType.Available, update } as Available), + AvailableForDownload: (update: IUpdate) => ({ type: StateType.AvailableForDownload, update } as AvailableForDownload), Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading), Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded), Updating: (update: IUpdate) => ({ type: StateType.Updating, update } as Updating), diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index ade88f2e6f5e7..97055d435d167 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import product from 'vs/platform/node/product'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IUpdateService, State, StateType, Available } from 'vs/platform/update/common/update'; +import { IUpdateService, State, StateType, AvailableForDownload } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -97,14 +97,14 @@ export abstract class AbstractUpdateService implements IUpdateService { } downloadUpdate(): TPromise { - if (this.state.type !== StateType.Available) { + if (this.state.type !== StateType.AvailableForDownload) { return TPromise.as(null); } return this.doDownloadUpdate(this.state); } - protected doDownloadUpdate(state: Available): TPromise { + protected doDownloadUpdate(state: AvailableForDownload): TPromise { return TPromise.as(null); } diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index a59e24fd7227c..53cb9a6ae9b7e 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -8,7 +8,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IRequestService } from 'vs/platform/request/node/request'; -import { State, IUpdate, Available } from 'vs/platform/update/common/update'; +import { State, IUpdate, AvailableForDownload } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -57,7 +57,7 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.Idle); } else { - this.setState(State.Available(update)); + this.setState(State.AvailableForDownload(update)); } }) .then(null, err => { @@ -73,7 +73,7 @@ export class LinuxUpdateService extends AbstractUpdateService { }); } - protected doDownloadUpdate(state: Available): TPromise { + protected doDownloadUpdate(state: AvailableForDownload): TPromise { shell.openExternal(state.update.url); this.setState(State.Idle); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index e091ce43563e5..2c705a7297426 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -338,7 +338,7 @@ export class UpdateContribution implements IGlobalActivity { } break; - case StateType.Available: + case StateType.AvailableForDownload: this.onUpdateAvailable(state.update); break; @@ -357,7 +357,7 @@ export class UpdateContribution implements IGlobalActivity { let badge: IBadge | undefined = undefined; - if (state.type === StateType.Available || state.type === StateType.Downloaded || state.type === StateType.Ready) { + if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); @@ -491,7 +491,7 @@ export class UpdateContribution implements IGlobalActivity { case StateType.CheckingForUpdates: return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); - case StateType.Available: + case StateType.AvailableForDownload: return new Action('update.downloadNow', nls.localize('download now', "Download Now"), null, true, () => this.updateService.downloadUpdate()); From 9f4dc79a03910415d28145e1798b81b2450dadb0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 09:09:02 +0100 Subject: [PATCH 15/21] linux: missing checking for updatse --- src/vs/platform/update/electron-main/updateService.linux.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 53cb9a6ae9b7e..cb36c65858561 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -44,6 +44,8 @@ export class LinuxUpdateService extends AbstractUpdateService { return; } + this.setState(State.CheckingForUpdates(explicit)); + this.requestService.request({ url: this.url }) .then(asJson) .then(update => { From 45bb5bf3be92f24de8a457a79ed21d23c7c1c943 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 10:41:54 +0100 Subject: [PATCH 16/21] move inno_updater.exe to windows build directory --- build/gulpfile.vscode.js | 2 ++ build/gulpfile.vscode.win32.js | 2 +- build/win32/code.iss | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index a0b70de7f147e..90f10ed4240f1 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -349,6 +349,8 @@ function packageTask(platform, arch, opts) { result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' }) .pipe(rename(product.nameShort + '.VisualElementsManifest.xml'))); + + result = es.merge(result, gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' })); } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 8630284bc1f63..a24daa47e9a02 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -77,7 +77,7 @@ gulp.task('vscode-win32-x64-setup', ['clean-vscode-win32-x64-setup'], buildWin32 function archiveWin32Setup(arch) { return cb => { - const args = ['a', '-tzip', zipPath(arch), '.', '-r']; + const args = ['a', '-tzip', zipPath(arch), '.', '-r', '-x!inno_updater.exe']; cp.spawn(_7z, args, { stdio: 'inherit', cwd: buildPath(arch) }) .on('error', cb) diff --git a/build/win32/code.iss b/build/win32/code.iss index fc9b79a33c842..1601f540fa2c3 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -67,8 +67,8 @@ Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent [Files] -Source: "*"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "{#RepoDir}\build\win32\inno_updater.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "*"; Excludes: "inno_updater.exe"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "inno_updater.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}" From 19bab787f4e7e970cabc7340008d56eadc129f61 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 10:47:00 +0100 Subject: [PATCH 17/21] move inno_updater into app when building setup --- build/gulpfile.vscode.js | 2 -- build/gulpfile.vscode.win32.js | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 90f10ed4240f1..a0b70de7f147e 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -349,8 +349,6 @@ function packageTask(platform, arch, opts) { result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' }) .pipe(rename(product.nameShort + '.VisualElementsManifest.xml'))); - - result = es.merge(result, gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' })); } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index a24daa47e9a02..b8d0be611166b 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -9,10 +9,12 @@ const gulp = require('gulp'); const path = require('path'); const assert = require('assert'); const cp = require('child_process'); +const es = require('event-stream'); const _7z = require('7zip')['7z']; const util = require('./lib/util'); const pkg = require('../package.json'); const product = require('../product.json'); +const vfs = require('vinyl-fs'); const repoPath = path.dirname(__dirname); const buildPath = arch => path.join(path.dirname(repoPath), `VSCode-win32-${arch}`); @@ -38,8 +40,8 @@ function packageInnoSetup(iss, options, cb) { .on('exit', () => cb(null)); } -function buildWin32Setup(arch) { - return cb => { +function _buildWin32Setup(arch) { + return es.through(null, function () { const ia32AppId = product.win32AppId; const x64AppId = product.win32x64AppId; @@ -65,7 +67,15 @@ function buildWin32Setup(arch) { OutputDir: setupDir(arch) }; - packageInnoSetup(issPath, { definitions }, cb); + packageInnoSetup(issPath, { definitions }, err => err ? this.emit('error', err) : this.emit('end')); + }); +} + +function buildWin32Setup(arch) { + return () => { + return gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' }) + .pipe(vfs.dest(buildPath)) + .pipe(_buildWin32Setup(arch)); }; } From 7b55cebb00783d170a0e3612033b2255ec467e31 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 11:10:04 +0100 Subject: [PATCH 18/21] show progress bar for update in progress --- .../parts/activitybar/activitybarPart.ts | 6 ++--- .../parts/compositebar/compositeBarActions.ts | 23 +++++++++++++++---- .../parts/update/electron-browser/update.ts | 4 +++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 627be4bee3400..122d34883f6d0 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -93,10 +93,10 @@ export class ActivitybarPart extends Part { return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority); } - return this.showGlobalActivity(viewletOrActionId, badge); + return this.showGlobalActivity(viewletOrActionId, badge, clazz); } - private showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable { + private showGlobalActivity(globalActivityId: string, badge: IBadge, clazz?: string): IDisposable { if (!badge) { throw illegalArgument('badge'); } @@ -106,7 +106,7 @@ export class ActivitybarPart extends Part { throw illegalArgument('globalActivityId'); } - action.setBadge(badge); + action.setBadge(badge, clazz); return toDisposable(() => action.setBadge(undefined)); } diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts index 6c4f3102f8464..61b5c8d569798 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts @@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom'; import { Builder, $ } from 'vs/base/browser/builder'; import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { dispose } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, empty, toDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; @@ -53,6 +53,7 @@ export interface ICompositeBar { export class ActivityAction extends Action { private badge: IBadge; + private clazz: string | undefined; private _onDidChangeBadge = new Emitter(); constructor(private _activity: IActivity) { @@ -85,8 +86,13 @@ export class ActivityAction extends Action { return this.badge; } - public setBadge(badge: IBadge): void { + public getClass(): string | undefined { + return this.clazz; + } + + public setBadge(badge: IBadge, clazz?: string): void { this.badge = badge; + this.clazz = clazz; this._onDidChangeBadge.fire(this); } } @@ -110,6 +116,7 @@ export class ActivityActionItem extends BaseActionItem { protected options: IActivityActionItemOptions; private $badgeContent: Builder; + private badgeDisposable: IDisposable = empty; private mouseUpTimeout: number; constructor( @@ -199,7 +206,10 @@ export class ActivityActionItem extends BaseActionItem { this.updateStyles(); } - protected updateBadge(badge: IBadge): void { + protected updateBadge(badge: IBadge, clazz?: string): void { + this.badgeDisposable.dispose(); + this.badgeDisposable = empty; + this.$badgeContent.empty(); this.$badge.hide(); @@ -234,6 +244,11 @@ export class ActivityActionItem extends BaseActionItem { else if (badge instanceof ProgressBadge) { this.$badge.show(); } + + if (clazz) { + this.$badge.addClass(clazz); + this.badgeDisposable = toDisposable(() => this.$badge.removeClass(clazz)); + } } // Title @@ -259,7 +274,7 @@ export class ActivityActionItem extends BaseActionItem { private handleBadgeChangeEvenet(): void { const action = this.getAction(); if (action instanceof ActivityAction) { - this.updateBadge(action.getBadge()); + this.updateBadge(action.getBadge(), action.getClass()); } } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 2c705a7297426..380a3c068ebbe 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -356,17 +356,19 @@ export class UpdateContribution implements IGlobalActivity { } let badge: IBadge | undefined = undefined; + let clazz: string | undefined; if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", product.nameShort)); + clazz = 'progress-badge'; } this.badgeDisposable.dispose(); if (badge) { - this.badgeDisposable = this.activityService.showActivity(this.id, badge); + this.badgeDisposable = this.activityService.showActivity(this.id, badge, clazz); } this.state = state; From cdb7246a2df5daa3abe6d8b23ec00555da415e14 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 11:16:23 +0100 Subject: [PATCH 19/21] add log statements to update service --- .../update/electron-main/abstractUpdateService.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 97055d435d167..fcc47b9f7a684 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -34,6 +34,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } protected setState(state: State): void { + this.logService.info('update#setState', state.type); this._state = state; this._onStateChange.fire(state); } @@ -45,20 +46,24 @@ export abstract class AbstractUpdateService implements IUpdateService { @ILogService protected logService: ILogService ) { if (this.environmentService.disableUpdates) { + this.logService.info('update#ctor - updates are disabled'); return; } if (!product.updateUrl || !product.commit) { + this.logService.info('update#ctor - updates are disabled'); return; } const quality = this.getProductQuality(); if (!quality) { + this.logService.info('update#ctor - updates are disabled'); return; } if (!this.setUpdateFeedUrl(quality)) { + this.logService.info('update#ctor - updates are disabled'); return; } @@ -89,6 +94,8 @@ export abstract class AbstractUpdateService implements IUpdateService { } checkForUpdates(explicit = false): TPromise { + this.logService.trace('update#checkForUpdates, state = ', this.state.type); + if (this.state.type !== StateType.Idle) { return TPromise.as(null); } @@ -97,6 +104,8 @@ export abstract class AbstractUpdateService implements IUpdateService { } downloadUpdate(): TPromise { + this.logService.trace('update#downloadUpdate, state = ', this.state.type); + if (this.state.type !== StateType.AvailableForDownload) { return TPromise.as(null); } @@ -109,6 +118,8 @@ export abstract class AbstractUpdateService implements IUpdateService { } applyUpdate(): TPromise { + this.logService.trace('update#applyUpdate, state = ', this.state.type); + if (this.state.type !== StateType.Ready) { return TPromise.as(null); } @@ -121,6 +132,8 @@ export abstract class AbstractUpdateService implements IUpdateService { } quitAndInstall(): TPromise { + this.logService.trace('update#quitAndInstall, state = ', this.state.type); + if (this.state.type !== StateType.Ready) { return TPromise.as(null); } From 11e05b6a3939a63980f8a0a5d495e6c4fb2c1172 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 12:07:47 +0100 Subject: [PATCH 20/21] fix bad build script --- build/gulpfile.vscode.win32.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index b8d0be611166b..a7ecdf66f5aee 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -74,7 +74,7 @@ function _buildWin32Setup(arch) { function buildWin32Setup(arch) { return () => { return gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' }) - .pipe(vfs.dest(buildPath)) + .pipe(vfs.dest(buildPath(arch))) .pipe(_buildWin32Setup(arch)); }; } From 4846088f22202df577c6b84b5c29745abefff730 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 22 Jan 2018 15:47:18 +0100 Subject: [PATCH 21/21] fix inno updater --- build/gulpfile.vscode.win32.js | 25 +++++++++++++------------ build/tfs/win32/1_build.ps1 | 4 ++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index a7ecdf66f5aee..15459f7dae80d 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -9,7 +9,6 @@ const gulp = require('gulp'); const path = require('path'); const assert = require('assert'); const cp = require('child_process'); -const es = require('event-stream'); const _7z = require('7zip')['7z']; const util = require('./lib/util'); const pkg = require('../package.json'); @@ -40,8 +39,8 @@ function packageInnoSetup(iss, options, cb) { .on('exit', () => cb(null)); } -function _buildWin32Setup(arch) { - return es.through(null, function () { +function buildWin32Setup(arch) { + return cb => { const ia32AppId = product.win32AppId; const x64AppId = product.win32x64AppId; @@ -67,15 +66,7 @@ function _buildWin32Setup(arch) { OutputDir: setupDir(arch) }; - packageInnoSetup(issPath, { definitions }, err => err ? this.emit('error', err) : this.emit('end')); - }); -} - -function buildWin32Setup(arch) { - return () => { - return gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' }) - .pipe(vfs.dest(buildPath(arch))) - .pipe(_buildWin32Setup(arch)); + packageInnoSetup(issPath, { definitions }, cb); }; } @@ -100,3 +91,13 @@ gulp.task('vscode-win32-ia32-archive', ['clean-vscode-win32-ia32-archive'], arch gulp.task('clean-vscode-win32-x64-archive', util.rimraf(zipDir('x64'))); gulp.task('vscode-win32-x64-archive', ['clean-vscode-win32-x64-archive'], archiveWin32Setup('x64')); + +function copyInnoUpdater(arch) { + return () => { + return gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' }) + .pipe(vfs.dest(buildPath(arch))); + }; +} + +gulp.task('vscode-win32-ia32-copy-inno-updater', copyInnoUpdater('ia32')); +gulp.task('vscode-win32-x64-copy-inno-updater', copyInnoUpdater('x64')); \ No newline at end of file diff --git a/build/tfs/win32/1_build.ps1 b/build/tfs/win32/1_build.ps1 index bc6ade13de33b..0fcb56c1b9e4a 100644 --- a/build/tfs/win32/1_build.ps1 +++ b/build/tfs/win32/1_build.ps1 @@ -45,6 +45,10 @@ step "Build minified" { exec { & npm run gulp -- "vscode-win32-$global:arch-min" } } +step "Copy Inno updater" { + exec { & npm run gulp -- "vscode-win32-$global:arch-copy-inno-updater" } +} + # step "Create loader snapshot" { # exec { & node build\lib\snapshotLoader.js --arch=$global:arch } # }