basic importing works

This commit is contained in:
David 2021-08-01 18:50:53 -04:00
parent ebbdb99f37
commit 39fd64ace7
5 changed files with 144 additions and 6 deletions

View File

@ -5,6 +5,18 @@ import (
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
// error message
type EventError struct {
tcell.EventTime
err error
}
func NewEventError(err error) *EventError {
e := &EventError{err: err}
e.SetEventNow()
return e
}
// save change to book // save change to book
type EventBookUpdate struct { type EventBookUpdate struct {
tcell.EventTime tcell.EventTime
@ -66,6 +78,18 @@ func NewEventOpenImport() *EventOpenImport {
return e return e
} }
// attempt to import given filename.csv
type EventAttemptImport struct {
tcell.EventTime
filename string
}
func NewEventAttemptImport(f string) *EventAttemptImport {
e := &EventAttemptImport{filename: f}
e.SetEventNow()
return e
}
// close import window // close import window
type EventCloseImport struct { type EventCloseImport struct {
tcell.EventTime tcell.EventTime

View File

@ -11,6 +11,7 @@ import (
"git.yetaga.in/alazyreader/library/book" "git.yetaga.in/alazyreader/library/book"
"git.yetaga.in/alazyreader/library/config" "git.yetaga.in/alazyreader/library/config"
"git.yetaga.in/alazyreader/library/database" "git.yetaga.in/alazyreader/library/database"
"git.yetaga.in/alazyreader/library/importer"
"git.yetaga.in/alazyreader/library/ui" "git.yetaga.in/alazyreader/library/ui"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
@ -119,9 +120,7 @@ func main() {
ui.StyleActive, ui.StyleActive,
false, false,
) )
book := ui.NewBookDetails(&book.Book{ activeBookDetails := ui.NewBookDetails(&book.Book{})
Title: "test title",
})
// book display (right column) // book display (right column)
activeBook := ui.NewBox( activeBook := ui.NewBox(
@ -129,7 +128,7 @@ func main() {
[]string{"˄˅ select", "⏎ edit", "(esc) close"}, []string{"˄˅ select", "⏎ edit", "(esc) close"},
ui.Contents{{ ui.Contents{{
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0}, Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0},
Container: book, Container: activeBookDetails,
}}, }},
ui.StyleInactive, ui.StyleInactive,
false, false,
@ -160,6 +159,19 @@ func main() {
) )
popup.SetVisible(false) popup.SetVisible(false)
// error pop-up
errorMessage := ui.NewEditableTextLine("")
errorPopup := ui.NewBox(
"error",
[]string{"⏎ close"},
ui.Contents{
{Container: errorMessage, Offsets: ui.Offsets{Top: 1, Left: 1}},
},
ui.StyleActive.Bold(true).Foreground(tcell.ColorRed),
false,
)
errorPopup.SetVisible(false)
// init // init
screen.Clear() screen.Clear()
w, h := screen.Size() w, h := screen.Size()
@ -215,6 +227,8 @@ func main() {
} else if v.Key() == tcell.KeyLeft { } else if v.Key() == tcell.KeyLeft {
fileSelector.MoveCursor(-1) fileSelector.MoveCursor(-1)
} else if v.Key() == tcell.KeyEnter {
screen.PostEvent(NewEventAttemptImport(fileSelector.Text()))
} else if v.Rune() != 0 { } else if v.Rune() != 0 {
fileSelector.InsertAtCursor(v.Rune()) fileSelector.InsertAtCursor(v.Rune())
} }
@ -233,16 +247,45 @@ func main() {
activeBook.SetStyle(ui.StyleInactive) activeBook.SetStyle(ui.StyleInactive)
menu.SetStyle(ui.StyleActive) menu.SetStyle(ui.StyleActive)
case *EventLoadBook: case *EventLoadBook:
book.SetBook(GetBookByID(v.ID, books)) activeBookDetails.SetBook(GetBookByID(v.ID, books))
case *EventOpenImport: case *EventOpenImport:
state.Set("ui_state", IN_IMPORT) state.Set("ui_state", IN_IMPORT)
menu.SetStyle(ui.StyleInactive) menu.SetStyle(ui.StyleInactive)
popup.SetVisible(true) popup.SetVisible(true)
popup.SetSize(6, 3, 5, 80) popup.SetSize(6, 3, 5, 80)
case *EventAttemptImport:
// this will block other events, but it shouldn't take too long...
f, err := os.Open(v.filename)
if err != nil {
screen.PostEvent(NewEventError(err))
continue
}
books, err := importer.CSVToBooks(f)
if err != nil {
screen.PostEvent(NewEventError(err))
continue
}
for b := range books {
err = lib.AddBook(context.Background(), &books[b])
if err != nil {
screen.PostEvent(NewEventError(err))
}
}
screen.PostEvent(NewEventCloseImport())
allbooks, err := lib.GetAllBooks(context.Background())
if err != nil {
screen.PostEvent(NewEventError(err))
}
state.Set("library", allbooks)
state.Set("ui_state", IN_MENU)
case *EventCloseImport: case *EventCloseImport:
state.Set("ui_state", IN_MENU) state.Set("ui_state", IN_MENU)
screen.HideCursor()
menu.SetStyle(ui.StyleActive) menu.SetStyle(ui.StyleActive)
popup.SetVisible(false) popup.SetVisible(false)
case *EventError:
errorMessage.SetText(v.err.Error())
errorPopup.SetVisible(true)
case *EventQuit: case *EventQuit:
screen.Fini() screen.Fini()
fmt.Printf("Thank you for playing Wing Commander!\n\n") fmt.Printf("Thank you for playing Wing Commander!\n\n")
@ -253,8 +296,10 @@ func main() {
default: default:
} }
// repaint // repaint
l.SetMembers(Titles(state.Get("library").([]book.Book)))
container.Draw(screen) container.Draw(screen)
popup.Draw(screen) popup.Draw(screen)
errorPopup.Draw(screen)
screen.Show() screen.Show()
} }
} }

