Tweak html layout to improve formatting.
[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/%s/%s/%s/%s/%s.dat", config.efsBasePath, 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     fmt.Fprintln(w, "ERROR!", err)
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     fmt.Fprintf(w, "Unrecognized request:  %s\n", r.URL.Path[1:])
60     fmt.Fprintf(w, "id: %s\n", r.FormValue("id"))
61   
62     fmt.Fprintln(w, "URL: ", r.URL)
63     fmt.Fprintln(w, "Query: ", r.URL.Query())
64   }
65 }
66
67 // Download a book, based on the path stored in the DB
68 func handleBook(w http.ResponseWriter, r *http.Request) {
69   path := r.URL.Path[1:]
70   tok := strings.Split(path, "/")
71   if 2 != len(tok) {
72     fmt.Fprintln(w, "Unexpected path for book download:", path)
73     return
74   }
75
76   bookId, err := strconv.Atoi(tok[1])
77   if (nil != err) || (0 == bookId) {
78     fmt.Fprintln(w, "Invalid id for book download:", path, err)
79     return
80   }
81
82   bookPath := queryBookPathById(bookId)
83   if 0 == len(bookPath) {
84     fmt.Fprintln(w, "No book for ID:", bookId)
85     return
86   }
87
88   bookFileName := filepath.Base(bookPath)
89
90   mimeType := bookMimeType(bookPath)
91   if 0 == len(mimeType) {
92     fmt.Fprintln(w, "Failed to determine MIME type for book:", bookPath)
93     return
94   }
95
96   var info os.FileInfo
97   info, err = os.Stat(bookPath)
98   if nil != err {
99     fmt.Fprintln(w, "Failed to read file metadata:", bookPath, err)
100     return
101   }
102   modTime := info.ModTime()
103   
104   var fd *os.File
105   fd, err = os.Open(bookPath)
106   if nil != err {
107     fmt.Fprintln(w, "Failed to open file:", bookPath, err)
108     return
109   }
110   defer fd.Close()
111
112   // TODO:  handle non-ASCII file names.  Need to look up the permutations on how to encode that.
113   w.Header().Set("Content-Disposition", "attachment; filename=" + bookFileName)
114   w.Header().Set("Content-Type", mimeType)
115   http.ServeContent(w, r, bookFileName, modTime, fd)
116 }
117
118 func handleDownload(w http.ResponseWriter, r *http.Request) {
119   path := r.URL.Path[1:]
120   tok := strings.Split(path, "/")
121   if 2 != len(tok)  {
122     fmt.Fprintln(w, "Unexpected path for download:", path)
123     return
124   }
125   efsId, err := strconv.Atoi(tok[1])
126   if (nil != err) || (0 == efsId) {
127     fmt.Fprintln(w, "Invalid id for download:", path, err)
128     return
129   }
130   
131   mimeType := queryMimeTypeByEfsId(efsId)
132   if 0 == len(mimeType) {
133     fmt.Fprintln(w, "No MIME type found for id:", efsId)
134     return
135   }
136
137   efsPath := efsPathForId(efsId)
138
139   var fd *os.File
140   fd, err = os.Open(efsPath)
141   if nil != err {
142     fmt.Fprintln(w, "Failed to read file for id:", efsId, err)
143     return
144   }
145   defer fd.Close()
146
147   w.Header().Set("Content-Type", mimeType)
148   io.Copy(w, fd)
149 }
150
151 func handleInfo(w http.ResponseWriter, r *http.Request) {
152   idParams := r.Form[PARAM_IDS]
153   if 1 != len(idParams) {
154     fmt.Fprintln(w, "ERROR!  Detected either zero or multiple ids= parameters.  Exactly one expected.")
155     return 
156   } 
157
158   idParam := idParams[0]
159   idStrings := strings.Split(idParam, ",")
160   ids := make([]int, len(idStrings))
161   var err error
162   for i, v := range(idStrings) {
163     ids[i], err = strconv.Atoi(v)
164     if nil != err {
165       ids[i] = 0
166     }
167   }
168
169   books := queryBooksByIds(ids)
170
171   var jsonValue []byte
172   jsonValue, err = json.Marshal(books)
173   if nil != err {
174     fmt.Fprintln(w, "ERROR!", err)
175   } else {
176     w.Write(jsonValue)
177   }
178 }
179
180 func handleSearch(w http.ResponseWriter, r *http.Request) {
181   var err error
182   fmt.Println("DEBUG:  handleSearch():  " + r.URL.Path)
183
184   fields := []Field{Author, Title, Series}
185
186   terms := make([]SearchTerm, len(fields))
187   
188   count := 0
189   for _, fv := range(fields) {
190     paramName := fv.String()
191     paramValues := r.Form[paramName]
192     for _, pv := range(paramValues) {
193       fmt.Println("DEBUG:  handleSearch():  ", paramName, "=", pv)
194       if count >= len(terms) {
195         fmt.Printf("WARNING:  limit of %d search terms exceeded.  One or more terms ignored.", len(terms))
196         break
197       }
198       terms[count] = SearchTerm{Attribute:fv, Text:pv}
199       count++
200     }
201   }
202
203   terms = terms[:count]
204
205   ids := queryIds(terms)
206
207   jsonValue, err := json.Marshal(ids)
208   if nil != err {
209     fmt.Fprintln(w, "ERROR!", err)
210   } else {
211     w.Write(jsonValue)
212   }
213 }
214