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.
Files changed (64) hide show
  1. qBitrr/__init__.py +14 -0
  2. qBitrr/arss.py +7100 -0
  3. qBitrr/auto_update.py +382 -0
  4. qBitrr/bundled_data.py +7 -0
  5. qBitrr/config.py +192 -0
  6. qBitrr/config_version.py +144 -0
  7. qBitrr/db_lock.py +400 -0
  8. qBitrr/db_recovery.py +202 -0
  9. qBitrr/env_config.py +73 -0
  10. qBitrr/errors.py +41 -0
  11. qBitrr/ffprobe.py +105 -0
  12. qBitrr/gen_config.py +1331 -0
  13. qBitrr/home_path.py +23 -0
  14. qBitrr/logger.py +235 -0
  15. qBitrr/main.py +790 -0
  16. qBitrr/search_activity_store.py +92 -0
  17. qBitrr/static/assets/ArrView.js +2 -0
  18. qBitrr/static/assets/ArrView.js.map +1 -0
  19. qBitrr/static/assets/ConfigView.js +4 -0
  20. qBitrr/static/assets/ConfigView.js.map +1 -0
  21. qBitrr/static/assets/LogsView.js +2 -0
  22. qBitrr/static/assets/LogsView.js.map +1 -0
  23. qBitrr/static/assets/ProcessesView.js +2 -0
  24. qBitrr/static/assets/ProcessesView.js.map +1 -0
  25. qBitrr/static/assets/app.css +1 -0
  26. qBitrr/static/assets/app.js +11 -0
  27. qBitrr/static/assets/app.js.map +1 -0
  28. qBitrr/static/assets/build.svg +3 -0
  29. qBitrr/static/assets/check-mark.svg +5 -0
  30. qBitrr/static/assets/close.svg +4 -0
  31. qBitrr/static/assets/download.svg +5 -0
  32. qBitrr/static/assets/gear.svg +5 -0
  33. qBitrr/static/assets/live-streaming.svg +8 -0
  34. qBitrr/static/assets/log.svg +3 -0
  35. qBitrr/static/assets/logo.svg +48 -0
  36. qBitrr/static/assets/plus.svg +4 -0
  37. qBitrr/static/assets/process.svg +15 -0
  38. qBitrr/static/assets/react-select.esm.js +7 -0
  39. qBitrr/static/assets/react-select.esm.js.map +1 -0
  40. qBitrr/static/assets/refresh-arrow.svg +3 -0
  41. qBitrr/static/assets/table.js +5 -0
  42. qBitrr/static/assets/table.js.map +1 -0
  43. qBitrr/static/assets/trash.svg +8 -0
  44. qBitrr/static/assets/up-arrow.svg +3 -0
  45. qBitrr/static/assets/useInterval.js +2 -0
  46. qBitrr/static/assets/useInterval.js.map +1 -0
  47. qBitrr/static/assets/vendor.js +2 -0
  48. qBitrr/static/assets/vendor.js.map +1 -0
  49. qBitrr/static/assets/visibility.svg +9 -0
  50. qBitrr/static/index.html +33 -0
  51. qBitrr/static/logov2-clean.svg +48 -0
  52. qBitrr/static/manifest.json +23 -0
  53. qBitrr/static/sw.js +87 -0
  54. qBitrr/static/vite.svg +1 -0
  55. qBitrr/tables.py +143 -0
  56. qBitrr/utils.py +274 -0
  57. qBitrr/versioning.py +136 -0
  58. qBitrr/webui.py +3114 -0
  59. qbitrr2-5.5.5.dist-info/METADATA +1191 -0
  60. qbitrr2-5.5.5.dist-info/RECORD +64 -0
  61. qbitrr2-5.5.5.dist-info/WHEEL +5 -0
  62. qbitrr2-5.5.5.dist-info/entry_points.txt +2 -0
  63. qbitrr2-5.5.5.dist-info/licenses/LICENSE +21 -0
  64. 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)