package main import ( "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strconv" "strings" ) 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 { fmt.Fprintln(w, "ERROR!", err) return } 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()) } } // 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) { fmt.Fprintln(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) { ids[i], err = strconv.Atoi(v) if nil != err { ids[i] = 0 } } books := queryBooksByIds(ids) var jsonValue []byte jsonValue, err = json.Marshal(books) if nil != err { fmt.Fprintln(w, "ERROR!", err) } else { w.Write(jsonValue) } } func handleSearch(w http.ResponseWriter, r *http.Request) { var err error fields := []Field{Author, Title, Series} 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 %d search terms exceeded. One or more terms ignored.", len(terms)) break } terms[count] = SearchTerm{Attribute:fv, Text:pv} count++ } } terms = terms[:count] ids := queryIds(terms) jsonValue, err := json.Marshal(ids) if nil != err { fmt.Fprintln(w, "ERROR!", err) } else { w.Write(jsonValue) } }