12345678module cli910import json11import os12import src.history13import src.storage1415pub fn cmd_doctor(raw []string) int {16 args := parse('doctor', raw, Options{17 values: ['backup']18 flags: ['json']19 }) or { return usage_error(err.msg()) }20 store := open_store() or { return refuse(args.json(), err.msg()) }21 defer {22 store.close()23 }24 mut report := DoctorJson{25 problems: []string{}26 }2728 report.problems << store.integrity_check() or { return refuse(args.json(), err.msg()) }29 report.problems << check_objects(store) or { return refuse(args.json(), err.msg()) }30 report.problems << check_save_files(store) or { return refuse(args.json(), err.msg()) }3132 report.objects = int(store.object_count() or { 0 })33 report.saves = int(history.save_count(store) or { 0 })34 report.checkpoints = int(history.checkpoint_count(store) or { 0 })35 report.cached_files = int(store.cache_count() or { 0 })36 report.peek_folders_removed = sweep_peek_folders()37 store.drop_cache() or { return refuse(args.json(), err.msg()) }3839 if path := args.value_or_none('backup') {40 if os.exists(path) {41 return refuse(args.json(), '${path} already exists')42 }43 store.backup(path) or { return refuse(args.json(), err.msg()) }44 report.backup = path45 }4647 if args.json() {48 emit_json(json.encode(report))49 } else {50 if report.problems.len == 0 {51 ok('${store.root} is sound')52 } else {53 for problem in report.problems {54 warn(problem)55 }56 }57 note('${report.objects} objects · ${report.saves} saves · ${report.checkpoints} checkpoints')58 if report.backup != '' {59 ok('backed up to ${report.backup}')60 }61 }62 if report.problems.len > 0 {63 return 164 }65 return 066}67686970fn check_objects(store &storage.Store) ![]string {71 mut problems := []string{}72 mut stmt := store.db.prepare('SELECT hash FROM objects ORDER BY hash')!73 defer {74 stmt.finalize()75 }76 mut hashes := []string{}77 for stmt.step()! {78 hashes << stmt.text(0)79 }80 for hash in hashes {81 store.get_object(hash) or { problems << 'object ${hash}: ${err.msg()}' }82 }83 return problems84}8586878889fn check_save_files(store &storage.Store) ![]string {90 mut problems := []string{}91 mut stmt := store.db.prepare('SELECT DISTINCT f.save_id, f.hash FROM save_files f92 LEFT JOIN objects o ON o.hash = f.hash WHERE o.hash IS NULL')!93 defer {94 stmt.finalize()95 }96 for stmt.step()! {97 problems << 'save ${stmt.text(0)} needs missing object ${stmt.text(1)}'98 }99 return problems100}101102103104fn sweep_peek_folders() int {105 mut removed := 0106 for name in os.ls(os.temp_dir()) or { return 0 } {107 if !name.starts_with(peek_prefix) {108 continue109 }110 os.rmdir_all(os.join_path(os.temp_dir(), name)) or { continue }111 removed++112 }113 return removed114}115116struct DoctorJson {117mut:118 problems []string119 objects int120 saves int121 checkpoints int122 cached_files int123 peek_folders_removed int124 backup string125}