Managing Preferences without global vars/arrays

Share your advanced PureBasic knowledge/code with the community.
User avatar
pdwyer
Addict
Addict
Posts: 2813
Joined: Tue May 08, 2007 1:27 pm
Location: Chiba, Japan

Managing Preferences without global vars/arrays

Post by pdwyer »

Perhaps others have had better ideas for managing this and if you have, please share below!
:idea:
I wanted to have a preferences file using the PB preferences library but
- I want to be using them and updating them in memory during runtime and not going to disk except to load and save all
- I don't want to use global vars or arrays to hold them
- I want a dynamic way to add new vars and not need to add more vars etc and still support groups
- I want something I can reuse in an include file

I guess I could have used a static array but I didn't want to enum an array looking for matches.

I've only just started using this in a real app so it might be buggy still. I'll update if I spot any.
I'm not sure if having a static prefs file name is a good idea or not. perhaps this should be passed with load and save instructions but it still leaves static filenames in the main code calls.

comments, criticisms and advice welcome

Cheers

(Edits)
- (Code updated) I think this needs a passable "default" val. Perhaps if you pass the val with get if the reply is "" then it sends the val you passed back to you

Code: Select all


#Pref_GetVar = 1
#Pref_SetVar = 2
#Pref_Admin_SavePrefs = 3
#Pref_Admin_LoadPrefs = 4

Declare .s AppPreferences(ActionType.l, PrefGroup.s = "", PrefName.s = "", PrefVal.s = "")

