|
@@ -1,7 +1,7 @@
|
|
|
package db
|
|
|
|
|
|
import (
|
|
|
- "notabug.org/apiote/amuse/tmdb"
|
|
|
+ "notabug.org/apiote/amuse/datastructure"
|
|
|
"notabug.org/apiote/amuse/utils"
|
|
|
|
|
|
"crypto/rand"
|
|
@@ -9,20 +9,18 @@ import (
|
|
|
"encoding/hex"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
+ "math"
|
|
|
"os"
|
|
|
+ "sort"
|
|
|
"time"
|
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
)
|
|
|
|
|
|
-type ItemType string
|
|
|
-
|
|
|
-const (
|
|
|
- ItemTypeBook ItemType = "book"
|
|
|
- ItemTypeFilm = "film"
|
|
|
- ItemTypeTvserie = "tvserie"
|
|
|
- ItemTypeUnkown = "unknown"
|
|
|
-)
|
|
|
+type CacheEntry struct {
|
|
|
+ Etag string
|
|
|
+ Data []byte
|
|
|
+}
|
|
|
|
|
|
type EmptyError struct {
|
|
|
message string
|
|
@@ -51,7 +49,6 @@ type Session struct {
|
|
|
}
|
|
|
|
|
|
func Migrate() error {
|
|
|
- // todo migrations
|
|
|
db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -59,23 +56,27 @@ func Migrate() error {
|
|
|
defer db.Close()
|
|
|
|
|
|
_, err = db.Exec(`create table cache(uri text primary key, etag text, date date, response blob, last_hit date)`)
|
|
|
- if err != nil {
|
|
|
+ if err != nil && err.Error() != "table cache already exists" {
|
|
|
return err
|
|
|
}
|
|
|
_, err = db.Exec(`create table users(username text primary key, password text, sfa text, avatar blob, avatar_small blob, is_admin bool, recovery_codes text, timezone text)`)
|
|
|
- if err != nil {
|
|
|
+ if err != nil && err.Error() != "table users already exists" {
|
|
|
return err
|
|
|
}
|
|
|
_, err = db.Exec(`create table sessions(id text primary key, username text, expiry datetime, is_long boolean, foreign key(username) references users(username))`)
|
|
|
- if err != nil {
|
|
|
+ if err != nil && err.Error() != "table sessions already exists" {
|
|
|
return err
|
|
|
}
|
|
|
_, err = db.Exec(`create table wantlist(username text, item_type text, item_id text, primary key(username, item_type, item_id), foreign key(username) references users(username))`)
|
|
|
- if err != nil {
|
|
|
+ if err != nil && err.Error() != "table wantlist already exists" {
|
|
|
return err
|
|
|
}
|
|
|
_, err = db.Exec(`create table experiences(username text, item_type text, item_id text, time datetime, foreign key(username) references users(username), primary key(username, item_type, item_id, time))`)
|
|
|
- if err != nil {
|
|
|
+ if err != nil && err.Error() != "table experiences already exists" {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ _, err = db.Exec(`create table item_cache (item_type text, item_id text, cover text, status text, title text, year_start int, year_end int, based_on text, genres text, runtime int, collection int, part int, ref_count int, primary key(item_type, item_id))`)
|
|
|
+ if err != nil && err.Error() != "table item_cache already exists" {
|
|
|
return err
|
|
|
}
|
|
|
return nil
|
|
@@ -224,7 +225,7 @@ func ClearSessions(username string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func GetItemExperiences(username, itemId string, itemType ItemType) (map[string][]time.Time, error) {
|
|
|
+func GetItemExperiences(username, itemId string, itemType datastructure.ItemType) (map[string][]time.Time, error) {
|
|
|
times := map[string][]time.Time{}
|
|
|
user, err := GetUser(username)
|
|
|
if err != nil {
|
|
@@ -263,10 +264,17 @@ func GetItemExperiences(username, itemId string, itemType ItemType) (map[string]
|
|
|
t = t.In(location)
|
|
|
times[id] = append(times[id], t)
|
|
|
}
|
|
|
+
|
|
|
+ for k, v := range times {
|
|
|
+ sort.Slice(v, func(i, j int) bool {
|
|
|
+ return v[i].After(v[j])
|
|
|
+ })
|
|
|
+ times[k] = v
|
|
|
+ }
|
|
|
return times, nil
|
|
|
}
|
|
|
|
|
|
-func AddToExperiences(username, itemId string, itemType ItemType, datetime time.Time) error {
|
|
|
+func AddToExperiences(username, itemId string, itemType datastructure.ItemType, datetime time.Time) error {
|
|
|
db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
if err != nil {
|
|
|
fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
@@ -284,7 +292,7 @@ func AddToExperiences(username, itemId string, itemType ItemType, datetime time.
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func SkipSpecials(username, itemId string, episodesNumber int, itemType ItemType, datetime time.Time) error {
|
|
|
+func SkipSpecials(username, itemId string, episodesNumber int, itemType datastructure.ItemType, datetime time.Time) error {
|
|
|
db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
if err != nil {
|
|
|
fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
@@ -294,6 +302,7 @@ func SkipSpecials(username, itemId string, episodesNumber int, itemType ItemType
|
|
|
|
|
|
for e := 1; e <= episodesNumber; e++ {
|
|
|
episodeId := fmt.Sprintf("%s/S00E%02d", itemId, e)
|
|
|
+ // todo if not watched already
|
|
|
_, err = db.Exec(`insert into experiences values(?, ?, ?, ?)`, username, itemType, episodeId, datetime)
|
|
|
if err != nil {
|
|
|
if err.Error()[:6] != "UNIQUE" {
|
|
@@ -308,7 +317,7 @@ func SkipSpecials(username, itemId string, episodesNumber int, itemType ItemType
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func AddToWantList(username, itemId string, itemType ItemType) error {
|
|
|
+func AddToWantList(username, itemId string, itemType datastructure.ItemType) error {
|
|
|
db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
if err != nil {
|
|
|
fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
@@ -324,7 +333,7 @@ func AddToWantList(username, itemId string, itemType ItemType) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func RemoveFromWantList(username, itemId string, itemType ItemType) error {
|
|
|
+func RemoveFromWantList(username, itemId string, itemType datastructure.ItemType) error {
|
|
|
db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
if err != nil {
|
|
|
fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
@@ -332,15 +341,25 @@ func RemoveFromWantList(username, itemId string, itemType ItemType) error {
|
|
|
}
|
|
|
defer db.Close()
|
|
|
|
|
|
- _, err = db.Exec(`delete from wantlist where username = ? and item_type = ? and item_id = ?`, username, itemType, itemId)
|
|
|
+ result, err := db.Exec(`delete from wantlist where username = ? and item_type = ? and item_id = ?`, username, itemType, itemId)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
|
|
|
+ fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ rows, err := result.RowsAffected()
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
|
|
|
return err
|
|
|
}
|
|
|
+ if rows == 0 {
|
|
|
+ return EmptyError{
|
|
|
+ message: "Empty delete",
|
|
|
+ }
|
|
|
+ }
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func IsOnWantList(username, itemId string, itemType ItemType) (bool, error) {
|
|
|
+func IsOnWantList(username, itemId string, itemType datastructure.ItemType) (bool, error) {
|
|
|
db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
if err != nil {
|
|
|
fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
@@ -359,12 +378,267 @@ func IsOnWantList(username, itemId string, itemType ItemType) (bool, error) {
|
|
|
return isOnlist, nil
|
|
|
}
|
|
|
|
|
|
-func GetItemTypeFromShow(show tmdb.Show) ItemType {
|
|
|
- if _, ok := show.(*tmdb.Film); ok {
|
|
|
- return ItemTypeFilm
|
|
|
- } else if _, ok := show.(*tmdb.TvSerie); ok {
|
|
|
- return ItemTypeTvserie
|
|
|
- } else {
|
|
|
- return ItemTypeUnkown
|
|
|
+func SaveCacheItem(itemType datastructure.ItemType, itemId string, itemInfo datastructure.ItemInfo) error {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ _, err = db.Exec(`insert into item_cache values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
+ on conflict(item_type, item_id) do update set ref_count = ref_count + 1`,
|
|
|
+ itemType, itemId, itemInfo.Cover, itemInfo.Status, itemInfo.Title, itemInfo.YearStart, itemInfo.YearEnd, itemInfo.BasedOn, itemInfo.Genres, itemInfo.Runtime, itemInfo.Collection, itemInfo.Part, 1)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func UpdateCacheItem(itemType datastructure.ItemType, itemId string, itemInfo datastructure.ItemInfo) error {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ db.Exec(`update item_cache set cover = ?, status = ?, title = ?, year_start = ?, year_end = ?, based_on = ?, genres = ?, runtime = ?, collection = ?, part = ? where item_type = ? and item_id = ?`, itemInfo.Cover, itemInfo.Status, itemInfo.Title, itemInfo.YearStart, itemInfo.YearEnd, itemInfo.BasedOn, itemInfo.Genres, itemInfo.Runtime, itemInfo.Collection, itemInfo.Part, itemType, itemId)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func RemoveCacheItem(itemType datastructure.ItemType, itemId string) error {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ _, err = db.Exec(`update item_cache set ref_count = ref_count - 1 where item_id = ?`, itemId)
|
|
|
+
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+func CleanItemCache() error {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ _, err = db.Exec(`delete from item_cache where ref_count <= 0`)
|
|
|
+
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+func GetCacheItem(itemType datastructure.ItemType, itemId string) (*datastructure.ItemInfo, error) {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ var (
|
|
|
+ itemInfo datastructure.ItemInfo
|
|
|
+ itemTypeDb datastructure.ItemType
|
|
|
+ itemIdDb string
|
|
|
+ refCount int
|
|
|
+ )
|
|
|
+
|
|
|
+ row := db.QueryRow(`select * from cache where item_type = ? and item_id = ?`, itemType, itemId)
|
|
|
+
|
|
|
+ err = row.Scan(&itemTypeDb, &itemIdDb, &itemInfo.Cover, &itemInfo.Status, &itemInfo.Title, &itemInfo.YearStart, &itemInfo.YearEnd, &itemInfo.BasedOn, &itemInfo.Genres, &itemInfo.Runtime, &itemInfo.Collection, &itemInfo.Part, refCount)
|
|
|
+ if err != nil {
|
|
|
+ if err == sql.ErrNoRows {
|
|
|
+ return nil, nil
|
|
|
+ } else {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return &itemInfo, nil
|
|
|
+}
|
|
|
+
|
|
|
+// ====
|
|
|
+
|
|
|
+func GetCacheEntry(uri string) (*CacheEntry, error) {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ row := db.QueryRow(`select etag, response from cache where uri = ?`, uri)
|
|
|
+
|
|
|
+ var cacheEntry CacheEntry
|
|
|
+ err = row.Scan(&cacheEntry.Etag, &cacheEntry.Data)
|
|
|
+ if err != nil {
|
|
|
+ if err == sql.ErrNoRows {
|
|
|
+ return nil, nil
|
|
|
+ } else {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return &cacheEntry, err
|
|
|
+}
|
|
|
+
|
|
|
+func CleanCache() error {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ row := db.QueryRow(`select count(*) from cache`)
|
|
|
+
|
|
|
+ var count int
|
|
|
+ err = row.Scan(&count)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ for count > 10000 {
|
|
|
+ _, err = db.Exec(`delete from cache where last_update = (select min(last_update) from cache)`)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ count--
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func SaveCacheEntry(uri, etag string, data []byte) error {
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ _, err = db.Exec(`insert into cache values(?, ?, null, ?, datetime('now'))
|
|
|
+ on conflict(uri) do update set etag = excluded.etag, response = excluded.response, last_hit = excluded.last_hit`, uri, etag, data)
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+func GetWatchlist(username, filter string, page int) (datastructure.Watchlist, error) {
|
|
|
+ watchlist := datastructure.Watchlist{}
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return watchlist, err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ if page <= 0 {
|
|
|
+ page = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ var pages float64
|
|
|
+ row := db.QueryRow(`select count(*) from wantlist where item_type = 'film' and username = ?`, username)
|
|
|
+ err = row.Scan(&pages)
|
|
|
+ if err != nil {
|
|
|
+ return watchlist, err
|
|
|
+ }
|
|
|
+ watchlist.Pages = int(math.Ceil(pages / 18))
|
|
|
+
|
|
|
+ offset := (page - 1) * 18
|
|
|
+
|
|
|
+ //todo filter, order by
|
|
|
+
|
|
|
+ var whereClause string
|
|
|
+ if filter != "" {
|
|
|
+ whereClause = "and c1.title like '%" + filter + "%'"
|
|
|
+ }
|
|
|
+
|
|
|
+ rows, err := db.Query(`select c1.item_id, c1.cover, c1.status, c1.title, c1.year_start, c1.based_on, c1.genres, c1.runtime, c1.part, c2.part from wantlist natural join item_cache c1 left join item_cache c2 on(c1.part-1 = c2.part and c1.collection = c2.collection) where c1.item_type = 'film' and username = ? `+whereClause+` order by c1.title limit ?,18`, username, offset)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
|
|
|
+ return watchlist, err
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ for rows.Next() {
|
|
|
+ var (
|
|
|
+ entry datastructure.WatchlistEntry
|
|
|
+ prevPart *int
|
|
|
+ )
|
|
|
+ err := rows.Scan(&entry.Id, &entry.Cover, &entry.Status, &entry.Title, &entry.YearStart, &entry.BasedOn, &entry.Genres, &entry.Runtime, &entry.Part, &prevPart)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Scan error")
|
|
|
+ return datastructure.Watchlist{}, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if prevPart != nil {
|
|
|
+ entry.HasPrevious = true // todo is not on watched
|
|
|
+ }
|
|
|
+ watchlist.List = append(watchlist.List, entry)
|
|
|
+ }
|
|
|
+
|
|
|
+ return watchlist, nil
|
|
|
+}
|
|
|
+
|
|
|
+func GetUserExperiences(username, filter string, page int) (datastructure.Experiences, error) {
|
|
|
+ experiences := datastructure.Experiences{}
|
|
|
+ db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "DB open err\n")
|
|
|
+ return experiences, err
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ if page <= 0 {
|
|
|
+ page = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ var pages float64
|
|
|
+ row := db.QueryRow(`select count(*) from experiences where username = ? and time != '0001-01-01 00:00:00+00:00'`, username)
|
|
|
+ err = row.Scan(&pages)
|
|
|
+ if err != nil {
|
|
|
+ return experiences, err
|
|
|
+ }
|
|
|
+ experiences.Pages = int(math.Ceil(pages / 18))
|
|
|
+
|
|
|
+ offset := (page - 1) * 18
|
|
|
+
|
|
|
+ //todo filter, order by
|
|
|
+
|
|
|
+ var whereClause string
|
|
|
+ if filter != "" {
|
|
|
+ whereClause = "and c1.title like '%" + filter + "%'"
|
|
|
}
|
|
|
+
|
|
|
+ rows, err := db.Query(`select case when substr(e.item_id, 1, pos-1) = '' then e.item_id else substr(e.item_id, 1, pos-1) end as id, substr(e.item_id, pos+1) as code, e.item_type, time, title, year_start, collection, part from (select *, instr(item_id, '/') as pos from experiences) e join item_cache c on id = c.item_id and e.item_type = c.item_type where username = ? `+whereClause+` order by time desc limit ?,18;`, username, offset)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
|
|
|
+ return experiences, err
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ for rows.Next() {
|
|
|
+ var (
|
|
|
+ entry datastructure.ExperiencesEntry
|
|
|
+ )
|
|
|
+ err := rows.Scan(&entry.Id, &entry.Code, &entry.Type, &entry.Datetime, &entry.Title, &entry.YearStart, &entry.Collection, &entry.Part)
|
|
|
+ entry.Part += 1
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Scan error")
|
|
|
+ return datastructure.Experiences{}, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if !entry.Datetime.IsZero() {
|
|
|
+ experiences.List = append(experiences.List, entry)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return experiences, nil
|
|
|
+
|
|
|
}
|