1 /** 2 * Copyright: Copyright (c) 2010-2011 Jacob Carlborg. All rights reserved. 3 * Authors: Jacob Carlborg 4 * Version: Initial created: Nov 8, 2010 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module dvm.commands.Fetch; 8 9 import std.algorithm : splitter, startsWith; 10 import std.exception : assumeUnique; 11 import std.format : format; 12 import std.range : empty, front; 13 import std.regex; 14 import std.stdio : writeln; 15 import std.string : split; 16 17 import tango.core.Exception; 18 import tango.io.device.File; 19 import tango.io.model.IConduit; 20 import Path = tango.io.Path; 21 import tango.net.InternetAddress; 22 import tango.net.device.Socket; 23 import tango.net.http.HttpGet; 24 import tango.net.http.HttpConst; 25 26 import dvm.commands.Command; 27 import dvm.util._; 28 import dvm.dvm._; 29 30 class Fetch : Command 31 { 32 this (string name, string summary = "") 33 { 34 super(name, summary); 35 } 36 37 this () 38 { 39 super("fetch", "Fetch a D compiler but don't install it."); 40 } 41 42 override void execute () 43 { 44 if (args.any && args.first == "dmc") 45 fetchDMC; 46 47 else 48 { 49 auto version_ = getDMDVersion(); 50 string filename = buildFilename(version_); 51 string url = buildUrl(version_, filename); 52 fetch(url, Path.join(".", filename).assumeUnique); 53 } 54 } 55 56 protected: 57 58 enum userAgent = buildUserAgent(); 59 enum dmcArchiveName = "dm852c.zip"; 60 61 void fetchDMC (string destinationPath=".") 62 { 63 auto url = "http://ftp.digitalmars.com/Digital_Mars_C++/Patch/" ~ dmcArchiveName; 64 fetch(url, Path.join(destinationPath, dmcArchiveName).assumeUnique); 65 } 66 67 void fetch (string source, string destination) 68 { 69 if (Path.exists(destination)) 70 return; 71 72 if (options.verbose) 73 { 74 writeln("Fetching:"); 75 writeln(options.indentation, "source: ", source); 76 writeln(options.indentation, "destination: ", destination, '\n'); 77 } 78 79 else 80 writeln("Fetching: ", source); 81 82 createPath(Path.parse(destination).folder); 83 writeFile(downloadFile(source), destination); 84 } 85 86 void[] downloadFile (string url) 87 { 88 static void print (A...)(A args) 89 { 90 import tango.io.Stdout; 91 92 static enum string fmt = "{}{}{}{}{}{}{}{}" ~ 93 "{}{}{}{}{}{}{}{}" ~ 94 "{}{}{}{}{}{}{}{}"; 95 96 static assert (A.length <= fmt.length / 2, "mambo.io.print :: too many arguments"); 97 98 Stdout.format(fmt[0 .. args.length * 2], args).flush; 99 } 100 101 auto page = new HttpGet(url); 102 page.setTimeout(30f); 103 page.getRequestHeaders.add(HttpHeader.UserAgent, userAgent); 104 auto buffer = page.open; 105 106 scope(exit) 107 page.close; 108 109 checkPageStatus(page, url); 110 111 // load in chunks in order to display progress 112 int contentLength = page.getResponseHeaders.getInt(HttpHeader.ContentLength); 113 114 enum width = 40; 115 int num = width; 116 117 version (Posix) 118 { 119 enum clearLine = "\033[1K"; // clear backwards 120 enum saveCursor = "\0337"; 121 enum restoreCursor = "\0338"; 122 } 123 124 else 125 { 126 enum clearLine = "\r"; 127 128 // Leaving these empty string causes a linker error: 129 // http://d.puremagic.com/issues/show_bug.cgi?id=4315 130 enum saveCursor = "\0"; 131 enum restoreCursor = "\0"; 132 } 133 134 print(saveCursor); 135 136 int bytesLeft = contentLength; 137 int chunkSize = bytesLeft / num; 138 139 while (bytesLeft > 0) 140 { 141 buffer.load(chunkSize > bytesLeft ? bytesLeft : chunkSize); 142 bytesLeft -= chunkSize; 143 int i = 0; 144 145 print(clearLine ~ restoreCursor ~ saveCursor); 146 print("["); 147 148 for ( ; i < (width - num); i++) 149 print("="); 150 151 print('>'); 152 153 for ( ; i < width; i++) 154 print(" "); 155 156 print("]"); 157 print(" ", (contentLength - bytesLeft) / 1024, "/", contentLength / 1024, " KB"); 158 159 num--; 160 } 161 162 writeln(restoreCursor); 163 writeln(); 164 165 return buffer.slice; 166 } 167 168 void writeFile (void[] data, string filename) 169 { 170 auto file = new File(filename, File.WriteCreate); 171 scope(exit) file.close(); 172 file.write(data); 173 } 174 175 string buildFilename (string ver="") 176 { 177 if (ver == "") 178 ver = getDMDVersion; 179 180 auto filename = format("dmd.%s.%s.zip", ver, platformForArchive); 181 auto url = buildUrl(ver, filename); 182 183 return urlExists(url) ? filename : format("dmd.%s.zip", ver); 184 } 185 186 string buildUrl (string version_, string filename) 187 { 188 auto url = dlangUrl(version_, filename); 189 190 if (urlExists(url)) 191 return url; 192 193 return digitalMarsUrl(filename); 194 } 195 196 string digitalMarsUrl (string filename) 197 { 198 return "http://ftp.digitalmars.com/" ~ filename; 199 } 200 201 string dlangUrl (string version_, string filename) 202 { 203 enum baseUrl = "http://downloads.dlang.org/"; 204 string releases = "releases"; 205 206 if (isPreRelease(version_)) 207 { 208 releases = "pre-releases"; 209 version_ = version_.split("-").front; 210 } 211 212 return format(baseUrl ~ "%s/%s.x/%s/%s", releases, version_.front, version_, filename); 213 } 214 215 void createPath (string path) 216 { 217 if (!Path.exists(path)) 218 Path.createPath(path); 219 } 220 221 void checkPageStatus (HttpGet page, string url) 222 { 223 if (page.getStatus == 404) 224 throw new IOException(format(`The resource with URL "%s" could not be found.`, url)); 225 226 else if (!page.isResponseOK) 227 throw new IOException(format(`An unexpected error occurred. The resource "%s" responded with the message "%s" and the status code %s.`, url, page.getResponse.getReason, page.getResponse.getStatus)); 228 } 229 230 bool urlExists (string url, float timeout = 5f) 231 { 232 scope page = new HttpGet(url); 233 page.setTimeout(timeout); 234 page.enableRedirect(); 235 page.open(); 236 237 return page.isResponseOK; 238 } 239 240 string getDMDVersion () 241 { 242 if (options.latest) 243 { 244 auto vers = getDVersion; 245 return args.first = getLatestDMDVersion(vers); 246 } 247 248 else 249 { 250 validateArguments(); 251 return args.first; 252 } 253 } 254 255 string getDVersion () 256 { 257 return args.empty() ? "2" : args.first; 258 } 259 260 string getLatestDMDVersion (string dVersion) 261 { 262 enum mirrors = [ 263 "http://downloads.dlang.org/releases/LATEST", 264 "http://ftp.digitalmars.com/LATEST" 265 ]; 266 267 static string fetchFromMirror(string url) 268 { 269 scope page = new HttpGet(url); 270 page.setTimeout(5f); 271 page.enableRedirect(); 272 page.open(); 273 274 return page.isResponseOK ? cast(string) page.read : null; 275 } 276 277 foreach (mirror ; mirrors) 278 { 279 if (auto content = fetchFromMirror(mirror)) 280 return content; 281 } 282 283 throw new DvmException("Failed to get the latest DMD version.", __FILE__, __LINE__); 284 } 285 286 void validateArguments (string errorMessage = null) 287 { 288 if (errorMessage.empty) 289 errorMessage = "Cannot fetch a compiler without specifying a version"; 290 291 if (args.empty) 292 throw new DvmException(errorMessage, __FILE__, __LINE__); 293 } 294 295 bool isPreRelease (string version_) 296 { 297 immutable parts = version_.split("-"); 298 299 if (parts.length != 2) 300 return false; 301 302 immutable suffix = parts[1]; 303 304 return suffix.front == 'b' || suffix.startsWith("rc"); 305 } 306 307 string platformForArchive() 308 { 309 auto platform = options.platform; 310 311 version (FreeBSD) 312 platform ~= options.is64bit ? "-64" : "-32"; 313 314 return platform; 315 } 316 } 317 318 string buildUserAgent () 319 { 320 import std.system; 321 import std.string; 322 323 version (X86) auto architecture = "i386"; 324 else version (X86_64) auto architecture = "x86_64"; 325 else static assert("Unhandled platform"); 326 327 return format("dvm/%s (%s-%s)", dvm.dvm.Version.Version, architecture, os); 328 } 329 330 template FetchImpl () 331 { 332 void execute () 333 { 334 auto filename = buildFilename; 335 auto url = buildUrl(filename); 336 fetch(url, join(".", filename)); 337 } 338 339 protected void fetch (string source, string destination) 340 { 341 writeFile(downloadFile(source), destination); 342 } 343 344 private void[] downloadFile (string url) 345 { 346 auto page = new HttpGet(url); 347 348 if (!page.isResponseOK()) 349 throw new IOException(format("%s", page.getResponse.getStatus)); 350 351 return page.read; 352 } 353 354 private void writeFile (void[] data, string filename) 355 { 356 auto file = new File(filename, File.WriteCreate); 357 file.write(data); 358 } 359 360 private string buildFilename () 361 { 362 return "dmd." ~ args.first ~ ".zip"; 363 } 364 365 private string buildUrl (string filename) 366 { 367 return "http://ftp.digitalmars.com/" ~ filename; 368 } 369 }