Store files on GridFS

This commit is contained in:
Las Zenow 2013-04-12 01:05:40 +02:00
parent 64375b6de5
commit 05b641201c
8 changed files with 94 additions and 75 deletions

View file

@ -235,13 +235,12 @@ func storeHandler(w http.ResponseWriter, r *http.Request) {
continue continue
} }
book := books[0] book := books[0]
path, err := StoreBook(book)
if err != nil { if err != nil {
sess.Notify("An error ocurred!", err.Error(), "error") sess.Notify("An error ocurred!", err.Error(), "error")
log.Println("Error storing book '", book.Title, "': ", err.Error()) log.Println("Error storing book '", book.Title, "': ", err.Error())
continue continue
} }
db.UpdateBook(id, bson.M{"active": true, "path": path}) db.UpdateBook(id, bson.M{"active": true})
titles = append(titles, book.Title) titles = append(titles, book.Title)
} }
if titles != nil { if titles != nil {

View file

@ -9,6 +9,8 @@ const (
BOOKS_COLL = "books" BOOKS_COLL = "books"
TAGS_COLL = "tags" TAGS_COLL = "tags"
USERS_COLL = "users" USERS_COLL = "users"
FS_BOOKS = "fs_books"
FS_IMGS = "fs_imgs"
PASS_SALT = "ImperialLibSalt" PASS_SALT = "ImperialLibSalt"
MINUTES_UPDATE_TAGS = 10 MINUTES_UPDATE_TAGS = 10
@ -17,7 +19,6 @@ const (
NEW_ITEMS_PAGE = 50 NEW_ITEMS_PAGE = 50
TEMPLATE_PATH = "templates/" TEMPLATE_PATH = "templates/"
BOOKS_PATH = "books/"
COVER_PATH = "cover/" COVER_PATH = "cover/"
NEW_PATH = "new/" NEW_PATH = "new/"
CSS_PATH = "css/" CSS_PATH = "css/"

View file

@ -31,7 +31,7 @@ type Book struct {
Coverage string Coverage string
Rights string Rights string
Meta string Meta string
Path string File bson.ObjectId
Cover string Cover string
CoverSmall string CoverSmall string
Active bool Active bool
@ -102,8 +102,8 @@ func (d *DB) IncVisit(id bson.ObjectId) error {
return d.books.Update(bson.M{"_id": id}, bson.M{"$inc": bson.M{"VisitsCount": 1}}) return d.books.Update(bson.M{"_id": id}, bson.M{"$inc": bson.M{"VisitsCount": 1}})
} }
func (d *DB) IncDownload(path string) error { func (d *DB) IncDownload(id bson.ObjectId) error {
return d.books.Update(bson.M{"path": path}, bson.M{"$inc": bson.M{"DownloadCount": 1}}) return d.books.Update(bson.M{"_id": id}, bson.M{"$inc": bson.M{"DownloadCount": 1}})
} }
/* optional parameters: length and start index /* optional parameters: length and start index
@ -178,6 +178,10 @@ func (d *DB) BookActive(id bson.ObjectId) bool {
return book.Active return book.Active
} }
func (d *DB) GetFS(prefix string) *mgo.GridFS {
return d.session.DB(DB_NAME).GridFS(prefix)
}
func (d *DB) areTagsOutdated() bool { func (d *DB) areTagsOutdated() bool {
var result struct { var result struct {
Id bson.ObjectId `bson:"_id"` Id bson.ObjectId `bson:"_id"`

View file

@ -149,7 +149,6 @@ func readHandler(w http.ResponseWriter, r *http.Request) {
var data readData var data readData
data.Book = books[0] data.Book = books[0]
var bookPath string
if !data.Book.Active { if !data.Book.Active {
sess := GetSession(r) sess := GetSession(r)
if sess.User == "" { if sess.User == "" {
@ -157,12 +156,10 @@ func readHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
data.Back = "/new/" data.Back = "/new/"
bookPath = NEW_PATH + data.Book.Path
} else { } else {
data.Back = "/book/" + id data.Back = "/book/" + id
bookPath = BOOKS_PATH + data.Book.Path
} }
e, err := epubgo.Open(bookPath) e, err := OpenBook(data.Book.File)
if err != nil { if err != nil {
http.NotFound(w, r) http.NotFound(w, r)
return return
@ -187,29 +184,30 @@ func readHandler(w http.ResponseWriter, r *http.Request) {
func contentHandler(w http.ResponseWriter, r *http.Request) { func contentHandler(w http.ResponseWriter, r *http.Request) {
_, id, file := parseUrl(r.URL.Path) _, id, file := parseUrl(r.URL.Path)
if file == "" {
http.NotFound(w, r)
return
}
books, _, err := db.GetBooks(bson.M{"_id": bson.ObjectIdHex(id)}) books, _, err := db.GetBooks(bson.M{"_id": bson.ObjectIdHex(id)})
if err != nil || len(books) == 0 { if err != nil || len(books) == 0 {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
book := books[0] book := books[0]
var bookPath string
if !book.Active { if !book.Active {
sess := GetSession(r) sess := GetSession(r)
if sess.User == "" { if sess.User == "" {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
bookPath = NEW_PATH + book.Path
} else {
bookPath = BOOKS_PATH + book.Path
} }
e, _ := epubgo.Open(bookPath) e, err := OpenBook(book.File)
defer e.Close() if err != nil {
if file == "" {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
defer e.Close()
html, err := e.OpenFile(file) html, err := e.OpenFile(file)
if err != nil { if err != nil {

View file

@ -3,19 +3,19 @@ package main
import ( import (
"git.gitorious.org/go-pkg/epubgo.git" "git.gitorious.org/go-pkg/epubgo.git"
"io" "io"
"log" "labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"os" "os"
"os/exec"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
func ParseFile(path string) (string, error) { func ParseFile(id bson.ObjectId) (string, error) {
book := map[string]interface{}{} book := map[string]interface{}{}
e, err := epubgo.Open(NEW_PATH + path) e, err := OpenBook(id)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -51,7 +51,7 @@ func ParseFile(path string) (string, error) {
} }
} }
title, _ := book["title"].(string) title, _ := book["title"].(string)
book["path"] = path book["file"] = id
cover, coverSmall := GetCover(e, title) cover, coverSmall := GetCover(e, title)
book["cover"] = cover book["cover"] = cover
book["coversmall"] = coverSmall book["coversmall"] = coverSmall
@ -61,35 +61,47 @@ func ParseFile(path string) (string, error) {
return title, nil return title, nil
} }
func StoreNewFile(name string, file io.Reader) (string, error) { func OpenBook(id bson.ObjectId) (*epubgo.Epub, error) {
path := storePath(name) fs := db.GetFS(FS_BOOKS)
fw, err := os.Create(NEW_PATH + path) var reader readerGrid
var err error
reader.f, err = fs.OpenId(id)
if err != nil {
return nil, err
}
defer reader.f.Close()
return epubgo.Load(reader, reader.f.Size())
}
type readerGrid struct {
f *mgo.GridFile
}
func (r readerGrid) ReadAt(p []byte, off int64) (n int, err error) {
_, err = r.f.Seek(off, 0)
if err != nil {
return
}
return r.f.Read(p)
}
func StoreNewFile(name string, file io.Reader) (bson.ObjectId, error) {
fs := db.GetFS(FS_BOOKS)
fw, err := fs.Create(name)
if err != nil { if err != nil {
return "", err return "", err
} }
defer fw.Close() defer fw.Close()
_, err = io.Copy(fw, file) _, err = io.Copy(fw, file)
return path, err id, _ := fw.Id().(bson.ObjectId)
return id, err
} }
func StoreBook(book Book) (path string, err error) { func DeleteFile(id bson.ObjectId) error {
title := book.Title fs := db.GetFS(FS_BOOKS)
path = validFileName(BOOKS_PATH, title, ".epub") return fs.RemoveId(id)
oldPath := NEW_PATH + book.Path
r, _ := utf8.DecodeRuneInString(title)
folder := string(r)
if _, err = os.Stat(BOOKS_PATH + folder); err != nil {
err = os.Mkdir(BOOKS_PATH+folder, os.ModePerm)
if err != nil {
log.Println("Error creating", BOOKS_PATH+folder, ":", err.Error())
return
}
}
cmd := exec.Command("mv", oldPath, BOOKS_PATH+path)
err = cmd.Run()
return
} }
func DeleteBook(book Book) { func DeleteBook(book Book) {
@ -99,7 +111,7 @@ func DeleteBook(book Book) {
if book.CoverSmall != "" { if book.CoverSmall != "" {
os.RemoveAll(book.CoverSmall[1:]) os.RemoveAll(book.CoverSmall[1:])
} }
os.RemoveAll(book.Path) DeleteFile(book.File)
} }
func validFileName(path string, title string, extension string) string { func validFileName(path string, title string, extension string) string {
@ -117,16 +129,6 @@ func validFileName(path string, title string, extension string) string {
return file return file
} }
func storePath(name string) string {
path := name
_, err := os.Stat(NEW_PATH + path)
for i := 0; err == nil; i++ {
path = strconv.Itoa(i) + "_" + name
_, err = os.Stat(NEW_PATH + path)
}
return path
}
func cleanStr(str string) string { func cleanStr(str string) string {
str = strings.Replace(str, "'", "'", -1) str = strings.Replace(str, "'", "'", -1)
exp, _ := regexp.Compile("&[^;]*;") exp, _ := regexp.Compile("&[^;]*;")

View file

@ -54,7 +54,7 @@ function delBook(){
{{end}} {{end}}
<div class="row"> <div class="row">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a href="/books/{{.Path}}" class="btn btn-large btn-inverse"><i class="icon-download-alt icon-white"></i> download</a> <a href="/books/{{.Id}}" class="btn btn-large btn-inverse"><i class="icon-download-alt icon-white"></i> download</a>
<a href="/read/{{.Id}}" class="btn btn-large btn-warning"><i class="icon-eye-open icon-white"></i> read it!</a> <a href="/read/{{.Id}}" class="btn btn-large btn-warning"><i class="icon-eye-open icon-white"></i> read it!</a>
</div> </div>
</div> </div>

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"io"
"labix.org/v2/mgo/bson" "labix.org/v2/mgo/bson"
"log" "log"
"net/http" "net/http"
@ -65,9 +66,27 @@ func bookHandler(w http.ResponseWriter, r *http.Request) {
} }
func downloadHandler(w http.ResponseWriter, r *http.Request) { func downloadHandler(w http.ResponseWriter, r *http.Request) {
file := BOOKS_PATH + r.URL.Path[len("/books/"):] id := bson.ObjectIdHex(r.URL.Path[len("/books/"):])
db.IncDownload(file) books, _, err := db.GetBooks(bson.M{"_id": id})
http.ServeFile(w, r, file) if err != nil || len(books) == 0 {
http.NotFound(w, r)
return
}
fs := db.GetFS(FS_BOOKS)
f, err := fs.OpenId(books[0].File)
if err != nil {
http.NotFound(w, r)
return
}
defer f.Close()
headers := w.Header()
headers["Content-Type"] = []string{"application/epub+zip"}
headers["Content-Disposition"] = []string{"attachment; filename=\"" + f.Name() + "\""}
io.Copy(w, f)
db.IncDownload(id)
} }
type indexData struct { type indexData struct {
@ -97,10 +116,6 @@ func main() {
/* create the needed folders */ /* create the needed folders */
var err error var err error
_, err = os.Stat(BOOKS_PATH)
if err != nil {
os.Mkdir(BOOKS_PATH, os.ModePerm)
}
_, err = os.Stat(COVER_PATH) _, err = os.Stat(COVER_PATH)
if err != nil { if err != nil {
os.Mkdir(COVER_PATH, os.ModePerm) os.Mkdir(COVER_PATH, os.ModePerm)

View file

@ -1,30 +1,30 @@
package main package main
import ( import (
"labix.org/v2/mgo/bson"
"log" "log"
"net/http" "net/http"
"os"
) )
func storeFiles(r *http.Request) ([]string, error) { func storeFiles(r *http.Request) ([]bson.ObjectId, error) {
r.ParseMultipartForm(20000000) r.ParseMultipartForm(20000000)
filesForm := r.MultipartForm.File["epub"] filesForm := r.MultipartForm.File["epub"]
paths := make([]string, 0, len(filesForm)) ids := make([]bson.ObjectId, 0, len(filesForm))
for _, f := range filesForm { for _, f := range filesForm {
log.Println("File uploaded:", f.Filename) log.Println("File uploaded:", f.Filename)
file, err := f.Open() file, err := f.Open()
if err != nil { if err != nil {
return paths, err return ids, err
} }
defer file.Close() defer file.Close()
path, err := StoreNewFile(f.Filename, file) id, err := StoreNewFile(f.Filename, file)
if err != nil { if err != nil {
return paths, err return ids, err
} }
paths = append(paths, path) ids = append(ids, id)
} }
return paths, nil return ids, nil
} }
type uploadData struct { type uploadData struct {
@ -34,17 +34,17 @@ type uploadData struct {
func uploadHandler(w http.ResponseWriter, r *http.Request) { func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" { if r.Method == "POST" {
sess := GetSession(r) sess := GetSession(r)
paths, err := storeFiles(r) ids, err := storeFiles(r)
if err != nil { if err != nil {
sess.Notify("Problem uploading!", "Some files were not stored. Try again or contact us if it keeps happening", "error") sess.Notify("Problem uploading!", "Some files were not stored. Try again or contact us if it keeps happening", "error")
} }
uploaded := "" uploaded := ""
for _, path := range paths { for _, id := range ids {
title, err := ParseFile(path) title, err := ParseFile(id)
if err != nil { if err != nil {
os.Remove(NEW_PATH + path) DeleteFile(id)
sess.Notify("Problem uploading!", "The file '"+path+"' is not a well formed epub: "+err.Error(), "error") sess.Notify("Problem uploading!", "The file is not a well formed epub: "+err.Error(), "error")
} else { } else {
uploaded = uploaded + " '" + title + "'" uploaded = uploaded + " '" + title + "'"
} }