1 module dubregistry.internal.utils;
2 
3 import vibe.core.core;
4 import vibe.core.concurrency;
5 import vibe.core.file;
6 import vibe.core.log;
7 import vibe.core.task;
8 import vibe.data.bson;
9 import vibe.inet.url;
10 import vibe.inet.path;
11 
12 import core.time;
13 import std.algorithm : any, among;
14 import std.file : tempDir;
15 import std.format;
16 import std.path;
17 import std.process;
18 import std.typecons;
19 
20 URL black(URL url)
21 @safe {
22 	if (url.username.length > 0) url.username = "***";
23 	if (url.password.length > 0) url.password = "***";
24 	return url;
25 }
26 
27 string black(string url)
28 @safe {
29 	return black(URL(url)).toString();
30 }
31 
32 /**
33  * Params:
34  *   file = the file to convert
35  * Returns: the PNG stream of the icon or empty on failure
36  * Throws: Exception if input is invalid format, invalid dimension or times out
37  */
38 bdata_t generateLogo(NativePath file) @safe
39 {
40 	static assert (isWeaklyIsolated!(typeof(&generateLogoUnsafe)));
41 	static assert (isWeaklyIsolated!NativePath);
42 	static assert (isWeaklyIsolated!LogoGenerateResponse);
43 	auto res = (() @trusted => async(&generateLogoUnsafe, file).getResult())();
44 	if (res.error.length)
45 		throw new Exception("Failed to generate logo: " ~ res.error);
46 	return res.data;
47 }
48 
49 private struct LogoGenerateResponse
50 {
51 	bdata_t data;
52 	string error;
53 }
54 
55 // need to return * here because stack returned values get destroyed for some reason...
56 private LogoGenerateResponse* generateLogoUnsafe(NativePath file) @safe nothrow
57 {
58 	import std.array : appender;
59 
60 	// TODO: replace imagemagick command line tools with something like imageformats on dub
61 
62 	// use [0] to only get first frame in gifs, has no effect on static images.
63 	string firstFrame = file.toNativeString ~ "[0]";
64 
65 	try {
66 		auto sizeInfo = execute(["identify", "-format", "%w %h %m", firstFrame]);
67 		if (sizeInfo.status != 0)
68 			return new LogoGenerateResponse(null, "Malformed image.");
69 		int width, height;
70 		string format;
71 		uint filled = formattedRead(sizeInfo.output, "%d %d %s", width, height, format);
72 		if (filled < 3)
73 			return new LogoGenerateResponse(null, "Malformed metadata.");
74 		if (!format.among("PNG", "JPEG", "GIF", "BMP"))
75 			return new LogoGenerateResponse(null, "Invalid image format, only supporting png, jpeg, gif and bmp.");
76 		if (width < 2 || height < 2 || width > 2048 || height > 2048)
77 			return new LogoGenerateResponse(null, "Invalid image dimenstions, must be between 2x2 and 2048x2048.");
78 
79 		auto png = pipeProcess(["timeout", "3", "convert", firstFrame, "-resize", "512x512>", "png:-"]);
80 
81 		auto a = appender!(immutable(ubyte)[])();
82 
83 		(() @trusted {
84 			foreach (chunk; png.stdout.byChunk(4096))
85 				a.put(chunk);
86 		})();
87 
88 		auto result = png.pid.wait;
89 		if (result != 0)
90 		{
91 			if (result == 126)
92 				return new LogoGenerateResponse(null, "Conversion timed out");
93 			(() @trusted {
94 				foreach (error; png.stderr.byLine)
95 					logDiagnostic("convert error: %s", error);
96 			})();
97 			return new LogoGenerateResponse(null, "An unexpected error occured");
98 		}
99 
100 		return new LogoGenerateResponse(a.data);
101 	} catch (Exception e) {
102 		return new LogoGenerateResponse(null, "Failed to invoke the logo conversion process.");
103 	}
104 }
105 
106 
107 /** Performs deduplication and compact re-allocation of individual strings.
108 
109 	Strings are allocated out of 64KB blocks of memory.
110 */
111 struct PackedStringAllocator {
112 	private {
113 		char[] memory;
114 		string[string] map;
115 	}
116 
117 	@disable this(this);
118 
119 	string alloc(in char[] chars)
120 	@safe {
121 		import std.algorithm.comparison : max;
122 
123 		if (auto pr = chars in map)
124 			return *pr;
125 
126 		if (memory.length < chars.length) memory = new char[](max(chars.length, 64*1024));
127 		auto str = memory[0 .. chars.length];
128 		memory = memory[chars.length .. $];
129 		str[] = chars[];
130 		auto istr = () @trusted { return cast(string)str; } ();
131 		map[istr] = istr;
132 		return istr;
133 	}
134 }