import "pkg:/source/utils/conditional.bs"
import "pkg:/source/utils/misc.bs"
' @fileoverview Functions that update the registry based on the last run version and the currently running version
' client version when settings were renamed from dotted names to camelCase
const SETTINGS_MIGRATION_VERSION = "1.1.0"
' client version when audio codec preference was renamed and migrated
const AUDIO_CODEC_MIGRATION_VERSION = "1.1.5"
' Run all necessary registry migrations on the "global" JellyRock registry section
sub runGlobalMigrations()
appLastRunVersion = m.global.app.lastRunVersion
' SETTINGS_MIGRATION_VERSION
if isValid(appLastRunVersion) and not versionChecker(appLastRunVersion, SETTINGS_MIGRATION_VERSION)
' last app version used < SETTINGS_MIGRATION_VERSION
m.wasMigrated = true
migrations = {
' Global settings
"global.rememberme": "globalRememberMe"
}
' Migrate each setting: read → write new → delete old
reg = CreateObject("roRegistrySection", getGlobalRegistrySection())
migratedCount = 0
for each oldName in migrations
if reg.exists(oldName)
if migratedCount = 0
print `Running ${SETTINGS_MIGRATION_VERSION} global registry migrations`
end if
value = reg.read(oldName)
newName = migrations[oldName]
reg.write(newName, value)
reg.delete(oldName)
migratedCount++
end if
end for
if migratedCount > 0
reg.flush()
end if
end if
end sub
' Run registry migrations for user sections
' Only processes sections with a valid serverId key (written by user.Login())
' Automatically skips orphaned/incomplete sections and global sections
' @param targetSections Optional array of specific section names to migrate. If invalid, migrates all sections.
sub runRegistryUserMigrations(targetSections = invalid as dynamic)
regSections = invalid
' If specific sections provided, use those; otherwise get all sections
if isValid(targetSections) and type(targetSections) = "roArray"
regSections = targetSections
else
regSections = getRegistrySections()
end if
' Detect test mode ONCE: if ANY section starts with "test-", we're in test mode
' In test mode, ONLY migrate test sections to prevent touching real user data
hasTestSections = false
for each checkSection in regSections
if LCase(checkSection).left(5) = "test-"
hasTestSections = true
exit for
end if
end for
for each section in regSections
' Skip global registry sections (JellyRock and test-global)
sectionLower = LCase(section)
if sectionLower = "jellyrock" or sectionLower = "test-global"
continue for
end if
' In test mode, skip non-test user sections
isTestSection = sectionLower.left(5) = "test-"
if hasTestSections and not isTestSection
continue for
end if
' Process this section
reg = CreateObject("roRegistrySection", section)
' Validate this is a legitimate user section by checking for serverId
' serverId is ALWAYS written by user.Login() regardless of saveCredentials
' If missing, this is an orphaned/incomplete section - skip it
if not reg.exists("serverId")
print `Skipping incomplete user section: ${section} (missing serverId)`
continue for
end if
' Check if user needs migrations
lastRunVersion = invalid
if reg.exists("LastRunVersion")
lastRunVersion = reg.read("LastRunVersion")
else
' No LastRunVersion found - could be old user data needing migration
' Use "0.0.0" to trigger all migrations (safe since migrations are idempotent)
lastRunVersion = "0.0.0"
reg.write("LastRunVersion", lastRunVersion)
end if
' SETTINGS_MIGRATION_VERSION - Rename dotted settings to camelCase
if isValid(lastRunVersion) and not versionChecker(lastRunVersion, SETTINGS_MIGRATION_VERSION)
m.wasMigrated = true
' Define user setting migrations (old dotted name → new camelCase name)
' Single source of truth for migration mapping
migrations = {
' Auth settings (stored per-user for multi-user support)
"token": "authToken",
"primaryimagetag": "primaryImageTag",
' Playback settings
"playback.bitrate.maxlimited": "playbackBitrateMaxLimited",
"playback.bitrate.limit": "playbackBitrateLimit",
"playback.cinemamode": "playbackCinemaMode",
"playback.subs.custom": "playbackSubsCustom",
"playback.resolution.max": "playbackResolutionMax",
"playback.nextupbuttonseconds": "playbackNextUpButtonSeconds",
"playback.playnextepisode": "playbackPlayNextEpisode",
"playback.preferredAudioCodec": "playbackPreferredAudioCodec",
"playback.subs.onlytext": "playbackSubsOnlyText",
"playback.mpeg2": "playbackMpeg2",
"playback.mpeg4": "playbackMpeg4",
"playback.tryDirect.h264ProfileLevel": "playbackTryDirectH264ProfileLevel",
"playback.tryDirect.hevcProfileLevel": "playbackTryDirectHevcProfileLevel",
' UI settings
"ui.general.episodeimagesnextup": "uiGeneralEpisodeImages",
"ui.font.fallback": "uiFontFallback",
"ui.design.hideclock": "uiDesignHideClock",
"ui.details.maxdaysnextup": "uiDetailsMaxDaysNextUp",
"ui.details.enablerewatchingnextup": "uiDetailsEnableRewatchingNextUp",
"ui.row.layout": "uiRowLayout",
"ui.home.splashBackground": "uiHomeSplashBackground",
"ui.home.useWebSectionArrangement": "uiHomeUseWebArrangement",
"ui.details.hidetagline": "uiDetailsHideTagline",
"ui.movies.showRatings": "uiMoviesShowRatings",
"ui.tvshows.blurunwatched": "uiTvShowsBlurUnwatched",
"ui.tvshows.disableCommunityRating": "uiTvShowsDisableCommunityRating",
"ui.tvshows.disableUnwatchedEpisodeCount": "uiTvShowsDisableUnwatchedCount",
"ui.tvshows.goStraightToEpisodeListing": "uiTvShowsGoStraightToEpisodes",
' Item grid settings
"itemgrid.showItemCount": "itemGridShowItemCount",
"itemgrid.gridTitles": "itemGridTitles",
"itemgrid.reset": "itemGridReset",
"itemgrid.movieDefaultView": "itemGridMovieDefaultView",
' Home section settings
"homesection0": "homeSection0",
"homesection1": "homeSection1",
"homesection2": "homeSection2",
"homesection3": "homeSection3",
"homesection4": "homeSection4",
"homesection5": "homeSection5",
"homesection6": "homeSection6",
' Display settings
"display.livetv.landing": "displayLiveTvLanding"
}
' Migrate each setting: read → write new → delete old
migratedCount = 0
for each oldName in migrations
if reg.exists(oldName)
if migratedCount = 0
print `Migrating user settings to v${SETTINGS_MIGRATION_VERSION} for userid: ${section}`
end if
value = reg.read(oldName)
newName = migrations[oldName]
reg.write(newName, value)
reg.delete(oldName)
migratedCount++
end if
end for
if migratedCount > 0
print `Successfully migrated ${migratedCount} settings for user ${section}`
end if
' Delete server policy keys that should never be in registry
' These were incorrectly stored as user settings but are server-authoritative
serverPolicyKeys = ["liveTvCanRecord", "contentCanDelete", "livetv.canrecord", "content.candelete"]
deletedCount = 0
for each key in serverPolicyKeys
if reg.exists(key)
reg.delete(key)
deletedCount++
end if
end for
if deletedCount > 0
print `Deleted ${deletedCount} server policy keys from user ${section}`
end if
reg.flush()
end if
' AUDIO_CODEC_MIGRATION_VERSION - Rename and migrate audio codec preference
if isValid(lastRunVersion) and not versionChecker(lastRunVersion, AUDIO_CODEC_MIGRATION_VERSION)
m.wasMigrated = true
' Rename playbackPreferredAudioCodec to playbackPreferredMultichannelCodec
' and migrate deprecated values (auto, aac) to new default (eac3)
oldSettingName = "playbackPreferredAudioCodec"
newSettingName = "playbackPreferredMultichannelCodec"
if reg.exists(oldSettingName)
print `Migrating audio codec settings to v${AUDIO_CODEC_MIGRATION_VERSION} for userid: ${section}`
oldValue = reg.read(oldSettingName)
' Migrate deprecated values (auto, aac) to eac3, otherwise preserve existing value
newValue = "eac3"
if isValid(oldValue) and oldValue <> "" and oldValue <> "auto" and oldValue <> "aac"
' User has a valid preference (ac3, eac3, dts) or custom value - preserve it
newValue = oldValue
end if
reg.write(newSettingName, newValue)
reg.delete(oldSettingName)
print `Migrated ${oldSettingName}='${oldValue}' to ${newSettingName}='${newValue}'`
reg.flush()
else
print `No audio codec migration needed for userid: ${section} (setting not found)`
end if
end if
end for
end sub