1 module served.commands.dcd_update;
2 
3 import served.extension;
4 import served.io.git_build;
5 import served.io.http_wrap;
6 import served.types;
7 
8 import workspaced.api;
9 import workspaced.coms;
10 
11 import rm.rf;
12 
13 import std.algorithm : endsWith;
14 import std.format : format;
15 import std.json : JSONValue;
16 import std.path : baseName, buildPath, chainPath, isAbsolute;
17 
18 import fs = std.file;
19 import io = std.stdio;
20 
21 version (ARM)
22 {
23 	version = DCDFromSource;
24 }
25 version (Win32)
26 {
27 }
28 else version (Win64)
29 {
30 }
31 else version (linux)
32 {
33 }
34 else version (OSX)
35 {
36 }
37 else version = DCDFromSource;
38 
39 version (DCDFromSource)
40 	enum isDCDFromSource = true;
41 else
42 	enum isDCDFromSource = false;
43 
44 __gshared string dcdUpdateReason = null;
45 __gshared bool dcdUpdating;
46 @protocolNotification("served/updateDCD")
47 void updateDCD()
48 {
49 	scope (exit)
50 	{
51 		dcdUpdating = false;
52 		dcdUpdateReason = null;
53 	}
54 
55 	if (dcdUpdateReason.length)
56 		rpc.notifyMethod("coded/logInstall", "Installing DCD: " ~ dcdUpdateReason);
57 	else
58 		rpc.notifyMethod("coded/logInstall", "Installing DCD");
59 
60 	string outputFolder = determineOutputFolder;
61 	if (fs.exists(outputFolder))
62 	{
63 		foreach (file; ["dcd", "DCD", "dcd-client", "dcd-server"])
64 		{
65 			auto path = buildPath(outputFolder, file);
66 			if (fs.exists(path))
67 			{
68 				if (fs.isFile(path))
69 					fs.remove(path);
70 				else
71 					rmdirRecurseForce(path);
72 			}
73 		}
74 	}
75 	if (!fs.exists(outputFolder))
76 		fs.mkdirRecurse(outputFolder);
77 	string ext = "";
78 	version (Windows)
79 		ext = ".exe";
80 	string finalDestinationClient;
81 	string finalDestinationServer;
82 
83 	bool success;
84 
85 	enum latestVersionBroken = false;
86 
87 	// the latest version that has downloads available
88 	enum bundledDCDVersion = "v0.13.6";
89 
90 	bool compileFromSource = false;
91 	version (DCDFromSource)
92 		compileFromSource = true;
93 	else
94 	{
95 		version (Windows)
96 		{
97 			// needed to check for 64 bit process compatibility on 32 bit binaries because of WoW64
98 			import core.sys.windows.windows : GetNativeSystemInfo, SYSTEM_INFO,
99 				PROCESSOR_ARCHITECTURE_INTEL;
100 
101 			SYSTEM_INFO sysInfo;
102 			GetNativeSystemInfo(&sysInfo);
103 			if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) // only 64 bit releases
104 				compileFromSource = true;
105 		}
106 
107 		if (!checkVersion(bundledDCDVersion, DCDComponent.latestKnownVersion))
108 			compileFromSource = true;
109 	}
110 
111 	static if (latestVersionBroken)
112 		compileFromSource = true;
113 
114 	string[] triedPaths;
115 
116 	if (compileFromSource)
117 	{
118 		success = compileDependency(outputFolder, "DCD",
119 				"https://github.com/dlang-community/DCD.git", [
120 					[
121 						firstConfig.git.userPath, "submodule", "update", "--init",
122 						"--recursive"
123 					], ["dub", "build", "--config=client"],
124 					["dub", "build", "--config=server"]
125 				]);
126 		finalDestinationClient = buildPath(outputFolder, "DCD", "dcd-client" ~ ext);
127 		if (!fs.exists(finalDestinationClient))
128 			finalDestinationClient = buildPath(outputFolder, "DCD", "bin", "dcd-client" ~ ext);
129 		finalDestinationServer = buildPath(outputFolder, "DCD", "dcd-server" ~ ext);
130 		if (!fs.exists(finalDestinationServer))
131 			finalDestinationServer = buildPath(outputFolder, "DCD", "bin", "dcd-server" ~ ext);
132 
133 		triedPaths = [
134 			"DCD/dcd-client" ~ ext, "DCD/dcd-server" ~ ext, "DCD/bin/dcd-client" ~ ext,
135 			"DCD/bin/dcd-server" ~ ext
136 		];
137 	}
138 	else
139 	{
140 		string url;
141 
142 		enum commonPrefix = "https://github.com/dlang-community/DCD/releases/download/"
143 			~ bundledDCDVersion ~ "/dcd-" ~ bundledDCDVersion;
144 
145 		version (Windows)
146 			url = commonPrefix ~ "-windows-x86_64.zip";
147 		else version (linux)
148 			url = commonPrefix ~ "-linux-x86_64.tar.gz";
149 		else version (OSX)
150 			url = commonPrefix ~ "-osx-x86_64.tar.gz";
151 		else
152 			static assert(false);
153 
154 		import std.process : pipeProcess, Redirect, Config, wait;
155 		import std.zip : ZipArchive;
156 		import core.thread : Fiber;
157 
158 		string destFile = buildPath(outputFolder, url.baseName);
159 
160 		try
161 		{
162 			rpc.notifyMethod("coded/logInstall", "Downloading from " ~ url ~ " to " ~ outputFolder);
163 
164 			if (fs.exists(destFile))
165 				rpc.notifyMethod("coded/logInstall",
166 						"Zip file already exists! Trying to install existing zip.");
167 			else
168 				downloadFile(url, "Downloading DCD...", destFile);
169 
170 			rpc.notifyMethod("coded/logInstall", "Extracting download...");
171 			if (url.endsWith(".tar.gz"))
172 			{
173 				rpc.notifyMethod("coded/logInstall", "> tar xvfz " ~ url.baseName);
174 				auto proc = pipeProcess(["tar", "xvfz", url.baseName],
175 						Redirect.stdout | Redirect.stderrToStdout, null, Config.none, outputFolder);
176 				foreach (line; proc.stdout.byLineCopy)
177 					rpc.notifyMethod("coded/logInstall", line);
178 				proc.pid.wait;
179 			}
180 			else if (url.endsWith(".zip"))
181 			{
182 				auto zip = new ZipArchive(fs.read(destFile));
183 				foreach (name, am; zip.directory)
184 				{
185 					if (name.isAbsolute)
186 						name = "." ~ name;
187 					zip.expand(am);
188 					fs.write(chainPath(outputFolder, name), am.expandedData);
189 				}
190 			}
191 			success = true;
192 
193 			finalDestinationClient = buildPath(outputFolder, "dcd-client" ~ ext);
194 			finalDestinationServer = buildPath(outputFolder, "dcd-server" ~ ext);
195 
196 			if (!fs.exists(finalDestinationClient))
197 				finalDestinationClient = buildPath(outputFolder, "bin", "dcd-client" ~ ext);
198 			if (!fs.exists(finalDestinationServer))
199 				finalDestinationServer = buildPath(outputFolder, "bin", "dcd-client" ~ ext);
200 
201 			triedPaths = [
202 				"dcd-client" ~ ext, "dcd-server" ~ ext, "bin/dcd-client" ~ ext,
203 				"bin/dcd-server" ~ ext
204 			];
205 		}
206 		catch (Exception e)
207 		{
208 			rpc.notifyMethod("coded/logInstall", "Failed installing: " ~ e.toString ~ "\n\n");
209 			rpc.notifyMethod("coded/logInstall",
210 					"If you have troube downloading via code-d, try manually downloading the DCD archive and placing it in "
211 					~ destFile ~ "!");
212 			success = false;
213 		}
214 	}
215 
216 	if (success && (!fs.exists(finalDestinationClient) || !fs.exists(finalDestinationServer)))
217 	{
218 		rpc.notifyMethod("coded/logInstall",
219 				"Successfully downloaded DCD, but could not find the executables.");
220 		rpc.notifyMethod("coded/logInstall",
221 				"Please open your user settings and insert the paths for dcd-client and dcd-server manually.");
222 		rpc.notifyMethod("coded/logInstall", "Download base location: " ~ outputFolder);
223 		rpc.notifyMethod("coded/logInstall", "");
224 		rpc.notifyMethod("coded/logInstall", format("Tried %(%s, %)", triedPaths));
225 
226 		finalDestinationClient = "dcd-client";
227 		finalDestinationServer = "dcd-server";
228 	}
229 
230 	if (success)
231 	{
232 		dcdUpdating = false;
233 
234 		backend.globalConfiguration.set("dcd", "clientPath", finalDestinationClient);
235 		backend.globalConfiguration.set("dcd", "serverPath", finalDestinationServer);
236 
237 		foreach (ref workspace; workspaces)
238 		{
239 			workspace.config.d.dcdClientPath = finalDestinationClient;
240 			workspace.config.d.dcdServerPath = finalDestinationServer;
241 		}
242 		rpc.notifyMethod("coded/updateSetting", UpdateSettingParams("dcdClientPath",
243 				JSONValue(finalDestinationClient), true));
244 		rpc.notifyMethod("coded/updateSetting", UpdateSettingParams("dcdServerPath",
245 				JSONValue(finalDestinationServer), true));
246 		rpc.notifyMethod("coded/logInstall", "Successfully installed DCD");
247 
248 		foreach (ref workspace; workspaces)
249 		{
250 			auto instance = backend.getInstance(workspace.folder.uri.uriToFile);
251 			if (instance is null)
252 				rpc.notifyMethod("coded/logInstall",
253 						"Failed to find workspace to start DCD for " ~ workspace.folder.uri);
254 			else
255 			{
256 				instance.config.set("dcd", "clientPath", finalDestinationClient);
257 				instance.config.set("dcd", "serverPath", finalDestinationServer);
258 
259 				lazyStartDCDServer(instance, workspace.folder.uri);
260 			}
261 		}
262 	}
263 }