Add slider to enable distant paging.
[quanweb.git] / main / handler.go
index 52d08e5f8ff98f192f9dbc109418424522ac6719..2d8076e35720c20e5422ecd87fe9eca83f70c7c9 100644 (file)
@@ -3,7 +3,10 @@ package main
 import (
   "encoding/json"
   "fmt"
+  "io"
   "net/http"
+  "os"
+  "path/filepath"
   "strconv"
   "strings"
 )
@@ -12,6 +15,28 @@ 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/%s/%s/%s/%s/%s.dat", config.efsBasePath, 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 {
@@ -19,9 +44,13 @@ func handler(w http.ResponseWriter, r *http.Request) {
     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":
@@ -35,6 +64,90 @@ func handler(w http.ResponseWriter, r *http.Request) {
   }
 }
 
+// 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)
+
+  mimeType := bookMimeType(bookPath)
+  if 0 == len(mimeType) {
+    fmt.Fprintln(w, "Failed to determine MIME type for book:", bookPath)
+    return
+  }
+
+  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()
+
+  // 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 handleInfo(w http.ResponseWriter, r *http.Request) {
   idParams := r.Form[PARAM_IDS]
   if 1 != len(idParams) {
@@ -45,8 +158,8 @@ func handleInfo(w http.ResponseWriter, r *http.Request) {
   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,7 +169,6 @@ 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)
@@ -66,6 +178,8 @@ func handleInfo(w http.ResponseWriter, r *http.Request) {
 }
 
 func handleSearch(w http.ResponseWriter, r *http.Request) {
+  var err error
+
   fields := []Field{Author, Title, Series}
 
   terms := make([]SearchTerm, len(fields))
@@ -76,7 +190,7 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
     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}
@@ -95,3 +209,4 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
     w.Write(jsonValue)
   }
 }
+