1 /**
2 	...
3 	
4 	Copyright: © 2012 Matthias Dondorff
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff
7 */
8 module dub.internal.utils;
9 
10 import dub.internal.vibecompat.core.file;
11 import dub.internal.vibecompat.core.log;
12 import dub.internal.vibecompat.data.json;
13 import dub.internal.vibecompat.inet.url;
14 import dub.version_;
15 
16 // todo: cleanup imports.
17 import std.algorithm : startsWith;
18 import std.array;
19 import std.conv;
20 import std.exception;
21 import std.file;
22 import std.process;
23 import std.string;
24 import std.typecons;
25 import std.zip;
26 version(DubUseCurl) import std.net.curl;
27 
28 
29 Path getTempDir()
30 {
31 	auto tmp = environment.get("TEMP");
32 	if( !tmp.length ) tmp = environment.get("TMP");
33 	if( !tmp.length ){
34 		version(Posix) tmp = "/tmp/";
35 		else tmp = "./";
36 	}
37 	return Path(tmp);
38 }
39 
40 bool isEmptyDir(Path p) {
41 	foreach(DirEntry e; dirEntries(p.toNativeString(), SpanMode.shallow))
42 		return false;
43 	return true;
44 }
45 
46 bool isWritableDir(Path p, bool create_if_missing = false)
47 {
48 	import std.random;
49 	auto fname = p ~ format("__dub_write_test_%08X", uniform(0, uint.max));
50 	if (create_if_missing && !exists(p.toNativeString())) mkdirRecurse(p.toNativeString());
51 	try openFile(fname, FileMode.CreateTrunc).close();
52 	catch return false;
53 	remove(fname.toNativeString());
54 	return true;
55 }
56 
57 Json jsonFromFile(Path file, bool silent_fail = false) {
58 	if( silent_fail && !existsFile(file) ) return Json.emptyObject;
59 	auto f = openFile(file.toNativeString(), FileMode.Read);
60 	scope(exit) f.close();
61 	auto text = stripUTF8Bom(cast(string)f.readAll());
62 	return parseJson(text);
63 }
64 
65 Json jsonFromZip(Path zip, string filename) {
66 	auto f = openFile(zip, FileMode.Read);
67 	ubyte[] b = new ubyte[cast(size_t)f.size];
68 	f.rawRead(b);
69 	f.close();
70 	auto archive = new ZipArchive(b);
71 	auto text = stripUTF8Bom(cast(string)archive.expand(archive.directory[filename]));
72 	return parseJson(text);
73 }
74 
75 void writeJsonFile(Path path, Json json)
76 {
77 	auto f = openFile(path, FileMode.CreateTrunc);
78 	scope(exit) f.close();
79 	f.writePrettyJsonString(json);
80 }
81 
82 bool isPathFromZip(string p) {
83 	enforce(p.length > 0);
84 	return p[$-1] == '/';
85 }
86 
87 bool existsDirectory(Path path) {
88 	if( !existsFile(path) ) return false;
89 	auto fi = getFileInfo(path);
90 	return fi.isDirectory;
91 }
92 
93 void runCommands(in string[] commands, string[string] env = null)
94 {
95 	foreach(cmd; commands){
96 		logDiagnostic("Running %s", cmd);
97 		Pid pid;
98 		if( env !is null ) pid = spawnShell(cmd, env);
99 		else pid = spawnShell(cmd);
100 		auto exitcode = pid.wait();
101 		enforce(exitcode == 0, "Command failed with exit code "~to!string(exitcode));
102 	}
103 }
104 
105 /**
106 	Downloads a file from the specified URL.
107 
108 	Any redirects will be followed until the actual file resource is reached or if the redirection
109 	limit of 10 is reached. Note that only HTTP(S) is currently supported.
110 */
111 void download(string url, string filename)
112 {
113 	version(DubUseCurl) {
114 		auto conn = HTTP();
115 		setupHTTPClient(conn);
116 		logDebug("Storing %s...", url);
117 		std.net.curl.download(url, filename, conn);
118 		enforce(conn.statusLine.code < 400,
119 			format("Failed to download %s: %s %s",
120 				url, conn.statusLine.code, conn.statusLine.reason));
121 	} else assert(false);
122 }
123 /// ditto
124 void download(Url url, Path filename)
125 {
126 	download(url.toString(), filename.toNativeString());
127 }
128 /// ditto
129 char[] download(string url)
130 {
131 	version(DubUseCurl) {
132 		auto conn = HTTP();
133 		setupHTTPClient(conn);
134 		logDebug("Getting %s...", url);
135 		auto ret = get(url, conn);
136 		enforce(conn.statusLine.code < 400,
137 			format("Failed to GET %s: %s %s",
138 				url, conn.statusLine.code, conn.statusLine.reason));
139 		return ret;
140 	} else assert(false);
141 }
142 /// ditto
143 char[] download(Url url)
144 {
145 	return download(url.toString());
146 }
147 
148 /// Returns the current DUB version in semantic version format
149 string getDUBVersion()
150 {
151 	import dub.version_;
152 	// convert version string to valid SemVer format
153 	auto verstr = dubVersion;
154 	if (verstr.startsWith("v")) verstr = verstr[1 .. $];
155 	auto parts = verstr.split("-");
156 	if (parts.length >= 3) {
157 		// detect GIT commit suffix
158 		if (parts[$-1].length == 8 && parts[$-1][1 .. $].isHexNumber() && parts[$-2].isNumber())
159 			verstr = parts[0 .. $-2].join("-") ~ "+" ~ parts[$-2 .. $].join("-");
160 	}
161 	return verstr;
162 }
163 
164 version(DubUseCurl) {
165 	void setupHTTPClient(ref HTTP conn)
166 	{
167 		static if( is(typeof(&conn.verifyPeer)) )
168 			conn.verifyPeer = false;
169 
170 		auto proxy = environment.get("http_proxy", null);
171 		if (proxy.length) conn.proxy = proxy;
172 
173 		conn.addRequestHeader("User-Agent", "dub/"~getDUBVersion()~" (std.net.curl; +https://github.com/rejectedsoftware/dub)");
174 	}
175 }
176 
177 private string stripUTF8Bom(string str)
178 {
179 	if( str.length >= 3 && str[0 .. 3] == [0xEF, 0xBB, 0xBF] )
180 		return str[3 ..$];
181 	return str;
182 }
183 
184 private bool isNumber(string str) {
185 	foreach (ch; str)
186 		switch (ch) {
187 			case '0': .. case '9': break;
188 			default: return false;
189 		}
190 	return true;
191 }
192 
193 private bool isHexNumber(string str) {
194 	foreach (ch; str)
195 		switch (ch) {
196 			case '0': .. case '9': break;
197 			case 'a': .. case 'f': break;
198 			case 'A': .. case 'F': break;
199 			default: return false;
200 		}
201 	return true;
202 }