1 module served.http;
2 
3 import core.thread : Fiber;
4 
5 import fs = std.file;
6 import std.conv;
7 import std.datetime.stopwatch;
8 import std.exception;
9 import std.format;
10 import std.json;
11 import std.math;
12 import std.path;
13 import std.random;
14 import std.stdio;
15 import std.string;
16 import std.traits;
17 
18 version (Windows) pragma(lib, "wininet");
19 else
20 {
21 	// only import in this scope
22 	import requests;
23 }
24 
25 struct InteractiveDownload
26 {
27 	string url;
28 	string title;
29 	string output;
30 }
31 
32 void downloadFileManual(string url, string title, string into, void delegate(string) onLog)
33 {
34 	File file = File(into, "wb");
35 
36 	StopWatch sw;
37 	sw.start();
38 
39 	version (Windows)
40 	{
41 		import core.sys.windows.windows : DWORD;
42 		import core.sys.windows.wininet : INTERNET_FLAG_NO_UI,
43 			INTERNET_OPEN_TYPE_PRECONFIG, InternetCloseHandle, InternetOpenA,
44 			InternetOpenUrlA, InternetReadFile, IRF_NO_WAIT, INTERNET_BUFFERSA,
45 			HttpQueryInfoA, HTTP_QUERY_CONTENT_LENGTH;
46 
47 		auto handle = enforce(InternetOpenA("serve-d_release-downloader/Pure-D/serve-d",
48 				INTERNET_OPEN_TYPE_PRECONFIG, null, null, 0), "Failed to open internet handle");
49 		scope (exit)
50 			InternetCloseHandle(handle);
51 
52 		auto obj = enforce(InternetOpenUrlA(handle, cast(const(char)*) url.toStringz,
53 				null, 0, INTERNET_FLAG_NO_UI, 0), "Opening URL failed");
54 		scope (exit)
55 			InternetCloseHandle(obj);
56 
57 		scope ubyte[] buffer = new ubyte[50 * 1024];
58 
59 		long maxLen;
60 		DWORD contentLengthLength = 16;
61 		DWORD index = 0;
62 		if (HttpQueryInfoA(obj, HTTP_QUERY_CONTENT_LENGTH, buffer.ptr, &contentLengthLength, &index))
63 			maxLen = (cast(char[]) buffer[0 .. contentLengthLength]).strip.to!long;
64 
65 		long received;
66 		while (true)
67 		{
68 			DWORD read;
69 			if (!InternetReadFile(obj, buffer.ptr, cast(DWORD) buffer.length, &read))
70 				throw new Exception("Failed to read from internet file");
71 
72 			if (read == 0)
73 				break;
74 
75 			file.rawWrite(buffer[0 .. read]);
76 			received += read;
77 
78 			if (sw.peek >= 1.seconds)
79 			{
80 				sw.reset();
81 				if (maxLen > 0)
82 					onLog(format!"%s %s / %s (%.1f %%)"(title, humanSize(received),
83 							humanSize(maxLen), received / cast(float) maxLen * 100));
84 				else
85 					onLog(format!"%s %s / ???"(title, humanSize(received)));
86 				Fiber.yield();
87 			}
88 		}
89 	}
90 	else
91 	{
92 		auto req = Request();
93 		req.useStreaming = true;
94 
95 		auto res = req.get(url);
96 
97 		foreach (part; res.receiveAsRange())
98 		{
99 			file.rawWrite(part);
100 
101 			if (sw.peek >= 1.seconds)
102 			{
103 				sw.reset();
104 				onLog(format!"%s %s / %s (%.1f %%)"(title, humanSize(res.contentReceived),
105 						humanSize(res.contentLength), res.contentReceived / cast(float) res.contentLength * 100));
106 				Fiber.yield();
107 			}
108 		}
109 	}
110 }
111 
112 string humanSize(T)(T bytes) if (isIntegral!T)
113 {
114 	static immutable string prefixes = "kMGTPE";
115 
116 	if (bytes < 1024)
117 		return text(bytes, " B");
118 	int exp = cast(int)(log2(bytes) / 8); // 8 = log2(1024)
119 	return format!"%.1f %siB"(bytes / cast(float) pow(1024, exp), prefixes[exp - 1]);
120 }