onionshare_cli 2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- onionshare_cli/__init__.py +522 -0
- onionshare_cli/common.py +296 -0
- onionshare_cli/mode_settings.py +146 -0
- onionshare_cli/onion.py +845 -0
- onionshare_cli/onionshare.py +110 -0
- onionshare_cli/resources/static/css/style.css +393 -0
- onionshare_cli/resources/static/img/ajax.gif +0 -0
- onionshare_cli/resources/static/img/favicon.ico +0 -0
- onionshare_cli/resources/static/img/logo.png +0 -0
- onionshare_cli/resources/static/img/logo_large.png +0 -0
- onionshare_cli/resources/static/img/web_file.png +0 -0
- onionshare_cli/resources/static/img/web_folder.png +0 -0
- onionshare_cli/resources/static/js/chat.js +173 -0
- onionshare_cli/resources/static/js/jquery-3.5.1.min.js +2 -0
- onionshare_cli/resources/static/js/receive.js +130 -0
- onionshare_cli/resources/static/js/send.js +75 -0
- onionshare_cli/resources/static/js/socket.io.min.js +3 -0
- onionshare_cli/resources/templates/401.html +21 -0
- onionshare_cli/resources/templates/403.html +21 -0
- onionshare_cli/resources/templates/404.html +21 -0
- onionshare_cli/resources/templates/405.html +21 -0
- onionshare_cli/resources/templates/chat.html +46 -0
- onionshare_cli/resources/templates/denied.html +14 -0
- onionshare_cli/resources/templates/listing.html +59 -0
- onionshare_cli/resources/templates/receive.html +46 -0
- onionshare_cli/resources/templates/send.html +69 -0
- onionshare_cli/resources/templates/thankyou.html +27 -0
- onionshare_cli/resources/torrc_template +8 -0
- onionshare_cli/resources/torrc_template-meek_lite_amazon +2 -0
- onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
- onionshare_cli/resources/torrc_template-obfs4 +27 -0
- onionshare_cli/resources/version.txt +1 -0
- onionshare_cli/resources/wordlist.txt +7776 -0
- onionshare_cli/settings.py +201 -0
- onionshare_cli/web/__init__.py +21 -0
- onionshare_cli/web/chat_mode.py +169 -0
- onionshare_cli/web/receive_mode.py +488 -0
- onionshare_cli/web/send_base_mode.py +338 -0
- onionshare_cli/web/share_mode.py +406 -0
- onionshare_cli/web/web.py +424 -0
- onionshare_cli/web/website_mode.py +123 -0
- onionshare_cli-2.3.dist-info/METADATA +34 -0
- onionshare_cli-2.3.dist-info/RECORD +45 -0
- onionshare_cli-2.3.dist-info/WHEEL +4 -0
- onionshare_cli-2.3.dist-info/entry_points.txt +3 -0
onionshare_cli/common.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
OnionShare | https://onionshare.org/
|
|
4
|
+
|
|
5
|
+
Copyright (C) 2014-2020 Micah Lee, et al. <micah@micahflee.com>
|
|
6
|
+
|
|
7
|
+
This program is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
"""
|
|
20
|
+
import base64
|
|
21
|
+
import hashlib
|
|
22
|
+
import inspect
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
25
|
+
import random
|
|
26
|
+
import socket
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
import threading
|
|
30
|
+
import time
|
|
31
|
+
import shutil
|
|
32
|
+
from pkg_resources import resource_filename
|
|
33
|
+
|
|
34
|
+
from .settings import Settings
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CannotFindTor(Exception):
|
|
38
|
+
"""
|
|
39
|
+
OnionShare can't find a tor binary
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Common:
|
|
44
|
+
"""
|
|
45
|
+
The Common object is shared amongst all parts of OnionShare.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, verbose=False):
|
|
49
|
+
self.verbose = verbose
|
|
50
|
+
|
|
51
|
+
# The platform OnionShare is running on
|
|
52
|
+
self.platform = platform.system()
|
|
53
|
+
if self.platform.endswith("BSD") or self.platform == "DragonFly":
|
|
54
|
+
self.platform = "BSD"
|
|
55
|
+
|
|
56
|
+
# The current version of OnionShare
|
|
57
|
+
with open(self.get_resource_path("version.txt")) as f:
|
|
58
|
+
self.version = f.read().strip()
|
|
59
|
+
|
|
60
|
+
def load_settings(self, config=None):
|
|
61
|
+
"""
|
|
62
|
+
Loading settings, optionally from a custom config json file.
|
|
63
|
+
"""
|
|
64
|
+
self.settings = Settings(self, config)
|
|
65
|
+
self.settings.load()
|
|
66
|
+
|
|
67
|
+
def log(self, module, func, msg=None):
|
|
68
|
+
"""
|
|
69
|
+
If verbose mode is on, log error messages to stdout
|
|
70
|
+
"""
|
|
71
|
+
if self.verbose:
|
|
72
|
+
timestamp = time.strftime("%b %d %Y %X")
|
|
73
|
+
|
|
74
|
+
final_msg = f"[{timestamp}] {module}.{func}"
|
|
75
|
+
if msg:
|
|
76
|
+
final_msg = f"{final_msg}: {msg}"
|
|
77
|
+
print(final_msg)
|
|
78
|
+
|
|
79
|
+
def get_resource_path(self, filename):
|
|
80
|
+
"""
|
|
81
|
+
Returns the absolute path of a resource
|
|
82
|
+
"""
|
|
83
|
+
self.log("Common", "get_resource_path", f"filename={filename}")
|
|
84
|
+
path = resource_filename("onionshare_cli", os.path.join("resources", filename))
|
|
85
|
+
self.log("Common", "get_resource_path", f"filename={filename}, path={path}")
|
|
86
|
+
return path
|
|
87
|
+
|
|
88
|
+
def get_tor_paths(self):
|
|
89
|
+
if self.platform == "Linux":
|
|
90
|
+
tor_path = shutil.which("tor")
|
|
91
|
+
if not tor_path:
|
|
92
|
+
raise CannotFindTor()
|
|
93
|
+
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
|
94
|
+
prefix = os.path.dirname(os.path.dirname(tor_path))
|
|
95
|
+
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
|
96
|
+
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
|
97
|
+
elif self.platform == "Windows":
|
|
98
|
+
base_path = self.get_resource_path("tor")
|
|
99
|
+
tor_path = os.path.join(base_path, "Tor", "tor.exe")
|
|
100
|
+
obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe")
|
|
101
|
+
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
|
|
102
|
+
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
|
103
|
+
elif self.platform == "Darwin":
|
|
104
|
+
tor_path = shutil.which("tor")
|
|
105
|
+
if not tor_path:
|
|
106
|
+
raise CannotFindTor()
|
|
107
|
+
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
|
108
|
+
prefix = os.path.dirname(os.path.dirname(tor_path))
|
|
109
|
+
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
|
110
|
+
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
|
111
|
+
elif self.platform == "BSD":
|
|
112
|
+
tor_path = "/usr/local/bin/tor"
|
|
113
|
+
tor_geo_ip_file_path = "/usr/local/share/tor/geoip"
|
|
114
|
+
tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
|
|
115
|
+
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
tor_path,
|
|
119
|
+
tor_geo_ip_file_path,
|
|
120
|
+
tor_geo_ipv6_file_path,
|
|
121
|
+
obfs4proxy_file_path,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def build_data_dir(self):
|
|
125
|
+
"""
|
|
126
|
+
Returns the path of the OnionShare data directory.
|
|
127
|
+
"""
|
|
128
|
+
if self.platform == "Windows":
|
|
129
|
+
try:
|
|
130
|
+
appdata = os.environ["APPDATA"]
|
|
131
|
+
onionshare_data_dir = f"{appdata}\\OnionShare"
|
|
132
|
+
except:
|
|
133
|
+
# If for some reason we don't have the 'APPDATA' environment variable
|
|
134
|
+
# (like running tests in Linux while pretending to be in Windows)
|
|
135
|
+
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
|
136
|
+
elif self.platform == "Darwin":
|
|
137
|
+
onionshare_data_dir = os.path.expanduser(
|
|
138
|
+
"~/Library/Application Support/OnionShare"
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
|
142
|
+
|
|
143
|
+
# Modify the data dir if running tests
|
|
144
|
+
if getattr(sys, "onionshare_test_mode", False):
|
|
145
|
+
onionshare_data_dir += "-testdata"
|
|
146
|
+
|
|
147
|
+
os.makedirs(onionshare_data_dir, 0o700, True)
|
|
148
|
+
return onionshare_data_dir
|
|
149
|
+
|
|
150
|
+
def build_tmp_dir(self):
|
|
151
|
+
"""
|
|
152
|
+
Returns path to a folder that can hold temporary files
|
|
153
|
+
"""
|
|
154
|
+
tmp_dir = os.path.join(self.build_data_dir(), "tmp")
|
|
155
|
+
os.makedirs(tmp_dir, 0o700, True)
|
|
156
|
+
return tmp_dir
|
|
157
|
+
|
|
158
|
+
def build_persistent_dir(self):
|
|
159
|
+
"""
|
|
160
|
+
Returns the path to the folder that holds persistent files
|
|
161
|
+
"""
|
|
162
|
+
persistent_dir = os.path.join(self.build_data_dir(), "persistent")
|
|
163
|
+
os.makedirs(persistent_dir, 0o700, True)
|
|
164
|
+
return persistent_dir
|
|
165
|
+
|
|
166
|
+
def build_tor_dir(self):
|
|
167
|
+
"""
|
|
168
|
+
Returns path to the tor data directory
|
|
169
|
+
"""
|
|
170
|
+
tor_dir = os.path.join(self.build_data_dir(), "tor_data")
|
|
171
|
+
os.makedirs(tor_dir, 0o700, True)
|
|
172
|
+
return tor_dir
|
|
173
|
+
|
|
174
|
+
def build_password(self, word_count=2):
|
|
175
|
+
"""
|
|
176
|
+
Returns a random string made of words from the wordlist, such as "deter-trig".
|
|
177
|
+
"""
|
|
178
|
+
with open(self.get_resource_path("wordlist.txt")) as f:
|
|
179
|
+
wordlist = f.read().split()
|
|
180
|
+
|
|
181
|
+
r = random.SystemRandom()
|
|
182
|
+
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
|
183
|
+
|
|
184
|
+
def build_username(self, word_count=2):
|
|
185
|
+
"""
|
|
186
|
+
Returns a random string made of words from the wordlist, such as "deter-trig".
|
|
187
|
+
"""
|
|
188
|
+
with open(self.get_resource_path("wordlist.txt")) as f:
|
|
189
|
+
wordlist = f.read().split()
|
|
190
|
+
|
|
191
|
+
r = random.SystemRandom()
|
|
192
|
+
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def random_string(num_bytes, output_len=None):
|
|
196
|
+
"""
|
|
197
|
+
Returns a random string with a specified number of bytes.
|
|
198
|
+
"""
|
|
199
|
+
b = os.urandom(num_bytes)
|
|
200
|
+
h = hashlib.sha256(b).digest()[:16]
|
|
201
|
+
s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8")
|
|
202
|
+
if not output_len:
|
|
203
|
+
return s
|
|
204
|
+
return s[:output_len]
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def human_readable_filesize(b):
|
|
208
|
+
"""
|
|
209
|
+
Returns filesize in a human readable format.
|
|
210
|
+
"""
|
|
211
|
+
thresh = 1024.0
|
|
212
|
+
if b < thresh:
|
|
213
|
+
return "{:.1f} B".format(b)
|
|
214
|
+
units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
|
215
|
+
u = 0
|
|
216
|
+
b /= thresh
|
|
217
|
+
while b >= thresh:
|
|
218
|
+
b /= thresh
|
|
219
|
+
u += 1
|
|
220
|
+
return "{:.1f} {}".format(b, units[u])
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def format_seconds(seconds):
|
|
224
|
+
"""Return a human-readable string of the format 1d2h3m4s"""
|
|
225
|
+
days, seconds = divmod(seconds, 86400)
|
|
226
|
+
hours, seconds = divmod(seconds, 3600)
|
|
227
|
+
minutes, seconds = divmod(seconds, 60)
|
|
228
|
+
|
|
229
|
+
human_readable = []
|
|
230
|
+
if days:
|
|
231
|
+
human_readable.append("{:.0f}d".format(days))
|
|
232
|
+
if hours:
|
|
233
|
+
human_readable.append("{:.0f}h".format(hours))
|
|
234
|
+
if minutes:
|
|
235
|
+
human_readable.append("{:.0f}m".format(minutes))
|
|
236
|
+
if seconds or not human_readable:
|
|
237
|
+
human_readable.append("{:.0f}s".format(seconds))
|
|
238
|
+
return "".join(human_readable)
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def estimated_time_remaining(bytes_downloaded, total_bytes, started):
|
|
242
|
+
now = time.time()
|
|
243
|
+
time_elapsed = now - started # in seconds
|
|
244
|
+
download_rate = bytes_downloaded / time_elapsed
|
|
245
|
+
remaining_bytes = total_bytes - bytes_downloaded
|
|
246
|
+
eta = remaining_bytes / download_rate
|
|
247
|
+
return Common.format_seconds(eta)
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def get_available_port(min_port, max_port):
|
|
251
|
+
"""
|
|
252
|
+
Find a random available port within the given range.
|
|
253
|
+
"""
|
|
254
|
+
with socket.socket() as tmpsock:
|
|
255
|
+
while True:
|
|
256
|
+
try:
|
|
257
|
+
tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
|
|
258
|
+
break
|
|
259
|
+
except OSError as e:
|
|
260
|
+
pass
|
|
261
|
+
_, port = tmpsock.getsockname()
|
|
262
|
+
return port
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def dir_size(start_path):
|
|
266
|
+
"""
|
|
267
|
+
Calculates the total size, in bytes, of all of the files in a directory.
|
|
268
|
+
"""
|
|
269
|
+
total_size = 0
|
|
270
|
+
for dirpath, dirnames, filenames in os.walk(start_path):
|
|
271
|
+
for f in filenames:
|
|
272
|
+
fp = os.path.join(dirpath, f)
|
|
273
|
+
if not os.path.islink(fp):
|
|
274
|
+
total_size += os.path.getsize(fp)
|
|
275
|
+
return total_size
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class AutoStopTimer(threading.Thread):
|
|
279
|
+
"""
|
|
280
|
+
Background thread sleeps t hours and returns.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
def __init__(self, common, time):
|
|
284
|
+
threading.Thread.__init__(self)
|
|
285
|
+
|
|
286
|
+
self.common = common
|
|
287
|
+
|
|
288
|
+
self.setDaemon(True)
|
|
289
|
+
self.time = time
|
|
290
|
+
|
|
291
|
+
def run(self):
|
|
292
|
+
self.common.log(
|
|
293
|
+
"AutoStopTimer", f"Server will shut down after {self.time} seconds"
|
|
294
|
+
)
|
|
295
|
+
time.sleep(self.time)
|
|
296
|
+
return 1
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
OnionShare | https://onionshare.org/
|
|
4
|
+
|
|
5
|
+
Copyright (C) 2014-2020 Micah Lee, et al. <micah@micahflee.com>
|
|
6
|
+
|
|
7
|
+
This program is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
"""
|
|
20
|
+
import os
|
|
21
|
+
import json
|
|
22
|
+
import platform
|
|
23
|
+
|
|
24
|
+
if platform.system() == "Darwin":
|
|
25
|
+
import pwd
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ModeSettings:
|
|
29
|
+
"""
|
|
30
|
+
This stores the settings for a single instance of an OnionShare mode. In CLI there
|
|
31
|
+
is only one ModeSettings, and in the GUI there is a separate ModeSettings for each tab
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, common, filename=None, id=None):
|
|
35
|
+
self.common = common
|
|
36
|
+
|
|
37
|
+
self.default_settings = {
|
|
38
|
+
"onion": {
|
|
39
|
+
"private_key": None,
|
|
40
|
+
"hidservauth_string": None,
|
|
41
|
+
"password": None,
|
|
42
|
+
},
|
|
43
|
+
"persistent": {"mode": None, "enabled": False},
|
|
44
|
+
"general": {
|
|
45
|
+
"public": False,
|
|
46
|
+
"autostart_timer": False,
|
|
47
|
+
"autostop_timer": False,
|
|
48
|
+
"legacy": False,
|
|
49
|
+
"client_auth": False,
|
|
50
|
+
"service_id": None,
|
|
51
|
+
},
|
|
52
|
+
"share": {"autostop_sharing": True, "filenames": []},
|
|
53
|
+
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
|
54
|
+
"website": {"disable_csp": False, "filenames": []},
|
|
55
|
+
"chat": {"room": "default"},
|
|
56
|
+
}
|
|
57
|
+
self._settings = {}
|
|
58
|
+
|
|
59
|
+
self.just_created = False
|
|
60
|
+
if id:
|
|
61
|
+
self.id = id
|
|
62
|
+
else:
|
|
63
|
+
self.id = self.common.build_password(3)
|
|
64
|
+
|
|
65
|
+
self.load(filename)
|
|
66
|
+
|
|
67
|
+
def fill_in_defaults(self):
|
|
68
|
+
"""
|
|
69
|
+
If there are any missing settings from self._settings, replace them with
|
|
70
|
+
their default values.
|
|
71
|
+
"""
|
|
72
|
+
for key in self.default_settings:
|
|
73
|
+
if key in self._settings:
|
|
74
|
+
for inner_key in self.default_settings[key]:
|
|
75
|
+
if inner_key not in self._settings[key]:
|
|
76
|
+
self._settings[key][inner_key] = self.default_settings[key][
|
|
77
|
+
inner_key
|
|
78
|
+
]
|
|
79
|
+
else:
|
|
80
|
+
self._settings[key] = self.default_settings[key]
|
|
81
|
+
|
|
82
|
+
def get(self, group, key):
|
|
83
|
+
return self._settings[group][key]
|
|
84
|
+
|
|
85
|
+
def set(self, group, key, val):
|
|
86
|
+
self._settings[group][key] = val
|
|
87
|
+
self.common.log(
|
|
88
|
+
"ModeSettings", "set", f"updating {self.id}: {group}.{key} = {val}"
|
|
89
|
+
)
|
|
90
|
+
self.save()
|
|
91
|
+
|
|
92
|
+
def build_default_receive_data_dir(self):
|
|
93
|
+
"""
|
|
94
|
+
Returns the path of the default Downloads directory for receive mode.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
if self.common.platform == "Darwin":
|
|
98
|
+
# We can't use os.path.expanduser() in macOS because in the sandbox it
|
|
99
|
+
# returns the path to the sandboxed homedir
|
|
100
|
+
real_homedir = pwd.getpwuid(os.getuid()).pw_dir
|
|
101
|
+
return os.path.join(real_homedir, "OnionShare")
|
|
102
|
+
elif self.common.platform == "Windows":
|
|
103
|
+
# On Windows, os.path.expanduser() needs to use backslash, or else it
|
|
104
|
+
# retains the forward slash, which breaks opening the folder in explorer.
|
|
105
|
+
return os.path.expanduser("~\\OnionShare")
|
|
106
|
+
else:
|
|
107
|
+
# All other OSes
|
|
108
|
+
return os.path.expanduser("~/OnionShare")
|
|
109
|
+
|
|
110
|
+
def load(self, filename=None):
|
|
111
|
+
# Load persistent settings from disk. If the file doesn't exist, create it
|
|
112
|
+
if filename:
|
|
113
|
+
self.filename = filename
|
|
114
|
+
else:
|
|
115
|
+
self.filename = os.path.join(
|
|
116
|
+
self.common.build_persistent_dir(), f"{self.id}.json"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if os.path.exists(self.filename):
|
|
120
|
+
try:
|
|
121
|
+
with open(self.filename, "r") as f:
|
|
122
|
+
self._settings = json.load(f)
|
|
123
|
+
self.fill_in_defaults()
|
|
124
|
+
self.common.log("ModeSettings", "load", f"loaded {self.filename}")
|
|
125
|
+
return
|
|
126
|
+
except:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
# If loading settings didn't work, create the settings file
|
|
130
|
+
self.common.log("ModeSettings", "load", f"creating {self.filename}")
|
|
131
|
+
self.fill_in_defaults()
|
|
132
|
+
self.just_created = True
|
|
133
|
+
|
|
134
|
+
def save(self):
|
|
135
|
+
# Save persistent setting to disk
|
|
136
|
+
if not self.get("persistent", "enabled"):
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
if self.filename:
|
|
140
|
+
with open(self.filename, "w") as file:
|
|
141
|
+
file.write(json.dumps(self._settings, indent=2))
|
|
142
|
+
|
|
143
|
+
def delete(self):
|
|
144
|
+
# Delete the file from disk
|
|
145
|
+
if os.path.exists(self.filename):
|
|
146
|
+
os.remove(self.filename)
|