Skip to content

Commit

Permalink
Merge pull request #14 from Contextualist/feat-logreaderr
Browse files Browse the repository at this point in the history
Logging file access error instead of aborting transfer
  • Loading branch information
Contextualist authored Nov 1, 2024
2 parents 5cacd8c + 818be1a commit 1011452
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 16 deletions.
9 changes: 6 additions & 3 deletions cmd/acp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ func transfer(ctx context.Context, conf *config.Config, filenames []string, logg
return
}

var status interface{ Next(tea.Model) }
var status interface {
Next(tea.Model) string
Logf(string, ...any)
}
if len(filenames) > 0 {
var s io.WriteCloser
strategyFinal := strategyConsensus(strategy, info.Strategy)
Expand All @@ -122,7 +125,7 @@ func transfer(ctx context.Context, conf *config.Config, filenames []string, logg
}
s, status = monitor(s)
logger.Debugf("sending...")
err = sendFiles(filenames, s)
err = sendFiles(filenames, s, status.Logf)
} else {
var s io.ReadCloser
strategyFinal := strategyConsensus(info.Strategy, strategy)
Expand All @@ -136,7 +139,7 @@ func transfer(ctx context.Context, conf *config.Config, filenames []string, logg
}

if !*debug {
status.Next(loggerModel)
exitStatement = status.Next(loggerModel) // save in-transit log, if any
}
checkErr(err)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/acp/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/klauspost/pgzip"
)

