package main import ( "os" "path/filepath" "strings" intrgb "netdome.biz/paul/qmk-rgb/internal/rgb" ) // The tool's own files live under one directory per platform, the one // os.UserConfigDir names, so they are the same on Linux, macOS and Windows and a // hardcoded ~/.config is not. // // There is one such directory and both kinds of file are read from it and // written to it. There used to be a search: definitions and profiles were looked // for next to the executable and in the working directory before the user's, and // that was wrong for both. A definition is written by `keyboard fetch` and read // by every command, so a search path can hide the file the user just fetched. A // profile is written to the user's directory, so a search path for reading can // find a same-named file in a checkout instead and load that one — the same name, // different content, and which one you get depends on where you stand. const dataDirName = "qmk-rgb-tool" // userConfigDir is a seam: os.UserConfigDir is the platform's own answer, and a // test cannot rely on which platform it runs on. var userConfigDir = os.UserConfigDir // The two overrides short-circuit the lookup, which is how the tests point the // lookup at a temporary directory. Empty means resolve it. var ( profilesDirOverride string definitionsDirOverride string ) // userDataDir is this tool's directory under the platform's configuration // directory: ~/.config/qmk-rgb-tool on Linux, ~/Library/Application // Support/qmk-rgb-tool on macOS, %AppData%\qmk-rgb-tool on Windows. A hardcoded // ~/.config would be wrong on two of the three. func userDataDir() string { base, err := userConfigDir() if err != nil || base == "" { return dataDirName } return filepath.Join(base, dataDirName) } // definitionsPath is where the definition files live: the per-user directory, // and nowhere else. A `keyboard fetch` writes here, so anything else would mean // the file a fetch produced is not the file the next command reads. The // definitions built into the binary are consulted after this one, by // candidateDefinitions in catalog.go. func definitionsPath() string { if definitionsDirOverride != "" { return definitionsDirOverride } return filepath.Join(userDataDir(), intrgb.DefinitionsDir) } // profilesPath is where the profiles live: the per-user directory, and nowhere // else. Reading and writing are the same place, so `save lava` followed by // `load lava` finds what was just written, and a same-named file in a checkout // cannot be loaded in place of it. func profilesPath() string { if profilesDirOverride != "" { return profilesDirOverride } return filepath.Join(userDataDir(), "profiles") } // profilesWritePath is where a saved profile goes, which is not profilesPath. // Writing to either of those would put a file the user did not ask for into a // $GOPATH/bin that a reinstall replaces, or leave an untracked profiles/lava.json // in someone's repository. So a save always goes to the per-user directory. func profilesWritePath() string { if profilesDirOverride != "" { return profilesDirOverride } return filepath.Join(userDataDir(), "profiles") } // ensureDataDir returns a directory and creates it, for the commands that write. func ensureDataDir(path string) (string, error) { if err := os.MkdirAll(path, 0o755); err != nil { return "", err } return path, nil } // describeDataDir annotates a path so a message that names one says which place // it is: every directory this tool writes to is under the user's, and a bare // path does not say that. func describeDataDir(path string) string { if user := userDataDir(); path == user || strings.HasPrefix(path, user+string(filepath.Separator)) { return path + " (your user directory)" } return path }