| 1 | module util |
| 2 | |
| 3 | import os |
| 4 | import rand |
| 5 | |
| 6 | const retries = 10000 |
| 7 | |
| 8 | @[params] |
| 9 | pub struct TempFileOptions { |
| 10 | pub: |
| 11 | path string = os.temp_dir() |
| 12 | pattern string |
| 13 | } |
| 14 | |
| 15 | // temp_file returns a uniquely named, open, writable, `os.File` and it's path. |
| 16 | pub fn temp_file(tfo TempFileOptions) !(os.File, string) { |
| 17 | mut d := tfo.path |
| 18 | if d == '' { |
| 19 | d = os.temp_dir() |
| 20 | } |
| 21 | os.ensure_folder_is_writable(d) or { |
| 22 | return error(@FN + |
| 23 | ' could not create temporary file in "${d}". Please ensure write permissions.') |
| 24 | } |
| 25 | d = d.trim_right(os.path_separator) |
| 26 | prefix, suffix := prefix_and_suffix(tfo.pattern) or { return error(@FN + ' ${err.msg()}') } |
| 27 | for retry := 0; retry < retries; retry++ { |
| 28 | path := os.join_path(d, prefix + random_number() + suffix) |
| 29 | mut mode := 'rw+' |
| 30 | $if windows { |
| 31 | mode = 'w+' |
| 32 | } |
| 33 | mut file := os.open_file(path, mode, 0o600) or { continue } |
| 34 | if os.exists(path) && os.is_file(path) { |
| 35 | return file, path |
| 36 | } |
| 37 | } |
| 38 | return error(@FN + |
| 39 | ' could not create temporary file in "${d}". Retry limit (${retries}) exhausted. Please ensure write permissions.') |
| 40 | } |
| 41 | |
| 42 | @[params] |
| 43 | pub struct TempDirOptions { |
| 44 | pub: |
| 45 | path string = os.temp_dir() |
| 46 | pattern string |
| 47 | } |
| 48 | |
| 49 | fn error_for_temporary_folder(fn_name string, d string) !string { |
| 50 | return error('${fn_name} could not create temporary directory "${d}". Please ensure you have write permissions for it.') |
| 51 | } |
| 52 | |
| 53 | // temp_dir returns a uniquely named, writable, directory path. |
| 54 | pub fn temp_dir(tdo TempDirOptions) !string { |
| 55 | mut d := tdo.path |
| 56 | if d == '' { |
| 57 | d = os.temp_dir() |
| 58 | } |
| 59 | os.ensure_folder_is_writable(d) or { return error_for_temporary_folder(@FN, d) } |
| 60 | d = d.trim_right(os.path_separator) |
| 61 | prefix, suffix := prefix_and_suffix(tdo.pattern) or { return error(@FN + ' ${err.msg()}') } |
| 62 | for retry := 0; retry < retries; retry++ { |
| 63 | path := os.join_path(d, prefix + random_number() + suffix) |
| 64 | os.mkdir_all(path) or { continue } |
| 65 | if os.is_dir(path) && os.exists(path) { |
| 66 | os.ensure_folder_is_writable(path) or { return error_for_temporary_folder(@FN, d) } |
| 67 | return path |
| 68 | } |
| 69 | } |
| 70 | return error('${@FN} could not create temporary directory "${d}". Retry limit (${retries}) exhausted.') |
| 71 | } |
| 72 | |
| 73 | // * Utility functions |
| 74 | fn random_number() string { |
| 75 | s := (1_000_000_000 + (u32(os.getpid()) + rand.u32n(1_000_000_000) or { 0 })).str() |
| 76 | return s.substr(1, s.len) |
| 77 | } |
| 78 | |
| 79 | fn prefix_and_suffix(pattern string) !(string, string) { |
| 80 | if pattern.contains(os.path_separator) { |
| 81 | return error('pattern cannot contain path separators (${os.path_separator}).') |
| 82 | } |
| 83 | prefix, suffix := pattern.rsplit_once('*') or { pattern, '' } |
| 84 | return prefix, suffix |
| 85 | } |
| 86 | |