mai2: rework photo uploads, relates to #67

This commit is contained in:
2024-10-06 03:47:10 -04:00
parent ed5e7dc561
commit 0cef797a8a
6 changed files with 272 additions and 42 deletions

View File

@ -1,11 +1,14 @@
from typing import List
from starlette.routing import Route, Mount
from starlette.requests import Request
from starlette.responses import Response, RedirectResponse
from os import path
from starlette.responses import Response, RedirectResponse, FileResponse
from os import path, walk, remove
import yaml
import jinja2
from datetime import datetime
from datetime import datetime, timedelta
from PIL import ImageFile
import re
import shutil
from core.frontend import FE_Base, UserSession, PermissionOffset
from core.config import CoreConfig
@ -31,7 +34,8 @@ class Mai2Frontend(FE_Base):
Route("/", self.render_GET, methods=['GET']),
Mount("/playlog", routes=[
Route("/", self.render_GET_playlog, methods=['GET']),
Route("/{index}", self.render_GET_playlog, methods=['GET']),
Route("/{index:int}", self.render_GET_playlog, methods=['GET']),
Route("/photos", self.render_GET_photos, methods=['GET']),
]),
Mount("/events", routes=[
Route("/", self.render_events, methods=['GET']),
@ -41,6 +45,7 @@ class Mai2Frontend(FE_Base):
]),
Route("/update.name", self.update_name, methods=['POST']),
Route("/version.change", self.version_change, methods=['POST']),
Route("/photo/{photo_id}", self.get_photo, methods=['GET']),
]
async def render_GET(self, request: Request) -> bytes:
@ -140,6 +145,50 @@ class Mai2Frontend(FE_Base):
else:
return RedirectResponse("/gate/", 303)
async def render_GET_photos(self, request: Request) -> bytes:
template = self.environment.get_template(
"titles/mai2/templates/mai2_photos.jinja"
)
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
if usr_sesh.user_id > 0:
if usr_sesh.maimai_version < 0:
return RedirectResponse("/game/mai2/", 303)
photos = await self.data.profile.get_user_photos_by_user(usr_sesh.user_id)
photos_fixed = []
for photo in photos:
if datetime.now().timestamp() > (photo['when_upload'] + timedelta(days=7)).timestamp():
await self.data.profile.delete_user_photo_by_id(photo['id'])
if path.exists(f"{self.game_cfg.uploads.photos_dir}/{photo['id']}.jpeg"):
remove(f"{self.game_cfg.uploads.photos_dir}/{photo['id']}.jpeg")
if path.exists(f"{self.game_cfg.uploads.photos_dir}/{photo['id']}"):
shutil.rmtree(f"{self.game_cfg.uploads.photos_dir}/{photo['id']}")
continue
photos_fixed.append({
"id": photo['id'],
"playlog_num": photo['playlog_num'],
"track_num": photo['track_num'],
"when_upload": photo['when_upload'],
})
return Response(template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
photos=photos_fixed,
expire_days=7,
), media_type="text/html; charset=utf-8")
else:
return RedirectResponse("/gate/", 303)
async def update_name(self, request: Request) -> bytes:
usr_sesh = self.validate_session(request)
if not usr_sesh:
@ -299,3 +348,76 @@ class Mai2Frontend(FE_Base):
await self.data.static.update_event_by_id(int(event_id), new_enabled, new_start_date)
return RedirectResponse("/game/mai2/events/?s=1", 303)
async def get_photo(self, request: Request) -> RedirectResponse:
usr_sesh = self.validate_session(request)
if not usr_sesh:
return RedirectResponse("/gate/", 303)
photo_jpeg = request.path_params.get("photo_id", None)
if not photo_jpeg:
return Response(status_code=400)
matcher = re.match(r"^([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}).jpeg$", photo_jpeg)
if not matcher:
return Response(status_code=400)
photo_id = matcher.groups()[0]
photo_info = await self.data.profile.get_user_photo_by_id(photo_id)
if not photo_info:
return Response(status_code=404)
if photo_info["user"] != usr_sesh.user_id:
return Response(status_code=403)
out_folder = f"{self.game_cfg.uploads.photos_dir}/{photo_id}"
if datetime.now().timestamp() > (photo_info['when_upload'] + timedelta(days=7)).timestamp():
await self.data.profile.delete_user_photo_by_id(photo_info['id'])
if path.exists(f"{out_folder}.jpeg"):
remove(f"{out_folder}.jpeg")
if path.exists(f"{out_folder}"):
shutil.rmtree(out_folder)
return Response(status_code=404)
if path.exists(f"{out_folder}"):
print("path exists")
max_idx = 0
p = ImageFile.Parser()
for _, _, files in walk("out_folder"):
if not files:
break
matcher = re.match("^(\d+)_(\d+)$", files[0])
if not matcher:
break
max_idx = int(matcher.groups()[1])
if max_idx + 1 != len(files):
self.logger.error(f"Expected {max_idx + 1} files, found {len(files)}")
max_idx = 0
break
if max_idx == 0:
return Response(status_code=500)
for i in range(max_idx + 1):
with open(f"{out_folder}/{i}_{max_idx}", "rb") as f:
p.feed(f.read())
try:
im = p.close()
im.save(f"{out_folder}.jpeg")
except Exception as e:
self.logger.error(f"{photo_id} failed PIL validation! - {e}")
shutil.rmtree(out_folder)
if path.exists(f"{out_folder}.jpeg"):
print(f"{out_folder}.jpeg exists")
return FileResponse(f"{out_folder}.jpeg")
return Response(status_code=404)