mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-19 13:34:34 -04:00
feat: save recovery data to disk and/or DAW project
This commit is contained in:
parent
97a1b2f766
commit
462faf5f4e
@ -57,16 +57,20 @@ type Tracker struct {
|
||||
|
||||
lastVolume tracker.Volume
|
||||
|
||||
wavFilePath string
|
||||
quitChannel chan struct{}
|
||||
quitWG sync.WaitGroup
|
||||
errorChannel chan error
|
||||
quitted bool
|
||||
synthService sointu.SynthService
|
||||
wavFilePath string
|
||||
quitChannel chan struct{}
|
||||
quitWG sync.WaitGroup
|
||||
errorChannel chan error
|
||||
quitted bool
|
||||
unmarshalRecoveryChannel chan []byte
|
||||
marshalRecoveryChannel chan (chan []byte)
|
||||
synthService sointu.SynthService
|
||||
|
||||
*tracker.Model
|
||||
*trackerModel
|
||||
}
|
||||
|
||||
type trackerModel = tracker.Model
|
||||
|
||||
func (t *Tracker) UnmarshalContent(bytes []byte) error {
|
||||
var units []sointu.Unit
|
||||
if errJSON := json.Unmarshal(bytes, &units); errJSON == nil {
|
||||
@ -143,7 +147,10 @@ func NewTracker(model *tracker.Model, synthService sointu.SynthService) *Tracker
|
||||
|
||||
errorChannel: make(chan error, 32),
|
||||
synthService: synthService,
|
||||
Model: model,
|
||||
trackerModel: model,
|
||||
|
||||
marshalRecoveryChannel: make(chan (chan []byte)),
|
||||
unmarshalRecoveryChannel: make(chan []byte),
|
||||
}
|
||||
t.Theme.Palette.Fg = primaryColor
|
||||
t.Theme.Palette.ContrastFg = black
|
||||
@ -213,6 +220,10 @@ mainloop:
|
||||
}
|
||||
case <-recoveryTicker.C:
|
||||
t.SaveRecovery()
|
||||
case retChn := <-t.marshalRecoveryChannel:
|
||||
retChn <- t.MarshalRecovery()
|
||||
case bytes := <-t.unmarshalRecoveryChannel:
|
||||
t.UnmarshalRecovery(bytes)
|
||||
}
|
||||
}
|
||||
w.Perform(system.ActionClose)
|
||||
@ -220,6 +231,18 @@ mainloop:
|
||||
t.quitWG.Done()
|
||||
}
|
||||
|
||||
// thread safe, executed in the GUI thread
|
||||
func (t *Tracker) SafeMarshalRecovery() []byte {
|
||||
retChn := make(chan []byte)
|
||||
t.marshalRecoveryChannel <- retChn
|
||||
return <-retChn
|
||||
}
|
||||
|
||||
// thread safe, executed in the GUI thread
|
||||
func (t *Tracker) SafeUnmarshalRecovery(data []byte) {
|
||||
t.unmarshalRecoveryChannel <- data
|
||||
}
|
||||
|
||||
func (t *Tracker) sendQuit() {
|
||||
select {
|
||||
case t.quitChannel <- struct{}{}:
|
||||
|
115
tracker/model.go
115
tracker/model.go
@ -13,7 +13,6 @@ import (
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/vm"
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Model implements the mutable state for the tracker program GUI.
|
||||
@ -26,25 +25,27 @@ import (
|
||||
type (
|
||||
// modelData is the part of the model that gets save to recovery file
|
||||
modelData struct {
|
||||
Song sointu.Song
|
||||
SelectionCorner SongPoint
|
||||
Cursor SongPoint
|
||||
LowNibble bool
|
||||
InstrIndex int
|
||||
UnitIndex int
|
||||
ParamIndex int
|
||||
Octave int
|
||||
NoteTracking bool
|
||||
UsedIDs map[int]bool
|
||||
MaxID int
|
||||
FilePath string
|
||||
ChangedSinceSave bool
|
||||
PatternUseCount [][]int
|
||||
Panic bool
|
||||
Playing bool
|
||||
Recording bool
|
||||
PlayPosition SongRow
|
||||
InstrEnlarged bool
|
||||
Song sointu.Song
|
||||
SelectionCorner SongPoint
|
||||
Cursor SongPoint
|
||||
LowNibble bool
|
||||
InstrIndex int
|
||||
UnitIndex int
|
||||
ParamIndex int
|
||||
Octave int
|
||||
NoteTracking bool
|
||||
UsedIDs map[int]bool
|
||||
MaxID int
|
||||
FilePath string
|
||||
ChangedSinceSave bool
|
||||
PatternUseCount [][]int
|
||||
Panic bool
|
||||
Playing bool
|
||||
Recording bool
|
||||
PlayPosition SongRow
|
||||
InstrEnlarged bool
|
||||
RecoveryFilePath string
|
||||
ChangedSinceRecovery bool
|
||||
|
||||
PrevUndoType string
|
||||
UndoSkipCounter int
|
||||
@ -116,63 +117,76 @@ const (
|
||||
const maxUndo = 64
|
||||
const RECOVERY_FILE = ".sointu_recovery"
|
||||
|
||||
func NewModel(modelMessages chan<- interface{}, playerMessages <-chan PlayerMessage) *Model {
|
||||
func NewModel(modelMessages chan<- interface{}, playerMessages <-chan PlayerMessage, recoveryFilePath string) *Model {
|
||||
ret := new(Model)
|
||||
ret.modelMessages = modelMessages
|
||||
ret.PlayerMessages = playerMessages
|
||||
ret.setSongNoUndo(defaultSong.Copy())
|
||||
ret.d.Octave = 4
|
||||
ret.d.RecoveryFilePath = recoveryFilePath
|
||||
if recoveryFilePath != "" {
|
||||
if bytes2, err := os.ReadFile(ret.d.RecoveryFilePath); err == nil {
|
||||
json.Unmarshal(bytes2, &ret.d)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func LoadRecovery(modelMessages chan<- interface{}, playerMessages <-chan PlayerMessage) (*Model, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
func (m *Model) MarshalRecovery() []byte {
|
||||
out, err := json.Marshal(m.d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get user home directory: %w", err)
|
||||
return nil
|
||||
}
|
||||
filePath := filepath.Join(homeDir, RECOVERY_FILE)
|
||||
b, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read recovery file: %w", err)
|
||||
if m.d.RecoveryFilePath != "" {
|
||||
os.Remove(m.d.RecoveryFilePath)
|
||||
}
|
||||
var ret Model
|
||||
err = json.Unmarshal(b, &ret.d)
|
||||
if err != nil {
|
||||
err = yaml.Unmarshal(b, &ret.d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal recovery file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ret.modelMessages = modelMessages
|
||||
ret.PlayerMessages = playerMessages
|
||||
ret.notifyPatchChange()
|
||||
ret.notifySamplesPerRowChange()
|
||||
ret.notifyScoreChange()
|
||||
return &ret, nil
|
||||
m.d.ChangedSinceRecovery = false
|
||||
return out
|
||||
}
|
||||
|
||||
func (m *Model) SaveRecovery() error {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get user home directory: %w", err)
|
||||
if !m.d.ChangedSinceRecovery {
|
||||
return nil
|
||||
}
|
||||
if m.d.RecoveryFilePath == "" {
|
||||
return errors.New("no backup file path")
|
||||
}
|
||||
out, err := json.Marshal(m.d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not marshal the model: %w", err)
|
||||
return fmt.Errorf("could not marshal recovery data: %w", err)
|
||||
}
|
||||
filePath := filepath.Join(homeDir, RECOVERY_FILE)
|
||||
file, err := os.Create(filePath)
|
||||
dir := filepath.Dir(m.d.RecoveryFilePath)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
file, err := os.Create(m.d.RecoveryFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open recovery file: %w", err)
|
||||
return fmt.Errorf("could not create recovery file: %w", err)
|
||||
}
|
||||
_, err = file.Write(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write recovery file: %w", err)
|
||||
}
|
||||
m.d.ChangedSinceRecovery = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) UnmarshalRecovery(bytes []byte) {
|
||||
err := json.Unmarshal(bytes, &m.d)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if m.d.RecoveryFilePath != "" { // check if there's a recovery file on disk and load it instead
|
||||
if bytes2, err := os.ReadFile(m.d.RecoveryFilePath); err == nil {
|
||||
json.Unmarshal(bytes2, &m.d)
|
||||
}
|
||||
}
|
||||
m.d.ChangedSinceRecovery = false
|
||||
m.notifyPatchChange()
|
||||
m.notifySamplesPerRowChange()
|
||||
m.notifyScoreChange()
|
||||
}
|
||||
|
||||
func (m *Model) FilePath() string {
|
||||
return m.d.FilePath
|
||||
}
|
||||
@ -1416,6 +1430,7 @@ func (m *Model) notifySamplesPerRowChange() {
|
||||
|
||||
func (m *Model) saveUndo(undoType string, undoSkipping int) {
|
||||
m.d.ChangedSinceSave = true
|
||||
m.d.ChangedSinceRecovery = true
|
||||
if m.d.PrevUndoType == undoType && m.d.UndoSkipCounter < undoSkipping {
|
||||
m.d.UndoSkipCounter++
|
||||
return
|
||||
|
Reference in New Issue
Block a user