datadir.go 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  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 overrides short-circuit the lookup, which is how the tests point a lookup
  31. // at a temporary directory. Empty means resolve it. Each directory has its own
  32. // because the directories are separate, and a test that exercises one of them is
  33. // not thereby exercising another.
  34. var (
  35. profilesDirOverride string
  36. definitionsDirOverride string
  37. namesDirOverride string
  38. )
  39. // userDataDir is this tool's directory under the platform's configuration
  40. // directory: ~/.config/qmk-rgb-tool on Linux, ~/Library/Application
  41. // Support/qmk-rgb-tool on macOS, %AppData%\qmk-rgb-tool on Windows. A hardcoded
  42. // ~/.config would be wrong on two of the three.
  43. func userDataDir() string {
  44. base, err := userConfigDir()
  45. if err != nil || base == "" {
  46. return dataDirName
  47. }
  48. return filepath.Join(base, dataDirName)
  49. }
  50. // definitionsPath is where the definition files live: the per-user directory,
  51. // and nowhere else. A `keyboard fetch` writes here, so anything else would mean
  52. // the file a fetch produced is not the file the next command reads. The
  53. // definitions built into the binary are consulted after this one, by
  54. // candidateDefinitions in catalog.go.
  55. func definitionsPath() string {
  56. if definitionsDirOverride != "" {
  57. return definitionsDirOverride
  58. }
  59. return filepath.Join(userDataDir(), intrgb.DefinitionsDir)
  60. }
  61. // profilesPath is where the profiles live: the per-user directory, and nowhere
  62. // else. Reading and writing are the same place, so `save lava` followed by
  63. // `load lava` finds what was just written, and a same-named file in a checkout
  64. // cannot be loaded in place of it. An argument ending in .json never comes here —
  65. // resolveProfileTarget in profile.go is where the rule is, and it sends a name to
  66. // this directory and a path to the file system.
  67. func profilesPath() string {
  68. if profilesDirOverride != "" {
  69. return profilesDirOverride
  70. }
  71. return filepath.Join(userDataDir(), "profiles")
  72. }
  73. // ensureDataDir returns a directory and creates it, for the commands that write.
  74. func ensureDataDir(path string) (string, error) {
  75. if err := os.MkdirAll(path, 0o755); err != nil {
  76. return "", err
  77. }
  78. return path, nil
  79. }
  80. // describeDataDir annotates a path so a message that names one says which place
  81. // it is: every directory this tool writes to is under the user's, and a bare
  82. // path does not say that.
  83. func describeDataDir(path string) string {
  84. if user := userDataDir(); path == user || strings.HasPrefix(path, user+string(filepath.Separator)) {
  85. return path + " (your user directory)"
  86. }
  87. return path
  88. }