Move files to a local folder

This commit is contained in:
Las Zenow 2014-08-21 19:24:23 -05:00
parent 97ed9ec073
commit f4690b2bba
18 changed files with 556 additions and 288 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
trantor.git trantor.git
store/
tools/adduser/adduser tools/adduser/adduser
tools/update/update tools/update/update
tools/togridfs/togridfs tools/togridfs/togridfs

View file

@ -25,7 +25,7 @@ func deleteHandler(h handler) {
h.sess.Notify("Book not found!", "The book with id '"+id+"' is not there", "error") h.sess.Notify("Book not found!", "The book with id '"+id+"' is not there", "error")
continue continue
} }
DeleteBook(book, h.db) h.store.Delete(id)
h.db.DeleteBook(id) h.db.DeleteBook(id)
if !book.Active { if !book.Active {
@ -101,7 +101,7 @@ func saveHandler(h handler) {
h.sess.Notify("Book Modified!", "", "success") h.sess.Notify("Book Modified!", "", "success")
h.sess.Save(h.w, h.r) 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) http.Redirect(h.w, h.r, "/book/"+id, http.StatusFound)
} else { } else {
http.Redirect(h.w, h.r, "/new/", http.StatusFound) http.Redirect(h.w, h.r, "/new/", http.StatusFound)
@ -174,17 +174,25 @@ func storeHandler(h handler) {
var titles []string var titles []string
ids := strings.Split(mux.Vars(h.r)["ids"], "/") ids := strings.Split(mux.Vars(h.r)["ids"], "/")
for _, id := range ids { for _, id := range ids {
if id == "" {
continue
}
book, err := h.db.GetBookId(id) book, err := h.db.GetBookId(id)
if err != nil { if err != nil {
h.sess.Notify("Book not found!", "The book with id '"+id+"' is not there", "error") h.sess.Notify("Book not found!", "The book with id '"+id+"' is not there", "error")
continue 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 { if err != nil {
h.sess.Notify("An error ocurred!", err.Error(), "error") h.sess.Notify("An error ocurred!", err.Error(), "error")
log.Error("Error storing book '", book.Title, "': ", err.Error()) log.Error("Error storing book '", book.Title, "': ", err.Error())
continue continue
} }
h.db.UpdateBook(id, map[string]interface{}{"active": true})
titles = append(titles, book.Title) titles = append(titles, book.Title)
} }
if titles != nil { if titles != nil {

View file

@ -6,8 +6,10 @@ const (
DB_IP = "127.0.0.1" DB_IP = "127.0.0.1"
DB_NAME = "trantor" DB_NAME = "trantor"
META_COLL = "meta" 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_TAGS = 11
MINUTES_UPDATE_VISITED = 41 MINUTES_UPDATE_VISITED = 41
@ -25,6 +27,7 @@ const (
NUM_NEWS = 10 NUM_NEWS = 10
DAYS_NEWS_INDEXPAGE = 15 DAYS_NEWS_INDEXPAGE = 15
STORE_PATH = "store/"
TEMPLATE_PATH = "templates/" TEMPLATE_PATH = "templates/"
CSS_PATH = "css/" CSS_PATH = "css/"
JS_PATH = "js/" JS_PATH = "js/"

View file

@ -8,15 +8,13 @@ import _ "image/gif"
import ( import (
"bytes" "bytes"
"git.gitorious.org/go-pkg/epubgo.git" "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/gorilla/mux"
"github.com/nfnt/resize" "github.com/nfnt/resize"
"image" "image"
"image/jpeg" "image/jpeg"
"io" "io"
"io/ioutil" "io/ioutil"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"regexp" "regexp"
"strings" "strings"
) )
@ -36,13 +34,11 @@ func coverHandler(h handler) {
} }
} }
fs := h.db.GetFS(FS_IMGS) file := COVER_FILE
var f *mgo.GridFile
if vars["size"] == "small" { if vars["size"] == "small" {
f, err = fs.OpenId(book.CoverSmall) file = COVER_SMALL_FILE
} else {
f, err = fs.OpenId(book.Cover)
} }
f, err := h.store.Get(book.Id, file)
if err != nil { if err != nil {
log.Error("Error while opening image: ", err) log.Error("Error while opening image: ", err)
notFound(h) notFound(h)
@ -53,18 +49,21 @@ func coverHandler(h handler) {
headers := h.w.Header() headers := h.w.Header()
headers["Content-Type"] = []string{"image/jpeg"} 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) { func GetCover(e *epubgo.Epub, id string, store *storage.Store) bool {
imgId, smallId := coverFromMetadata(e, title, db) if coverFromMetadata(e, id, store) {
if imgId != "" { return true
return imgId, smallId
} }
imgId, smallId = searchCommonCoverNames(e, title, db) if searchCommonCoverNames(e, id, store) {
if imgId != "" { return true
return imgId, smallId
} }
/* search for img on the text */ /* 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) img, err := e.OpenFile(url)
if err == nil { if err == nil {
defer img.Close() defer img.Close()
return storeImg(img, title, db) return storeImg(img, id, store)
} }
} }
errNext = it.Next() 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") metaList, _ := e.MetadataAttr("meta")
for _, meta := range metaList { for _, meta := range metaList {
if meta["name"] == "cover" { if meta["name"] == "cover" {
img, err := e.OpenFileId(meta["content"]) img, err := e.OpenFileId(meta["content"])
if err == nil { if err == nil {
defer img.Close() 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"} { for _, p := range []string{"cover.jpg", "Images/cover.jpg", "images/cover.jpg", "cover.jpeg", "cover1.jpg", "cover1.jpeg"} {
img, err := e.OpenFile(p) img, err := e.OpenFile(p)
if err == nil { if err == nil {
defer img.Close() 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 */ /* open the files */
fBig, err := createCoverFile(title, db) fBig, err := store.Create(id, COVER_FILE)
if err != nil { if err != nil {
log.Error("Error creating ", title, ": ", err.Error()) log.Error("Error creating cover ", id, ": ", err.Error())
return "", "" return false
} }
defer fBig.Close() defer fBig.Close()
fSmall, err := createCoverFile(title+"_small", db) fSmall, err := store.Create(id, COVER_SMALL_FILE)
if err != nil { if err != nil {
log.Error("Error creating ", title+"_small", ": ", err.Error()) log.Error("Error creating small cover ", id, ": ", err.Error())
return "", "" return false
} }
defer fSmall.Close() 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) imgResized, err := resizeImg(img1, IMG_WIDTH_BIG)
if err != nil { if err != nil {
log.Error("Error resizing big image: ", err.Error()) log.Error("Error resizing big image: ", err.Error())
return "", "" return false
} }
err = jpeg.Encode(fBig, imgResized, &jpgOptions) err = jpeg.Encode(fBig, imgResized, &jpgOptions)
if err != nil { if err != nil {
log.Error("Error encoding big image: ", err.Error()) log.Error("Error encoding big image: ", err.Error())
return "", "" return false
} }
imgSmallResized, err := resizeImg(&img2, IMG_WIDTH_SMALL) imgSmallResized, err := resizeImg(&img2, IMG_WIDTH_SMALL)
if err != nil { if err != nil {
log.Error("Error resizing small image: ", err.Error()) log.Error("Error resizing small image: ", err.Error())
return "", "" return false
} }
err = jpeg.Encode(fSmall, imgSmallResized, &jpgOptions) err = jpeg.Encode(fSmall, imgSmallResized, &jpgOptions)
if err != nil { if err != nil {
log.Error("Error encoding small image: ", err.Error()) log.Error("Error encoding small image: ", err.Error())
return "", "" return false
} }
return true
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")
} }
func resizeImg(imgReader io.Reader, width uint) (image.Image, error) { func resizeImg(imgReader io.Reader, width uint) (image.Image, error) {

View file

@ -1,7 +1,6 @@
package database package database
import ( import (
"errors"
"gopkg.in/mgo.v2" "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson" "gopkg.in/mgo.v2/bson"
"gopkgs.com/unidecode.v1" "gopkgs.com/unidecode.v1"
@ -14,7 +13,7 @@ const (
) )
type Book struct { type Book struct {
Id string `bson:"_id"` Id string
Title string Title string
Author []string Author []string
Contributor string Contributor string
@ -31,10 +30,8 @@ type Book struct {
Coverage string Coverage string
Rights string Rights string
Meta string Meta string
File bson.ObjectId
FileSize int FileSize int
Cover bson.ObjectId Cover bool
CoverSmall bson.ObjectId
Active bool Active bool
Keywords []string Keywords []string
} }
@ -66,35 +63,41 @@ func _getBooks(coll *mgo.Collection, query bson.M, length int, start int) (books
} }
err = q.All(&books) err = q.All(&books)
for i, b := range books {
books[i].Id = bson.ObjectId(b.Id).Hex()
}
return return
} }
func getBookId(coll *mgo.Collection, id string) (Book, error) { func getBookId(coll *mgo.Collection, id string) (Book, error) {
var book Book var book Book
if !bson.IsObjectIdHex(id) { err := coll.Find(bson.M{"id": id}).One(&book)
return book, errors.New("Not valid book id")
}
err := coll.FindId(bson.ObjectIdHex(id)).One(&book)
book.Id = bson.ObjectId(book.Id).Hex()
return book, err return book, err
} }
func deleteBook(coll *mgo.Collection, id string) error { 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 { func updateBook(coll *mgo.Collection, id string, data map[string]interface{}) error {
data["keywords"] = keywords(data) var book map[string]interface{}
return coll.UpdateId(bson.ObjectIdHex(id), bson.M{"$set": data}) 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 var book Book
err := coll.FindId(bson.ObjectIdHex(id)).One(&book) err := coll.Find(bson.M{"id": id}).One(&book)
if err != nil { if err != nil {
return false return false
} }
@ -123,15 +126,29 @@ func buildQuery(q string) bson.M {
func keywords(b map[string]interface{}) (k []string) { func keywords(b map[string]interface{}) (k []string) {
title, _ := b["title"].(string) title, _ := b["title"].(string)
k = tokens(title) k = tokens(title)
author, _ := b["author"].([]string)
for _, a := range author { k = append(k, listKeywords(b["author"])...)
k = append(k, tokens(a)...)
}
publisher, _ := b["publisher"].(string) publisher, _ := b["publisher"].(string)
k = append(k, tokens(publisher)...) k = append(k, tokens(publisher)...)
subject, _ := b["subject"].([]string)
for _, s := range subject { k = append(k, listKeywords(b["subject"])...)
k = append(k, tokens(s)...) 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 return
} }

View file

@ -5,6 +5,7 @@ import "testing"
var book = map[string]interface{}{ var book = map[string]interface{}{
"title": "some title", "title": "some title",
"author": []string{"Alice", "Bob"}, "author": []string{"Alice", "Bob"},
"id": "r_m-IOzzIbA6QK5w",
} }
func TestAddBook(t *testing.T) { func TestAddBook(t *testing.T) {
@ -15,7 +16,7 @@ func TestAddBook(t *testing.T) {
books, num, err := db.GetNewBooks(1, 0) books, num, err := db.GetNewBooks(1, 0)
if err != nil { if err != nil {
t.Fatalf("db.GetBooks() return an error: ", err) t.Fatal("db.GetBooks() return an error: ", err)
} }
if num < 1 { if num < 1 {
t.Fatalf("db.GetBooks() didn't find any result.") 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.") t.Fatalf("db.GetBooks() didn't return any result.")
} }
if books[0].Title != book["title"] { 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) { func tAddBook(t *testing.T, db *DB) {
err := db.AddBook(book) err := db.AddBook(book)
if err != nil { if err != nil {
t.Errorf("db.AddBook(", book, ") return an error: ", err) t.Error("db.AddBook(", book, ") return an error: ", err)
} }
} }

View file

@ -73,9 +73,14 @@ func (db *DB) UpdateBook(id string, data map[string]interface{}) error {
return updateBook(booksColl, id, data) 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) 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 { func (db *DB) User(name string) *User {
@ -154,10 +159,6 @@ func (db *DB) UpdateDownloadedBooks() error {
return u.UpdateMostBooks("download") 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) { func (db *DB) GetTags() ([]string, error) {
tagsColl := db.session.DB(db.name).C(tags_coll) tagsColl := db.session.DB(db.name).C(tags_coll)
return GetTags(tagsColl) return GetTags(tagsColl)

View file

@ -1,11 +1,15 @@
package main package main
import log "github.com/cihub/seelog"
import ( import (
"bytes"
"git.gitorious.org/go-pkg/epubgo.git" "git.gitorious.org/go-pkg/epubgo.git"
"git.gitorious.org/trantor/trantor.git/database" "git.gitorious.org/trantor/trantor.git/database"
"git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"io" "io"
"labix.org/v2/mgo/bson" "io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -134,6 +138,7 @@ func readStartHandler(h handler) {
id := mux.Vars(h.r)["id"] id := mux.Vars(h.r)["id"]
e, _ := openReadEpub(h) e, _ := openReadEpub(h)
if e == nil { if e == nil {
log.Warn("Open epub returns an empty file")
notFound(h) notFound(h)
return return
} }
@ -141,6 +146,7 @@ func readStartHandler(h handler) {
it, err := e.Spine() it, err := e.Spine()
if err != nil { if err != nil {
log.Warn("No spine in the epub")
notFound(h) notFound(h)
return return
} }
@ -175,7 +181,7 @@ func readHandler(h handler) {
func openReadEpub(h handler) (*epubgo.Epub, database.Book) { func openReadEpub(h handler) (*epubgo.Epub, database.Book) {
var book database.Book var book database.Book
id := mux.Vars(h.r)["id"] id := mux.Vars(h.r)["id"]
if !bson.IsObjectIdHex(id) { if id == "" {
return nil, book return nil, book
} }
book, err := h.db.GetBookId(id) book, err := h.db.GetBookId(id)
@ -188,7 +194,7 @@ func openReadEpub(h handler) (*epubgo.Epub, database.Book) {
return nil, book return nil, book
} }
} }
e, err := OpenBook(book.File, h.db) e, err := openBook(book.Id, h.store)
if err != nil { if err != nil {
return nil, book return nil, book
} }
@ -199,7 +205,7 @@ func contentHandler(h handler) {
vars := mux.Vars(h.r) vars := mux.Vars(h.r)
id := vars["id"] id := vars["id"]
file := vars["file"] file := vars["file"]
if file == "" || !bson.IsObjectIdHex(id) { if file == "" || id == "" {
notFound(h) notFound(h)
return return
} }
@ -215,7 +221,7 @@ func contentHandler(h handler) {
return return
} }
} }
e, err := OpenBook(book.File, h.db) e, err := openBook(book.Id, h.store)
if err != nil { if err != nil {
notFound(h) notFound(h)
return return
@ -230,3 +236,16 @@ func contentHandler(h handler) {
defer html.Close() defer html.Close()
io.Copy(h.w, html) 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)))
}

View file

@ -4,32 +4,49 @@ import log "github.com/cihub/seelog"
import ( import (
"git.gitorious.org/trantor/trantor.git/database" "git.gitorious.org/trantor/trantor.git/database"
"git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"labix.org/v2/mgo/bson"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
) )
const (
stats_version = 2
)
type handler struct { type handler struct {
w http.ResponseWriter w http.ResponseWriter
r *http.Request r *http.Request
sess *Session sess *Session
db *database.DB db *database.DB
store *storage.Store
} }
func InitStats(database *database.DB) { type StatsGatherer struct {
statsChannel = make(chan statsRequest, CHAN_SIZE) db *database.DB
go statsWorker(database) 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) { return func(w http.ResponseWriter, r *http.Request) {
log.Info("Query ", r.Method, " ", r.RequestURI) log.Info("Query ", r.Method, " ", r.RequestURI)
var h handler var h handler
h.db = database.Copy() h.store = sg.store
h.db = sg.db.Copy()
defer h.db.Close() defer h.db.Close()
h.w = w h.w = w
@ -37,12 +54,10 @@ func GatherStats(function func(handler), database *database.DB) func(http.Respon
h.sess = GetSession(r, h.db) h.sess = GetSession(r, h.db)
function(h) 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 { type statsRequest struct {
date time.Time date time.Time
vars map[string]string vars map[string]string
@ -50,16 +65,17 @@ type statsRequest struct {
r *http.Request r *http.Request
} }
func statsWorker(database *database.DB) { func (sg StatsGatherer) worker() {
db := database.Copy() db := sg.db.Copy()
defer db.Close() defer db.Close()
for req := range statsChannel { for req := range sg.channel {
stats := make(map[string]interface{}) stats := make(map[string]interface{})
appendFiles(req.r, stats) appendFiles(req.r, stats)
appendMuxVars(req.vars, stats) appendMuxVars(req.vars, stats)
appendUrl(req.r, stats) appendUrl(req.r, stats)
appendSession(req.sess, stats) appendSession(req.sess, stats)
stats["version"] = stats_version
stats["method"] = req.r.Method stats["method"] = req.r.Method
stats["date"] = req.date stats["date"] = req.date
db.AddStats(stats) db.AddStats(stats)
@ -141,16 +157,12 @@ func appendMuxVars(vars map[string]string, stats map[string]interface{}) {
for key, value := range vars { for key, value := range vars {
switch { switch {
case key == "id": case key == "id":
if bson.IsObjectIdHex(value) { stats["id"] = value
stats["id"] = bson.ObjectIdHex(value)
}
case key == "ids": case key == "ids":
var objectIds []bson.ObjectId var objectIds []string
ids := strings.Split(value, "/") ids := strings.Split(value, "/")
for _, id := range ids { for _, id := range ids {
if bson.IsObjectIdHex(value) { objectIds = append(objectIds, id)
objectIds = append(objectIds, bson.ObjectIdHex(id))
}
} }
if len(objectIds) > 0 { if len(objectIds) > 0 {
stats["ids"] = objectIds stats["ids"] = objectIds

42
storage/dir.go Normal file
View file

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

57
storage/storage.go Normal file
View file

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

113
storage/storage_test.go Normal file
View file

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

115
store.go
View file

@ -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, "&#39;", "'", -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, "</p>", "\n", -1)
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)
}

View file

@ -26,7 +26,7 @@
<li class="span2"> <li class="span2">
<div class="thumbnail centered" style="border:none;"> <div class="thumbnail centered" style="border:none;">
<a href="/book/{{.Id}}"> <a href="/book/{{.Id}}">
{{if .CoverSmall}}<div class="down"><img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" /></div>{{end}} {{if .Cover}}<div class="down"><img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" /></div>{{end}}
<p><strong>{{.Title}}</strong></p> <p><strong>{{.Title}}</strong></p>
</a> </a>
</div> </div>
@ -44,7 +44,7 @@
<li class="span2"> <li class="span2">
<div class="thumbnail centered" style="border:none;"> <div class="thumbnail centered" style="border:none;">
<a href="/book/{{.Id}}"> <a href="/book/{{.Id}}">
{{if .CoverSmall}}<div class="down"><img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" /></div>{{end}} {{if .Cover}}<div class="down"><img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" /></div>{{end}}
<p><strong>{{.Title}}</strong></p> <p><strong>{{.Title}}</strong></p>
</a> </a>
</div> </div>
@ -62,7 +62,7 @@
<li class="span2"> <li class="span2">
<div class="thumbnail centered" style="border:none;"> <div class="thumbnail centered" style="border:none;">
<a href="/book/{{.Id}}"> <a href="/book/{{.Id}}">
{{if .CoverSmall}}<div class="down"><img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" /></div>{{end}} {{if .Cover}}<div class="down"><img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" /></div>{{end}}
<p><strong>{{.Title}}</strong></p> <p><strong>{{.Title}}</strong></p>
</a> </a>
</div> </div>

View file

@ -26,7 +26,7 @@
{{with .B}} {{with .B}}
<div class="row"> <div class="row">
<div class="span1"> <div class="span1">
<p class="pull-right">{{if .CoverSmall}}<img src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" />{{end}}</p> <p class="pull-right">{{if .Cover}}<img src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" />{{end}}</p>
</div> </div>
<div class="span9"> <div class="span9">
<p><a href="/search/?q=title:{{.Title}}"><strong>{{.Title}}</strong></a> <small>({{$titleFound}})</small><br /> <p><a href="/search/?q=title:{{.Title}}"><strong>{{.Title}}</strong></a> <small>({{$titleFound}})</small><br />

View file

@ -21,7 +21,7 @@
{{range .}} {{range .}}
<div class="row"> <div class="row">
<div class="span1"> <div class="span1">
<p class="pull-right"><a href="/book/{{.Id}}">{{if .CoverSmall}}<img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" />{{end}}</a></p> <p class="pull-right"><a href="/book/{{.Id}}">{{if .Cover}}<img class="img-rounded" src="/cover/{{.Id}}/small/{{.Title}}.jpg" alt="{{.Title}}" />{{end}}</a></p>
</div> </div>
<div class="span10 well"> <div class="span10 well">
<div class="row"> <div class="row">

View file

@ -4,9 +4,11 @@ import log "github.com/cihub/seelog"
import ( import (
"git.gitorious.org/trantor/trantor.git/database" "git.gitorious.org/trantor/trantor.git/database"
"git.gitorious.org/trantor/trantor.git/storage"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"io" "io"
"net/http" "net/http"
"os"
"strings" "strings"
) )
@ -71,8 +73,7 @@ func downloadHandler(h handler) {
} }
} }
fs := h.db.GetFS(FS_BOOKS) f, err := h.store.Get(book.Id, EPUB_FILE)
f, err := fs.OpenId(book.File)
if err != nil { if err != nil {
notFound(h) notFound(h)
return return
@ -81,7 +82,7 @@ func downloadHandler(h handler) {
headers := h.w.Header() headers := h.w.Header()
headers["Content-Type"] = []string{"application/epub+zip"} 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) io.Copy(h.w, f)
} }
@ -137,47 +138,55 @@ func main() {
db := database.Init(DB_IP, DB_NAME) db := database.Init(DB_IP, DB_NAME)
defer db.Close() defer db.Close()
InitTasks(db) store, err := storage.Init(STORE_PATH)
InitStats(db) if err != nil {
InitUpload(db) 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)) 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() r := mux.NewRouter()
var notFoundHandler http.HandlerFunc var notFoundHandler http.HandlerFunc
notFoundHandler = GatherStats(notFound, db) notFoundHandler = sg.Gather(notFound)
r.NotFoundHandler = notFoundHandler r.NotFoundHandler = notFoundHandler
r.HandleFunc("/", GatherStats(indexHandler, db)) r.HandleFunc("/", sg.Gather(indexHandler))
r.HandleFunc("/book/{id:[0-9a-fA-F]+}", GatherStats(bookHandler, db)) r.HandleFunc("/book/{id:"+id_pattern+"}", sg.Gather(bookHandler))
r.HandleFunc("/search/", GatherStats(searchHandler, db)) r.HandleFunc("/search/", sg.Gather(searchHandler))
r.HandleFunc("/upload/", GatherStats(uploadHandler, db)).Methods("GET") r.HandleFunc("/upload/", sg.Gather(uploadHandler)).Methods("GET")
r.HandleFunc("/upload/", GatherStats(uploadPostHandler, db)).Methods("POST") r.HandleFunc("/upload/", sg.Gather(uploadPostHandler)).Methods("POST")
r.HandleFunc("/login/", GatherStats(loginHandler, db)).Methods("GET") r.HandleFunc("/login/", sg.Gather(loginHandler)).Methods("GET")
r.HandleFunc("/login/", GatherStats(loginPostHandler, db)).Methods("POST") r.HandleFunc("/login/", sg.Gather(loginPostHandler)).Methods("POST")
r.HandleFunc("/create_user/", GatherStats(createUserHandler, db)).Methods("POST") r.HandleFunc("/create_user/", sg.Gather(createUserHandler)).Methods("POST")
r.HandleFunc("/logout/", GatherStats(logoutHandler, db)) r.HandleFunc("/logout/", sg.Gather(logoutHandler))
r.HandleFunc("/new/", GatherStats(newHandler, db)) r.HandleFunc("/new/", sg.Gather(newHandler))
r.HandleFunc("/store/{ids:([0-9a-fA-F]+/)+}", GatherStats(storeHandler, db)) r.HandleFunc("/store/{ids:("+id_pattern+"/)+}", sg.Gather(storeHandler))
r.HandleFunc("/delete/{ids:([0-9a-fA-F]+/)+}", GatherStats(deleteHandler, db)) r.HandleFunc("/delete/{ids:("+id_pattern+"/)+}", sg.Gather(deleteHandler))
r.HandleFunc("/read/{id:[0-9a-fA-F]+}", GatherStats(readStartHandler, db)) r.HandleFunc("/read/{id:"+id_pattern+"}", sg.Gather(readStartHandler))
r.HandleFunc("/read/{id:[0-9a-fA-F]+}/{file:.*}", GatherStats(readHandler, db)) r.HandleFunc("/read/{id:"+id_pattern+"}/{file:.*}", sg.Gather(readHandler))
r.HandleFunc("/content/{id:[0-9a-fA-F]+}/{file:.*}", GatherStats(contentHandler, db)) r.HandleFunc("/content/{id:"+id_pattern+"}/{file:.*}", sg.Gather(contentHandler))
r.HandleFunc("/edit/{id:[0-9a-fA-F]+}", GatherStats(editHandler, db)) r.HandleFunc("/edit/{id:"+id_pattern+"}", sg.Gather(editHandler))
r.HandleFunc("/save/{id:[0-9a-fA-F]+}", GatherStats(saveHandler, db)).Methods("POST") r.HandleFunc("/save/{id:"+id_pattern+"}", sg.Gather(saveHandler)).Methods("POST")
r.HandleFunc("/about/", GatherStats(aboutHandler, db)) r.HandleFunc("/about/", sg.Gather(aboutHandler))
r.HandleFunc("/help/", GatherStats(helpHandler, db)) r.HandleFunc("/help/", sg.Gather(helpHandler))
r.HandleFunc("/download/{id:[0-9a-fA-F]+}/{epub:.*}", GatherStats(downloadHandler, db)) r.HandleFunc("/download/{id:"+id_pattern+"}/{epub:.*}", sg.Gather(downloadHandler))
r.HandleFunc("/cover/{id:[0-9a-fA-F]+}/{size}/{img:.*}", GatherStats(coverHandler, db)) r.HandleFunc("/cover/{id:"+id_pattern+"}/{size}/{img:.*}", sg.Gather(coverHandler))
r.HandleFunc("/dashboard/", GatherStats(dashboardHandler, db)) r.HandleFunc("/dashboard/", sg.Gather(dashboardHandler))
r.HandleFunc("/settings/", GatherStats(settingsHandler, db)) r.HandleFunc("/settings/", sg.Gather(settingsHandler))
r.HandleFunc("/stats/", GatherStats(statsHandler, db)) r.HandleFunc("/stats/", sg.Gather(statsHandler))
r.HandleFunc("/news/", GatherStats(newsHandler, db)) r.HandleFunc("/news/", sg.Gather(newsHandler))
r.HandleFunc("/news/edit", GatherStats(editNewsHandler, db)).Methods("GET") r.HandleFunc("/news/edit", sg.Gather(editNewsHandler)).Methods("GET")
r.HandleFunc("/news/edit", GatherStats(postNewsHandler, db)).Methods("POST") r.HandleFunc("/news/edit", sg.Gather(postNewsHandler)).Methods("POST")
h := http.FileServer(http.Dir(IMG_PATH)) h := http.FileServer(http.Dir(IMG_PATH))
r.Handle("/img/{img}", http.StripPrefix("/img/", h)) r.Handle("/img/{img}", http.StripPrefix("/img/", h))
h = http.FileServer(http.Dir(CSS_PATH)) h = http.FileServer(http.Dir(CSS_PATH))

101
upload.go
View file

@ -4,16 +4,20 @@ import log "github.com/cihub/seelog"
import ( import (
"bytes" "bytes"
"crypto/rand"
"encoding/base64"
"git.gitorious.org/go-pkg/epubgo.git" "git.gitorious.org/go-pkg/epubgo.git"
"git.gitorious.org/trantor/trantor.git/database" "git.gitorious.org/trantor/trantor.git/database"
"git.gitorious.org/trantor/trantor.git/storage"
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
"regexp"
"strings" "strings"
) )
func InitUpload(database *database.DB) { func InitUpload(database *database.DB, store *storage.Store) {
uploadChannel = make(chan uploadRequest, CHAN_SIZE) uploadChannel = make(chan uploadRequest, CHAN_SIZE)
go uploadWorker(database) go uploadWorker(database, store)
} }
var uploadChannel chan uploadRequest var uploadChannel chan uploadRequest
@ -23,16 +27,16 @@ type uploadRequest struct {
filename string filename string
} }
func uploadWorker(database *database.DB) { func uploadWorker(database *database.DB, store *storage.Store) {
db := database.Copy() db := database.Copy()
defer db.Close() defer db.Close()
for req := range uploadChannel { 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() defer req.file.Close()
epub, err := openMultipartEpub(req.file) epub, err := openMultipartEpub(req.file)
@ -42,20 +46,18 @@ func processFile(req uploadRequest, db *database.DB) {
} }
defer epub.Close() defer epub.Close()
book := parseFile(epub, db) book, id := parseFile(epub, store)
title, _ := book["title"].(string)
req.file.Seek(0, 0) 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 { if err != nil {
log.Error("Error storing book (", title, "): ", err) log.Error("Error storing book (", id, "): ", err)
return return
} }
book["file"] = id
book["filesize"] = size book["filesize"] = size
err = db.AddBook(book) err = db.AddBook(book)
if err != nil { if err != nil {
log.Error("Error storing metadata (", title, "): ", err) log.Error("Error storing metadata (", id, "): ", err)
return return
} }
log.Info("File uploaded: ", req.filename) 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))) 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{}{} book := map[string]interface{}{}
for _, m := range epub.MetadataFields() { for _, m := range epub.MetadataFields() {
data, err := epub.Metadata(m) 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, ", ") book[m] = strings.Join(data, ", ")
} }
} }
title, _ := book["title"].(string)
book["file"] = nil id = genId()
cover, coverSmall := GetCover(epub, title, db) book["id"] = id //TODO
if cover != "" { book["cover"] = GetCover(epub, id, store)
book["cover"] = cover return book, id
book["coversmall"] = coverSmall }
}
return book func genId() string {
b := make([]byte, 12)
rand.Read(b)
return base64.URLEncoding.EncodeToString(b)
}
func cleanStr(str string) string {
str = strings.Replace(str, "&#39;", "'", -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, "</p>", "\n", -1)
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)
} }