diff --git a/admin.go b/admin.go index acf9d32..af9f325 100644 --- a/admin.go +++ b/admin.go @@ -5,9 +5,15 @@ import ( "labix.org/v2/mgo/bson" "net/http" "os" + "os/exec" + "strconv" ) -func deleteHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) { +const ( + PATH = "books/" +) + +func deleteHandler(coll *mgo.Collection, url string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { sess := GetSession(r) if sess.User == "" { @@ -15,6 +21,7 @@ func deleteHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request return } + // cutre hack: /delete/ and /delnew/ have the same lenght: id := bson.ObjectIdHex(r.URL.Path[len("/delete/"):]) books, err := GetBook(coll, bson.M{"_id": id}) if err != nil { @@ -28,7 +35,7 @@ func deleteHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request coll.Remove(bson.M{"_id": id}) sess.Notify("Removed book!", "The book '"+book.Title+"' it's completly removed", "success") sess.Save(w, r) - http.Redirect(w, r, "/", 307) + http.Redirect(w, r, url, 307) } } @@ -101,6 +108,12 @@ func saveHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) } } +type newData struct { + S Status + Found int + Books []Book +} + func newHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { sess := GetSession(r) @@ -108,5 +121,47 @@ func newHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) { http.NotFound(w, r) return } + + res, _ := GetBook(coll, bson.M{}) + var data newData + data.S = GetStatus(w, r) + data.Found = len(res) + data.Books = res + loadTemplate(w, "new", data) + } +} + +func storeHandler(newColl, coll *mgo.Collection) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + sess := GetSession(r) + if sess.User == "" { + http.NotFound(w, r) + return + } + + id := bson.ObjectIdHex(r.URL.Path[len("/store/"):]) + books, err := GetBook(newColl, bson.M{"_id": id}) + if err != nil { + http.NotFound(w, r) + return + } + book := books[0] + + path := PATH + book.Title[:1] + "/" + book.Title + ".epub" + _, err = os.Stat(path) + for i := 0; err == nil; i++ { + path := PATH + book.Title[:1] + "/" + book.Title + "_" + strconv.Itoa(i) + ".epub" + _, err = os.Stat(path) + } + + os.Mkdir(PATH+book.Title[:1], os.ModePerm) + cmd := exec.Command("mv", book.Path, path) + cmd.Run() + book.Path = path + coll.Insert(book) + newColl.Remove(bson.M{"_id": id}) + sess.Notify("Store book!", "The book '"+book.Title+"' it's stored for public download", "success") + sess.Save(w, r) + http.Redirect(w, r, "/new/", 307) } } diff --git a/template.go b/template.go index 12648b0..ea0848b 100644 --- a/template.go +++ b/template.go @@ -36,6 +36,7 @@ func loadTemplate(w http.ResponseWriter, tmpl string, data interface{}) { TEMPLATE_DIR+"book.html", TEMPLATE_DIR+"search.html", TEMPLATE_DIR+"upload.html", + TEMPLATE_DIR+"new.html", TEMPLATE_DIR+"edit.html", )) diff --git a/templates/new.html b/templates/new.html new file mode 100644 index 0000000..e8f1535 --- /dev/null +++ b/templates/new.html @@ -0,0 +1,41 @@ +{{template "header.html" .S}} + +

Found {{.Found}} books.

+ +{{with .Books}} + {{range .}} +
+
+

{{if .CoverSmall}}{{.Title}}{{end}}

+
+
+

{{.Title}}
+ {{if .Author}}Author: {{range .Author}}{{.}}, {{end}}
{{end}} + {{if .Publisher}}Publisher: {{.Publisher}}
{{end}} + {{if .Subject}}Tags: {{range .Subject}}{{.}}, {{end}}
{{end}} + {{if .Date}}Date: {{.Date}}
{{end}} + {{if .Lang}}Lang: {{range .Lang}}{{.}} {{end}}
{{end}} + {{.Description}} +

+
+
+ +

+ +
+
+ {{end}} +{{end}} +
+
+ +
+
+ + +{{template "footer.html"}} diff --git a/templates/upload.html b/templates/upload.html index 704d9cd..689b750 100644 --- a/templates/upload.html +++ b/templates/upload.html @@ -1,10 +1,10 @@ {{template "header.html" .S}} -{{if .Msg}}

{{.Msg}}

{{end}} +

Upload your epubs to help the library.

-
+ - +
{{template "footer.html"}} diff --git a/trantor.go b/trantor.go index 1f0916b..2c9f70f 100644 --- a/trantor.go +++ b/trantor.go @@ -108,13 +108,15 @@ func main() { http.HandleFunc("/book/", bookHandler(coll)) http.HandleFunc("/search/", searchHandler(coll)) - http.HandleFunc("/upload/", uploadHandler(coll)) + http.HandleFunc("/upload/", uploadHandler(newColl)) http.HandleFunc("/login/", loginHandler(userColl)) http.HandleFunc("/logout/", logoutHandler) http.HandleFunc("/new/", newHandler(newColl)) + http.HandleFunc("/delnew/", deleteHandler(newColl, "/new/")) + http.HandleFunc("/store/", storeHandler(newColl, coll)) http.HandleFunc("/edit/", editHandler(coll)) http.HandleFunc("/save/", saveHandler(coll)) - http.HandleFunc("/delete/", deleteHandler(coll)) + http.HandleFunc("/delete/", deleteHandler(coll, "/")) http.HandleFunc("/about/", aboutHandler) fileHandler("/img/") fileHandler("/cover/") diff --git a/upload.go b/upload.go index 5f629fd..08faa3d 100644 --- a/upload.go +++ b/upload.go @@ -1,11 +1,21 @@ package main import ( + "git.gitorious.org/go-pkg/epub.git" "labix.org/v2/mgo" - "os" - "strconv" //"labix.org/v2/mgo/bson" "net/http" + "os" + "os/exec" + "regexp" + "strconv" + "strings" +) + +const ( + COVER_PATH = "cover/" + RESIZE = "/usr/bin/convert -resize 300 -quality 60 " + RESIZE_THUMB = "/usr/bin/convert -resize 60 -quality 60 " ) func storePath(name string) string { @@ -18,16 +28,17 @@ func storePath(name string) string { return path } -func storeFile(r *http.Request) error { +func storeFile(r *http.Request) (string, error) { f, header, err := r.FormFile("epub") if err != nil { - return err + return "", err } defer f.Close() - fw, err := os.Create(storePath(header.Filename)) + path := storePath(header.Filename) + fw, err := os.Create(path) if err != nil { - return err + return "", err } defer fw.Close() @@ -39,29 +50,218 @@ func storeFile(r *http.Request) error { fw.Write(buff) } - return nil + return path, nil +} + +func cleanStr(str string) string { + str = strings.Replace(str, "'", "'", -1) + exp, _ := regexp.Compile("[ ,]*$") + str = exp.ReplaceAllString(str, "") + return str +} + +func storeImg(img []byte, title, extension string) (string, string) { + name := title + folder := COVER_PATH + name[:1] + "/" + os.Mkdir(folder, os.ModePerm) + imgPath := folder + name + extension + _, err := os.Stat(imgPath) + for i := 0; err == nil; i++ { + name = title + "_" + strconv.Itoa(i) + imgPath = folder + name + extension + _, err = os.Stat(imgPath) + } + + /* store img on disk */ + file, _ := os.Create(imgPath) + defer file.Close() + file.Write(img) + + /* resize img */ + resize := append(strings.Split(RESIZE, " "), imgPath, imgPath) + cmd := exec.Command(resize[0], resize[1:]...) + cmd.Run() + imgPathSmall := folder + name + "_small" + extension + resize = append(strings.Split(RESIZE_THUMB, " "), imgPath, imgPathSmall) + cmd = exec.Command(resize[0], resize[1:]...) + cmd.Run() + return "/" + imgPath, "/" + imgPathSmall +} + +func getCover(e *epub.Epub, title string) (string, string) { + /* Try first common names */ + img := e.Data("cover.jpg") + if len(img) != 0 { + return storeImg(img, title, ".jpg") + } + img = e.Data("cover.jpeg") + if len(img) != 0 { + return storeImg(img, title, ".jpg") + } + img = e.Data("cover1.jpg") + if len(img) != 0 { + return storeImg(img, title, ".jpg") + } + img = e.Data("cover1.jpeg") + if len(img) != 0 { + return storeImg(img, title, ".jpg") + } + + /* search for img on the text */ + exp, _ := regexp.Compile("]*>") + str = exp.ReplaceAllString(str, "") + str = strings.Replace(str, "&", "&", -1) + str = strings.Replace(str, "<", "<", -1) + str = strings.Replace(str, ">", ">", -1) + str = strings.Replace(str, "\\n", "\n", -1) + return str +} + +func parseSubject(subject []string) []string { + var res []string + for _, s := range subject { + res = append(res, strings.Split(s, " / ")...) + } + return res +} + +func parseDate(date []string) string { + if len(date) == 0 { + return "" + } + return strings.Replace(date[0], "Unspecified: ", "", -1) +} + +func keywords(b map[string] interface{}) (k []string) { + title, _ := b["title"].(string) + k = strings.Split(title, " ") + author, _ := b["author"].([]string) + for _, a := range author { + k = append(k, strings.Split(a, " ")...) + } + publisher, _ := b["publisher"].(string) + k = append(k, strings.Split(publisher, " ")...) + subject, _ := b["subject"].([]string) + k = append(k, subject...) + return +} + +func parseFile(coll *mgo.Collection, path string) (string, error) { + book := map[string] interface{}{} + + e, err := epub.Open(path, 0) + if err != nil { + return "", err + } + defer e.Close() + + title := cleanStr(strings.Join(e.Metadata(epub.EPUB_TITLE), ", ")) + book["title"] = title + book["author"] = parseAuthr(e.Metadata(epub.EPUB_CREATOR)) + book["contributor"] = cleanStr(strings.Join(e.Metadata(epub.EPUB_CONTRIB), ", ")) + book["publisher"] = cleanStr(strings.Join(e.Metadata(epub.EPUB_PUBLISHER), ", ")) + book["description"] = parseDescription(e.Metadata(epub.EPUB_DESCRIPTION)) + book["subject"] = parseSubject(e.Metadata(epub.EPUB_SUBJECT)) + book["date"] = parseDate(e.Metadata(epub.EPUB_DATE)) + book["lang"] = e.Metadata(epub.EPUB_LANG) + book["type"] = strings.Join(e.Metadata(epub.EPUB_TYPE), ", ") + book["format"] = strings.Join(e.Metadata(epub.EPUB_FORMAT), ", ") + book["source"] = strings.Join(e.Metadata(epub.EPUB_SOURCE), ", ") + book["relation"] = strings.Join(e.Metadata(epub.EPUB_RELATION), ", ") + book["coverage"] = strings.Join(e.Metadata(epub.EPUB_COVERAGE), ", ") + book["rights"] = strings.Join(e.Metadata(epub.EPUB_RIGHTS), ", ") + book["meta"] = strings.Join(e.Metadata(epub.EPUB_META), ", ") + book["path"] = path + cover, coverSmall := getCover(e, title) + book["cover"] = cover + book["coversmall"] = coverSmall + book["keywords"] = keywords(book) + + coll.Insert(book) + return title, nil } type uploadData struct { - S Status - Msg string + S Status } func uploadHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - var data uploadData - data.S = GetStatus(w, r) - data.S.Upload = true - data.Msg = "" if r.Method == "POST" { - err := storeFile(r) + path, err := storeFile(r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - data.Msg = "Upload successful." + title, err := parseFile(coll, path) + sess := GetSession(r) + if err != nil { + os.Remove(path) + sess.Notify("Problem uploading!", "The file is not a well formed epub", "error") + } else { + sess.Notify("Upload successful!", "Added '"+title+"'. Thank you for your contribution", "success") + } } + var data uploadData + data.S = GetStatus(w, r) + data.S.Upload = true loadTemplate(w, "upload", data) } }