|
4 | 4 | # >> IMPORTS |
5 | 5 | # ============================================================================= |
6 | 6 | # Python Imports |
7 | | -# Collections |
8 | | -from collections import defaultdict |
9 | | -# Pickle |
10 | | -import pickle |
| 7 | +# SQLite3 |
| 8 | +from sqlite3 import connect |
11 | 9 |
|
12 | 10 | # Source.Python Imports |
13 | 11 | from paths import SP_DATA_PATH |
|
21 | 19 | # Get the path to the user settings database file |
22 | 20 | _STORAGE_PATH = SP_DATA_PATH.joinpath('settings', 'users.db') |
23 | 21 |
|
| 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 | + |
24 | 28 |
|
25 | 29 | # ============================================================================= |
26 | 30 | # >> CLASSES |
27 | 31 | # ============================================================================= |
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): |
29 | 76 | '''Dictionary class used to store user specific settings values''' |
30 | 77 |
|
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''' |
33 | 81 |
|
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__() |
36 | 84 |
|
37 | | - # Has the database previously been saved? |
38 | | - if not _STORAGE_PATH.isfile(): |
| 85 | + # Connect to the database |
| 86 | + self._connection = connect(_STORAGE_PATH) |
39 | 87 |
|
40 | | - # No need to go further |
41 | | - return |
| 88 | + # Set the text factory |
| 89 | + self.connection.text_factory = str |
42 | 90 |
|
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() |
45 | 93 |
|
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)''') |
48 | 98 |
|
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)''') |
51 | 103 |
|
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))''') |
54 | 108 |
|
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() |
57 | 117 |
|
58 | | - # Are there any values stored in the dictionary? |
59 | | - if not self: |
| 118 | + # Is the table empty? |
| 119 | + if not data: |
60 | 120 |
|
61 | | - # If not, no need to go further |
| 121 | + # No need to go further |
62 | 122 | return |
63 | 123 |
|
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''' |
66 | 133 |
|
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() |
69 | 153 |
|
70 | 154 | # Get the _PlayerSettingsDictionary instance |
71 | | -_PlayerSettingsStorage = _PlayerSettingsDictionary(dict) |
| 155 | +_PlayerSettingsStorage = _PlayerSettingsDictionary() |
72 | 156 |
|
73 | 157 | # Register for the event server_spawn in order |
74 | 158 | # to store the database to file on map change |
|
0 commit comments