Unit3DwebUp 0.0.14__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 (53) hide show
  1. config/__init__.py +4 -0
  2. config/api_data.py +28 -0
  3. config/constants.py +34 -0
  4. config/host_data.py +47 -0
  5. config/itt.py +154 -0
  6. config/logger.py +37 -0
  7. config/settings.py +212 -0
  8. config/sis.py +137 -0
  9. config/tags.py +395 -0
  10. config/trackers.py +47 -0
  11. external/__init__.py +0 -0
  12. external/async_http_client_service.py +48 -0
  13. external/websocket.py +41 -0
  14. models/__init__.py +0 -0
  15. models/interfaces.py +39 -0
  16. models/keywords.py +13 -0
  17. models/media.py +597 -0
  18. models/media_info.py +142 -0
  19. models/movie.py +287 -0
  20. models/tv.py +266 -0
  21. models/tvdb_search.py +102 -0
  22. models/videos.py +26 -0
  23. repositories/__init__.py +0 -0
  24. repositories/db_online.py +82 -0
  25. repositories/interfaces.py +59 -0
  26. repositories/job_repos.py +166 -0
  27. repositories/media_info_factory.py +28 -0
  28. services/__init__.py +0 -0
  29. services/auto_async_service.py +237 -0
  30. services/create_torrent_service.py +94 -0
  31. services/interfaces.py +72 -0
  32. services/itt_tracker_helper.py +463 -0
  33. services/itt_tracker_service.py +85 -0
  34. services/lifespan_service.py +58 -0
  35. services/media_service.py +114 -0
  36. services/tags_service.py +389 -0
  37. services/tmdb.py +246 -0
  38. services/torrent_client_service.py +92 -0
  39. services/torrent_service.py +107 -0
  40. services/tvdb.py +65 -0
  41. services/utility.py +433 -0
  42. services/video_service.py +356 -0
  43. unit3dwebup-0.0.14.dist-info/METADATA +191 -0
  44. unit3dwebup-0.0.14.dist-info/RECORD +53 -0
  45. unit3dwebup-0.0.14.dist-info/WHEEL +5 -0
  46. unit3dwebup-0.0.14.dist-info/entry_points.txt +2 -0
  47. unit3dwebup-0.0.14.dist-info/top_level.txt +6 -0
  48. use_case/__init__.py +0 -0
  49. use_case/make_torrent_usecase.py +67 -0
  50. use_case/process_all_usecase.py +43 -0
  51. use_case/scan_media_usecase.py +133 -0
  52. use_case/seed_usecase.py +117 -0
  53. use_case/upload_usecase.py +77 -0
