Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented search result sorting #12

Merged
merged 3 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The Internet Archive site offers a vast collection of free "old-time radio" show
To make this process easier, I developed Audiobook Builder. With this app, all you need is the name of a show or book, or a direct link on archive.org. It will download the .mp3 files for the book, re-encode them with the same bit rate, generate a list of chapters (which can be edited during the process), and ultimately create an audiobook in .m4b format.


![Audiobook Builder in action](https://github.com/vpoluyaktov/abb_ia/blob/master/assets/abb_ia.gif)
![Audiobook Builder in action](https://github.com/vpoluyaktov/abb_ia/blob/master/assets/abb_ia_v2.gif)

## Features
- TUI interface. It allows you to run this application either on your own computer or on a remote server using ssh with tmux, screen, or byobu. This can be helpful when creating an audiobook that takes a long time.
Expand Down Expand Up @@ -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

Binary file removed assets/abb_ia.gif
Binary file not shown.
Binary file added assets/abb_ia_v2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions internal/controller/search_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
6 changes: 4 additions & 2 deletions internal/dto/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 7 additions & 7 deletions internal/ia/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions internal/ia/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
46 changes: 41 additions & 5 deletions internal/ui/search_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ui
import (
"fmt"
"strconv"
"strings"

"abb_ia/internal/config"
"abb_ia/internal/dto"
Expand All @@ -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
Expand Down Expand Up @@ -50,18 +53,23 @@ 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)
f := newForm()
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)
Expand All @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -291,15 +301,15 @@ 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() {})
}

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() {})
}
Expand All @@ -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
}
}
Loading