12345678module snapshot910import os11import src.storage1213pub struct RestoreReport {14pub:15 written int16 removed int17}1819202122232425pub fn restore_files(store &storage.Store, root string, ig Ignore, target []FileEntry, remove []string) !RestoreReport {26 mut wanted := map[string]bool{}27 for entry in target {28 wanted[entry.path] = true29 }30 mut removed := 031 for path in remove {32 full := os.join_path(root, path)33 if !os.is_file(full) || ig.matches(path, false) || path in wanted {34 continue35 }36 os.rm(full)!37 removed++38 prune_empty_dirs(root, os.dir(full))39 }40 41 42 43 for entry in target {44 full := os.join_path(root, entry.path)45 if os.is_dir(full) {46 os.rmdir(full) or {47 return error('${entry.path} is a folder of ignored files where a saved file belongs — move it, then run the command again')48 }49 }50 }51 mut written := 052 cache := store.load_cache()!53 for entry in target {54 if ig.matches(entry.path, false) && os.exists(os.join_path(root, entry.path)) {55 continue56 }57 58 59 if already_there(root, entry, cache) {60 continue61 }62 data := store.get_object(entry.hash)!63 write_file(os.join_path(root, entry.path), data, entry.executable)!64 written++65 }66 return RestoreReport{67 written: written68 removed: removed69 }70}7172737475fn already_there(root string, entry FileEntry, cache map[string]storage.CacheRow) bool {76 full := os.join_path(root, entry.path)77 st := lstat(full) or { return false }78 if st.kind != .file || st.size != entry.size || is_executable(st.mode) != entry.executable {79 return false80 }81 if row := cache[entry.path] {82 if row.size == st.size && row.mtime_ns == st.mtime_ns && row.ctime_ns == st.ctime_ns {83 return row.hash == entry.hash84 }85 }86 data := os.read_bytes(full) or { return false }87 return storage.hash_hex(data) == entry.hash88}8990fn write_file(path string, data []u8, executable bool) ! {91 os.mkdir_all(os.dir(path), os.MkdirParams{})!92 tmp := '${path}.koh-tmp-${os.getpid()}'93 os.write_file_array(tmp, data)!94 mode := if executable { 0o755 } else { 0o644 }95 os.chmod(tmp, mode)!96 os.rename(tmp, path)!97}9899100fn prune_empty_dirs(root string, dir string) {101 mut current := dir102 for current.len > root.len && current.starts_with(root) {103 if (os.ls(current) or { return }).len > 0 {104 return105 }106 os.rmdir(current) or { return }107 current = os.dir(current)108 }109}