db: migrate invites, user expiry

some fixes to stuff in there too, probably
This commit is contained in:
Harvey Tindall 2023-06-24 19:13:05 +01:00
parent a470d77938
commit 63948a6de0
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
9 changed files with 168 additions and 204 deletions

View File

@ -15,16 +15,15 @@ import (
func (app *appContext) checkInvites() { func (app *appContext) checkInvites() {
currentTime := time.Now() currentTime := time.Now()
app.storage.loadInvites() app.storage.loadInvites()
changed := false for _, data := range app.storage.GetInvites() {
for code, data := range app.storage.GetInvites() {
expiry := data.ValidTill expiry := data.ValidTill
if !currentTime.After(expiry) { if !currentTime.After(expiry) {
continue continue
} }
app.debug.Printf("Housekeeping: Deleting old invite %s", code) app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code)
notify := data.Notify notify := data.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code) app.debug.Printf("%s: Expiry notification", data.Code)
var wait sync.WaitGroup var wait sync.WaitGroup
for address, settings := range notify { for address, settings := range notify {
if !settings["notify-expiry"] { if !settings["notify-expiry"] {
@ -33,9 +32,9 @@ func (app *appContext) checkInvites() {
wait.Add(1) wait.Add(1)
go func(addr string) { go func(addr string) {
defer wait.Done() defer wait.Done()
msg, err := app.email.constructExpiry(code, data, app, false) msg, err := app.email.constructExpiry(data.Code, data, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err) app.err.Printf("%s: Failed to construct expiry notification: %v", data.Code, err)
} else { } else {
// Check whether notify "address" is an email address of Jellyfin ID // Check whether notify "address" is an email address of Jellyfin ID
if strings.Contains(addr, "@") { if strings.Contains(addr, "@") {
@ -44,7 +43,7 @@ func (app *appContext) checkInvites() {
err = app.sendByID(msg, addr) err = app.sendByID(msg, addr)
} }
if err != nil { if err != nil {
app.err.Printf("%s: Failed to send expiry notification: %v", code, err) app.err.Printf("%s: Failed to send expiry notification: %v", data.Code, err)
} else { } else {
app.info.Printf("Sent expiry notification to %s", addr) app.info.Printf("Sent expiry notification to %s", addr)
} }
@ -53,18 +52,13 @@ func (app *appContext) checkInvites() {
} }
wait.Wait() wait.Wait()
} }
changed = true app.storage.DeleteInvitesKey(data.Code)
app.storage.DeleteInvitesKey(code)
}
if changed {
app.storage.storeInvites()
} }
} }
func (app *appContext) checkInvite(code string, used bool, username string) bool { func (app *appContext) checkInvite(code string, used bool, username string) bool {
currentTime := time.Now() currentTime := time.Now()
app.storage.loadInvites() app.storage.loadInvites()
changed := false
inv, match := app.storage.GetInvitesKey(code) inv, match := app.storage.GetInvitesKey(code)
if !match { if !match {
return false return false
@ -103,11 +97,9 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
} }
wait.Wait() wait.Wait()
} }
changed = true
match = false match = false
app.storage.DeleteInvitesKey(code) app.storage.DeleteInvitesKey(code)
} else if used { } else if used {
changed = true
del := false del := false
newInv := inv newInv := inv
if newInv.RemainingUses == 1 { if newInv.RemainingUses == 1 {
@ -122,9 +114,6 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
app.storage.SetInvitesKey(code, newInv) app.storage.SetInvitesKey(code, newInv)
} }
} }
if changed {
app.storage.storeInvites()
}
return match return match
} }
@ -220,7 +209,6 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
} }
} }
app.storage.SetInvitesKey(inviteCode, invite) app.storage.SetInvitesKey(inviteCode, invite)
app.storage.storeInvites()
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -236,10 +224,10 @@ func (app *appContext) GetInvites(gc *gin.Context) {
app.storage.loadInvites() app.storage.loadInvites()
app.checkInvites() app.checkInvites()
var invites []inviteDTO var invites []inviteDTO
for code, inv := range app.storage.GetInvites() { for _, inv := range app.storage.GetInvites() {
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime) _, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
invite := inviteDTO{ invite := inviteDTO{
Code: code, Code: inv.Code,
Months: months, Months: months,
Days: days, Days: days,
Hours: hours, Hours: hours,
@ -277,21 +265,19 @@ func (app *appContext) GetInvites(gc *gin.Context) {
invite.SendTo = inv.SendTo invite.SendTo = inv.SendTo
} }
if len(inv.Notify) != 0 { if len(inv.Notify) != 0 {
var address string // app.err.Printf("%s has notify section: %+v, you are %s\n", inv.Code, inv.Notify, gc.GetString("jfId"))
var addressOrID string
if app.config.Section("ui").Key("jellyfin_login").MustBool(false) { if app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
app.storage.loadEmails() addressOrID = gc.GetString("jfId")
if addr, ok := app.storage.GetEmailsKey(gc.GetString("jfId")); ok && addr.Addr != "" {
address = addr.Addr
}
} else { } else {
address = app.config.Section("ui").Key("email").String() addressOrID = app.config.Section("ui").Key("email").String()
} }
if _, ok := inv.Notify[address]; ok { if _, ok := inv.Notify[addressOrID]; ok {
if _, ok = inv.Notify[address]["notify-expiry"]; ok { if _, ok = inv.Notify[addressOrID]["notify-expiry"]; ok {
invite.NotifyExpiry = inv.Notify[address]["notify-expiry"] invite.NotifyExpiry = inv.Notify[addressOrID]["notify-expiry"]
} }
if _, ok = inv.Notify[address]["notify-creation"]; ok { if _, ok = inv.Notify[addressOrID]["notify-creation"]; ok {
invite.NotifyCreation = inv.Notify[address]["notify-creation"] invite.NotifyCreation = inv.Notify[addressOrID]["notify-creation"]
} }
} }
} }
@ -338,7 +324,6 @@ func (app *appContext) SetProfile(gc *gin.Context) {
inv, _ := app.storage.GetInvitesKey(req.Invite) inv, _ := app.storage.GetInvitesKey(req.Invite)
inv.Profile = req.Profile inv.Profile = req.Profile
app.storage.SetInvitesKey(req.Invite, inv) app.storage.SetInvitesKey(req.Invite, inv)
app.storage.storeInvites()
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -401,9 +386,6 @@ func (app *appContext) SetNotify(gc *gin.Context) {
app.storage.SetInvitesKey(code, invite) app.storage.SetInvitesKey(code, invite)
} }
} }
if changed {
app.storage.storeInvites()
}
} }
// @Summary Delete an invite. // @Summary Delete an invite.
@ -422,7 +404,6 @@ func (app *appContext) DeleteInvite(gc *gin.Context) {
_, ok = app.storage.GetInvitesKey(req.Code) _, ok = app.storage.GetInvitesKey(req.Code)
if ok { if ok {
app.storage.DeleteInvitesKey(req.Code) app.storage.DeleteInvitesKey(req.Code)
app.storage.storeInvites()
app.info.Printf("%s: Invite deleted", req.Code) app.info.Printf("%s: Invite deleted", req.Code)
respondBool(200, true, gc) respondBool(200, true, gc)
return return

View File

@ -387,11 +387,6 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
change := dcUser.Contact != req.Discord change := dcUser.Contact != req.Discord
dcUser.Contact = req.Discord dcUser.Contact = req.Discord
app.storage.SetDiscordKey(req.ID, dcUser) app.storage.SetDiscordKey(req.ID, dcUser)
if err := app.storage.storeDiscordUsers(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Discord: Failed to store users: %v", err)
return
}
if change { if change {
msg := "" msg := ""
if !req.Discord { if !req.Discord {
@ -404,11 +399,6 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
change := mxUser.Contact != req.Matrix change := mxUser.Contact != req.Matrix
mxUser.Contact = req.Matrix mxUser.Contact = req.Matrix
app.storage.SetMatrixKey(req.ID, mxUser) app.storage.SetMatrixKey(req.ID, mxUser)
if err := app.storage.storeMatrixUsers(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Matrix: Failed to store users: %v", err)
return
}
if change { if change {
msg := "" msg := ""
if !req.Matrix { if !req.Matrix {
@ -421,11 +411,6 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
change := email.Contact != req.Email change := email.Contact != req.Email
email.Contact = req.Email email.Contact = req.Email
app.storage.SetEmailsKey(req.ID, email) app.storage.SetEmailsKey(req.ID, email)
if err := app.storage.storeEmails(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Failed to store emails: %v", err)
return
}
if change { if change {
msg := "" msg := ""
if !req.Email { if !req.Email {
@ -646,7 +631,7 @@ func (app *appContext) MatrixConnect(gc *gin.Context) {
var req MatrixConnectUserDTO var req MatrixConnectUserDTO
gc.BindJSON(&req) gc.BindJSON(&req)
if app.storage.GetMatrix() == nil { if app.storage.GetMatrix() == nil {
app.storage.matrix = matrixStore{} app.storage.deprecatedMatrix = matrixStore{}
} }
roomID, encrypted, err := app.matrix.CreateRoom(req.UserID) roomID, encrypted, err := app.matrix.CreateRoom(req.UserID)
if err != nil { if err != nil {
@ -662,11 +647,6 @@ func (app *appContext) MatrixConnect(gc *gin.Context) {
Encrypted: encrypted, Encrypted: encrypted,
}) })
app.matrix.isEncrypted[roomID] = encrypted app.matrix.isEncrypted[roomID] = encrypted
if err := app.storage.storeMatrixUsers(); err != nil {
app.err.Printf("Failed to store Matrix users: %v", err)
respondBool(500, false, gc)
return
}
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -717,11 +697,6 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
return return
} }
app.storage.SetDiscordKey(req.JellyfinID, user) app.storage.SetDiscordKey(req.JellyfinID, user)
if err := app.storage.storeDiscordUsers(); err != nil {
app.err.Printf("Failed to store Discord users: %v", err)
respondBool(500, false, gc)
return
}
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
respondBool(200, true, gc) respondBool(200, true, gc)
} }

View File

@ -38,8 +38,8 @@ func (app *appContext) MyDetails(gc *gin.Context) {
} }
resp.Disabled = user.Policy.IsDisabled resp.Disabled = user.Policy.IsDisabled
if exp, ok := app.storage.users[user.ID]; ok { if exp, ok := app.storage.GetUserExpiryKey(user.ID); ok {
resp.Expiry = exp.Unix() resp.Expiry = exp.Expiry.Unix()
} }
app.storage.loadEmails() app.storage.loadEmails()
@ -199,7 +199,6 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
} }
} }
app.storage.storeEmails()
app.info.Println("Email list modified") app.info.Println("Email list modified")
gc.Redirect(http.StatusSeeOther, "/my/account") gc.Redirect(http.StatusSeeOther, "/my/account")
return return

View File

@ -62,7 +62,6 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
if emailEnabled { if emailEnabled {
app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true}) app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true})
app.storage.storeEmails()
} }
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.storage.loadOmbiTemplate() app.storage.loadOmbiTemplate()
@ -327,29 +326,19 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
// if app.config.Section("password_resets").Key("enabled").MustBool(false) { // if app.config.Section("password_resets").Key("enabled").MustBool(false) {
if req.Email != "" { if req.Email != "" {
app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true}) app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true})
app.storage.storeEmails()
} }
expiry := time.Time{} expiry := time.Time{}
if invite.UserExpiry { if invite.UserExpiry {
app.storage.usersLock.Lock()
defer app.storage.usersLock.Unlock()
expiry = time.Now().AddDate(0, invite.UserMonths, invite.UserDays).Add(time.Duration((60*invite.UserHours)+invite.UserMinutes) * time.Minute) expiry = time.Now().AddDate(0, invite.UserMonths, invite.UserDays).Add(time.Duration((60*invite.UserHours)+invite.UserMinutes) * time.Minute)
app.storage.users[id] = expiry app.storage.SetUserExpiryKey(id, UserExpiry{Expiry: expiry})
if err := app.storage.storeUsers(); err != nil {
app.err.Printf("Failed to store user duration: %v", err)
}
} }
if discordVerified { if discordVerified {
discordUser.Contact = req.DiscordContact discordUser.Contact = req.DiscordContact
if app.storage.discord == nil { if app.storage.deprecatedDiscord == nil {
app.storage.discord = discordStore{} app.storage.deprecatedDiscord = discordStore{}
} }
app.storage.SetDiscordKey(user.ID, discordUser) app.storage.SetDiscordKey(user.ID, discordUser)
if err := app.storage.storeDiscordUsers(); err != nil { delete(app.discord.verifiedTokens, req.DiscordPIN)
app.err.Printf("Failed to store Discord users: %v", err)
} else {
delete(app.discord.verifiedTokens, req.DiscordPIN)
}
} }
if telegramVerified { if telegramVerified {
tgUser := TelegramUser{ tgUser := TelegramUser{
@ -360,8 +349,8 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok { if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
tgUser.Lang = lang tgUser.Lang = lang
} }
if app.storage.telegram == nil { if app.storage.deprecatedTelegram == nil {
app.storage.telegram = telegramStore{} app.storage.deprecatedTelegram = telegramStore{}
} }
app.telegram.DeleteVerifiedToken(req.TelegramPIN) app.telegram.DeleteVerifiedToken(req.TelegramPIN)
app.storage.SetTelegramKey(user.ID, tgUser) app.storage.SetTelegramKey(user.ID, tgUser)
@ -404,13 +393,10 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
if matrixVerified { if matrixVerified {
matrixUser.Contact = req.MatrixContact matrixUser.Contact = req.MatrixContact
delete(app.matrix.tokens, req.MatrixPIN) delete(app.matrix.tokens, req.MatrixPIN)
if app.storage.matrix == nil { if app.storage.deprecatedMatrix == nil {
app.storage.matrix = matrixStore{} app.storage.deprecatedMatrix = matrixStore{}
} }
app.storage.SetMatrixKey(user.ID, matrixUser) app.storage.SetMatrixKey(user.ID, matrixUser)
if err := app.storage.storeMatrixUsers(); err != nil {
app.err.Printf("Failed to store Matrix users: %v", err)
}
} }
if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramVerified || discordVerified || matrixVerified { if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramVerified || discordVerified || matrixVerified {
name := app.getAddressOrName(user.ID) name := app.getAddressOrName(user.ID)
@ -629,21 +615,16 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
app.storage.usersLock.Lock()
defer app.storage.usersLock.Unlock()
for _, id := range req.Users { for _, id := range req.Users {
if expiry, ok := app.storage.users[id]; ok { base := time.Now()
app.storage.users[id] = expiry.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute) if expiry, ok := app.storage.GetUserExpiryKey(id); ok {
base = expiry.Expiry
app.debug.Printf("Expiry extended for \"%s\"", id) app.debug.Printf("Expiry extended for \"%s\"", id)
} else { } else {
app.storage.users[id] = time.Now().AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)
app.debug.Printf("Created expiry for \"%s\"", id) app.debug.Printf("Created expiry for \"%s\"", id)
} }
} expiry := UserExpiry{Expiry: base.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)}
if err := app.storage.storeUsers(); err != nil { app.storage.SetUserExpiryKey(id, expiry)
app.err.Printf("Failed to store user duration: %v", err)
respondBool(500, false, gc)
return
} }
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -853,8 +834,6 @@ func (app *appContext) GetUsers(gc *gin.Context) {
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true) adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
allowAll := app.config.Section("ui").Key("allow_all").MustBool(false) allowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
i := 0 i := 0
app.storage.usersLock.Lock()
defer app.storage.usersLock.Unlock()
for _, jfUser := range users { for _, jfUser := range users {
user := respUser{ user := respUser{
ID: jfUser.ID, ID: jfUser.ID,
@ -871,9 +850,9 @@ func (app *appContext) GetUsers(gc *gin.Context) {
user.Label = email.Label user.Label = email.Label
user.AccountsAdmin = (app.jellyfinLogin) && (email.Admin || (adminOnly && jfUser.Policy.IsAdministrator) || allowAll) user.AccountsAdmin = (app.jellyfinLogin) && (email.Admin || (adminOnly && jfUser.Policy.IsAdministrator) || allowAll)
} }
expiry, ok := app.storage.users[jfUser.ID] expiry, ok := app.storage.GetUserExpiryKey(jfUser.ID)
if ok { if ok {
user.Expiry = expiry.Unix() user.Expiry = expiry.Expiry.Unix()
} }
if tgUser, ok := app.storage.GetTelegramKey(jfUser.ID); ok { if tgUser, ok := app.storage.GetTelegramKey(jfUser.ID); ok {
user.Telegram = tgUser.Username user.Telegram = tgUser.Username
@ -924,10 +903,6 @@ func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, emailStore)
} }
} }
if err := app.storage.storeEmails(); err != nil {
app.err.Printf("Failed to store email list: %v", err)
respondBool(500, false, gc)
}
app.info.Println("Email list modified") app.info.Println("Email list modified")
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -961,10 +936,6 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, emailStore)
} }
} }
if err := app.storage.storeEmails(); err != nil {
app.err.Printf("Failed to store email list: %v", err)
respondBool(500, false, gc)
}
app.info.Println("Email list modified") app.info.Println("Email list modified")
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -999,7 +970,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
// Auto enable contact by email for newly added addresses // Auto enable contact by email for newly added addresses
if !ok || oldEmail.Addr == "" { if !ok || oldEmail.Addr == "" {
emailStore.Contact = true emailStore.Contact = true
app.storage.storeEmails()
} }
emailStore.Addr = address emailStore.Addr = address
@ -1016,7 +986,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
} }
} }
} }
app.storage.storeEmails()
app.info.Println("Email list modified") app.info.Println("Email list modified")
respondBool(200, true, gc) respondBool(200, true, gc)
} }

