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 import std.format : format; 24 25 private enum descriptionJson = import("description.json"); 26 private immutable changelogUrl = format!"https://github.com/d-language-server/dls/blob/v%s/CHANGELOG.md"( 27 currentVersion); 28 29 void cleanup() 30 { 31 import dls.bootstrap : dubBinDir; 32 import dub.semver : compareVersions; 33 import std.file : FileException, SpanMode, dirEntries, isSymlink, remove, 34 rmdirRecurse; 35 import std.path : baseName; 36 import std.regex : matchFirst; 37 38 foreach (string entry; dirEntries(dubBinDir, SpanMode.shallow)) 39 { 40 const match = entry.baseName.matchFirst(`dls-v([\d.]+)`); 41 42 if (match) 43 { 44 if (compareVersions(currentVersion, match[1]) > 0) 45 { 46 try 47 { 48 rmdirRecurse(entry); 49 } 50 catch (FileException e) 51 { 52 } 53 } 54 } 55 else if (isSymlink(entry)) 56 { 57 try 58 { 59 version (Windows) 60 { 61 import std.file : isDir, rmdir; 62 import std.stdio : File; 63 64 if (isDir(entry)) 65 { 66 try 67 { 68 dirEntries(entry, SpanMode.shallow); 69 } 70 catch (FileException e) 71 { 72 rmdir(entry); 73 } 74 } 75 else 76 { 77 try 78 { 79 File(entry, "rb"); 80 } 81 catch (Exception e) 82 { 83 remove(entry); 84 } 85 } 86 } 87 else version (Posix) 88 { 89 import std.file : exists, readLink; 90 91 if (!exists(readLink(entry))) 92 { 93 remove(entry); 94 } 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, apiEndpoint, buildDls, 108 canDownloadDls, downloadDls, linkDls; 109 static import dls.protocol.jsonrpc; 110 import dls.protocol.interfaces.dls : DlsUpgradeSizeParams, 111 TranslationParams; 112 import dls.protocol.messages.methods : Dls; 113 import dls.protocol.messages.window : Util; 114 import dls.util.constants : Tr; 115 import dls.util.logger : logger; 116 import dls.util.path : normalized; 117 import dub.dependency : Dependency; 118 import dub.dub : Dub, FetchOptions; 119 import dub.semver : compareVersions; 120 import std.algorithm : stripLeft; 121 import std.concurrency : ownerTid, receiveOnly, register, send, thisTid; 122 import std.datetime : Clock, SysTime; 123 import std.json : parseJSON; 124 import std.net.curl : get; 125 126 const latestRelease = parseJSON(get(format!apiEndpoint("releases/latest"))); 127 const latestVersion = latestRelease["tag_name"].str.stripLeft('v'); 128 const releaseTime = SysTime.fromISOExtString(latestRelease["published_at"].str); 129 130 if (latestVersion.length == 0 || compareVersions(currentVersion, 131 latestVersion) >= 0 || (Clock.currTime.toUTC() - releaseTime < 1.hours)) 132 { 133 return; 134 } 135 136 if (!autoUpdate) 137 { 138 auto id = Util.sendMessageRequest(Tr.app_upgradeDls, 139 [Tr.app_upgradeDls_upgrade], [latestVersion, currentVersion]); 140 const threadName = "updater"; 141 register(threadName, thisTid()); 142 send(ownerTid(), Util.ThreadMessageData(id, Tr.app_upgradeDls, threadName)); 143 144 const shouldUpgrade = receiveOnly!bool(); 145 146 if (!shouldUpgrade) 147 { 148 return; 149 } 150 } 151 152 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStart, 153 new TranslationParams(Tr.app_upgradeDls_upgrading)); 154 155 scope (exit) 156 { 157 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStop); 158 } 159 160 bool upgradeSuccessful; 161 162 if (canDownloadDls) 163 { 164 try 165 { 166 enum totalSizeCallback = (size_t size) { 167 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeTotalSize, 168 new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size)); 169 }; 170 enum chunkSizeCallback = (size_t size) { 171 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeCurrentSize, 172 new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size)); 173 }; 174 enum extractCallback = () { 175 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didExtract, 176 new TranslationParams(Tr.app_upgradeDls_extracting)); 177 }; 178 179 downloadDls(totalSizeCallback, chunkSizeCallback, extractCallback); 180 upgradeSuccessful = true; 181 } 182 catch (Exception e) 183 { 184 logger.errorf("Could not download DLS: %s", e.message); 185 } 186 } 187 188 if (!upgradeSuccessful) 189 { 190 auto dub = new Dub(); 191 FetchOptions fetchOpts; 192 fetchOpts |= FetchOptions.forceBranchUpgrade; 193 const pack = dub.fetch("dls", Dependency(">=0.0.0"), 194 dub.defaultPlacementLocation, fetchOpts); 195 196 int i; 197 const additionalArgs = [[], ["--force"]]; 198 199 do 200 { 201 try 202 { 203 buildDls(pack.path.toString().normalized, additionalArgs[i]); 204 upgradeSuccessful = true; 205 } 206 catch (UpgradeFailedException e) 207 { 208 ++i; 209 } 210 } 211 while (i < additionalArgs.length && !upgradeSuccessful); 212 213 if (!upgradeSuccessful) 214 { 215 Util.sendMessage(Tr.app_buildError); 216 return; 217 } 218 } 219 220 try 221 { 222 linkDls(); 223 auto id = Util.sendMessageRequest(Tr.app_showChangelog, 224 [Tr.app_showChangelog_show], [latestVersion]); 225 send(ownerTid(), Util.ThreadMessageData(id, Tr.app_showChangelog, changelogUrl)); 226 } 227 catch (UpgradeFailedException e) 228 { 229 Util.sendMessage(Tr.app_linkError); 230 } 231 } 232 233 @property private string currentVersion() 234 { 235 import std.algorithm : find; 236 import std.json : parseJSON; 237 238 const desc = parseJSON(descriptionJson); 239 return desc["packages"].array.find!(p => p["name"] == desc["rootPackage"])[0]["version"].str; 240 }