Skip to content

Commit

Permalink
duplicate: better handling of ^C
Browse files Browse the repository at this point in the history
  • Loading branch information
laaqxdze1k committed Sep 16, 2023
1 parent bf59715 commit 17f3b6b
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 67 deletions.
30 changes: 19 additions & 11 deletions cmdduplicate/duplicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"immich-go/immich/logger"
"immich-go/ui"
"sort"
"strconv"
"strings"
"time"
)
Expand All @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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] {
Expand All @@ -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())
}
Expand Down
7 changes: 6 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).<br>
`-yes` Assume Yes to all questions (default: FALSE).<br>
`-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
Expand Down Expand Up @@ -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
Expand Down
82 changes: 27 additions & 55 deletions ui/ask.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,75 +4,47 @@ 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"
if defaultAnswer == "n" {
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
}

0 comments on commit 17f3b6b

Please sign in to comment.