Add systemd start/stop for transmission.service

This commit is contained in:
Vladan Popovic 2024-10-27 01:39:20 +02:00
parent 385694da91
commit 66acf6b421
7 changed files with 85 additions and 92 deletions

View file

@ -1,7 +1,7 @@
[project] [project]
name = "torrent-downloader" name = "torrent-downloader"
version = "0.1.0" version = "0.1.1"
description = "Add your description here" description = "Web interface for searching and downloading torrents"
authors = [ authors = [
{ name = "Vladan Popovic", email = "vladanovic@gmail.com" } { name = "Vladan Popovic", email = "vladanovic@gmail.com" }
] ]
@ -12,6 +12,7 @@ dependencies = [
"fastapi>=0.114.2", "fastapi>=0.114.2",
"jinja2>=3.1.4", "jinja2>=3.1.4",
"cinemagoer>=2023.5.1", "cinemagoer>=2023.5.1",
"dbus-python>=1.3.2",
] ]
readme = "README.md" readme = "README.md"
requires-python = ">= 3.8" requires-python = ">= 3.8"

View file

@ -11,92 +11,45 @@
-e file:. -e file:.
annotated-types==0.7.0 annotated-types==0.7.0
# via pydantic
anyio==4.4.0 anyio==4.4.0
# via starlette
asttokens==2.4.1 asttokens==2.4.1
# via stack-data
certifi==2024.8.30 certifi==2024.8.30
# via requests
charset-normalizer==3.3.2 charset-normalizer==3.3.2
# via requests
cinemagoer==2023.5.1 cinemagoer==2023.5.1
# via torrent-downloader
click==8.1.7 click==8.1.7
# via uvicorn dbus-python==1.3.2
decorator==5.1.1 decorator==5.1.1
# via ipython
executing==2.1.0 executing==2.1.0
# via stack-data
fastapi==0.114.2 fastapi==0.114.2
# via torrent-downloader
greenlet==3.1.0 greenlet==3.1.0
# via sqlalchemy
h11==0.14.0 h11==0.14.0
# via uvicorn
idna==3.8 idna==3.8
# via anyio
# via requests
ipython==8.27.0 ipython==8.27.0
jedi==0.19.1 jedi==0.19.1
# via ipython
jinja2==3.1.4 jinja2==3.1.4
# via torrent-downloader
lxml==5.3.0 lxml==5.3.0
# via cinemagoer
# via tpblite
markupsafe==2.1.5 markupsafe==2.1.5
# via jinja2
matplotlib-inline==0.1.7 matplotlib-inline==0.1.7
# via ipython
parso==0.8.4 parso==0.8.4
# via jedi
pexpect==4.9.0 pexpect==4.9.0
# via ipython
prompt-toolkit==3.0.47 prompt-toolkit==3.0.47
# via ipython
ptyprocess==0.7.0 ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.3 pure-eval==0.2.3
# via stack-data
pydantic==2.8.2 pydantic==2.8.2
# via fastapi
# via pydantic-settings
pydantic-core==2.20.1 pydantic-core==2.20.1
# via pydantic
pydantic-settings==2.4.0 pydantic-settings==2.4.0
# via torrent-downloader
pygments==2.18.0 pygments==2.18.0
# via ipython
python-dotenv==1.0.1 python-dotenv==1.0.1
# via pydantic-settings
requests==2.32.3 requests==2.32.3
# via transmission-rpc
six==1.16.0 six==1.16.0
# via asttokens
sniffio==1.3.1 sniffio==1.3.1
# via anyio
sqlalchemy==2.0.34 sqlalchemy==2.0.34
# via cinemagoer
stack-data==0.6.3 stack-data==0.6.3
# via ipython
starlette==0.38.5 starlette==0.38.5
# via fastapi
tpblite==0.8.0 tpblite==0.8.0
# via torrent-downloader
traitlets==5.14.3 traitlets==5.14.3
# via ipython
# via matplotlib-inline
transmission-rpc==7.0.11 transmission-rpc==7.0.11
# via torrent-downloader
typing-extensions==4.12.2 typing-extensions==4.12.2
# via fastapi
# via pydantic
# via pydantic-core
# via sqlalchemy
# via transmission-rpc
urllib3==2.2.2 urllib3==2.2.2
# via requests
uvicorn==0.30.6 uvicorn==0.30.6
wcwidth==0.2.13 wcwidth==0.2.13
# via prompt-toolkit

View file

