@ -1,10 +1,11 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, HTTPException, Request, Response
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from typer import Typer, Argument
|
from typer import Typer, Argument
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from anime.routes import router
|
from anime.routes import router
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
|
||||||
CURRENT_DIR = Path(__file__).parent
|
CURRENT_DIR = Path(__file__).parent
|
||||||
STATIC_DIR = CURRENT_DIR / "static"
|
STATIC_DIR = CURRENT_DIR / "static"
|
||||||
@ -12,6 +13,20 @@ STATIC_DIR = CURRENT_DIR / "static"
|
|||||||
cli = Typer()
|
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()
|
@cli.command()
|
||||||
def run_app(
|
def run_app(
|
||||||
host: str = "0.0.0.0",
|
host: str = "0.0.0.0",
|
||||||
@ -33,7 +48,9 @@ def run_app(
|
|||||||
check_dir=False,
|
check_dir=False,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
app.add_middleware(BaseHTTPMiddleware, dispatch=response_formatter)
|
||||||
app.state.anime_dir = anime_dir
|
app.state.anime_dir = anime_dir
|
||||||
|
app.state.pid = None
|
||||||
|
|
||||||
uvicorn.run(app, host=host, port=port, workers=1)
|
uvicorn.run(app, host=host, port=port, workers=1)
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
from fastapi import APIRouter, HTTPException, Request
|
from fastapi import APIRouter, HTTPException, Request
|
||||||
|
|
||||||
from anime.dtos import KillRequest, PlayerOffsetRequest
|
from anime.dtos import KillRequest, PlayerOffsetRequest
|
||||||
@ -25,17 +27,54 @@ def start_watching(request: Request) -> None:
|
|||||||
anime_dir = request.app.state.anime_dir
|
anime_dir = request.app.state.anime_dir
|
||||||
if not anime_dir:
|
if not anime_dir:
|
||||||
raise HTTPException(status_code=400, detail="Anime directory is not set.")
|
raise HTTPException(status_code=400, detail="Anime directory is not set.")
|
||||||
os.chdir(anime_dir)
|
if request.app.state.pid:
|
||||||
os.system("awatch")
|
try:
|
||||||
os.chdir(CWD)
|
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")
|
@router.post("/player/offset")
|
||||||
async def offset(req: PlayerOffsetRequest) -> None:
|
async def offset(req: PlayerOffsetRequest) -> None:
|
||||||
direction = "+" if req.forward else "-"
|
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")
|
@router.post("/player/play-pause")
|
||||||
async def play_pause() -> None:
|
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
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import copytree, rmtree
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
CURRENT_DIR = Path(__file__).parent
|
CURRENT_DIR = Path(__file__).parent
|
||||||
@ -29,15 +30,19 @@ def build(setup_kwargs):
|
|||||||
This script is useful for packaging the application.
|
This script is useful for packaging the application.
|
||||||
"""
|
"""
|
||||||
print("Starting building frontend ...")
|
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"):
|
subprocess.run(
|
||||||
ret_status = os.system("pnpm build")
|
[pnpm_path, "build"],
|
||||||
if ret_status != 0:
|
cwd=CURRENT_DIR / "frontend",
|
||||||
raise Exception("Frontend build failed.")
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
print("Frontend build finished.")
|
print("Frontend build finished.")
|
||||||
rmtree(STATIC_OUTPUT_DIR, ignore_errors=True)
|
shutil.rmtree(STATIC_OUTPUT_DIR, ignore_errors=True)
|
||||||
copytree(symlinks=True, src=DIST_DIR, dst=STATIC_OUTPUT_DIR)
|
shutil.copytree(symlinks=True, src=DIST_DIR, dst=STATIC_OUTPUT_DIR)
|
||||||
|
|
||||||
return setup_kwargs
|
return setup_kwargs
|
||||||
|
|
||||||
|
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -11,6 +11,7 @@ node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dev-dist
|
||||||
coverage
|
coverage
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Vite App</title>
|
<title>Anime</title>
|
||||||
</head>
|
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||||
<body>
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -24,6 +24,7 @@
|
|||||||
"eslint-plugin-vue": "^9.23.0",
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"vite": "^5.2.8",
|
"vite": "^5.2.8",
|
||||||
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
"vite-plugin-vue-devtools": "^7.0.25"
|
"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>
|
<script setup>
|
||||||
import { Tabbar, TabbarItem, ActionBar, ActionBarButton } from 'vant';
|
import { ActionBar, ActionBarButton } from 'vant';
|
||||||
import { postRequest } from '@/utils'
|
import { postRequest } from '@/utils'
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { showFailToast } from 'vant';
|
||||||
|
|
||||||
async function postRequest(url, data) {
|
async function postRequest(url, data) {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -6,7 +8,13 @@ async function postRequest(url, data) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(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 { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
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: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
Reference in New Issue
Block a user