From: Chris Jaekl Date: Sat, 4 Nov 2017 13:40:29 +0000 (+0900) Subject: Adds download support for files store in the QuanLib EFS. X-Git-Url: https://jaekl.net/gitweb/?a=commitdiff_plain;h=bb350871a3ae81484ae3a736b16018e340908251;p=quanweb.git Adds download support for files store in the QuanLib EFS. --- diff --git a/main/config.go b/main/config.go index 7aa8195..3d0a6cf 100644 --- a/main/config.go +++ b/main/config.go @@ -1,10 +1,10 @@ package main -type dbConfig struct { - user, pass, dbName string +type qwConfig struct { + user, pass, dbName, efsBasePath string } -func getConfig() dbConfig { +func getConfig() qwConfig { // TODO: use a real password, and load config info from a file - return dbConfig{user:"quanlib", pass:"quanlib", dbName:"quanlib"} + return qwConfig{user:"quanlib", pass:"quanlib", dbName:"quanlib", efsBasePath:"/arc/quanlib/efs"} } diff --git a/main/db.go b/main/db.go index 0f7bf2b..cd2c79c 100644 --- a/main/db.go +++ b/main/db.go @@ -15,6 +15,7 @@ type Book struct { AuthorGrouping string // unique rendering of the author's name, used for internal grouping AuthorReading string // reading order of author's name, e.g. "Charles Dickens" AuthorSort string // sort order of author's name, e.g. "Dickens, Charles" + CoverId int // index into EFS table for cover, if there is one DDC string // Dewey Decimal Classification Description string // Back cover / inside flap blurb, describing the book Genre string // e.g. "adventure", "historical", "mystery", "romance", "sf" (Science Fiction) @@ -63,6 +64,13 @@ func getDb() (*sql.DB) { return g_db } +func niVal(ni sql.NullInt64) int { + if ni.Valid { + return int(ni.Int64) + } + return 0 +} + func nsVal(ns sql.NullString) string { if ns.Valid { return ns.String @@ -88,7 +96,7 @@ func openDb(user, pass, dbName string) (*sql.DB) { } func queryBooksByIds(ids []int) []Book { - query := `SELECT s.age,a.grouping,a.reading,a.sort,c.ddc,b.description,s.genre,c.lcc,s.descr,b.title,b.volume + query := `SELECT s.age,a.grouping,a.reading,a.sort,b.cover,c.ddc,b.description,s.genre,c.lcc,s.descr,b.title,b.volume FROM Authors a INNER JOIN Books b ON a.id=b.author LEFT OUTER JOIN Classifications c ON c.id=b.classification @@ -120,8 +128,9 @@ func queryBooksByIds(ids []int) []Book { row := ps.QueryRow(id) var age, grouping, reading, sort, ddc, description, genre, lcc, name, title, volume sql.NullString + var cover sql.NullInt64 - err = row.Scan(&age, &grouping, &reading, &sort, &ddc, &description, &genre, &lcc, &name, &title, &volume) + err = row.Scan(&age, &grouping, &reading, &sort, &cover, &ddc, &description, &genre, &lcc, &name, &title, &volume) if err != nil { report("Error: Failed to read book:" + strconv.Itoa(id) + ":", err) } else { @@ -131,6 +140,7 @@ func queryBooksByIds(ids []int) []Book { b.AuthorGrouping = nsVal(grouping) b.AuthorReading = nsVal(reading) b.AuthorSort = nsVal(sort) + b.CoverId = niVal(cover) b.DDC = nsVal(ddc) b.Description = nsVal(description) b.Genre = nsVal(genre) @@ -204,6 +214,27 @@ func queryIds(criteria []SearchTerm) []int { return res } +func queryMimeTypeByEfsId(efsId int) string { + const query = "SELECT mimeType FROM Efs WHERE id=$1" + + ps, err := getDb().Prepare(query) + if nil != err { + report("Failed to Prepare query: " + query, err) + return "" + } + defer ps.Close() + + row := ps.QueryRow(efsId) + var mimeType sql.NullString + err = row.Scan(&mimeType) + if nil != err { + report(fmt.Sprintf("Failed to retrieve mimeType for id %v: ", efsId), err) + return "" + } + + return nsVal(mimeType) +} + func report(msg string, err error) { fmt.Println("Error: " + msg, err) } diff --git a/main/handler.go b/main/handler.go index 52d08e5..255f193 100644 --- a/main/handler.go +++ b/main/handler.go @@ -3,7 +3,9 @@ package main import ( "encoding/json" "fmt" + "io" "net/http" + "os" "strconv" "strings" ) @@ -12,6 +14,15 @@ const PARAM_IDS = "ids" const MAX_TERMS = 10 // ============================================================================ +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 +30,11 @@ 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 "download": + handleDownload(w, r) case "info": handleInfo(w, r) case "search": @@ -35,6 +48,38 @@ func handler(w http.ResponseWriter, r *http.Request) { } } +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() + + io.Copy(w, fd) +} + func handleInfo(w http.ResponseWriter, r *http.Request) { idParams := r.Form[PARAM_IDS] if 1 != len(idParams) {