Fix bug in list search that was introduced by sort order work.
[quanweb.git] / main / handler.go
index 52d08e5f8ff98f192f9dbc109418424522ac6719..bdd6410520896253fc1d68deb76904612adf1ff0 100644 (file)
@@ -3,7 +3,10 @@ package main
 import (
   "encoding/json"
   "fmt"
+  "io"
   "net/http"
+  "os"
+  "path/filepath"
   "strconv"
   "strings"
 )
@@ -12,41 +15,161 @@ 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()
+
+  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
@@ -56,27 +179,28 @@ func handleInfo(w http.ResponseWriter, r *http.Request) {
   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, Sort, 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}
@@ -90,8 +214,9 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
 
   jsonValue, err := json.Marshal(ids)
   if nil != err {
-    fmt.Fprintln(w, "ERROR!", err)
+    handleError(w, err.Error())
   } else {
     w.Write(jsonValue)
   }
 }
+