qBitrr2 5.5.5__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.
- qBitrr/__init__.py +14 -0
- qBitrr/arss.py +7100 -0
- qBitrr/auto_update.py +382 -0
- qBitrr/bundled_data.py +7 -0
- qBitrr/config.py +192 -0
- qBitrr/config_version.py +144 -0
- qBitrr/db_lock.py +400 -0
- qBitrr/db_recovery.py +202 -0
- qBitrr/env_config.py +73 -0
- qBitrr/errors.py +41 -0
- qBitrr/ffprobe.py +105 -0
- qBitrr/gen_config.py +1331 -0
- qBitrr/home_path.py +23 -0
- qBitrr/logger.py +235 -0
- qBitrr/main.py +790 -0
- qBitrr/search_activity_store.py +92 -0
- qBitrr/static/assets/ArrView.js +2 -0
- qBitrr/static/assets/ArrView.js.map +1 -0
- qBitrr/static/assets/ConfigView.js +4 -0
- qBitrr/static/assets/ConfigView.js.map +1 -0
- qBitrr/static/assets/LogsView.js +2 -0
- qBitrr/static/assets/LogsView.js.map +1 -0
- qBitrr/static/assets/ProcessesView.js +2 -0
- qBitrr/static/assets/ProcessesView.js.map +1 -0
- qBitrr/static/assets/app.css +1 -0
- qBitrr/static/assets/app.js +11 -0
- qBitrr/static/assets/app.js.map +1 -0
- qBitrr/static/assets/build.svg +3 -0
- qBitrr/static/assets/check-mark.svg +5 -0
- qBitrr/static/assets/close.svg +4 -0
- qBitrr/static/assets/download.svg +5 -0
- qBitrr/static/assets/gear.svg +5 -0
- qBitrr/static/assets/live-streaming.svg +8 -0
- qBitrr/static/assets/log.svg +3 -0
- qBitrr/static/assets/logo.svg +48 -0
- qBitrr/static/assets/plus.svg +4 -0
- qBitrr/static/assets/process.svg +15 -0
- qBitrr/static/assets/react-select.esm.js +7 -0
- qBitrr/static/assets/react-select.esm.js.map +1 -0
- qBitrr/static/assets/refresh-arrow.svg +3 -0
- qBitrr/static/assets/table.js +5 -0
- qBitrr/static/assets/table.js.map +1 -0
- qBitrr/static/assets/trash.svg +8 -0
- qBitrr/static/assets/up-arrow.svg +3 -0
- qBitrr/static/assets/useInterval.js +2 -0
- qBitrr/static/assets/useInterval.js.map +1 -0
- qBitrr/static/assets/vendor.js +2 -0
- qBitrr/static/assets/vendor.js.map +1 -0
- qBitrr/static/assets/visibility.svg +9 -0
- qBitrr/static/index.html +33 -0
- qBitrr/static/logov2-clean.svg +48 -0
- qBitrr/static/manifest.json +23 -0
- qBitrr/static/sw.js +87 -0
- qBitrr/static/vite.svg +1 -0
- qBitrr/tables.py +143 -0
- qBitrr/utils.py +274 -0
- qBitrr/versioning.py +136 -0
- qBitrr/webui.py +3114 -0
- qbitrr2-5.5.5.dist-info/METADATA +1191 -0
- qbitrr2-5.5.5.dist-info/RECORD +64 -0
- qbitrr2-5.5.5.dist-info/WHEEL +5 -0
- qbitrr2-5.5.5.dist-info/entry_points.txt +2 -0
- qbitrr2-5.5.5.dist-info/licenses/LICENSE +21 -0
- qbitrr2-5.5.5.dist-info/top_level.txt +1 -0
qBitrr/home_path.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
3
|
+
from jaraco.docker import is_docker
|
|
4
|
+
|
|
5
|
+
from qBitrr.env_config import ENVIRO_CONFIG
|
|
6
|
+
|
|
7
|
+
if (
|
|
8
|
+
ENVIRO_CONFIG.overrides.data_path is None
|
|
9
|
+
or not (p := pathlib.Path(ENVIRO_CONFIG.overrides.data_path)).exists()
|
|
10
|
+
):
|
|
11
|
+
if is_docker():
|
|
12
|
+
ON_DOCKER = True
|
|
13
|
+
HOME_PATH = pathlib.Path("/config")
|
|
14
|
+
else:
|
|
15
|
+
ON_DOCKER = False
|
|
16
|
+
HOME_PATH = pathlib.Path().absolute().joinpath(".config")
|
|
17
|
+
HOME_PATH.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
else:
|
|
19
|
+
HOME_PATH = p
|
|
20
|
+
|
|
21
|
+
APPDATA_FOLDER = HOME_PATH.joinpath("qBitManager")
|
|
22
|
+
APPDATA_FOLDER.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
APPDATA_FOLDER.chmod(mode=0o777)
|
qBitrr/logger.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import pathlib
|
|
5
|
+
import time
|
|
6
|
+
from logging import Logger
|
|
7
|
+
|
|
8
|
+
import coloredlogs
|
|
9
|
+
|
|
10
|
+
from qBitrr.config import (
|
|
11
|
+
AUTO_PAUSE_RESUME,
|
|
12
|
+
COMPLETED_DOWNLOAD_FOLDER,
|
|
13
|
+
CONFIG,
|
|
14
|
+
CONSOLE_LOGGING_LEVEL_STRING,
|
|
15
|
+
COPIED_TO_NEW_DIR,
|
|
16
|
+
ENABLE_LOGS,
|
|
17
|
+
FAILED_CATEGORY,
|
|
18
|
+
FREE_SPACE,
|
|
19
|
+
HOME_PATH,
|
|
20
|
+
IGNORE_TORRENTS_YOUNGER_THAN,
|
|
21
|
+
LOOP_SLEEP_TIMER,
|
|
22
|
+
NO_INTERNET_SLEEP_TIMER,
|
|
23
|
+
PING_URLS,
|
|
24
|
+
RECHECK_CATEGORY,
|
|
25
|
+
SEARCH_LOOP_DELAY,
|
|
26
|
+
TAGLESS,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = ("run_logs",)
|
|
30
|
+
|
|
31
|
+
TRACE = 5
|
|
32
|
+
VERBOSE = 7
|
|
33
|
+
NOTICE = 23
|
|
34
|
+
HNOTICE = 24
|
|
35
|
+
SUCCESS = 25
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class VerboseLogger(Logger):
|
|
39
|
+
def __init__(self, *args, **kwargs):
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
if self.name.startswith("qBitrr"):
|
|
42
|
+
self.set_config_level()
|
|
43
|
+
|
|
44
|
+
def success(self, message, *args, **kwargs):
|
|
45
|
+
if self.isEnabledFor(SUCCESS):
|
|
46
|
+
self._log(SUCCESS, message, args, **kwargs)
|
|
47
|
+
|
|
48
|
+
def hnotice(self, message, *args, **kwargs):
|
|
49
|
+
if self.isEnabledFor(HNOTICE):
|
|
50
|
+
self._log(HNOTICE, message, args, **kwargs)
|
|
51
|
+
|
|
52
|
+
def notice(self, message, *args, **kwargs):
|
|
53
|
+
if self.isEnabledFor(NOTICE):
|
|
54
|
+
self._log(NOTICE, message, args, **kwargs)
|
|
55
|
+
|
|
56
|
+
def verbose(self, message, *args, **kwargs):
|
|
57
|
+
if self.isEnabledFor(VERBOSE):
|
|
58
|
+
self._log(VERBOSE, message, args, **kwargs)
|
|
59
|
+
|
|
60
|
+
def trace(self, message, *args, **kwargs):
|
|
61
|
+
if self.isEnabledFor(TRACE):
|
|
62
|
+
self._log(TRACE, message, args, **kwargs)
|
|
63
|
+
|
|
64
|
+
def set_config_level(self):
|
|
65
|
+
self.setLevel(CONSOLE_LOGGING_LEVEL_STRING)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
logging.addLevelName(SUCCESS, "SUCCESS")
|
|
69
|
+
logging.addLevelName(HNOTICE, "HNOTICE")
|
|
70
|
+
logging.addLevelName(NOTICE, "NOTICE")
|
|
71
|
+
logging.addLevelName(VERBOSE, "VERBOSE")
|
|
72
|
+
logging.addLevelName(TRACE, "TRACE")
|
|
73
|
+
logging.setLoggerClass(VerboseLogger)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def getLogger(name: str | None = None):
|
|
77
|
+
return VerboseLogger.manager.getLogger(name) if name else logging.root
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
logging.getLogger = getLogger
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
logger = logging.getLogger("qBitrr.Misc")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
HAS_RUN = False
|
|
87
|
+
ALL_LOGS_HANDLER = None # Global handler for unified All.log file
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run_logs(logger: Logger, _name: str = None) -> None:
|
|
91
|
+
global HAS_RUN, ALL_LOGS_HANDLER
|
|
92
|
+
try:
|
|
93
|
+
configkeys = {f"qBitrr.{i}" for i in CONFIG.sections()}
|
|
94
|
+
key_length = max(len(max(configkeys, key=len)), 10)
|
|
95
|
+
except BaseException:
|
|
96
|
+
key_length = 10
|
|
97
|
+
coloredlogs.install(
|
|
98
|
+
logger=logger,
|
|
99
|
+
level=logging._nameToLevel.get(CONSOLE_LOGGING_LEVEL_STRING),
|
|
100
|
+
fmt="[%(asctime)-15s] [pid:%(process)8d][tid:%(thread)8d] "
|
|
101
|
+
f"%(levelname)-8s: %(name)-{key_length}s: %(message)s",
|
|
102
|
+
level_styles={
|
|
103
|
+
"trace": {"color": "black", "bold": True},
|
|
104
|
+
"debug": {"color": "magenta", "bold": True},
|
|
105
|
+
"verbose": {"color": "blue", "bold": True},
|
|
106
|
+
"info": {"color": "white"},
|
|
107
|
+
"notice": {"color": "cyan"},
|
|
108
|
+
"hnotice": {"color": "cyan", "bold": True},
|
|
109
|
+
"warning": {"color": "yellow", "bold": True},
|
|
110
|
+
"success": {"color": "green", "bold": True},
|
|
111
|
+
"error": {"color": "red"},
|
|
112
|
+
"critical": {"color": "red", "bold": True},
|
|
113
|
+
},
|
|
114
|
+
field_styles={
|
|
115
|
+
"asctime": {"color": "green"},
|
|
116
|
+
"process": {"color": "magenta"},
|
|
117
|
+
"levelname": {"color": "red", "bold": True},
|
|
118
|
+
"name": {"color": "blue", "bold": True},
|
|
119
|
+
"thread": {"color": "cyan"},
|
|
120
|
+
},
|
|
121
|
+
reconfigure=True,
|
|
122
|
+
)
|
|
123
|
+
logger.propagate = False
|
|
124
|
+
if ENABLE_LOGS:
|
|
125
|
+
# Initialize unified All.log handler once (first time run_logs is called)
|
|
126
|
+
if ALL_LOGS_HANDLER is None:
|
|
127
|
+
logs_folder = HOME_PATH.joinpath("logs")
|
|
128
|
+
logs_folder.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
logs_folder.chmod(mode=0o777)
|
|
130
|
+
all_logfile = logs_folder.joinpath("All.log")
|
|
131
|
+
# Rotate old All.log if it exists
|
|
132
|
+
if all_logfile.exists():
|
|
133
|
+
all_logold = logs_folder.joinpath("All.log.old")
|
|
134
|
+
if all_logold.exists():
|
|
135
|
+
all_logold.unlink()
|
|
136
|
+
all_logfile.rename(all_logold)
|
|
137
|
+
# Create handler for All.log that all loggers will use
|
|
138
|
+
ALL_LOGS_HANDLER = logging.FileHandler(all_logfile)
|
|
139
|
+
ALL_LOGS_HANDLER.setFormatter(
|
|
140
|
+
coloredlogs.ColoredFormatter(
|
|
141
|
+
fmt="[%(asctime)-15s] " f"%(levelname)-8s: %(name)-{key_length}s: %(message)s",
|
|
142
|
+
level_styles={
|
|
143
|
+
"trace": {"color": "black", "bold": True},
|
|
144
|
+
"debug": {"color": "magenta", "bold": True},
|
|
145
|
+
"verbose": {"color": "blue", "bold": True},
|
|
146
|
+
"info": {"color": "white"},
|
|
147
|
+
"notice": {"color": "cyan"},
|
|
148
|
+
"hnotice": {"color": "cyan", "bold": True},
|
|
149
|
+
"warning": {"color": "yellow", "bold": True},
|
|
150
|
+
"success": {"color": "green", "bold": True},
|
|
151
|
+
"error": {"color": "red"},
|
|
152
|
+
"critical": {"color": "red", "bold": True},
|
|
153
|
+
},
|
|
154
|
+
field_styles={
|
|
155
|
+
"asctime": {"color": "green"},
|
|
156
|
+
"process": {"color": "magenta"},
|
|
157
|
+
"levelname": {"color": "red", "bold": True},
|
|
158
|
+
"name": {"color": "blue", "bold": True},
|
|
159
|
+
"thread": {"color": "cyan"},
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Add All.log handler to this logger (since propagate=False, we can't use root)
|
|
165
|
+
logger.addHandler(ALL_LOGS_HANDLER)
|
|
166
|
+
|
|
167
|
+
# Add individual component log file handler
|
|
168
|
+
if _name:
|
|
169
|
+
logs_folder = HOME_PATH.joinpath("logs")
|
|
170
|
+
logs_folder.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
logs_folder.chmod(mode=0o777)
|
|
172
|
+
logfile = logs_folder.joinpath(_name + ".log")
|
|
173
|
+
if pathlib.Path(logfile).is_file():
|
|
174
|
+
logold = logs_folder.joinpath(_name + ".log.old")
|
|
175
|
+
if pathlib.Path(logold).exists():
|
|
176
|
+
logold.unlink()
|
|
177
|
+
logfile.rename(logold)
|
|
178
|
+
fh = logging.FileHandler(logfile)
|
|
179
|
+
# Use ColoredFormatter for file output to include ANSI colors in log files
|
|
180
|
+
fh.setFormatter(
|
|
181
|
+
coloredlogs.ColoredFormatter(
|
|
182
|
+
fmt="[%(asctime)-15s] " f"%(levelname)-8s: %(name)-{key_length}s: %(message)s",
|
|
183
|
+
level_styles={
|
|
184
|
+
"trace": {"color": "black", "bold": True},
|
|
185
|
+
"debug": {"color": "magenta", "bold": True},
|
|
186
|
+
"verbose": {"color": "blue", "bold": True},
|
|
187
|
+
"info": {"color": "white"},
|
|
188
|
+
"notice": {"color": "cyan"},
|
|
189
|
+
"hnotice": {"color": "cyan", "bold": True},
|
|
190
|
+
"warning": {"color": "yellow", "bold": True},
|
|
191
|
+
"success": {"color": "green", "bold": True},
|
|
192
|
+
"error": {"color": "red"},
|
|
193
|
+
"critical": {"color": "red", "bold": True},
|
|
194
|
+
},
|
|
195
|
+
field_styles={
|
|
196
|
+
"asctime": {"color": "green"},
|
|
197
|
+
"process": {"color": "magenta"},
|
|
198
|
+
"levelname": {"color": "red", "bold": True},
|
|
199
|
+
"name": {"color": "blue", "bold": True},
|
|
200
|
+
"thread": {"color": "cyan"},
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
logger.addHandler(fh)
|
|
205
|
+
if HAS_RUN is False:
|
|
206
|
+
HAS_RUN = True
|
|
207
|
+
log_debugs(logger)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def log_debugs(logger):
|
|
211
|
+
logger.debug("Log Level: %s", CONSOLE_LOGGING_LEVEL_STRING)
|
|
212
|
+
logger.debug("Ping URLs: %s", PING_URLS)
|
|
213
|
+
logger.debug("Script Config: Logging=%s", ENABLE_LOGS)
|
|
214
|
+
logger.debug("Script Config: FailedCategory=%s", FAILED_CATEGORY)
|
|
215
|
+
logger.debug("Script Config: RecheckCategory=%s", RECHECK_CATEGORY)
|
|
216
|
+
logger.debug("Script Config: Tagless=%s", TAGLESS)
|
|
217
|
+
logger.debug("Script Config: CompletedDownloadFolder=%s", COMPLETED_DOWNLOAD_FOLDER)
|
|
218
|
+
logger.debug("Script Config: FreeSpace=%s", FREE_SPACE)
|
|
219
|
+
logger.debug("Script Config: LoopSleepTimer=%s", LOOP_SLEEP_TIMER)
|
|
220
|
+
logger.debug("Script Config: SearchLoopDelay=%s", SEARCH_LOOP_DELAY)
|
|
221
|
+
logger.debug("Script Config: AutoPauseResume=%s", AUTO_PAUSE_RESUME)
|
|
222
|
+
logger.debug("Script Config: NoInternetSleepTimer=%s", NO_INTERNET_SLEEP_TIMER)
|
|
223
|
+
logger.debug("Script Config: IgnoreTorrentsYoungerThan=%s", IGNORE_TORRENTS_YOUNGER_THAN)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if COPIED_TO_NEW_DIR is False and not HOME_PATH.joinpath("config.toml").exists():
|
|
227
|
+
logger.warning(
|
|
228
|
+
"Config.toml should exist in '%s', in a future update this will be a requirement.",
|
|
229
|
+
HOME_PATH,
|
|
230
|
+
)
|
|
231
|
+
time.sleep(5)
|
|
232
|
+
if COPIED_TO_NEW_DIR:
|
|
233
|
+
logger.warning("Config.toml new location is %s", HOME_PATH)
|
|
234
|
+
time.sleep(5)
|
|
235
|
+
run_logs(logger)
|