db.go 31 KB

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