import (
"encoding/json"
"fmt"
+ "io"
"net/http"
+ "os"
+ "path/filepath"
"strconv"
"strings"
)
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()
+
+ idStr := fmt.Sprintf("%010d", efsId)
+ path := fmt.Sprintf("%s/efs/%s/%s/%s/%s/%s.dat", config.basePath, idStr[0:2], idStr[2:4], idStr[4:6], idStr[6:8], idStr)
+
+ return path
+}
+
func handler(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if nil != err {
- fmt.Fprintln(w, "ERROR!", err)
+ handleError(w, err.Error())
return
}
- action := r.URL.Path[1:]
+ action := strings.Split(r.URL.Path[1:], "/")[0]
switch(action) {
+ case "book":
+ handleBook(w, r)
+ case "download":
+ handleDownload(w, r)
case "info":
handleInfo(w, r)
case "search":
handleSearch(w, r)
default:
- fmt.Fprintf(w, "Unrecognized request: %s\n", r.URL.Path[1:])
- fmt.Fprintf(w, "id: %s\n", r.FormValue("id"))
-
- fmt.Fprintln(w, "URL: ", r.URL)
- fmt.Fprintln(w, "Query: ", r.URL.Query())
+ handleError(
+ w,
+ fmt.Sprintf(
+ "Unrecognized request: %s\nid: %s\nURL: %s\nQuery: %s\n",
+ r.URL.Path[1:],
+ r.FormValue("id"),
+ r.URL,
+ r.URL.Query()))
}
}
+// Download a book, based on the path stored in the DB
+func handleBook(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("handleBook:", r.URL.Path)
+ path := r.URL.Path[1:]
+ tok := strings.Split(path, "/")
+ if 2 != len(tok) {
+ handleError(w, fmt.Sprintf("Unexpected path for book download: %s\n", path))
+ return
+ }
+
+ bookId, err := strconv.Atoi(tok[1])
+ if (nil != err) || (0 == bookId) {
+ msg := fmt.Sprintf("Invalid id for book download: \"%s\" (%s)\n", path, err.Error())
+ handleError(w, msg)
+ return
+ }
+
+ bookPath := queryBookPathById(bookId)
+ if 0 == len(bookPath) {
+ handleError(w, fmt.Sprintf("No book for ID: %s\n", bookId))
+ return
+ }
+
+ bookFileName := filepath.Base(bookPath)
+
+ mimeType := bookMimeType(bookPath)
+ if 0 == len(mimeType) {
+ handleError(w, fmt.Sprintf("Failed to determine MIME type for book: %s\n", bookPath))
+ return
+ }
+
+ var info os.FileInfo
+ info, err = os.Stat(bookPath)
+ if nil != err {
+ handleError(w, fmt.Sprintf("Failed to read file metadata: \"%s\" (%s)\n", bookPath, err.Error()))
+ return
+ }
+ modTime := info.ModTime()
+
+ var fd *os.File
+ fd, err = os.Open(bookPath)
+ if nil != err {
+ handleError(w, fmt.Sprintf("Failed to open file: \"%s\" (%s)\n", bookPath, err.Error()))
+ return
+ }
+ defer fd.Close()
+
+ // 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:]
+ tok := strings.Split(path, "/")
+ if 2 != len(tok) {
+ fmt.Fprintln(w, "Unexpected path for download:", path)
+ return
+ }
+ efsId, err := strconv.Atoi(tok[1])
+ if (nil != err) || (0 == efsId) {
+ fmt.Fprintln(w, "Invalid id for download:", path, err)
+ return
+ }
+
+ mimeType := queryMimeTypeByEfsId(efsId)
+ if 0 == len(mimeType) {
+ fmt.Fprintln(w, "No MIME type found for id:", efsId)
+ return
+ }
+
+ efsPath := efsPathForId(efsId)
+
+ var fd *os.File
+ fd, err = os.Open(efsPath)
+ if nil != err {
+ fmt.Fprintln(w, "Failed to read file for id:", efsId, err)
+ return
+ }
+ defer fd.Close()
+
+ w.Header().Set("Content-Type", mimeType)
+ io.Copy(w, fd)
+}
+
+func handleError(w http.ResponseWriter, msg string) {
+ fmt.Printf("ERROR: %s", msg)
+ http.Error(w, msg, http.StatusInternalServerError)
+}
+
func handleInfo(w http.ResponseWriter, r *http.Request) {
idParams := r.Form[PARAM_IDS]
if 1 != len(idParams) {
- fmt.Fprintln(w, "ERROR! Detected either zero or multiple ids= parameters. Exactly one expected.")
- return
- }
+ handleError(w, "ERROR! Detected either zero or multiple ids= parameters. Exactly one expected.")
+ return
+ }
idParam := idParams[0]
idStrings := strings.Split(idParam, ",")
ids := make([]int, len(idStrings))
+ var err error
for i, v := range(idStrings) {
- var err error
ids[i], err = strconv.Atoi(v)
if nil != err {
ids[i] = 0
books := queryBooksByIds(ids)
var jsonValue []byte
- var err error
jsonValue, err = json.Marshal(books)
if nil != err {
- fmt.Fprintln(w, "ERROR!", err)
+ handleError(w, err.Error())
} else {
w.Write(jsonValue)
}
}
func handleSearch(w http.ResponseWriter, r *http.Request) {
- fields := []Field{Author, Title, Series}
+ var err error
+
+ fields := []Field{Author, Language, List, Series, Title}
terms := make([]SearchTerm, len(fields))
-
+
count := 0
for _, fv := range(fields) {
paramName := fv.String()
paramValues := r.Form[paramName]
for _, pv := range(paramValues) {
if count >= len(terms) {
- fmt.Printf("WARNING: limit of %v search terms exceeded. One or more terms ignored.")
+ fmt.Printf("WARNING: limit of %d search terms exceeded. One or more terms ignored.", len(terms))
break
}
terms[count] = SearchTerm{Attribute:fv, Text:pv}
jsonValue, err := json.Marshal(ids)
if nil != err {
- fmt.Fprintln(w, "ERROR!", err)
+ handleError(w, err.Error())
} else {
w.Write(jsonValue)
}
}
+