1package cache
2
3import (
4 "fmt"
5 "io"
6 "io/ioutil"
7 "os"
8 "path"
9 "strconv"
10
11 "github.com/MichaelMure/git-bug/repository"
12 "github.com/MichaelMure/git-bug/util"
13)
14
15const lockfile = "lock"
16const excerptsFile = "excerpts"
17
18type RootCache struct {
19 repos map[string]*RepoCache
20}
21
22func NewCache() RootCache {
23 return RootCache{
24 repos: make(map[string]*RepoCache),
25 }
26}
27
28// RegisterRepository register a named repository. Use this for multi-repo setup
29func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) error {
30 err := c.lockRepository(repo)
31 if err != nil {
32 return err
33 }
34
35 r, err := NewRepoCache(repo)
36 if err != nil {
37 return err
38 }
39
40 c.repos[ref] = r
41 return nil
42}
43
44// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
45func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) error {
46 err := c.lockRepository(repo)
47 if err != nil {
48 return err
49 }
50
51 r, err := NewRepoCache(repo)
52 if err != nil {
53 return err
54 }
55
56 c.repos[""] = r
57 return nil
58}
59
60func (c *RootCache) lockRepository(repo repository.Repo) error {
61 lockPath := repoLockFilePath(repo)
62
63 err := RepoIsAvailable(repo)
64 if err != nil {
65 return err
66 }
67
68 f, err := os.Create(lockPath)
69 if err != nil {
70 return err
71 }
72
73 pid := fmt.Sprintf("%d", os.Getpid())
74 _, err = f.WriteString(pid)
75 if err != nil {
76 return err
77 }
78
79 return f.Close()
80}
81
82// ResolveRepo retrieve a repository by name
83func (c *RootCache) DefaultRepo() (*RepoCache, error) {
84 if len(c.repos) != 1 {
85 return nil, fmt.Errorf("repository is not unique")
86 }
87
88 for _, r := range c.repos {
89 return r, nil
90 }
91
92 panic("unreachable")
93}
94
95// DefaultRepo retrieve the default repository
96func (c *RootCache) ResolveRepo(ref string) (*RepoCache, error) {
97 r, ok := c.repos[ref]
98 if !ok {
99 return nil, fmt.Errorf("unknown repo")
100 }
101 return r, nil
102}
103
104// Close will do anything that is needed to close the cache properly
105func (c *RootCache) Close() error {
106 for _, cachedRepo := range c.repos {
107 lockPath := repoLockFilePath(cachedRepo.repo)
108 err := os.Remove(lockPath)
109 if err != nil {
110 return err
111 }
112 }
113 return nil
114}
115
116// RepoIsAvailable check is the given repository is locked by a Cache.
117// Note: this is a smart function that will cleanup the lock file if the
118// corresponding process is not there anymore.
119// If no error is returned, the repo is free to edit.
120func RepoIsAvailable(repo repository.Repo) error {
121 lockPath := repoLockFilePath(repo)
122
123 // Todo: this leave way for a racey access to the repo between the test
124 // if the file exist and the actual write. It's probably not a problem in
125 // practice because using a repository will be done from user interaction
126 // or in a context where a single instance of git-bug is already guaranteed
127 // (say, a server with the web UI running). But still, that might be nice to
128 // have a mutex or something to guard that.
129
130 // Todo: this will fail if somehow the filesystem is shared with another
131 // computer. Should add a configuration that prevent the cleaning of the
132 // lock file
133
134 f, err := os.Open(lockPath)
135
136 if err != nil && !os.IsNotExist(err) {
137 return err
138 }
139
140 if err == nil {
141 // lock file already exist
142 buf, err := ioutil.ReadAll(io.LimitReader(f, 10))
143 if err != nil {
144 return err
145 }
146 if len(buf) == 10 {
147 return fmt.Errorf("The lock file should be < 10 bytes")
148 }
149
150 pid, err := strconv.Atoi(string(buf))
151 if err != nil {
152 return err
153 }
154
155 if util.ProcessIsRunning(pid) {
156 return fmt.Errorf("The repository you want to access is already locked by the process pid %d", pid)
157 }
158
159 // The lock file is just laying there after a crash, clean it
160
161 fmt.Println("A lock file is present but the corresponding process is not, removing it.")
162 err = f.Close()
163 if err != nil {
164 return err
165 }
166
167 os.Remove(lockPath)
168 if err != nil {
169 return err
170 }
171 }
172
173 return nil
174}
175
176func repoLockFilePath(repo repository.Repo) string {
177 return path.Join(repo.GetPath(), ".git", "git-bug", lockfile)
178}