package main import ( "fmt" "os" "path/filepath" "sort" "strings" ) // Check is one line of xdg_check's answer. type Check struct { Name string `json:"name"` OK bool `json:"ok"` Detail string `json:"detail"` } // Check is xdg_check: whether the account's XDG conventions are as the module declares them, and // what is wrong in what it holds as found. func (m *Machine) Check(env string) map[string]any { var checks []Check add := func(name string, ok bool, detail string) { checks = append(checks, Check{name, ok, detail}) } // The folders. u := m.UserDirs() add("user-dirs.dirs is the mesh's", u.Meshs, map[bool]string{ true: u.File + " carries the module's header", false: u.File + " has no header of module xdg: the module is not assigned here, or the file was rewritten since the last push", }[u.Meshs]) var missing []string for _, d := range u.Dirs { if !d.Home && !d.Exists { missing = append(missing, fmt.Sprintf("%s → %s (%s)", d.Name, d.Path, d.Kind)) } } add("every folder it names exists", len(missing) == 0, orNone(missing, "each is a directory, or the home")) add("xdg-user-dirs-update leaves it alone", !u.Update.Enabled, "enabled is "+fmt.Sprint(u.Update.Enabled)+", from "+u.Update.DecidedBy) // The default applications. var listProblems []string meshListFound := false for _, l := range m.lists() { if l.File == MeshList { meshListFound = true } switch { case l.Broken != "": listProblems = append(listProblems, l.File+": "+l.Broken) case len(l.Keys.DupGroups) > 0: listProblems = append(listProblems, fmt.Sprintf("%s: the group %s is written twice, and GLib and xdg-mime then answer differently", l.File, strings.Join(l.Keys.DupGroups, ", "))) case len(l.Keys.DupKeys) > 0: listProblems = append(listProblems, fmt.Sprintf("%s: %s written twice, and GLib and xdg-mime then answer differently", l.File, strings.Join(l.Keys.DupKeys, ", "))) case len(l.Keys.Unparsable) > 0: listProblems = append(listProblems, fmt.Sprintf("%s: lines %v are not key=value under a group", l.File, l.Keys.Unparsable)) } if m.deprecated(l.File) && strings.HasPrefix(l.File, "~/") { listProblems = append(listProblems, l.File+": a list in the deprecated place, which nothing current writes (a predecessor's; remove it once)") } } for _, f := range []string{filepath.Join(m.dataHome(), "applications", "defaults.list")} { if m.kind(f) != "absent" || m.isLink(f) { listProblems = append(listProblems, m.tilde(f)+": the long-obsolete defaults.list, which nothing reads any more; remove it once") } } add("the mesh's default applications are in place", meshListFound, MeshList) add("the mimeapps lists are sane", len(listProblems) == 0, orNone(listProblems, "each parses, with no group or key written twice")) var dead []string for _, d := range m.deadDefaults() { dead = append(dead, fmt.Sprintf("%s: %s=%s", d.File, d.Type, d.ID)) } add("every default names an installed application", len(dead) == 0, orNone(dead, "all installed")) var broken []string for _, e := range m.brokenHandlers() { why := e.Broken if why == "" { why = e.Program + " is not installed" } broken = append(broken, fmt.Sprintf("%s (%s): %s", e.ID, e.File, why)) } add("no handler's program is missing", len(broken) == 0, orNone(broken, "every application declaring a type can run")) // Autostart. starts := m.Autostarts(env) byProgram := map[string][]string{} var twice, failing []string for _, a := range starts { if !a.DexStarts { continue } byProgram[filepath.Base(a.Program)] = append(byProgram[filepath.Base(a.Program)], a.File) if a.AlsoStarted != "" { twice = append(twice, fmt.Sprintf("%s: by %s and by %s", a.Program, a.File, a.AlsoStarted)) } if !a.Found { failing = append(failing, fmt.Sprintf("%s: %s is not installed", a.File, a.Program)) } } for _, p := range sortedKeys(byProgram) { if len(byProgram[p]) > 1 { twice = append(twice, fmt.Sprintf("%s: by %s", p, strings.Join(byProgram[p], " and "))) } } sort.Strings(twice) add("no program is started twice at login", len(twice) == 0, orNone(twice, "each autostarted program has one starter")) add("every program dex starts is installed", len(failing) == 0, orNone(failing, "all installed")) failingN := 0 for _, c := range checks { if !c.OK { failingN++ } } return map[string]any{"checks": checks, "failing": failingN} } func (m *Machine) isLink(path string) bool { _, err := os.Lstat(m.p(path)) return err == nil } func orNone(items []string, fine string) string { if len(items) == 0 { return fine } return strings.Join(items, "; ") }