Some checks failed
CloudSave/pipeline/head There was a failure building this commit
411 lines
9.4 KiB
Go
411 lines
9.4 KiB
Go
package data
|
|
|
|
import (
|
|
"cloudsave/pkg/remote/client"
|
|
"cloudsave/pkg/repository"
|
|
"cloudsave/pkg/tools/archive"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type (
|
|
Service struct {
|
|
repo repository.Repository
|
|
}
|
|
)
|
|
|
|
func NewService(repo repository.Repository) *Service {
|
|
return &Service{
|
|
repo: repo,
|
|
}
|
|
}
|
|
|
|
func (s *Service) Add(name, path, remote string) (string, error) {
|
|
gameID := repository.NewGameIdentifier(uuid.NewString())
|
|
|
|
if err := s.repo.Mkdir(gameID); err != nil {
|
|
return "", fmt.Errorf("failed to add game reference: %w", err)
|
|
}
|
|
|
|
m := repository.Metadata{
|
|
ID: gameID.Key(),
|
|
Name: name,
|
|
Path: path,
|
|
Version: 0,
|
|
Date: time.Now(),
|
|
}
|
|
|
|
if err := s.repo.WriteMetadata(gameID, m); err != nil {
|
|
return "", fmt.Errorf("failed to add game reference: %w", err)
|
|
}
|
|
|
|
return gameID.Key(), nil
|
|
}
|
|
|
|
func (s *Service) One(gameID string) (repository.Metadata, error) {
|
|
id := repository.NewGameIdentifier(gameID)
|
|
|
|
m, err := s.repo.Metadata(id)
|
|
if err != nil {
|
|
return repository.Metadata{}, fmt.Errorf("failed to get metadata: %w", err)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (s *Service) Backup(gameID, backupID string) (repository.Backup, error) {
|
|
id := repository.NewBackupIdentifier(gameID, backupID)
|
|
|
|
if err := s.repo.Mkdir(id); err != nil {
|
|
return repository.Backup{}, fmt.Errorf("failed to make game dir: %w", err)
|
|
}
|
|
|
|
return s.repo.Backup(id)
|
|
}
|
|
|
|
func (s *Service) UpdateMetadata(gameID string, m repository.Metadata) error {
|
|
id := repository.NewGameIdentifier(gameID)
|
|
|
|
if err := s.repo.Mkdir(id); err != nil {
|
|
return fmt.Errorf("failed to make game dir: %w", err)
|
|
}
|
|
|
|
if err := s.repo.WriteMetadata(id, m); err != nil {
|
|
return fmt.Errorf("failed to write metadate: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) Scan(gameID string) (bool, error) {
|
|
id := repository.NewGameIdentifier(gameID)
|
|
|
|
lastRun, err := s.repo.LastScan(id)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get last scan time: %w", err)
|
|
}
|
|
|
|
m, err := s.repo.Metadata(id)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get game metadata: %w", err)
|
|
}
|
|
|
|
if !IsDirectoryChanged(m.Path, lastRun) {
|
|
return false, nil
|
|
}
|
|
|
|
if err := s.MakeBackup(gameID); err != nil {
|
|
return false, fmt.Errorf("failed to make the backup: %w", err)
|
|
}
|
|
|
|
f, err := s.repo.WriteBlob(id)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get datastore stream: %w", err)
|
|
}
|
|
if v, ok := f.(io.Closer); ok {
|
|
defer v.Close()
|
|
}
|
|
|
|
if err := archive.Tar(f, m.Path); err != nil {
|
|
return false, fmt.Errorf("failed to make archive: %w", err)
|
|
}
|
|
|
|
if err := s.repo.ResetLastScan(id); err != nil {
|
|
return false, fmt.Errorf("failed to reset scan date: %w", err)
|
|
}
|
|
|
|
m.Date = time.Now()
|
|
m.Version += 1
|
|
|
|
if err := s.repo.WriteMetadata(id, m); err != nil {
|
|
return false, fmt.Errorf("failed to update metadata: %w", err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (s *Service) MakeBackup(gameID string) error {
|
|
var id repository.Identifier = repository.NewGameIdentifier(gameID)
|
|
|
|
src, err := s.repo.ReadBlob(id)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrNotFound) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
if v, ok := src.(io.Closer); ok {
|
|
defer v.Close()
|
|
}
|
|
|
|
id = repository.NewBackupIdentifier(gameID, uuid.NewString())
|
|
|
|
if err := s.repo.Mkdir(id); err != nil {
|
|
return err
|
|
}
|
|
|
|
dst, err := s.repo.WriteBlob(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := dst.(io.Closer); ok {
|
|
defer v.Close()
|
|
}
|
|
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) AllGames() ([]repository.Metadata, error) {
|
|
ids, err := s.repo.All()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get the list of ids: %w", err)
|
|
}
|
|
|
|
var ms []repository.Metadata
|
|
for _, id := range ids {
|
|
m, err := s.repo.Metadata(repository.NewGameIdentifier(id))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open metadata: %w", err)
|
|
}
|
|
ms = append(ms, m)
|
|
}
|
|
|
|
return ms, nil
|
|
}
|
|
|
|
func (s *Service) AllBackups(gameID string) ([]repository.Backup, error) {
|
|
ids, err := s.repo.AllHist(repository.NewGameIdentifier(gameID))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get the list of ids: %w", err)
|
|
}
|
|
|
|
var bs []repository.Backup
|
|
for _, id := range ids {
|
|
b, err := s.repo.Backup(repository.NewBackupIdentifier(gameID, id))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open metadata: %w", err)
|
|
}
|
|
bs = append(bs, b)
|
|
}
|
|
|
|
return bs, nil
|
|
}
|
|
|
|
func (l Service) PullArchive(gameID, backupID string, cli *client.Client) error {
|
|
if len(backupID) > 0 {
|
|
path := l.repo.DataPath(repository.NewBackupIdentifier(gameID, backupID))
|
|
return cli.PullBackup(gameID, backupID, filepath.Join(path, "data.tar.gz"))
|
|
}
|
|
|
|
path := l.repo.DataPath(repository.NewGameIdentifier(gameID))
|
|
return cli.Pull(gameID, filepath.Join(path, "data.tar.gz"))
|
|
}
|
|
|
|
func (l Service) PushArchive(gameID, backupID string, cli *client.Client) error {
|
|
m, err := l.repo.Metadata(repository.NewGameIdentifier(gameID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(backupID) > 0 {
|
|
path := l.repo.DataPath(repository.NewBackupIdentifier(gameID, backupID))
|
|
return cli.PushSave(filepath.Join(path, "data.taz.gz"), m)
|
|
}
|
|
|
|
path := l.repo.DataPath(repository.NewGameIdentifier(gameID))
|
|
return cli.PushSave(filepath.Join(path, "data.tar.gz"), m)
|
|
}
|
|
|
|
func (l Service) PullCurrent(id, path string, cli *client.Client) error {
|
|
gameID := repository.NewGameIdentifier(id)
|
|
if err := l.repo.Mkdir(gameID); err != nil {
|
|
return err
|
|
}
|
|
|
|
m, err := cli.Metadata(id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get metadata from the server: %w", err)
|
|
}
|
|
|
|
if err := l.repo.WriteMetadata(gameID, m); err != nil {
|
|
return fmt.Errorf("failed to write metadata: %w", err)
|
|
}
|
|
|
|
archivePath := filepath.Join(l.repo.DataPath(gameID), "data.tar.gz")
|
|
|
|
if err := cli.Pull(id, archivePath); err != nil {
|
|
return fmt.Errorf("failed to pull from the server: %w", err)
|
|
}
|
|
|
|
f, err := l.repo.ReadBlob(gameID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open blob from local repository: %w", err)
|
|
}
|
|
|
|
if _, err := os.Stat(path); err == nil {
|
|
if err := os.RemoveAll(path); err != nil {
|
|
return fmt.Errorf("failed to clean the destination directory: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := os.MkdirAll(path, 0600); err != nil {
|
|
return fmt.Errorf("failed to create destination directory: %w", err)
|
|
}
|
|
|
|
if err := archive.Untar(f, path); err != nil {
|
|
return fmt.Errorf("failed to untar archive: %w", err)
|
|
}
|
|
|
|
if err := l.repo.ResetLastScan(gameID); err != nil {
|
|
return fmt.Errorf("failed to create .last_run file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l Service) PullBackup(gameID, backupID string, cli *client.Client) error {
|
|
id := repository.NewBackupIdentifier(gameID, backupID)
|
|
|
|
archivePath := filepath.Join(l.repo.DataPath(id), "data.tar.gz")
|
|
|
|
if err := cli.PullBackup(gameID, backupID, archivePath); err != nil {
|
|
return fmt.Errorf("failed to pull backup: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l Service) RemoveGame(gameID string) error {
|
|
return l.repo.Remove(repository.NewGameIdentifier(gameID))
|
|
}
|
|
|
|
func (l Service) SetVersion(gameID string, value int) error {
|
|
id := repository.NewGameIdentifier(gameID)
|
|
|
|
m, err := l.repo.Metadata(id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get metadata from the server: %w", err)
|
|
}
|
|
|
|
m.Version = value
|
|
|
|
if err := l.repo.WriteMetadata(id, m); err != nil {
|
|
return fmt.Errorf("failed to write metadata: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IsDirectoryChanged(path string, lastRun time.Time) bool {
|
|
changed := false
|
|
_ = filepath.Walk(path, func(path string, info os.FileInfo, walkErr error) error {
|
|
if walkErr != nil {
|
|
return nil
|
|
}
|
|
if info.ModTime().After(lastRun) {
|
|
changed = true
|
|
return io.EOF // early exit
|
|
}
|
|
return nil
|
|
})
|
|
return changed
|
|
}
|
|
|
|
func (l Service) Copy(id string, src io.Reader) error {
|
|
dst, err := l.repo.WriteBlob(repository.NewGameIdentifier(id))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := dst.(io.Closer); ok {
|
|
defer v.Close()
|
|
}
|
|
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l Service) CopyBackup(gameID, backupID string, src io.Reader) error {
|
|
id := repository.NewBackupIdentifier(gameID, backupID)
|
|
|
|
if err := l.repo.Mkdir(id); err != nil {
|
|
return err
|
|
}
|
|
|
|
dst, err := l.repo.WriteBlob(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := dst.(io.Closer); ok {
|
|
defer v.Close()
|
|
}
|
|
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l Service) ApplyCurrent(gameID string) error {
|
|
id := repository.NewGameIdentifier(gameID)
|
|
path := l.repo.DataPath(id)
|
|
|
|
g, err := l.repo.Metadata(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return l.apply(filepath.Join(path, "data.tar.gz"), g.Path)
|
|
}
|
|
|
|
func (l Service) ApplyBackup(gameID, backupID string) error {
|
|
id := repository.NewGameIdentifier(gameID)
|
|
fullID := repository.NewBackupIdentifier(gameID, backupID)
|
|
path := l.repo.DataPath(fullID)
|
|
|
|
g, err := l.repo.Metadata(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return l.apply(filepath.Join(path, "data.tar.gz"), g.Path)
|
|
}
|
|
|
|
func (l Service) Repository() repository.Repository {
|
|
return l.repo
|
|
}
|
|
|
|
func (l Service) ReloadCache(gameID string) error {
|
|
if er, ok := l.repo.(*repository.EagerRepository); ok {
|
|
return er.ReloadMetadata(repository.NewGameIdentifier(gameID))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l Service) apply(src, dst string) error {
|
|
if err := os.RemoveAll(dst); err != nil {
|
|
return fmt.Errorf("failed to remove old save: %w", err)
|
|
}
|
|
|
|
f, err := os.OpenFile(src, os.O_RDONLY, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open archive: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
return archive.Untar(f, dst)
|
|
}
|