1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2025-01-06 00:10:11 +00:00

Compare commits

...

14 Commits

Author SHA1 Message Date
e49996c401
Setup: Don't break if setup not completed, fix restart
Since an invalid example config was created on first run, if the app restarted
before setup was completed, it would crash on the next start. The
example now has a "first_run" flag in it, which is only set to false
when the config is modified. Also fixed restart at the end of setup for
tray builds.
2021-06-01 19:54:13 +01:00
Malte
aa40a72075 Translated using Weblate (German)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/de/
2021-06-01 18:29:10 +02:00
Richard de Boer
19b7341e80 Translated using Weblate (Dutch)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/nl/
2021-06-01 18:29:10 +02:00
Malte
73645a7569 Translated using Weblate (German)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/de/
2021-06-01 18:29:10 +02:00
Malte
a9dac8c04c translation from Weblate (German)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/de/
2021-06-01 18:29:09 +02:00
Malte
43fbbbcd16 translation from Weblate (German)
Currently translated at 100.0% (163 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/de/
2021-06-01 18:29:09 +02:00
Richard de Boer
fc57a8c97f translation from Weblate (Dutch)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/nl/
2021-06-01 18:29:07 +02:00
Richard de Boer
1fb2ef5675 translation from Weblate (Dutch)
Currently translated at 100.0% (163 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-06-01 18:29:06 +02:00
Malte
e0d994c35c Added translation using Weblate (German) 2021-06-01 17:21:53 +02:00
hrfee
cab30eb628 Added translation using Weblate (English (United Kingdom)) 2021-06-01 17:13:56 +02:00
71df011556
merge translations 2021-06-01 16:13:28 +01:00
Levi
b2828110e3 Translated using Weblate (Swedish)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/sv/
2021-06-01 17:07:49 +02:00
Levi
50eb05776a translation from Weblate (Swedish)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/sv/
2021-06-01 17:07:49 +02:00
19715f25f6
Move all migrations to separate file
Also fixed some inconsistent uses of snake case.
2021-06-01 14:18:49 +01:00
18 changed files with 329 additions and 209 deletions

8
api.go
View File

@ -1576,6 +1576,7 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
}
}
}
tempConfig.Section("").Key("first_run").SetValue("false")
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
respondBool(500, false, gc)
@ -1585,9 +1586,10 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
gc.JSON(200, map[string]bool{"success": true})
if req["restart-program"] != nil && req["restart-program"].(bool) {
app.info.Println("Restarting...")
err := app.Restart()
if err != nil {
app.err.Printf("Couldn't restart, try restarting manually: %s", err)
if TRAY {
TRAYRESTART <- true
} else {
RESTART <- true
}
}
app.loadConfig()

View File

@ -151,53 +151,3 @@ func (app *appContext) loadConfig() error {
return nil
}
func (app *appContext) migrateEmailConfig() {
tempConfig, _ := ini.Load(app.configPath)
fmt.Println(warning("Part of your email configuration will be migrated to the new \"messages\" section.\nA backup will be made."))
err := tempConfig.SaveTo(app.configPath + "_" + commit + ".bak")
if err != nil {
app.err.Fatalf("Failed to backup config: %v", err)
return
}
for _, setting := range []string{"use_24h", "date_format", "message"} {
if val := app.config.Section("email").Key(setting).Value(); val != "" {
tempConfig.Section("email").Key(setting).SetValue("")
tempConfig.Section("messages").Key(setting).SetValue(val)
}
}
if app.config.Section("messages").Key("enabled").MustBool(false) || app.config.Section("telegram").Key("enabled").MustBool(false) {
tempConfig.Section("messages").Key("enabled").SetValue("true")
}
err = tempConfig.SaveTo(app.configPath)
if err != nil {
app.err.Fatalf("Failed to save config: %v", err)
return
}
app.loadConfig()
}
func (app *appContext) migrateEmailStorage() error {
var emails map[string]interface{}
err := loadJSON(app.storage.emails_path, &emails)
if err != nil {
return err
}
newEmails := map[string]EmailAddress{}
for jfID, addr := range emails {
newEmails[jfID] = EmailAddress{
Addr: addr.(string),
Contact: true,
}
}
err = storeJSON(app.storage.emails_path+".bak", emails)
if err != nil {
return err
}
err = storeJSON(app.storage.emails_path, newEmails)
if err != nil {
return err
}
app.info.Println("Migrated to new email format. A backup has also been made.")
return nil
}

View File

@ -5,10 +5,10 @@
<script>
window.URLBase = "{{ .urlBase }}";
window.notificationsEnabled = {{ .notifications }};
window.emailEnabled = {{ .email_enabled }};
window.telegramEnabled = {{ .telegram_enabled }};
window.discordEnabled = {{ .discord_enabled }};
window.matrixEnabled = {{ .matrix_enabled }};
window.emailEnabled = {{ .emailEnabled }};
window.telegramEnabled = {{ .telegramEnabled }};
window.discordEnabled = {{ .discordEnabled }};
window.matrixEnabled = {{ .matrixEnabled }};
window.ombiEnabled = {{ .ombiEnabled }};
window.usernameEnabled = {{ .username }};
window.langFile = JSON.parse({{ .language }});

View File

@ -42,7 +42,7 @@ func (rt *inviteDaemon) run() {
}
}
func (rt *inviteDaemon) shutdown() {
func (rt *inviteDaemon) Shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel

View File

@ -94,7 +94,13 @@
"conditionals": "Bedingungen",
"contactThrough": "Kontakt über:",
"sendPIN": "Bitte den Benutzer, die unten stehende PIN an den Bot zu senden.",
"inviteMonths": "Monate"
"inviteMonths": "Monate",
"add": "Hinzufügen",
"select": "Auswählen",
"searchDiscordUser": "Gib den Discord-Benutzername ein, um den Benutzer zu finden.",
"findDiscordUser": "Suche Discord-Benutzer",
"linkMatrixDescription": "Gib den Benutzernamen und das Passwort des Benutzers ein, der als Bot verwendet werden soll. Nach dem Absenden wird die App neu gestartet.",
"matrixHomeServer": "Adresse des Homeservers"
},
"notifications": {
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
@ -133,7 +139,8 @@
"updateAvailable": "Eine neue Aktualisierung ist verfügbar, überprüfe die Einstellungen.",
"noUpdatesAvailable": "Keinen neuen Aktualisierungen verfügbar.",
"updateAppliedRefresh": "Update angewendet, bitte aktualisieren.",
"telegramVerified": "Telegram-Konto verifiziert."
"telegramVerified": "Telegram-Konto verifiziert.",
"accountConnected": "Konto verbunden."
},
"quantityStrings": {
"modifySettingsFor": {

View File

@ -94,7 +94,13 @@
"conditionals": "Voorwaarden",
"donate": "Doneer",
"contactThrough": "Stuur bericht via:",
"sendPIN": "Vraag de gebruiker om onderstaande pincode naar de bot te sturen."
"sendPIN": "Vraag de gebruiker om onderstaande pincode naar de bot te sturen.",
"add": "Voeg toe",
"searchDiscordUser": "Begin de Discord gebruikersnaam te typen om de gebruiker te vinden.",
"linkMatrixDescription": "Vul de gebruikersnaam en wachtwoord in van de gebruiker om als bot te gebruiken. De app start zodra ze zijn verstuurd.",
"select": "Selecteer",
"findDiscordUser": "Zoek Discord gebruiker",
"matrixHomeServer": "Adres home server"
},
"notifications": {
"changedEmailAddress": "E-mailadres van {n} gewijzigd.",
@ -133,7 +139,8 @@
"updateAvailable": "Er is een nieuwe update beschikbaar, kijk bij instellingen.",
"noUpdatesAvailable": "Geen nieuwe updates beschikbaar.",
"telegramVerified": "Telegram-account goedgekeurd.",
"updateAppliedRefresh": "Update toegepast, ververs alsjeblieft."
"updateAppliedRefresh": "Update toegepast, ververs alsjeblieft.",
"accountConnected": "Account gekoppeld."
},
"quantityStrings": {
"modifySettingsFor": {

View File

@ -17,6 +17,10 @@
"copied": "Kopiert",
"linkTelegram": "Link Telegram",
"contactEmail": "Kontakt über E-Mail",
"contactTelegram": "Kontakt über Telegram"
"contactTelegram": "Kontakt über Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"send": "Senden",
"contactDiscord": "Kontakt über Discord"
}
}

View File

@ -17,6 +17,10 @@
"copied": "Gekopieerd",
"linkTelegram": "Koppel Telegram",
"contactEmail": "Stuur e-mailbericht",
"contactTelegram": "Stuur Telegram-bericht"
"contactTelegram": "Stuur Telegram-bericht",
"send": "Verstuur",
"linkDiscord": "Koppel Discord",
"linkMatrix": "Koppel Matrix",
"contactDiscord": "Stuur Discord bericht"
}
}

