diff --git a/cmdduplicate/duplicate.go b/cmdduplicate/duplicate.go
index c7c16210..da751ac7 100644
--- a/cmdduplicate/duplicate.go
+++ b/cmdduplicate/duplicate.go
@@ -10,6 +10,7 @@ import (
"immich-go/immich/logger"
"immich-go/ui"
"sort"
+ "strconv"
"strings"
"time"
)
@@ -18,7 +19,7 @@ type DuplicateCmd struct {
logger *logger.Logger
Immich *immich.ImmichClient // Immich client
- Confirm bool // Display actions but don't change anything
+ AssumeYes bool // Display actions but don't change anything
DateRange immich.DateRange // Set capture date range
}
@@ -30,14 +31,18 @@ type duplicateKey struct {
func NewDuplicateCmd(ctx context.Context, ic *immich.ImmichClient, logger *logger.Logger, args []string) (*DuplicateCmd, error) {
cmd := flag.NewFlagSet("duplicate", flag.ExitOnError)
validRange := immich.DateRange{}
- validRange.Set("1850-01-04,2100-01-01")
+ validRange.Set("1850-01-04,2030-01-01")
app := DuplicateCmd{
logger: logger,
Immich: ic,
DateRange: validRange,
}
- cmd.BoolVar(&app.Confirm, "confirm", true, "When true, actions must be confirmed")
+ cmd.BoolFunc("yes", "When true, assume Yes to all actions", func(s string) error {
+ var err error
+ app.AssumeYes, err = strconv.ParseBool(s)
+ return err
+ })
cmd.Var(&app.DateRange, "date", "Process only document having a capture date in that range.")
err := cmd.Parse(args)
return &app, err
@@ -111,7 +116,7 @@ func DuplicateCommand(ctx context.Context, ic *immich.ImmichClient, log *logger.
default:
app.logger.OK("There are %d copies of the asset %s, taken on %s ", len(duplicate[k]), k.Name, k.Date.Format(time.RFC3339))
l := duplicate[k]
- albums := []string{}
+ albums := []immich.AlbumSimplified{}
delete := []string{}
sort.Slice(l, func(i, j int) bool { return l[i].ExifInfo.FileSizeInByte < l[j].ExifInfo.FileSizeInByte })
for p, a := range duplicate[k] {
@@ -123,27 +128,30 @@ func DuplicateCommand(ctx context.Context, ic *immich.ImmichClient, log *logger.
log.Error("Can't get asset's albums: %s", err.Error())
} else {
for _, al := range r {
- albums = append(albums, al.ID)
+ albums = append(albums, al)
}
}
} else {
log.OK(" %s %dx%d, %s, %s to be kept", a.OriginalFileName, a.ExifInfo.ExifImageWidth, a.ExifInfo.ExifImageHeight, ui.FormatBytes(a.ExifInfo.FileSizeInByte), a.OriginalPath)
- yes := !app.Confirm
- if app.Confirm {
- r := ui.ConfirmYesNo("Proceed?", "n")
+ yes := app.AssumeYes
+ if !app.AssumeYes {
+ r, err := ui.ConfirmYesNo(ctx, "Proceed?", "n")
+ if err != nil {
+ return err
+ }
if r == "y" {
yes = true
}
}
if yes {
- log.OK("Asset removed")
+ log.OK(" Asset removed")
_, err = app.Immich.DeleteAssets(ctx, delete)
if err != nil {
log.Error("Can't delete asset: %s", err.Error())
}
for _, al := range albums {
- log.OK("Update the album %s with the best copy", al)
- _, err = app.Immich.AddAssetToAlbum(ctx, al, []string{a.ID})
+ log.OK(" Update the album %s with the best copy", al.AlbumName)
+ _, err = app.Immich.AddAssetToAlbum(ctx, al.ID, []string{a.ID})
if err != nil {
log.Error("Can't delete asset: %s", err.Error())
}
diff --git a/readme.md b/readme.md
index a99621db..bc5ea7f0 100644
--- a/readme.md
+++ b/readme.md
@@ -107,7 +107,8 @@ Use this command for analyzing the content of your `immich` server to find any f
Before deleting the inferior copies, the system get all albums they belong to, and add the superior copy to them.
### Switches and options:
-`-dry-run` Preview all actions as they would be done (default: TRUE).
+`-yes` Assume Yes to all questions (default: FALSE).
+`-date` Check only assets have a date of capture in the given range. (default: 1850-01-04,2030-01-01)
### Example Usage: clean the `immich` server after having merged a google photo archive and original files
@@ -245,6 +246,10 @@ Additionally, deploying a Node.js program on user machines presents challenges.
# Release notes
+- Improvement of duplicate command
+ - `-yes` option to assume Yes to all questions
+ - `-date` to limit the check to a a given date range
+
### Release 0.2.2
- improvement of date of capture when there isn't any exif data in the file
diff --git a/ui/ask.go b/ui/ask.go
index 33b4e43a..a37030c2 100644
--- a/ui/ask.go
+++ b/ui/ask.go
@@ -4,12 +4,14 @@ import (
"bufio"
"context"
"fmt"
- "io"
"os"
"strings"
)
-func ConfirmYesNo(prompt string, defaultAnswer string) string {
+func ConfirmYesNo(ctx context.Context, prompt string, defaultAnswer string) (string, error) {
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
reader := bufio.NewReader(os.Stdin)
defaultAnswer = strings.ToLower(defaultAnswer)
other := "n"
@@ -17,62 +19,32 @@ func ConfirmYesNo(prompt string, defaultAnswer string) string {
other = "y"
}
- for {
- fmt.Printf("%s [%s]/%s: ", prompt, defaultAnswer, other)
- userInput, _ := reader.ReadString('\n')
- userInput = strings.ToLower(strings.TrimSpace(userInput))
- switch userInput {
- case "":
- return defaultAnswer
- case "y", "n":
- return userInput
- }
- }
-}
-
-type CancelableReader struct {
- ctx context.Context
- data chan []byte
- err error
- r io.Reader
-}
+ runeChan := make(chan (rune))
-func (c *CancelableReader) begin() {
- buf := make([]byte, 1024)
- for {
- n, err := c.r.Read(buf)
- if n > 0 {
- tmp := make([]byte, n)
- copy(tmp, buf[:n])
- c.data <- tmp
+ go func() {
+ for {
+ r, _, _ := reader.ReadRune()
+ select {
+ case runeChan <- r:
+ case <-ctx.Done():
+ return
+ }
}
- if err != nil {
- c.err = err
- close(c.data)
- return
- }
- }
-}
+ }()
-func (c *CancelableReader) Read(p []byte) (int, error) {
- select {
- case <-c.ctx.Done():
- return 0, c.ctx.Err()
- case d, ok := <-c.data:
- if !ok {
- return 0, c.err
+ for {
+ fmt.Printf("%s [%s]/%s: ", prompt, defaultAnswer, other)
+ select {
+ case r := <-runeChan:
+ userInput := strings.ToLower(string(r))
+ switch userInput {
+ case "":
+ return defaultAnswer, nil
+ case "y", "n":
+ return userInput, nil
+ }
+ case <-ctx.Done():
+ return "", ctx.Err()
}
- copy(p, d)
- return len(d), nil
- }
-}
-
-func New(ctx context.Context, r io.Reader) *CancelableReader {
- c := &CancelableReader{
- r: r,
- ctx: ctx,
- data: make(chan []byte),
}
- go c.begin()
- return c
}