Moderate incoming epubs
This commit is contained in:
parent
67907a3629
commit
ff6d44d821
6 changed files with 321 additions and 22 deletions
59
admin.go
59
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
41
templates/new.html
Normal 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"}}
|
|
@ -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"}}
|
||||
|
|
|
@ -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
230
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("<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, "&", "&", -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)
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue