From 23d77d1681abb3361f68c56bf0a78481ea479248 Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Tue, 7 Nov 2017 21:55:20 +0900 Subject: [PATCH] Tweak html layout to improve formatting. Also implement Previous/Next buttons. --- app/index.html | 10 +++---- app/lib.js | 60 ++++++++++++++++++++++++++++++++++------- main/db.go | 27 +++++++++++++++++-- main/handler.go | 72 ++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 139 insertions(+), 30 deletions(-) diff --git a/app/index.html b/app/index.html index 322ac7e..4b246d4 100644 --- a/app/index.html +++ b/app/index.html @@ -17,16 +17,14 @@ Series: -

Books

- -
(No books found)
- +
(No books found)
+ diff --git a/app/lib.js b/app/lib.js index 7175471..5e2ecf8 100644 --- a/app/lib.js +++ b/app/lib.js @@ -6,7 +6,8 @@ g_state = { count: 0, first: 0, ids: [], - last: 0, + last: (-1), + pageSize: 100 } function constructSearchUrl() { @@ -40,9 +41,27 @@ function constructSearchUrl() { } function onNext() { + if (g_state.last < (g_state.count - 1)) { + g_state.first += g_state.pageSize; + g_state.last += g_state.pageSize; + if (g_state.last >= g_state.count) { + g_state.last = g_state.count - 1; + } + + refreshData(); + } } function onPrev() { + if (g_state.first > 0) { + g_state.first -= g_state.pageSize; + if (g_state.first < 0) { + g_state.first = 0; + } + g_state.last = g_state.first + g_state.pageSize; + + refreshData(); + } } function onSearch() { @@ -58,10 +77,11 @@ function onSearch() { .then((jsonValue) => { console.log('JSON response: ', jsonValue); g_state.ids = jsonValue + g_state.count = g_state.ids.length; g_state.first = 0 g_state.last = (g_state.ids.length) - 1; - if (g_state.last > 100) { - g_state.last = 100; + if (g_state.last > g_state.pageSize) { + g_state.last = g_state.pageSize; } refreshData() }) @@ -78,7 +98,7 @@ function refreshData() { var i; var url = '/info/?ids='; for (i = g_state.first; i <= g_state.last; ++i) { - if (i > 0) { + if (i > g_state.first) { url += ','; } url += g_state.ids[i]; @@ -102,12 +122,16 @@ function refreshData() { function refreshLayout() { var i; var html = ''; - for (i = g_state.first; i <= g_state.last; ++i) { + var limit = g_state.last - g_state.first; + for (i = 0; i <= limit; ++i) { var book = g_state.cache[i]; html += bookHtml(book); } document.getElementById('books').innerHTML = html; + document.getElementById('first').innerHTML = (g_state.first + 1); + document.getElementById('last').innerHTML = (g_state.last + 1); + document.getElementById('count').innerHTML = g_state.count; } function report(message) { @@ -116,10 +140,28 @@ function report(message) { function bookHtml(book) { console.log('bookHtml(): ', book); - var result = '
' - + '' - + '
' - + '' + book.Description + '
'; + var result = '
' + + '' + + '' + + '' + + '' + + '' + + '
'; + if (0 == book.CoverId) { + result += '(No cover available)' + } else { + result += '' + } + result += '' + + '

' + book.Title + '

' + + '

' + + '' + book.AuthorReading + ''; + if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) { + result += '
' + book.SeriesName + ' ' + book.Volume + ''; + } + result += '

' + + '
' + + '
'; return result; } diff --git a/main/db.go b/main/db.go index fa34828..21c6818 100644 --- a/main/db.go +++ b/main/db.go @@ -161,12 +161,33 @@ func queryBooksByIds(ids []int) []Book { return res } +func queryBookPathById(id int) (string) { + query := "SELECT b.path FROM Books b WHERE b.id=$1" + + ps, err := getDb().Prepare(query) + if nil != err { + report("Failed to Prepare query: " + query, err) + return "" + } + defer ps.Close() + + row := ps.QueryRow(id) + var path sql.NullString + err = row.Scan(&path) + if nil != err { + report(fmt.Sprintf("Failed to retrieve path for book id %v: ", id), err) + return "" + } + + return nsVal(path) +} + func queryIds(criteria []SearchTerm) []int { fmt.Println("queryIds():", criteria) query := "SELECT b.id FROM Books b" + " INNER JOIN Authors a ON a.id=b.author" + - " LEFT OUTER JOIN Series s ON s.id=b.series" + " LEFT OUTER JOIN Series s ON s.id=b.series" args := make([]interface{}, len(criteria)) @@ -190,6 +211,8 @@ func queryIds(criteria []SearchTerm) []int { args[i] = criterion.Text } + query += " ORDER BY b.path" + res := []int{} ps, err := getDb().Prepare(query) @@ -207,7 +230,7 @@ func queryIds(criteria []SearchTerm) []int { } defer rows.Close() - for rows.Next(); rows.Next(); { + for rows.Next() { var id int rows.Scan(&id) res = append(res, id) diff --git a/main/handler.go b/main/handler.go index 8686f2b..8a74c62 100644 --- a/main/handler.go +++ b/main/handler.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "os" + "path/filepath" "strconv" "strings" ) @@ -14,6 +15,19 @@ const PARAM_IDS = "ids" const MAX_TERMS = 10 // ============================================================================ +func bookMimeType(bookPath string) string { + upper := strings.ToUpper(bookPath) + + if strings.HasSuffix(upper, ".EPUB") { + return "application/epub+zip" + } else if strings.HasSuffix(upper, ".PDF") { + return "application/pdf" + } else { + fmt.Println("Warning: Cannot determine MIME type, will use application/octet-stream:", bookPath) + return "application/octet-stream" + } +} + func efsPathForId(efsId int) string { config := getConfig() @@ -33,6 +47,8 @@ func handler(w http.ResponseWriter, r *http.Request) { action := strings.Split(r.URL.Path[1:], "/")[0] switch(action) { + case "book": + handleBook(w, r) case "download": handleDownload(w, r) case "info": @@ -48,27 +64,56 @@ func handler(w http.ResponseWriter, r *http.Request) { } } -/* -func handleApp(w http.ResponseWriter, r *http.Request) { - fmt.Println("handleApp():", r.URL.Path) +// Download a book, based on the path stored in the DB +func handleBook(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path[1:] + tok := strings.Split(path, "/") + if 2 != len(tok) { + fmt.Fprintln(w, "Unexpected path for book download:", path) + return + } + + bookId, err := strconv.Atoi(tok[1]) + if (nil != err) || (0 == bookId) { + fmt.Fprintln(w, "Invalid id for book download:", path, err) + return + } + + bookPath := queryBookPathById(bookId) + if 0 == len(bookPath) { + fmt.Fprintln(w, "No book for ID:", bookId) + return + } + + bookFileName := filepath.Base(bookPath) - // Security check: prevent walking up the directory - pos := strings.Index(r.Url.Path, "../") - if (-1) == pos { - fmt.Fprintln(w, "Paths containing \"../\" are not permitted:", r.URL.Path) + mimeType := bookMimeType(bookPath) + if 0 == len(mimeType) { + fmt.Fprintln(w, "Failed to determine MIME type for book:", bookPath) return } - fileName := "../app" + r.URL.Path - _, err := os.Stat(fileName) - if nil != err { - fmt.Fprintln(w, "Failed to find file:", fileName, err) + var info os.FileInfo + info, err = os.Stat(bookPath) + if nil != err { + fmt.Fprintln(w, "Failed to read file metadata:", bookPath, err) return } + modTime := info.ModTime() + + var fd *os.File + fd, err = os.Open(bookPath) + if nil != err { + fmt.Fprintln(w, "Failed to open file:", bookPath, err) + return + } + defer fd.Close() - http.ServeFile(w, r, "../app/" + r.URL.Path[1:]) + // TODO: handle non-ASCII file names. Need to look up the permutations on how to encode that. + w.Header().Set("Content-Disposition", "attachment; filename=" + bookFileName) + w.Header().Set("Content-Type", mimeType) + http.ServeContent(w, r, bookFileName, modTime, fd) } -*/ func handleDownload(w http.ResponseWriter, r *http.Request) { path := r.URL.Path[1:] @@ -99,6 +144,7 @@ func handleDownload(w http.ResponseWriter, r *http.Request) { } defer fd.Close() + w.Header().Set("Content-Type", mimeType) io.Copy(w, fd) } -- 2.39.2