2021-09-12 14:15:40 +00:00
package backup
2018-04-22 09:57:20 +00:00
import (
"fmt"
"os"
"sort"
"time"
"github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/restic"
2021-09-12 14:15:40 +00:00
"github.com/restic/restic/internal/ui"
2018-04-22 09:57:20 +00:00
"github.com/restic/restic/internal/ui/termstatus"
)
2021-09-12 14:15:40 +00:00
// TextProgress reports progress for the `backup` command.
type TextProgress struct {
* ui . Message
* ui . StdioWrapper
2018-04-22 09:57:20 +00:00
2021-01-26 19:52:00 +00:00
term * termstatus . Terminal
2018-04-22 09:57:20 +00:00
}
2021-08-18 11:07:53 +00:00
// assert that Backup implements the ProgressPrinter interface
2021-09-12 14:15:40 +00:00
var _ ProgressPrinter = & TextProgress { }
2021-08-18 11:07:53 +00:00
2021-09-12 14:15:40 +00:00
// NewTextProgress returns a new backup progress reporter.
func NewTextProgress ( term * termstatus . Terminal , verbosity uint ) * TextProgress {
return & TextProgress {
Message : ui . NewMessage ( term , verbosity ) ,
StdioWrapper : ui . NewStdioWrapper ( term ) ,
2018-04-22 09:57:20 +00:00
term : term ,
}
}
2021-09-12 14:15:40 +00:00
// Update updates the status lines.
func ( b * TextProgress ) Update ( total , processed Counter , errors uint , currentFiles map [ string ] struct { } , start time . Time , secs uint64 ) {
2018-04-22 09:57:20 +00:00
var status string
if total . Files == 0 && total . Dirs == 0 {
// no total count available yet
status = fmt . Sprintf ( "[%s] %v files, %s, %d errors" ,
2021-01-26 19:52:00 +00:00
formatDuration ( time . Since ( start ) ) ,
2018-04-22 09:57:20 +00:00
processed . Files , formatBytes ( processed . Bytes ) , errors ,
)
} else {
2018-05-06 18:20:10 +00:00
var eta , percent string
2018-04-22 09:57:20 +00:00
2018-04-29 12:40:49 +00:00
if secs > 0 && processed . Bytes < total . Bytes {
2018-04-22 09:57:20 +00:00
eta = fmt . Sprintf ( " ETA %s" , formatSeconds ( secs ) )
2018-05-06 18:20:10 +00:00
percent = formatPercent ( processed . Bytes , total . Bytes )
percent += " "
2018-04-22 09:57:20 +00:00
}
// include totals
2018-05-06 18:20:10 +00:00
status = fmt . Sprintf ( "[%s] %s%v files %s, total %v files %v, %d errors%s" ,
2021-01-26 19:52:00 +00:00
formatDuration ( time . Since ( start ) ) ,
2018-05-06 18:20:10 +00:00
percent ,
2018-04-22 09:57:20 +00:00
processed . Files ,
formatBytes ( processed . Bytes ) ,
total . Files ,
formatBytes ( total . Bytes ) ,
errors ,
eta ,
)
}
lines := make ( [ ] string , 0 , len ( currentFiles ) + 1 )
for filename := range currentFiles {
lines = append ( lines , filename )
}
2019-06-30 20:20:32 +00:00
sort . Strings ( lines )
2018-04-22 09:57:20 +00:00
lines = append ( [ ] string { status } , lines ... )
b . term . SetStatus ( lines )
}
// ScannerError is the error callback function for the scanner, it prints the
// error in verbose mode and returns nil.
2021-09-12 14:15:40 +00:00
func ( b * TextProgress ) ScannerError ( item string , fi os . FileInfo , err error ) error {
2018-04-22 09:57:20 +00:00
b . V ( "scan: %v\n" , err )
return nil
}
// Error is the error callback function for the archiver, it prints the error and returns nil.
2021-09-12 14:15:40 +00:00
func ( b * TextProgress ) Error ( item string , fi os . FileInfo , err error ) error {
2018-04-22 09:57:20 +00:00
b . E ( "error: %v\n" , err )
return nil
}
func formatPercent ( numerator uint64 , denominator uint64 ) string {
if denominator == 0 {
return ""
}
percent := 100.0 * float64 ( numerator ) / float64 ( denominator )
if percent > 100 {
percent = 100
}
return fmt . Sprintf ( "%3.2f%%" , percent )
}
func formatSeconds ( sec uint64 ) string {
hours := sec / 3600
sec -= hours * 3600
min := sec / 60
sec -= min * 60
if hours > 0 {
return fmt . Sprintf ( "%d:%02d:%02d" , hours , min , sec )
}
return fmt . Sprintf ( "%d:%02d" , min , sec )
}
func formatDuration ( d time . Duration ) string {
sec := uint64 ( d / time . Second )
return formatSeconds ( sec )
}
func formatBytes ( c uint64 ) string {
b := float64 ( c )
switch {
case c > 1 << 40 :
return fmt . Sprintf ( "%.3f TiB" , b / ( 1 << 40 ) )
case c > 1 << 30 :
return fmt . Sprintf ( "%.3f GiB" , b / ( 1 << 30 ) )
case c > 1 << 20 :
return fmt . Sprintf ( "%.3f MiB" , b / ( 1 << 20 ) )
case c > 1 << 10 :
return fmt . Sprintf ( "%.3f KiB" , b / ( 1 << 10 ) )
default :
return fmt . Sprintf ( "%d B" , c )
}
}
2018-08-11 05:34:37 +00:00
// CompleteItem is the status callback function for the archiver when a
2018-04-22 09:57:20 +00:00
// file/dir has been saved successfully.
2021-09-12 14:15:40 +00:00
func ( b * TextProgress ) CompleteItem ( messageType , item string , previous , current * restic . Node , s archiver . ItemStats , d time . Duration ) {
2021-01-26 19:52:00 +00:00
switch messageType {
case "dir new" :
2022-05-01 12:41:36 +00:00
b . VV ( "new %v, saved in %.3fs (%v added, %v stored, %v metadata)" , item , d . Seconds ( ) , formatBytes ( s . DataSize ) , formatBytes ( s . DataSizeInRepo ) , formatBytes ( s . TreeSizeInRepo ) )
2021-01-26 19:52:00 +00:00
case "dir unchanged" :
b . VV ( "unchanged %v" , item )
case "dir modified" :
2022-05-01 12:41:36 +00:00
b . VV ( "modified %v, saved in %.3fs (%v added, %v stored, %v metadata)" , item , d . Seconds ( ) , formatBytes ( s . DataSize ) , formatBytes ( s . DataSizeInRepo ) , formatBytes ( s . TreeSizeInRepo ) )
2021-01-26 19:52:00 +00:00
case "file new" :
b . VV ( "new %v, saved in %.3fs (%v added)" , item , d . Seconds ( ) , formatBytes ( s . DataSize ) )
case "file unchanged" :
b . VV ( "unchanged %v" , item )
case "file modified" :
2022-05-01 12:41:36 +00:00
b . VV ( "modified %v, saved in %.3fs (%v added, %v stored)" , item , d . Seconds ( ) , formatBytes ( s . DataSize ) , formatBytes ( s . DataSizeInRepo ) )
2018-04-22 09:57:20 +00:00
}
}
// ReportTotal sets the total stats up to now
2021-09-12 14:15:40 +00:00
func ( b * TextProgress ) ReportTotal ( item string , start time . Time , s archiver . ScanStats ) {
2021-08-17 23:25:34 +00:00
b . V ( "scan finished in %.3fs: %v files, %s" ,
time . Since ( start ) . Seconds ( ) ,
s . Files , formatBytes ( s . Bytes ) ,
)
2018-04-22 09:57:20 +00:00
}
2021-01-26 19:52:00 +00:00
// Reset status
2021-09-12 14:15:40 +00:00
func ( b * TextProgress ) Reset ( ) {
2021-01-26 19:52:00 +00:00
if b . term . CanUpdateStatus ( ) {
b . term . SetStatus ( [ ] string { "" } )
}
}
2018-04-29 13:01:21 +00:00
2021-01-26 19:52:00 +00:00
// Finish prints the finishing messages.
2021-09-12 14:15:40 +00:00
func ( b * TextProgress ) Finish ( snapshotID restic . ID , start time . Time , summary * Summary , dryRun bool ) {
2018-05-01 20:02:48 +00:00
b . P ( "\n" )
2021-01-26 19:52:00 +00:00
b . P ( "Files: %5d new, %5d changed, %5d unmodified\n" , summary . Files . New , summary . Files . Changed , summary . Files . Unchanged )
b . P ( "Dirs: %5d new, %5d changed, %5d unmodified\n" , summary . Dirs . New , summary . Dirs . Changed , summary . Dirs . Unchanged )
b . V ( "Data Blobs: %5d new\n" , summary . ItemStats . DataBlobs )
b . V ( "Tree Blobs: %5d new\n" , summary . ItemStats . TreeBlobs )
backup: add --dry-run/-n flag to show what would happen.
This can be used to check how large a backup is or validate exclusions.
It does not actually write any data to the underlying backend. This is
implemented as a simple overlay backend that accepts writes without
forwarding them, passes through reads, and generally does the minimal
necessary to pretend that progress is actually happening.
Fixes #1542
Example usage:
$ restic -vv --dry-run . | grep add
new /changelog/unreleased/issue-1542, saved in 0.000s (350 B added)
modified /cmd/restic/cmd_backup.go, saved in 0.000s (16.543 KiB added)
modified /cmd/restic/global.go, saved in 0.000s (0 B added)
new /internal/backend/dry/dry_backend_test.go, saved in 0.000s (3.866 KiB added)
new /internal/backend/dry/dry_backend.go, saved in 0.000s (3.744 KiB added)
modified /internal/backend/test/tests.go, saved in 0.000s (0 B added)
modified /internal/repository/repository.go, saved in 0.000s (20.707 KiB added)
modified /internal/ui/backup.go, saved in 0.000s (9.110 KiB added)
modified /internal/ui/jsonstatus/status.go, saved in 0.001s (11.055 KiB added)
modified /restic, saved in 0.131s (25.542 MiB added)
Would add to the repo: 25.892 MiB
2019-06-13 03:39:13 +00:00
verb := "Added"
2021-08-18 11:03:08 +00:00
if dryRun {
backup: add --dry-run/-n flag to show what would happen.
This can be used to check how large a backup is or validate exclusions.
It does not actually write any data to the underlying backend. This is
implemented as a simple overlay backend that accepts writes without
forwarding them, passes through reads, and generally does the minimal
necessary to pretend that progress is actually happening.
Fixes #1542
Example usage:
$ restic -vv --dry-run . | grep add
new /changelog/unreleased/issue-1542, saved in 0.000s (350 B added)
modified /cmd/restic/cmd_backup.go, saved in 0.000s (16.543 KiB added)
modified /cmd/restic/global.go, saved in 0.000s (0 B added)
new /internal/backend/dry/dry_backend_test.go, saved in 0.000s (3.866 KiB added)
new /internal/backend/dry/dry_backend.go, saved in 0.000s (3.744 KiB added)
modified /internal/backend/test/tests.go, saved in 0.000s (0 B added)
modified /internal/repository/repository.go, saved in 0.000s (20.707 KiB added)
modified /internal/ui/backup.go, saved in 0.000s (9.110 KiB added)
modified /internal/ui/jsonstatus/status.go, saved in 0.001s (11.055 KiB added)
modified /restic, saved in 0.131s (25.542 MiB added)
Would add to the repo: 25.892 MiB
2019-06-13 03:39:13 +00:00
verb = "Would add"
}
2022-05-01 12:41:36 +00:00
b . P ( "%s to the repo: %-5s (%-5s stored)\n" , verb , formatBytes ( summary . ItemStats . DataSize + summary . ItemStats . TreeSize ) , formatBytes ( summary . ItemStats . DataSizeInRepo + summary . ItemStats . TreeSizeInRepo ) )
2018-05-01 20:02:48 +00:00
b . P ( "\n" )
b . P ( "processed %v files, %v in %s" ,
2021-01-26 19:52:00 +00:00
summary . Files . New + summary . Files . Changed + summary . Files . Unchanged ,
formatBytes ( summary . ProcessedBytes ) ,
formatDuration ( time . Since ( start ) ) ,
2018-05-01 20:02:48 +00:00
)
2018-04-22 09:57:20 +00:00
}