View File

@ -201,7 +201,7 @@ func (m *MySQL) AddBook(ctx context.Context, b *book.Book) error {
INSERT INTO `+m.tableName+` INSERT INTO `+m.tableName+`
(title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series, volume, year, signed, description, notes, onloan, coverurl) (title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series, volume, year, signed, description, notes, onloan, coverurl)
VALUES VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, )`, (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
b.Title, b.Title,
strings.Join(b.Authors, ";"), strings.Join(b.Authors, ";"),
b.SortAuthor, b.SortAuthor,

65
importer/csv.go Normal file
View File

@ -0,0 +1,65 @@
package importer
import (
"encoding/csv"
"io"
"strings"
"git.yetaga.in/alazyreader/library/book"
)
func CSVToBooks(r io.Reader) ([]book.Book, error) {
reader := csv.NewReader(r)
header, err := reader.Read()
if err != nil {
return nil, err
}
hmap := parseHeader(header)
books := []book.Book{}
for {
row, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return books, err
}
b := book.Book{
Title: row[hmap["title"]],
Authors: parseAuthors(row[hmap["author"]]),
SortAuthor: row[hmap["authorlast"]],
ISBN10: row[hmap["isbn-10"]],
ISBN13: row[hmap["isbn-13"]],
Format: row[hmap["format"]],
Genre: row[hmap["genre"]],
Publisher: row[hmap["publisher"]],
Series: row[hmap["series"]],
Volume: row[hmap["volume"]],
Year: row[hmap["year"]],
Signed: row[hmap["signed"]] == "yes", // convert from known string to bool
Description: row[hmap["description"]],
Notes: row[hmap["notes"]],
OnLoan: row[hmap["onloan"]],
CoverURL: row[hmap["coverurl"]],
}
books = append(books, b)
}
return books, nil
}
func parseHeader(header []string) map[string]int {
m := make(map[string]int, len(header)-1)
for i := range header {
m[strings.TrimSpace(strings.ToLower(header[i]))] = i
}
return m
}
func parseAuthors(a string) []string {
as := strings.Split(a, ";")
for i := range as {
as[i] = strings.TrimSpace(as[i])
}
return as
}

View File

@ -353,6 +353,10 @@ func (l *List) ListMembers() []ListKeyValue {
return l.listItems return l.listItems
} }
func (l *List) SetMembers(lkv []ListKeyValue) {
l.listItems = lkv
}
// BookDetails displays an editable list of book details // BookDetails displays an editable list of book details
type BookDetails struct { type BookDetails struct {
x, y int x, y int