1 module dls.updater; 2 3 import dls.bootstrap : repoBase; 4 import dls.protocol.interfaces : InitializeParams; 5 import dub.dub : Dub; 6 import std.file : FileException; 7 import std.format : format; 8 import std.json : parseJSON; 9 10 private enum descriptionJson = import("description.json"); 11 private immutable changelogUrl = format!"https://github.com/%s/dls/blob/master/CHANGELOG.md"( 12 repoBase); 13 14 void cleanup() 15 { 16 import dls.bootstrap : dubBinDir; 17 import dub.package_ : Package; 18 import std.file : SpanMode, dirEntries, remove, rmdirRecurse; 19 import std.path : baseName; 20 import std.regex : matchFirst; 21 22 auto dub = new Dub(); 23 Package[] packagesToRemove; 24 25 foreach (dlsPackage; dub.packageManager.getPackageIterator("dls")) 26 { 27 if (dlsPackage.version_.toString() < currentVersion) 28 { 29 packagesToRemove ~= dlsPackage; 30 } 31 } 32 33 foreach (dlsPackage; packagesToRemove) 34 { 35 try 36 { 37 dub.remove(dlsPackage); 38 } 39 catch (FileException e) 40 { 41 } 42 } 43 44 bool[string] entriesToRemove; 45 46 foreach (entry; dirEntries(dubBinDir, SpanMode.shallow)) 47 { 48 const match = entry.name.baseName.matchFirst(`dls-v([\d.]+)`); 49 50 if (match) 51 { 52 if (match[1] < currentVersion) 53 { 54 foreach (subEntry; dirEntries(entry.name, SpanMode.shallow)) 55 { 56 if (subEntry.baseName !in entriesToRemove) 57 { 58 entriesToRemove[subEntry.name.baseName] = true; 59 } 60 } 61 62 try 63 { 64 rmdirRecurse(entry.name); 65 } 66 catch (FileException e) 67 { 68 } 69 } 70 else 71 { 72 foreach (subEntry; dirEntries(entry.name, SpanMode.shallow)) 73 { 74 entriesToRemove[subEntry.name.baseName] = false; 75 } 76 } 77 } 78 } 79 80 foreach (entry; dirEntries(dubBinDir, SpanMode.shallow)) 81 { 82 if (entry.name.baseName in entriesToRemove && entriesToRemove[entry.name.baseName]) 83 { 84 try 85 { 86 remove(entry.name); 87 } 88 catch (FileException e) 89 { 90 } 91 } 92 } 93 } 94 95 @trusted void update() 96 { 97 import core.time : hours; 98 import dls.bootstrap : UpgradeFailedException, apiEndpoint, buildDls, 99 canDownloadDls, downloadDls, linkDls; 100 static import dls.protocol.jsonrpc; 101 import dls.protocol.messages.window : Util; 102 import dls.util.logger : logger; 103 import dls.util.path : normalized; 104 import dub.dependency : Dependency; 105 import dub.dub : FetchOptions; 106 import std.concurrency : ownerTid, receiveOnly, register, send, thisTid; 107 import std.datetime : Clock, SysTime; 108 import std.net.curl : get; 109 110 const latestRelease = parseJSON(get(format!apiEndpoint("releases/latest"))); 111 const latestVersion = latestRelease["tag_name"].str; 112 const releaseDate = SysTime.fromISOExtString(latestRelease["published_at"].str); 113 114 if (latestVersion.length == 0 || ('v' ~ currentVersion) >= latestVersion 115 || (Clock.currTime - releaseDate < 1.hours)) 116 { 117 return; 118 } 119 120 auto id = Util.sendMessageRequest(Util.ShowMessageRequestType.upgradeDls, 121 [latestVersion, ('v' ~ currentVersion)]); 122 const threadName = "updater"; 123 register(threadName, thisTid()); 124 send(ownerTid(), Util.ThreadMessageData(id, 125 Util.ShowMessageRequestType.upgradeDls, threadName)); 126 127 const shouldUpgrade = receiveOnly!bool(); 128 129 if (!shouldUpgrade) 130 { 131 return; 132 } 133 134 dls.protocol.jsonrpc.send("$/dls.upgradeDls.start"); 135 136 scope (exit) 137 { 138 dls.protocol.jsonrpc.send("$/dls.upgradeDls.stop"); 139 } 140 141 bool success; 142 143 if (canDownloadDls) 144 { 145 try 146 { 147 enum totalSizeCallback = (size_t size) { 148 dls.protocol.jsonrpc.send("$/dls.upgradeDls.totalSize", size); 149 }; 150 enum chunkSizeCallback = (size_t size) { 151 dls.protocol.jsonrpc.send("$/dls.upgradeDls.currentSize", size); 152 }; 153 enum extractCallback = () { 154 dls.protocol.jsonrpc.send("$/dls.upgradeDls.extract"); 155 }; 156 157 downloadDls(totalSizeCallback, chunkSizeCallback, extractCallback); 158 success = true; 159 } 160 catch (Exception e) 161 { 162 logger.warningf("Could not download DLS: %s", e.message); 163 } 164 } 165 166 if (!success) 167 { 168 auto dub = new Dub(); 169 FetchOptions fetchOpts; 170 fetchOpts |= FetchOptions.forceBranchUpgrade; 171 const pack = dub.fetch("dls", Dependency(">=0.0.0"), 172 dub.defaultPlacementLocation, fetchOpts); 173 174 int i; 175 const additionalArgs = [[], ["--force"]]; 176 177 do 178 { 179 try 180 { 181 buildDls(pack.path.toString().normalized, additionalArgs[i]); 182 success = true; 183 } 184 catch (UpgradeFailedException e) 185 { 186 ++i; 187 } 188 } 189 while (i < additionalArgs.length && !success); 190 191 if (!success) 192 { 193 Util.sendMessage(Util.ShowMessageType.dlsBuildError); 194 return; 195 } 196 } 197 198 try 199 { 200 linkDls(); 201 id = Util.sendMessageRequest(Util.ShowMessageRequestType.showChangelog, [latestVersion]); 202 send(ownerTid(), Util.ThreadMessageData(id, 203 Util.ShowMessageRequestType.showChangelog, changelogUrl)); 204 } 205 catch (FileException e) 206 { 207 Util.sendMessage(Util.ShowMessageType.dlsLinkError); 208 } 209 } 210 211 @property private string currentVersion() 212 { 213 import std.algorithm : find; 214 215 const desc = parseJSON(descriptionJson); 216 return desc["packages"].array.find!(p => p["name"] == desc["rootPackage"])[0]["version"].str; 217 }