Moderate incoming epubs

This commit is contained in:
Las Zenow 2012-08-20 14:25:18 +02:00
parent 67907a3629
commit ff6d44d821
6 changed files with 321 additions and 22 deletions

View file

@ -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)
}
}

View file

@ -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",
))

41
templates/new.html Normal file
View file

@ -0,0 +1,41 @@
{{template "header.html" .S}}
<p class="centered">Found {{.Found}} books.</p>
{{with .Books}}
{{range .}}
<div class="row">
<div class="span1">
<p class="pull-right">{{if .CoverSmall}}<img src="{{.CoverSmall}}" alt="{{.Title}}" />{{end}}</p>
</div>
<div class="span9">
<p><a href="/search/?q=title:{{.Title}}"><strong>{{.Title}}</strong></a><br />
{{if .Author}}<strong>Author:</strong> {{range .Author}}<a href="/search/?q=author:{{.}}">{{.}}</a>, {{end}}<br />{{end}}
{{if .Publisher}}<strong>Publisher:</strong> <a href="/search/?q=publisher:{{.Publisher}}">{{.Publisher}}</a><br />{{end}}
{{if .Subject}}<strong>Tags:</strong> {{range .Subject}}<a href="/search/?q=subject:{{.}}">{{.}}</a>, {{end}}<br />{{end}}
{{if .Date}}<strong>Date:</strong> {{.Date}}<br />{{end}}
{{if .Lang}}<strong>Lang:</strong> {{range .Lang}}<a href="/search/?q=lang:{{.}}">{{.}}</a> {{end}}<br />{{end}}
{{.Description}}
</p>
</div>
<div class="span2">
<div class="row">
<p><a href="/{{.Path}}" class="btn btn-inverse pull-right"><i class="icon-download-alt icon-white"></i> download</a></p>
</div>
<div class="row"><p></p></div>
<div class="row btn-group pull-right">
<a href="/store/{{.Id}}" class="btn btn-success"><i class="icon-ok"></i> Save</a>
<a href="/delnew/{{.Id}}" class="btn btn-danger"><i class="icon-remove"></i> Delete</a>
</div>
</div>
</div>
{{end}}
{{end}}
<div class="row">
<div class="span12">
<button type="submit" class="btn btn-large btn-primary pull-right">Save changes</button>
</div>
</div>
</form>
{{template "footer.html"}}

View file

@ -1,10 +1,10 @@
{{template "header.html" .S}}
{{if .Msg}}<p>{{.Msg}}</p>{{end}}
<p>Upload your epubs to help the library.</p>
<form method="POST" enctype="multipart/form-data">
<form class="well form-inline" method="POST" enctype="multipart/form-data">
<input accept="application/epub+zip" type="file" name="epub" />
<input type="submit" />
<button type="submit" class="btn">Submit</button>
</form>
{{template "footer.html"}}

View file

@ -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/")

230
upload.go
View file

@ -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, "&#39;", "'", -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("<img.*src=[\"']([^\"']*(\\.[^\\.\"']*))[\"']")
it := e.Iterator(epub.EITERATOR_SPINE)
defer it.Close()
var err error = nil
txt := it.Curr()
for err == nil {
res := exp.FindStringSubmatch(txt)
if res != nil {
urlPart := strings.Split(it.CurrUrl(), "/")
url := strings.Join(urlPart[:len(urlPart)-1], "/")
if res[1][:3] == "../" {
res[1] = res[1][3:]
url = strings.Join(urlPart[:len(urlPart)-2], "/")
}
res[1] = strings.Replace(res[1], "%20", " ", -1)
res[1] = strings.Replace(res[1], "%27", "'", -1)
res[1] = strings.Replace(res[1], "%28", "(", -1)
res[1] = strings.Replace(res[1], "%29", ")", -1)
if url == "" {
url = res[1]
} else {
url = url + "/" + res[1]
}
img := e.Data(url)
if len(img) != 0 {
return storeImg(img, title, res[2])
}
}
txt, err = it.Next()
}
return "", ""
}
func parseAuthr(creator []string) []string {
exp1, _ := regexp.Compile("^(.*\\( *([^\\)]*) *\\))*$")
exp2, _ := regexp.Compile("^[^:]*: *(.*)$")
var res []string //TODO: can be predicted the lenght
for _, s := range creator {
auth := exp1.FindStringSubmatch(s)
if auth != nil {
res = append(res, cleanStr(strings.Join(auth[2:], ", ")))
} else {
auth := exp2.FindStringSubmatch(s)
if auth != nil {
res = append(res, cleanStr(auth[1]))
} else {
res = append(res, cleanStr(s))
}
}
}
return res
}
func parseDescription(description []string) string {
str := cleanStr(strings.Join(description, ", "))
exp, _ := regexp.Compile("<[^>]*>")
str = exp.ReplaceAllString(str, "")
str = strings.Replace(str, "&amp;", "&", -1)
str = strings.Replace(str, "&lt;", "<", -1)
str = strings.Replace(str, "&gt;", ">", -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)
}
}