db.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. package db
  2. import (
  3. "notabug.org/apiote/amuse/datastructure"
  4. "notabug.org/apiote/amuse/config"
  5. "crypto/rand"
  6. "database/sql"
  7. "encoding/hex"
  8. "errors"
  9. "fmt"
  10. "math"
  11. "os"
  12. "sort"
  13. "time"
  14. _ "github.com/mattn/go-sqlite3"
  15. )
  16. type CacheEntry struct {
  17. Etag string
  18. Data []byte
  19. }
  20. type EmptyError struct {
  21. message string
  22. }
  23. func (e EmptyError) Error() string {
  24. return e.message
  25. }
  26. type User struct {
  27. Username string
  28. PasswordHash string
  29. Sfa string
  30. Avatar []byte
  31. AvatarSmall []byte
  32. IsAdmin bool
  33. RecoveryCodes string
  34. Timezone string
  35. }
  36. type Session struct {
  37. Id string
  38. Username string
  39. Expiry time.Time
  40. IsLong bool
  41. }
  42. func Migrate() error {
  43. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  44. if err != nil {
  45. return err
  46. }
  47. defer db.Close()
  48. _, err = db.Exec(`create table cache(uri text primary key, etag text, date date, response blob, last_hit date)`)
  49. if err != nil && err.Error() != "table cache already exists" {
  50. return err
  51. }
  52. _, err = db.Exec(`create table users(username text primary key, password text, sfa text, is_admin bool, recovery_codes text, avatar blob, avatar_small blob, timezone text)`)
  53. if err != nil && err.Error() != "table users already exists" {
  54. return err
  55. }
  56. _, err = db.Exec(`create table sessions(id text primary key, username text, expiry datetime, is_long boolean, foreign key(username) references users(username))`)
  57. if err != nil && err.Error() != "table sessions already exists" {
  58. return err
  59. }
  60. _, 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))`)
  61. if err != nil && err.Error() != "table wantlist already exists" {
  62. return err
  63. }
  64. _, 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))`)
  65. if err != nil && err.Error() != "table experiences already exists" {
  66. return err
  67. }
  68. _, 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, episodes int, primary key(item_type, item_id))`)
  69. if err != nil && err.Error() != "table item_cache already exists" {
  70. return err
  71. }
  72. return nil
  73. }
  74. func MakeAdmin(username string) error {
  75. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  76. if err != nil {
  77. fmt.Fprintf(os.Stderr, "DB open err\n")
  78. return err
  79. }
  80. defer db.Close()
  81. _, err = db.Exec("update users set is_admin = 1 where username = ?", username)
  82. if err != nil {
  83. fmt.Fprintf(os.Stderr, "Update err %v\n", err)
  84. return err
  85. }
  86. rows, err := db.Query(`select is_admin from users where username = ?`, username)
  87. if err != nil {
  88. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  89. return err
  90. }
  91. defer rows.Close()
  92. if !rows.Next() {
  93. fmt.Fprintf(os.Stderr, "User %s does not exist\n", username)
  94. return errors.New("User does not exist")
  95. }
  96. var isAdmin bool
  97. err = rows.Scan(&isAdmin)
  98. if err != nil {
  99. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  100. return err
  101. }
  102. fmt.Println(isAdmin)
  103. return nil
  104. }
  105. func InsertUser(username, password, sfaSecret, recoveryCodes string) error {
  106. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  107. if err != nil {
  108. fmt.Fprintf(os.Stderr, "DB open err\n")
  109. return err
  110. }
  111. defer db.Close()
  112. _, err = db.Exec("insert into users values(?, ?, ?, 0, ?, '', '', 'UTC')", username, password, sfaSecret, recoveryCodes)
  113. if err != nil {
  114. fmt.Fprintf(os.Stderr, "Inert err %v\n", err)
  115. return err
  116. }
  117. return nil
  118. }
  119. func GetUser(username string) (*User, error) {
  120. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  121. if err != nil {
  122. fmt.Fprintf(os.Stderr, "DB open err\n")
  123. return nil, err
  124. }
  125. defer db.Close()
  126. rows, err := db.Query(`select password, sfa, recovery_codes, is_admin, avatar, avatar_small, timezone from users where username = ?`, username)
  127. if err != nil {
  128. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  129. return nil, err
  130. }
  131. defer rows.Close()
  132. if !rows.Next() {
  133. return nil, EmptyError{message: "User does not exist"}
  134. }
  135. user := User{Username: username}
  136. err = rows.Scan(&user.PasswordHash, &user.Sfa, &user.RecoveryCodes, &user.IsAdmin, &user.Avatar, &user.AvatarSmall, &user.Timezone)
  137. if err != nil {
  138. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  139. return nil, err
  140. }
  141. return &user, nil
  142. }
  143. func UpdateRecoveryCodes(username, recoveryCodes string) error {
  144. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  145. if err != nil {
  146. fmt.Fprintf(os.Stderr, "DB open err\n")
  147. return err
  148. }
  149. defer db.Close()
  150. _, err = db.Exec(`update users set recovery_codes = ? where username = ?`, recoveryCodes, username)
  151. if err != nil {
  152. return err
  153. }
  154. return nil
  155. }
  156. func CreateSession(username string, long bool) (Session, error) {
  157. sessionIdRaw := make([]byte, 64)
  158. rand.Read(sessionIdRaw)
  159. sessionId := hex.EncodeToString(sessionIdRaw)
  160. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  161. if err != nil {
  162. fmt.Fprintf(os.Stderr, "DB open err\n")
  163. return Session{}, err
  164. }
  165. defer db.Close()
  166. var length string
  167. if long {
  168. length = "30 days"
  169. } else {
  170. length = "1 day"
  171. }
  172. _, err = db.Exec(`insert into sessions values(?, ?, datetime('now', '`+length+`'), ?)`, sessionId, username, long)
  173. if err != nil {
  174. return Session{}, err
  175. }
  176. return Session{Id: sessionId, Username: username, IsLong: long}, nil
  177. }
  178. func GetSession(token string) (*Session, error) {
  179. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  180. if err != nil {
  181. fmt.Fprintf(os.Stderr, "DB open err\n")
  182. return nil, err
  183. }
  184. defer db.Close()
  185. rows, err := db.Query(`select username, expiry, is_long from sessions where id = ?`, token)
  186. if err != nil {
  187. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  188. return nil, err
  189. }
  190. defer rows.Close()
  191. if !rows.Next() {
  192. return nil, EmptyError{message: "Session does not exist"}
  193. }
  194. session := Session{Id: token}
  195. err = rows.Scan(&session.Username, &session.Expiry, &session.IsLong)
  196. if err != nil {
  197. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  198. return nil, err
  199. }
  200. return &session, nil
  201. }
  202. func ClearSessions(username string) error {
  203. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  204. if err != nil {
  205. fmt.Fprintf(os.Stderr, "DB open err\n")
  206. return err
  207. }
  208. defer db.Close()
  209. _, err = db.Exec(`delete from sessions where username = ? and expiry < datetime('now')`, username)
  210. if err != nil {
  211. fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
  212. return err
  213. }
  214. return nil
  215. }
  216. func RemoveSession(username, token string) error {
  217. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  218. if err != nil {
  219. fmt.Fprintf(os.Stderr, "DB open err\n")
  220. return err
  221. }
  222. defer db.Close()
  223. rows, err := db.Exec(`delete from sessions where id = ? and username = ?`, token, username)
  224. affected, _ := rows.RowsAffected()
  225. if affected == 0 {
  226. return EmptyError{
  227. message: "No session " + token + " for user " + username,
  228. }
  229. }
  230. return err
  231. }
  232. func GetItemExperiences(username, itemId string, itemType datastructure.ItemType) (map[string][]time.Time, error) {
  233. times := map[string][]time.Time{}
  234. user, err := GetUser(username)
  235. if err != nil {
  236. fmt.Fprintf(os.Stderr, "Get user err: %v\n", err)
  237. return times, err
  238. }
  239. location, err := time.LoadLocation(user.Timezone)
  240. if err != nil {
  241. fmt.Fprintf(os.Stderr, "Load location err: %v\n", err)
  242. return times, err
  243. }
  244. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  245. if err != nil {
  246. fmt.Fprintf(os.Stderr, "DB open err\n")
  247. return times, err
  248. }
  249. defer db.Close()
  250. rows, err := db.Query(`select time, item_id from experiences where username = ? and item_type = ? and (item_id = ? or item_id like ?)`, username, itemType, itemId, itemId+"/%")
  251. if err != nil {
  252. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  253. return times, err
  254. }
  255. defer rows.Close()
  256. for rows.Next() {
  257. var (
  258. t time.Time
  259. id string
  260. )
  261. err := rows.Scan(&t, &id)
  262. if err != nil {
  263. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  264. return times, err
  265. }
  266. t = t.In(location)
  267. times[id] = append(times[id], t)
  268. }
  269. for k, v := range times {
  270. sort.Slice(v, func(i, j int) bool {
  271. return v[i].After(v[j])
  272. })
  273. times[k] = v
  274. }
  275. return times, nil
  276. }
  277. func AddToExperiences(username, itemId string, itemType datastructure.ItemType, datetime time.Time) (int, error) {
  278. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  279. if err != nil {
  280. fmt.Fprintf(os.Stderr, "DB open err\n")
  281. return 0, err
  282. }
  283. defer db.Close()
  284. tx, err := db.Begin()
  285. if err != nil {
  286. fmt.Fprintf(os.Stderr, "Transaction err %s\n", err)
  287. return 0, err
  288. }
  289. defer tx.Rollback()
  290. rows, err := tx.Query(`select time from experiences where item_type = ? and item_id like ? and username = ?`, itemType, itemId, username)
  291. if err != nil {
  292. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  293. return 0, err
  294. }
  295. defer rows.Close()
  296. watchedTimes := []time.Time{}
  297. for rows.Next() {
  298. var t time.Time
  299. err := rows.Scan(&t)
  300. if err != nil {
  301. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  302. return 0, err
  303. }
  304. watchedTimes = append(watchedTimes, t)
  305. }
  306. if datetime.IsZero() && len(watchedTimes) > 0 {
  307. return 0, datastructure.ValueError{Message: "Cannot skip watched item"}
  308. }
  309. deletedRows, err := tx.Exec(`delete from experiences where username = ? and item_type = ? and item_id = ? and time = '0001-01-01 00:00:00+00:00'`, username, itemType, itemId)
  310. if err != nil {
  311. fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
  312. return 0, err
  313. }
  314. deletedRowsNumber, err := deletedRows.RowsAffected()
  315. if err != nil {
  316. fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
  317. return 0, err
  318. }
  319. insertedRows, err := tx.Exec(`insert into experiences values(?, ?, ?, ?)`, username, itemType, itemId, datetime)
  320. if err != nil {
  321. fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
  322. return 0, err
  323. }
  324. insertedRowsNumber, err := insertedRows.RowsAffected()
  325. if err != nil {
  326. fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
  327. return 0, err
  328. }
  329. tx.Commit()
  330. return int(insertedRowsNumber - deletedRowsNumber), nil
  331. }
  332. func WatchWholeSerie(username, itemId string, episodes []string, itemType datastructure.ItemType, datetime time.Time) (int, error) {
  333. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  334. if err != nil {
  335. fmt.Fprintf(os.Stderr, "DB open err\n")
  336. return 0, err
  337. }
  338. defer db.Close()
  339. tx, err := db.Begin()
  340. if err != nil {
  341. fmt.Fprintf(os.Stderr, "Transaction err %s\n", err)
  342. return 0, err
  343. }
  344. defer tx.Rollback()
  345. rows, err := tx.Query(`select item_id from experiences where item_type = ? and item_id like ? || '/%' and username = ?`, itemType, itemId, username)
  346. if err != nil {
  347. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  348. return 0, err
  349. }
  350. defer rows.Close()
  351. watched := map[string]int{}
  352. for rows.Next() {
  353. var watchedId string
  354. err := rows.Scan(&watchedId)
  355. if err != nil {
  356. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  357. return 0, err
  358. }
  359. watched[watchedId]++
  360. }
  361. modifiedRows := 0
  362. for _, episodeId := range episodes {
  363. if watched[episodeId] > 0 {
  364. continue
  365. }
  366. _, err = tx.Exec(`insert into experiences values(?, ?, ?, ?)`, username, itemType, episodeId, datetime)
  367. if err != nil {
  368. if err.Error()[:6] != "UNIQUE" {
  369. fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
  370. return 0, err
  371. } else {
  372. fmt.Fprintf(os.Stderr, "WARNING: Insert err: Unique constraint violation\n")
  373. }
  374. }
  375. modifiedRows++
  376. }
  377. err = tx.Commit()
  378. if err != nil {
  379. fmt.Fprintf(os.Stderr, "Commit err %v\n", err)
  380. return 0, err
  381. }
  382. return modifiedRows, nil
  383. }
  384. func ClearSpecials(username, itemId string, episodes []string, itemType datastructure.ItemType) error {
  385. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  386. if err != nil {
  387. fmt.Fprintf(os.Stderr, "DB open err\n")
  388. return err
  389. }
  390. defer db.Close()
  391. tx, err := db.Begin()
  392. if err != nil {
  393. fmt.Fprintf(os.Stderr, "Transaction err %s\n", err)
  394. return err
  395. }
  396. defer tx.Rollback()
  397. rows, err := tx.Query(`select item_id from experiences where item_type = ? and item_id like ? || '/S00E%' and username = ? and time = "0001-01-01 00:00:00+00:00"`, itemType, itemId, username)
  398. if err != nil {
  399. fmt.Fprintf(os.Stderr, "Select err %v\n", err)
  400. return err
  401. }
  402. defer rows.Close()
  403. watched := []string{}
  404. for rows.Next() {
  405. var watchedId string
  406. err := rows.Scan(&watchedId)
  407. if err != nil {
  408. fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
  409. return err
  410. }
  411. watched = append(watched, watchedId)
  412. }
  413. seriesEpisodes := map[string]int{}
  414. for _, episode := range episodes {
  415. seriesEpisodes[episode]++
  416. }
  417. for _, episode := range watched {
  418. if seriesEpisodes[episode] == 0 {
  419. _, err = tx.Exec(`delete from experiences where item_type = ? and item_id = ? and username = ?`, itemType, episode, username)
  420. if err != nil {
  421. fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
  422. return err
  423. }
  424. }
  425. }
  426. err = tx.Commit()
  427. if err != nil {
  428. fmt.Fprintf(os.Stderr, "Commit err %v\n", err)
  429. return err
  430. }
  431. return nil
  432. }
  433. func AddToWantList(username, itemId string, itemType datastructure.ItemType) error {
  434. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  435. if err != nil {
  436. fmt.Fprintf(os.Stderr, "DB open err\n")
  437. return err
  438. }
  439. defer db.Close()
  440. _, err = db.Exec(`insert into wantlist values(?, ?, ?)`, username, itemType, itemId)
  441. if err != nil {
  442. fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
  443. return err
  444. }
  445. return nil
  446. }
  447. func RemoveFromWantList(username, itemId string, itemType datastructure.ItemType) error {
  448. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  449. if err != nil {
  450. fmt.Fprintf(os.Stderr, "DB open err\n")
  451. return err
  452. }
  453. defer db.Close()
  454. result, err := db.Exec(`delete from wantlist where username = ? and item_type = ? and item_id = ?`, username, itemType, itemId)
  455. if err != nil {
  456. fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
  457. return err
  458. }
  459. rows, err := result.RowsAffected()
  460. if err != nil {
  461. fmt.Fprintf(os.Stderr, "Delete err %v\n", err)
  462. return err
  463. }
  464. if rows == 0 {
  465. return EmptyError{
  466. message: "Empty delete",
  467. }
  468. }
  469. return nil
  470. }
  471. func IsOnWantList(username, itemId string, itemType datastructure.ItemType) (bool, error) {
  472. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  473. if err != nil {
  474. fmt.Fprintf(os.Stderr, "DB open err\n")
  475. return false, err
  476. }
  477. defer db.Close()
  478. rows, err := db.Query(`select 1 from wantlist where username = ? and item_id = ? and item_type = ?`, username, itemId, string(itemType))
  479. if err != nil {
  480. fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
  481. return false, err
  482. }
  483. defer rows.Close()
  484. isOnlist := rows.Next()
  485. return isOnlist, nil
  486. }
  487. func SaveCacheItem(itemType datastructure.ItemType, itemId string, itemInfo datastructure.ItemInfo, refs int) error {
  488. if refs == 0 {
  489. return nil
  490. }
  491. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  492. if err != nil {
  493. fmt.Fprintf(os.Stderr, "DB open err\n")
  494. return err
  495. }
  496. defer db.Close()
  497. _, err = db.Exec(`insert into item_cache values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  498. on conflict(item_type, item_id) do update set ref_count = ref_count + ?`,
  499. itemType, itemId, itemInfo.Cover, itemInfo.Status, itemInfo.Title, itemInfo.YearStart, itemInfo.YearEnd, itemInfo.BasedOn, itemInfo.Genres, itemInfo.Runtime, itemInfo.Collection, itemInfo.Part, refs, itemInfo.Episodes, refs)
  500. if err != nil {
  501. return err
  502. }
  503. return nil
  504. }
  505. func UpdateCacheItem(itemType datastructure.ItemType, itemId string, itemInfo datastructure.ItemInfo) error {
  506. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  507. if err != nil {
  508. fmt.Fprintf(os.Stderr, "DB open err\n")
  509. return err
  510. }
  511. defer db.Close()
  512. db.Exec(`update item_cache set cover = ?, status = ?, title = ?, year_start = ?, year_end = ?, based_on = ?, genres = ?, runtime = ?, collection = ?, part = ?, episodes = ? 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, itemInfo.Episodes, itemType, itemId)
  513. return nil
  514. }
  515. func RemoveCacheItem(itemType datastructure.ItemType, itemId string) error {
  516. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  517. if err != nil {
  518. fmt.Fprintf(os.Stderr, "DB open err\n")
  519. return err
  520. }
  521. defer db.Close()
  522. _, err = db.Exec(`update item_cache set ref_count = ref_count - 1 where item_id = ?`, itemId)
  523. return err
  524. }
  525. func CleanItemCache() error {
  526. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  527. if err != nil {
  528. fmt.Fprintf(os.Stderr, "DB open err\n")
  529. return err
  530. }
  531. defer db.Close()
  532. _, err = db.Exec(`delete from item_cache where ref_count <= 0`)
  533. return err
  534. }
  535. func GetCacheItem(itemType datastructure.ItemType, itemId string) (*datastructure.ItemInfo, error) {
  536. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  537. if err != nil {
  538. fmt.Fprintf(os.Stderr, "DB open err\n")
  539. return nil, err
  540. }
  541. defer db.Close()
  542. var (
  543. itemInfo datastructure.ItemInfo
  544. itemTypeDb datastructure.ItemType
  545. itemIdDb string
  546. refCount int
  547. )
  548. row := db.QueryRow(`select * from cache where item_type = ? and item_id = ?`, itemType, itemId)
  549. 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)
  550. if err != nil {
  551. if err == sql.ErrNoRows {
  552. return nil, nil
  553. } else {
  554. return nil, err
  555. }
  556. }
  557. return &itemInfo, nil
  558. }
  559. // ====
  560. func GetCacheEntry(uri string) (*CacheEntry, error) {
  561. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  562. if err != nil {
  563. fmt.Fprintf(os.Stderr, "DB open err\n")
  564. return nil, err
  565. }
  566. defer db.Close()
  567. row := db.QueryRow(`select etag, response from cache where uri = ?`, uri)
  568. var cacheEntry CacheEntry
  569. err = row.Scan(&cacheEntry.Etag, &cacheEntry.Data)
  570. if err != nil {
  571. if err == sql.ErrNoRows {
  572. return nil, nil
  573. } else {
  574. return nil, err
  575. }
  576. }
  577. return &cacheEntry, err
  578. }
  579. func CleanCache() error {
  580. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  581. if err != nil {
  582. fmt.Fprintf(os.Stderr, "DB open err\n")
  583. return err
  584. }
  585. defer db.Close()
  586. row := db.QueryRow(`select count(*) from cache`)
  587. var count int
  588. err = row.Scan(&count)
  589. if err != nil {
  590. return err
  591. }
  592. for count > 10000 {
  593. _, err = db.Exec(`delete from cache where last_update = (select min(last_update) from cache)`)
  594. if err != nil {
  595. return err
  596. }
  597. count--
  598. }
  599. return nil
  600. }
  601. func SaveCacheEntry(uri, etag string, data []byte) error {
  602. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  603. if err != nil {
  604. fmt.Fprintf(os.Stderr, "DB open err\n")
  605. return err
  606. }
  607. defer db.Close()
  608. _, err = db.Exec(`insert into cache values(?, ?, null, ?, datetime('now'))
  609. on conflict(uri) do update set etag = excluded.etag, response = excluded.response, last_hit = excluded.last_hit`, uri, etag, data)
  610. return err
  611. }
  612. func GetWatchlist(username, filter string, page int) (datastructure.Watchlist, error) {
  613. watchlist := datastructure.Watchlist{}
  614. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  615. if err != nil {
  616. fmt.Fprintf(os.Stderr, "DB open err\n")
  617. return watchlist, err
  618. }
  619. defer db.Close()
  620. if page <= 0 {
  621. page = 1
  622. }
  623. offset := (page - 1) * 18
  624. //todo filter, order by
  625. var whereClause string
  626. if filter != "" {
  627. whereClause = "and c1.title like '%" + filter + "%'"
  628. }
  629. var pages float64
  630. row := db.QueryRow(`select distinct count(*) from wantlist w natural join item_cache c1 where c1.item_type = 'film' and w.username = ? `+whereClause, username)
  631. err = row.Scan(&pages)
  632. if err != nil {
  633. return watchlist, err
  634. }
  635. watchlist.Pages = int(math.Ceil(pages / 18))
  636. rows, err := db.Query(`select distinct 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 w natural join item_cache c1) left join (experiences e natural join item_cache c2) on(c1.part-1 = c2.part and c1.collection = c2.collection and e.username = w.username) where c1.item_type = 'film' and w.username = ? `+whereClause+` order by c1.title limit ?,18`, username, offset)
  637. if err != nil {
  638. fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
  639. return watchlist, err
  640. }
  641. defer rows.Close()
  642. for rows.Next() {
  643. var (
  644. entry datastructure.WatchlistEntry
  645. prevPart *int
  646. )
  647. err := rows.Scan(&entry.Id, &entry.Cover, &entry.Status, &entry.Title, &entry.YearStart, &entry.BasedOn, &entry.Genres, &entry.Runtime, &entry.Part, &prevPart)
  648. if err != nil {
  649. fmt.Println("Scan error")
  650. return datastructure.Watchlist{}, err
  651. }
  652. if entry.Part > 0 && prevPart == nil {
  653. entry.HasPrevious = true
  654. }
  655. watchlist.List = append(watchlist.List, entry)
  656. }
  657. return watchlist, nil
  658. }
  659. func GetReadlist(username, filter string, page int) (datastructure.Readlist, error) {
  660. readlist := datastructure.Readlist{}
  661. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  662. if err != nil {
  663. fmt.Fprintf(os.Stderr, "DB open err\n")
  664. return readlist, err
  665. }
  666. defer db.Close()
  667. if page <= 0 {
  668. page = 1
  669. }
  670. var pages float64
  671. row := db.QueryRow(`select count(*) from wantlist where item_type = 'book' and username = ?`, username)
  672. err = row.Scan(&pages)
  673. if err != nil {
  674. return readlist, err
  675. }
  676. readlist.Pages = int(math.Ceil(pages / 18))
  677. offset := (page - 1) * 18
  678. //todo filter, order by
  679. var whereClause string
  680. if filter != "" {
  681. whereClause = "and c1.title like '%" + filter + "%'"
  682. }
  683. rows, err := db.Query(`select distinct 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 w natural join item_cache c1) left join (experiences e natural join item_cache c2) on(c1.part-1 = c2.part and c1.collection = c2.collection and e.username = w.username) where c1.item_type = 'book' and w.username = ? `+whereClause+` order by c1.title limit ?,18`, username, offset)
  684. if err != nil {
  685. fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
  686. return readlist, err
  687. }
  688. defer rows.Close()
  689. for rows.Next() {
  690. var (
  691. entry datastructure.ReadlistEntry
  692. prevPart *int
  693. )
  694. err := rows.Scan(&entry.Id, &entry.Cover, &entry.Status, &entry.Title, &entry.YearStart, &entry.BasedOn, &entry.Genres, &entry.Runtime, &entry.Part, &prevPart)
  695. if err != nil {
  696. fmt.Println("Scan error")
  697. return datastructure.Readlist{}, err
  698. }
  699. if entry.Part > 0 && prevPart == nil {
  700. entry.HasPrevious = true
  701. }
  702. readlist.List = append(readlist.List, entry)
  703. }
  704. return readlist, nil
  705. }
  706. func GetTvQueue(username, filter string, page int) (datastructure.TvQueue, error) {
  707. tvQueue := datastructure.TvQueue{}
  708. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  709. if err != nil {
  710. fmt.Fprintf(os.Stderr, "DB open err\n")
  711. return tvQueue, err
  712. }
  713. defer db.Close()
  714. if page <= 0 {
  715. page = 1
  716. }
  717. var pages float64
  718. row := db.QueryRow(`select count(*) from wantlist where item_type = 'tvserie' and username = ?`, username)
  719. err = row.Scan(&pages)
  720. if err != nil {
  721. return tvQueue, err
  722. }
  723. tvQueue.Pages = int(math.Ceil(pages / 18))
  724. offset := (page - 1) * 18
  725. //todo filter, order by
  726. var whereClause string
  727. if filter != "" {
  728. whereClause = "and c1.title like '%" + filter + "%'"
  729. }
  730. rows, err := db.Query(`select item_id, cover, status, based_on, genres, title, year_start, year_end, substr(e.id, 1, pos-1) as series_id, episodes from wantlist w left join (select item_id as id, instr(item_id, '/') as pos from experiences where item_type = 'tvserie' group by substr(id, 1, pos-1)) e on item_id = series_id natural join item_cache c where item_type = 'tvserie' and username = ? `+whereClause+` order by title limit ?,18`, username, offset)
  731. if err != nil {
  732. fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
  733. return tvQueue, err
  734. }
  735. defer rows.Close()
  736. for rows.Next() {
  737. var (
  738. entry datastructure.TvQueueEntry
  739. episodes_watched *int
  740. episodes_skipped *int
  741. series_id *int
  742. )
  743. err := rows.Scan(&entry.Id, &entry.Cover, &entry.Status, &entry.BasedOn, &entry.Genres, &entry.Title, &entry.YearStart, &entry.YearEnd, &series_id, &entry.Episodes)
  744. if err != nil {
  745. fmt.Println("Scan error")
  746. return datastructure.TvQueue{}, err
  747. }
  748. if series_id != nil {
  749. row := db.QueryRow(`select count(time) from experiences where item_type = 'tvserie' and username = ? and item_id like ? || '/%' and time != '0001-01-01 00:00:00+00:00'`, username, *series_id)
  750. err = row.Scan(&episodes_watched)
  751. if err != nil {
  752. fmt.Println("Scan error")
  753. return datastructure.TvQueue{}, err
  754. }
  755. row = db.QueryRow(`select count(time) from experiences where item_type = 'tvserie' and username = ? and item_id like ? || '/%' and time == '0001-01-01 00:00:00+00:00'`, username, *series_id)
  756. err = row.Scan(&episodes_skipped)
  757. if err != nil {
  758. fmt.Println("Scan error")
  759. return datastructure.TvQueue{}, err
  760. }
  761. if episodes_watched == nil {
  762. entry.WatchedEpisodes = 0
  763. } else {
  764. entry.WatchedEpisodes = *episodes_watched
  765. }
  766. if episodes_skipped == nil {
  767. entry.SkippedEpisodes = 0
  768. } else {
  769. entry.SkippedEpisodes = *episodes_skipped
  770. }
  771. }
  772. tvQueue.List = append(tvQueue.List, entry)
  773. }
  774. return tvQueue, nil
  775. }
  776. func GetWantlistUris() ([]string, error) {
  777. uris := []string{}
  778. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  779. if err != nil {
  780. fmt.Fprintf(os.Stderr, "DB open err\n")
  781. return uris, err
  782. }
  783. defer db.Close()
  784. rows, err := db.Query(`select item_type, item_id from wantlist where item_type in ('film', 'tvserie')`)
  785. if err != nil {
  786. fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
  787. return uris, err
  788. }
  789. defer rows.Close()
  790. for rows.Next() {
  791. var (
  792. itemType string
  793. itemId string
  794. )
  795. err := rows.Scan(&itemType, &itemId)
  796. if err != nil {
  797. fmt.Println("Scan error")
  798. return uris, err
  799. }
  800. uris = append(uris, itemType+"/"+itemId)
  801. }
  802. return uris, nil
  803. }
  804. func GetUserExperiences(username, filter string, page int) (datastructure.Experiences, error) {
  805. experiences := datastructure.Experiences{}
  806. db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
  807. if err != nil {
  808. fmt.Fprintf(os.Stderr, "DB open err\n")
  809. return experiences, err
  810. }
  811. defer db.Close()
  812. if page <= 0 {
  813. page = 1
  814. }
  815. var pages float64
  816. row := db.QueryRow(`select count(*) from experiences where username = ? and time != '0001-01-01 00:00:00+00:00'`, username)
  817. err = row.Scan(&pages)
  818. if err != nil {
  819. return experiences, err
  820. }
  821. experiences.Pages = int(math.Ceil(pages / 18))
  822. offset := (page - 1) * 18
  823. //todo filter, order by
  824. var whereClause string
  825. if filter != "" {
  826. whereClause = "and c1.title like '%" + filter + "%'"
  827. }
  828. 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)
  829. if err != nil {
  830. fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
  831. return experiences, err
  832. }
  833. defer rows.Close()
  834. for rows.Next() {
  835. var (
  836. entry datastructure.ExperiencesEntry
  837. )
  838. err := rows.Scan(&entry.Id, &entry.Code, &entry.Type, &entry.Datetime, &entry.Title, &entry.YearStart, &entry.Collection, &entry.Part)
  839. entry.Part += 1
  840. if err != nil {
  841. fmt.Println("Scan error")
  842. return datastructure.Experiences{}, err
  843. }
  844. if !entry.Datetime.IsZero() {
  845. experiences.List = append(experiences.List, entry)
  846. }
  847. }
  848. return experiences, nil
  849. }