func sendFiles(filenames []string, to io.WriteCloser) (err error) {
func sendFiles(filenames []string, to io.WriteCloser, errorf func(string, ...any)) (err error) {
defer to.Close()
z := pgzip.NewWriter(to)
defer z.Close()
Expand All @@ -29,7 +29,7 @@ func sendFiles(filenames []string, to io.WriteCloser) (err error) {
if err != nil {
return err
}
err = tarWalk(fname, tz)
err = tarWalk(fname, tz, errorf)
if err != nil {
return fmt.Errorf("tar: %w", err)
}
Expand Down
17 changes: 11 additions & 6 deletions cmd/acp/tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,42 @@ import (
"runtime"
)

func tarWalk(source string, t *tar.Writer) error {
func tarWalk(source string, t *tar.Writer, errorf func(string, ...any)) error {
sourceInfo, err := os.Stat(source)
if err != nil {
return fmt.Errorf("%s: stat: %w", source, err)
}
sourceIsDir := sourceInfo.IsDir()
return filepath.Walk(source, func(fpath string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("traversing %s: %w", fpath, err)
errorf("traversing %s: %v", fpath, err)
return nil
}
if info == nil {
return fmt.Errorf("no file info for %s", fpath)
errorf("no file info for %s", fpath)
return nil
}
// build the name to be used within the archive
relName, err := nameInArchive(sourceIsDir, source, fpath)
if err != nil {
return err
errorf("name in archive: %v", err)
return nil
}
var linkTarget string
if info.Mode()&os.ModeSymlink != 0 {
linkTarget, err = os.Readlink(fpath)
if err != nil {
return fmt.Errorf("%s: readlink: %v", fpath, err)
errorf("%s: readlink: %v", fpath, err)
return nil
}
linkTarget = filepath.ToSlash(linkTarget)
}
var file io.ReadCloser
if info.Mode().IsRegular() {
file, err = os.Open(fpath)
if err != nil {
return fmt.Errorf("%s: opening: %w", fpath, err)
errorf("%s: opening: %v", fpath, err)
return nil
}
defer file.Close()
}
Expand Down
82 changes: 82 additions & 0 deletions pkg/tui/auxlogger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package tui

import (
"fmt"
"strings"

tea "github.com/charmbracelet/bubbletea"
)

type (
auxLoggerControl struct {
ch chan tea.Msg
chFinalLog chan string
maxRows int
}
auxLoggerQuitMsg struct{}
)

func newAuxLoggerControl(maxRows int) auxLoggerControl {
return auxLoggerControl{make(chan tea.Msg), make(chan string), maxRows}
}

// Logf logs a message
func (c auxLoggerControl) Logf(format string, a ...any) {
c.ch <- logMsg(fmt.Sprintf(format, a...))
}

// epilogue shuts down the logger and returns the current displayed log
func (c auxLoggerControl) epilogue() string {
c.ch <- auxLoggerQuitMsg{}
close(c.ch)
return <-c.chFinalLog
}

// auxLoggerModel is a model that displays a rolling log alongside other models.
// i.e. it is intended to be used as an embedded model
type auxLoggerModel struct {
logger auxLoggerControl
logs []string
logRepr string
}

func newAuxLoggerModel(c auxLoggerControl) tea.Model {
return auxLoggerModel{logger: c}
}

func (m auxLoggerModel) waitForLog() tea.Msg {
return <-m.logger.ch
}

func (m auxLoggerModel) Init() tea.Cmd {
return m.waitForLog
}

func (m auxLoggerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case logMsg:
m.logs = append(m.logs, string(msg))
if len(m.logs) > m.logger.maxRows {
m.logs = m.logs[1:]
}
m.logRepr = strings.Join(m.logs, "\n")
return m, m.waitForLog
case auxLoggerQuitMsg:
final := m.logRepr
if final != "" {
heading := "Skipped errors:"
if len(m.logs) == m.logger.maxRows {
heading += fmt.Sprintf(" (showing only last %d entries)", m.logger.maxRows)
}
final = heading + "\n" + final
}
m.logger.chFinalLog <- final
close(m.logger.chFinalLog)
return m, nil
}
return m, nil
}

func (m auxLoggerModel) View() string {
return m.logRepr
}
23 changes: 18 additions & 5 deletions pkg/tui/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ type (
// A StatusControl is the user handler for a StausModel
StatusControl[T io.Closer] struct {
*meteredReadWriteCloser[T]
auxLoggerControl
chNext chan tea.Msg
}
)

func NewStatusControl[T io.Closer]() *StatusControl[T] {
return &StatusControl[T]{
chNext: make(chan tea.Msg),
auxLoggerControl: newAuxLoggerControl(5),
chNext: make(chan tea.Msg),
}
}

Expand All @@ -31,10 +33,11 @@ func (c *StatusControl[T]) Monitor(stream T) T {
return any(c.meteredReadWriteCloser).(T)
}

// Next switches to the next BubbleTea Model and shut down current StatusModel
func (c *StatusControl[_]) Next(m tea.Model) {
// Next switches to the next BubbleTea Model, shuts down current StatusModel and passes the final log
func (c *StatusControl[_]) Next(m tea.Model) string {
c.chNext <- modelSwitchMsg{m}
close(c.chNext)
return c.auxLoggerControl.epilogue()
}

type meteredReadWriteCloser[T io.Closer] struct {
Expand Down Expand Up @@ -90,12 +93,14 @@ func (m *meteredReadWriteCloser[_]) Close() error {
// A StatusModel displays a updating stats of data transfer
type StatusModel[T io.Closer] struct {
spinner spinner.Model
logger tea.Model
status *StatusControl[T]
}

func NewStatusModel[T io.Closer](c *StatusControl[T]) tea.Model {
return StatusModel[T]{
spinner: spinner.New(spinner.WithSpinner(spinner.Points)),
logger: newAuxLoggerModel(c.auxLoggerControl),
status: c,
}
}
Expand All @@ -105,7 +110,7 @@ func (m StatusModel[_]) waitForNext() tea.Msg {
}

func (m StatusModel[_]) Init() tea.Cmd {
return tea.Batch(m.spinner.Tick, m.waitForNext)
return tea.Batch(m.spinner.Tick, m.logger.Init(), m.waitForNext)
}

func (m StatusModel[_]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Expand All @@ -114,6 +119,10 @@ func (m StatusModel[_]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
case logMsg, auxLoggerQuitMsg:
var cmd tea.Cmd
m.logger, cmd = m.logger.Update(msg)
return m, cmd
case modelSwitchMsg:
return modelSwitchTo(msg.model), nil
case tea.KeyMsg:
Expand All @@ -128,5 +137,9 @@ func (m StatusModel[_]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

func (m StatusModel[_]) View() string {
rate, total := m.status.rate.Load(), m.status.total.Load()
return fmt.Sprintf("%s %6s/s %6s", m.spinner.View(), humanize.Bytes(rate), humanize.Bytes(total))
spinnerView := fmt.Sprintf("%s %6s/s %6s", m.spinner.View(), humanize.Bytes(rate), humanize.Bytes(total))
if loggerView := m.logger.View(); loggerView != "" {
return spinnerView + "\n" + loggerView
}
return spinnerView
}

0 comments on commit 1011452

Please sign in to comment.