262 lines
9.5 KiB
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
|
|
}
|