1 /*
2  *Copyright (C) 2018 Laurent Tréguier
3  *
4  *This file is part of DLS.
5  *
6  *DLS is free software: you can redistribute it and/or modify
7  *it under the terms of the GNU General Public License as published by
8  *the Free Software Foundation, either version 3 of the License, or
9  *(at your option) any later version.
10  *
11  *DLS is distributed in the hope that it will be useful,
12  *but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *GNU General Public License for more details.
15  *
16  *You should have received a copy of the GNU General Public License
17  *along with DLS.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 module dls.updater;
22 
23 private immutable changelogUrl = "https://github.com/d-language-server/dls/blob/v%s/CHANGELOG.md";
24 
25 void cleanup()
26 {
27     import dls.bootstrap : dubBinDir;
28     import dls.info : currentVersion;
29     import dub.semver : compareVersions;
30     import std.file : FileException, SpanMode, dirEntries, isSymlink, remove, rmdirRecurse;
31     import std.path : baseName;
32     import std.regex : matchFirst;
33 
34     foreach (string entry; dirEntries(dubBinDir, SpanMode.shallow))
35     {
36         const match = entry.baseName.matchFirst(`dls-v([\d.]+)`);
37 
38         if (match)
39         {
40             if (compareVersions(currentVersion, match[1]) > 0)
41             {
42                 try
43                 {
44                     rmdirRecurse(entry);
45                 }
46                 catch (FileException e)
47                 {
48                 }
49             }
50         }
51         else if (isSymlink(entry))
52         {
53             try
54             {
55                 version (Windows)
56                 {
57                     import std.file : isDir, rmdir;
58                     import std.stdio : File;
59 
60                     if (isDir(entry))
61                     {
62                         try
63                         {
64                             dirEntries(entry, SpanMode.shallow);
65                         }
66                         catch (FileException e)
67                         {
68                             rmdir(entry);
69                         }
70                     }
71                     else
72                     {
73                         try
74                         {
75                             File(entry, "rb");
76                         }
77                         catch (Exception e)
78                         {
79                             remove(entry);
80                         }
81                     }
82                 }
83                 else version (Posix)
84                 {
85                     import std.file : exists, readLink;
86 
87                     if (!exists(readLink(entry)))
88                     {
89                         remove(entry);
90                     }
91                 }
92                 else
93                 {
94                     static assert(false, "Platform not supported");
95                 }
96             }
97             catch (Exception e)
98             {
99             }
100         }
101     }
102 }
103 
104 void update(bool autoUpdate)
105 {
106     import core.time : hours;
107     import dls.bootstrap : UpgradeFailedException, buildDls, canDownloadDls,
108         downloadDls, allReleases, linkDls;
109     import dls.info : currentVersion;
110     static import dls.protocol.jsonrpc;
111     import dls.protocol.interfaces.dls : DlsUpgradeSizeParams, TranslationParams;
112     import dls.protocol.logger : logger;
113     import dls.protocol.messages.methods : Dls;
114     import dls.protocol.messages.window : Util;
115     import dls.protocol.state : initOptions;
116     import dls.util.i18n : Tr;
117     import dub.dependency : Dependency;
118     import dub.dub : Dub, FetchOptions;
119     import dub.semver : compareVersions;
120     import std.algorithm : filter, stripLeft;
121     import std.array : array;
122     import std.concurrency : ownerTid, receiveOnly, register, send, thisTid;
123     import std.datetime : Clock, SysTime;
124     import std.format : format;
125     import std.json : JSON_TYPE;
126     import std.path : asNormalizedPath;
127 
128     auto validReleases = allReleases.filter!(r => r["prerelease"].type == JSON_TYPE.FALSE
129             || initOptions.preReleaseBuilds);
130 
131     if (validReleases.empty)
132     {
133         logger.warning("Unable to find any valid release");
134         return;
135     }
136 
137     immutable latestRelease = validReleases.front;
138     immutable latestVersion = latestRelease["tag_name"].str.stripLeft('v');
139     immutable releaseTime = SysTime.fromISOExtString(latestRelease["published_at"].str);
140 
141     if (latestVersion.length == 0 || compareVersions(currentVersion,
142             latestVersion) >= 0 || (Clock.currTime.toUTC() - releaseTime < 1.hours))
143     {
144         return;
145     }
146 
147     if (!autoUpdate)
148     {
149         auto id = Util.sendMessageRequest(Tr.app_upgradeDls,
150                 [Tr.app_upgradeDls_upgrade], [latestVersion, currentVersion]);
151         immutable threadName = "updater";
152         register(threadName, thisTid());
153         send(ownerTid(), Util.ThreadMessageData(id, Tr.app_upgradeDls, threadName));
154 
155         immutable shouldUpgrade = receiveOnly!bool();
156 
157         if (!shouldUpgrade)
158         {
159             return;
160         }
161     }
162 
163     dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStart,
164             new TranslationParams(Tr.app_upgradeDls_upgrading));
165 
166     scope (exit)
167     {
168         dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStop);
169     }
170 
171     bool upgradeSuccessful;
172 
173     if (canDownloadDls)
174     {
175         try
176         {
177             enum totalSizeCallback = (size_t size) {
178                 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeTotalSize,
179                         new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size));
180             };
181             enum chunkSizeCallback = (size_t size) {
182                 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeCurrentSize,
183                         new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size));
184             };
185             enum extractCallback = () {
186                 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didExtract,
187                         new TranslationParams(Tr.app_upgradeDls_extracting));
188             };
189 
190             downloadDls(totalSizeCallback, chunkSizeCallback, extractCallback);
191             upgradeSuccessful = true;
192         }
193         catch (Exception e)
194         {
195             logger.error("Could not download DLS: %s", e.msg);
196         }
197     }
198 
199     if (!upgradeSuccessful)
200     {
201         auto dub = new Dub();
202         FetchOptions fetchOpts;
203         fetchOpts |= FetchOptions.forceBranchUpgrade;
204         const pack = dub.fetch("dls", Dependency(">=0.0.0"),
205                 dub.defaultPlacementLocation, fetchOpts);
206 
207         int i;
208         immutable additionalArgs = [[], ["--force"]];
209 
210         do
211         {
212             try
213             {
214                 buildDls(pack.path.toString().asNormalizedPath.array, additionalArgs[i]);
215                 upgradeSuccessful = true;
216             }
217             catch (UpgradeFailedException e)
218             {
219                 ++i;
220             }
221         }
222         while (i < additionalArgs.length && !upgradeSuccessful);
223 
224         if (!upgradeSuccessful)
225         {
226             Util.sendMessage(Tr.app_upgradeDls_buildError);
227             return;
228         }
229     }
230 
231     try
232     {
233         linkDls();
234         auto id = Util.sendMessageRequest(Tr.app_showChangelog,
235                 [Tr.app_showChangelog_show], [latestVersion]);
236         send(ownerTid(), Util.ThreadMessageData(id, Tr.app_showChangelog,
237                 format!changelogUrl(latestVersion)));
238     }
239     catch (UpgradeFailedException e)
240     {
241         Util.sendMessage(Tr.app_upgradeDls_linkError);
242     }
243 }