12345678910module remote1112import crypto.blake313import time14import os1516pub struct Folder {17pub:18 base string19}202122pub fn new_folder(base string) !Folder {23 os.mkdir_all(base, os.MkdirParams{})!24 return Folder{25 base: base26 }27}2829pub fn (mut f Folder) get(key string) !Object {30 path := f.path_of(key)31 if !os.is_file(path) {32 return Object{}33 }34 body := os.read_bytes(path)!35 return Object{36 found: true37 body: body38 tag: tag_of(body)39 }40}4142pub fn (mut f Folder) list(prefix string) ![]string {43 dir := os.join_path(f.base, prefix)44 if !os.is_dir(dir) {45 return []string{}46 }47 mut keys := []string{}48 mut walk := [prefix]49 for walk.len > 0 {50 relative := walk.pop()51 for name in os.ls(f.path_of(relative))! {52 child := if relative == '' { name } else { '${relative}/${name}' }53 if os.is_dir(f.path_of(child)) {54 walk << child55 } else if !is_scratch(name) {56 keys << child57 }58 }59 }60 keys.sort()61 return keys62}6364656667pub fn (mut f Folder) put_if_absent(key string, body []u8) !bool {68 path := f.path_of(key)69 dir := os.dir(path)70 os.mkdir_all(dir, os.MkdirParams{})!71 temporary := '${path}.tmp-${os.getpid()}'72 os.write_file_array(temporary, body)!73 defer {74 if os.exists(temporary) {75 os.rm(temporary) or {}76 }77 }78 os.link(temporary, path) or {79 if os.exists(path) {80 return false81 }82 return err83 }84 return true85}8687888990pub fn (mut f Folder) put_if_match(key string, body []u8, tag string) !bool {91 path := f.path_of(key)92 os.mkdir_all(os.dir(path), os.MkdirParams{})!93 guard := '${path}.lock'94 if !take_lock(guard)! {95 clear_stale_lock(guard)96 if !take_lock(guard)! {97 return false98 }99 }100 defer {101 release_lock(guard)102 }103 if tag != '' {104 current := f.get(key)!105 if !current.found || current.tag != tag {106 return false107 }108 } else if f.get(key)!.found {109 return false110 }111 temporary := '${path}.tmp-${os.getpid()}'112 os.write_file_array(temporary, body)!113 114 if os.is_dir(path) {115 os.rm(temporary) or {}116 return error('${key} is a directory on the remote')117 }118 os.rename(temporary, path)!119 return true120}121122123124fn take_lock(path string) !bool {125 fresh := '${path}.${os.getpid()}.${time.now().unix_nano()}'126 os.write_file(fresh, '${os.getpid()}')!127 defer {128 os.rm(fresh) or {}129 }130 os.link(fresh, path) or { return false }131 return true132}133134135136fn is_scratch(name string) bool {137 return name.contains('.tmp-') || name.contains('.lock')138}139140fn release_lock(path string) {141 os.rm(path) or {}142}143144145146147fn clear_stale_lock(path string) {148 info := os.stat(path) or { return }149 if time.now().unix() - info.mtime > 60 {150 os.rm(path) or {}151 }152}153154pub fn (mut f Folder) delete(key string) ! {155 path := f.path_of(key)156 if os.exists(path) {157 os.rm(path)!158 }159}160161fn (f &Folder) path_of(key string) string {162 return os.join_path(f.base, key)163}164165166167168pub fn tag_of(body []u8) string {169 return blake3.sum256(body).hex()170}