d0771fa61d303e5a6bcb8b748c5b277ab2146b73
[quanweb.git] / main / handler.go
1 package main
2
3 import (
4   "encoding/json"
5   "fmt"
6   "io"
7   "net/http"
8   "os"
9   "path/filepath"
10   "strconv"
11   "strings"
12 )
13
14 const PARAM_IDS = "ids"
15 const MAX_TERMS = 10
16
17 // ============================================================================
18 func bookMimeType(bookPath string) string {
19   upper := strings.ToUpper(bookPath)
20
21   if strings.HasSuffix(upper, ".EPUB") {
22     return "application/epub+zip"
23   } else if strings.HasSuffix(upper, ".PDF") {
24     return "application/pdf"
25   } else {
26     fmt.Println("Warning:  Cannot determine MIME type, will use application/octet-stream:", bookPath)
27     return "application/octet-stream"
28   }
29 }
30
31 func efsPathForId(efsId int) string {
32   config := GetConfig()
33
34   idStr := fmt.Sprintf("%010d", efsId)
35   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)
36
37   return path
38 }
39
40 func handler(w http.ResponseWriter, r *http.Request) {
41   err := r.ParseForm()
42   if nil != err {
43     handleError(w, err.Error())
44     return
45   }
46
47   action := strings.Split(r.URL.Path[1:], "/")[0]
48
49   switch(action) {
50   case "book":
51     handleBook(w, r)
52   case "download":
53     handleDownload(w, r)
54   case "info":
55     handleInfo(w, r)
56   case "search":
57     handleSearch(w, r)
58   default:
59     handleError(
60       w,
61       fmt.Sprintf(
62         "Unrecognized request:  %s\nid: %s\nURL: %s\nQuery: %s\n",
63         r.URL.Path[1:],
64         r.FormValue("id"),
65         r.URL,
66         r.URL.Query()))
67   }
68 }
69
70 // Download a book, based on the path stored in the DB
71 func handleBook(w http.ResponseWriter, r *http.Request) {
72   fmt.Println("handleBook:", r.URL.Path)
73   path := r.URL.Path[1:]
74   tok := strings.Split(path, "/")
75   if 2 != len(tok) {
76     handleError(w, fmt.Sprintf("Unexpected path for book download: %s\n", path))
77     return
78   }
79
80   bookId, err := strconv.Atoi(tok[1])
81   if (nil != err) || (0 == bookId) {
82     msg := fmt.Sprintf("Invalid id for book download: \"%s\" (%s)\n", path, err.Error())
83     handleError(w, msg)
84     return
85   }
86
87   bookPath := queryBookPathById(bookId)
88   if 0 == len(bookPath) {
89     handleError(w, fmt.Sprintf("No book for ID: %s\n", bookId))
90     return
91   }
92
93   bookFileName := filepath.Base(bookPath)
94
95   mimeType := bookMimeType(bookPath)
96   if 0 == len(mimeType) {
97     handleError(w, fmt.Sprintf("Failed to determine MIME type for book: %s\n", bookPath))
98     return
99   }
100
101   var info os.FileInfo
102   info, err = os.Stat(bookPath)
103   if nil != err {
104     handleError(w, fmt.Sprintf("Failed to read file metadata: \"%s\" (%s)\n", bookPath, err.Error()))
105     return
106   }
107   modTime := info.ModTime()
108
109   var fd *os.File
110   fd, err = os.Open(bookPath)
111   if nil != err {
112     handleError(w, fmt.Sprintf("Failed to open file: \"%s\" (%s)\n", bookPath, err.Error()))
113     return
114   }
115   defer fd.Close()
116
117   // TODO:  handle non-ASCII file names.  Need to look up the permutations on how to encode that.
118   w.Header().Set("Content-Disposition", "attachment; filename=" + bookFileName)
119   w.Header().Set("Content-Type", mimeType)
120   http.ServeContent(w, r, bookFileName, modTime, fd)
121 }
122
123 func handleDownload(w http.ResponseWriter, r *http.Request) {
124   path := r.URL.Path[1:]
125   tok := strings.Split(path, "/")
126   if 2 != len(tok)  {
127     fmt.Fprintln(w, "Unexpected path for download:", path)
128     return
129   }
130   efsId, err := strconv.Atoi(tok[1])
131   if (nil != err) || (0 == efsId) {
132     fmt.Fprintln(w, "Invalid id for download:", path, err)
133     return
134   }
135
136   mimeType := queryMimeTypeByEfsId(efsId)
137   if 0 == len(mimeType) {
138     fmt.Fprintln(w, "No MIME type found for id:", efsId)
139     return
140   }
141
142   efsPath := efsPathForId(efsId)
143
144   var fd *os.File
145   fd, err = os.Open(efsPath)
146   if nil != err {
147     fmt.Fprintln(w, "Failed to read file for id:", efsId, err)
148     return
149   }
150   defer fd.Close()
151
152   w.Header().Set("Content-Type", mimeType)
153   io.Copy(w, fd)
154 }
155
156 func handleError(w http.ResponseWriter, msg string) {
157   fmt.Printf("ERROR: %s", msg)
158   http.Error(w, msg, http.StatusInternalServerError)
159 }
160
161 func handleInfo(w http.ResponseWriter, r *http.Request) {
162   idParams := r.Form[PARAM_IDS]
163   if 1 != len(idParams) {
164     handleError(w, "ERROR!  Detected either zero or multiple ids= parameters.  Exactly one expected.")
165     return
166   }
167
168   idParam := idParams[0]
169   idStrings := strings.Split(idParam, ",")
170   ids := make([]int, len(idStrings))
171   var err error
172   for i, v := range(idStrings) {
173     ids[i], err = strconv.Atoi(v)
174     if nil != err {
175       ids[i] = 0
176     }
177   }
178
179   books := queryBooksByIds(ids)
180
181   var jsonValue []byte
182   jsonValue, err = json.Marshal(books)
183   if nil != err {
184     handleError(w, err.Error())
185   } else {
186     w.Write(jsonValue)
187   }
188 }
189
190 func handleSearch(w http.ResponseWriter, r *http.Request) {
191   var err error
192
193   fields := []Field{Author, Language, Series, Title}
194
195   terms := make([]SearchTerm, len(fields))
196   
197   count := 0
198   for _, fv := range(fields) {
199     paramName := fv.String()
200     paramValues := r.Form[paramName]
201     for _, pv := range(paramValues) {
202       if count >= len(terms) {
203         fmt.Printf("WARNING:  limit of %d search terms exceeded.  One or more terms ignored.", len(terms))
204         break
205       }
206       terms[count] = SearchTerm{Attribute:fv, Text:pv}
207       count++
208     }
209   }
210
211   terms = terms[:count]
212
213   ids := queryIds(terms)
214
215   jsonValue, err := json.Marshal(ids)
216   if nil != err {
217     handleError(w, err.Error())
218   } else {
219     w.Write(jsonValue)
220   }
221 }
222