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 }