View File

@ -18,7 +18,9 @@
"confirmationRequired": "E-Mail-Bestätigung erforderlich",
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
"sendPIN": "Sende die untenstehende PIN an den Bot und komm dann hierher zurück, um dein Konto zu verbinden."
"sendPIN": "Sende die untenstehende PIN an den Bot und komm dann hierher zurück, um dein Konto zu verbinden.",
"sendPINDiscord": "Gib auf Discord {command} in {server_channel} ein und sende die untenstehende PIN als DM an den Bot.",
"matrixEnterUser": "Gib deine Benutzer-ID ein und drücke auf Absenden. Anschließend erhälst du ein PIN, die hier eingegeben wird um fortzufahren."
},
"validationStrings": {
"length": {
@ -47,6 +49,10 @@
"errorInvalidCode": "Ungültiger Invite-Code.",
"telegramVerified": "Telegram-Konto verifiziert.",
"errorTelegramVerification": "Verifizierung von Telegram erforderlich.",
"errorInvalidPIN": "Telegram PIN ist ungültig."
"errorInvalidPIN": "PIN ist ungültig.",
"errorDiscordVerification": "Discord-Verifizierung erforderlich.",
"errorMatrixVerification": "Matrix-Verifizierung erforderlich.",
"errorUnknown": "Unbekannter Fehler.",
"verified": "Konto verifiziert."
}
}

