v0.9.0: add manifest checksums
This commit is contained in:
+51
-6
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
@@ -42,6 +44,7 @@ type exportOptions struct {
|
||||
verify bool
|
||||
format string
|
||||
sidecar string
|
||||
checksum string
|
||||
xmpPrivacy string
|
||||
xmpKeywords string
|
||||
xmpRating string
|
||||
@@ -228,6 +231,9 @@ COMMON EXPORT FLAGS
|
||||
--verify
|
||||
Run manifest/file verification after export or backup-all.
|
||||
|
||||
--checksum none|sha256
|
||||
Store optional file checksum metadata in the manifest. Default: none.
|
||||
|
||||
--sidecar none|xmp|json|xmp,json
|
||||
Write opt-in metadata sidecars next to each exported file. Default: none.
|
||||
If sidecar writing fails, the asset is counted as failed.
|
||||
@@ -789,7 +795,7 @@ func logEntry(event, level, assetID, album, filename, cloud string, size int64,
|
||||
}
|
||||
}
|
||||
|
||||
func addManifestEntry(m manifest.Manifest, pa pendingAsset, result photos.ExportResult) {
|
||||
func addManifestEntry(m manifest.Manifest, pa pendingAsset, result photos.ExportResult, checksum string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
@@ -802,7 +808,33 @@ func addManifestEntry(m manifest.Manifest, pa pendingAsset, result photos.Export
|
||||
if err != nil || strings.HasPrefix(relPath, "..") {
|
||||
relPath = result.Filename
|
||||
}
|
||||
m.AddEntry(manifest.NewEntry(pa.asset.ID, result.Filename, relPath, result.Size, result.Cloud))
|
||||
m.AddEntry(manifest.NewEntryWithChecksum(pa.asset.ID, result.Filename, relPath, result.Size, result.Cloud, checksum))
|
||||
}
|
||||
|
||||
func addManifestEntryForResult(m manifest.Manifest, pa pendingAsset, result photos.ExportResult, opts exportOptions) error {
|
||||
checksum := ""
|
||||
if opts.checksum == "sha256" && !result.Skipped {
|
||||
var err error
|
||||
checksum, err = fileSHA256(filepath.Join(pa.path, result.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
addManifestEntry(m, pa, result, checksum)
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileSHA256(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
type xmpSidecarData struct {
|
||||
@@ -1468,16 +1500,20 @@ func exportPendingSerial(pending []pendingAsset, targetSize, quality int, origin
|
||||
failed++
|
||||
appendFailure(pa.path, pa, exportErr)
|
||||
} else if isSkipped {
|
||||
addManifestEntry(m, pa, result)
|
||||
_ = addManifestEntryForResult(m, pa, result, opts)
|
||||
} else {
|
||||
if sidecarErr := writeSidecarIfNeeded(pa, result, originals, opts, geo, bridge); sidecarErr != nil {
|
||||
failed++
|
||||
exportErr = sidecarErr
|
||||
isErr = true
|
||||
appendFailure(pa.path, pa, sidecarErr)
|
||||
} else if checksumErr := addManifestEntryForResult(m, pa, result, opts); checksumErr != nil {
|
||||
failed++
|
||||
exportErr = checksumErr
|
||||
isErr = true
|
||||
appendFailure(pa.path, pa, checksumErr)
|
||||
} else {
|
||||
done++
|
||||
addManifestEntry(m, pa, result)
|
||||
}
|
||||
}
|
||||
avgSpeed := float64(0)
|
||||
@@ -1608,16 +1644,20 @@ func exportPendingParallel(pending []pendingAsset, targetSize, quality int, orig
|
||||
failed++
|
||||
appendFailure(entry.pa.path, entry.pa, entry.err)
|
||||
} else if isSkipped {
|
||||
addManifestEntry(m, entry.pa, entry.result)
|
||||
_ = addManifestEntryForResult(m, entry.pa, entry.result, opts)
|
||||
} else {
|
||||
if sidecarErr := writeSidecarIfNeeded(entry.pa, entry.result, originals, opts, geo, bridge); sidecarErr != nil {
|
||||
failed++
|
||||
entry.err = sidecarErr
|
||||
isErr = true
|
||||
appendFailure(entry.pa.path, entry.pa, sidecarErr)
|
||||
} else if checksumErr := addManifestEntryForResult(m, entry.pa, entry.result, opts); checksumErr != nil {
|
||||
failed++
|
||||
entry.err = checksumErr
|
||||
isErr = true
|
||||
appendFailure(entry.pa.path, entry.pa, checksumErr)
|
||||
} else {
|
||||
done++
|
||||
addManifestEntry(m, entry.pa, entry.result)
|
||||
}
|
||||
}
|
||||
avgSpeed := float64(0)
|
||||
@@ -1966,6 +2006,7 @@ func parseExportOptions(args []string, stderr io.Writer) (exportOptions, bool) {
|
||||
verify: hasFlag(args, "--verify"),
|
||||
format: flagValWithDefault(args, "--format", "jpeg"),
|
||||
sidecar: flagValWithDefault(args, "--sidecar", "none"),
|
||||
checksum: flagValWithDefault(args, "--checksum", "none"),
|
||||
xmpPrivacy: flagValWithDefault(args, "--xmp-privacy", "keep"),
|
||||
xmpKeywords: flagValWithDefault(args, "--xmp-keywords", "album-path"),
|
||||
xmpRating: flagValWithDefault(args, "--xmp-rating", "favorite"),
|
||||
@@ -1994,6 +2035,10 @@ func parseExportOptions(args []string, stderr io.Writer) (exportOptions, bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if opts.checksum != "none" && opts.checksum != "sha256" {
|
||||
fmt.Fprintf(stderr, "error: --checksum must be none or sha256, got %q\n", opts.checksum)
|
||||
return opts, false
|
||||
}
|
||||
if opts.xmpPrivacy != "keep" && opts.xmpPrivacy != "strip-location" && opts.xmpPrivacy != "strip-address" {
|
||||
fmt.Fprintf(stderr, "error: --xmp-privacy must be keep, strip-location, or strip-address, got %q\n", opts.xmpPrivacy)
|
||||
return opts, false
|
||||
|
||||
Reference in New Issue
Block a user