//! `koh doctor` — integrity check and cleanup. //! //! One source of truth means there is nothing to rebuild; what is left to check //! is that the file is sound and that every object a save names is really //! there. Doctor also tidies the things that are safe to lose: the file cache //! and old peek folders. module cli import json2 import os import time import src.history pub fn cmd_doctor(raw []string) int { args := parse('doctor', raw, Options{ values: ['backup'] flags: ['json'] }) or { return usage_error(err.msg()) } store := open_store() or { return refuse(args.json(), err.msg()) } defer { store.close() } mut report := DoctorJson{ problems: []string{} } report.problems << store.integrity_check() or { return refuse(args.json(), err.msg()) } report.problems << store.check_objects() or { return refuse(args.json(), err.msg()) } report.problems << store.check_save_files() or { return refuse(args.json(), err.msg()) } report.objects = int(store.object_count() or { 0 }) report.saves = int(history.save_count(store) or { 0 }) report.checkpoints = int(history.checkpoint_count(store) or { 0 }) report.cached_files = int(store.cache_count() or { 0 }) report.peek_folders_removed = sweep_peek_folders() store.drop_cache() or { return refuse(args.json(), err.msg()) } if path := args.value_or_none('backup') { if os.exists(path) { return refuse(args.json(), '${path} already exists') } store.backup(path) or { return refuse(args.json(), err.msg()) } report.backup = path } if args.json() { emit_json(json2.encode(report)) } else { if report.problems.len == 0 { ok('${store.root} is sound') } else { for problem in report.problems { warn(problem) } } note('${report.objects} objects · ${report.saves} saves · ${report.checkpoints} checkpoints') if report.backup != '' { ok('backed up to ${report.backup}') } } if report.problems.len > 0 { return 1 } return 0 } // peek_age is how old a peek folder must be before doctor clears it. const peek_age = 86400 // sweep_peek_folders clears the scratch trees `koh peek` leaves in the system // temp directory, once they are a day old. fn sweep_peek_folders() int { mut removed := 0 for name in os.ls(os.temp_dir()) or { return 0 } { path := os.join_path(os.temp_dir(), name) if !name.starts_with(peek_prefix) || !peek_is_old(os.file_last_mod_unix(path), time.now().unix()) { continue } os.rmdir_all(path) or { continue } removed++ } return removed } // peek_is_old is true once a peek is more than a day old. A younger one may // still be open in somebody's editor. fn peek_is_old(modified i64, now i64) bool { return modified < now - peek_age } struct DoctorJson { mut: problems []string objects int saves int checkpoints int cached_files int peek_folders_removed int backup string }