View File

@ -18,7 +18,9 @@
"confirmationRequired": "Bevestiging van e-mailadres verplicht",
"confirmationRequiredMessage": "Controleer je e-mail inbox om je adres te bevestigen.",
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
"sendPIN": "Stuur onderstaande pincode naar de bot, en kom daarna hier terug om je account te koppelen."
"sendPIN": "Stuur onderstaande pincode naar de bot, en kom daarna hier terug om je account te koppelen.",
"matrixEnterUser": "Voer je gebruikers ID in, druk op versturen, en er wordt je een pincode toegestuurd. Vul die hier in om door te gaan.",
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode via DM naar de bot."
},
"validationStrings": {
"length": {
@ -47,6 +49,10 @@
"errorInvalidCode": "Ongeldige uitnodigingscode.",
"telegramVerified": "Telegram-account goedgekeurd.",
"errorTelegramVerification": "Telegram-verificatie nodig.",
"errorInvalidPIN": "Telegram pincode is ongeldig."
"errorInvalidPIN": "Pincode is ongeldig.",
"errorDiscordVerification": "Discord-verificatie vereist.",
"errorUnknown": "Onbekende fout.",
"errorMatrixVerification": "Matrix-verificatie vereist.",
"verified": "Account geverifieerd."
}
}

View File