@ -11,55 +11,26 @@
-e file:. -e file:.
annotated-types==0.7.0 annotated-types==0.7.0
# via pydantic
anyio==4.4.0 anyio==4.4.0
# via starlette
certifi==2024.8.30 certifi==2024.8.30
# via requests
charset-normalizer==3.3.2 charset-normalizer==3.3.2
# via requests
cinemagoer==2023.5.1 cinemagoer==2023.5.1
# via torrent-downloader dbus-python==1.3.2
fastapi==0.114.2 fastapi==0.114.2
# via torrent-downloader
greenlet==3.1.0 greenlet==3.1.0
# via sqlalchemy
idna==3.8 idna==3.8
# via anyio
# via requests
jinja2==3.1.4 jinja2==3.1.4
# via torrent-downloader
lxml==5.3.0 lxml==5.3.0
# via cinemagoer
# via tpblite
markupsafe==2.1.5 markupsafe==2.1.5
# via jinja2
pydantic==2.8.2 pydantic==2.8.2
# via fastapi
# via pydantic-settings
pydantic-core==2.20.1 pydantic-core==2.20.1
# via pydantic
pydantic-settings==2.4.0 pydantic-settings==2.4.0
# via torrent-downloader
python-dotenv==1.0.1 python-dotenv==1.0.1
# via pydantic-settings
requests==2.32.3 requests==2.32.3
# via transmission-rpc
sniffio==1.3.1 sniffio==1.3.1
# via anyio
sqlalchemy==2.0.34 sqlalchemy==2.0.34
# via cinemagoer
starlette==0.38.5 starlette==0.38.5
# via fastapi
tpblite==0.8.0 tpblite==0.8.0
# via torrent-downloader
transmission-rpc==7.0.11 transmission-rpc==7.0.11
# via torrent-downloader
typing-extensions==4.12.2 typing-extensions==4.12.2
# via fastapi
# via pydantic
# via pydantic-core
# via sqlalchemy
# via transmission-rpc
urllib3==2.2.2 urllib3==2.2.2
# via requests

View file

@ -3,12 +3,13 @@ import urllib.parse
import transmission_rpc import transmission_rpc
from fastapi import Depends, FastAPI, Request from fastapi import Depends, FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from torrent_downloader.client import TorrentDownloader from torrent_downloader.client import TorrentDownloader
from torrent_downloader.imdb import search_string_from_url from torrent_downloader.imdb import search_string_from_url
from torrent_downloader.systemd import Systemd
app = FastAPI() app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")
@ -19,8 +20,16 @@ def get_downloader() -> TorrentDownloader:
return TorrentDownloader() return TorrentDownloader()
def get_active_torrents(downloader=get_downloader()) -> list[transmission_rpc.Torrent]: def get_systemd() -> Systemd:
return [t for t in downloader.get_active_torrents() if t.format_eta() != "not available"] return Systemd()
def get_active_torrents(
downloader=get_downloader(),
) -> list[transmission_rpc.Torrent]:
return [
t for t in downloader.get_active_torrents() if t.format_eta().lower() != "not available"
]
def hx(func): def hx(func):
@ -41,13 +50,8 @@ def hx(func):
@app.get("/") @app.get("/")
async def index(_: Request):
return RedirectResponse("/search")
@app.get("/search", response_class=HTMLResponse)
@hx @hx
async def search_for_torrents(request: Request): async def index(request: Request):
return templates.TemplateResponse(request=request, name="search.html") return templates.TemplateResponse(request=request, name="search.html")
@ -70,7 +74,9 @@ async def download(request: Request, downloader=Depends(get_downloader)):
downloader.download_magnet(magnet) downloader.download_magnet(magnet)
active = downloader.get_active_torrents() active = downloader.get_active_torrents()
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="active_torrents.html", context={"torrents": active} request=request,
name="active_torrents.html",
context={"torrents": active},
) )
@ -78,5 +84,34 @@ async def download(request: Request, downloader=Depends(get_downloader)):
@hx @hx
async def active(request: Request, active=Depends(get_active_torrents)): async def active(request: Request, active=Depends(get_active_torrents)):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="active_torrents.html", context={"torrents": active} request=request,
name="active_torrents.html",
context={"torrents": active},
)
@app.get("/transmission", response_class=HTMLResponse)
@hx
async def transmission_status(request: Request, systemd=Depends(get_systemd)):
return templates.TemplateResponse(
request=request,
name="transmission.html",
context={"is_active": systemd.service_is_active("transmission")},
)
@app.post("/transmission/{action}", response_class=HTMLResponse)
@hx
async def transmission_start(request: Request, action: str, systemd=Depends(get_systemd)):
match action:
case "start":
systemd.service_start("transmission")
case "stop":
systemd.service_stop("transmission")
case _:
return 404
return templates.TemplateResponse(
request=request,
name="transmission.html",
context={"is_active": systemd.service_is_active("transmission")},
) )

View file

@ -0,0 +1,24 @@
import dbus
class Systemd:
def __init__(self):
self.bus = dbus.SessionBus()
self.systemd = self.bus.get_object(
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
)
self.manager = dbus.Interface(self.systemd, "org.freedesktop.systemd1.Manager")
def service_start(self, service: str):
return self.manager.StartUnit(f"{service}.service", "replace")
def service_stop(self, service: str):
return self.manager.StopUnit(f"{service}.service", "replace")
def service_is_active(self, service: str):
try:
self.manager.GetUnit(service)
return True
except dbus.exceptions.DBusException:
return False

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search and download torrents directly to mediacenter</title> <title>Search and download torrents directly to mediacenter</title>
<script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"></script> <script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"></script>
<link href="{{ url_for('static', path='/style.css') }}" rel="stylesheet"> <link href="/static/style.css" rel="stylesheet">
</head> </head>
<body> <body>
<menu> <menu>

View file

@ -0,0 +1,9 @@
{% block content %}
{% if is_active %}
<h3>Transmission is running!</h3>
<button hx-post="/transmission/stop" hx-target="#content">Stop now!</button>
{% else %}
<h3>Transmission is stopped!</h3>
<button hx-post="/transmission/start" hx-target="#content">Start now!</button>
{% endif %}
{% endblock %}