;===============================================================================================
; Testing code
;===============================================================================================
AppPreferences(#Pref_Admin_LoadPrefs)  ;creates empty prefs if not loaded

Debug "Write some vars in groups"
AppPreferences(#Pref_SetVar, "", "MyVar", "10")  ;no group
AppPreferences(#Pref_SetVar, "MyGroup", "MyVar", "10")
AppPreferences(#Pref_SetVar, "MyGroup", "MyVar2", "10.00")
AppPreferences(#Pref_SetVar, "MyOtherGroup", "MyVar2", "Hello")
AppPreferences(#Pref_SetVar, "MyGroup", "MyVar3", "5")

Debug "Read them back"
Debug AppPreferences(#Pref_GetVar, "", "MyVar")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar2")
Debug AppPreferences(#Pref_GetVar, "MyOtherGroup", "MyVar2")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar3")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar7", "100") ;look for a var that's missing and update and return result

Debug "flush to disk"
AppPreferences(#Pref_Admin_SavePrefs) 

Debug "Update after disk write"
AppPreferences(#Pref_SetVar, "MyGroup", "MyVar4", "50")
Debug "read And check update"
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar4")

Debug "Refresh from disk"
AppPreferences(#Pref_Admin_LoadPrefs) 

Debug "Check prefs from disk"
Debug AppPreferences(#Pref_GetVar, "", "MyVar")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar2")
Debug AppPreferences(#Pref_GetVar, "MyOtherGroup", "MyVar2")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar3")
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar7")  ;note it's updated

Debug "Check removal of pref not written to disk "
Debug AppPreferences(#Pref_GetVar, "MyGroup", "MyVar4")
Debug "nothing returned"


;===============================================================================================


Procedure.s AppPreferences(ActionType.l, PrefGroup.s = "", PrefName.s = "", PrefVal.s = "")
    
    ;Map Format = "group.name"="val"
    ;if group or "." omitted then add to "Default" group, don't return "Default" name for that group, just name=val
    
    Static NewMap AppPrefs.s()
    Define Prefs_Filename.s = "AppPrefs.conf"
    Define CurMapKey.s, CurMapGroup.s, CurMapName.s, CurMapVal.s
    Define ReturnVal.s
    
    Select ActionType
        Case #Pref_GetVar       ; (Gets a pref from the Map - in memory only, Not read from disk prefs file)
            If PrefName = "" 
                ProcedureReturn ""
            Else
                If Trim(PrefGroup) = "" ; no group
                    ReturnVal = AppPrefs("Default." + PrefName)  
                Else
                    ReturnVal = AppPrefs(PrefGroup + "." + PrefName)
                EndIf
            EndIf   
            
            If Trim(ReturnVal) = "" ;send details back and update
                AppPreferences(#Pref_SetVar, PrefGroup, PrefName, PrefVal)
                ProcedureReturn PrefVal
            Else
                ProcedureReturn ReturnVal
            EndIf
                           
        Case #Pref_SetVar       ; (Sets a pref in the map - in memory only, not writen to disk prefs file)
            If PrefName = "" Or PrefVal = ""
                ProcedureReturn ""
            Else
                If Trim(PrefGroup) = "" ; no group
                    AppPrefs("Default." + PrefName) = PrefVal    
                Else
                    AppPrefs(PrefGroup + "." + PrefName) = PrefVal
                EndIf

                ProcedureReturn ""
            EndIf
        Case #Pref_Admin_SavePrefs ; (Saves prefs to file)
            If OpenPreferences(Prefs_Filename,#PB_Preference_GroupSeparator)
 
            Else
                CreatePreferences(Prefs_Filename,#PB_Preference_GroupSeparator)
            EndIf
            
            ResetMap(AppPrefs())
            
            While NextMapElement(AppPrefs()) 
                CurMapKey = MapKey(AppPrefs())
                
                If FindString(CurMapKey,".") ;contains group
                    CurMapGroup = StringField(CurMapKey,1,".")
                    CurMapName = StringField(CurMapKey,2,".")
                    CurMapVal = AppPrefs(CurMapKey)
                Else ;add to "default" group
                    CurMapGroup = "Default"
                    CurMapName = StringField(CurMapKey,2,".")
                    CurMapVal = AppPrefs(CurMapKey)
                EndIf
                PreferenceGroup(CurMapGroup)
                WritePreferenceString(CurMapName, CurMapVal)
            Wend
            
            ClosePreferences()
                
        Case #Pref_Admin_LoadPrefs ; (Loads prefs to file)
            ;Clear MAP for loading
            ClearMap(AppPrefs())
            If OpenPreferences(Prefs_Filename,#PB_Preference_GroupSeparator)
                ResetMap(AppPrefs())
                
                ExaminePreferenceGroups()
                While NextPreferenceGroup() 
                    CurMapGroup = PreferenceGroupName()
                    ExaminePreferenceKeys()
                    While  NextPreferenceKey() 
                        CurMapName = PreferenceKeyName()
                        CurMapVal = PreferenceKeyValue()
                        AppPrefs(CurMapGroup + "." + CurMapName) = CurMapVal
                    Wend
                Wend
  
                ClosePreferences()
            Else
                ;No prefs file so create
                CreatePreferences(Prefs_Filename,#PB_Preference_GroupSeparator)
                ClosePreferences()
                ProcedureReturn ""
            EndIf
        Default
            ProcedureReturn ""
    EndSelect

EndProcedure


Paul Dwyer

“In nature, it’s not the strongest nor the most intelligent who survives. It’s the most adaptable to change” - Charles Darwin
“If you can't explain it to a six-year old you really don't understand it yourself.” - Albert Einstein
User avatar
pdwyer
Addict
Addict
Posts: 2813
Joined: Tue May 08, 2007 1:27 pm
Location: Chiba, Japan

Re: Managing Preferences without global vars/arrays

Post by pdwyer »

I found a bug, but I'm not sure of the best way to solve it

The problems with this line are

Code: Select all

Define Prefs_Filename.s = "AppPrefs.conf"
  • Two apps in using this in the same folder will clash (not too likely but possible)
  • Since there is no path, if you open the app from a associated file (eg click on a csv file to open your csv reader app) then the conf file is looked for in the location of the csv file, not the exe and you get conf files created all over the place
I was thinking about this:

Code: Select all

Define Prefs_Filename.s = GetPathPart(ProgramFilename()) +  GetFilePart(ProgramFilename(), #PB_FileSystem_NoExtension) + ".conf"
But, when running from the compiler in debug more the conf file is created for the temp exe stub that the compiler creates so debugging is a pain, and there is name clash with other debugged apps

I guess I can pass the file name as a param after all.

Anyway, if you use this before I (or someone) comes up with a good fix, be aware
Paul Dwyer

“In nature, it’s not the strongest nor the most intelligent who survives. It’s the most adaptable to change” - Charles Darwin
“If you can't explain it to a six-year old you really don't understand it yourself.” - Albert Einstein
User avatar
HeX0R
Addict
Addict
Posts: 992
Joined: Mon Sep 20, 2004 7:12 am
Location: Hell

Re: Managing Preferences without global vars/arrays

Post by HeX0R »

Why not make a module out of it instead of a one-procedure thing?
That shouldn't interefere also with any main code, and you could even use globals, because they wouldn't really be global.

And you could easily add filename and path for the config file.

Plan B, if you don't want all this, you could use something like this:

Code: Select all

Procedure.s AppPreferences(ActionType.l, PrefGroup.s = "", PrefName.s = "", PrefVal.s = "")
 
	Define Prefs_Filename.s
	
	[...]
	Case #Pref_Admin_SavePrefs ; (Saves prefs to file)
		If PrefGroup = ""
			Prefs_Filename = "AppPrefs.conf"
		Else
			Prefs_Filename = PrefGroup + PrefName ;use PrefGroup as optional Path and PrefName as Filename, maybe rename the vars to... GroupOrPath or something ;)
		EndIf
	[...]
	;same for LoadPrefs
Plan C:
Store all config files in one folder, %APPDATA%/pdwyers_kick_azz_appz/ e.g. for windows, use hash from ProgramFilename() as filename.
Axolotl
Enthusiast
Enthusiast
Posts: 448
Joined: Wed Dec 31, 2008 3:36 pm

Re: Managing Preferences without global vars/arrays

Post by Axolotl »

you can do this to distinguish runtime and development mode

Code: Select all

;:: 
;:: local (portable) application preferences filename 
;:: 
;'' important -- internally used constants (copy to main source file only, not necessary to change this value)  
  #Program_MainSourceFileName$ = #PB_Compiler_Filename         ;' create Preferences$, can be different to #ProgramName$ 
  #Program_MainSourceFilePath$ = #PB_Compiler_FilePath         ;' make debuggable path 

CompilerIf #PB_Editor_CreateExecutable = 0                          ;' development mode 
  Define Prefs_Filename.s = #Program_MainSourceFilePath$ +  GetFilePart(#Program_MainSourceFileName$, #PB_FileSystem_NoExtension) + ".conf"
CompilerElse 
  Define Prefs_Filename.s = GetPathPart(ProgramFilename()) +  GetFilePart(ProgramFilename(), #PB_FileSystem_NoExtension) + ".conf"
CompilerEndIf 
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
Post Reply