@ -17,11 +17,15 @@
"successContinueButton": "Fortsätt",
"confirmationRequired": "E-postbekräftelse krävs",
"confirmationRequiredMessage": "Kontrollera din e-postkorg för att verifiera din adress.",
"yourAccountIsValidUntil": "Ditt konto är giltigt fram tills {date}."
"yourAccountIsValidUntil": "Ditt konto är giltigt fram tills {date}.",
"sendPIN": "Skicka PIN-koden nedan till botten, återvänd sedan till den här sidan för att länka ditt konto."
},
"notifications": {
"errorUserExists": "Användare finns redan.",
"errorInvalidCode": "Ogiltig inbjudningskod."
"errorInvalidCode": "Ogiltig inbjudningskod.",
"errorInvalidPIN": "Telegram-PIN är ogiltig.",
"errorTelegramVerification": "Telegram-verifiering krävs.",
"telegramVerified": "Telegram-konto verifierades."
},
"validationStrings": {
"length": {

13
lang/pwreset/en-GB.json Normal file
View File

@ -0,0 +1,13 @@
{
"meta": {
"name": ""
},
"strings": {
"passwordReset": "",
"resetFailed": "",
"tryAgain": "",
"youCanLogin": "",
"youCanLoginOmbi": "",
"changeYourPassword": ""
}
}

View File

@ -69,7 +69,7 @@
},
"ombi": {
"title": "Ombi",
"description": "Genom att ansluta till Ombi skapas både ett Jellyfin- och Ombi-konto när en användare går med genom jfa-go. Efter installationen, om du är klar, gå till Inställningar för att ställa in en standardprofil för nya ombi-användare.",
"description": "Genom att ansluta till Ombi skapas både ett Jellyfin- och Ombi-konto när en användare går med genom jfa-go. Efter installationen är klar, gå till Inställningar för att ställa in en standardprofil för nya ombi-användare.",
"apiKeyNotice": "Hitta detta i den första fliken i Ombi-inställningarna."
},
"email": {
@ -101,7 +101,10 @@
"title": "Lösenordsåterställningar",
"description": "När en användare försöker återställa sitt lösenord skapar Jellyfin en fil med namnet 'passwordreset-*.json' som innehåller en PIN-kod. jfa-go läser filen och skickar PIN-koden till användaren.",
"pathToJellyfin": "Sökväg till Jellyfin-konfigurationskatalogen",
"pathToJellyfinNotice": "Om du inte vet var det är, försök återställa lösenordet i Jellyfin. En popup med '<sökväg till jellyfin>/passwordreset-*.json' kommer då visas."
"pathToJellyfinNotice": "Om du inte vet var det är, försök återställa lösenordet i Jellyfin. En popup med '<sökväg till jellyfin>/passwordreset-*.json' kommer då visas.",
"resetLinks": "Skicka en länk istället för en PIN",
"resetLinksNotice": "Om Ombi-integrationen är aktiverad, använd den för att synkronisera lösenordsåterställningar med Ombi.",
"resetLinksLanguage": "Förvalt språk för återställningslänk"
},
"passwordValidation": {
"title": "Validering av lösenord",
@ -123,5 +126,12 @@
"successMessageNotice": "Visas när en användare skapar sitt konto.",
"emailMessage": "E-postmeddelande",
"emailMessageNotice": "Visas längst ner i e-postmeddelanden."
},
"updates": {
"title": "Uppdateringar",
"updateChannel": "Uppdateringskanal",
"description": "Aktivera för att få notifikation när nya uppdateringar finns tillgängliga. jfa-go kollar {n} var 30:e minut. Inga IP-adresser eller personligt identifierande handlingar samlas in.",
"stable": "Stabil",
"unstable": "Ostabil"
}
}

12
lang/telegram/de-DE.json Normal file
View File

