diff --git a/README.md b/README.md index 2863b04..9eb10c9 100644 --- a/README.md +++ b/README.md @@ -145,4 +145,6 @@ Since the copyrights for the majority of old-time radio shows have expired and m ## Todo - The Text to Speech (**TTS**) version of Audiobook Builder is coming soon. It will allow you to create audiobooks from .epub, fb2, and other formats of electronic books. +## Join us on Discord +https://discord.gg/ntYyJ7xfzX diff --git a/assets/abb_ia.gif b/assets/abb_ia.gif index 8a6e607..04c8b3b 100644 Binary files a/assets/abb_ia.gif and b/assets/abb_ia.gif differ diff --git a/internal/controller/search_controller.go b/internal/controller/search_controller.go index e1fe341..dfd5c1c 100644 --- a/internal/controller/search_controller.go +++ b/internal/controller/search_controller.go @@ -61,7 +61,7 @@ func (c *SearchController) search(cmd *dto.SearchCommand) { c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.SetBusyIndicator{Busy: true}, false) c.totalItemsFetched = 0 c.ia = ia_client.New(config.Instance().GetRowsPerPage(), config.Instance().IsUseMock(), config.Instance().IsSaveMock()) - resp := c.ia.Search(cmd.Condition.Author, cmd.Condition.Title, "audio") + resp := c.ia.Search(cmd.Condition.Author, cmd.Condition.Title, "audio", cmd.Condition.SortBy, cmd.Condition.SortOrder) if resp == nil { logger.Error(mq.SearchController + ": Failed to perform IA search with condition: " + cmd.Condition.Author + " - " + cmd.Condition.Title) } @@ -83,7 +83,7 @@ func (c *SearchController) search(cmd *dto.SearchCommand) { func (c *SearchController) getGetNextPage(cmd *dto.GetNextPageCommand) { c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.UpdateStatus{Message: "Fetching Internet Archive items..."}, false) c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.SetBusyIndicator{Busy: true}, false) - resp := c.ia.GetNextPage(cmd.Condition.Author, cmd.Condition.Title, "audio") + resp := c.ia.GetNextPage(cmd.Condition.Author, cmd.Condition.Title, "audio", cmd.Condition.SortBy, cmd.Condition.SortOrder) if resp == nil { logger.Error(mq.SearchController + ": Failed to perform IA search with condition: " + cmd.Condition.Author + " - " + cmd.Condition.Title) } diff --git a/internal/dto/search.go b/internal/dto/search.go index bae190f..f4bd326 100644 --- a/internal/dto/search.go +++ b/internal/dto/search.go @@ -3,8 +3,10 @@ package dto import "fmt" type SearchCondition struct { - Author string - Title string + Author string + Title string + SortBy string + SortOrder string } type SearchCommand struct { diff --git a/internal/ia/client.go b/internal/ia/client.go index 26fd8f5..12049a2 100644 --- a/internal/ia/client.go +++ b/internal/ia/client.go @@ -44,27 +44,27 @@ func New(maxSearchRows int, useMock bool, saveMock bool) *IAClient { return client } -func (client *IAClient) Search(author string, title string, mediaType string) *SearchResponse { +func (client *IAClient) Search(author string, title string, mediaType string, sortBy string, sortOrder string) *SearchResponse { if strings.Contains(title, IA_BASE_URL+"/details/") { item_id := strings.Split(title, "/")[4] return client.searchByID(item_id, mediaType) } else { client.page = 1 - return client.searchByTitle(author, title, mediaType) + return client.searchByTitle(author, title, mediaType, sortBy, sortOrder) } } -func (client *IAClient) GetNextPage(author string, title string, mediaType string) *SearchResponse { +func (client *IAClient) GetNextPage(author string, title string, mediaType string, sortBy string, sortOrder string) *SearchResponse { if strings.Contains(title, IA_BASE_URL+"/details/") { return &SearchResponse{} } else { client.page += 1 - resp := client.searchByTitle(author, title, mediaType) + resp := client.searchByTitle(author, title, mediaType, sortBy, sortOrder) return resp } } -func (client *IAClient) searchByTitle(author string, title string, mediaType string) *SearchResponse { +func (client *IAClient) searchByTitle(author string, title string, mediaType string, sortBy string, sortOrder string) *SearchResponse { mockFile := MOCK_DIR + "/SearchByAuthorAndTitle.json" result := &SearchResponse{} if client.loadMockResult { @@ -80,8 +80,8 @@ func (client *IAClient) searchByTitle(author string, title string, mediaType str } else if title != "" { searchCondition = fmt.Sprintf("title:(%s)", url.QueryEscape(title)) } - var searchURL = fmt.Sprintf(IA_BASE_URL+"/advancedsearch.php?q=%s+AND+mediatype:(%s)&output=json&rows=%d&page=%d", - searchCondition, mediaType, client.maxSearchRows, client.page) + var searchURL = fmt.Sprintf(IA_BASE_URL+"/advancedsearch.php?q=%s+AND+mediatype:(%s)&sort=%s+%s&output=json&rows=%d&page=%d", + searchCondition, mediaType, sortBy, sortOrder, client.maxSearchRows, client.page) logger.Debug("IA request: " + searchURL) _, err := client.restyClient.R().SetResult(result).Get(searchURL) if err != nil { diff --git a/internal/ia/client_test.go b/internal/ia/client_test.go index b64d22a..8fadd59 100644 --- a/internal/ia/client_test.go +++ b/internal/ia/client_test.go @@ -26,19 +26,19 @@ func TestMain(m *testing.M) { func TestSearch(t *testing.T) { ia := ia_client.New(5, false, false) - res := ia.Search("Old Time Radio Researchers", "Single Episodes", "audio") // search by author and title + res := ia.Search("Old Time Radio Researchers", "Single Episodes", "audio", "date", "asc") // search by author and title assert.NotNil(t, res) assert.Equal(t, 5, len(res.Response.Docs)) - res = ia.Search("Old Time Radio Researchers", "", "audio") // search by author only + res = ia.Search("Old Time Radio Researchers", "", "audio", "date", "asc") // search by author only assert.NotNil(t, res) assert.Equal(t, 5, len(res.Response.Docs)) - res = ia.Search("", "Single Episodes", "audio") // search by title only + res = ia.Search("", "Single Episodes", "audio", "date", "asc") // search by title only assert.NotNil(t, res) assert.Equal(t, 5, len(res.Response.Docs)) - res = ia.Search("", "https://archive.org/details/OTRR_Frank_Race_Singles", "audio") // search by item ID + res = ia.Search("", "https://archive.org/details/OTRR_Frank_Race_Singles", "audio", "date", "asc") // search by item ID assert.NotNil(t, res) assert.Equal(t, 1, len(res.Response.Docs)) } diff --git a/internal/ui/search_page.go b/internal/ui/search_page.go index cc2d8d7..d17afae 100644 --- a/internal/ui/search_page.go +++ b/internal/ui/search_page.go @@ -3,6 +3,7 @@ package ui import ( "fmt" "strconv" + "strings" "abb_ia/internal/config" "abb_ia/internal/dto" @@ -23,6 +24,8 @@ type SearchPage struct { searchSection *grid author *tview.InputField title *tview.InputField + SortBy *tview.DropDown + sortOrder *tview.DropDown searchButton *tview.Button clearButton *tview.Button createAudioBookButton *tview.Button @@ -50,7 +53,7 @@ func newSearchPage(dispatcher *mq.Dispatcher) *SearchPage { // search section p.searchSection = newGrid() - p.searchSection.SetColumns(50, -1) + p.searchSection.SetColumns(50, -1, -1) p.searchSection.SetBorder(true) p.searchSection.SetTitle(" Internet Archive Search ") p.searchSection.SetTitleAlign(tview.AlignLeft) @@ -58,10 +61,15 @@ func newSearchPage(dispatcher *mq.Dispatcher) *SearchPage { f.SetHorizontal(false) p.author = f.AddInputField("Creator", config.Instance().GetDefaultAuthor(), 40, nil, func(t string) { p.searchCondition.Author = t }) p.title = f.AddInputField("Title", config.Instance().GetDefaultTitle(), 40, nil, func(t string) { p.searchCondition.Title = t }) + p.searchButton = f.AddButton("Search", p.runSearch) p.clearButton = f.AddButton("Clear", p.clearEverything) f.SetButtonsAlign(tview.AlignRight) p.searchSection.AddItem(f, 0, 0, 1, 1, 0, 0, true) + f = newForm() + p.SortBy = f.AddDropdown("Sort by:", utils.AddSpaces([]string{"Creator", "Title", "Date", "Size "}), 1, func(o string, i int) { p.searchCondition.SortBy = p.mapSortBy(o) }) + p.sortOrder = f.AddDropdown("Sort order:", utils.AddSpaces([]string{"Ascending", "Descending"}), 1, func(o string, i int) { p.searchCondition.SortOrder = p.mapSortOrder(o) }) + p.searchSection.AddItem(f, 0, 1, 1, 1, 0, 0, true) g := newGrid() g.SetRows(-1, -1) g.SetColumns(0) @@ -75,7 +83,7 @@ func newSearchPage(dispatcher *mq.Dispatcher) *SearchPage { f.SetButtonsAlign(tview.AlignRight) g.AddItem(f, 1, 0, 1, 1, 1, 1, true) p.SettingsButton = f.AddButton("Settings", p.updateConfig) - p.searchSection.AddItem(g, 0, 1, 1, 1, 0, 0, true) + p.searchSection.AddItem(g, 0, 2, 1, 1, 0, 0, true) p.mainGrid.AddItem(p.searchSection.Grid, 0, 0, 1, 1, 0, 0, true) @@ -87,7 +95,7 @@ func newSearchPage(dispatcher *mq.Dispatcher) *SearchPage { p.resultSection.SetBorder(true) p.resultTable = newTable() - p.resultTable.setHeaders(" # ", "Author", "Title", "Files", "Duration", "Size") + p.resultTable.setHeaders(" # ", "Creator", "Title", "Files", "Duration", "Size") p.resultTable.setWeights(1, 3, 6, 1, 2, 2) p.resultTable.setAlign(tview.AlignRight, tview.AlignLeft, tview.AlignLeft, tview.AlignRight, tview.AlignRight, tview.AlignRight) p.resultTable.SetSelectionChangedFunc(p.updateDetails) @@ -134,6 +142,8 @@ func newSearchPage(dispatcher *mq.Dispatcher) *SearchPage { p.title, p.searchButton, p.clearButton, + p.SortBy, + p.sortOrder, p.createAudioBookButton, p.SettingsButton, p.resultTable.Table, @@ -291,7 +301,7 @@ func (p *SearchPage) updateConfig() { func (p *SearchPage) showNothingFoundError(dto *dto.NothingFoundError) { newMessageDialog(p.mq, "Error", "\nNo results were found for your search term:\n"+ - "Creator: [darkblue]'"+dto.Condition.Author+"'[black] Title: [darkblue]'"+dto.Condition.Title+"'[black].\n"+ + "Creator: [darkblue]'"+dto.Condition.Author+"'[black] Title: [darkblue]'"+dto.Condition.Title+"'[black].\n"+ "Please revise your search criteria.", p.searchSection.Grid, func() {}) } @@ -299,7 +309,7 @@ func (p *SearchPage) showNothingFoundError(dto *dto.NothingFoundError) { func (p *SearchPage) showLastPageMessage(dto *dto.LastPageMessage) { newMessageDialog(p.mq, "Notification", "No more items were found for your search term: \n"+ - "Creator: [darkblue]'"+dto.Condition.Author+"'[black] Title: [darkblue]'"+dto.Condition.Title+"'[black].\n"+ + "Creator: [darkblue]'"+dto.Condition.Author+"'[black] Title: [darkblue]'"+dto.Condition.Title+"'[black].\n"+ "This is the last page.\n", p.resultSection.Grid, func() {}) } @@ -319,3 +329,29 @@ func (p *SearchPage) showNewVersionMessage(dto *dto.NewAppVersionFound) { "You can download the new version of the application from:\n[darkblue]https://github.com/"+config.Instance().GetRepoOwner()+"/"+config.Instance().GetRepoName()+"/releases", p.searchSection.Grid, func() {}) } + +func (p *SearchPage) mapSortBy(source string) string { + switch s := strings.TrimSpace(source); s { + case "Creator": + return "creator" + case "Title": + return "title" + case "Date": + return "date" + case "Size": + return "item_size" + default: + return source + } +} + +func (p *SearchPage) mapSortOrder(source string) string { + switch s := strings.TrimSpace(source); s { + case "Ascending": + return "asc" + case "Descending": + return "desc" + default: + return source + } +}