mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-13 11:13:50 +08:00
243 lines
6.0 KiB
Go
243 lines
6.0 KiB
Go
package streamd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/facebookincubator/go-belt/tool/logger"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
|
"github.com/xaionaro-go/streamctl/pkg/repository"
|
|
"github.com/xaionaro-go/streamctl/pkg/streamd/config"
|
|
"github.com/xaionaro-go/streamctl/pkg/xsync"
|
|
)
|
|
|
|
const gitRepoPanelConfigFileName = "streampanel.yaml"
|
|
const gitCommitterName = "streampanel"
|
|
const gitCommitterEmail = "streampanel@noemail.invalid"
|
|
|
|
func (d *StreamD) sendConfigViaGIT(
|
|
ctx context.Context,
|
|
) error {
|
|
logger.Debugf(ctx, "sendConfigViaGIT")
|
|
defer logger.Debugf(ctx, "/sendConfigViaGIT")
|
|
return xsync.DoA1R1(ctx, &d.GitSyncerMutex, d.sendConfigViaGITNoLock, ctx)
|
|
}
|
|
|
|
func (d *StreamD) sendConfigViaGITNoLock(
|
|
ctx context.Context,
|
|
) error {
|
|
var newBytes bytes.Buffer
|
|
latestSyncCommit := d.Config.GitRepo.LatestSyncCommit
|
|
d.Config.GitRepo.LatestSyncCommit = ""
|
|
_, err := d.Config.WriteTo(&newBytes)
|
|
d.Config.GitRepo.LatestSyncCommit = latestSyncCommit
|
|
if err != nil {
|
|
return fmt.Errorf("unable to serialize the panel data: %w", err)
|
|
}
|
|
|
|
hash, err := d.GitStorage.Write(ctx, newBytes.Bytes())
|
|
if errors.As(err, &repository.ErrNeedsRebase{}) {
|
|
d.gitSync(ctx)
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write the config to the git repo: %w", err)
|
|
}
|
|
if !hash.IsZero() {
|
|
d.setLastKnownGitCommitHash(hash)
|
|
if err := d.SaveConfigFunc(ctx, d.Config); err != nil {
|
|
return fmt.Errorf("unable to store the new commit hash in the config file: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *StreamD) gitSync(ctx context.Context) {
|
|
logger.Debugf(ctx, "gitSync")
|
|
defer logger.Debugf(ctx, "/gitSync")
|
|
xsync.DoA1(ctx, &d.GitSyncerMutex, d.gitSyncNoLock, ctx)
|
|
}
|
|
|
|
func (d *StreamD) gitSyncNoLock(ctx context.Context) {
|
|
logger.Debugf(ctx, "last_known_commit: %s", d.Config.GitRepo.LatestSyncCommit)
|
|
err := d.GitStorage.Pull(
|
|
ctx,
|
|
d.getLastKnownGitCommitHash(),
|
|
func(ctx context.Context, commitHash plumbing.Hash, b []byte) {
|
|
panelData := config.NewConfig()
|
|
_, err := panelData.Read(b)
|
|
if err != nil {
|
|
d.UI.DisplayError(fmt.Errorf("unable to read panel data: %w", err))
|
|
return
|
|
}
|
|
panelData.GitRepo.LatestSyncCommit = commitHash.String()
|
|
d.onConfigUpdateViaGIT(ctx, &panelData)
|
|
},
|
|
)
|
|
if err != nil {
|
|
d.UI.DisplayError(fmt.Errorf("unable to sync with the remote GIT repository: %w", err))
|
|
}
|
|
}
|
|
|
|
func (d *StreamD) setLastKnownGitCommitHash(newHash plumbing.Hash) {
|
|
d.Config.GitRepo.LatestSyncCommit = newHash.String()
|
|
}
|
|
|
|
func (d *StreamD) getLastKnownGitCommitHash() plumbing.Hash {
|
|
return plumbing.NewHash(d.Config.GitRepo.LatestSyncCommit)
|
|
}
|
|
|
|
func (d *StreamD) onConfigUpdateViaGIT(ctx context.Context, cfg *config.Config) {
|
|
d.Config = *cfg
|
|
err := d.SaveConfig(ctx)
|
|
if err != nil {
|
|
d.UI.DisplayError(fmt.Errorf("unable to save data: %w", err))
|
|
}
|
|
if d.GitInitialized {
|
|
d.UI.Restart(
|
|
ctx,
|
|
"Received an updated config from another device, please restart the application",
|
|
)
|
|
}
|
|
}
|
|
|
|
func (d *StreamD) initGitIfNeeded(ctx context.Context) {
|
|
logger.Debugf(ctx, "initGitIfNeeded: %#+v", d.Config.GitRepo)
|
|
if d.Config.GitRepo.Enable != nil && !*d.Config.GitRepo.Enable {
|
|
logger.Debugf(ctx, "git is disabled in the configuration")
|
|
return
|
|
}
|
|
|
|
newUserData := false
|
|
if d.Config.GitRepo.URL == "" || d.Config.GitRepo.PrivateKey == "" {
|
|
newUserData = true
|
|
}
|
|
|
|
for {
|
|
logger.Debugf(ctx, "attempting to configure a git storage")
|
|
if newUserData {
|
|
ok, url, privKey, err := d.UI.InputGitUserData(ctx)
|
|
if err != nil {
|
|
d.UI.DisplayError(fmt.Errorf("unable to input the git user data: %w", err))
|
|
continue
|
|
}
|
|
d.Config.GitRepo.Enable = ptr(ok)
|
|
d.Config.GitRepo.URL = url
|
|
d.Config.GitRepo.PrivateKey = string(privKey)
|
|
if err := d.SaveConfig(ctx); err != nil {
|
|
d.UI.DisplayError(err)
|
|
}
|
|
if !ok {
|
|
d.deinitGitStorage(ctx)
|
|
return
|
|
}
|
|
}
|
|
|
|
logger.Debugf(ctx, "newGitStorage")
|
|
gitStorage, err := repository.NewGit(
|
|
ctx,
|
|
d.Config.GitRepo.URL,
|
|
[]byte(d.Config.GitRepo.PrivateKey),
|
|
gitRepoPanelConfigFileName,
|
|
gitCommitterName,
|
|
gitCommitterEmail,
|
|
)
|
|
if err != nil {
|
|
d.UI.DisplayError(fmt.Errorf("unable to sync with the remote GIT repository: %w", err))
|
|
if newUserData {
|
|
continue
|
|
}
|
|
d.Config.GitRepo.Enable = ptr(false)
|
|
d.Config.GitRepo.URL = ""
|
|
d.Config.GitRepo.PrivateKey = ""
|
|
return
|
|
}
|
|
|
|
d.GitSyncerMutex.Do(ctx, func() {
|
|
d.deinitGitStorage(ctx)
|
|
if gitStorage == nil {
|
|
panic("gitStorage == nil")
|
|
}
|
|
d.GitStorage = gitStorage
|
|
})
|
|
break
|
|
}
|
|
|
|
if d.GitStorage == nil {
|
|
panic("d.GitStorage == nil")
|
|
}
|
|
d.startPeriodicGitSyncer(ctx)
|
|
d.GitInitialized = true
|
|
}
|
|
|
|
func (d *StreamD) deinitGitStorage(_ context.Context) {
|
|
if d.GitStorage != nil {
|
|
err := d.GitStorage.Close()
|
|
if err != nil {
|
|
d.UI.DisplayError(err)
|
|
}
|
|
d.GitStorage = nil
|
|
}
|
|
if d.CancelGitSyncer != nil {
|
|
d.CancelGitSyncer()
|
|
d.CancelGitSyncer = nil
|
|
}
|
|
}
|
|
|
|
func (d *StreamD) startPeriodicGitSyncer(ctx context.Context) {
|
|
logger.Debugf(ctx, "startPeriodicGitSyncer")
|
|
defer logger.Debugf(ctx, "/startPeriodicGitSyncer")
|
|
|
|
d.GitSyncerMutex.Do(ctx, func() {
|
|
if d.CancelGitSyncer != nil {
|
|
logger.Debugf(ctx, "git syncer is already started")
|
|
return
|
|
}
|
|
|
|
ctx, d.CancelGitSyncer = context.WithCancel(ctx)
|
|
})
|
|
|
|
d.gitSync(ctx)
|
|
observability.Go(ctx, func() {
|
|
err := d.sendConfigViaGIT(ctx)
|
|
if err != nil {
|
|
d.UI.DisplayError(
|
|
fmt.Errorf("unable to send the config to the remote git repository: %w", err),
|
|
)
|
|
}
|
|
|
|
ticker := time.NewTicker(time.Minute)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
d.GitSyncerMutex.Do(ctx, func() {
|
|
d.CancelGitSyncer = nil
|
|
})
|
|
return
|
|
case <-ticker.C:
|
|
}
|
|
|
|
d.gitSync(ctx)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (d *StreamD) OBSOLETE_GitRelogin(ctx context.Context) error {
|
|
alreadyLoggedIn := d.GitStorage != nil
|
|
oldCfg := d.Config.GitRepo
|
|
d.Config.GitRepo = config.GitRepoConfig{}
|
|
d.initGitIfNeeded(ctx)
|
|
if d.GitStorage == nil {
|
|
d.Config.GitRepo = oldCfg
|
|
if alreadyLoggedIn {
|
|
d.initGitIfNeeded(ctx)
|
|
}
|
|
}
|
|
return nil
|
|
}
|