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 {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}128129fn release_lock(path string) {130 os.rm(path) or {}131}132133134135136fn clear_stale_lock(path string) {137 info := os.stat(path) or { return }138 if time.now().unix() - info.mtime > 60 {139 os.rm(path) or {}140 }141}142143pub fn (mut f Folder) delete(key string) ! {144 path := f.path_of(key)145 if os.exists(path) {146 os.rm(path)!147 }148}149150fn (f &Folder) path_of(key string) string {151 return os.join_path(f.base, key)152}153154155156157pub fn tag_of(body []u8) string {158 return blake3.sum256(body).hex()159}