projects.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package projects
  2. import (
  3. "encoding/json"
  4. "os"
  5. "path/filepath"
  6. "slices"
  7. "sync"
  8. "time"
  9. "github.com/charmbracelet/crush/internal/config"
  10. )
  11. const projectsFileName = "projects.json"
  12. // Project represents a tracked project directory.
  13. type Project struct {
  14. Path string `json:"path"`
  15. DataDir string `json:"data_dir"`
  16. LastAccessed time.Time `json:"last_accessed"`
  17. }
  18. // ProjectList holds the list of tracked projects.
  19. type ProjectList struct {
  20. Projects []Project `json:"projects"`
  21. }
  22. var mu sync.Mutex
  23. // projectsFilePath returns the path to the projects.json file.
  24. func projectsFilePath() string {
  25. return filepath.Join(filepath.Dir(config.GlobalConfigData()), projectsFileName)
  26. }
  27. // Load reads the projects list from disk.
  28. func Load() (*ProjectList, error) {
  29. mu.Lock()
  30. defer mu.Unlock()
  31. path := projectsFilePath()
  32. data, err := os.ReadFile(path)
  33. if err != nil {
  34. if os.IsNotExist(err) {
  35. return &ProjectList{Projects: []Project{}}, nil
  36. }
  37. return nil, err
  38. }
  39. var list ProjectList
  40. if err := json.Unmarshal(data, &list); err != nil {
  41. return nil, err
  42. }
  43. return &list, nil
  44. }
  45. // Save writes the projects list to disk.
  46. func Save(list *ProjectList) error {
  47. mu.Lock()
  48. defer mu.Unlock()
  49. path := projectsFilePath()
  50. // Ensure directory exists
  51. if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
  52. return err
  53. }
  54. data, err := json.MarshalIndent(list, "", " ")
  55. if err != nil {
  56. return err
  57. }
  58. return os.WriteFile(path, data, 0o600)
  59. }
  60. // Register adds or updates a project in the list.
  61. func Register(workingDir, dataDir string) error {
  62. list, err := Load()
  63. if err != nil {
  64. return err
  65. }
  66. now := time.Now().UTC()
  67. // Check if project already exists
  68. found := false
  69. for i, p := range list.Projects {
  70. if p.Path == workingDir {
  71. list.Projects[i].DataDir = dataDir
  72. list.Projects[i].LastAccessed = now
  73. found = true
  74. break
  75. }
  76. }
  77. if !found {
  78. list.Projects = append(list.Projects, Project{
  79. Path: workingDir,
  80. DataDir: dataDir,
  81. LastAccessed: now,
  82. })
  83. }
  84. // Sort by last accessed (most recent first)
  85. slices.SortFunc(list.Projects, func(a, b Project) int {
  86. if a.LastAccessed.After(b.LastAccessed) {
  87. return -1
  88. }
  89. if a.LastAccessed.Before(b.LastAccessed) {
  90. return 1
  91. }
  92. return 0
  93. })
  94. return Save(list)
  95. }
  96. // List returns all tracked projects sorted by last accessed.
  97. func List() ([]Project, error) {
  98. list, err := Load()
  99. if err != nil {
  100. return nil, err
  101. }
  102. return list.Projects, nil
  103. }