@ -1,10 +1,11 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, HTTPException, Request, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from typer import Typer, Argument
|
||||
import uvicorn
|
||||
from anime.routes import router
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
CURRENT_DIR = Path(__file__).parent
|
||||
STATIC_DIR = CURRENT_DIR / "static"
|
||||
@ -12,6 +13,20 @@ STATIC_DIR = CURRENT_DIR / "static"
|
||||
cli = Typer()
|
||||
|
||||
|
||||
async def response_formatter(request: Request, call_next):
|
||||
try:
|
||||
response = await call_next(request)
|
||||
print(response)
|
||||
except Exception as e:
|
||||
response = Response(
|
||||
status_code=500,
|
||||
content={
|
||||
"detail": str(e),
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
@cli.command()
|
||||
def run_app(
|
||||
host: str = "0.0.0.0",
|
||||
@ -33,7 +48,9 @@ def run_app(
|
||||
check_dir=False,
|
||||
),
|
||||
)
|
||||
app.add_middleware(BaseHTTPMiddleware, dispatch=response_formatter)
|
||||
app.state.anime_dir = anime_dir
|
||||
app.state.pid = None
|
||||
|
||||
uvicorn.run(app, host=host, port=port, workers=1)
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
|
||||
from anime.dtos import KillRequest, PlayerOffsetRequest
|
||||
@ -25,17 +27,54 @@ def start_watching(request: Request) -> None:
|
||||
anime_dir = request.app.state.anime_dir
|
||||
if not anime_dir:
|
||||
raise HTTPException(status_code=400, detail="Anime directory is not set.")
|
||||
os.chdir(anime_dir)
|
||||
os.system("awatch")
|
||||
os.chdir(CWD)
|
||||
if request.app.state.pid:
|
||||
try:
|
||||
os.kill(request.app.state.pid, 0)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Awatch is already running.",
|
||||
)
|
||||
awatch = shutil.which("awatch")
|
||||
if awatch is None:
|
||||
raise Exception(
|
||||
"awatch command is not available. Please install awatch.\n"
|
||||
"https://gitlab.le-memese.com/s3rius/awatch/"
|
||||
)
|
||||
ret = subprocess.Popen(
|
||||
[awatch],
|
||||
cwd=anime_dir,
|
||||
)
|
||||
request.app.state.pid = ret.pid
|
||||
ret.wait()
|
||||
request.app.state.pid = None
|
||||
|
||||
|
||||
@router.post("/player/offset")
|
||||
async def offset(req: PlayerOffsetRequest) -> None:
|
||||
direction = "+" if req.forward else "-"
|
||||
os.system(f"playerctl position {req.offset}{direction}")
|
||||
playerctl = shutil.which("playerctl")
|
||||
if playerctl is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="playerctl command is not available.",
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
[playerctl, "position", f"{req.offset}{direction}"],
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/player/play-pause")
|
||||
async def play_pause() -> None:
|
||||
os.system("playerctl play-pause")
|
||||
playerctl = shutil.which("playerctl")
|
||||
if playerctl is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="playerctl command is not available.",
|
||||
)
|
||||
|
||||
subprocess.run([playerctl, "play-pause"], check=False)
|
||||
|
19
build.py
19
build.py
@ -1,6 +1,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from shutil import copytree, rmtree
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
CURRENT_DIR = Path(__file__).parent
|
||||
@ -29,15 +30,19 @@ def build(setup_kwargs):
|
||||
This script is useful for packaging the application.
|
||||
"""
|
||||
print("Starting building frontend ...")
|
||||
pnpm_path = shutil.which("pnpm")
|
||||
if pnpm_path is None:
|
||||
raise Exception("pnpm command is not available. PLease install pnpm.")
|
||||
|
||||
with DirChanger(CURRENT_DIR / "frontend"):
|
||||
ret_status = os.system("pnpm build")
|
||||
if ret_status != 0:
|
||||
raise Exception("Frontend build failed.")
|
||||
subprocess.run(
|
||||
[pnpm_path, "build"],
|
||||
cwd=CURRENT_DIR / "frontend",
|
||||
check=True,
|
||||
)
|
||||
|
||||
print("Frontend build finished.")
|
||||
rmtree(STATIC_OUTPUT_DIR, ignore_errors=True)
|
||||
copytree(symlinks=True, src=DIST_DIR, dst=STATIC_OUTPUT_DIR)
|
||||
shutil.rmtree(STATIC_OUTPUT_DIR, ignore_errors=True)
|
||||
shutil.copytree(symlinks=True, src=DIST_DIR, dst=STATIC_OUTPUT_DIR)
|
||||
|
||||
return setup_kwargs
|
||||
|
||||
|
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -11,6 +11,7 @@ node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
dev-dist
|
||||
coverage
|
||||
*.local
|
||||
|
||||
|
@ -1,13 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Anime</title>
|
||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -24,6 +24,7 @@
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vite": "^5.2.8",
|
||||
"vite-plugin-pwa": "^0.20.0",
|
||||
"vite-plugin-vue-devtools": "^7.0.25"
|
||||
}
|
||||
}
|
||||
|
2274
frontend/pnpm-lock.yaml
generated
2274
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
frontend/public/icon.jpg
Normal file
BIN
frontend/public/icon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
frontend/public/pwa-192x192.jpg
Normal file
BIN
frontend/public/pwa-192x192.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
BIN
frontend/public/pwa-512x512.jpg
Normal file
BIN
frontend/public/pwa-512x512.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { Tabbar, TabbarItem, ActionBar, ActionBarButton } from 'vant';
|
||||
import { ActionBar, ActionBarButton } from 'vant';
|
||||
import { postRequest } from '@/utils'
|
||||
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { showFailToast } from 'vant';
|
||||
|
||||
async function postRequest(url, data) {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
@ -6,7 +8,13 @@ async function postRequest(url, data) {
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
return response.json()
|
||||
const resp_data = await response.json()
|
||||
if (!response.ok) {
|
||||
showFailToast({
|
||||
message: resp_data.detail
|
||||
})
|
||||
}
|
||||
return resp_data
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,11 +2,36 @@ import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
VitePWA({
|
||||
manifest: {
|
||||
name: 'Anime',
|
||||
short_name: 'Anime',
|
||||
start_url: '/',
|
||||
theme_color: '#000000',
|
||||
icons: [
|
||||
{
|
||||
src: '/pwa-192x192.jpg',
|
||||
sizes: '192x192',
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
{
|
||||
src: '/pwa-512x512.jpg',
|
||||
sizes: '512x512',
|
||||
type: 'image/jpeg',
|
||||
}
|
||||
]
|
||||
},
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
},
|
||||
registerType: "autoUpdate",
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
Reference in New Issue
Block a user