cmd_doctor.v

3.4 KB · raw

1//! `koh doctor` — integrity check and cleanup.2//!3//! One source of truth means there is nothing to rebuild; what is left to check4//! is that the file is sound and that every object a save names is really5//! there. Doctor also tidies the things that are safe to lose: the file cache6//! and old peek folders.78module cli910import json211import os12import time13import src.history14import src.snapshot15import src.storage1617pub fn cmd_doctor(raw []string) int {18	args := parse('doctor', raw, Options{19		values: ['backup']20		flags:  ['json']21	}) or { return usage_error(err.msg()) }22	store := open_store() or { return refuse(args.json(), err.msg()) }23	defer {24		store.close()25	}26	mut report := DoctorJson{27		problems: []string{}28	}2930	report.problems << store.integrity_check() or { return refuse(args.json(), err.msg()) }31	report.problems << store.check_objects() or { return refuse(args.json(), err.msg()) }32	report.problems << store.check_save_files() or { return refuse(args.json(), err.msg()) }33	report.problems << unsafe_save_paths(store) or { return refuse(args.json(), err.msg()) }3435	report.objects = int(store.object_count() or { 0 })36	report.saves = int(history.save_count(store) or { 0 })37	report.checkpoints = int(history.checkpoint_count(store) or { 0 })38	report.cached_files = int(store.cache_count() or { 0 })39	report.peek_folders_removed = sweep_peek_folders()40	store.drop_cache() or { return refuse(args.json(), err.msg()) }4142	if path := args.value_or_none('backup') {43		if os.exists(path) {44			return refuse(args.json(), '${path} already exists')45		}46		store.backup(path) or { return refuse(args.json(), err.msg()) }47		report.backup = path48	}4950	if args.json() {51		emit_json(json2.encode(report))52	} else {53		if report.problems.len == 0 {54			ok('${store.root} is sound')55		} else {56			for problem in report.problems {57				warn(problem)58			}59		}60		note('${report.objects} objects · ${report.saves} saves · ${report.checkpoints} checkpoints')61		if report.backup != '' {62			ok('backed up to ${report.backup}')63		}64	}65	if report.problems.len > 0 {66		return 167	}68	return 069}7071fn unsafe_save_paths(store &storage.Store) ![]string {72	mut problems := []string{}73	for save in history.line(store)! {74		for file in history.files_of(store, save.id)! {75			if !snapshot.legal_path(file.path) {76				problems << 'save [${save.id}] has unsafe path ${snapshot.escape_path(file.path)} — koh strip ${shell_quote(file.path)}'77			}78		}79	}80	return problems81}8283fn shell_quote(text string) string {84	return "'${text.replace("'", "'\\''")}'"85}8687// peek_age is how old a peek folder must be before doctor clears it.88const peek_age = 864008990// sweep_peek_folders clears the scratch trees `koh peek` leaves in the system91// temp directory, once they are a day old.92fn sweep_peek_folders() int {93	mut removed := 094	for name in os.ls(os.temp_dir()) or { return 0 } {95		path := os.join_path(os.temp_dir(), name)96		if !name.starts_with(peek_prefix)97			|| !peek_is_old(os.file_last_mod_unix(path), time.now().unix()) {98			continue99		}100		os.rmdir_all(path) or { continue }101		removed++102	}103	return removed104}105106// peek_is_old is true once a peek is more than a day old. A younger one may107// still be open in somebody's editor.108fn peek_is_old(modified i64, now i64) bool {109	return modified < now - peek_age110}111112struct DoctorJson {113mut:114	problems             []string115	objects              int116	saves                int117	checkpoints          int118	cached_files         int119	peek_folders_removed int120	backup               string121}