@ -0,0 +1,12 @@
{
"meta": {
"name": "Deutsch (DE)"
},
"strings": {
"startMessage": "Hallo,\ngib deinen Jellyfin-PIN-Code ein, um dein Konto zu verifizieren.",
"matrixStartMessage": "Hallo,\ngib den untenstehenden PIN auf der Anmeldeseite von Jellyfin ein, um dein Konto zu verifizieren.",
"invalidPIN": "Diese PIN war ungültig, versuche es erneut.",
"pinSuccess": "Erfolg! Du kannst nun zur Anmeldeseite zurückkehren.",
"languageMessage": "Hinweis: Zeige verfügbare Sprachen mit {command} an und stelle mit {command} <Sprachcode> die gewünschte ein."
}
}

118
main.go
View File

@ -36,6 +36,7 @@ var (
SOCK string = "jfa-go.sock"
SRV *http.Server
RESTART chan bool
TRAYRESTART chan bool
DATA, CONFIG, HOST *string
PORT *int
DEBUG *bool
@ -201,6 +202,9 @@ func start(asDaemon, firstCall bool) {
app.err.Fatalf("Couldn't copy default config.")
}
app.info.Printf("Copied default configuration to \"%s\"", app.configPath)
tempConfig, _ := ini.Load(app.configPath)
tempConfig.Section("").Key("first_run").SetValue("true")
tempConfig.SaveTo(app.configPath)
}
var debugMode bool
@ -209,9 +213,8 @@ func start(asDaemon, firstCall bool) {
app.err.Fatalf("Failed to load config file \"%s\": %v", app.configPath, err)
}
// Some message settings have been moved from "email" to "messages", this will switch them.
if app.config.Section("email").Key("use_24h").Value() != "" {
app.migrateEmailConfig()
if app.config.Section("").Key("first_run").MustBool(false) {
firstRun = true
}
app.version = app.config.Section("jellyfin").Key("version").String()
@ -325,7 +328,7 @@ func start(asDaemon, firstCall bool) {
app.storage.emails_path = app.config.Section("files").Key("emails").String()
if err := app.storage.loadEmails(); err != nil {
app.err.Printf("Failed to load Emails: %v", err)
err := app.migrateEmailStorage()
err := migrateEmailStorage(app)
if err != nil {
app.err.Printf("Failed to migrate Email storage: %v", err)
}
@ -361,23 +364,6 @@ func start(asDaemon, firstCall bool) {
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
app.storage.loadProfiles()
// Migrate from pre-0.2.0 user templates to profiles
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")
app.storage.migrateToProfile()
for _, path := range [3]string{app.storage.policy_path, app.storage.configuration_path, app.storage.displayprefs_path} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
dir, fname := filepath.Split(path)
newFname := strings.Replace(fname, ".json", ".old.json", 1)
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()
}
if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String()
@ -396,17 +382,6 @@ func start(asDaemon, firstCall bool) {
configBase, _ := fs.ReadFile(localFS, app.configBasePath)
json.Unmarshal(configBase, &app.configBase)
themes := map[string]string{
"Jellyfin (Dark)": "dark-theme",
"Default (Light)": "light-theme",
}
// For move from Bootstrap to a17t (0.2.5)
if app.config.Section("ui").Key("theme").String() == "Bootstrap (Light)" {
app.config.Section("ui").Key("theme").SetValue("Default (Light)")
}
if val, ok := themes[app.config.Section("ui").Key("theme").String()]; ok {
app.cssClass = val
}
secret, err := generateSecret(16)
if err != nil {
app.err.Fatal(err)
@ -427,7 +402,7 @@ func start(asDaemon, firstCall bool) {
app.info.Println("Using Jellyfin server type")
}
app.jf, _ = mediabrowser.NewServer(
app.jf, err = mediabrowser.NewServer(
serverType,
server,
app.config.Section("jellyfin").Key("client").String(),
@ -437,6 +412,9 @@ func start(asDaemon, firstCall bool) {
timeoutHandler,
cacheTimeout,
)
if err != nil {
app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: %v", server, err)
}
if debugMode {
app.jf.Verbose = true
}
@ -446,76 +424,8 @@ func start(asDaemon, firstCall bool) {
app.err.Fatalf("Failed to authenticate with Jellyfin @ %s (%d): %v", server, status, err)
}
app.info.Printf("Authenticated with %s", server)
// /* A couple of unstable Jellyfin 10.7.0 releases decided to hyphenate user IDs.
// This checks if the version is equal or higher. */
// checkVersion := func(version string) int {
// numberStrings := strings.Split(version, ".")
// n := 0
// for _, s := range numberStrings {
// num, err := strconv.Atoi(s)
// if err == nil {
// n += num
// }
// }
// return n
// }
// if serverType == mediabrowser.JellyfinServer && checkVersion(app.jf.ServerInfo.Version) >= checkVersion("10.7.0") {
// // Get users to check if server uses hyphenated userIDs
// app.jf.GetUsers(false)
// noHyphens := true
// for id := range app.storage.emails {
// if strings.Contains(id, "-") {
// noHyphens = false
// break
// }
// }
// if noHyphens == app.jf.Hyphens {
// var newEmails map[string]interface{}
// var newUsers map[string]time.Time
// var status, status2 int
// var err, err2 error
// if app.jf.Hyphens {
// app.info.Println(info("Your build of Jellyfin appears to hypenate user IDs. Your emails.json/users.json file will be modified to match."))
// time.Sleep(time.Second * time.Duration(3))
// newEmails, status, err = app.hyphenateEmailStorage(app.storage.emails)
// newUsers, status2, err2 = app.hyphenateUserStorage(app.storage.users)
// } else {
// app.info.Println(info("Your emails.json/users.json file uses hyphens, but the Jellyfin server no longer does. It will be modified."))
// time.Sleep(time.Second * time.Duration(3))
// newEmails, status, err = app.deHyphenateEmailStorage(app.storage.emails)
// newUsers, status2, err2 = app.deHyphenateUserStorage(app.storage.users)
// }
// if status != 200 || err != nil {
// app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
// app.err.Fatalf("Couldn't upgrade emails.json")
// }
// if status2 != 200 || err2 != nil {
// app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
// app.err.Fatalf("Couldn't upgrade users.json")
// }
// emailBakFile := app.storage.emails_path + ".bak"
// usersBakFile := app.storage.users_path + ".bak"
// err = storeJSON(emailBakFile, app.storage.emails)
// err2 = storeJSON(usersBakFile, app.storage.users)
// if err != nil {
// app.err.Fatalf("couldn't store emails.json backup: %v", err)
// }
// if err2 != nil {
// app.err.Fatalf("couldn't store users.json backup: %v", err)
// }
// app.storage.emails = newEmails
// app.storage.users = newUsers
// err = app.storage.storeEmails()
// err2 = app.storage.storeUsers()
// if err != nil {
// app.err.Fatalf("couldn't store emails.json: %v", err)
// }
// if err2 != nil {
// app.err.Fatalf("couldn't store users.json: %v", err)
// }
// }
// }
runMigrations(app)
// Auth (manual user/pass or jellyfin)
app.jellyfinLogin = true
@ -561,7 +471,7 @@ func start(asDaemon, firstCall bool) {
invDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
go invDaemon.run()
defer invDaemon.shutdown()
defer invDaemon.Shutdown()
userDaemon := newUserDaemon(time.Duration(60*time.Second), app)
go userDaemon.run()
@ -690,7 +600,7 @@ func flagPassed(name string) (found bool) {
}
// @title jfa-go internal API
// @version 0.3.4
// @version 0.3.6
// @description API for the jfa-go frontend
// @contact.name Harvey Tindall
// @contact.email hrfee@hrfee.dev

180
migrations.go Normal file
View File

@ -0,0 +1,180 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/ini.v1"
)
func runMigrations(app *appContext) {
migrateProfiles(app)
migrateBootstrap(app)
migrateEmailStorage(app)
// migrateHyphens(app)
}
// Migrate pre-0.2.0 user templates to profiles
func migrateProfiles(app *appContext) {
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")
app.storage.migrateToProfile()
for _, path := range [3]string{app.storage.policy_path, app.storage.configuration_path, app.storage.displayprefs_path} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
dir, fname := filepath.Split(path)
newFname := strings.Replace(fname, ".json", ".old.json", 1)
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()
}
}
// Migrate pre-0.2.5 bootstrap theme choice to a17t version.
func migrateBootstrap(app *appContext) {
themes := map[string]string{
"Jellyfin (Dark)": "dark-theme",
"Default (Light)": "light-theme",
}
if app.config.Section("ui").Key("theme").String() == "Bootstrap (Light)" {
app.config.Section("ui").Key("theme").SetValue("Default (Light)")
}
if val, ok := themes[app.config.Section("ui").Key("theme").String()]; ok {
app.cssClass = val
}
}
func migrateEmailConfig(app *appContext) {
tempConfig, _ := ini.Load(app.configPath)
fmt.Println(warning("Part of your email configuration will be migrated to the new \"messages\" section.\nA backup will be made."))
err := tempConfig.SaveTo(app.configPath + "_" + commit + ".bak")
if err != nil {
app.err.Fatalf("Failed to backup config: %v", err)
return
}
for _, setting := range []string{"use_24h", "date_format", "message"} {
if val := app.config.Section("email").Key(setting).Value(); val != "" {
tempConfig.Section("email").Key(setting).SetValue("")
tempConfig.Section("messages").Key(setting).SetValue(val)
}
}
if app.config.Section("messages").Key("enabled").MustBool(false) || app.config.Section("telegram").Key("enabled").MustBool(false) {
tempConfig.Section("messages").Key("enabled").SetValue("true")
}
err = tempConfig.SaveTo(app.configPath)
if err != nil {
app.err.Fatalf("Failed to save config: %v", err)
return
}
app.loadConfig()
}
// Migrate pre-0.3.6 email settings to the new messages section.
// Called just after loading email storage in main.go.
func migrateEmailStorage(app *appContext) error {
// use_24h was moved to messages, so this checks if migration has already occurred or not.
if app.config.Section("email").Key("use_24h").Value() == "" {
return nil
}
var emails map[string]interface{}
err := loadJSON(app.storage.emails_path, &emails)
if err != nil {
return err
}
newEmails := map[string]EmailAddress{}
for jfID, addr := range emails {
newEmails[jfID] = EmailAddress{
Addr: addr.(string),
Contact: true,
}
}
err = storeJSON(app.storage.emails_path+".bak", emails)
if err != nil {
return err
}
err = storeJSON(app.storage.emails_path, newEmails)
if err != nil {
return err
}
app.info.Println("Migrated to new email format. A backup has also been made.")
return nil
}
// Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled.
// func migrateHyphens(app *appContext) {
// checkVersion := func(version string) int {
// numberStrings := strings.Split(version, ".")
// n := 0
// for _, s := range numberStrings {
// num, err := strconv.Atoi(s)
// if err == nil {
// n += num
// }
// }
// return n
// }
// if serverType == mediabrowser.JellyfinServer && checkVersion(app.jf.ServerInfo.Version) >= checkVersion("10.7.0") {
// // Get users to check if server uses hyphenated userIDs
// app.jf.GetUsers(false)
//
// noHyphens := true
// for id := range app.storage.emails {
// if strings.Contains(id, "-") {
// noHyphens = false
// break
// }
// }
// if noHyphens == app.jf.Hyphens {
// var newEmails map[string]interface{}
// var newUsers map[string]time.Time
// var status, status2 int
// var err, err2 error
// if app.jf.Hyphens {
// app.info.Println(info("Your build of Jellyfin appears to hypenate user IDs. Your emails.json/users.json file will be modified to match."))
// time.Sleep(time.Second * time.Duration(3))
// newEmails, status, err = app.hyphenateEmailStorage(app.storage.emails)
// newUsers, status2, err2 = app.hyphenateUserStorage(app.storage.users)
// } else {
// app.info.Println(info("Your emails.json/users.json file uses hyphens, but the Jellyfin server no longer does. It will be modified."))
// time.Sleep(time.Second * time.Duration(3))
// newEmails, status, err = app.deHyphenateEmailStorage(app.storage.emails)
// newUsers, status2, err2 = app.deHyphenateUserStorage(app.storage.users)
// }
// if status != 200 || err != nil {
// app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
// app.err.Fatalf("Couldn't upgrade emails.json")
// }
// if status2 != 200 || err2 != nil {
// app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
// app.err.Fatalf("Couldn't upgrade users.json")
// }
// emailBakFile := app.storage.emails_path + ".bak"
// usersBakFile := app.storage.users_path + ".bak"
// err = storeJSON(emailBakFile, app.storage.emails)
// err2 = storeJSON(usersBakFile, app.storage.users)
// if err != nil {
// app.err.Fatalf("couldn't store emails.json backup: %v", err)
// }
// if err2 != nil {
// app.err.Fatalf("couldn't store users.json backup: %v", err)
// }
// app.storage.emails = newEmails
// app.storage.users = newUsers
// err = app.storage.storeEmails()
// err2 = app.storage.storeUsers()
// if err != nil {
// app.err.Fatalf("couldn't store emails.json: %v", err)
// }
// if err2 != nil {
// app.err.Fatalf("couldn't store users.json: %v", err)
// }
// }
// }
// }

37
tray.go
View File

@ -53,11 +53,29 @@ func onReady() {
}()
RESTART = make(chan bool, 1)
TRAYRESTART = make(chan bool, 1)
go start(false, true)
mStart.Disable()
mStop.Enable()
mRestart.Enable()
go as.HandleCheck()
trayRestart := func() {
if RUNNING {
RESTART <- true
mStop.Disable()
mStart.Enable()
mRestart.Disable()
for {
if !RUNNING {
break
}
}
go start(false, false)
mStart.Disable()
mStop.Enable()
mRestart.Enable()
}
}
for {
select {
case <-mStart.ClickedCh:
@ -74,27 +92,14 @@ func onReady() {
mStart.Enable()
mRestart.Disable()
}
case <-TRAYRESTART:
trayRestart()
case <-mRestart.ClickedCh:
if RUNNING {
RESTART <- true
mStop.Disable()
mStart.Enable()
mRestart.Disable()
for {
if !RUNNING {
break
}
}
go start(false, false)
mStart.Disable()
mStop.Enable()
mRestart.Enable()
}
trayRestart()
case <-mOpenLogs.ClickedCh:
open.Start(logPath)
case <-mQuit.ClickedCh:
systray.Quit()
// case <-mOnLogin.ClickedCh:
}
}
}

View File

@ -117,23 +117,23 @@ func (app *appContext) AdminPage(gc *gin.Context) {
}
license = string(l)
gcHTML(gc, http.StatusOK, "admin.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass,
"contactMessage": "",
"email_enabled": emailEnabled,
"telegram_enabled": telegramEnabled,
"discord_enabled": discordEnabled,
"matrix_enabled": matrixEnabled,
"notifications": notificationsEnabled,
"version": version,
"commit": commit,
"ombiEnabled": ombiEnabled,
"username": !app.config.Section("email").Key("no_username").MustBool(false),
"strings": app.storage.lang.Admin[lang].Strings,
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
"language": app.storage.lang.Admin[lang].JSON,
"langName": lang,
"license": license,
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass,
"contactMessage": "",
"emailEnabled": emailEnabled,
"telegramEnabled": telegramEnabled,
"discordEnabled": discordEnabled,
"matrixEnabled": matrixEnabled,
"ombiEnabled": ombiEnabled,
"notifications": notificationsEnabled,
"version": version,
"commit": commit,
"username": !app.config.Section("email").Key("no_username").MustBool(false),
"strings": app.storage.lang.Admin[lang].Strings,
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
"language": app.storage.lang.Admin[lang].JSON,
"langName": lang,
"license": license,
})
}