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 	} else assert(false);
119 }
120 /// ditto
121 void download(Url url, Path filename)
122 {
123 	download(url.toString(), filename.toNativeString());
124 }
125 /// ditto
126 char[] download(string url)
127 {
128 	version(DubUseCurl) {
129 		auto conn = HTTP();
130 		setupHTTPClient(conn);
131 		logDebug("Getting %s...", url);
132 		return get(url, conn);
133 	} else assert(false);
134 }
135 /// ditto
136 char[] download(Url url)
137 {
138 	return download(url.toString());
139 }
140 
141 /// Returns the current DUB version in semantic version format
142 string getDUBVersion()
143 {
144 	import dub.version_;
145 	// convert version string to valid SemVer format
146 	auto verstr = dubVersion;
147 	if (verstr.startsWith("v")) verstr = verstr[1 .. $];
148 	auto parts = verstr.split("-");
149 	if (parts.length >= 3) {
150 		// detect GIT commit suffix
151 		if (parts[$-1].length == 8 && parts[$-1][1 .. $].isHexNumber() && parts[$-2].isNumber())
152 			verstr = parts[0 .. $-2].join("-") ~ "+" ~ parts[$-2 .. $].join("-");
153 	}
154 	return verstr;
155 }
156 
157 version(DubUseCurl) {
158 	void setupHTTPClient(ref HTTP conn)
159 	{
160 		static if( is(typeof(&conn.verifyPeer)) )
161 			conn.verifyPeer = false;
162 
163 		auto proxy = environment.get("http_proxy", null);
164 		if (proxy.length) conn.proxy = proxy;
165 
166 		conn.addRequestHeader("User-Agent", "dub/"~getDUBVersion()~" (std.net.curl; +https://github.com/rejectedsoftware/dub)");
167 	}
168 }
169 
170 private string stripUTF8Bom(string str)
171 {
172 	if( str.length >= 3 && str[0 .. 3] == [0xEF, 0xBB, 0xBF] )
173 		return str[3 ..$];
174 	return str;
175 }
176 
177 private bool isNumber(string str) {
178 	foreach (ch; str)
179 		switch (ch) {
180 			case '0': .. case '9': break;
181 			default: return false;
182 		}
183 	return true;
184 }
185 
186 private bool isHexNumber(string str) {
187 	foreach (ch; str)
188 		switch (ch) {
189 			case '0': .. case '9': break;
190 			case 'a': .. case 'f': break;
191 			case 'A': .. case 'F': break;
192 			default: return false;
193 		}
194 	return true;
195 }