datadir.go 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. package main
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  7. )
  8. // The tool's own files live under one directory per platform, the one
  9. // os.UserConfigDir names, so they are the same on Linux, macOS and Windows and a
  10. // hardcoded ~/.config is not.
  11. //
  12. // There is one such directory and both kinds of file are read from it and
  13. // written to it. There used to be a search: definitions and profiles were looked
  14. // for next to the executable and in the working directory before the user's, and
  15. // that was wrong for both. A definition is written by `keyboard fetch` and read
  16. // by every command, so a search path can hide the file the user just fetched. A
  17. // profile is written to the user's directory, so a search path for reading can
  18. // find a same-named file in a checkout instead and load that one — the same name,
  19. // different content, and which one you get depends on where you stand.
  20. //
  21. // One argument reaches a file outside it: a profile argument ending in .json is a
  22. // path, and `load profiles/lava.json` reads that file. That is the user naming the
  23. // file rather than a directory being searched, so it does not reintroduce either
  24. // defect above — the argument is the same in every working directory and names
  25. // one file either way.
  26. const dataDirName = "qmk-rgb-tool"
  27. // userConfigDir is a seam: os.UserConfigDir is the platform's own answer, and a
  28. // test cannot rely on which platform it runs on.
  29. var userConfigDir = os.UserConfigDir
  30. // The two overrides short-circuit the lookup, which is how the tests point the
  31. // lookup at a temporary directory. Empty means resolve it.
  32. var (
  33. profilesDirOverride string
  34. definitionsDirOverride string
  35. )
  36. // userDataDir is this tool's directory under the platform's configuration
  37. // directory: ~/.config/qmk-rgb-tool on Linux, ~/Library/Application
  38. // Support/qmk-rgb-tool on macOS, %AppData%\qmk-rgb-tool on Windows. A hardcoded
  39. // ~/.config would be wrong on two of the three.
  40. func userDataDir() string {
  41. base, err := userConfigDir()
  42. if err != nil || base == "" {
  43. return dataDirName
  44. }
  45. return filepath.Join(base, dataDirName)
  46. }
  47. // definitionsPath is where the definition files live: the per-user directory,
  48. // and nowhere else. A `keyboard fetch` writes here, so anything else would mean
  49. // the file a fetch produced is not the file the next command reads. The
  50. // definitions built into the binary are consulted after this one, by
  51. // candidateDefinitions in catalog.go.
  52. func definitionsPath() string {
  53. if definitionsDirOverride != "" {
  54. return definitionsDirOverride
  55. }
  56. return filepath.Join(userDataDir(), intrgb.DefinitionsDir)
  57. }
  58. // profilesPath is where the profiles live: the per-user directory, and nowhere
  59. // else. Reading and writing are the same place, so `save lava` followed by
  60. // `load lava` finds what was just written, and a same-named file in a checkout
  61. // cannot be loaded in place of it. An argument ending in .json never comes here —
  62. // resolveProfileTarget in profile.go is where the rule is, and it sends a name to
  63. // this directory and a path to the file system.
  64. func profilesPath() string {
  65. if profilesDirOverride != "" {
  66. return profilesDirOverride
  67. }
  68. return filepath.Join(userDataDir(), "profiles")
  69. }
  70. // ensureDataDir returns a directory and creates it, for the commands that write.
  71. func ensureDataDir(path string) (string, error) {
  72. if err := os.MkdirAll(path, 0o755); err != nil {
  73. return "", err
  74. }
  75. return path, nil
  76. }
  77. // describeDataDir annotates a path so a message that names one says which place
  78. // it is: every directory this tool writes to is under the user's, and a bare
  79. // path does not say that.
  80. func describeDataDir(path string) string {
  81. if user := userDataDir(); path == user || strings.HasPrefix(path, user+string(filepath.Separator)) {
  82. return path + " (your user directory)"
  83. }
  84. return path
  85. }