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, bool preReleaseBuilds) 105 { 106 import core.time : hours; 107 import dls.bootstrap : UpgradeFailedException, canDownloadDls, downloadDls, 108 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.util.i18n : Tr; 116 import dub.semver : compareVersions; 117 import std.algorithm : filter, stripLeft; 118 import std.concurrency : ownerTid, receiveOnly, register, send, thisTid; 119 import std.datetime : Clock, SysTime; 120 import std.format : format; 121 import std.json : JSON_TYPE; 122 123 auto validReleases = allReleases.filter!( 124 r => r["prerelease"].type == JSON_TYPE.FALSE || preReleaseBuilds); 125 126 if (validReleases.empty) 127 { 128 logger.warning("Unable to find any valid release"); 129 return; 130 } 131 132 immutable latestRelease = validReleases.front; 133 immutable latestVersion = latestRelease["tag_name"].str.stripLeft('v'); 134 immutable releaseTime = SysTime.fromISOExtString(latestRelease["published_at"].str); 135 136 if (latestVersion.length == 0 || compareVersions(currentVersion, 137 latestVersion) >= 0 || (Clock.currTime.toUTC() - releaseTime < 1.hours)) 138 { 139 return; 140 } 141 142 if (!autoUpdate) 143 { 144 auto id = Util.sendMessageRequest(Tr.app_upgradeDls, 145 [Tr.app_upgradeDls_upgrade], [latestVersion, currentVersion]); 146 immutable threadName = "updater"; 147 register(threadName, thisTid()); 148 send(ownerTid(), Util.ThreadMessageData(id, Tr.app_upgradeDls, threadName)); 149 150 immutable shouldUpgrade = receiveOnly!bool(); 151 152 if (!shouldUpgrade) 153 { 154 return; 155 } 156 } 157 158 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStart, 159 new TranslationParams(Tr.app_upgradeDls_upgrading)); 160 161 scope (exit) 162 { 163 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStop); 164 } 165 166 bool upgradeSuccessful; 167 168 if (canDownloadDls) 169 { 170 try 171 { 172 enum totalSizeCallback = (size_t size) { 173 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeTotalSize, 174 new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size)); 175 }; 176 enum chunkSizeCallback = (size_t size) { 177 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeCurrentSize, 178 new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size)); 179 }; 180 enum extractCallback = () { 181 dls.protocol.jsonrpc.send(Dls.UpgradeDls.didExtract, 182 new TranslationParams(Tr.app_upgradeDls_extracting)); 183 }; 184 185 downloadDls(totalSizeCallback, chunkSizeCallback, extractCallback); 186 upgradeSuccessful = true; 187 } 188 catch (Exception e) 189 { 190 logger.error("Could not download DLS: %s", e.msg); 191 Util.sendMessage(Tr.app_upgradeDls_downloadError); 192 } 193 } 194 195 try 196 { 197 linkDls(); 198 auto id = Util.sendMessageRequest(Tr.app_showChangelog, 199 [Tr.app_showChangelog_show], [latestVersion]); 200 send(ownerTid(), Util.ThreadMessageData(id, Tr.app_showChangelog, 201 format!changelogUrl(latestVersion))); 202 } 203 catch (UpgradeFailedException e) 204 { 205 logger.error("Could not symlink DLS: %s", e.msg); 206 Util.sendMessage(Tr.app_upgradeDls_linkError); 207 } 208 }