/core/data/data.go

https://github.com/gabek/owncast · Go · 135 lines · 100 code · 24 blank · 11 comment · 23 complexity · 0262df8c8170b71cc0dbc325d1f713b4 MD5 · raw file

  1. // This is a centralized place to connect to the database, and hold a reference to it.
  2. // Other packages can share this reference. This package would also be a place to add any kind of
  3. // persistence-related convenience methods or migrations.
  4. package data
  5. import (
  6. "database/sql"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "time"
  11. "github.com/owncast/owncast/config"
  12. "github.com/owncast/owncast/utils"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. const (
  16. schemaVersion = 1
  17. )
  18. var _db *sql.DB
  19. var _datastore *Datastore
  20. // GetDatabase will return the shared instance of the actual database.
  21. func GetDatabase() *sql.DB {
  22. return _db
  23. }
  24. // GetStore will return the shared instance of the read/write datastore.
  25. func GetStore() *Datastore {
  26. return _datastore
  27. }
  28. // SetupPersistence will open the datastore and make it available.
  29. func SetupPersistence(file string) error {
  30. // Create empty DB file if it doesn't exist.
  31. if !utils.DoesFileExists(file) {
  32. log.Traceln("Creating new database at", file)
  33. _, err := os.Create(file)
  34. if err != nil {
  35. log.Fatal(err.Error())
  36. }
  37. }
  38. db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?_cache_size=10000&cache=shared&_journal_mode=WAL", file))
  39. db.SetMaxOpenConns(1)
  40. _db = db
  41. // Some SQLite optimizations
  42. _, _ = db.Exec("pragma journal_mode = WAL")
  43. _, _ = db.Exec("pragma synchronous = normal")
  44. _, _ = db.Exec("pragma temp_store = memory")
  45. _, _ = db.Exec("pragma wal_checkpoint(full)")
  46. createWebhooksTable()
  47. createUsersTable(db)
  48. if err != nil {
  49. return err
  50. }
  51. if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS config (
  52. "key" string NOT NULL PRIMARY KEY,
  53. "value" TEXT
  54. );`); err != nil {
  55. return err
  56. }
  57. var version int
  58. err = db.QueryRow("SELECT value FROM config WHERE key='version'").
  59. Scan(&version)
  60. if err != nil {
  61. if err != sql.ErrNoRows {
  62. return err
  63. }
  64. // fresh database: initialize it with the current schema version
  65. _, err := db.Exec("INSERT INTO config(key, value) VALUES(?, ?)", "version", schemaVersion)
  66. if err != nil {
  67. return err
  68. }
  69. version = schemaVersion
  70. }
  71. // is database from a newer Owncast version?
  72. if version > schemaVersion {
  73. return fmt.Errorf("incompatible database version %d (versions up to %d are supported)",
  74. version, schemaVersion)
  75. }
  76. // is database schema outdated?
  77. if version < schemaVersion {
  78. if err := migrateDatabase(db, version, schemaVersion); err != nil {
  79. return err
  80. }
  81. }
  82. _datastore = &Datastore{}
  83. _datastore.Setup()
  84. dbBackupTicker := time.NewTicker(1 * time.Hour)
  85. go func() {
  86. backupFile := filepath.Join(config.BackupDirectory, "owncastdb.bak")
  87. for range dbBackupTicker.C {
  88. utils.Backup(_db, backupFile)
  89. }
  90. }()
  91. return nil
  92. }
  93. func migrateDatabase(db *sql.DB, from, to int) error {
  94. log.Printf("Migrating database from version %d to %d", from, to)
  95. dbBackupFile := filepath.Join(config.BackupDirectory, fmt.Sprintf("owncast-v%d.bak", from))
  96. utils.Backup(db, dbBackupFile)
  97. for v := from; v < to; v++ {
  98. switch v {
  99. case 0:
  100. log.Tracef("Migration step from %d to %d\n", v, v+1)
  101. migrateToSchema1(db)
  102. default:
  103. log.Fatalln("missing database migration step")
  104. }
  105. }
  106. _, err := db.Exec("UPDATE config SET value = ? WHERE key = ?", to, "version")
  107. if err != nil {
  108. return err
  109. }
  110. return nil
  111. }