/
data_manager.py
130 lines (103 loc) · 4.13 KB
/
data_manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Contains the DataManager base class."""
import copy
import os
import errno
import threading
import time
import _thread
from mpf.core.file_manager import FileManager
from mpf.core.mpf_controller import MpfController
class DataManager(MpfController):
"""Handles key value data loading and saving for the machine."""
config_name = "data_manager"
def __init__(self, machine, name, min_wait_secs=1):
"""Initialise data manger.
The DataManager is responsible for reading and writing data to/from a
file on disk.
Args:
machine: The main MachineController instance.
name: A string name that represents what this DataManager instance
is for. This name is used to lookup the configuration option
in the machine config in the mpf:paths:<name> location. That's
how you specify the file name this DataManager will use.
min_wait_secs: Minimal seconds to wait between two writes.
"""
super().__init__(machine)
self.name = name
self.min_wait_secs = min_wait_secs
config_path = self.machine.config['mpf']['paths'][name]
if config_path is False:
self.filename = False
elif isinstance(config_path, str) and config_path.startswith("/"):
self.filename = config_path
elif isinstance(config_path, str):
self.filename = os.path.join(self.machine.machine_path,
self.machine.config['mpf']['paths'][name])
else:
raise AssertionError("Invalid path {} for {}".format(config_path, name))
self.data = dict()
self._dirty = threading.Event()
if self.filename:
self._setup_file()
_thread.start_new_thread(self._writing_thread, ())
def _setup_file(self):
self._make_sure_path_exists(os.path.dirname(self.filename))
self._load()
@classmethod
def _make_sure_path_exists(cls, path):
try:
os.makedirs(path)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
def _load(self):
self.debug_log("Loading %s from %s", self.name, self.filename)
if os.path.isfile(self.filename):
self.data = FileManager.load(self.filename, halt_on_error=False)
else:
self.debug_log("Didn't find the %s file. No prob. We'll create "
"it when we save.", self.name)
if not self.data:
self.data = {}
def get_data(self, section=None):
"""Return the value of this DataManager's data.
Args:
section: Optional string name of a section (dictionary key) for the
data you want returned. Default is None which returns the
entire dictionary.
"""
if not section:
data = copy.copy(self.data)
else:
try:
data = copy.copy(self.data[section])
except (KeyError, TypeError):
data = dict()
if isinstance(data, dict):
return data
else:
return dict()
def _trigger_save(self):
"""Trigger a write of this DataManager's data to the disk."""
self.debug_log("Will write %s to disk", self.name)
self._dirty.set()
def save_all(self, data):
"""Update all data."""
self.data = data
self._trigger_save()
def _writing_thread(self): # pragma: no cover
# prevent early writes at start-up
time.sleep(self.min_wait_secs)
while not self.machine.thread_stopper.is_set():
if not self._dirty.wait(1):
continue
self._dirty.clear()
data = copy.deepcopy(self.data)
self.debug_log("Writing %s to: %s", self.name, self.filename)
# save data
FileManager.save(self.filename, data)
# prevent too many writes
time.sleep(self.min_wait_secs)
# if dirty write data one last time during shutdown
if self._dirty.is_set():
FileManager.save(self.filename, data)