Unit3DwebUp 0.0.25__tar.gz → 0.0.26__tar.gz
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.
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/PKG-INFO +1 -1
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/Unit3DwebUp.egg-info/PKG-INFO +1 -1
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/Unit3DwebUp.egg-info/SOURCES.txt +11 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/pyproject.toml +1 -1
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/__init__.py +1 -1
- unit3dwebup-0.0.26/unit3dwup/lifespan.py +154 -0
- {unit3dwebup-0.0.25/unit3dwup/services → unit3dwebup-0.0.26/unit3dwup/routers}/__init__.py +0 -0
- unit3dwebup-0.0.26/unit3dwup/routers/jobs.py +54 -0
- unit3dwebup-0.0.26/unit3dwup/routers/posters.py +110 -0
- unit3dwebup-0.0.26/unit3dwup/routers/process.py +90 -0
- unit3dwebup-0.0.26/unit3dwup/routers/scan.py +146 -0
- unit3dwebup-0.0.26/unit3dwup/routers/search.py +47 -0
- unit3dwebup-0.0.26/unit3dwup/routers/settings.py +151 -0
- unit3dwebup-0.0.26/unit3dwup/routers/ws.py +42 -0
- unit3dwebup-0.0.26/unit3dwup/schemas.py +33 -0
- unit3dwebup-0.0.26/unit3dwup/services/__init__.py +0 -0
- unit3dwebup-0.0.26/unit3dwup/start.py +37 -0
- unit3dwebup-0.0.25/unit3dwup/start.py → unit3dwebup-0.0.26/unit3dwup/start_backup.py +47 -14
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/README.md +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/Unit3DwebUp.egg-info/dependency_links.txt +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/Unit3DwebUp.egg-info/entry_points.txt +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/Unit3DwebUp.egg-info/requires.txt +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/Unit3DwebUp.egg-info/top_level.txt +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/setup.cfg +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/__init__.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/api_data.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/constants.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/host_data.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/itt.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/logger.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/settings.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/sis.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/tags.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/config/trackers.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/external/__init__.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/external/async_http_client_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/external/websocket.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/__init__.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/interfaces.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/keywords.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/media.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/media_info.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/movie.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/subtitles.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/tv.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/tvdb_search.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/models/videos.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/repositories/__init__.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/repositories/db_online.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/repositories/interfaces.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/repositories/job_repos.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/repositories/media_info_factory.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/auto_async_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/create_torrent_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/interfaces.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/itt_tracker_helper.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/itt_tracker_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/lifespan_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/media_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/tags_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/tmdb.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/torrent_client_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/torrent_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/tvdb.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/utility.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/services/video_service.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/use_case/__init__.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/use_case/make_torrent_usecase.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/use_case/process_all_usecase.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/use_case/scan_media_usecase.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/use_case/seed_usecase.py +0 -0
- {unit3dwebup-0.0.25 → unit3dwebup-0.0.26}/unit3dwup/use_case/upload_usecase.py +0 -0
|
@@ -7,7 +7,10 @@ Unit3DwebUp.egg-info/entry_points.txt
|
|
|
7
7
|
Unit3DwebUp.egg-info/requires.txt
|
|
8
8
|
Unit3DwebUp.egg-info/top_level.txt
|
|
9
9
|
unit3dwup/__init__.py
|
|
10
|
+
unit3dwup/lifespan.py
|
|
11
|
+
unit3dwup/schemas.py
|
|
10
12
|
unit3dwup/start.py
|
|
13
|
+
unit3dwup/start_backup.py
|
|
11
14
|
unit3dwup/config/__init__.py
|
|
12
15
|
unit3dwup/config/api_data.py
|
|
13
16
|
unit3dwup/config/constants.py
|
|
@@ -36,6 +39,14 @@ unit3dwup/repositories/db_online.py
|
|
|
36
39
|
unit3dwup/repositories/interfaces.py
|
|
37
40
|
unit3dwup/repositories/job_repos.py
|
|
38
41
|
unit3dwup/repositories/media_info_factory.py
|
|
42
|
+
unit3dwup/routers/__init__.py
|
|
43
|
+
unit3dwup/routers/jobs.py
|
|
44
|
+
unit3dwup/routers/posters.py
|
|
45
|
+
unit3dwup/routers/process.py
|
|
46
|
+
unit3dwup/routers/scan.py
|
|
47
|
+
unit3dwup/routers/search.py
|
|
48
|
+
unit3dwup/routers/settings.py
|
|
49
|
+
unit3dwup/routers/ws.py
|
|
39
50
|
unit3dwup/services/__init__.py
|
|
40
51
|
unit3dwup/services/auto_async_service.py
|
|
41
52
|
unit3dwup/services/create_torrent_service.py
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from unit3dwup.config import get_settings
|
|
9
|
+
from unit3dwup.config import get_logger
|
|
10
|
+
|
|
11
|
+
from unit3dwup.repositories.job_repos import JobRedisRepo
|
|
12
|
+
|
|
13
|
+
from unit3dwup.services.lifespan_service import update_mounted_paths, checking_env_file
|
|
14
|
+
|
|
15
|
+
from unit3dwup.external.websocket import WebSocketManager
|
|
16
|
+
|
|
17
|
+
from fastapi import FastAPI
|
|
18
|
+
|
|
19
|
+
from watchdog.observers import Observer
|
|
20
|
+
from watchdog.events import FileSystemEventHandler
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RedisEventHandler(FileSystemEventHandler):
|
|
24
|
+
"""
|
|
25
|
+
Watch only a specific folder
|
|
26
|
+
This event is shared by app.state FastApi
|
|
27
|
+
it uses a queue 'redis_event'
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, app):
|
|
31
|
+
self.app = app
|
|
32
|
+
|
|
33
|
+
def on_created(self, event):
|
|
34
|
+
self.app.state.redis_events.put_nowait({
|
|
35
|
+
"type": "created",
|
|
36
|
+
"path": event.src_path,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
def on_deleted(self, event):
|
|
40
|
+
self.app.state.redis_events.put_nowait({
|
|
41
|
+
"type": "deleted",
|
|
42
|
+
"path": event.src_path,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def redis_event_consumer(app: FastAPI):
|
|
47
|
+
"""
|
|
48
|
+
:param app: it is mr FastApi
|
|
49
|
+
:return: None
|
|
50
|
+
|
|
51
|
+
It is a consumer. Wait for any news from the queue and extract the new path created or deleted
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
# > The queue :|
|
|
55
|
+
queue = app.state.redis_events
|
|
56
|
+
|
|
57
|
+
# Logger
|
|
58
|
+
logger = get_logger("settings_logger")
|
|
59
|
+
|
|
60
|
+
while True:
|
|
61
|
+
event = await queue.get()
|
|
62
|
+
try:
|
|
63
|
+
app.state.folder_event = event
|
|
64
|
+
relative = Path(app.state.folder_event['path']).relative_to(Path(app.state.watcher_path))
|
|
65
|
+
new_path = Path(app.state.settings.prefs.WATCHER_PATH) / relative.parts[0]
|
|
66
|
+
|
|
67
|
+
# Send logs to the client
|
|
68
|
+
await app.state.ws_manager.broadcast({
|
|
69
|
+
"type": "log",
|
|
70
|
+
"level": "info",
|
|
71
|
+
"message": f"{app.state.folder_event['type']} {new_path}",
|
|
72
|
+
})
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.debug("Consumer Folder error", e)
|
|
75
|
+
finally:
|
|
76
|
+
queue.task_done()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@asynccontextmanager
|
|
80
|
+
async def lifespan(app: FastAPI):
|
|
81
|
+
"""
|
|
82
|
+
Lifespan initialize DB and app configuration
|
|
83
|
+
We need to shared state
|
|
84
|
+
https://fastapi.tiangolo.com/advanced/events/
|
|
85
|
+
|
|
86
|
+
:param app: FastAPI
|
|
87
|
+
:return: None
|
|
88
|
+
"""
|
|
89
|
+
# Load the configuration file
|
|
90
|
+
settings = get_settings()
|
|
91
|
+
app.state.settings = settings
|
|
92
|
+
|
|
93
|
+
# Check configuration file
|
|
94
|
+
await checking_env_file(app=app)
|
|
95
|
+
|
|
96
|
+
# Create a new state for the watcher queue
|
|
97
|
+
app.state.redis_events = asyncio.Queue()
|
|
98
|
+
|
|
99
|
+
# Environment variabile in the container backend
|
|
100
|
+
redis_host = os.getenv("REDIS_HOST", "localhost")
|
|
101
|
+
redis_port = int(os.getenv("REDIS_PORT", 6379))
|
|
102
|
+
|
|
103
|
+
# Build redis url
|
|
104
|
+
REDIS_URL = f"redis://{redis_host}:{redis_port}"
|
|
105
|
+
|
|
106
|
+
# Connect to redis
|
|
107
|
+
job = JobRedisRepo(url=REDIS_URL)
|
|
108
|
+
await job.connect(app=app)
|
|
109
|
+
|
|
110
|
+
# Store the job reference to app.state
|
|
111
|
+
app.state.job = job
|
|
112
|
+
|
|
113
|
+
# RestartDocker notify
|
|
114
|
+
# Set flag to true when setEnv is called from the frontend
|
|
115
|
+
app.state.restart_docker = False
|
|
116
|
+
|
|
117
|
+
# Create a new profile from user_preferences Job_id is '0'
|
|
118
|
+
# Later will be recalled from the setting endpoint
|
|
119
|
+
await job.create_profile(dict(settings.prefs))
|
|
120
|
+
|
|
121
|
+
# The WebSocket. Send to client progress bar value( Torrent creation) and short log message
|
|
122
|
+
app.state.ws_manager = WebSocketManager()
|
|
123
|
+
|
|
124
|
+
# Update mounted paths string
|
|
125
|
+
await update_mounted_paths(app=app)
|
|
126
|
+
|
|
127
|
+
# Watcher zone
|
|
128
|
+
# Shared event
|
|
129
|
+
app.state.folder_event = None
|
|
130
|
+
|
|
131
|
+
# back to watcher
|
|
132
|
+
observer = Observer()
|
|
133
|
+
|
|
134
|
+
# Callback
|
|
135
|
+
handler = RedisEventHandler(app)
|
|
136
|
+
|
|
137
|
+
# Start to watch
|
|
138
|
+
observer.schedule(handler, app.state.watcher_path, recursive=True)
|
|
139
|
+
if app.state.settings.prefs.WATCHER_DESTINATION_PATH != os.getcwd():
|
|
140
|
+
observer.start()
|
|
141
|
+
|
|
142
|
+
# Create a consumer that works in the background
|
|
143
|
+
consumer_task = asyncio.create_task(redis_event_consumer(app))
|
|
144
|
+
|
|
145
|
+
# Goes..
|
|
146
|
+
yield
|
|
147
|
+
|
|
148
|
+
# Come back to clean and close
|
|
149
|
+
if app.state.settings.prefs.WATCHER_DESTINATION_PATH != os.getcwd():
|
|
150
|
+
observer.stop()
|
|
151
|
+
observer.join()
|
|
152
|
+
|
|
153
|
+
consumer_task.cancel()
|
|
154
|
+
await job.close()
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Request
|
|
6
|
+
|
|
7
|
+
from unit3dwup.config import get_logger
|
|
8
|
+
|
|
9
|
+
from unit3dwup.schemas import ClearJobListRequest
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.post("/cjoblist")
|
|
15
|
+
async def clear_job_list_id(payload: ClearJobListRequest, request: Request):
|
|
16
|
+
"""
|
|
17
|
+
This endpoint deletes a job list and all related posters
|
|
18
|
+
|
|
19
|
+
Required:
|
|
20
|
+
- job_list_id: identifier of the job list
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
- status: operation result
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
app = request.app
|
|
27
|
+
|
|
28
|
+
# Load the job list
|
|
29
|
+
# Carica la joblist per ottenere il percorso e inviarlo a log. Mi sembra troppo per una stampa
|
|
30
|
+
job_list = await app.state.job.get_job_list(job_id=payload.job_list_id)
|
|
31
|
+
results = [json.loads(await app.state.job.get_job(job_id)) for job_id in job_list]
|
|
32
|
+
|
|
33
|
+
# Logger
|
|
34
|
+
frame = inspect.currentframe()
|
|
35
|
+
logger = get_logger(frame.f_code.co_name)
|
|
36
|
+
|
|
37
|
+
# Delete the job list
|
|
38
|
+
# TODO: delete all job ids
|
|
39
|
+
await app.state.job.delete_job_list(job_id=payload.job_list_id)
|
|
40
|
+
|
|
41
|
+
if results:
|
|
42
|
+
await app.state.ws_manager.broadcast({
|
|
43
|
+
"type": "log",
|
|
44
|
+
"level": "success",
|
|
45
|
+
"message": f"Clear JobList {payload.job_list_id} {results[0]['folder']}",
|
|
46
|
+
})
|
|
47
|
+
logger.info(f"-> Clear JobList ID° {payload.job_list_id} {results[0]['folder']}\n")
|
|
48
|
+
else:
|
|
49
|
+
await app.state.ws_manager.broadcast({
|
|
50
|
+
"type": "log",
|
|
51
|
+
"level": "error",
|
|
52
|
+
"message": f"Clear JobList : JobList id not found",
|
|
53
|
+
})
|
|
54
|
+
logger.info(f"-> Clear JobList : JobList ID non trovato\n")
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Request
|
|
5
|
+
|
|
6
|
+
from unit3dwup.config import MediaStatus
|
|
7
|
+
from unit3dwup.config import get_logger
|
|
8
|
+
|
|
9
|
+
from unit3dwup.schemas import UpdatePosterRequest
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def update_poster(app, msg: str, job_id: str, field_id: str, new_id: str):
|
|
15
|
+
# Fix the DB ID with the received one
|
|
16
|
+
await app.state.job.update_job(job_id=job_id, new_data={field_id: new_id})
|
|
17
|
+
|
|
18
|
+
# Update the Media status
|
|
19
|
+
await app.state.job.update_job(job_id=job_id, new_data={'status': str(MediaStatus.DB_IDENTIFIED)})
|
|
20
|
+
|
|
21
|
+
# Logger
|
|
22
|
+
frame = inspect.currentframe()
|
|
23
|
+
logger = get_logger(frame.f_code.co_name)
|
|
24
|
+
|
|
25
|
+
# Console message
|
|
26
|
+
logger.info(f"-> Update {msg} JOB_ID: {job_id}\n")
|
|
27
|
+
|
|
28
|
+
# Send log to the client
|
|
29
|
+
await app.state.ws_manager.broadcast({
|
|
30
|
+
"type": "log",
|
|
31
|
+
"level": "success",
|
|
32
|
+
"message": f"Update {msg} JOB_ID {job_id}",
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@router.post("/settmdbid")
|
|
37
|
+
async def set_poster_id(payload: UpdatePosterRequest, request: Request):
|
|
38
|
+
"""
|
|
39
|
+
Set a Tmdb id for example when tmdb returns an empty result
|
|
40
|
+
|
|
41
|
+
Required
|
|
42
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
43
|
+
|
|
44
|
+
Return
|
|
45
|
+
- none
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
await update_poster(request.app, msg="TMDB", job_id=payload.job_id, field_id=payload.field_id, new_id=payload.new_id)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@router.post("/settvdbid")
|
|
52
|
+
async def set_tvdb_id(payload: UpdatePosterRequest, request: Request):
|
|
53
|
+
"""
|
|
54
|
+
Set a TVdb id for example when tvdb returns an empty result
|
|
55
|
+
|
|
56
|
+
Required
|
|
57
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
58
|
+
|
|
59
|
+
Return
|
|
60
|
+
- none
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
await update_poster(request.app, msg="TVDB", job_id=payload.job_id, field_id=payload.field_id, new_id=payload.new_id)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@router.post("/setimdbid")
|
|
67
|
+
async def set_imdb_id(payload: UpdatePosterRequest, request: Request):
|
|
68
|
+
"""
|
|
69
|
+
Set an Imdb id for example when the remote list of tvdb is empty
|
|
70
|
+
|
|
71
|
+
Required
|
|
72
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
73
|
+
|
|
74
|
+
Return
|
|
75
|
+
- none
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
await update_poster(request.app, msg="IMDB", job_id=payload.job_id, field_id=payload.field_id, new_id=payload.new_id)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.post("/setposterurl")
|
|
82
|
+
async def set_poster_url(payload: UpdatePosterRequest, request: Request):
|
|
83
|
+
"""
|
|
84
|
+
Set a poster url for example when tmdb returns an empty result only for frontend
|
|
85
|
+
|
|
86
|
+
Required
|
|
87
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
88
|
+
|
|
89
|
+
Return
|
|
90
|
+
- none
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
await update_poster(request.app, msg="TMDB Poster Url", job_id=payload.job_id, field_id=payload.field_id, new_id=payload.new_id)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@router.post("/setposterdname")
|
|
97
|
+
async def set_poster_dname(payload: UpdatePosterRequest, request: Request):
|
|
98
|
+
"""
|
|
99
|
+
Set a poster display name for example if you dont like it
|
|
100
|
+
Display name is the name shown on the dedicated torrent page
|
|
101
|
+
|
|
102
|
+
Required
|
|
103
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
104
|
+
|
|
105
|
+
Return
|
|
106
|
+
- none
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
await update_poster(request.app, msg="Update DisplayName", job_id=payload.job_id, field_id=payload.field_id,
|
|
110
|
+
new_id=payload.new_id)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from fastapi import APIRouter, Request
|
|
3
|
+
from fastapi.responses import JSONResponse
|
|
4
|
+
|
|
5
|
+
from unit3dwup.use_case.process_all_usecase import ProcessAllUseCase
|
|
6
|
+
from unit3dwup.use_case.upload_usecase import UploadUseCase
|
|
7
|
+
from unit3dwup.use_case.seed_usecase import SeedUseCase
|
|
8
|
+
from unit3dwup.use_case.make_torrent_usecase import MakeTorrentUseCase
|
|
9
|
+
|
|
10
|
+
from unit3dwup.schemas import ProcessAllRequest, JobRequest
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.post("/processall")
|
|
16
|
+
async def process_all(payload: ProcessAllRequest, request: Request):
|
|
17
|
+
"""
|
|
18
|
+
Start a chain load the joblist filter for existing torrent create torrent upload the complete joblist
|
|
19
|
+
|
|
20
|
+
Required
|
|
21
|
+
- job_list_id identifies the list created by the scan endpoint
|
|
22
|
+
|
|
23
|
+
Prerequisite
|
|
24
|
+
- valid description status media class attribute
|
|
25
|
+
|
|
26
|
+
Return
|
|
27
|
+
- none
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
app = request.app
|
|
31
|
+
|
|
32
|
+
use_case = ProcessAllUseCase(app=app, job_list_id=payload.job_list_id,
|
|
33
|
+
torrent_client_name=app.state.settings.torrent.TORRENT_CLIENT)
|
|
34
|
+
await use_case.execute()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.post("/maketorrent")
|
|
38
|
+
async def make(payload: JobRequest, request: Request):
|
|
39
|
+
"""
|
|
40
|
+
Create one or more torrent files
|
|
41
|
+
|
|
42
|
+
Required
|
|
43
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
44
|
+
|
|
45
|
+
Return
|
|
46
|
+
- none
|
|
47
|
+
"""
|
|
48
|
+
app = request.app
|
|
49
|
+
|
|
50
|
+
torrent_service = MakeTorrentUseCase(app=app, job_id=payload.job_id)
|
|
51
|
+
await torrent_service.execute()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.post("/upload")
|
|
55
|
+
async def upload(payload: JobRequest, request: Request):
|
|
56
|
+
"""
|
|
57
|
+
Upload a single torrent file
|
|
58
|
+
|
|
59
|
+
Required
|
|
60
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
61
|
+
|
|
62
|
+
Return
|
|
63
|
+
- none
|
|
64
|
+
|
|
65
|
+
WebSocket events emitted
|
|
66
|
+
- posterLogMessage: sent for each uploaded torrent
|
|
67
|
+
- job_id: media identifier
|
|
68
|
+
- message: upload result message
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
app = request.app
|
|
72
|
+
|
|
73
|
+
upload_service = UploadUseCase(app=app, job_id=payload.job_id)
|
|
74
|
+
await upload_service.execute()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.post("/seed")
|
|
78
|
+
async def seed(payload: JobRequest, request: Request) -> JSONResponse:
|
|
79
|
+
"""
|
|
80
|
+
Required
|
|
81
|
+
- job_id identifies each poster corresponds to Media.job_id
|
|
82
|
+
|
|
83
|
+
Return
|
|
84
|
+
- none
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
app = request.app
|
|
88
|
+
|
|
89
|
+
use_case = SeedUseCase(app=app, client=app.state.settings.torrent.TORRENT_CLIENT, job_id=payload.job_id)
|
|
90
|
+
return await use_case.execute()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import hashlib
|
|
3
|
+
import inspect
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from fastapi import APIRouter, Request, status
|
|
10
|
+
from fastapi.responses import JSONResponse
|
|
11
|
+
|
|
12
|
+
from unit3dwup.config import get_logger
|
|
13
|
+
|
|
14
|
+
from unit3dwup.repositories.db_online import Tmdb, Tvdb
|
|
15
|
+
|
|
16
|
+
from unit3dwup.services.media_service import MediaService, MediaService2
|
|
17
|
+
from unit3dwup.services.auto_async_service import AsyncMediaManager
|
|
18
|
+
|
|
19
|
+
from unit3dwup.use_case.scan_media_usecase import ScanMediaUseCase
|
|
20
|
+
|
|
21
|
+
from unit3dwup.schemas import ScanRequest
|
|
22
|
+
|
|
23
|
+
router = APIRouter()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@router.post("/scan")
|
|
27
|
+
async def scan(payload: ScanRequest, request: Request) -> JSONResponse:
|
|
28
|
+
"""
|
|
29
|
+
This endpoint scans the local files and creates a Media object for each associating it with its description
|
|
30
|
+
|
|
31
|
+
Required
|
|
32
|
+
- path: user path for the scan process, filesystem path on the hdd
|
|
33
|
+
|
|
34
|
+
Prerequisite
|
|
35
|
+
- path must be valid and accessible
|
|
36
|
+
|
|
37
|
+
Http response
|
|
38
|
+
- status: http status code
|
|
39
|
+
- source: indicates source of the posters local hdd or remote tracker
|
|
40
|
+
- results: dictionary containing source and list of Media objects
|
|
41
|
+
|
|
42
|
+
Websocket events
|
|
43
|
+
- type: log type for frontend display
|
|
44
|
+
- level: result of the process success or error
|
|
45
|
+
- message: message displayed in the frontend console
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
app = request.app
|
|
49
|
+
|
|
50
|
+
frame = inspect.currentframe()
|
|
51
|
+
logger = get_logger(frame.f_code.co_name)
|
|
52
|
+
|
|
53
|
+
if app.state.restart_docker:
|
|
54
|
+
return JSONResponse(
|
|
55
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
56
|
+
content={
|
|
57
|
+
"source": "local",
|
|
58
|
+
"message": "Please restart the Docker container",
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
start_time = time.perf_counter()
|
|
63
|
+
|
|
64
|
+
# Get the id for the current path
|
|
65
|
+
job_list_id = hashlib.sha256(app.state.settings.prefs.SCAN_PATH.encode()).hexdigest()
|
|
66
|
+
logger.info(f"Current joblist_id {job_list_id} {app.state.settings.prefs.SCAN_PATH}")
|
|
67
|
+
|
|
68
|
+
# Load the jobs list using the previous id
|
|
69
|
+
job_list = await app.state.job.get_job_list(job_id=job_list_id)
|
|
70
|
+
|
|
71
|
+
# Load Media for each job id from the job_list
|
|
72
|
+
job_list_results = []
|
|
73
|
+
for job_id in job_list:
|
|
74
|
+
job = await app.state.job.get_job(job_id)
|
|
75
|
+
if job:
|
|
76
|
+
job_list_results.append(json.loads(job))
|
|
77
|
+
|
|
78
|
+
# New session
|
|
79
|
+
async with aiohttp.ClientSession() as session:
|
|
80
|
+
|
|
81
|
+
manager = AsyncMediaManager(
|
|
82
|
+
path=app.state.scan_path,
|
|
83
|
+
app=app,
|
|
84
|
+
job_id_list=job_list_id
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Instance repo ( or gateway..?) for each db online (TVDB, TMDB). Read imdb id from the tvdb remote_ids list
|
|
88
|
+
tvdb_repo = Tvdb(session=session)
|
|
89
|
+
tmdb_repo = Tmdb(session=session)
|
|
90
|
+
|
|
91
|
+
# Pass the repository to the MediaService class for async task purposes
|
|
92
|
+
media_service = MediaService(tmdb_repo)
|
|
93
|
+
media_service2 = MediaService2(tvdb_repo)
|
|
94
|
+
|
|
95
|
+
# Create a use_case
|
|
96
|
+
use_case = ScanMediaUseCase(
|
|
97
|
+
manager=manager,
|
|
98
|
+
media_service=media_service,
|
|
99
|
+
media_service2=media_service2,
|
|
100
|
+
job_repo=app.state.job,
|
|
101
|
+
session=session,
|
|
102
|
+
job_list=job_list
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Run all
|
|
106
|
+
results = await use_case.execute()
|
|
107
|
+
|
|
108
|
+
# Send a message to the frontend by ws
|
|
109
|
+
await app.state.ws_manager.broadcast({
|
|
110
|
+
"type": "log",
|
|
111
|
+
"level": "success",
|
|
112
|
+
"message": f"Scan completato in {time.perf_counter() - start_time:.2f} secondi",
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
# Analyze the results and build a new job_list with the new and old posters
|
|
116
|
+
build_results = []
|
|
117
|
+
new_job_list = []
|
|
118
|
+
|
|
119
|
+
for media in results:
|
|
120
|
+
# The current media object has no description because the job_id is already in the job_list
|
|
121
|
+
if not media.description:
|
|
122
|
+
for job in job_list_results:
|
|
123
|
+
if job['job_id'] == media.job_id:
|
|
124
|
+
build_results.append(job)
|
|
125
|
+
# append the old poster
|
|
126
|
+
new_job_list.append(job['job_id'])
|
|
127
|
+
else:
|
|
128
|
+
# new description. This is a new job_id
|
|
129
|
+
build_results.append(media.to_dict())
|
|
130
|
+
new_job_list.append(media.job_id)
|
|
131
|
+
|
|
132
|
+
if new_job_list:
|
|
133
|
+
# Save the new job_list
|
|
134
|
+
await app.state.job.create_job_list(
|
|
135
|
+
job_id=job_list_id,
|
|
136
|
+
job_list=new_job_list
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# return to frontend the new posters
|
|
140
|
+
return JSONResponse(
|
|
141
|
+
status_code=status.HTTP_200_OK,
|
|
142
|
+
content={
|
|
143
|
+
"source": "local",
|
|
144
|
+
"results": build_results,
|
|
145
|
+
}
|
|
146
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import aiohttp
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Request, status
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
|
|
7
|
+
from unit3dwup.services.itt_tracker_service import ITTtrackerService
|
|
8
|
+
from unit3dwup.services.interfaces import TrackerServiceInterface
|
|
9
|
+
|
|
10
|
+
from unit3dwup.schemas import FilterRequest
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.post("/filter")
|
|
16
|
+
async def filter_search(payload: FilterRequest, request: Request):
|
|
17
|
+
"""
|
|
18
|
+
Search words or title in the tracker
|
|
19
|
+
|
|
20
|
+
Required
|
|
21
|
+
- title: title or part of it
|
|
22
|
+
|
|
23
|
+
Return
|
|
24
|
+
- none
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
app = request.app
|
|
28
|
+
|
|
29
|
+
# Search for a title in the tracker
|
|
30
|
+
async with aiohttp.ClientSession() as session:
|
|
31
|
+
# A new ITT tracker instance with interface
|
|
32
|
+
tracker_service: TrackerServiceInterface = ITTtrackerService(session, app=app)
|
|
33
|
+
|
|
34
|
+
# Search
|
|
35
|
+
data = await tracker_service.search(payload.title)
|
|
36
|
+
|
|
37
|
+
# Return the source as remote ( change the bottom line color on the poster)
|
|
38
|
+
# job_id not applicable
|
|
39
|
+
# Extract attributes field for each data found
|
|
40
|
+
return JSONResponse(
|
|
41
|
+
status_code=status.HTTP_200_OK,
|
|
42
|
+
content={
|
|
43
|
+
"source": "remote",
|
|
44
|
+
"job_id": '-1',
|
|
45
|
+
"results": [c['attributes'] for c in data['data']],
|
|
46
|
+
}
|
|
47
|
+
)
|