11import Dexie from 'dexie' ;
2- import ManagerSettings from '../../r2mm/manager/ManagerSettings' ;
32import Game from '../../model/game/Game' ;
43import EnumResolver from '../../model/enums/_EnumResolver' ;
54import { SortNaming } from '../../model/real_enums/sort/SortNaming' ;
65import { SortDirection } from '../../model/real_enums/sort/SortDirection' ;
76import { SortLocalDisabledMods } from '../../model/real_enums/sort/SortLocalDisabledMods' ;
8- import GameManager from '../../model/game/GameManager' ;
97import { StorePlatform } from '../../model/game/StorePlatform' ;
108import { GameSelectionViewMode } from '../../model/enums/GameSelectionViewMode' ;
9+ import GameManager from '../../model/game/GameManager'
1110
1211export const SETTINGS_DB_NAME = "settings" ;
1312
1413export default class SettingsDexieStore extends Dexie {
1514
16- global : Dexie . Table < SettingsInterface , number > ;
17- gameSpecific : Dexie . Table < SettingsInterface , number > ;
1815 activeGame : Game ;
16+ games : Dexie . Table < GameSettingsInterface , string > ;
17+ global : Dexie . Table < SettingsInterface , number > ;
1918
2019 constructor ( game : Game ) {
2120 super ( SETTINGS_DB_NAME ) ;
@@ -24,66 +23,73 @@ export default class SettingsDexieStore extends Dexie {
2423 } ) ;
2524
2625 const store = {
27- value : `++id,settings`
26+ value : `++id,settings` ,
27+ games : `identifier,settings` ,
2828 } as any ;
2929
30- GameManager . gameList
31- . forEach ( value => {
32- store [ value . settingsIdentifier ] = `++id,settings` ;
33- } ) ;
30+ // Setup the v3 (>= 75) dexie store.
31+ // This contains the following tables:
32+ // - `games`: (settingsIdentifier,settings) <-- Game-specific settings.
33+ // - `value`: (++id, settings) <-- Global settings.
34+ this . version ( 75 ) . stores ( store ) . upgrade ( async ( tx ) => {
35+ // Migrate the current game's legacy (v2) table to the v3 schema, if applicable - versions < 75.
36+ //
37+ // v2 (whose types we still utilize) stored game-specific settings in individual tables.
38+ // This worked but required us to bump the Dexie store version every time we added a new game. Yuck!
39+ // To get around this we have moved all game settings into the `games` table, where the key
40+ // is the settings identifier of the game, and the value is a JSON string.
41+ for ( const game of GameManager . gameList ) {
42+ let gameTable ;
43+
44+ try {
45+ gameTable = tx . table ( game . settingsIdentifier ) ;
46+ } catch {
47+ continue ;
48+ }
3449
35- // Add all games to store. Borked v2-3 locally
36- // Increment per game or change to settings.
37- this . version ( 74 ) . stores ( store ) ;
50+ const legacyEntry = await gameTable . toCollection ( ) . first ( ) ;
51+ if ( legacyEntry === undefined ) {
52+ continue ;
53+ }
54+
55+ // If the legacy game table exists AND it contains a valid settings field, write it to the games table.
56+ // Then clear the contents of the legacy game table.
57+ await this . games . put ( { identifier : gameTable . name , settings : legacyEntry . settings } ) ;
58+ await gameTable . clear ( ) ;
59+ }
60+ } )
3861
3962 this . activeGame = game ;
4063 this . global = this . table ( "value" ) ;
41- this . gameSpecific = this . table ( game . settingsIdentifier ) ;
64+ this . games = this . table ( "games" ) ;
4265 }
4366
4467 public async getLatestGlobal ( ) : Promise < ManagerSettingsInterfaceGlobal_V2 > {
45- return this . global . toArray ( ) . then ( async result => {
46- if ( result . length > 0 ) {
47- const globalEntry = result [ result . length - 1 ] ;
48- const parsed = JSON . parse ( globalEntry . settings ) ;
49- if ( ( parsed as ManagerSettingsInterfaceGlobal_V2 ) . version ) {
50- // Is modern (at least V2).
51- return parsed ;
52- } else {
53- // Is legacy.
54- const legacyToV2 = this . mapLegacyToV2 ( parsed , this . activeGame ) ;
55- await this . global . put ( { settings : JSON . stringify ( legacyToV2 . global ) } ) ;
56- await this . gameSpecific . put ( { settings : JSON . stringify ( legacyToV2 . gameSpecific ) } ) ;
57- return legacyToV2 . global ;
58- }
59- } else {
60- ManagerSettings . NEEDS_MIGRATION = true ;
61- const obj = this . createNewSettingsInstance ( ) ;
62- await this . global . put ( { settings : JSON . stringify ( obj . global ) } ) ;
63- await this . gameSpecific . put ( { settings : JSON . stringify ( obj . gameSpecific ) } ) ;
64- return obj . global ;
65- }
66- } ) ;
68+ const global = await this . global . get ( 1 ) ;
69+
70+ // Create the global settings row if it does not already exist.
71+ if ( global === undefined ) {
72+ const newSettings = this . createNewSettingsInstance ( ) ;
73+ await this . global . put ( { settings : JSON . stringify ( newSettings . global ) } ) ;
74+ return newSettings . global ;
75+ }
76+
77+ // Otherwise parse and return the settings field.
78+ return JSON . parse ( global . settings ) ;
6779 }
6880
6981 public async getLatestGameSpecific ( ) : Promise < ManagerSettingsInterfaceGame_V2 > {
70- return this . gameSpecific . toArray ( ) . then ( async result => {
71- if ( result . length > 0 ) {
72- const globalEntry = result [ result . length - 1 ] ;
73- const parsed = JSON . parse ( globalEntry . settings ) ;
74- if ( ( parsed as ManagerSettingsInterfaceGame_V2 ) . version === 2 ) {
75- // Is modern (at least V2).
76- return parsed ;
77- } else {
78- // Placeholder for future migration
79- return ;
80- }
81- } else {
82- const obj = this . createNewSettingsInstance ( ) ;
83- await this . gameSpecific . put ( { settings : JSON . stringify ( obj . gameSpecific ) } ) ;
84- return obj . gameSpecific ;
85- }
86- } ) ;
82+ const identifier = this . activeGame . settingsIdentifier ;
83+ const game = await this . games . get ( identifier ) ;
84+
85+ if ( game !== undefined ) {
86+ return JSON . parse ( game . settings ) ;
87+ }
88+
89+ let newGame = this . createNewSettingsInstance ( ) ;
90+ await this . games . put ( { identifier : identifier , settings : JSON . stringify ( newGame . gameSpecific ) } ) ;
91+
92+ return newGame . gameSpecific ;
8793 }
8894
8995 public async getLatest ( ) : Promise < ManagerSettingsInterfaceHolder > {
@@ -96,7 +102,7 @@ export default class SettingsDexieStore extends Dexie {
96102 } ;
97103 } ;
98104
99- return await this . transaction ( "rw!" , this . global , this . gameSpecific , get ) ;
105+ return await this . transaction ( "rw!" , this . global , this . games , get ) ;
100106 }
101107
102108 private createNewSettingsInstance ( ) : ManagerSettingsInterfaceHolder {
@@ -130,57 +136,31 @@ export default class SettingsDexieStore extends Dexie {
130136
131137 public async save ( holder : ManagerSettingsInterfaceHolder ) {
132138 const update = async ( ) => {
139+ // Update global settings.
133140 await this . global . toArray ( ) . then ( async result => {
134141 for ( let settingsInterface of result ) {
135142 await this . global . update ( settingsInterface . id ! , { settings : JSON . stringify ( holder . global ) } ) ;
136143 }
137144 } ) ;
138- await this . gameSpecific . toArray ( ) . then ( async result => {
139- for ( let settingsInterface of result ) {
140- await this . gameSpecific . update ( settingsInterface . id ! , { settings : JSON . stringify ( holder . gameSpecific ) } ) ;
141- }
142- } ) ;
143- }
144145
145- await this . transaction ( "rw!" , this . global , this . gameSpecific , update ) ;
146- }
147-
148- private mapLegacyToV2 ( itf : ManagerSettingsInterface_Legacy , game : Game ) : ManagerSettingsInterfaceHolder {
149- return {
150- global : {
151- darkTheme : itf . darkTheme ,
152- dataDirectory : itf . dataDirectory ,
153- expandedCards : itf . expandedCards ,
154- funkyModeEnabled : itf . funkyModeEnabled ,
155- ignoreCache : itf . ignoreCache ,
156- steamDirectory : itf . steamDirectory ,
157- lastSelectedGame : null ,
158- version : 2 ,
159- favouriteGames : [ ] ,
160- defaultGame : undefined ,
161- defaultStore : undefined ,
162- gameSelectionViewMode : GameSelectionViewMode . CARD
163- } ,
164- gameSpecific : {
165- version : 2 ,
166- gameDirectory : game . displayName === "Risk of Rain 2" ? itf . riskOfRain2Directory : null ,
167- installedDisablePosition : itf . installedDisablePosition ,
168- installedSortBy : itf . installedDisablePosition ,
169- installedSortDirection : itf . installedSortDirection ,
170- lastSelectedProfile : itf . lastSelectedProfile ,
171- launchParameters : itf . launchParameters ,
172- linkedFiles : itf . linkedFiles
173- }
146+ // Update the active game's settings.
147+ await this . games . put ( { identifier : this . activeGame . settingsIdentifier , settings : JSON . stringify ( holder . gameSpecific ) } ) ;
174148 }
175- }
176149
150+ await this . transaction ( "rw!" , this . global , this . games , update ) ;
151+ }
177152}
178153
179154interface SettingsInterface {
180155 id ?: number ;
181156 settings : string ;
182157}
183158
159+ interface GameSettingsInterface {
160+ identifier : string ;
161+ settings : string ;
162+ }
163+
184164/**
185165 * Legacy interface as manager was designed to originally only support Risk of Rain 2.
186166 * Expanding supported games means that the "riskOfRain2Directory" setting should no longer be global.
0 commit comments