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 }