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