From f4690b2bbad6f3cbe2a196bd9954e531ae8c55cc Mon Sep 17 00:00:00 2001
From: Las Zenow
Date: Thu, 21 Aug 2014 19:24:23 -0500
Subject: [PATCH] Move files to a local folder
---
.gitignore | 1 +
admin.go | 14 +++--
config.go | 7 ++-
cover.go | 79 ++++++++++++---------------
database/books.go | 69 +++++++++++++++---------
database/books_test.go | 55 +++++++++++++++++--
database/database.go | 13 ++---
reader.go | 29 ++++++++--
stats.go | 58 ++++++++++++--------
storage/dir.go | 42 +++++++++++++++
storage/storage.go | 57 ++++++++++++++++++++
storage/storage_test.go | 113 +++++++++++++++++++++++++++++++++++++++
store.go | 115 ----------------------------------------
templates/index.html | 6 +--
templates/new.html | 2 +-
templates/search.html | 2 +-
trantor.go | 81 +++++++++++++++-------------
upload.go | 101 ++++++++++++++++++++++++++++-------
18 files changed, 556 insertions(+), 288 deletions(-)
create mode 100644 storage/dir.go
create mode 100644 storage/storage.go
create mode 100644 storage/storage_test.go
delete mode 100644 store.go
diff --git a/.gitignore b/.gitignore
index 76fdaad..eaa3020 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
trantor.git
+store/
tools/adduser/adduser
tools/update/update
tools/togridfs/togridfs
diff --git a/admin.go b/admin.go
index 6015dfe..e93a010 100644
--- a/admin.go
+++ b/admin.go
@@ -25,7 +25,7 @@ func deleteHandler(h handler) {
h.sess.Notify("Book not found!", "The book with id '"+id+"' is not there", "error")
continue
}
- DeleteBook(book, h.db)
+ h.store.Delete(id)
h.db.DeleteBook(id)
if !book.Active {
@@ -101,7 +101,7 @@ func saveHandler(h handler) {
h.sess.Notify("Book Modified!", "", "success")
h.sess.Save(h.w, h.r)
- if h.db.BookActive(id) {
+ if h.db.IsBookActive(id) {
http.Redirect(h.w, h.r, "/book/"+id, http.StatusFound)
} else {
http.Redirect(h.w, h.r, "/new/", http.StatusFound)
@@ -174,17 +174,25 @@ func storeHandler(h handler) {
var titles []string
ids := strings.Split(mux.Vars(h.r)["ids"], "/")
for _, id := range ids {
+ if id == "" {
+ continue
+ }
book, err := h.db.GetBookId(id)
if err != nil {
h.sess.Notify("Book not found!", "The book with id '"+id+"' is not there", "error")
continue
}
+ if err != nil {
+ h.sess.Notify("An error ocurred!", err.Error(), "error")
+ log.Error("Error getting book for storing '", book.Title, "': ", err.Error())
+ continue
+ }
+ err = h.db.ActiveBook(id)
if err != nil {
h.sess.Notify("An error ocurred!", err.Error(), "error")
log.Error("Error storing book '", book.Title, "': ", err.Error())
continue
}
- h.db.UpdateBook(id, map[string]interface{}{"active": true})
titles = append(titles, book.Title)
}
if titles != nil {
diff --git a/config.go b/config.go
index bd27d3a..81b7487 100644
--- a/config.go
+++ b/config.go
@@ -6,8 +6,10 @@ const (
DB_IP = "127.0.0.1"
DB_NAME = "trantor"
META_COLL = "meta"
- FS_BOOKS = "fs_books"
- FS_IMGS = "fs_imgs"
+
+ EPUB_FILE = "book.epub"
+ COVER_FILE = "cover.jpg"
+ COVER_SMALL_FILE = "coverSmall.jpg"
MINUTES_UPDATE_TAGS = 11
MINUTES_UPDATE_VISITED = 41
@@ -25,6 +27,7 @@ const (
NUM_NEWS = 10
DAYS_NEWS_INDEXPAGE = 15
+ STORE_PATH = "store/"
TEMPLATE_PATH = "templates/"
CSS_PATH = "css/"
JS_PATH = "js/"
diff --git a/cover.go b/cover.go
index 66be75b..6ebd342 100644
--- a/cover.go
+++ b/cover.go
@@ -8,15 +8,13 @@ import _ "image/gif"
import (
"bytes"
"git.gitorious.org/go-pkg/epubgo.git"
- "git.gitorious.org/trantor/trantor.git/database"
+ "git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux"
"github.com/nfnt/resize"
"image"
"image/jpeg"
"io"
"io/ioutil"
- "labix.org/v2/mgo"
- "labix.org/v2/mgo/bson"
"regexp"
"strings"
)
@@ -36,13 +34,11 @@ func coverHandler(h handler) {
}
}
- fs := h.db.GetFS(FS_IMGS)
- var f *mgo.GridFile
+ file := COVER_FILE
if vars["size"] == "small" {
- f, err = fs.OpenId(book.CoverSmall)
- } else {
- f, err = fs.OpenId(book.Cover)
+ file = COVER_SMALL_FILE
}
+ f, err := h.store.Get(book.Id, file)
if err != nil {
log.Error("Error while opening image: ", err)
notFound(h)
@@ -53,18 +49,21 @@ func coverHandler(h handler) {
headers := h.w.Header()
headers["Content-Type"] = []string{"image/jpeg"}
- io.Copy(h.w, f)
+ _, err = io.Copy(h.w, f)
+ if err != nil {
+ log.Error("Error while copying image: ", err)
+ notFound(h)
+ return
+ }
}
-func GetCover(e *epubgo.Epub, title string, db *database.DB) (bson.ObjectId, bson.ObjectId) {
- imgId, smallId := coverFromMetadata(e, title, db)
- if imgId != "" {
- return imgId, smallId
+func GetCover(e *epubgo.Epub, id string, store *storage.Store) bool {
+ if coverFromMetadata(e, id, store) {
+ return true
}
- imgId, smallId = searchCommonCoverNames(e, title, db)
- if imgId != "" {
- return imgId, smallId
+ if searchCommonCoverNames(e, id, store) {
+ return true
}
/* search for img on the text */
@@ -103,52 +102,52 @@ func GetCover(e *epubgo.Epub, title string, db *database.DB) (bson.ObjectId, bso
img, err := e.OpenFile(url)
if err == nil {
defer img.Close()
- return storeImg(img, title, db)
+ return storeImg(img, id, store)
}
}
errNext = it.Next()
}
- return "", ""
+ return false
}
-func coverFromMetadata(e *epubgo.Epub, title string, db *database.DB) (bson.ObjectId, bson.ObjectId) {
+func coverFromMetadata(e *epubgo.Epub, id string, store *storage.Store) bool {
metaList, _ := e.MetadataAttr("meta")
for _, meta := range metaList {
if meta["name"] == "cover" {
img, err := e.OpenFileId(meta["content"])
if err == nil {
defer img.Close()
- return storeImg(img, title, db)
+ return storeImg(img, id, store)
}
}
}
- return "", ""
+ return false
}
-func searchCommonCoverNames(e *epubgo.Epub, title string, db *database.DB) (bson.ObjectId, bson.ObjectId) {
+func searchCommonCoverNames(e *epubgo.Epub, id string, store *storage.Store) bool {
for _, p := range []string{"cover.jpg", "Images/cover.jpg", "images/cover.jpg", "cover.jpeg", "cover1.jpg", "cover1.jpeg"} {
img, err := e.OpenFile(p)
if err == nil {
defer img.Close()
- return storeImg(img, title, db)
+ return storeImg(img, id, store)
}
}
- return "", ""
+ return false
}
-func storeImg(img io.Reader, title string, db *database.DB) (bson.ObjectId, bson.ObjectId) {
+func storeImg(img io.Reader, id string, store *storage.Store) bool {
/* open the files */
- fBig, err := createCoverFile(title, db)
+ fBig, err := store.Create(id, COVER_FILE)
if err != nil {
- log.Error("Error creating ", title, ": ", err.Error())
- return "", ""
+ log.Error("Error creating cover ", id, ": ", err.Error())
+ return false
}
defer fBig.Close()
- fSmall, err := createCoverFile(title+"_small", db)
+ fSmall, err := store.Create(id, COVER_SMALL_FILE)
if err != nil {
- log.Error("Error creating ", title+"_small", ": ", err.Error())
- return "", ""
+ log.Error("Error creating small cover ", id, ": ", err.Error())
+ return false
}
defer fSmall.Close()
@@ -159,32 +158,24 @@ func storeImg(img io.Reader, title string, db *database.DB) (bson.ObjectId, bson
imgResized, err := resizeImg(img1, IMG_WIDTH_BIG)
if err != nil {
log.Error("Error resizing big image: ", err.Error())
- return "", ""
+ return false
}
err = jpeg.Encode(fBig, imgResized, &jpgOptions)
if err != nil {
log.Error("Error encoding big image: ", err.Error())
- return "", ""
+ return false
}
imgSmallResized, err := resizeImg(&img2, IMG_WIDTH_SMALL)
if err != nil {
log.Error("Error resizing small image: ", err.Error())
- return "", ""
+ return false
}
err = jpeg.Encode(fSmall, imgSmallResized, &jpgOptions)
if err != nil {
log.Error("Error encoding small image: ", err.Error())
- return "", ""
+ return false
}
-
- idBig, _ := fBig.Id().(bson.ObjectId)
- idSmall, _ := fSmall.Id().(bson.ObjectId)
- return idBig, idSmall
-}
-
-func createCoverFile(title string, db *database.DB) (*mgo.GridFile, error) {
- fs := db.GetFS(FS_IMGS)
- return fs.Create(title + ".jpg")
+ return true
}
func resizeImg(imgReader io.Reader, width uint) (image.Image, error) {
diff --git a/database/books.go b/database/books.go
index 701bdc8..6bef9d7 100644
--- a/database/books.go
+++ b/database/books.go
@@ -1,7 +1,6 @@
package database
import (
- "errors"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"gopkgs.com/unidecode.v1"
@@ -14,7 +13,7 @@ const (
)
type Book struct {
- Id string `bson:"_id"`
+ Id string
Title string
Author []string
Contributor string
@@ -31,10 +30,8 @@ type Book struct {
Coverage string
Rights string
Meta string
- File bson.ObjectId
FileSize int
- Cover bson.ObjectId
- CoverSmall bson.ObjectId
+ Cover bool
Active bool
Keywords []string
}
@@ -66,35 +63,41 @@ func _getBooks(coll *mgo.Collection, query bson.M, length int, start int) (books
}
err = q.All(&books)
- for i, b := range books {
- books[i].Id = bson.ObjectId(b.Id).Hex()
- }
return
}
func getBookId(coll *mgo.Collection, id string) (Book, error) {
var book Book
- if !bson.IsObjectIdHex(id) {
- return book, errors.New("Not valid book id")
- }
-
- err := coll.FindId(bson.ObjectIdHex(id)).One(&book)
- book.Id = bson.ObjectId(book.Id).Hex()
+ err := coll.Find(bson.M{"id": id}).One(&book)
return book, err
}
func deleteBook(coll *mgo.Collection, id string) error {
- return coll.RemoveId(bson.ObjectIdHex(id))
+ return coll.Remove(bson.M{"id": id})
}
func updateBook(coll *mgo.Collection, id string, data map[string]interface{}) error {
- data["keywords"] = keywords(data)
- return coll.UpdateId(bson.ObjectIdHex(id), bson.M{"$set": data})
+ var book map[string]interface{}
+ err := coll.Find(bson.M{"id": id}).One(&book)
+ if err != nil {
+ return err
+ }
+ for k, v := range data {
+ book[k] = v
+ }
+
+ data["keywords"] = keywords(book)
+ return coll.Update(bson.M{"id": id}, bson.M{"$set": data})
}
-func bookActive(coll *mgo.Collection, id string) bool {
+func activeBook(coll *mgo.Collection, id string) error {
+ data := map[string]interface{}{"active": true}
+ return coll.Update(bson.M{"id": id}, bson.M{"$set": data})
+}
+
+func isBookActive(coll *mgo.Collection, id string) bool {
var book Book
- err := coll.FindId(bson.ObjectIdHex(id)).One(&book)
+ err := coll.Find(bson.M{"id": id}).One(&book)
if err != nil {
return false
}
@@ -123,15 +126,29 @@ func buildQuery(q string) bson.M {
func keywords(b map[string]interface{}) (k []string) {
title, _ := b["title"].(string)
k = tokens(title)
- author, _ := b["author"].([]string)
- for _, a := range author {
- k = append(k, tokens(a)...)
- }
+
+ k = append(k, listKeywords(b["author"])...)
+
publisher, _ := b["publisher"].(string)
k = append(k, tokens(publisher)...)
- subject, _ := b["subject"].([]string)
- for _, s := range subject {
- k = append(k, tokens(s)...)
+
+ k = append(k, listKeywords(b["subject"])...)
+ return
+}
+
+func listKeywords(v interface{}) (k []string) {
+ list, ok := v.([]string)
+ if !ok {
+ list, _ := v.([]interface{})
+ for _, e := range list {
+ str := e.(string)
+ k = append(k, tokens(str)...)
+ }
+ return
+ }
+
+ for _, e := range list {
+ k = append(k, tokens(e)...)
}
return
}
diff --git a/database/books_test.go b/database/books_test.go
index fd9744d..a32e6dc 100644
--- a/database/books_test.go
+++ b/database/books_test.go
@@ -5,6 +5,7 @@ import "testing"
var book = map[string]interface{}{
"title": "some title",
"author": []string{"Alice", "Bob"},
+ "id": "r_m-IOzzIbA6QK5w",
}
func TestAddBook(t *testing.T) {
@@ -15,7 +16,7 @@ func TestAddBook(t *testing.T) {
books, num, err := db.GetNewBooks(1, 0)
if err != nil {
- t.Fatalf("db.GetBooks() return an error: ", err)
+ t.Fatal("db.GetBooks() return an error: ", err)
}
if num < 1 {
t.Fatalf("db.GetBooks() didn't find any result.")
@@ -24,13 +25,61 @@ func TestAddBook(t *testing.T) {
t.Fatalf("db.GetBooks() didn't return any result.")
}
if books[0].Title != book["title"] {
- t.Errorf("Book title don't match : '", books[0].Title, "' <=> '", book["title"], "'")
+ t.Error("Book title don't match : '", books[0].Title, "' <=> '", book["title"], "'")
+ }
+}
+
+func TestActiveBook(t *testing.T) {
+ db := Init(test_host, test_coll)
+ defer db.del()
+
+ tAddBook(t, db)
+ books, _, _ := db.GetNewBooks(1, 0)
+ id := books[0].Id
+
+ err := db.ActiveBook(id)
+ if err != nil {
+ t.Fatal("db.ActiveBook(", id, ") return an error: ", err)
+ }
+
+ b, err := db.GetBookId(id)
+ if err != nil {
+ t.Fatal("db.GetBookId(", id, ") return an error: ", err)
+ }
+ if b.Author[0] != books[0].Author[0] {
+ t.Error("Book author don't match : '", b.Author, "' <=> '", book["author"], "'")
+ }
+}
+
+func TestUpdateBookKeywords(t *testing.T) {
+ db := Init(test_host, test_coll)
+ defer db.del()
+
+ tAddBook(t, db)
+ books, _, _ := db.GetNewBooks(1, 0)
+
+ db.UpdateBook(books[0].Id, map[string]interface{}{"title": "Some other title"})
+ books, _, _ = db.GetNewBooks(1, 0)
+ keywords := books[0].Keywords
+
+ alice := false
+ bob := false
+ for _, e := range keywords {
+ if e == "alice" {
+ alice = true
+ }
+ if e == "bob" {
+ bob = true
+ }
+ }
+ if !alice || !bob {
+ t.Error("Alce or Bob are not in the keywords:", keywords)
}
}
func tAddBook(t *testing.T, db *DB) {
err := db.AddBook(book)
if err != nil {
- t.Errorf("db.AddBook(", book, ") return an error: ", err)
+ t.Error("db.AddBook(", book, ") return an error: ", err)
}
}
diff --git a/database/database.go b/database/database.go
index dad79a7..1b32c68 100644
--- a/database/database.go
+++ b/database/database.go
@@ -73,9 +73,14 @@ func (db *DB) UpdateBook(id string, data map[string]interface{}) error {
return updateBook(booksColl, id, data)
}
-func (db *DB) BookActive(id string) bool {
+func (db *DB) ActiveBook(id string) error {
booksColl := db.session.DB(db.name).C(books_coll)
- return bookActive(booksColl, id)
+ return activeBook(booksColl, id)
+}
+
+func (db *DB) IsBookActive(id string) bool {
+ booksColl := db.session.DB(db.name).C(books_coll)
+ return isBookActive(booksColl, id)
}
func (db *DB) User(name string) *User {
@@ -154,10 +159,6 @@ func (db *DB) UpdateDownloadedBooks() error {
return u.UpdateMostBooks("download")
}
-func (db *DB) GetFS(prefix string) *mgo.GridFS {
- return db.session.DB(db.name).GridFS(prefix)
-}
-
func (db *DB) GetTags() ([]string, error) {
tagsColl := db.session.DB(db.name).C(tags_coll)
return GetTags(tagsColl)
diff --git a/reader.go b/reader.go
index 5402076..ad2b9ce 100644
--- a/reader.go
+++ b/reader.go
@@ -1,11 +1,15 @@
package main
+import log "github.com/cihub/seelog"
+
import (
+ "bytes"
"git.gitorious.org/go-pkg/epubgo.git"
"git.gitorious.org/trantor/trantor.git/database"
+ "git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux"
"io"
- "labix.org/v2/mgo/bson"
+ "io/ioutil"
"net/http"
"strconv"
"strings"
@@ -134,6 +138,7 @@ func readStartHandler(h handler) {
id := mux.Vars(h.r)["id"]
e, _ := openReadEpub(h)
if e == nil {
+ log.Warn("Open epub returns an empty file")
notFound(h)
return
}
@@ -141,6 +146,7 @@ func readStartHandler(h handler) {
it, err := e.Spine()
if err != nil {
+ log.Warn("No spine in the epub")
notFound(h)
return
}
@@ -175,7 +181,7 @@ func readHandler(h handler) {
func openReadEpub(h handler) (*epubgo.Epub, database.Book) {
var book database.Book
id := mux.Vars(h.r)["id"]
- if !bson.IsObjectIdHex(id) {
+ if id == "" {
return nil, book
}
book, err := h.db.GetBookId(id)
@@ -188,7 +194,7 @@ func openReadEpub(h handler) (*epubgo.Epub, database.Book) {
return nil, book
}
}
- e, err := OpenBook(book.File, h.db)
+ e, err := openBook(book.Id, h.store)
if err != nil {
return nil, book
}
@@ -199,7 +205,7 @@ func contentHandler(h handler) {
vars := mux.Vars(h.r)
id := vars["id"]
file := vars["file"]
- if file == "" || !bson.IsObjectIdHex(id) {
+ if file == "" || id == "" {
notFound(h)
return
}
@@ -215,7 +221,7 @@ func contentHandler(h handler) {
return
}
}
- e, err := OpenBook(book.File, h.db)
+ e, err := openBook(book.Id, h.store)
if err != nil {
notFound(h)
return
@@ -230,3 +236,16 @@ func contentHandler(h handler) {
defer html.Close()
io.Copy(h.w, html)
}
+
+func openBook(id string, store *storage.Store) (*epubgo.Epub, error) {
+ f, err := store.Get(id, EPUB_FILE)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ buff, err := ioutil.ReadAll(f)
+ reader := bytes.NewReader(buff)
+
+ return epubgo.Load(reader, int64(len(buff)))
+}
diff --git a/stats.go b/stats.go
index e2552c7..14ef110 100644
--- a/stats.go
+++ b/stats.go
@@ -4,32 +4,49 @@ import log "github.com/cihub/seelog"
import (
"git.gitorious.org/trantor/trantor.git/database"
+ "git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux"
- "labix.org/v2/mgo/bson"
"net/http"
"strconv"
"strings"
"time"
)
+const (
+ stats_version = 2
+)
+
type handler struct {
- w http.ResponseWriter
- r *http.Request
- sess *Session
- db *database.DB
+ w http.ResponseWriter
+ r *http.Request
+ sess *Session
+ db *database.DB
+ store *storage.Store
}
-func InitStats(database *database.DB) {
- statsChannel = make(chan statsRequest, CHAN_SIZE)
- go statsWorker(database)
+type StatsGatherer struct {
+ db *database.DB
+ store *storage.Store
+ channel chan statsRequest
}
-func GatherStats(function func(handler), database *database.DB) func(http.ResponseWriter, *http.Request) {
+func InitStats(database *database.DB, store *storage.Store) *StatsGatherer {
+ sg := new(StatsGatherer)
+ sg.channel = make(chan statsRequest, CHAN_SIZE)
+ sg.db = database
+ sg.store = store
+
+ go sg.worker()
+ return sg
+}
+
+func (sg StatsGatherer) Gather(function func(handler)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Info("Query ", r.Method, " ", r.RequestURI)
var h handler
- h.db = database.Copy()
+ h.store = sg.store
+ h.db = sg.db.Copy()
defer h.db.Close()
h.w = w
@@ -37,12 +54,10 @@ func GatherStats(function func(handler), database *database.DB) func(http.Respon
h.sess = GetSession(r, h.db)
function(h)
- statsChannel <- statsRequest{bson.Now(), mux.Vars(r), h.sess, r}
+ sg.channel <- statsRequest{time.Now(), mux.Vars(r), h.sess, r}
}
}
-var statsChannel chan statsRequest
-
type statsRequest struct {
date time.Time
vars map[string]string
@@ -50,16 +65,17 @@ type statsRequest struct {
r *http.Request
}
-func statsWorker(database *database.DB) {
- db := database.Copy()
+func (sg StatsGatherer) worker() {
+ db := sg.db.Copy()
defer db.Close()
- for req := range statsChannel {
+ for req := range sg.channel {
stats := make(map[string]interface{})
appendFiles(req.r, stats)
appendMuxVars(req.vars, stats)
appendUrl(req.r, stats)
appendSession(req.sess, stats)
+ stats["version"] = stats_version
stats["method"] = req.r.Method
stats["date"] = req.date
db.AddStats(stats)
@@ -141,16 +157,12 @@ func appendMuxVars(vars map[string]string, stats map[string]interface{}) {
for key, value := range vars {
switch {
case key == "id":
- if bson.IsObjectIdHex(value) {
- stats["id"] = bson.ObjectIdHex(value)
- }
+ stats["id"] = value
case key == "ids":
- var objectIds []bson.ObjectId
+ var objectIds []string
ids := strings.Split(value, "/")
for _, id := range ids {
- if bson.IsObjectIdHex(value) {
- objectIds = append(objectIds, bson.ObjectIdHex(id))
- }
+ objectIds = append(objectIds, id)
}
if len(objectIds) > 0 {
stats["ids"] = objectIds
diff --git a/storage/dir.go b/storage/dir.go
new file mode 100644
index 0000000..066f0bb
--- /dev/null
+++ b/storage/dir.go
@@ -0,0 +1,42 @@
+package storage
+
+import p "path"
+
+import (
+ "os"
+)
+
+const (
+ dir_depth = 2
+ encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+)
+
+func mkstore(path string) error {
+ return _mkstore(path, dir_depth)
+}
+
+func _mkstore(path string, depth int) error {
+ err := os.MkdirAll(path, os.ModePerm)
+ if err != nil || depth == 0 {
+ return err
+ }
+
+ for _, l := range encodeURL {
+ next_path := p.Join(path, string(l))
+ err = _mkstore(next_path, depth-1)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func idPath(storePath string, id string) string {
+ path := storePath
+ for i := 0; i < dir_depth; i++ {
+ dir := string(id[i])
+ path = p.Join(path, dir)
+ }
+ path = p.Join(path, id)
+ return path
+}
diff --git a/storage/storage.go b/storage/storage.go
new file mode 100644
index 0000000..f84b3ab
--- /dev/null
+++ b/storage/storage.go
@@ -0,0 +1,57 @@
+package storage
+
+import p "path"
+
+import (
+ "io"
+ "os"
+)
+
+type Store struct {
+ path string
+}
+
+func Init(path string) (*Store, error) {
+ st := new(Store)
+ st.path = path
+
+ _, err := os.Stat(path)
+ if err != nil {
+ err = mkstore(st.path)
+ }
+ return st, err
+}
+
+func (st *Store) Create(id string, name string) (io.WriteCloser, error) {
+ path := idPath(st.path, id)
+ err := os.MkdirAll(path, os.ModePerm)
+ if err != nil {
+ return nil, err
+ }
+
+ return os.Create(p.Join(path, name))
+}
+
+func (st *Store) Store(id string, file io.Reader, name string) (size int64, err error) {
+ dest, err := st.Create(id, name)
+ if err != nil {
+ return 0, err
+ }
+ defer dest.Close()
+
+ return io.Copy(dest, file)
+}
+
+func (st *Store) Get(id string, name string) (io.ReadCloser, error) {
+ path := idPath(st.path, id)
+ return os.Open(p.Join(path, name))
+}
+
+func (st *Store) Delete(id string) error {
+ path := idPath(st.path, id)
+ return os.RemoveAll(path)
+}
+
+func (st *Store) del() {
+ os.RemoveAll(st.path)
+}
diff --git a/storage/storage_test.go b/storage/storage_test.go
new file mode 100644
index 0000000..c8aa657
--- /dev/null
+++ b/storage/storage_test.go
@@ -0,0 +1,113 @@
+package storage
+
+import "testing"
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "os"
+ "strings"
+)
+
+const (
+ test_path = "/tmp/store"
+ test_book = `
+ HARI SELDON-… born in the 11,988th year of the Galactic Era; died
+ 12,069. The dates are more commonly given in terms of the current
+ Foundational Era as - 79 to the year 1 F.E. Born to middle-class
+ parents on Helicon, Arcturus sector (where his father, in a legend of
+ doubtful authenticity, was a tobacco grower in the hydroponic plants
+ of the planet), he early showed amazing ability in mathematics.
+ Anecdotes concerning his ability are innumerable, and some are
+ contradictory. At the age of two, he is said to have …`
+ test_id = "1234567890abcdef"
+)
+
+func TestInit(t *testing.T) {
+ st, err := Init(test_path)
+ if err != nil {
+ t.Fatal("An error ocurred initializing the store =>", err)
+ }
+ defer st.del()
+
+ info, err := os.Stat(test_path)
+ if err != nil {
+ t.Fatal("An error ocurred =>", err)
+ }
+ if !info.Mode().IsDir() {
+ t.Errorf(test_path, " is not dir.")
+ }
+
+ info, err = os.Stat(test_path + "/a/M")
+ if err != nil {
+ t.Fatal("An error ocurred =>", err)
+ }
+ if !info.Mode().IsDir() {
+ t.Errorf(test_path, " is not dir.")
+ }
+}
+
+func TestStore(t *testing.T) {
+ st, err := Init(test_path)
+ defer st.del()
+
+ _, err = st.Store(test_id, strings.NewReader(test_book), "epub")
+ if err != nil {
+ t.Fatal("An error ocurred storing the book =>", err)
+ }
+ book, err := st.Get(test_id, "epub")
+ if err != nil {
+ t.Fatal("An error ocurred getting the book =>", err)
+ }
+
+ content, err := ioutil.ReadAll(book)
+ if err != nil {
+ t.Fatal("An error ocurred reading the book =>", err)
+ }
+ if !bytes.Equal(content, []byte(test_book)) {
+ t.Error("Not the same content")
+ }
+}
+
+func TestCreate(t *testing.T) {
+ st, err := Init(test_path)
+ defer st.del()
+
+ f, err := st.Create(test_id, "img")
+ if err != nil {
+ t.Fatal("An error ocurred storing the book =>", err)
+ }
+ io.Copy(f, strings.NewReader(test_book))
+ img, err := st.Get(test_id, "img")
+ if err != nil {
+ t.Fatal("An error ocurred getting the book =>", err)
+ }
+
+ content, err := ioutil.ReadAll(img)
+ if err != nil {
+ t.Fatal("An error ocurred reading the book =>", err)
+ }
+ if !bytes.Equal(content, []byte(test_book)) {
+ t.Error("Not the same content")
+ }
+}
+
+func TestDelete(t *testing.T) {
+ st, err := Init(test_path)
+ defer st.del()
+
+ _, err = st.Store(test_id, strings.NewReader(test_book), "epub")
+ if err != nil {
+ t.Fatal("An error ocurred storing the book =>", err)
+ }
+ err = st.Delete(test_id)
+ if err != nil {
+ t.Fatal("An error ocurred deleteing id =>", err)
+ }
+
+ _, err = st.Get(test_id, "epub")
+ if err == nil {
+ t.Fatal("Retrieve book without error.")
+ }
+}
diff --git a/store.go b/store.go
deleted file mode 100644
index 2a8f1f7..0000000
--- a/store.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package main
-
-import (
- "bytes"
- "git.gitorious.org/go-pkg/epubgo.git"
- "git.gitorious.org/trantor/trantor.git/database"
- "io"
- "io/ioutil"
- "labix.org/v2/mgo/bson"
- "regexp"
- "strings"
-)
-
-func OpenBook(id bson.ObjectId, db *database.DB) (*epubgo.Epub, error) {
- fs := db.GetFS(FS_BOOKS)
- f, err := fs.OpenId(id)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- buff, err := ioutil.ReadAll(f)
- reader := bytes.NewReader(buff)
-
- return epubgo.Load(reader, int64(len(buff)))
-}
-
-func StoreNewFile(name string, file io.Reader, db *database.DB) (bson.ObjectId, int64, error) {
- fs := db.GetFS(FS_BOOKS)
- fw, err := fs.Create(name)
- if err != nil {
- return "", 0, err
- }
- defer fw.Close()
-
- size, err := io.Copy(fw, file)
- id, _ := fw.Id().(bson.ObjectId)
- return id, size, err
-}
-
-func DeleteFile(id bson.ObjectId, db *database.DB) error {
- fs := db.GetFS(FS_BOOKS)
- return fs.RemoveId(id)
-}
-
-func DeleteCover(id bson.ObjectId, db *database.DB) error {
- fs := db.GetFS(FS_IMGS)
- return fs.RemoveId(id)
-}
-
-func DeleteBook(book database.Book, db *database.DB) {
- if book.Cover != "" {
- DeleteCover(book.Cover, db)
- }
- if book.CoverSmall != "" {
- DeleteCover(book.CoverSmall, db)
- }
- DeleteFile(book.File, db)
-}
-
-func cleanStr(str string) string {
- str = strings.Replace(str, "'", "'", -1)
- exp, _ := regexp.Compile("&[^;]*;")
- str = exp.ReplaceAllString(str, "")
- exp, _ = regexp.Compile("[ ,]*$")
- str = exp.ReplaceAllString(str, "")
- return str
-}
-
-func parseAuthr(creator []string) []string {
- exp1, _ := regexp.Compile("^(.*\\( *([^\\)]*) *\\))*$")
- exp2, _ := regexp.Compile("^[^:]*: *(.*)$")
- res := make([]string, len(creator))
- for i, s := range creator {
- auth := exp1.FindStringSubmatch(s)
- if auth != nil {
- res[i] = cleanStr(strings.Join(auth[2:], ", "))
- } else {
- auth := exp2.FindStringSubmatch(s)
- if auth != nil {
- res[i] = cleanStr(auth[1])
- } else {
- res[i] = cleanStr(s)
- }
- }
- }
- return res
-}
-
-func parseDescription(description []string) string {
- str := cleanStr(strings.Join(description, "\n"))
- str = strings.Replace(str, "
", "\n", -1)
- 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)
-}
diff --git a/templates/index.html b/templates/index.html
index 6a41dd6..e3cfcd2 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -26,7 +26,7 @@
@@ -44,7 +44,7 @@
@@ -62,7 +62,7 @@
diff --git a/templates/new.html b/templates/new.html
index 58756bc..50c0a29 100644
--- a/templates/new.html
+++ b/templates/new.html
@@ -26,7 +26,7 @@
{{with .B}}
-
{{if .CoverSmall}}
{{end}}
+
{{if .Cover}}
{{end}}
{{.Title}} ({{$titleFound}})
diff --git a/templates/search.html b/templates/search.html
index b1a9d6c..1d191d1 100644
--- a/templates/search.html
+++ b/templates/search.html
@@ -21,7 +21,7 @@
{{range .}}
diff --git a/trantor.go b/trantor.go
index 533f620..38f1db7 100644
--- a/trantor.go
+++ b/trantor.go
@@ -4,9 +4,11 @@ import log "github.com/cihub/seelog"
import (
"git.gitorious.org/trantor/trantor.git/database"
+ "git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux"
"io"
"net/http"
+ "os"
"strings"
)
@@ -71,8 +73,7 @@ func downloadHandler(h handler) {
}
}
- fs := h.db.GetFS(FS_BOOKS)
- f, err := fs.OpenId(book.File)
+ f, err := h.store.Get(book.Id, EPUB_FILE)
if err != nil {
notFound(h)
return
@@ -81,7 +82,7 @@ func downloadHandler(h handler) {
headers := h.w.Header()
headers["Content-Type"] = []string{"application/epub+zip"}
- headers["Content-Disposition"] = []string{"attachment; filename=\"" + f.Name() + "\""}
+ headers["Content-Disposition"] = []string{"attachment; filename=\"" + book.Title + ".epub\""}
io.Copy(h.w, f)
}
@@ -137,47 +138,55 @@ func main() {
db := database.Init(DB_IP, DB_NAME)
defer db.Close()
- InitTasks(db)
- InitStats(db)
- InitUpload(db)
+ store, err := storage.Init(STORE_PATH)
+ if err != nil {
+ log.Critical("Problem initializing store: ", err)
+ os.Exit(1)
+ }
- initRouter(db)
+ InitTasks(db)
+ sg := InitStats(db, store)
+ InitUpload(db, store)
+
+ initRouter(db, sg)
log.Error(http.ListenAndServe(":"+PORT, nil))
}
-func initRouter(db *database.DB) {
+func initRouter(db *database.DB, sg *StatsGatherer) {
+ const id_pattern = "[0-9a-zA-Z\\-\\_]{16}"
+
r := mux.NewRouter()
var notFoundHandler http.HandlerFunc
- notFoundHandler = GatherStats(notFound, db)
+ notFoundHandler = sg.Gather(notFound)
r.NotFoundHandler = notFoundHandler
- r.HandleFunc("/", GatherStats(indexHandler, db))
- r.HandleFunc("/book/{id:[0-9a-fA-F]+}", GatherStats(bookHandler, db))
- r.HandleFunc("/search/", GatherStats(searchHandler, db))
- r.HandleFunc("/upload/", GatherStats(uploadHandler, db)).Methods("GET")
- r.HandleFunc("/upload/", GatherStats(uploadPostHandler, db)).Methods("POST")
- r.HandleFunc("/login/", GatherStats(loginHandler, db)).Methods("GET")
- r.HandleFunc("/login/", GatherStats(loginPostHandler, db)).Methods("POST")
- r.HandleFunc("/create_user/", GatherStats(createUserHandler, db)).Methods("POST")
- r.HandleFunc("/logout/", GatherStats(logoutHandler, db))
- r.HandleFunc("/new/", GatherStats(newHandler, db))
- r.HandleFunc("/store/{ids:([0-9a-fA-F]+/)+}", GatherStats(storeHandler, db))
- r.HandleFunc("/delete/{ids:([0-9a-fA-F]+/)+}", GatherStats(deleteHandler, db))
- r.HandleFunc("/read/{id:[0-9a-fA-F]+}", GatherStats(readStartHandler, db))
- r.HandleFunc("/read/{id:[0-9a-fA-F]+}/{file:.*}", GatherStats(readHandler, db))
- r.HandleFunc("/content/{id:[0-9a-fA-F]+}/{file:.*}", GatherStats(contentHandler, db))
- r.HandleFunc("/edit/{id:[0-9a-fA-F]+}", GatherStats(editHandler, db))
- r.HandleFunc("/save/{id:[0-9a-fA-F]+}", GatherStats(saveHandler, db)).Methods("POST")
- r.HandleFunc("/about/", GatherStats(aboutHandler, db))
- r.HandleFunc("/help/", GatherStats(helpHandler, db))
- r.HandleFunc("/download/{id:[0-9a-fA-F]+}/{epub:.*}", GatherStats(downloadHandler, db))
- r.HandleFunc("/cover/{id:[0-9a-fA-F]+}/{size}/{img:.*}", GatherStats(coverHandler, db))
- r.HandleFunc("/dashboard/", GatherStats(dashboardHandler, db))
- r.HandleFunc("/settings/", GatherStats(settingsHandler, db))
- r.HandleFunc("/stats/", GatherStats(statsHandler, db))
- r.HandleFunc("/news/", GatherStats(newsHandler, db))
- r.HandleFunc("/news/edit", GatherStats(editNewsHandler, db)).Methods("GET")
- r.HandleFunc("/news/edit", GatherStats(postNewsHandler, db)).Methods("POST")
+ r.HandleFunc("/", sg.Gather(indexHandler))
+ r.HandleFunc("/book/{id:"+id_pattern+"}", sg.Gather(bookHandler))
+ r.HandleFunc("/search/", sg.Gather(searchHandler))
+ r.HandleFunc("/upload/", sg.Gather(uploadHandler)).Methods("GET")
+ r.HandleFunc("/upload/", sg.Gather(uploadPostHandler)).Methods("POST")
+ r.HandleFunc("/login/", sg.Gather(loginHandler)).Methods("GET")
+ r.HandleFunc("/login/", sg.Gather(loginPostHandler)).Methods("POST")
+ r.HandleFunc("/create_user/", sg.Gather(createUserHandler)).Methods("POST")
+ r.HandleFunc("/logout/", sg.Gather(logoutHandler))
+ r.HandleFunc("/new/", sg.Gather(newHandler))
+ r.HandleFunc("/store/{ids:("+id_pattern+"/)+}", sg.Gather(storeHandler))
+ r.HandleFunc("/delete/{ids:("+id_pattern+"/)+}", sg.Gather(deleteHandler))
+ r.HandleFunc("/read/{id:"+id_pattern+"}", sg.Gather(readStartHandler))
+ r.HandleFunc("/read/{id:"+id_pattern+"}/{file:.*}", sg.Gather(readHandler))
+ r.HandleFunc("/content/{id:"+id_pattern+"}/{file:.*}", sg.Gather(contentHandler))
+ r.HandleFunc("/edit/{id:"+id_pattern+"}", sg.Gather(editHandler))
+ r.HandleFunc("/save/{id:"+id_pattern+"}", sg.Gather(saveHandler)).Methods("POST")
+ r.HandleFunc("/about/", sg.Gather(aboutHandler))
+ r.HandleFunc("/help/", sg.Gather(helpHandler))
+ r.HandleFunc("/download/{id:"+id_pattern+"}/{epub:.*}", sg.Gather(downloadHandler))
+ r.HandleFunc("/cover/{id:"+id_pattern+"}/{size}/{img:.*}", sg.Gather(coverHandler))
+ r.HandleFunc("/dashboard/", sg.Gather(dashboardHandler))
+ r.HandleFunc("/settings/", sg.Gather(settingsHandler))
+ r.HandleFunc("/stats/", sg.Gather(statsHandler))
+ r.HandleFunc("/news/", sg.Gather(newsHandler))
+ r.HandleFunc("/news/edit", sg.Gather(editNewsHandler)).Methods("GET")
+ r.HandleFunc("/news/edit", sg.Gather(postNewsHandler)).Methods("POST")
h := http.FileServer(http.Dir(IMG_PATH))
r.Handle("/img/{img}", http.StripPrefix("/img/", h))
h = http.FileServer(http.Dir(CSS_PATH))
diff --git a/upload.go b/upload.go
index 832e0da..ceb4306 100644
--- a/upload.go
+++ b/upload.go
@@ -4,16 +4,20 @@ import log "github.com/cihub/seelog"
import (
"bytes"
+ "crypto/rand"
+ "encoding/base64"
"git.gitorious.org/go-pkg/epubgo.git"
"git.gitorious.org/trantor/trantor.git/database"
+ "git.gitorious.org/trantor/trantor.git/storage"
"io/ioutil"
"mime/multipart"
+ "regexp"
"strings"
)
-func InitUpload(database *database.DB) {
+func InitUpload(database *database.DB, store *storage.Store) {
uploadChannel = make(chan uploadRequest, CHAN_SIZE)
- go uploadWorker(database)
+ go uploadWorker(database, store)
}
var uploadChannel chan uploadRequest
@@ -23,16 +27,16 @@ type uploadRequest struct {
filename string
}
-func uploadWorker(database *database.DB) {
+func uploadWorker(database *database.DB, store *storage.Store) {
db := database.Copy()
defer db.Close()
for req := range uploadChannel {
- processFile(req, db)
+ processFile(req, db, store)
}
}
-func processFile(req uploadRequest, db *database.DB) {
+func processFile(req uploadRequest, db *database.DB, store *storage.Store) {
defer req.file.Close()
epub, err := openMultipartEpub(req.file)
@@ -42,20 +46,18 @@ func processFile(req uploadRequest, db *database.DB) {
}
defer epub.Close()
- book := parseFile(epub, db)
- title, _ := book["title"].(string)
+ book, id := parseFile(epub, store)
req.file.Seek(0, 0)
- id, size, err := StoreNewFile(title+".epub", req.file, db)
+ size, err := store.Store(id, req.file, EPUB_FILE)
if err != nil {
- log.Error("Error storing book (", title, "): ", err)
+ log.Error("Error storing book (", id, "): ", err)
return
}
- book["file"] = id
book["filesize"] = size
err = db.AddBook(book)
if err != nil {
- log.Error("Error storing metadata (", title, "): ", err)
+ log.Error("Error storing metadata (", id, "): ", err)
return
}
log.Info("File uploaded: ", req.filename)
@@ -104,7 +106,7 @@ func openMultipartEpub(file multipart.File) (*epubgo.Epub, error) {
return epubgo.Load(reader, int64(len(buff)))
}
-func parseFile(epub *epubgo.Epub, db *database.DB) map[string]interface{} {
+func parseFile(epub *epubgo.Epub, store *storage.Store) (metadata map[string]interface{}, id string) {
book := map[string]interface{}{}
for _, m := range epub.MetadataFields() {
data, err := epub.Metadata(m)
@@ -135,12 +137,71 @@ func parseFile(epub *epubgo.Epub, db *database.DB) map[string]interface{} {
book[m] = strings.Join(data, ", ")
}
}
- title, _ := book["title"].(string)
- book["file"] = nil
- cover, coverSmall := GetCover(epub, title, db)
- if cover != "" {
- book["cover"] = cover
- book["coversmall"] = coverSmall
- }
- return book
+
+ id = genId()
+ book["id"] = id //TODO
+ book["cover"] = GetCover(epub, id, store)
+ return book, id
+}
+
+func genId() string {
+ b := make([]byte, 12)
+ rand.Read(b)
+ return base64.URLEncoding.EncodeToString(b)
+}
+
+func cleanStr(str string) string {
+ str = strings.Replace(str, "'", "'", -1)
+ exp, _ := regexp.Compile("&[^;]*;")
+ str = exp.ReplaceAllString(str, "")
+ exp, _ = regexp.Compile("[ ,]*$")
+ str = exp.ReplaceAllString(str, "")
+ return str
+}
+
+func parseAuthr(creator []string) []string {
+ exp1, _ := regexp.Compile("^(.*\\( *([^\\)]*) *\\))*$")
+ exp2, _ := regexp.Compile("^[^:]*: *(.*)$")
+ res := make([]string, len(creator))
+ for i, s := range creator {
+ auth := exp1.FindStringSubmatch(s)
+ if auth != nil {
+ res[i] = cleanStr(strings.Join(auth[2:], ", "))
+ } else {
+ auth := exp2.FindStringSubmatch(s)
+ if auth != nil {
+ res[i] = cleanStr(auth[1])
+ } else {
+ res[i] = cleanStr(s)
+ }
+ }
+ }
+ return res
+}
+
+func parseDescription(description []string) string {
+ str := cleanStr(strings.Join(description, "\n"))
+ str = strings.Replace(str, "", "\n", -1)
+ 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)
}