View File

@ -362,7 +362,7 @@ func start(asDaemon, firstCall bool) {
app.err.Printf("Failed to load Displayprefs: %v", err) app.err.Printf("Failed to load Displayprefs: %v", err)
} }
app.storage.users_path = app.config.Section("files").Key("users").String() app.storage.users_path = app.config.Section("files").Key("users").String()
if err := app.storage.loadUsers(); err != nil { if err := app.storage.loadUserExpiries(); err != nil {
app.err.Printf("Failed to load Users: %v", err) app.err.Printf("Failed to load Users: %v", err)
} }
app.storage.telegram_path = app.config.Section("files").Key("telegram_users").String() app.storage.telegram_path = app.config.Section("files").Key("telegram_users").String()
@ -400,11 +400,6 @@ func start(asDaemon, firstCall bool) {
app.storage.db_path = filepath.Join(app.dataPath, "db") app.storage.db_path = filepath.Join(app.dataPath, "db")
app.ConnectDB() app.ConnectDB()
defer app.storage.db.Close() defer app.storage.db.Close()
if !app.config.Section("").Key("migrated_to_db").MustBool(false) {
// FIXME: Mark as done at some point
migrateToBadger(app)
}
// Read config-base for settings on web. // Read config-base for settings on web.
app.configBasePath = "config-base.json" app.configBasePath = "config-base.json"
configBase, _ := fs.ReadFile(localFS, app.configBasePath) configBase, _ := fs.ReadFile(localFS, app.configBasePath)

View File

@ -16,26 +16,28 @@ func runMigrations(app *appContext) {
migrateNotificationMethods(app) migrateNotificationMethods(app)
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
// migrateHyphens(app) // migrateHyphens(app)
migrateToBadger(app)
} }
// Migrate pre-0.2.0 user templates to profiles // Migrate pre-0.2.0 user templates to profiles
func migrateProfiles(app *appContext) { func migrateProfiles(app *appContext) {
if !(app.storage.policy.BlockedTags == nil && app.storage.configuration.GroupedFolders == nil && len(app.storage.displayprefs) == 0) { if app.storage.policy.BlockedTags == nil && app.storage.configuration.GroupedFolders == nil && len(app.storage.displayprefs) == 0 {
app.info.Println("Migrating user template files to new profile format") return
app.storage.migrateToProfile() }
for _, path := range [3]string{app.storage.policy_path, app.storage.configuration_path, app.storage.displayprefs_path} { app.info.Println("Migrating user template files to new profile format")
if _, err := os.Stat(path); !os.IsNotExist(err) { app.storage.migrateToProfile()
dir, fname := filepath.Split(path) for _, path := range [3]string{app.storage.policy_path, app.storage.configuration_path, app.storage.displayprefs_path} {
newFname := strings.Replace(fname, ".json", ".old.json", 1) if _, err := os.Stat(path); !os.IsNotExist(err) {
err := os.Rename(path, filepath.Join(dir, newFname)) dir, fname := filepath.Split(path)
if err != nil { newFname := strings.Replace(fname, ".json", ".old.json", 1)
app.err.Fatalf("Failed to rename %s: %s", fname, err) err := os.Rename(path, filepath.Join(dir, newFname))
} if err != nil {
app.err.Fatalf("Failed to rename %s: %s", fname, err)
} }
} }
app.info.Println("In case of a problem, your original files have been renamed to <file>.old.json")
app.storage.storeProfiles()
} }
app.info.Println("In case of a problem, your original files have been renamed to <file>.old.json")
app.storage.storeProfiles()
} }
// Migrate pre-0.2.5 bootstrap theme choice to a17t version. // Migrate pre-0.2.5 bootstrap theme choice to a17t version.
@ -131,7 +133,7 @@ func migrateNotificationMethods(app *appContext) error {
return nil return nil
} }
changes := false changes := false
for code, invite := range app.storage.invites { for code, invite := range app.storage.deprecatedInvites {
if invite.Notify == nil { if invite.Notify == nil {
continue continue
} }
@ -149,7 +151,7 @@ func migrateNotificationMethods(app *appContext) error {
} }
} }
if changes { if changes {
app.storage.invites[code] = invite app.storage.deprecatedInvites[code] = invite
} }
} }
if changes { if changes {
@ -195,31 +197,45 @@ func linkExistingOmbiDiscordTelegram(app *appContext) error {
} }
func migrateToBadger(app *appContext) { func migrateToBadger(app *appContext) {
if app.config.Section("").Key("migrated_to_db").MustBool(false) {
return
// FIXME: Mark as done at some point
}
app.info.Println("Migrating to Badger(hold)") app.info.Println("Migrating to Badger(hold)")
app.storage.loadAnnouncements() app.storage.loadAnnouncements()
for k, v := range app.storage.announcements { for k, v := range app.storage.deprecatedAnnouncements {
app.storage.SetAnnouncementsKey(k, v) app.storage.SetAnnouncementsKey(k, v)
} }
app.storage.loadDiscordUsers() app.storage.loadDiscordUsers()
for jfID, v := range app.storage.discord { for jfID, v := range app.storage.deprecatedDiscord {
app.storage.SetDiscordKey(jfID, v) app.storage.SetDiscordKey(jfID, v)
} }
app.storage.loadTelegramUsers() app.storage.loadTelegramUsers()
for jfID, v := range app.storage.telegram { for jfID, v := range app.storage.deprecatedTelegram {
app.storage.SetTelegramKey(jfID, v) app.storage.SetTelegramKey(jfID, v)
} }
app.storage.loadMatrixUsers() app.storage.loadMatrixUsers()
for jfID, v := range app.storage.matrix { for jfID, v := range app.storage.deprecatedMatrix {
app.storage.SetMatrixKey(jfID, v) app.storage.SetMatrixKey(jfID, v)
} }
app.storage.loadEmails() app.storage.loadEmails()
for jfID, v := range app.storage.emails { for jfID, v := range app.storage.deprecatedEmails {
app.storage.SetEmailsKey(jfID, v) app.storage.SetEmailsKey(jfID, v)
} }
app.storage.loadInvites()
for k, v := range app.storage.deprecatedInvites {
app.storage.SetInvitesKey(k, v)
}
app.storage.loadUserExpiries()
for k, v := range app.storage.deprecatedUserExpiries {
app.storage.SetUserExpiryKey(k, UserExpiry{Expiry: v})
}
} }
// Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled. // Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled.
@ -283,7 +299,7 @@ func migrateToBadger(app *appContext) {
// app.storage.emails = newEmails // app.storage.emails = newEmails
// app.storage.users = newUsers // app.storage.users = newUsers
// err = app.storage.storeEmails() // err = app.storage.storeEmails()
// err2 = app.storage.storeUsers() // err2 = app.storage.storeUserExpiries()
// if err != nil { // if err != nil {
// app.err.Fatalf("couldn't store emails.json: %v", err) // app.err.Fatalf("couldn't store emails.json: %v", err)
// } // }

View File

@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
@ -21,6 +20,11 @@ type telegramStore map[string]TelegramUser
type matrixStore map[string]MatrixUser type matrixStore map[string]MatrixUser
type emailStore map[string]EmailAddress type emailStore map[string]EmailAddress
type UserExpiry struct {
JellyfinID string `badgerhold:"key"`
Expiry time.Time
}
type Storage struct { type Storage struct {
timePattern string timePattern string
@ -28,22 +32,21 @@ type Storage struct {
db *badgerhold.Store db *badgerhold.Store
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path, userPage_path string invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path, userPage_path string
users map[string]time.Time // Map of Jellyfin User IDs to their expiry times. deprecatedUserExpiries map[string]time.Time // Map of Jellyfin User IDs to their expiry times.
invites Invites deprecatedInvites Invites
profiles map[string]Profile profiles map[string]Profile
defaultProfile string defaultProfile string
displayprefs, ombi_template map[string]interface{} displayprefs, ombi_template map[string]interface{}
emails emailStore // Map of Jellyfin User IDs to Email addresses. deprecatedEmails emailStore // Map of Jellyfin User IDs to Email addresses.
telegram telegramStore // Map of Jellyfin User IDs to telegram users. deprecatedTelegram telegramStore // Map of Jellyfin User IDs to telegram users.
discord discordStore // Map of Jellyfin user IDs to discord users. deprecatedDiscord discordStore // Map of Jellyfin user IDs to discord users.
matrix matrixStore // Map of Jellyfin user IDs to Matrix users. deprecatedMatrix matrixStore // Map of Jellyfin user IDs to Matrix users.
customEmails customEmails customEmails customEmails
userPage userPageContent userPage userPageContent
policy mediabrowser.Policy policy mediabrowser.Policy
configuration mediabrowser.Configuration configuration mediabrowser.Configuration
lang Lang lang Lang
announcements map[string]announcementTemplate deprecatedAnnouncements map[string]announcementTemplate
invitesLock, usersLock, discordLock, telegramLock, matrixLock, emailsLock sync.Mutex
} }
func (app *appContext) ConnectDB() { func (app *appContext) ConnectDB() {
@ -203,36 +206,39 @@ func (st *Storage) DeleteMatrixKey(k string) {
} }
// GetInvites returns a copy of the store. // GetInvites returns a copy of the store.
func (st *Storage) GetInvites() Invites { func (st *Storage) GetInvites() []Invite {
if st.invites == nil { result := []Invite{}
st.invites = Invites{} err := st.db.Find(&result, &badgerhold.Query{})
if err != nil {
// fmt.Printf("Failed to find invites: %v\n", err)
} }
return st.invites return result
} }
// GetInvitesKey returns the value stored in the store's key. // GetInvitesKey returns the value stored in the store's key.
func (st *Storage) GetInvitesKey(k string) (Invite, bool) { func (st *Storage) GetInvitesKey(k string) (Invite, bool) {
v, ok := st.invites[k] result := Invite{}
return v, ok err := st.db.Get(k, &result)
ok := true
if err != nil {
// fmt.Printf("Failed to find invite: %v\n", err)
ok = false
}
return result, ok
} }
// SetInvitesKey stores value v in key k. // SetInvitesKey stores value v in key k.
func (st *Storage) SetInvitesKey(k string, v Invite) { func (st *Storage) SetInvitesKey(k string, v Invite) {
st.invitesLock.Lock() v.Code = k
if st.invites == nil { err := st.db.Upsert(k, v)
st.invites = Invites{} if err != nil {
// fmt.Printf("Failed to set invite: %v\n", err)
} }
st.invites[k] = v
st.storeInvites()
st.invitesLock.Unlock()
} }
// DeleteInvitesKey deletes value at key k. // DeleteInvitesKey deletes value at key k.
func (st *Storage) DeleteInvitesKey(k string) { func (st *Storage) DeleteInvitesKey(k string) {
st.invitesLock.Lock() st.db.Delete(k, Invite{})
delete(st.invites, k)
st.storeInvites()
st.invitesLock.Unlock()
} }
// GetAnnouncements returns a copy of the store. // GetAnnouncements returns a copy of the store.
@ -270,6 +276,42 @@ func (st *Storage) DeleteAnnouncementsKey(k string) {
st.db.Delete(k, announcementTemplate{}) st.db.Delete(k, announcementTemplate{})
} }
// GetUserExpiries returns a copy of the store.
func (st *Storage) GetUserExpiries() []UserExpiry {
result := []UserExpiry{}
err := st.db.Find(&result, &badgerhold.Query{})
if err != nil {
// fmt.Printf("Failed to find expiries: %v\n", err)
}
return result
}
// GetUserExpiryKey returns the value stored in the store's key.
func (st *Storage) GetUserExpiryKey(k string) (UserExpiry, bool) {
result := UserExpiry{}
err := st.db.Get(k, &result)
ok := true
if err != nil {
// fmt.Printf("Failed to find expiry: %v\n", err)
ok = false
}
return result, ok
}
// SetUserExpiryKey stores value v in key k.
func (st *Storage) SetUserExpiryKey(k string, v UserExpiry) {
v.JellyfinID = k
err := st.db.Upsert(k, v)
if err != nil {
// fmt.Printf("Failed to set expiry: %v\n", err)
}
}
// DeleteUserExpiryKey deletes value at key k.
func (st *Storage) DeleteUserExpiryKey(k string) {
st.db.Delete(k, UserExpiry{})
}
type TelegramUser struct { type TelegramUser struct {
JellyfinID string `badgerhold:"key"` JellyfinID string `badgerhold:"key"`
ChatID int64 `badgerhold:"index"` ChatID int64 `badgerhold:"index"`
@ -335,6 +377,7 @@ type Profile struct {
} }
type Invite struct { type Invite struct {
Code string `badgerhold:"key"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
NoLimit bool `json:"no-limit"` NoLimit bool `json:"no-limit"`
RemainingUses int `json:"remaining-uses"` RemainingUses int `json:"remaining-uses"`
@ -1036,18 +1079,16 @@ func (st *Storage) loadLangTelegram(filesystems ...fs.FS) error {
type Invites map[string]Invite type Invites map[string]Invite
func (st *Storage) loadInvites() error { func (st *Storage) loadInvites() error {
return loadJSON(st.invite_path, &st.invites) return loadJSON(st.invite_path, &st.deprecatedInvites)
} }
func (st *Storage) storeInvites() error { func (st *Storage) storeInvites() error {
return storeJSON(st.invite_path, st.invites) return storeJSON(st.invite_path, st.deprecatedInvites)
} }
func (st *Storage) loadUsers() error { func (st *Storage) loadUserExpiries() error {
st.usersLock.Lock() if st.deprecatedUserExpiries == nil {
defer st.usersLock.Unlock() st.deprecatedUserExpiries = map[string]time.Time{}
if st.users == nil {
st.users = map[string]time.Time{}
} }
temp := map[string]time.Time{} temp := map[string]time.Time{}
err := loadJSON(st.users_path, &temp) err := loadJSON(st.users_path, &temp)
@ -1055,47 +1096,47 @@ func (st *Storage) loadUsers() error {
return err return err
} }
for id, t1 := range temp { for id, t1 := range temp {
if _, ok := st.users[id]; !ok { if _, ok := st.deprecatedUserExpiries[id]; !ok {
st.users[id] = t1 st.deprecatedUserExpiries[id] = t1
} }
} }
return nil return nil
} }
func (st *Storage) storeUsers() error { func (st *Storage) storeUserExpiries() error {
return storeJSON(st.users_path, st.users) return storeJSON(st.users_path, st.deprecatedUserExpiries)
} }
func (st *Storage) loadEmails() error { func (st *Storage) loadEmails() error {
return loadJSON(st.emails_path, &st.emails) return loadJSON(st.emails_path, &st.deprecatedEmails)
} }
func (st *Storage) storeEmails() error { func (st *Storage) storeEmails() error {
return storeJSON(st.emails_path, st.emails) return storeJSON(st.emails_path, st.deprecatedEmails)
} }
func (st *Storage) loadTelegramUsers() error { func (st *Storage) loadTelegramUsers() error {
return loadJSON(st.telegram_path, &st.telegram) return loadJSON(st.telegram_path, &st.deprecatedTelegram)
} }
func (st *Storage) storeTelegramUsers() error { func (st *Storage) storeTelegramUsers() error {
return storeJSON(st.telegram_path, st.telegram) return storeJSON(st.telegram_path, st.deprecatedTelegram)
} }
func (st *Storage) loadDiscordUsers() error { func (st *Storage) loadDiscordUsers() error {
return loadJSON(st.discord_path, &st.discord) return loadJSON(st.discord_path, &st.deprecatedDiscord)
} }
func (st *Storage) storeDiscordUsers() error { func (st *Storage) storeDiscordUsers() error {
return storeJSON(st.discord_path, st.discord) return storeJSON(st.discord_path, st.deprecatedDiscord)
} }
func (st *Storage) loadMatrixUsers() error { func (st *Storage) loadMatrixUsers() error {
return loadJSON(st.matrix_path, &st.matrix) return loadJSON(st.matrix_path, &st.deprecatedMatrix)
} }
func (st *Storage) storeMatrixUsers() error { func (st *Storage) storeMatrixUsers() error {
return storeJSON(st.matrix_path, st.matrix) return storeJSON(st.matrix_path, st.deprecatedMatrix)
} }
func (st *Storage) loadCustomEmails() error { func (st *Storage) loadCustomEmails() error {
@ -1147,11 +1188,11 @@ func (st *Storage) storeOmbiTemplate() error {
} }
func (st *Storage) loadAnnouncements() error { func (st *Storage) loadAnnouncements() error {
return loadJSON(st.announcements_path, &st.announcements) return loadJSON(st.announcements_path, &st.deprecatedAnnouncements)
} }
func (st *Storage) storeAnnouncements() error { func (st *Storage) storeAnnouncements() error {
return storeJSON(st.announcements_path, st.announcements) return storeJSON(st.announcements_path, st.deprecatedAnnouncements)
} }
func (st *Storage) loadProfiles() error { func (st *Storage) loadProfiles() error {

View File

@ -221,9 +221,6 @@ func (t *TelegramDaemon) commandLang(upd *tg.Update, sects []string, lang string
if user.ChatID == upd.Message.Chat.ID { if user.ChatID == upd.Message.Chat.ID {
user.Lang = sects[1] user.Lang = sects[1]
t.app.storage.SetTelegramKey(user.JellyfinID, user) t.app.storage.SetTelegramKey(user.JellyfinID, user)
if err := t.app.storage.storeTelegramUsers(); err != nil {
t.app.err.Printf("Failed to store Telegram users: %v", err)
}
break break
} }
} }

View File

@ -50,13 +50,7 @@ func (rt *userDaemon) shutdown() {
} }
func (app *appContext) checkUsers() { func (app *appContext) checkUsers() {
if err := app.storage.loadUsers(); err != nil { if len(app.storage.GetUserExpiries()) == 0 {
app.err.Printf("Failed to load user expiries: %v", err)
return
}
app.storage.usersLock.Lock()
defer app.storage.usersLock.Unlock()
if len(app.storage.users) == 0 {
return return
} }
app.info.Println("Daemon: Checking for user expiry") app.info.Println("Daemon: Checking for user expiry")
@ -80,11 +74,12 @@ func (app *appContext) checkUsers() {
for _, user := range users { for _, user := range users {
userExists[user.ID] = true userExists[user.ID] = true
} }
for id, expiry := range app.storage.users { for _, expiry := range app.storage.GetUserExpiries() {
id := expiry.JellyfinID
if _, ok := userExists[id]; !ok { if _, ok := userExists[id]; !ok {
app.info.Printf("Deleting expiry for non-existent user \"%s\"", id) app.info.Printf("Deleting expiry for non-existent user \"%s\"", id)
delete(app.storage.users, id) app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
} else if time.Now().After(expiry) { } else if time.Now().After(expiry.Expiry) {
found := false found := false
var user mediabrowser.User var user mediabrowser.User
for _, u := range users { for _, u := range users {
@ -96,7 +91,7 @@ func (app *appContext) checkUsers() {
} }
if !found { if !found {
app.info.Printf("Expired user already deleted, ignoring.") app.info.Printf("Expired user already deleted, ignoring.")
delete(app.storage.users, id) app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
continue continue
} }
app.info.Printf("%s expired user \"%s\"", termPlural, user.Name) app.info.Printf("%s expired user \"%s\"", termPlural, user.Name)
@ -112,7 +107,7 @@ func (app *appContext) checkUsers() {
app.err.Printf("Failed to %s \"%s\" (%d): %s", mode, user.Name, status, err) app.err.Printf("Failed to %s \"%s\" (%d): %s", mode, user.Name, status, err)
continue continue
} }
delete(app.storage.users, id) app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
if contact { if contact {
if !ok { if !ok {
@ -130,8 +125,4 @@ func (app *appContext) checkUsers() {
} }
} }
} }
err = app.storage.storeUsers()
if err != nil {
app.err.Printf("Failed to store user expiries: %s", err)
}
} }