2014-06-29 19:41:29 -05:00
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
2020-05-03 09:35:17 +00:00
|
|
|
log "github.com/cihub/seelog"
|
|
|
|
|
2014-08-30 13:17:50 -05:00
|
|
|
"strings"
|
2015-01-22 21:58:27 -06:00
|
|
|
"time"
|
2017-05-19 00:27:14 +00:00
|
|
|
|
2020-11-30 19:03:31 +00:00
|
|
|
"github.com/go-pg/pg/v10"
|
2014-06-29 19:41:29 -05:00
|
|
|
)
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// Book metadata
|
2014-06-29 19:41:29 -05:00
|
|
|
type Book struct {
|
2019-11-06 06:53:41 +00:00
|
|
|
ID string `pg:"type:varchar(16)"`
|
2016-07-30 07:10:33 -04:00
|
|
|
Title string
|
2019-11-06 06:53:41 +00:00
|
|
|
Authors []string `pg:"authors,array"`
|
2016-07-30 07:10:33 -04:00
|
|
|
Contributor string
|
|
|
|
Publisher string
|
|
|
|
Description string
|
2019-11-06 06:53:41 +00:00
|
|
|
Tags []string `pg:"tags,array"`
|
2016-07-30 07:10:33 -04:00
|
|
|
Date string
|
2020-05-03 09:35:17 +00:00
|
|
|
Lang string `pg:"type:varchar(3)"`
|
|
|
|
Isbn string `pg:"type:varchar(13)"`
|
|
|
|
FileSize int `pg:"type:integer"`
|
|
|
|
FileHash []byte
|
2020-11-30 19:03:31 +00:00
|
|
|
Cover bool `pg:",use_zero"`
|
|
|
|
Active bool `pg:",use_zero"`
|
2019-11-06 06:53:41 +00:00
|
|
|
UploadDate time.Time `pg:"type:timestamp"`
|
|
|
|
Tsv string `pg:"type:tsvector"`
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:10:33 -04:00
|
|
|
// AddBook to the database
|
|
|
|
func (db *pgDB) AddBook(book Book) error {
|
|
|
|
emptyTime := time.Time{}
|
|
|
|
if book.UploadDate == emptyTime {
|
|
|
|
book.UploadDate = time.Now()
|
2014-08-31 15:09:59 -05:00
|
|
|
}
|
|
|
|
|
2020-11-30 19:03:31 +00:00
|
|
|
_, err := db.sql.Model(&book).Insert()
|
|
|
|
return err
|
2014-08-31 15:09:59 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:10:33 -04:00
|
|
|
// GetBooks matching query
|
|
|
|
func (db *pgDB) GetBooks(query string, length int, start int) (books []Book, num int, err error) {
|
|
|
|
return db.getBooks(true, query, length, start)
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// GetNewBooks returns a list of books in the incoming queue and the number of books
|
|
|
|
// in the queue
|
2016-07-30 07:10:33 -04:00
|
|
|
func (db *pgDB) GetNewBooks(query string, length int, start int) (books []Book, num int, err error) {
|
|
|
|
return db.getBooks(false, query, length, start)
|
2014-07-02 20:58:00 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:10:33 -04:00
|
|
|
func (db *pgDB) getBooks(active bool, query string, length int, start int) (books []Book, num int, err error) {
|
2017-05-18 22:16:16 +00:00
|
|
|
rank := []string{}
|
|
|
|
rankParams := []interface{}{}
|
|
|
|
searchCondition := ""
|
2016-07-30 07:10:33 -04:00
|
|
|
if active {
|
2017-05-18 22:16:16 +00:00
|
|
|
searchCondition += "active is true"
|
2016-07-30 07:10:33 -04:00
|
|
|
} else {
|
2017-05-18 22:16:16 +00:00
|
|
|
searchCondition += "active is not true"
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
2017-03-23 11:13:39 +00:00
|
|
|
searchParams := []interface{}{}
|
2016-07-30 07:10:33 -04:00
|
|
|
|
2017-03-23 11:13:39 +00:00
|
|
|
textQuery, columnQuerys, trigramQuerys := buildQuery(query)
|
2016-07-30 07:10:33 -04:00
|
|
|
for _, c := range columnQuerys {
|
2017-05-18 22:16:16 +00:00
|
|
|
searchCondition += " AND " + c.column + " = ?"
|
2017-03-23 11:13:39 +00:00
|
|
|
searchParams = append(searchParams, c.value)
|
|
|
|
}
|
|
|
|
for _, c := range trigramQuerys {
|
2017-05-18 22:16:16 +00:00
|
|
|
rank = append(rank, "word_similarity(?, "+c.column+")")
|
|
|
|
rankParams = append(rankParams, c.value)
|
|
|
|
searchCondition += " AND " + c.column + " %> ?"
|
2017-03-23 11:13:39 +00:00
|
|
|
searchParams = append(searchParams, c.value)
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
2016-07-30 07:10:33 -04:00
|
|
|
if textQuery != "" {
|
2017-05-29 12:38:35 +00:00
|
|
|
rank = append(rank, "ts_rank(tsv, to_tsquery_multilingual(?))")
|
2017-05-18 22:16:16 +00:00
|
|
|
rankParams = append(rankParams, textQuery)
|
2017-05-29 12:38:35 +00:00
|
|
|
searchCondition += " AND to_tsquery_multilingual(?) @@ tsv"
|
2017-03-23 11:13:39 +00:00
|
|
|
searchParams = append(searchParams, textQuery)
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
|
|
|
|
2017-03-23 11:13:39 +00:00
|
|
|
order := "upload_date DESC"
|
2017-05-18 22:16:16 +00:00
|
|
|
if len(rank) > 0 {
|
|
|
|
order = strings.Join(rank, "+") + " DESC, upload_date DESC"
|
2015-04-21 21:52:34 -04:00
|
|
|
}
|
|
|
|
|
2021-05-07 07:51:57 +00:00
|
|
|
q := db.sql.Model(&books).
|
2017-03-23 11:13:39 +00:00
|
|
|
Where(searchCondition, searchParams...).
|
2017-05-18 22:16:16 +00:00
|
|
|
OrderExpr(order, rankParams...).
|
2016-07-30 07:10:33 -04:00
|
|
|
Offset(start).
|
2021-05-07 07:51:57 +00:00
|
|
|
Limit(length)
|
|
|
|
if active {
|
|
|
|
num, err = q.SelectAndCountEstimate(1000)
|
|
|
|
} else {
|
|
|
|
num, err = q.SelectAndCount()
|
|
|
|
}
|
2016-07-30 07:10:33 -04:00
|
|
|
return books, num, err
|
2015-04-21 21:52:34 -04:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// GetBookID returns a the book with the specified id
|
|
|
|
func (db *pgDB) GetBookID(id string) (Book, error) {
|
2014-07-02 20:40:24 -05:00
|
|
|
var book Book
|
2016-07-30 07:10:33 -04:00
|
|
|
err := db.sql.Model(&book).
|
|
|
|
Where("id = ?", id).
|
|
|
|
Select()
|
2014-07-02 20:40:24 -05:00
|
|
|
return book, err
|
|
|
|
}
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// DeleteBook removes the book with id from the database
|
2016-07-30 07:10:33 -04:00
|
|
|
func (db *pgDB) DeleteBook(id string) error {
|
|
|
|
_, err := db.sql.Model(&Book{}).
|
|
|
|
Where("id = ?", id).
|
|
|
|
Delete()
|
|
|
|
return err
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// UpdateBook metadata
|
2016-07-30 07:10:33 -04:00
|
|
|
func (db *pgDB) UpdateBook(id string, data map[string]interface{}) error {
|
|
|
|
setCondition := ""
|
|
|
|
params := []interface{}{}
|
|
|
|
for col, val := range data {
|
|
|
|
colValid := false
|
|
|
|
for _, name := range []string{"title", "authors", "contributor", "publisher",
|
|
|
|
"description", "tags", "date", "lang", "isbn"} {
|
|
|
|
if col == name {
|
|
|
|
colValid = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !colValid {
|
|
|
|
continue
|
|
|
|
}
|
2015-01-22 21:58:27 -06:00
|
|
|
|
2016-07-30 07:10:33 -04:00
|
|
|
if len(setCondition) != 0 {
|
|
|
|
setCondition += ", "
|
2015-01-22 21:58:27 -06:00
|
|
|
}
|
2016-07-30 07:10:33 -04:00
|
|
|
setCondition += col + " = ?"
|
2017-05-30 23:28:42 +00:00
|
|
|
if col == "authors" || col == "tags" {
|
|
|
|
params = append(params, pg.Array(val))
|
|
|
|
} else {
|
|
|
|
params = append(params, val)
|
|
|
|
}
|
2014-08-21 19:24:23 -05:00
|
|
|
}
|
2016-07-30 07:10:33 -04:00
|
|
|
_, err := db.sql.Model(&Book{}).
|
|
|
|
Set(setCondition, params...).
|
|
|
|
Where("id = ?", id).
|
|
|
|
Update()
|
|
|
|
return err
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// ActiveBook activates the book
|
2016-07-30 07:10:33 -04:00
|
|
|
func (db *pgDB) ActiveBook(id string) error {
|
|
|
|
uploadDate := time.Now()
|
|
|
|
_, err := db.sql.Model(&Book{}).
|
|
|
|
Set("active = true, upload_date = ? ", uploadDate).
|
|
|
|
Where("id = ?", id).
|
|
|
|
Update()
|
|
|
|
return err
|
2014-08-21 19:24:23 -05:00
|
|
|
}
|
|
|
|
|
2016-07-30 07:59:30 -04:00
|
|
|
// IsBookActive checks if the book is active
|
2016-07-30 07:10:33 -04:00
|
|
|
func (db *pgDB) IsBookActive(id string) bool {
|
|
|
|
var active []bool
|
|
|
|
err := db.sql.Model(&Book{}).
|
2021-04-30 14:26:40 +00:00
|
|
|
Column("active").
|
2016-07-30 07:10:33 -04:00
|
|
|
Where("id = ?", id).
|
|
|
|
Select(&active)
|
|
|
|
if err != nil || len(active) != 1 {
|
2014-06-29 19:41:29 -05:00
|
|
|
return false
|
|
|
|
}
|
2016-07-30 07:10:33 -04:00
|
|
|
return active[0]
|
|
|
|
}
|
|
|
|
|
2020-05-03 09:35:17 +00:00
|
|
|
// ExistsBookHash checks if the given hash matches the hash of any book in the lbirary
|
|
|
|
func (db *pgDB) ExistsBookHash(hash []byte) bool {
|
|
|
|
count, err := db.sql.Model(&Book{}).
|
|
|
|
Where("file_hash = ?", hash).
|
|
|
|
Count()
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("There was an error looking for the hash: %v", err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return count > 0
|
|
|
|
}
|
|
|
|
|
2016-07-30 07:10:33 -04:00
|
|
|
type columnq struct {
|
|
|
|
column string
|
|
|
|
value string
|
2014-06-29 19:41:29 -05:00
|
|
|
}
|
2014-07-02 20:58:00 -05:00
|
|
|
|
2017-03-23 11:13:39 +00:00
|
|
|
func buildQuery(query string) (textQuery string, columnQuerys []columnq, trigramQuerys []columnq) {
|
2017-09-20 19:53:33 +00:00
|
|
|
tokens := extractTokens(query)
|
|
|
|
for _, token := range tokens {
|
|
|
|
if token == "" {
|
2016-07-30 07:10:33 -04:00
|
|
|
continue
|
|
|
|
}
|
2017-09-20 19:53:33 +00:00
|
|
|
tag := strings.SplitN(token, ":", 2)
|
|
|
|
value := ""
|
|
|
|
if len(tag) > 1 {
|
|
|
|
value = strings.Replace(tag[1], "%", "\\%", 0)
|
2016-07-30 07:10:33 -04:00
|
|
|
value = strings.Replace(value, "_", "\\_", 0)
|
2017-09-20 19:53:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch tag[0] {
|
|
|
|
case "lang":
|
|
|
|
columnQuerys = append(columnQuerys, columnq{"lang", value})
|
|
|
|
case "isbn":
|
|
|
|
columnQuerys = append(columnQuerys, columnq{"isbn", value})
|
|
|
|
case "author":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"text(authors)", value})
|
|
|
|
case "title":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"title", value})
|
|
|
|
case "contributor":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"contributor", value})
|
|
|
|
case "publisher":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"publisher", value})
|
|
|
|
case "subject":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"text(tags)", value})
|
|
|
|
case "tag":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"text(tags)", value})
|
2020-03-23 11:35:10 +00:00
|
|
|
case "date":
|
|
|
|
trigramQuerys = append(trigramQuerys, columnq{"date", value})
|
2017-09-20 19:53:33 +00:00
|
|
|
default:
|
2016-07-30 07:10:33 -04:00
|
|
|
if len(textQuery) != 0 {
|
|
|
|
lastChar := textQuery[len(textQuery)-1:]
|
2017-09-20 19:53:33 +00:00
|
|
|
if token[:1] != "&" && token[:1] != "|" && lastChar != "&" && lastChar != "|" {
|
2016-07-30 07:10:33 -04:00
|
|
|
textQuery += " | "
|
|
|
|
} else {
|
|
|
|
textQuery += " "
|
|
|
|
}
|
2014-10-26 12:12:27 -06:00
|
|
|
}
|
2017-09-20 19:53:33 +00:00
|
|
|
textQuery += strings.Join(strings.Fields(token), " <-> ")
|
2014-07-02 20:58:00 -05:00
|
|
|
}
|
|
|
|
}
|
2017-03-23 11:13:39 +00:00
|
|
|
return
|
2014-07-02 20:58:00 -05:00
|
|
|
}
|
2017-09-20 19:53:33 +00:00
|
|
|
|
|
|
|
func extractTokens(query string) []string {
|
|
|
|
tokens := []string{}
|
|
|
|
quoted := strings.Split(query, "\"")
|
|
|
|
for i, s := range quoted {
|
|
|
|
if i%2 == 0 {
|
|
|
|
tokens = append(tokens, strings.Fields(s)...)
|
|
|
|
} else {
|
|
|
|
// quoted string
|
|
|
|
if len(tokens) > 0 {
|
|
|
|
lastToken := tokens[len(tokens)-1]
|
|
|
|
if len(lastToken) > 0 && lastToken[len(lastToken)-1] == ':' {
|
|
|
|
tokens[len(tokens)-1] += s
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tokens = append(tokens, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tokens
|
|
|
|
}
|