Skip to content

Commit

Permalink
Always try to keep the downloaded title in the same position
Browse files Browse the repository at this point in the history
Also got the terminal width in order to not truncate when it's not
necessary

refs #24
  • Loading branch information
elboletaire committed Jan 2, 2025
1 parent 2ad147a commit a37562e
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 30 deletions.
83 changes: 71 additions & 12 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"strings"
"sync"
"syscall"

"github.com/elboletaire/manga-downloader/downloader"
"github.com/elboletaire/manga-downloader/grabber"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/spf13/cobra"
"github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor"
"golang.org/x/term"

cc "github.com/ivanpirog/coloredcobra"
)
Expand Down Expand Up @@ -78,7 +80,7 @@ func Run(cmd *cobra.Command, args []string) {
s.InitFlags(cmd)

// fetch series title
_, err := s.FetchTitle()
title, err := s.FetchTitle()
cerr(err, "Error fetching title: ")

// fetch all chapters
Expand Down Expand Up @@ -138,6 +140,10 @@ func Run(cmd *cobra.Command, args []string) {

green, blue := color.New(color.FgGreen), color.New(color.FgBlue)

// Get terminal width for title truncation
termWidth := getTerminalWidth()
mangaLen, chapterLen := calculateTitleLengths(termWidth)

for _, chap := range chapters {
g <- struct{}{}
wg.Add(1)
Expand All @@ -152,12 +158,27 @@ func Run(cmd *cobra.Command, args []string) {
return
}

// generate the filename for the chapter
filename, err := packer.NewFilenameFromTemplate(s.GetFilenameTemplate(), packer.NewChapterFileTemplateParts(title, chapter))
if err != nil {
color.Red("- error creating filename for chapter %s: %s", chapter.GetTitle(), err.Error())
<-g
return
}
filename += ".cbz"

// chapter download progress bar
title := fmt.Sprintf("%s:", truncateString(chap.GetTitle(), 30))
barTitle := fmt.Sprintf("%s - %s:", truncateString(title, mangaLen), truncateString(chap.GetTitle(), chapterLen))
status := "downloading"
cdbar := p.AddBar(chapter.PagesCount,
mpb.PrependDecorators(
decor.Name(title, decor.WCSyncWidthR),
decor.Meta(decor.Name("downloading", decor.WC{C: decor.DextraSpace}), toMetaFunc(blue)),
decor.Name(barTitle, decor.WCSyncWidthR),
decor.Any(func(s decor.Statistics) string {
if strings.HasPrefix(status, "error:") {
return color.New(color.FgRed).Sprint(status)
}
return blue.Sprint(status)
}, decor.WC{C: decor.DextraSpace}),
decor.CountersNoUnit("%d / %d", decor.WC{C: decor.DextraSpace}),
),
mpb.AppendDecorators(
Expand All @@ -172,11 +193,11 @@ func Run(cmd *cobra.Command, args []string) {
mpb.BarQueueAfter(cdbar),
mpb.BarFillerClearOnComplete(),
mpb.PrependDecorators(
decor.Name(title, decor.WCSyncWidthR),
decor.OnComplete(decor.Name(barTitle, decor.WC{}), ""),
decor.OnCompleteMeta(
decor.OnComplete(
decor.Meta(decor.Name("archiving", decor.WC{C: decor.DextraSpace}), toMetaFunc(blue)),
"done!",
filename+" saved",
),
toMetaFunc(green),
),
Expand All @@ -187,8 +208,13 @@ func Run(cmd *cobra.Command, args []string) {
),
)

files, err := downloader.FetchChapter(s, chapter, func(page, _ int) {
cdbar.IncrBy(page)
files, err := downloader.FetchChapter(s, chapter, func(page int, progress int, err error) {
if err != nil {
status = "error: " + err.Error()
cdbar.SetCurrent(int64(progress))
} else {
cdbar.IncrBy(page)
}
})
if err != nil {
color.Red("- error downloading chapter %s: %s", chapter.GetTitle(), err.Error())
Expand All @@ -205,10 +231,7 @@ func Run(cmd *cobra.Command, args []string) {
_, err := packer.PackSingle(settings.OutputDir, s, d, func(page, _ int) {
scbar.IncrBy(page)
})
// filename, err := packer.PackSingle(settings.OutputDir, s, d)
if err == nil {
// fmt.Printf("- %s %s\n", color.GreenString("saved file"), color.HiBlackString(filename))
} else {
if err != nil {
color.Red(err.Error())
}
} else {
Expand Down Expand Up @@ -359,9 +382,45 @@ func getUrlArg(args []string) string {
return args[1]
}

// getTerminalWidth returns the current terminal width or a default value if it can't be determined
func getTerminalWidth() int {
width, _, err := term.GetSize(int(syscall.Stdin))
if err != nil {
return 80 // default terminal width
}
return width
}

// calculateTitleLengths calculates how much space to allocate for manga and chapter titles
func calculateTitleLengths(termWidth int) (mangaLen, chapterLen int) {
// Reserve space for other elements in the progress bar:
// - status (e.g. "downloading", "archiving", etc.): ~15 chars
// - progress counter (e.g. "123/456"): ~10 chars
// - percentage: ~5 chars
// - decorators (spaces, colons, etc.): ~5 chars
reservedSpace := 35
availableSpace := termWidth - reservedSpace

// Allocate 60% to manga title and 40% to chapter title if there's enough space
if availableSpace > 20 {
mangaLen = (availableSpace * 60) / 100
chapterLen = (availableSpace * 40) / 100
} else {
// If space is very limited, use minimal lengths
mangaLen = 10
chapterLen = 10
}

return
}

// truncateString truncates the input string at a specified maximum length
// without cutting words. It finds the last space within the limit and truncates there.
func truncateString(input string, maxLength int) string {
if maxLength <= 0 {
return ""
}

if len(input) <= maxLength {
return input
}
Expand Down
44 changes: 27 additions & 17 deletions downloader/fetch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package downloader

import (
"fmt"
"io"
"sort"
"sync"
Expand All @@ -15,12 +16,16 @@ type File struct {
Page uint
}

// ProgressCallback is a function type for progress updates with optional error
type ProgressCallback func(page, progress int, err error)

// FetchChapter downloads all the pages of a chapter
func FetchChapter(site grabber.Site, chapter *grabber.Chapter, onprogress func(page, progress int)) (files []*File, err error) {
func FetchChapter(site grabber.Site, chapter *grabber.Chapter, onprogress ProgressCallback) (files []*File, err error) {
wg := sync.WaitGroup{}
guard := make(chan struct{}, site.GetMaxConcurrency().Pages)
errChan := make(chan error, 1)
errChan := make(chan error, len(chapter.Pages)) // Buffer for all possible page errors
done := make(chan bool)
var mu sync.Mutex

for _, page := range chapter.Pages {
guard <- struct{}{}
Expand All @@ -35,35 +40,40 @@ func FetchChapter(site grabber.Site, chapter *grabber.Chapter, onprogress func(p
Referer: site.BaseUrl(),
}, uint(page.Number))

pn := int(page.Number)
cp := pn * 100 / len(chapter.Pages)

if err != nil {
select {
case errChan <- err:
default:
}
errChan <- fmt.Errorf("page %d: %w", page.Number, err)
onprogress(pn, cp, err)
return
}

mu.Lock()
files = append(files, file)
pn := int(page.Number)
cp := pn * 100 / len(chapter.Pages)
mu.Unlock()

onprogress(pn, cp)
onprogress(pn, cp, nil)
}(page)
}

go func() {
wg.Wait()
// signal that all goroutines have completed
close(done)
close(errChan)
}()

select {
// in case of error, return the very first one
case err := <-errChan:
close(guard)
return nil, err
case <-done:
// all goroutines finished successfully, continue
// Collect all errors
var errors []error
for err := range errChan {
errors = append(errors, err)
}

<-done
close(guard)

if len(errors) > 0 {
return files, fmt.Errorf("failed to download %d pages", len(errors))
}

// sort files by page number
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down

0 comments on commit a37562e

Please sign in to comment.