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 os.mv(temporary, path, os.MvParams{})!114 return true115}116117118119fn take_lock(path string) !bool {120 fresh := '${path}.${os.getpid()}.${time.now().unix_nano()}'121 os.write_file(fresh, '${os.getpid()}')!122 defer {123 os.rm(fresh) or {}124 }125 os.link(fresh, path) or { return false }126 return true127}128129130131fn is_scratch(name string) bool {132 return name.contains('.tmp-') || name.contains('.lock')133}134135fn release_lock(path string) {136 os.rm(path) or {}137}138139140141142fn clear_stale_lock(path string) {143 info := os.stat(path) or { return }144 if time.now().unix() - info.mtime > 60 {145 os.rm(path) or {}146 }147}148149pub fn (mut f Folder) delete(key string) ! {150 path := f.path_of(key)151 if os.exists(path) {152 os.rm(path)!153 }154}155156fn (f &Folder) path_of(key string) string {157 return os.join_path(f.base, key)158}159160161162163pub fn tag_of(body []u8) string {164 return blake3.sum256(body).hex()165}