@@ -0,0 +1,463 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Based on the old code 0.8.21
3
+ import io
4
+ import asyncio
5
+ import aiohttp
6
+
7
+ from pathlib import Path
8
+ from urllib.parse import urljoin
9
+ from config.api_data import trackers_api_data
10
+
11
+
12
+ class Myhttp:
13
+ def __init__(
14
+ self,
15
+ tracker_name: str,
16
+ pass_key: str = "",
17
+ session: aiohttp.ClientSession | None = None,
18
+ ):
19
+ """
20
+ :param tracker_name: short name of the tracker
21
+ :param pass_key: not used
22
+ :param session: aiohttp.ClientSession | None
23
+ """
24
+
25
+ api_data = trackers_api_data.get(tracker_name.upper())
26
+ if not api_data:
27
+ raise ValueError(f"Tracker '{tracker_name}' not found")
28
+
29
+ self.session = session or aiohttp.ClientSession()
30
+ self.pass_key = pass_key
31
+
32
+ self.base_url = api_data["url"]
33
+ self.api_token = api_data["api_key"]
34
+
35
+ # Endpoints
36
+ self.upload_url = urljoin(str(self.base_url), "api/torrents/upload")
37
+ self.filter_url = urljoin(str(self.base_url), "api/torrents/filter")
38
+ self.fetch_url = urljoin(str(self.base_url), "api/torrents/")
39
+
40
+ self.headers = {
41
+ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
42
+ "Accept": "application/json",
43
+ }
44
+
45
+ self.base_params = {}
46
+
47
+ # default Payload
48
+ self.data = {
49
+ "name": "TEST.torrent",
50
+ "description": "", # mandatory
51
+ "mediainfo": "",
52
+ "bdinfo": "",
53
+ "type_id": 1,
54
+ "resolution_id": 10, # mandatory
55
+ "tmdb": 0, # mandatory
56
+ "imdb": 0,
57
+ "tvdb": 0,
58
+ "mal": 0, # no ancora implementato
59
+ "igdb": 0,
60
+ "anonymous": 0,
61
+ "sd": 0,
62
+ "keywords": "",
63
+ "personal_release": 0,
64
+ "internal": 0,
65
+ "featured": 0,
66
+ "free": 0,
67
+ "doubleup": 0,
68
+ "sticky": 0,
69
+ }
70
+
71
+ async def close(self):
72
+ await self.session.close()
73
+
74
+
75
+ class Tracker(Myhttp):
76
+ """
77
+ calls "low level"...
78
+ """
79
+
80
+ async def _get(self, params: dict):
81
+
82
+ # Build the params
83
+ final_params = {k: v for k, v in params.items() if v is not None}
84
+
85
+ # Set the _token_
86
+ headers = {
87
+ **self.headers,
88
+ "Authorization": f"Bearer {self.api_token}"
89
+ }
90
+
91
+ while True:
92
+ try:
93
+ async with self.session.get(self.filter_url, params=final_params, headers=headers,
94
+ timeout=aiohttp.ClientTimeout(total=15)) as resp:
95
+ if resp.status == 429:
96
+ await asyncio.sleep(60)
97
+ continue
98
+
99
+ resp.raise_for_status()
100
+ data = await resp.json()
101
+ return data
102
+
103
+ except aiohttp.ClientResponseError as e:
104
+ raise RuntimeError(f"Tracker HTTP error {e.status}") from e
105
+
106
+ except asyncio.TimeoutError as e:
107
+ raise TimeoutError("Tracker timeout") from e
108
+
109
+ async def _post(self, files: dict, data: dict) -> dict:
110
+ # Prepare the form
111
+ form = aiohttp.FormData()
112
+
113
+ # Fill the form
114
+ for k, v in data.items():
115
+ form.add_field(k, str(v))
116
+
117
+ # Prepare the field for the file
118
+ for k, (name, fp, ctype) in files.items():
119
+ form.add_field(k, fp, filename=name, content_type=ctype)
120
+
121
+ # Set the _token_
122
+ headers = {
123
+ **self.headers,
124
+ "Authorization": f"Bearer {self.api_token}"
125
+ }
126
+
127
+ # Send to the tracker timeout 30
128
+ async with self.session.post(self.upload_url, headers=headers, data=form,
129
+ timeout=aiohttp.ClientTimeout(total=30)) as resp:
130
+ return await resp.json()
131
+
132
+ # Main endpoints fetch all
133
+ async def _fetch_all(self, params: dict):
134
+ final_params = {**self.base_params, **params}
135
+ async with self.session.get(
136
+ self.fetch_url,
137
+ params=final_params,
138
+ headers=self.headers,
139
+ ) as resp:
140
+ resp.raise_for_status()
141
+ return await resp.json()
142
+
143
+ # Main endpoints fetch by id
144
+ async def _fetch_id(self, torrent_id: int):
145
+ async with self.session.get(
146
+ f"{self.fetch_url}{torrent_id}",
147
+ params=self.base_params,
148
+ headers=self.headers,
149
+ ) as resp:
150
+ resp.raise_for_status()
151
+ return await resp.json()
152
+
153
+ @staticmethod
154
+ def bool_to_str(value: bool) -> str:
155
+ return 'true' if value else 'false'
156
+
157
+
158
+ class filterAPI(Tracker):
159
+ """
160
+ Calls "high level"...
161
+ """
162
+
163
+ async def tmdb(self, tmdb_id: int, perPage: int = None):
164
+ return await self._get({"tmdbId": tmdb_id, "perPage": perPage})
165
+
166
+ async def imdb(self, imdb_id: int, perPage: int = None):
167
+ return await self._get({"imdbId": imdb_id, "perPage": perPage})
168
+
169
+ async def tvdb(self, tvdb_id: int, perPage: int = None):
170
+ return await self._get({"tvdbId": tvdb_id, "perPage": perPage})
171
+
172
+ async def mal(self, mal_id: int, perPage: int = None):
173
+ return await self._get({"malId": mal_id, "perPage": perPage})
174
+
175
+ async def playlist_id(self, playlistId: int, perPage: int = None):
176
+ return await self._get({"playlistId": playlistId, "perPage": perPage})
177
+
178
+ async def collection_id(self, collectionId: int, perPage: int = None):
179
+ return await self._get({"collectionId": collectionId, "perPage": perPage})
180
+
181
+ async def freeleech(self, freeleech: int, perPage: int = None):
182
+ return await self._get({"free": freeleech, "perPage": perPage})
183
+
184
+ async def name(self, name: str, perPage: int = None):
185
+ return await self._get({"name": name, "perPage": perPage})
186
+
187
+ async def description(self, description: str, perPage: int = None):
188
+ return await self._get({"description": description, "perPage": perPage})
189
+
190
+ async def mediainfo(self, mediainfo: str, perPage: int = None):
191
+ return await self._get({"mediainfo": mediainfo, "perPage": perPage})
192
+
193
+ async def bdinfo(self, bdinfo: str, perPage: int = None):
194
+ return await self._get({"bdinfo": bdinfo, "perPage": perPage})
195
+
196
+ async def start_year(self, start_year: str, perPage: int = None):
197
+ return await self._get({"startYear": start_year, "perPage": perPage})
198
+
199
+ async def end_year(self, end_year: str, perPage: int = None):
200
+ return await self._get({"endYear": end_year, "perPage": perPage})
201
+
202
+ async def uploader(self, uploader: str, perPage: int = None):
203
+ return await self._get({"uploader": uploader, "perPage": perPage})
204
+
205
+ async def alive(self, alive: int, perPage: int = None):
206
+ return await self._get({"alive": alive, "perPage": perPage})
207
+
208
+ async def dying(self, dying: int, perPage: int = None):
209
+ return await self._get({"dying": dying, "perPage": perPage})
210
+
211
+ async def dead(self, dead: int, perPage: int = None):
212
+ return await self._get({"dead": dead, "perPage": perPage})
213
+
214
+ async def file_name(self, file_name: str, perPage: int = None):
215
+ return await self._get({"file_name": file_name, "perPage": perPage})
216
+
217
+ async def seasonNumber(self, seasonNumber: int, perPage: int = None):
218
+ return await self._get({"seasonNumber": seasonNumber, "perPage": perPage})
219
+
220
+ async def episodeNumber(self, episodeNumber: int, perPage: int = None):
221
+ return await self._get({"episodeNumber": episodeNumber, "perPage": perPage})
222
+
223
+ async def types(self, type_id: str, perPage: int = None):
224
+ return await self._get({"types[]": type_id, "perPage": perPage})
225
+
226
+ async def resolution(self, res_id: str, perPage: int = None):
227
+ return await self._get({"resolutions[]": res_id, "perPage": perPage})
228
+
229
+ async def doubleup(self, double_up: int, perPage: int = None):
230
+ return await self._get({"doubleup": double_up, "perPage": perPage})
231
+
232
+ async def featured(self, featured: int, perPage: int = None):
233
+ return await self._get({"featured": featured, "perPage": perPage})
234
+
235
+ async def refundable(self, refundable: int, perPage: int = None):
236
+ return await self._get({"refundable": refundable, "perPage": perPage})
237
+
238
+ async def stream(self, stream: int, perPage: int = None):
239
+ return await self._get({"stream": stream, "perPage": perPage})
240
+
241
+ async def sd(self, sd: int, perPage: int = None):
242
+ return await self._get({"sd": sd, "perPage": perPage})
243
+
244
+ async def highspeed(self, high_speed: int, perPage: int = None):
245
+ return await self._get({"highspeed": high_speed, "perPage": perPage})
246
+
247
+ async def internal(self, internal: int, perPage: int = None):
248
+ return await self._get({"internal": internal, "perPage": perPage})
249
+
250
+ async def personal_release(self, personalRelease: int, perPage: int = None):
251
+ return await self._get({"personalRelease": personalRelease, "perPage": perPage})
252
+
253
+ async def tmdb_res(self, tmdb_id: int, res_id: str, perPage: int = None):
254
+ return await self._get({
255
+ "tmdbId": tmdb_id,
256
+ "resolutions[]": res_id,
257
+ "perPage": perPage,
258
+ })
259
+
260
+
261
+ class Torrents(Tracker):
262
+ """
263
+ TORRENTS
264
+ High level calls for fetch endpoints
265
+ """
266
+
267
+ async def torrents(self, perPage: int = None):
268
+ return await self._fetch_all({"perPage": perPage})
269
+
270
+ async def torrent(self, torrent_id: int):
271
+ return await self._fetch_id(torrent_id)
272
+
273
+
274
+ class Uploader(Tracker):
275
+ """
276
+ UPLOADER
277
+ Upload the torrent file *.torrent
278
+ Upload the nfo file
279
+ """
280
+
281
+ async def upload_t(self, data: dict, torrent_path: Path, nfo_path=None) -> dict | None:
282
+ files = {}
283
+
284
+ if Path(torrent_path).exists():
285
+ with open(torrent_path, "rb") as tf:
286
+ files["torrent"] = (
287
+ "upload.torrent",
288
+ tf,
289
+ "application/x-bittorrent",
290
+ )
291
+
292
+ if nfo_path:
293
+ with open(nfo_path, "rb") as nf:
294
+ files["nfo"] = ("file.nfo", nf, "text/plain")
295
+ return await self._post(files, data)
296
+
297
+ return await self._post(files, data)
298
+ return None
299
+
300
+ @staticmethod
301
+ def encode_utf8(file_path: str) -> bytes | io.BytesIO:
302
+ encodings = ["utf-8", "iso-8859-1", "windows-1252", "latin1"]
303
+ raw = open(file_path, "rb").read()
304
+
305
+ for enc in encodings:
306
+ try:
307
+ return raw.decode(enc).encode("utf-8")
308
+ except UnicodeDecodeError:
309
+ pass
310
+
311
+ return io.BytesIO(b"Error: Unable to decode NFO file")
312
+
313
+
314
+ class Unit3D(filterAPI, Torrents, Uploader):
315
+ """
316
+ UNIT3D - calls
317
+ """
318
+
319
+ async def get_tmdb(self, tmdb_id: int, perPage: int = None):
320
+ return await self.tmdb(tmdb_id=tmdb_id, perPage=perPage)
321
+
322
+ async def get_tvdb(self, tvdb_id: int, perPage: int = None):
323
+ return await self.tvdb(tvdb_id=tvdb_id, perPage=perPage)
324
+
325
+ async def get_imdb(self, imdb_id: int, perPage: int = None):
326
+ return await self.imdb(imdb_id=imdb_id, perPage=perPage)
327
+
328
+ async def get_igdb(self, igdb_id: int, perPage: int = None):
329
+ return await self.igdb(igdb_id=igdb_id, perPage=perPage)
330
+
331
+ async def get_mal(self, mal_id: int, perPage: int = None):
332
+ return await self.mal(mal_id=mal_id, perPage=perPage)
333
+
334
+ async def get_playlist_id(self, playlist_id: int, perPage: int = None):
335
+ return await self.playlist_id(
336
+ playlistId=playlist_id, perPage=perPage
337
+ )
338
+
339
+ async def get_collection_id(self, collection_id: int, perPage: int = None):
340
+ return await self.collection_id(
341
+ collectionId=collection_id, perPage=perPage
342
+ )
343
+
344
+ async def get_freeleech(self, freeleech: int, perPage: int = None):
345
+ return await self.freeleech(
346
+ freeleech=freeleech, perPage=perPage
347
+ )
348
+
349
+ async def get_name(self, name: str, perPage: int = None):
350
+ return await self.name(name=name, perPage=perPage)
351
+
352
+ async def get_description(self, description: str, perPage: int = None):
353
+ return await self.description(
354
+ description=description, perPage=perPage
355
+ )
356
+
357
+ async def get_bdinfo(self, bdinfo: str, perPage: int = None):
358
+ return await self.bdinfo(bdinfo=bdinfo, perPage=perPage)
359
+
360
+ async def get_mediainfo(self, mediainfo: str, perPage: int = None):
361
+ return await self.mediainfo(
362
+ mediainfo=mediainfo, perPage=perPage
363
+ )
364
+
365
+ async def get_uploader(self, uploader: str, perPage: int = None):
366
+ return await self.uploader(
367
+ uploader=uploader, perPage=perPage
368
+ )
369
+
370
+ async def after_start_year(self, start_year: str, perPage: int = None):
371
+ return await self.start_year(
372
+ start_year=start_year, perPage=perPage
373
+ )
374
+
375
+ async def before_end_year(self, end_year: str, perPage: int = None):
376
+ return await self.end_year(
377
+ end_year=end_year, perPage=perPage
378
+ )
379
+
380
+ async def get_alive(self, alive: int, perPage: int = None):
381
+ return await self.alive(alive=alive, perPage=perPage)
382
+
383
+ async def get_dying(self, dying: int, perPage: int = None):
384
+ return await self.dying(dying=dying, perPage=perPage)
385
+
386
+ async def get_dead(self, dead: int, perPage: int = None):
387
+ return await self.dead(dead=dead, perPage=perPage)
388
+
389
+ async def get_filename(self, file_name: str, perPage: int = None):
390
+ return await self.file_name(
391
+ file_name=file_name, perPage=perPage
392
+ )
393
+
394
+ async def get_season_number(self, se_number: int, perPage: int = None):
395
+ return await self.seasonNumber(
396
+ seasonNumber=se_number, perPage=perPage
397
+ )
398
+
399
+ async def get_episode_number(self, ep_number: int, perPage: int = None):
400
+ return await self.episodeNumber(
401
+ episodeNumber=ep_number, perPage=perPage
402
+ )
403
+
404
+ async def get_types(self, type_id: str, perPage: int = None):
405
+ if type_id:
406
+ return await self.types(type_id=type_id, perPage=perPage)
407
+ return None
408
+
409
+ async def get_res(self, res_id: str, perPage: int = None):
410
+ if res_id:
411
+ return await self.resolution(res_id=res_id, perPage=perPage)
412
+ return None
413
+
414
+ async def fetch_all(self, perPage: int = None):
415
+ return await self.torrents(perPage=perPage)
416
+
417
+ async def fetch_id(self, torrent_id: int):
418
+ return await self.torrent(torrent_id=torrent_id)
419
+
420
+ async def get_double_up(self, double_up: int, perPage: int = None):
421
+ return await self.doubleup(
422
+ double_up=double_up, perPage=perPage
423
+ )
424
+
425
+ async def get_featured(self, featured: int, perPage: int = None):
426
+ return await self.featured(
427
+ featured=featured, perPage=perPage
428
+ )
429
+
430
+ async def get_refundable(self, refundable: int, perPage: int = None):
431
+ return await self.refundable(
432
+ refundable=refundable, perPage=perPage
433
+ )
434
+
435
+ async def get_stream(self, stream: int, perPage: int = None):
436
+ return await self.stream(
437
+ stream=stream, perPage=perPage
438
+ )
439
+
440
+ async def get_sd(self, sd: int, perPage: int = None):
441
+ return await self.sd(sd=sd, perPage=perPage)
442
+
443
+ async def get_highspeed(self, highspeed: int, perPage: int = None):
444
+ return await self.highspeed(
445
+ high_speed=highspeed, perPage=perPage
446
+ )
447
+
448
+ async def get_internal(self, internal: int, perPage: int = None):
449
+ return await self.internal(
450
+ internal=internal, perPage=perPage
451
+ )
452
+
453
+ async def get_personal_release(self, personalRelease: int, perPage: int = None):
454
+ return await self.personal_release(
455
+ personalRelease=personalRelease, perPage=perPage
456
+ )
457
+
458
+ async def get_tmdb_res(self, tmdb_id: int, res_id: str, perPage: int = None):
459
+ if tmdb_id and res_id:
460
+ return await self.tmdb_res(
461
+ tmdb_id=tmdb_id, res_id=res_id, perPage=perPage
462
+ )
463
+ return None
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ from config.constants import MediaStatus
3
+ from config.trackers import TRACKData
4
+ from config.settings import get_settings
5
+
6
+ from services.interfaces import TrackerServiceInterface
7
+ from services.itt_tracker_helper import Unit3D
8
+ from models.media import Media
9
+
10
+ from fastapi import FastAPI
11
+ import aiohttp
12
+
13
+
14
+ class ITTtrackerService(TrackerServiceInterface):
15
+ """
16
+ a Class to interact with tracker
17
+ """
18
+
19
+ def __init__(self, session: aiohttp.ClientSession, app: FastAPI):
20
+ """
21
+ :param session: aiohttp.ClientSession
22
+ :param app: FastAPI
23
+ """
24
+ self.session = session
25
+ self.app = app
26
+ self.tracker_name = "ITT"
27
+ self.tracker_data = TRACKData.load_from_module(self.tracker_name)
28
+ self.tracker = Unit3D(tracker_name=self.tracker_name, session=session)
29
+
30
+ async def prepare_payload(self, media: Media) -> dict:
31
+ """
32
+ :param media: The Media object processed
33
+ :return:
34
+
35
+ The Media object processed helps to build the payload for the tracker
36
+ """
37
+ settings = get_settings()
38
+ return {
39
+ "name": media.display_name,
40
+ "tmdb": media.tmdb_id or 0,
41
+ "imdb": media.imdb_id_from_tvdb or 0,
42
+ "tvdb": media.tvdb_id if media.tvdb_id and media.category == 'series' else None,
43
+ "keywords": media.keyword,
44
+ "category_id": self.tracker_data.category.get(media.category),
45
+ "anonymous": int(settings.prefs.ANON),
46
+ "resolution_id": self.tracker_data.resolution.get(media.resolution),
47
+ "mediainfo": media.media_to_string,
48
+ "description": media.description,
49
+ "sd": media.is_hd,
50
+ "type_id": self.tracker_data.filter_type(media.file_name),
51
+ "season_number": media.guess_season or "",
52
+ "episode_number": media.guess_episode or "",
53
+ "personal_release": int(settings.prefs.PERSONAL_RELEASE)
54
+ }
55
+
56
+ async def upload(self, media: Media) -> dict:
57
+ """
58
+ :param media: Media object processed
59
+ :return:
60
+ """
61
+ # Payload ready
62
+ payload = await self.prepare_payload(media)
63
+
64
+ # Load the file and send to the tracker
65
+ response = await self.tracker.upload_t(data=payload, torrent_path=media.torrent_file_path)
66
+
67
+ # Wait for response
68
+ if not response:
69
+ media.status = MediaStatus.TRACKER_NOT_UPLOADED
70
+ return {'status': '404', 'message': 'Torrent file not found !', 'file': media.torrent_file_path,
71
+ 'job_id': media.job_id}
72
+
73
+ if response.get('success', None):
74
+ media.status = MediaStatus.TRACKER_UPLOADED
75
+ await self.app.state.job.update_job(job_id=media.job_id, new_data=media.to_dict())
76
+ return {'status': '200', 'message': 'Torrent uploaded', 'file': media.torrent_file_path,
77
+ 'job_id': media.job_id}
78
+ else:
79
+ media.status = MediaStatus.TRACKER_NOT_UPLOADED
80
+ await self.app.state.job.update_job(job_id=media.job_id, new_data=media.to_dict())
81
+ return {'status': '409', 'message': response.get('data', None),
82
+ 'file': media.torrent_file_path, 'job_id': media.job_id}
83
+
84
+ async def search(self, query: str) -> dict:
85
+ return await self.tracker.name(query)
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from fastapi import FastAPI
6
+ from config.settings import Settings
7
+ from config.logger import get_logger
8
+
9
+
10
+ async def update_mounted_paths(app: FastAPI):
11
+ """
12
+ Updates to mounted path strings are valid only when the backend is running on the host
13
+ When using docker ,docker must also be restarted
14
+ :param app: the FastAPI app
15
+ :return:
16
+ """
17
+ settings = Settings()
18
+ logger = get_logger(__name__)
19
+
20
+ # Switch path between dev and Docker
21
+ app.state.watcher_path = "/home/app/watcher" if os.getenv(
22
+ "DOCKER") == "true" else app.state.settings.prefs.WATCHER_PATH
23
+ app.state.watcher_destination_path = "/home/app/watcher_destination_path" if os.getenv(
24
+ "DOCKER") == "true" else app.state.settings.prefs.WATCHER_DESTINATION_PATH
25
+
26
+ app.state.scan_path = "/home/app/scan" if os.getenv("DOCKER") == "true" else app.state.settings.prefs.SCAN_PATH
27
+ torrent_archive_path = Path("/home/app/torrent_archive") if os.getenv("DOCKER") == "true" else Path(
28
+ settings.prefs.TORRENT_ARCHIVE_PATH)
29
+ app.state.torrent_archive_path = torrent_archive_path.expanduser().resolve()
30
+
31
+ # Env file mount: used to edit Env file from the frontend
32
+ app.state.env_file = "/home/app/config/.env" if os.getenv("DOCKER") == "true" else Path(".env")
33
+
34
+ # Main folder
35
+ logger.info("\nChecking Unit3DwebUp configuration file..\n")
36
+ logger.info(f"Unit3DwebUp : v.{settings.unit3DwebUp.VERSION}")
37
+ logger.info(f"Docker mode : '{os.getenv("DOCKER")}'")
38
+ logger.info(f"Env Path : '{app.state.env_file}'\n")
39
+
40
+ logger.info(f"Scan Path : '{app.state.scan_path}' <-> {app.state.settings.prefs.SCAN_PATH}")
41
+ logger.info(
42
+ f"Torrent Archive Path : '{app.state.torrent_archive_path}' <-> {app.state.settings.prefs.TORRENT_ARCHIVE_PATH}")
43
+ logger.info(f"Watcher Path : '{app.state.watcher_path}' <-> {app.state.settings.prefs.WATCHER_PATH}")
44
+ logger.info(
45
+ f"Watcher Dest. Path : '{app.state.watcher_destination_path}' <-> {app.state.settings.prefs.WATCHER_DESTINATION_PATH}\n")
46
+
47
+
48
+ async def checking_env_file(app: FastAPI):
49
+ settings = Settings()
50
+ logger = get_logger(__name__)
51
+
52
+ # Check configuration file (only once because get_settings() is cached)
53
+ for field_name, field in settings.tracker.model_fields.items():
54
+ # optional field (default=None)
55
+ if field.default is None:
56
+ value = getattr(settings.tracker, field_name)
57
+ if value is None:
58
+ logger.warning(f"{field_name} not set in .env – related feature may be disabled")