1 /// Utility functions to work with paths 2 module thepath.utils; 3 4 private import std.exception: enforce, errnoEnforce; 5 private static import std.path; 6 7 private import thepath.exception: PathException; 8 private import thepath.path: Path; 9 10 11 // Max attempts to create temp directory 12 private immutable ushort MAX_TMP_ATTEMPTS = 1000; 13 14 15 /** Create temporary directory 16 * Note, that caller is responsible to remove created directory. 17 * The temp directory will be created inside specified path. 18 * 19 * Params: 20 * path = path to already existing directory to create 21 * temp directory inside. Default: std.file.tempDir 22 * prefix = prefix to be used in name of temp directory. Default: "tmp" 23 * Returns: string, representing path to created temporary directory 24 * Throws: 25 * ErrnoException (Posix) incase if mkdtemp was not able to create tempdir 26 * PathException (Windows) in case of failure of creation of temp dir 27 **/ 28 @safe string createTempDirectory(in string prefix="tmp") { 29 import std.file : tempDir; 30 return createTempDirectory(tempDir, prefix); 31 } 32 33 /// ditto 34 @safe string createTempDirectory(in string path, in string prefix) { 35 version(Posix) { 36 import std.string : fromStringz; 37 import std.conv: to; 38 import core.sys.posix.stdlib : mkdtemp; 39 40 // Make trusted version of mkdtemp 41 char* t_mkdtemp(scope char* tmpl) @trusted nothrow => mkdtemp(tmpl); 42 43 // Prepare template for mkdtemp function. 44 // It have to be mutable array of chars ended with zero to be compatibale 45 // with mkdtemp function. 46 scope char[] tempname_str = std.path.buildNormalizedPath( 47 std.path.expandTilde(path), 48 prefix ~ "-XXXXXX").dup ~ '\0'; 49 50 // mkdtemp will modify tempname_str directly. 51 // and res will be pointer to tempname_str in case of success. 52 // in case of failure, res will be set to null. 53 char* res = t_mkdtemp(&tempname_str[0]); 54 errnoEnforce(res !is null, "Cannot create temporary directory"); 55 56 // Convert tempname to string. 57 // Just remove trailing \0 symbol, and duplicate. 58 return tempname_str[0 .. $-1].idup; 59 } else version (Windows) { 60 import std.ascii: letters; 61 import std.random: uniform; 62 import std.file; 63 import core.sys.windows.winerror; 64 import std.windows.syserror; 65 import std.format: format; 66 67 // Generate new random temp path to test using provided path and prefix 68 // as template. 69 string generate_temp_dir() { 70 string suffix = "-"; 71 for(ubyte i; i<6; i++) suffix ~= letters[uniform(0, $)]; 72 return std.path.buildNormalizedPath( 73 std.path.expandTilde(path), prefix ~ suffix); 74 } 75 76 // Make trusted funcs to get windows error code and msg 77 auto get_err_code(WindowsException e) @trusted { 78 return e.code; 79 } 80 string get_err_str(WindowsException e) @trusted { 81 return sysErrorString(e.code); 82 } 83 84 // Try to create new temp directory 85 for(ushort i=0; i<MAX_TMP_ATTEMPTS; i++) { 86 string temp_dir = generate_temp_dir(); 87 try { 88 std.file.mkdir(temp_dir); 89 } catch (WindowsException e) { 90 if (get_err_code(e) == ERROR_ALREADY_EXISTS) 91 continue; 92 throw new PathException( 93 "Cannot create temporary directory: %s".format( 94 get_err_str(e))); 95 } 96 return temp_dir; 97 } 98 throw new PathException( 99 "Cannot create temporary directory: No usable name found!"); 100 } else assert(0, "Not supported platform!"); 101 } 102 103 version(Posix) @system unittest { 104 import dshould; 105 import std.exception; 106 createTempDirectory("/some/unexisting/path").should.throwA!ErrnoException; 107 } 108 109 version(Windows) @system unittest { 110 import dshould; 111 import std.exception; 112 createTempDirectory("/some/unexisting/path").should.throwA!PathException; 113 } 114 115 116 /** Create temporary directory 117 * Note, that caller is responsible to remove created directory. 118 * The temp directory will be created inside specified path. 119 * 120 * Params: 121 * path = path to already existing directory to create 122 * temp directory inside. Default: std.file.tempDir 123 * prefix = prefix to be used in name of temp directory. Default: "tmp" 124 * Returns: Path to created temp directory 125 * Throws: PathException in case of error 126 **/ 127 @safe Path createTempPath(in string prefix="tmp") { 128 return Path(createTempDirectory(prefix)); 129 } 130 131 /// ditto 132 @safe Path createTempPath(in string path, in string prefix) { 133 return Path(createTempDirectory(path, prefix)); 134 } 135 136 /// ditto 137 @safe Path createTempPath(in Path path, in string prefix) { 138 return createTempPath(path.toString, prefix); 139 }