Files
gvc/common/manager/manager.go

168 lines
6.6 KiB
Go

package manager
import (
"encoding/base64"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/deranjer/gvc/common/database"
engine "github.com/deranjer/gvc/common/engine"
"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, log *zerolog.Logger) (*Manager, error) {
log.Info().Msg("Creating new Manager...")
dirPaths, err := checkPaths(rootDir)
if err != nil {
return &Manager{}, err
}
// Create new patcher
patcher := engine.Patcher{
log,
dirPaths.KeyFolder,
dirPaths.DownloadFolder,
dirPaths.SyncFolder,
dirPaths.ThumbFolder,
dirPaths.DiffFolder,
}
if err != nil {
log.Fatal().Msgf("Error creating a patcher %s", err)
return &Manager{}, err
}
gvcDB, err := database.OpenOrCreateDB(dbPath, log)
if err != nil {
log.Fatal().Msgf("unable to create or open db: %s", err)
}
m := Manager{
version,
//settings,
log,
patcher,
gvcDB,
informer,
dirPaths,
}
return &m, nil
}
func checkPaths(rootDir string) (filePaths *FilePaths, err error) {
// 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 &FilePaths{}, err
}
path := rootFolder + string(filepath.Separator) + ".gvc"
//path = filepath.Join(rootFolder, filepath.Separator+".gvc")
var fullFilePaths FilePaths
//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.DiffFolder = filepath.Join(path, "diff")
//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")
engine.InitiateDirectory(fullFilePaths.KeyFolder)
engine.InitiateDirectory(fullFilePaths.DownloadFolder)
engine.InitiateDirectory(fullFilePaths.SyncFolder)
engine.InitiateDirectory(fullFilePaths.DiffFolder)
engine.InitiateDirectory(fullFilePaths.ThumbFolder)
engine.InitiateDirectory(fullFilePaths.LogFolder)
engine.InitiateDirectory(fullFilePaths.PluginFolder)
return &fullFilePaths, 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.
var tmpFile database.File
filename := filepath.Base(relFilePath)
var hash [16]byte
//check that the file actually exists (currently done by client/server)
// if filename, err = engine.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 = engine.UniqueFileHash(relFilePath); err != nil {
return err
}
if tmpFile, err = m.dB.CheckIfFileCurrentlyMonitored(relFilePath, hash); err != nil {
if strings.Index(err.Error(), "not found") != -1 {
//the file wasn't found, this is an ok error
m.Info().Msgf("The file was [%s], so continuing to create it in the database", err)
} else {
return fmt.Errorf("File was not found in repo, please add file first")
}
tmpFile.CurrentHash = hash
tmpFile.Name = filename
tmpFile.Path = relFilePath
tmpFile.CreatedAt = time.Now()
tmpFile.Unique = base64.URLEncoding.EncodeToString([]byte(filename)) + "_" + base64.URLEncoding.EncodeToString((tmpFile.CurrentHash[:])) + "_" + 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.CurrentHash, 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
}