From f1c08e5a368c095966296950486ce5b94a166de7 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 28 May 2019 16:35:01 +0200 Subject: [PATCH] add tui --- Makefile | 13 +- cmd/ecload-tui/main.go | 274 +++++++++++++++++++++++++++++++++++++++++ cmd/ecload/main.go | 19 +-- go.mod | 8 ++ go.sum | 23 ++++ pkg/ecload/ecload.go | 2 +- pkg/ecload/fetcher.go | 2 +- pkg/ecload/logger.go | 19 ++- 8 files changed, 337 insertions(+), 23 deletions(-) create mode 100644 cmd/ecload-tui/main.go diff --git a/Makefile b/Makefile index 71c408f..4f38d38 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ .PHONY: all -all: bin/ecload bin/ecload.exe bin/ecload-mac +all: bin/ecload bin/ecload.exe bin/ecload-mac bin/ecload-tui bin/ecload-tui.exe bin/ecload-tui-mac .PHONY: clean clean: rm -r bin/ - go clean ./cmd/ecload/ ./pkg/ecload/ + go clean ./cmd/ecload/ ./cmd/ecload-tui/ ./pkg/ecload/ bin/ecload: cmd/ecload/*.go pkg/ecload/*.go GOOS=linux GOARCH=amd64 go build -ldflags '-s' -v -o $@ cmd/ecload/main.go @@ -14,3 +14,12 @@ bin/ecload.exe: cmd/ecload/*.go pkg/ecload/*.go bin/ecload-mac: cmd/ecload/*.go pkg/ecload/*.go GOOS=darwin GOARCH=amd64 go build -ldflags '-s' -v -o $@ cmd/ecload/main.go + +bin/ecload-tui: cmd/ecload-tui/*.go pkg/ecload/*.go + GOOS=linux GOARCH=amd64 go build -ldflags '-s' -v -o $@ cmd/ecload-tui/main.go + +bin/ecload-tui.exe: cmd/ecload-tui/*.go pkg/ecload/*.go + GOOS=windows GOARCH=amd64 go build -ldflags '-s' -v -o $@ cmd/ecload-tui/main.go + +bin/ecload-tui-mac: cmd/ecload-tui/*.go pkg/ecload/*.go + GOOS=darwin GOARCH=amd64 go build -ldflags '-s' -v -o $@ cmd/ecload-tui/main.go diff --git a/cmd/ecload-tui/main.go b/cmd/ecload-tui/main.go new file mode 100644 index 0000000..8712bd2 --- /dev/null +++ b/cmd/ecload-tui/main.go @@ -0,0 +1,274 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package main + +import ( + "encoding/json" + ui "github.com/VladimirMarkelov/clui" + "io/ioutil" + "os" + "path" + + "github.com/shibukawa/configdir" + + "ecload/pkg/ecload" +) + +const THEME = "//----------------- Theme properties -----------------" + + "//----------------- Colors -----------------" + + "// View colors - internal area and border" + + "ViewBack = white" + + "ViewText = white bold" + + "// general colors" + + "Back = white" + + "Text = black" + + "DisabledText = white" + + "DisabledBack = black bold" + + "// editable & listbox-like controls (interactive ones)" + + "EditBack = blue" + + "EditText = yellow" + + "EditActiveBack = blue bold" + + "EditActiveText = yellow bold" + + "SelectionText = yellow bold" + + "SelectionBack = cyan bold" + + "// scroll control" + + "ScrollText = white bold" + + "ScrollBack = white" + + "ThumbText = white bold" + + "ThumbBack = white" + + "// window-like controls (checkbox, radiogroup...)" + + "ControlText = black" + + "ControlBack = cyan bold" + + "ControlActiveBack = cyan bold" + + "ControlActiveText = yellow bold" + + "ControlDisabledBack = cyan bold" + + "ControlDisabledText = black bold" + + "ControlShadow = black" + + "// progressbar control" + + "ProgressBack = blue" + + "ProgressText = yellow" + + "ProgressActiveBack = blue bold" + + "ProgressActiveText = yellow bold" + + "// button control" + + "ButtonBack=green bold" + + "ButtonText=black" + + "ButtonActiveBack=green bold" + + "ButtonActiveText=white bold" + + "ButtonShadowBack=black" + + "ButtonDisabledText=black bold" + + "ButtonDisabledBack=white" + + "// bar chart control" + + "BarChartBack=black" + + "BarChartText=white" + + "// spark chart" + + "SparkChartBack=black" + + "SparkChartText=white" + + "SparkChartBarBack=black" + + "SparkChartBarText=cyan" + + "SparkChartMaxBack=black" + + "SparkChartMaxText=cyan bold" + + "// table view" + + "TableText=white" + + "TableBack=black" + + "TableSelectedText=white" + + "TableSelectedBack=black bold" + + "TableActiveCellText=white bold" + + "TableActiveCellBack=black bold" + + "TableLineText=white" + + "TableHeaderText=white" + + "TableHeaderBack=black" + + "//----------------- Objects -----------------" + + "SingleBorder=─│┌┐└┘" + + "DoubleBorder=═║╔╗╚╝" + + "Edit=←→V*" + + "ScrollBar=░■▲▼◄►" + + "ViewButtons=^_■[]" + + "CheckBox=[] X?" + + "Radio=() *" + + "ProgressBar=░▒" + + "BarChart=█─│┌┐└┘┬┴├┤┼" + + "SparkChart=█" + + "TableView=─│┼▼▲" + +const VENDOR = "vanwa.ch" +const APPNAME = "ecload" +const SETTINGNAME = "last.json" + +const WAITWIDTH = 40 +const WAITHEIGHT = 10 + +type settings struct { + Out string + Size int +} + +func main() { + logger := ecload.InitLogger(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) + + tmp, err := ioutil.TempDir("", "ecload") + if err != nil { + logger.Error.Println(err) + os.Exit(1) + } + + defer os.RemoveAll(tmp) + + err = ioutil.WriteFile(path.Join(tmp, "ecload.theme"), []byte(THEME), 0600) + if err != nil { + logger.Error.Println(err) + os.Exit(1) + } + + mainLoop(tmp, logger) +} + +func createView(themeDir string, logger ecload.Logger) { + configDirs := configdir.New(VENDOR, APPNAME) + cache := configDirs.QueryCacheFolder() + + sizes := []string{"small", "medium", "large", "max"} + outPath, _ := os.UserHomeDir() + size := 0 + + if cache != nil { + var config settings + + data, _ := cache.ReadFile(SETTINGNAME) + err := json.Unmarshal(data, &config) + + if err == nil { + if config.Out != "" { + outPath = config.Out + } + size = config.Size + } + } + + ui.SetThemePath(themeDir) + ui.SetCurrentTheme("ecload") + + view := ui.AddWindow(0, 0, 75, 10, "Download e-codices") + view.SetPack(ui.Vertical) + view.SetGaps(0, 1) + view.SetPaddings(2, 2) + view.SetSizable(false) + + frmPath := ui.CreateFrame(view, 1, 1, ui.BorderThin, ui.Fixed) + frmPath.SetPack(ui.Vertical) + frmPath.SetTitle("Download path") + + lblPath := ui.CreateLabel(frmPath, ui.AutoSize, ui.AutoSize, outPath, ui.Fixed) + btnSet := ui.CreateButton(frmPath, ui.AutoSize, ui.AutoSize, "Select", ui.Fixed) + + frmSize := ui.CreateFrame(view, 1, 1, ui.BorderThin, ui.Fixed) + frmSize.SetPack(ui.Vertical) + frmSize.SetTitle("Size") + rg := ui.CreateRadioGroup() + + for _, size := range sizes { + radio := ui.CreateRadio(frmSize, ui.AutoSize, size, ui.Fixed) + radio.OnActive(func(active bool) { + go save(lblPath, rg) + }) + rg.AddItem(radio) + } + rg.SetSelected(size) + + frmId := ui.CreateFrame(view, 1, 1, ui.BorderThin, ui.Fixed) + frmId.SetTitle("Id") + fldId := ui.CreateEditField(frmId, 70, "", ui.Fixed) + + frmBtns := ui.CreateFrame(view, 1, 1, ui.BorderNone, ui.Fixed) + frmBtns.SetPack(ui.Vertical) + btnStart := ui.CreateButton(frmBtns, ui.AutoSize, ui.AutoSize, "Download", ui.Fixed) + btnStart.SetEnabled(false) + + ui.ActivateControl(view, fldId) + + fldId.OnChange(func(ev ui.Event) { + allowStart := fldId.Title() != "" + btnStart.SetEnabled(allowStart) + }) + + btnSet.OnClick(func(ev ui.Event) { + s := "Select directory" + + dlg := ui.CreateFileSelectDialog( + s, + "*", + outPath, + true, + true) + dlg.OnClose(func() { + if dlg.Selected { + lblPath.SetTitle(dlg.FilePath) + go save(lblPath, rg) + } + }) + }) + + btnStart.OnClick(func(ev ui.Event) { + size := sizes[rg.Selected()] + w, h := view.Size() + + wait := ui.AddWindow(w / 2 - WAITWIDTH / 2, h / 2 - WAITHEIGHT / 2, WAITWIDTH, WAITHEIGHT, "Downloading...") + wait.SetModal(true) + wait.SetPack(ui.Vertical) + wait.SetGaps(0, 1) + wait.SetPaddings(2, 2) + wait.SetSizable(false) + + ui.CreateLabel(wait, ui.AutoSize, ui.AutoSize, "Please be patient. Maybe bring me some tea.", ui.Fixed) + + ec := make(chan error) + finished := make(chan bool, 1) + + go func(out string, size string, id string, logger ecload.Logger) { + err := ecload.DownloadBook(lblPath.Title(), size, fldId.Title(), logger) + if err != nil { + ec <- err + } else { + close(finished) + } + + }(lblPath.Title(), size, fldId.Title(), logger) + + select { + case <-finished: + successDlg := ui.CreateAlertDialog("Success", "Download finished.", "Yay!") + successDlg.OnClose(func() { + ui.PutEvent(ui.Event{Type: ui.EventCloseWindow}) + ui.ActivateControl(view, fldId) + }) + case err := <-ec: + if err != nil { + errorDlg := ui.CreateAlertDialog("Error", err.Error(), "Ah well") + errorDlg.OnClose(func() { + ui.PutEvent(ui.Event{Type: ui.EventCloseWindow}) + ui.ActivateControl(view, fldId) + }) + } + } + }) +} + +func save(lblPath *ui.Label, rg *ui.RadioGroup) { + configDirs := configdir.New(VENDOR, APPNAME) + cache := configDirs.QueryCacheFolder() + + config := settings { Out: lblPath.Title(), Size: rg.Selected() } + data, _ := json.Marshal(&config) + + cache.WriteFile(SETTINGNAME, data) +} + +func mainLoop(themeDir string, logger ecload.Logger) { + ui.InitLibrary() + defer ui.DeinitLibrary() + + createView(themeDir, logger) + + ui.MainLoop() +} diff --git a/cmd/ecload/main.go b/cmd/ecload/main.go index b4da6c6..fef4e9b 100644 --- a/cmd/ecload/main.go +++ b/cmd/ecload/main.go @@ -5,9 +5,7 @@ package main import ( - "io" "io/ioutil" - "log" "os" "github.com/jawher/mow.cli" @@ -15,23 +13,8 @@ import ( "ecload/pkg/ecload" ) -// Initialize logger formats. -func initLogger( - traceHandle io.Writer, - infoHandle io.Writer, - warningHandle io.Writer, - errorHandle io.Writer) ecload.Logger { - - return ecload.Logger{ - Trace: log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime), - Info: log.New(infoHandle, "INFO: ", log.Ldate|log.Ltime), - Warning: log.New(warningHandle, "WARNING: ", log.Ldate|log.Ltime), - Error: log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile), - } -} - func main() { - logger := initLogger(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr) + logger := ecload.InitLogger(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr) app := cli.App("ecload", "Download books from https://www.e-codices.unifr.ch") app.Version("v version", "0.1.0") diff --git a/go.mod b/go.mod index 1fb556a..0c6035f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,14 @@ go 1.12 require ( github.com/PuerkitoBio/goquery v1.5.0 + github.com/VladimirMarkelov/clui v1.2.0 + github.com/atotto/clipboard v0.1.2 // indirect + github.com/gizak/termui/v3 v3.0.0 // indirect + github.com/huandu/xstrings v1.2.0 // indirect github.com/jawher/mow.cli v1.1.0 + github.com/jroimartin/gocui v0.4.0 // indirect github.com/jung-kurt/gofpdf v1.4.2 + github.com/mattn/go-runewidth v0.0.4 // indirect + github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e // indirect + github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 ) diff --git a/go.sum b/go.sum index 3b8e34f..defaf36 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,44 @@ github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= +github.com/VladimirMarkelov/clui v1.2.0 h1:65AXI0Zml4mbt7cDwBJca0ktCzDHfY5We4Rz9NZ9nO0= +github.com/VladimirMarkelov/clui v1.2.0/go.mod h1:Z/EV0mFYdsx8tzmRPzFoq6xkmF9wJ+euTC4p5h690xA= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= +github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM= +github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gizak/termui/v3 v3.0.0 h1:NYTUG6ig/sJK05O5FyhWemwlVPO8ilNpvS/PgRtrKAE= +github.com/gizak/termui/v3 v3.0.0/go.mod h1:uinu2dMdtMI+FTIdEFUJQT5y+KShnhQRshvPblXq3lY= +github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/jawher/mow.cli v1.1.0 h1:NdtHXRc0CwZQ507wMvQ/IS+Q3W3x2fycn973/b8Zuk8= github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg= +github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8= +github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.4.2 h1:3u2ojTwxPPu3ysIOc5iTwcECpvkFCAe2RJ/tQrvfLi0= github.com/jung-kurt/gofpdf v1.4.2/go.mod h1:rZsO0wEsunjT/L9stF3fJjYbAHgqNYuQB4B8FWvBck0= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k= +github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w= +github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= diff --git a/pkg/ecload/ecload.go b/pkg/ecload/ecload.go index 6903b6f..6aa576d 100644 --- a/pkg/ecload/ecload.go +++ b/pkg/ecload/ecload.go @@ -87,7 +87,7 @@ func DownloadBook(outDir string, size string, id string, logger Logger) error { return err } - pdfPath := path.Join(outDir, fmt.Sprintf("%s.pdf", strings.ReplaceAll(id, "/", "_"))) + pdfPath := path.Join(outDir, fmt.Sprintf("%s-%s.pdf", strings.ReplaceAll(id, "/", "_"), size)) logger.Info.Printf("Saving pdf to %s...", pdfPath) return ImgDirToPdf(dir, pdfPath) diff --git a/pkg/ecload/fetcher.go b/pkg/ecload/fetcher.go index 8bbcc54..216e8b1 100644 --- a/pkg/ecload/fetcher.go +++ b/pkg/ecload/fetcher.go @@ -52,7 +52,7 @@ func downloadToFile(filename string, dir string, pageUrl string) error { defer res.Body.Close() if res.StatusCode != 200 { - return fmt.Errorf("status code error: %d %s", res.StatusCode, res.Status) + return fmt.Errorf("status code error: %s", res.Status) } _, err = io.Copy(out, res.Body) diff --git a/pkg/ecload/logger.go b/pkg/ecload/logger.go index 550931d..0784dcc 100644 --- a/pkg/ecload/logger.go +++ b/pkg/ecload/logger.go @@ -4,7 +4,10 @@ package ecload -import "log" +import ( + "io" + "log" +) type Logger struct { Trace *log.Logger @@ -12,3 +15,17 @@ type Logger struct { Warning *log.Logger Error *log.Logger } + +func InitLogger( + traceHandle io.Writer, + infoHandle io.Writer, + warningHandle io.Writer, + errorHandle io.Writer) Logger { + + return Logger{ + Trace: log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime), + Info: log.New(infoHandle, "INFO: ", log.Ldate|log.Ltime), + Warning: log.New(warningHandle, "WARNING: ", log.Ldate|log.Ltime), + Error: log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile), + } +}