aboutsummaryrefslogblamecommitdiffstats
path: root/cache/cache.go
blob: 618ec98114d6c6e4d63af1316e21b57dc4db94c3 (plain) (tree)
1
2
3
4
5
6
7
8
9



             
            



                   
 
                                                   
                                             

 

                       
                       



                                                                                                
 
                                                    
                                                   
                                                      
                                         
 
                                                                            
                     

 

                                   

 

                           
                                                   


         


                                                                                
                       
                          

         

                                         

 


                                                                                        
                       
                          

         

                                        

 

                                                                
 
                                    
                       
                          

         
                                     
                       
                          

         

                                             
                       
                          

         
                        

 



                                                                  
         
 

                                   

         
                            

 




                                                                 
         
                     

 






                                                                     
         
                  

 

                                                  
 





                                                                                    
 


                                                                                
 
                                   
 
                                              
                          

         








                                                                               
 



                                                     
 


                                                                                                                           
 
                                                                             
 




                                                                                                        
 




                                   
 
                  

 

                                                                     
 
package cache

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"

	"github.com/MichaelMure/git-bug/repository"
	"github.com/MichaelMure/git-bug/util"
)

const lockfile = "lock"

type Cacher interface {
	// RegisterRepository register a named repository. Use this for multi-repo setup
	RegisterRepository(ref string, repo repository.Repo) error
	// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
	RegisterDefaultRepository(repo repository.Repo) error

	// ResolveRepo retrieve a repository by name
	ResolveRepo(ref string) (RepoCacher, error)
	// DefaultRepo retrieve the default repository
	DefaultRepo() (RepoCacher, error)

	// Close will do anything that is needed to close the cache properly
	Close() error
}

type RootCache struct {
	repos map[string]RepoCacher
}

func NewCache() RootCache {
	return RootCache{
		repos: make(map[string]RepoCacher),
	}
}

// RegisterRepository register a named repository. Use this for multi-repo setup
func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) error {
	err := c.lockRepository(repo)
	if err != nil {
		return err
	}

	c.repos[ref] = NewRepoCache(repo)
	return nil
}

// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) error {
	err := c.lockRepository(repo)
	if err != nil {
		return err
	}

	c.repos[""] = NewRepoCache(repo)
	return nil
}

func (c *RootCache) lockRepository(repo repository.Repo) error {
	lockPath := repoLockFilePath(repo)

	err := RepoIsAvailable(repo)
	if err != nil {
		return err
	}

	f, err := os.Create(lockPath)
	if err != nil {
		return err
	}

	pid := fmt.Sprintf("%d", os.Getpid())
	_, err = f.WriteString(pid)
	if err != nil {
		return err
	}

	return f.Close()
}

// ResolveRepo retrieve a repository by name
func (c *RootCache) DefaultRepo() (RepoCacher, error) {
	if len(c.repos) != 1 {
		return nil, fmt.Errorf("repository is not unique")
	}

	for _, r := range c.repos {
		return r, nil
	}

	panic("unreachable")
}

// DefaultRepo retrieve the default repository
func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) {
	r, ok := c.repos[ref]
	if !ok {
		return nil, fmt.Errorf("unknown repo")
	}
	return r, nil
}

func (c *RootCache) Close() error {
	for _, cachedRepo := range c.repos {
		lockPath := repoLockFilePath(cachedRepo.Repository())
		err := os.Remove(lockPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func RepoIsAvailable(repo repository.Repo) error {
	lockPath := repoLockFilePath(repo)

	// Todo: this leave way for a racey access to the repo between the test
	// if the file exist and the actual write. It's probably not a problem in
	// practice because using a repository will be done from user interaction
	// or in a context where a single instance of git-bug is already guaranteed
	// (say, a server with the web UI running). But still, that might be nice to
	// have a mutex or something to guard that.

	// Todo: this will fail if somehow the filesystem is shared with another
	// computer. Should add a configuration that prevent the cleaning of the
	// lock file

	f, err := os.Open(lockPath)

	if err != nil && !os.IsNotExist(err) {
		return err
	}

	if err == nil {
		// lock file already exist
		buf, err := ioutil.ReadAll(io.LimitReader(f, 10))
		if err != nil {
			return err
		}
		if len(buf) == 10 {
			return fmt.Errorf("The lock file should be < 10 bytes")
		}

		pid, err := strconv.Atoi(string(buf))
		if err != nil {
			return err
		}

		if util.ProcessIsRunning(pid) {
			return fmt.Errorf("The repository you want to access is already locked by the process pid %d", pid)
		}

		// The lock file is just laying there after a crash, clean it

		fmt.Println("A lock file is present but the corresponding process is not, removing it.")
		err = f.Close()
		if err != nil {
			return err
		}

		os.Remove(lockPath)
		if err != nil {
			return err
		}
	}

	return nil
}

func repoLockFilePath(repo repository.Repo) string {
	return path.Join(repo.GetPath(), ".git", "git-bug", lockfile)
}