v0.2.1: add status messages, fix parallel export progress
- mustAuth: print 'requesting photo library access...' / 'access granted' - cmdBackupAll: print 'loading photo library tree...' / 'found N albums' - cmdExport: print 'loading assets...' / 'exporting N assets (mode)...' - backupTree: print 'album: Name (N assets)' per album - exportAssetsParallel: slot-based progress shows each asset as it completes instead of waiting for all to finish - Fix data race: use jobs channel instead of shared range iteration
This commit is contained in:
+40
-35
@@ -67,10 +67,12 @@ Flags:
|
||||
}
|
||||
|
||||
func mustAuth(stderr io.Writer, bridge photos.Bridge) int {
|
||||
fmt.Fprintln(stderr, "requesting photo library access...")
|
||||
if err := bridge.RequestAccess(); err != nil {
|
||||
fmt.Fprintf(stderr, "error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(stderr, "access granted")
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -78,6 +80,7 @@ func cmdAlbums(stdout, stderr io.Writer, bridge photos.Bridge) int {
|
||||
if rc := mustAuth(stderr, bridge); rc != 0 {
|
||||
return rc
|
||||
}
|
||||
fmt.Fprintln(stderr, "loading albums...")
|
||||
albums, err := bridge.ListAlbums()
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "error: %v\n", err)
|
||||
@@ -179,12 +182,14 @@ func cmdExport(args []string, stdout, stderr io.Writer, bridge photos.Bridge) in
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "loading assets for album %s...\n", albumID)
|
||||
assets, total, err := bridge.ListAssets(resolved)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "exporting %d assets (%s) to %s...\n", total, exportMode(originals), outDir)
|
||||
exported, failed := exportAssets(assets, outDir, size, originals, total, stderr, bridge, "")
|
||||
|
||||
if exported == 0 && failed > 0 {
|
||||
@@ -226,12 +231,14 @@ func cmdBackupAll(args []string, stdout, stderr io.Writer, bridge photos.Bridge)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(stderr, "loading photo library tree...")
|
||||
nodes, err := bridge.ListTree()
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
albumCount := countAlbums(nodes)
|
||||
fmt.Fprintf(stderr, "found %d albums, exporting to %s...\n", albumCount, outDir)
|
||||
|
||||
totalAssets, _, failed, err := backupTree(nodes, outDir, size, originals, stderr, bridge)
|
||||
if err != nil {
|
||||
@@ -273,6 +280,7 @@ func backupTree(nodes []photos.CollectionNode, outDir string, targetSize int, or
|
||||
fmt.Fprintf(stderr, "\n skipped album %s: %v\n", node.Name, err)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(stderr, "\nalbum: %s (%d assets)\n", node.Name, assetTotal)
|
||||
total += assetTotal
|
||||
n, f := exportAssets(assets, path, targetSize, originals, total, stderr, bridge, path+"/")
|
||||
exported += n
|
||||
@@ -282,17 +290,6 @@ func backupTree(nodes []photos.CollectionNode, outDir string, targetSize int, or
|
||||
return exported, total, failed, nil
|
||||
}
|
||||
|
||||
type exportJob struct {
|
||||
asset photos.Asset
|
||||
index int
|
||||
}
|
||||
|
||||
type exportResult struct {
|
||||
index int
|
||||
result photos.ExportResult
|
||||
err error
|
||||
}
|
||||
|
||||
func exportAssets(assets []photos.Asset, outDir string, targetSize int, originals bool, total int, stderr io.Writer, bridge photos.Bridge, dirPrefix string) (int, int) {
|
||||
if len(assets) == 0 {
|
||||
return 0, 0
|
||||
@@ -322,50 +319,51 @@ func exportAssetsSerial(assets []photos.Asset, outDir string, targetSize int, or
|
||||
}
|
||||
|
||||
func exportAssetsParallel(assets []photos.Asset, outDir string, targetSize int, originals bool, total int, stderr io.Writer, bridge photos.Bridge, workers int, dirPrefix string) (int, int) {
|
||||
jobs := make(chan exportJob, len(assets))
|
||||
results := make(chan exportResult, len(assets))
|
||||
type slot struct {
|
||||
result photos.ExportResult
|
||||
err error
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
slots := make([]slot, len(assets))
|
||||
for i := range slots {
|
||||
slots[i].done = make(chan struct{})
|
||||
}
|
||||
|
||||
jobs := make(chan int, len(assets))
|
||||
var wg sync.WaitGroup
|
||||
for w := 0; w < workers; w++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for job := range jobs {
|
||||
result, exportErr := exportOne(bridge, job.asset, outDir, targetSize, originals, job.index)
|
||||
results <- exportResult{index: job.index, result: result, err: exportErr}
|
||||
for i := range jobs {
|
||||
result, exportErr := exportOne(bridge, assets[i], outDir, targetSize, originals, i)
|
||||
slots[i].result = result
|
||||
slots[i].err = exportErr
|
||||
close(slots[i].done)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
for i, a := range assets {
|
||||
jobs <- exportJob{asset: a, index: i}
|
||||
}
|
||||
close(jobs)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
ordered := make([]exportResult, len(assets))
|
||||
for r := range results {
|
||||
ordered[r.index] = r
|
||||
for i := range assets {
|
||||
jobs <- i
|
||||
}
|
||||
close(jobs)
|
||||
|
||||
exported := 0
|
||||
failed := 0
|
||||
for i, a := range assets {
|
||||
r := ordered[i]
|
||||
progressBar(stderr, exported+failed+1, total, dirPrefix+r.result.Filename, r.result.Size, r.result.Cloud)
|
||||
if r.err != nil {
|
||||
fmt.Fprintf(stderr, "\n failed: %s: %v\n", a.Filename, r.err)
|
||||
<-slots[i].done
|
||||
s := slots[i]
|
||||
progressBar(stderr, exported+failed+1, total, dirPrefix+s.result.Filename, s.result.Size, s.result.Cloud)
|
||||
if s.err != nil {
|
||||
fmt.Fprintf(stderr, "\n failed: %s: %v\n", a.Filename, s.err)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
exported++
|
||||
}
|
||||
wg.Wait()
|
||||
return exported, failed
|
||||
}
|
||||
|
||||
@@ -452,3 +450,10 @@ func formatSize(bytes int64) string {
|
||||
}
|
||||
return fmt.Sprintf("%.1f KB", float64(bytes)/float64(kb))
|
||||
}
|
||||
|
||||
func exportMode(originals bool) string {
|
||||
if originals {
|
||||
return "originals"
|
||||
}
|
||||
return "previews"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user