Files
gvc/common/engine/manager.go

262 lines
9.5 KiB
Go

package engine
import (
"bytes"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/deranjer/gvc/common/database"
"github.com/rs/zerolog"
)
// NewManager creates a new manager interface that contains all the needed information to make changes to the repo
// rootPath is passed by client or server to let the manager know where to look for the .gvc folder and all the components needed
func NewManager(rootDir string, version string, dbPath string, informer chan OperatingMessage, dirPaths *FilePaths, log *zerolog.Logger) (*Manager, error) {
log.Info().Msg("Creating new Manager...")
// Create new patcher
patcher := Patcher{
Logger: log,
KeyFolder: dirPaths.KeyFolder,
DownloadFolder: dirPaths.DownloadFolder,
SyncFolder: dirPaths.SyncFolder,
ThumbFolder: dirPaths.ThumbFolder,
DiffFolder: dirPaths.ObjectFolder,
}
gvcDB, err := database.OpenDB(dbPath, log, version)
if err != nil {
log.Err(err).Msgf("unable to create or open db: %s", err)
return nil, err
}
var wg *sync.WaitGroup
m := Manager{
version,
//settings,
log,
wg,
patcher,
gvcDB,
informer,
dirPaths,
}
return &m, nil
}
func generatePaths(rootDir string) (filePaths *FilePaths, err error) {
var fullFilePaths FilePaths
// checking for the .gvc folder (the client (but not the server) already checks for the .gvc folder, but this checks all subdirects to make sure they are there)
rootFolder, err := filepath.Abs(rootDir)
if err != nil {
return &fullFilePaths, err
}
path := rootFolder + string(filepath.Separator) + ".gvc"
//path = filepath.Join(rootFolder, filepath.Separator+".gvc")
//where private and public keys are kept
fullFilePaths.KeyFolder = filepath.Join(path, "keys")
//where downloaded files start
fullFilePaths.DownloadFolder = filepath.Join(path, "downloads")
//where file originals live
fullFilePaths.SyncFolder = filepath.Join(path, "sync")
//where patches and last versions live
fullFilePaths.ObjectFolder = filepath.Join(path, "objects")
//where the thumbnails are stored
fullFilePaths.ThumbFolder = filepath.Join(path, "thumb")
//where the logs are stored
fullFilePaths.LogFolder = filepath.Join(path, "logs")
//where plugins are stored
fullFilePaths.PluginFolder = filepath.Join(path, "plugins")
return &fullFilePaths, nil
}
// CheckPaths just checks the .gvc folder structure
func CheckPaths(rootDir string) (filePaths *FilePaths, err error) {
fullFilePaths, err := generatePaths(rootDir)
if err != nil {
return nil, fmt.Errorf("unable to generate paths, err: %s", err)
}
if _, err := os.Stat(fullFilePaths.DownloadFolder); os.IsNotExist(err) {
return nil, err
}
if _, err := os.Stat(fullFilePaths.KeyFolder); os.IsNotExist(err) {
return nil, err
}
if _, err := os.Stat(fullFilePaths.LogFolder); os.IsNotExist(err) {
return nil, err
}
if _, err := os.Stat(fullFilePaths.ObjectFolder); os.IsNotExist(err) {
return nil, err
}
if _, err := os.Stat(fullFilePaths.PluginFolder); os.IsNotExist(err) {
return nil, err
}
if _, err := os.Stat(fullFilePaths.SyncFolder); os.IsNotExist(err) {
return nil, err
}
if _, err := os.Stat(fullFilePaths.ThumbFolder); os.IsNotExist(err) {
return nil, err
}
return fullFilePaths, nil
}
// CreatePaths creates the paths needed in the .gvc folder
func CreatePaths(rootDir string) error {
fullFilePaths, err := generatePaths(rootDir)
if err != nil {
return fmt.Errorf("unable to generate file paths.. %s", err)
}
CreateDirectory(fullFilePaths.KeyFolder)
CreateDirectory(fullFilePaths.DownloadFolder)
CreateDirectory(fullFilePaths.SyncFolder)
CreateDirectory(fullFilePaths.ObjectFolder)
CreateDirectory(fullFilePaths.ThumbFolder)
CreateDirectory(fullFilePaths.LogFolder)
CreateDirectory(fullFilePaths.PluginFolder)
return nil
}
// This adds a file for the watcher to keep an eye on
// however the file will also need to be backedup
// and added to the database.
// This changes all paths to absolute paths rather than relative
// when adding a file to monitor, this should check if the database
// is already expecting to monitor this file. If it is this function should
// do checks to make sure that it is successfully monitoring it, and that there
// is a historical breadcrumb trail to recreate all the versions that the database
// claims to have a copy of
func (m *Manager) AddFileToRepo(relFilePath string) error {
var err error
// the filepath should be absolute, but this should be done dynamically
// if file, err = filepath.Abs(file); err != nil {
// return "", err
// }
//TODO: what needs to happen is a channel for errors/progress is created
//then pass that channel to a routine, and put all of the following in it
// whenever an error returns, fire the string to the channel,
// or send progress on the progress channel
//however need to work out best way of returning the final result to the caller
//- the way to do that is send the result on a third channel, for which is just the result
//see commsManagment.go
// f := NewFileManager()
//DELAYED: this feature affects only large files and user experience. It can wait.
relFilePath = strings.TrimSpace(relFilePath) //purging any odd spaces TODO: Make sure not needed
var tmpFile database.File
filename := filepath.Base(relFilePath)
var hash []byte
//check that the file actually exists (currently done by client/server)
// if filename, err = VerifySrcFile(relFilePath); err != nil {
// //there was no source file or it was not recognisable as a file
// return "", err
// }
//generate a unique file name from the hash and the moment it was created
//a sampled (and therefore) fast, hash of the file for 'uniqueness'
if hash, err = UniqueFileHash(relFilePath); err != nil {
return err
}
m.Info().Msgf("Hash generated for file: %s hash: %s", relFilePath, hex.EncodeToString(hash))
alreadyTracked := m.dB.CheckIfFileCurrentlyMonitored(relFilePath)
if alreadyTracked {
return fmt.Errorf("file already found in tracked files, not adding: %s", relFilePath)
}
tmpFile = database.File{}
tmpFile.Hash = hash
tmpFile.Name = filename
tmpFile.Path = relFilePath
tmpFile.CreatedAt = time.Now()
tmpFile.Unique = hex.EncodeToString([]byte(filename)) + "_" + hex.EncodeToString((tmpFile.Hash)) + "_" + strconv.FormatInt(tmpFile.CreatedAt.Unix(), 10) + "_" + filename
//tmpFile.BkpLocation = filepath.Join(m.SyncFolder, tmpFile.Unique)
//tmpFile.CurrentBase = tmpFile.BkpLocation
//tmpFile.Ignore = false //we can have files in the database that are ignored. TODO: This was initially added so that 'All Files' would show up as a file (its a hack as it adds a dummy to the database)
//we should now have a unique name for this file
//if needs be, we can find out the real file name from the string
//the hash will give us a reasonable indication of the similarity of the files
//define filename of backup(s)
_, err = m.prepareDatabaseForFile(tmpFile)
if err != nil {
return err
}
m.Info().Msgf("added file: %s at path: %s with hash: %s at time: %s", filename, relFilePath, tmpFile.Hash, tmpFile.CreatedAt.String)
return nil
}
// prepareDatabaseForFile is responsible for keeping all references to the version of the file,
// the diff and the metadata of the diffs. Before any file is copied and stored, it should be managed by the database
//
// TODO: This will need to initialise a diff object in the database, currently created by the diff package,
// however going forward a diff maybe defined by the manager.
func (m *Manager) prepareDatabaseForFile(tmpFile database.File) (int, error) {
fileID, err := m.dB.InitializeFileInDatabase(tmpFile)
if err != nil {
m.Error().Msgf("Error checking if file [%s] is monitored. Error %s", tmpFile.Path, err)
return 0, err
}
return fileID, nil
}
// BeginCommit starts the commit process
func (m *Manager) BeginCommit(branch string, commitMessage string) error {
fmt.Println("Beginning Commit on Branch: ", branch)
trackedFiles, err := m.dB.RetrieveTrackedFiles()
if err != nil {
return err
}
if len(trackedFiles) == 0 {
return fmt.Errorf("no files show as tracked in repo, cannot commit, aborting...")
}
var filesToDiff []database.File // Contains the list of files that have changed
for _, trackedFile := range trackedFiles {
fmt.Println("Working on file: ", trackedFile.Path)
if trackedFile.Path == "" {
fmt.Println("No filepath found for file: ", trackedFile.Name)
continue
}
currentFile, err := os.Stat(trackedFile.Path)
if err != nil {
fmt.Printf("unable to stat tracked file: %s error: %s\n", currentFile.Name(), err)
continue
}
currentFileHash, err := UniqueFileHash(trackedFile.Path)
if err != nil {
fmt.Printf("unable to create hash for file: %s error: %s\n", currentFile.Name(), err)
continue
}
result := bytes.Compare(currentFileHash, trackedFile.Hash) // Compare the hashes of the two files
if result == 0 { //If they are equal
fmt.Printf("No changes found in file: %s when compared to file: %s\n", currentFile.Name(), trackedFile.Name)
continue
}
filesToDiff = append(filesToDiff, trackedFile)
}
//diffChannel := make(chan database.DiffObject)
//diffContext := context.Background()
//m.WaitGroup.Add(2)
commit, err := m.dB.FetchLastCommitOnBranch(branch)
if err != nil {
m.Info().Msgf("unable to fetch last commit on branch, assuming first commit on branch", err)
err := m.CreateInitialCommit(filesToDiff, commitMessage)
if err != nil {
m.Err(err).Msgf("unable to create initial commit: %s", err)
return err
}
}
fmt.Println("COMMIT: ", commit.CommitHash)
return nil
}
func (m *Manager) FetchCommitByNumber(branch string, commitNumber string) error {
return nil
}