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 }