Skip to content

Commit 7cd8679

Browse files
committed
Changed user settings package to use sqlite3 instead of pickle. Thanks to Ash for teaching me a bit of sqlite3
1 parent 91cb541 commit 7cd8679

File tree

1 file changed

+115
-31
lines changed
  • addons/source-python/packages/source-python/settings

1 file changed

+115
-31
lines changed

addons/source-python/packages/source-python/settings/storage.py

Lines changed: 115 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
# >> IMPORTS
55
# =============================================================================
66
# Python Imports
7-
# Collections
8-
from collections import defaultdict
9-
# Pickle
10-
import pickle
7+
# SQLite3
8+
from sqlite3 import connect
119

1210
# Source.Python Imports
1311
from paths import SP_DATA_PATH
@@ -21,54 +19,140 @@
2119
# Get the path to the user settings database file
2220
_STORAGE_PATH = SP_DATA_PATH.joinpath('settings', 'users.db')
2321

22+
# Does the ../data/source-python/storage/ directory exist?
23+
if not _STORAGE_PATH.parent.isdir():
24+
25+
# Create the ../data/source-python/storage/ directory
26+
_STORAGE_PATH.parent.mkdir()
27+
2428

2529
# =============================================================================
2630
# >> CLASSES
2731
# =============================================================================
28-
class _PlayerSettingsDictionary(defaultdict):
32+
class _UniqueSettings(dict):
33+
'''Dictionary class used to interact with
34+
the database for a specific uniqueid'''
35+
36+
def __init__(self, uniqueid):
37+
'''Stores the given uniqueid and adds it to the players table'''
38+
39+
# Call the super class' __init__ to initialize the dictionary
40+
super(_UniqueSettings, self).__init__()
41+
42+
# Store the given uniqueid
43+
self._uniqueid = uniqueid
44+
45+
# Add the uniqueid to the players table if it is not already a member
46+
_PlayerSettingsStorage.cursor.execute(
47+
'''INSERT OR IGNORE INTO players VALUES(null, ?)''',
48+
(self.uniqueid, ))
49+
50+
def __setitem__(self, variable, value):
51+
'''Override __setitem__ to insert the given
52+
variable and value to their respective tables'''
53+
54+
# Set the given variable's value in the dictionary
55+
super(_UniqueSettings, self).__setitem__(variable, value)
56+
57+
# Add the variable to the variables table if it is not already a member
58+
_PlayerSettingsStorage.cursor.execute(
59+
'''INSERT OR IGNORE INTO variables VALUES(null, ?)''',
60+
(variable, ))
61+
62+
# Set the value of the variable/uniqueid combination
63+
_PlayerSettingsStorage.cursor.execute(
64+
'''INSERT OR REPLACE INTO variable_values SELECT ''' +
65+
'''variables.id, players.id, ? FROM variables, players ''' +
66+
'''WHERE variables.name=? AND players.uniqueid=?''',
67+
(value, variable, self.uniqueid))
68+
69+
@property
70+
def uniqueid(self):
71+
'''Returns the instance's uniqueid'''
72+
return self._uniqueid
73+
74+
75+
class _PlayerSettingsDictionary(dict):
2976
'''Dictionary class used to store user specific settings values'''
3077

31-
def __init__(self, default_factory):
32-
'''Loads all values from the database into the dictionary'''
78+
def __init__(self):
79+
'''Connects to the database, creates the database tables, and
80+
loads all values from the database into the dictionary'''
3381

34-
# Call the super class' __init__ to initialize the defaultdict
35-
super(_PlayerSettingsDictionary, self).__init__(default_factory)
82+
# Call the super class' __init__ to initialize the dictionary
83+
super(_PlayerSettingsDictionary, self).__init__()
3684

37-
# Has the database previously been saved?
38-
if not _STORAGE_PATH.isfile():
85+
# Connect to the database
86+
self._connection = connect(_STORAGE_PATH)
3987

40-
# No need to go further
41-
return
88+
# Set the text factory
89+
self.connection.text_factory = str
4290

43-
# Open/close the settings database
44-
with _STORAGE_PATH.open('rb') as _user_settings:
91+
# Get the cursor instance
92+
self._cursor = self.connection.cursor()
4593

46-
# Get the stored settings
47-
_settings = pickle.load(_user_settings)
94+
# Create the variables table if it does not exist
95+
self.cursor.execute(
96+
'''CREATE TABLE IF NOT EXISTS variables (id INTEGER '''
97+
'''PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)''')
4898

49-
# Loop through all stored settings
50-
for key, value in _settings.items():
99+
# Create the players table if it does not exist
100+
self.cursor.execute(
101+
'''CREATE TABLE IF NOT EXISTS players (id INTEGER ''' +
102+
'''PRIMARY KEY AUTOINCREMENT, uniqueid TEXT UNIQUE)''')
51103

52-
# Store the setting
53-
self[key] = value
104+
# Create the variable_values table if it does not exist
105+
self.cursor.execute(
106+
'''CREATE TABLE IF NOT EXISTS variable_values (vid '''
107+
'''INTEGER, pid INTEGER, value, PRIMARY KEY (vid, pid))''')
54108

55-
def server_spawn(self, game_event):
56-
'''Stores the dictionary to the database on map change'''
109+
# Get all current data from the variable_values table
110+
data = self.cursor.execute(
111+
'''SELECT V.name, P.uniqueid, value FROM ''' +
112+
'''variable_values AS R JOIN variables AS V ON ''' +
113+
'''R.vid=V.id JOIN players AS P ON R.pid=P.id''')
114+
115+
# Fetch all objects from the result
116+
data = data.fetchall()
57117

58-
# Are there any values stored in the dictionary?
59-
if not self:
118+
# Is the table empty?
119+
if not data:
60120

61-
# If not, no need to go further
121+
# No need to go further
62122
return
63123

64-
# Open/close the settings database as write
65-
with _STORAGE_PATH.open('wb') as _user_settings:
124+
# Loop through all values in the data
125+
for variable, uniqueid, value in data:
126+
127+
# Store the value for the given uniqueid and variable name
128+
self[uniqueid][variable] = value
129+
130+
def __missing__(self, uniqueid):
131+
'''Adds the given uniqueid to the
132+
dictionary as a _UniqueSettings instance'''
66133

67-
# Dump the dictionary to the database file
68-
pickle.dump(self, _user_settings)
134+
# Add the uniqueid to the dictionary
135+
value = self[uniqueid] = _UniqueSettings(uniqueid)
136+
137+
# Return the _UniqueSettings instance
138+
return value
139+
140+
@property
141+
def connection(self):
142+
'''Returns the connection to the database'''
143+
return self._connection
144+
145+
@property
146+
def cursor(self):
147+
'''Returns the cursor instance'''
148+
return self._cursor
149+
150+
def server_spawn(self, game_event):
151+
'''Stores the dictionary to the database on map change'''
152+
self.connection.commit()
69153

70154
# Get the _PlayerSettingsDictionary instance
71-
_PlayerSettingsStorage = _PlayerSettingsDictionary(dict)
155+
_PlayerSettingsStorage = _PlayerSettingsDictionary()
72156

73157
# Register for the event server_spawn in order
74158
# to store the database to file on map change

0 commit comments

Comments
 (0)