Files
Igor Sirotin f9cc782a6f refactor: move services to pkg/services
Part of the Go project layout migration, item 31.

Pure move plus import-path rewrite across 687 files. No API or behaviour
change.

The services keep their grouping under pkg/services/<name> rather than
being promoted to pkg/<name>: 27 top-level directories in pkg/ would read
worse than what we have, and the grouping is what makes "an RPC service"
identifiable at a glance.

Paths that follow the move: the logosstorage test target and generate
step, the two wallet token-list tools, the migration-order check (and the
pre-rebase hook symlinked to it), and the storage env helper.

refs #7067
2026-08-21 10:11:05 +02:00

187 lines
3.5 KiB
Go

package backup
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"reflect"
"sync"
"time"
"go.uber.org/zap"
"github.com/status-im/status-go/internal/panics"
"github.com/status-im/status-go/internal/pausable"
"github.com/status-im/status-go/internal/signal"
)
//go:generate go tool mockgen -package=mock_backup_controller -source controller.go -destination=mock/mock_backup_controller.go
type Config struct {
PrivateKey []byte
FileNameProvider FilenameProvider
BackupEnabled bool
Interval time.Duration
}
type FilenameProvider interface {
GetBackupFilename() (string, error)
}
type Provider interface {
ExportBackup() ([]byte, error)
ImportBackup(data []byte) error
}
type Controller struct {
pausable.PauseBroadcaster
config Config
core *core
logger *zap.Logger
quit chan struct{}
mutex sync.Mutex
wg *sync.WaitGroup
}
type CompletedEvent struct {
FileName string
}
func (b CompletedEvent) MarshalJSON() ([]byte, error) {
responseItem := struct {
FileName string `json:"fileName,omitempty"`
}{
FileName: b.FileName,
}
return json.Marshal(responseItem)
}
func NewController(config Config, logger *zap.Logger) (*Controller, error) {
if len(config.PrivateKey) == 0 {
return nil, errors.New("private key must be provided")
}
if isNil(config.FileNameProvider) {
return nil, errors.New("filename provider must be provided")
}
return &Controller{
config: config,
core: newCore(),
logger: logger,
wg: &sync.WaitGroup{},
quit: make(chan struct{}),
}, nil
}
func (c *Controller) Register(componentName string, provider Provider) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.core.Register(componentName, provider)
}
func (c *Controller) Start() {
if !c.config.BackupEnabled {
return
}
c.MarkStarted()
c.wg.Add(1)
go func() {
defer panics.LogOnPanic()
defer c.wg.Done()
sub := c.Subscribe()
defer sub.Unsubscribe()
pt := pausable.NewPausableTicker(pausable.PausableTickerConfig{
Interval: c.config.Interval,
OnTick: func() {
_, err := c.PerformBackup()
if err != nil {
c.logger.Error("Error performing backup", zap.Error(err))
}
},
}, sub.C())
pt.Run(c.quit)
}()
}
func (c *Controller) Stop() {
close(c.quit)
c.wg.Wait()
c.MarkStopped()
}
func (c *Controller) PerformBackup() (string, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
backupData, err := c.core.Create(c.config.PrivateKey)
if err != nil {
return "", err
}
fileName, err := c.config.FileNameProvider.GetBackupFilename()
if err != nil {
return "", err
}
if err := os.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
return "", err
}
file, err := os.Create(fileName)
if err != nil {
return "", err
}
defer file.Close()
_, err = file.Write(backupData)
if err != nil {
return "", err
}
signal.SendLocalBackUpCompleted(CompletedEvent{
FileName: fileName,
})
return fileName, nil
}
func (c *Controller) LoadBackup(filePath string) error {
c.mutex.Lock()
defer c.mutex.Unlock()
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
backupData := make([]byte, fileInfo.Size())
_, err = file.Read(backupData)
if err != nil {
return err
}
return c.core.Restore(c.config.PrivateKey, backupData)
}
// isNil reports whether i is a nil pointer or a nil value held in an interface.
func isNil(i interface{}) bool {
if i == nil {
return true
}
switch reflect.TypeOf(i).Kind() {
case reflect.Ptr, reflect.Interface:
return reflect.ValueOf(i).IsNil()
}
return false
}