Mega refactor of asset

This commit is contained in:
Georges-Antoine Assi
2024-02-01 17:59:06 -05:00
parent c5f79e0872
commit eb18388a81
39 changed files with 377 additions and 461 deletions

1
.gitignore vendored
View File

@@ -47,6 +47,7 @@ romm_mock
# testing
backend/romm_test/resources
backend/romm_test/assets
backend/romm_test/logs
backend/romm_test/config
.pytest_cache

View File

@@ -8,6 +8,7 @@
mkdir -p romm_mock/library/roms/switch
touch romm_mock/library/roms/switch/metroid.xci
mkdir -p romm_mock/resources
mkdir -p romm_mock/assets
mkdir -p romm_mock/config
touch romm_mock/config.yml
```
@@ -87,6 +88,7 @@ npm install
```sh
mkdir assets/romm
ln -s ../../../romm_mock/resources assets/romm/resources
ln -s ../../../romm_mock/assets assets/romm/assets
```
### - Run the frontend

View File

@@ -0,0 +1,58 @@
"""empty message
Revision ID: 0019_migrate_to_mysql
Revises: 0018_increase_file_extension
Create Date: 2024-01-24 13:54:32.458301
"""
import os
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from config import ROMM_DB_DRIVER
from config.config_manager import ConfigManager, SQLITE_DB_BASE_PATH
# revision identifiers, used by Alembic.
revision = '0019_migrate_to_mysql'
down_revision = '0018_increase_file_extension'
branch_labels = None
depends_on = None
def upgrade() -> None:
if ROMM_DB_DRIVER != "mariadb":
raise Exception("Version 3.0 requires MariaDB as database driver!")
# Skip if sqlite database is not mounted
if not os.path.exists(f"{SQLITE_DB_BASE_PATH}/romm.db"):
return
maria_engine = create_engine(ConfigManager.get_db_engine(), pool_pre_ping=True)
maria_session = sessionmaker(bind=maria_engine, expire_on_commit=False)
sqlite_engine = create_engine(f"sqlite:////{SQLITE_DB_BASE_PATH}/romm.db", pool_pre_ping=True)
sqlite_session = sessionmaker(bind=sqlite_engine, expire_on_commit=False)
# Copy all data from sqlite to maria
with maria_session.begin() as maria_conn:
with sqlite_session.begin() as sqlite_conn:
maria_conn.execute(text("SET FOREIGN_KEY_CHECKS=0"))
tables = sqlite_conn.execute(text("SELECT name FROM sqlite_master WHERE type='table';")).fetchall()
for table_name in tables:
table_name = table_name[0]
if table_name == "alembic_version":
continue
table_data = sqlite_conn.execute(text(f"SELECT * FROM {table_name}")).fetchall()
# Insert data into MariaDB table
for row in table_data:
maria_conn.execute(text(f"INSERT INTO {table_name} VALUES {tuple(row)}"))
maria_conn.execute(text("SET FOREIGN_KEY_CHECKS=1"))
def downgrade() -> None:
pass

View File

@@ -0,0 +1,56 @@
"""empty message
Revision ID: 0020_assets_user_id
Revises: 0019_migrate_to_mysql
Create Date: 2024-02-01 17:08:02.103046
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "0020_assets_user_id"
down_revision = "0019_migrate_to_mysql"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("saves", schema=None) as batch_op:
batch_op.add_column(sa.Column("user_id", sa.Integer(), nullable=False))
batch_op.create_foreign_key(
"saves_user_FK", "users", ["user_id"], ["id"], ondelete="CASCADE"
)
with op.batch_alter_table("screenshots", schema=None) as batch_op:
batch_op.add_column(sa.Column("user_id", sa.Integer(), nullable=False))
batch_op.create_foreign_key(
"screenshots_user_FK", "users", ["user_id"], ["id"], ondelete="CASCADE"
)
with op.batch_alter_table("states", schema=None) as batch_op:
batch_op.add_column(sa.Column("user_id", sa.Integer(), nullable=False))
batch_op.create_foreign_key(
"states_user_FK", "users", ["user_id"], ["id"], ondelete="CASCADE"
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("states", schema=None) as batch_op:
batch_op.drop_constraint("states_user_FK", type_="foreignkey")
batch_op.drop_column("user_id")
with op.batch_alter_table("screenshots", schema=None) as batch_op:
batch_op.drop_constraint("screenshots_user_FK", type_="foreignkey")
batch_op.drop_column("user_id")
with op.batch_alter_table("saves", schema=None) as batch_op:
batch_op.drop_constraint("saves_user_FK", type_="foreignkey")
batch_op.drop_column("user_id")
# ### end Alembic commands ###

View File

@@ -14,9 +14,10 @@ ROMM_HOST: Final = os.environ.get("ROMM_HOST", DEV_HOST)
# PATHS
ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm")
LIBRARY_BASE_PATH: Final = f"{ROMM_BASE_PATH}/library"
FRONTEND_LIBRARY_PATH: Final = "/assets/romm/library"
RESOURCES_BASE_PATH: Final = f"{ROMM_BASE_PATH}/resources"
ASSETS_BASE_PATH: Final = f"{ROMM_BASE_PATH}/assets"
FRONTEND_RESOURCES_PATH: Final = "/assets/romm/resources"
FRONTEND_ASSETS_PATH: Final = "/assets/romm/assets"
# DEFAULT RESOURCES
DEFAULT_URL_COVER_L: Final = (
"https://images.igdb.com/igdb/image/upload/t_cover_big/nocover.png"

View File

@@ -38,9 +38,6 @@ class Config:
PLATFORMS_BINDING: dict[str, str]
PLATFORMS_VERSIONS: dict[str, str]
ROMS_FOLDER_NAME: str
SAVES_FOLDER_NAME: str
STATES_FOLDER_NAME: str
SCREENSHOTS_FOLDER_NAME: str
HIGH_PRIO_STRUCTURE_PATH: str
def __init__(self, **entries):
@@ -133,15 +130,6 @@ class ConfigManager:
ROMS_FOLDER_NAME=pydash.get(
self._raw_config, "filesystem.roms_folder", "roms"
),
SAVES_FOLDER_NAME=pydash.get(
self._raw_config, "filesystem.saves_folder", "saves"
),
STATES_FOLDER_NAME=pydash.get(
self._raw_config, "filesystem.states_folder", "states"
),
SCREENSHOTS_FOLDER_NAME=pydash.get(
self._raw_config, "filesystem.screenshots_folder", "screenshots"
),
)
def _validate_config(self):
@@ -212,40 +200,6 @@ class ConfigManager:
)
sys.exit(3)
if not isinstance(self.config.SAVES_FOLDER_NAME, str):
log.critical("Invalid config.yml: filesystem.saves_folder must be a string")
sys.exit(3)
if self.config.SAVES_FOLDER_NAME == "":
log.critical(
"Invalid config.yml: filesystem.saves_folder cannot be an empty string"
)
sys.exit(3)
if not isinstance(self.config.STATES_FOLDER_NAME, str):
log.critical(
"Invalid config.yml: filesystem.states_folder must be a string"
)
sys.exit(3)
if self.config.STATES_FOLDER_NAME == "":
log.critical(
"Invalid config.yml: filesystem.states_folder cannot be an empty string"
)
sys.exit(3)
if not isinstance(self.config.SCREENSHOTS_FOLDER_NAME, str):
log.critical(
"Invalid config.yml: filesystem.screenshots_folder must be a string"
)
sys.exit(3)
if self.config.SCREENSHOTS_FOLDER_NAME == "":
log.critical(
"Invalid config.yml: filesystem.screenshots_folder cannot be an empty string"
)
sys.exit(3)
def read_config(self) -> None:
try:
with open(self.config_file) as config_file:

View File

@@ -18,9 +18,6 @@ def test_config_loader():
assert loader.config.PLATFORMS_BINDING == {"gc": "ngc"}
assert loader.config.PLATFORMS_VERSIONS == {"naomi": "arcade"}
assert loader.config.ROMS_FOLDER_NAME == "ROMS"
assert loader.config.SAVES_FOLDER_NAME == "SAVES"
assert loader.config.STATES_FOLDER_NAME == "STATES"
assert loader.config.SCREENSHOTS_FOLDER_NAME == "SCREENSHOTS"
def test_empty_config_loader():
@@ -35,6 +32,3 @@ def test_empty_config_loader():
assert loader.config.PLATFORMS_BINDING == {}
assert loader.config.PLATFORMS_VERSIONS == {}
assert loader.config.ROMS_FOLDER_NAME == "roms"
assert loader.config.SAVES_FOLDER_NAME == "saves"
assert loader.config.STATES_FOLDER_NAME == "states"
assert loader.config.SCREENSHOTS_FOLDER_NAME == "screenshots"

View File

@@ -1,17 +1,39 @@
from fastapi import APIRouter, Request
from fastapi.responses import FileResponse
from decorators.auth import protected_route
from config import LIBRARY_BASE_PATH
from config import LIBRARY_BASE_PATH, ASSETS_BASE_PATH
router = APIRouter()
@protected_route(router.head, "/raw/{path:path}", ["assets.read"])
@protected_route(router.head, "/raw/roms/{path:path}", ["roms.read"])
def head_raw_rom(request: Request, path: str):
rom_path = f"{LIBRARY_BASE_PATH}/{path}"
return FileResponse(path=rom_path, filename=path.split("/")[-1])
@protected_route(router.get, "/raw/roms/{path:path}", ["roms.read"])
def get_raw_rom(request: Request, path: str):
"""Download a single rom file
Args:
request (Request): Fastapi Request object
Returns:
FileResponse: Returns a single rom file
"""
rom_path = f"{LIBRARY_BASE_PATH}/{path}"
return FileResponse(path=rom_path, filename=path.split("/")[-1])
@protected_route(router.head, "/raw/assets/{path:path}", ["assets.read"])
def head_raw_asset(request: Request, path: str):
asset_path = f"{LIBRARY_BASE_PATH}/{path}"
asset_path = f"{ASSETS_BASE_PATH}/{path}"
return FileResponse(path=asset_path, filename=path.split("/")[-1])
@protected_route(router.get, "/raw/{path:path}", ["assets.read"])
@protected_route(router.get, "/raw/assets/{path:path}", ["assets.read"])
def get_raw_asset(request: Request, path: str):
"""Download a single asset file
@@ -22,6 +44,5 @@ def get_raw_asset(request: Request, path: str):
FileResponse: Returns a single asset file
"""
asset_path = f"{LIBRARY_BASE_PATH}/{path}"
asset_path = f"{ASSETS_BASE_PATH}/{path}"
return FileResponse(path=asset_path, filename=path.split("/")[-1])

View File

@@ -11,7 +11,4 @@ class ConfigResponse(TypedDict):
PLATFORMS_BINDING: dict[str, str]
PLATFORMS_VERSIONS: dict[str, str]
ROMS_FOLDER_NAME: str
SAVES_FOLDER_NAME: str
STATES_FOLDER_NAME: str
SCREENSHOTS_FOLDER_NAME: str
HIGH_PRIO_STRUCTURE_PATH: str

View File

@@ -46,7 +46,6 @@ class RomSchema(BaseModel):
url_screenshots: list[str]
merged_screenshots: list[str]
full_path: str
download_path: str
class Config:
from_attributes = True

View File

@@ -20,7 +20,6 @@ from fastapi_pagination.ext.sqlalchemy import paginate
from handler import (
db_platform_handler,
db_rom_handler,
fs_asset_handler,
fs_resource_handler,
fs_rom_handler,
)
@@ -257,7 +256,7 @@ async def update_rom(
)
cleaned_data.update(
fs_asset_handler.get_rom_screenshots(
fs_resource_handler.get_rom_screenshots(
platform_fs_slug=platform_fs_slug,
rom_name=cleaned_data["name"],
url_screenshots=cleaned_data.get("url_screenshots", []),

View File

@@ -1,12 +1,15 @@
from config.config_manager import config_manager as cm
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.assets import UploadedSavesResponse, SaveSchema
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from handler import db_save_handler, fs_asset_handler, db_rom_handler, db_screenshot_handler
from handler.scan_handler import scan_save, build_asset_file_path
from handler import (
db_save_handler,
fs_asset_handler,
db_rom_handler,
db_screenshot_handler,
)
from handler.scan_handler import scan_save
from logger.logger import log
from config import LIBRARY_BASE_PATH
router = APIRouter()
@@ -27,19 +30,18 @@ def add_saves(
detail="No saves were uploaded",
)
saves_path = build_asset_file_path(
rom.platform.fs_slug, folder=cm.config.SAVES_FOLDER_NAME, emulator=emulator
saves_path = fs_asset_handler.build_saves_file_path(
user=request.user, platform_fs_slug=rom.platform.fs_slug, emulator=emulator
)
for save in saves:
fs_asset_handler._write_file(
file=save, path=f"{LIBRARY_BASE_PATH}/{saves_path}"
)
fs_asset_handler.write_file(file=save, path=saves_path)
# Scan or update save
scanned_save = scan_save(
file_name=save.filename,
platform_slug=rom.platform.fs_slug,
user=request.user,
platform_fs_slug=rom.platform.fs_slug,
emulator=emulator,
)
db_save = db_save_handler.get_save_by_filename(rom.id, save.filename)
@@ -50,6 +52,7 @@ def add_saves(
continue
scanned_save.rom_id = rom.id
scanned_save.user_id = request.user.id
scanned_save.emulator = emulator
db_save_handler.add_save(scanned_save)
@@ -79,9 +82,7 @@ async def update_save(request: Request, id: int) -> SaveSchema:
if "file" in data:
file: UploadFile = data["file"]
fs_asset_handler._write_file(
file=file, path=f"{LIBRARY_BASE_PATH}/{db_save.file_path}"
)
fs_asset_handler.write_file(file=file, path=db_save.file_path)
db_save_handler.update_save(db_save.id, {"file_size_bytes": file.size})
db_save = db_save_handler.get_save(id)
@@ -119,7 +120,7 @@ async def delete_saves(request: Request) -> MessageResponse:
error = f"Save file {save.file_name} not found for platform {save.rom.platform_slug}"
log.error(error)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
if save.screenshot:
db_screenshot_handler.delete_screenshot(save.screenshot.id)

View File

@@ -1,4 +1,3 @@
from config.config_manager import config_manager as cm
from decorators.auth import protected_route
from endpoints.responses.assets import UploadedScreenshotsResponse
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
@@ -22,16 +21,18 @@ def add_screenshots(
detail="No screenshots were uploaded",
)
screenshots_path = fs_asset_handler.build_upload_file_path(
rom.platform.fs_slug, folder=cm.config.SCREENSHOTS_FOLDER_NAME
screenshots_path = fs_asset_handler.build_screenshots_file_path(
user=request.user, platform_fs_slug=rom.platform_slug
)
for screenshot in screenshots:
fs_asset_handler._write_file(file=screenshot, path=screenshots_path)
fs_asset_handler.write_file(file=screenshot, path=screenshots_path)
# Scan or update screenshot
scanned_screenshot = scan_screenshot(
file_name=screenshot.filename, platform_slug=rom.platform_slug
file_name=screenshot.filename,
user=request.user,
platform_fs_slug=rom.platform_slug,
)
db_screenshot = db_screenshot_handler.get_screenshot_by_filename(
file_name=screenshot.filename, rom_id=rom.id
@@ -44,6 +45,7 @@ def add_screenshots(
continue
scanned_screenshot.rom_id = rom.id
scanned_screenshot.user_id = request.user.id
db_screenshot_handler.add_screenshot(scanned_screenshot)
rom = db_rom_handler.get_roms(rom_id)

View File

@@ -10,23 +10,15 @@ from exceptions.fs_exceptions import (
from handler import (
db_platform_handler,
db_rom_handler,
db_save_handler,
db_screenshot_handler,
db_state_handler,
fs_asset_handler,
fs_platform_handler,
fs_resource_handler,
fs_rom_handler,
socket_handler,
)
from handler.fs_handler import Asset
from handler.redis_handler import high_prio_queue, redis_url
from handler.scan_handler import (
scan_platform,
scan_rom,
scan_save,
scan_screenshot,
scan_state,
)
from logger.logger import log
@@ -129,133 +121,9 @@ async def scan_platforms(
},
)
# Scanning saves
fs_saves = fs_asset_handler.get_assets(
platform.fs_slug, rom.file_name_no_ext, Asset.SAVES
)
if len(fs_saves) > 0:
log.info(f"\t · {len(fs_saves)} saves found")
for fs_emulator, fs_save_filename in fs_saves:
scanned_save = scan_save(
file_name=fs_save_filename,
platform_slug=platform.fs_slug,
emulator=fs_emulator,
)
save = db_save_handler.get_save_by_filename(rom.id, fs_save_filename)
if save:
# Update file size if changed
if save.file_size_bytes != scanned_save.file_size_bytes:
db_save_handler.update_save(
save.id, {"file_size_bytes": scanned_save.file_size_bytes}
)
continue
scanned_save.emulator = fs_emulator
if rom:
scanned_save.rom_id = rom.id
db_save_handler.add_save(scanned_save)
# Scanning states
fs_states = fs_asset_handler.get_assets(
platform.fs_slug, rom.file_name_no_ext, Asset.STATES
)
if len(fs_states) > 0:
log.info(f"\t · {len(fs_states)} states found")
for fs_emulator, fs_state_filename in fs_states:
scanned_state = scan_state(
file_name=fs_state_filename,
platform_slug=platform.fs_slug,
emulator=fs_emulator,
)
state = db_state_handler.get_state_by_filename(
rom.id, fs_state_filename
)
if state:
# Update file size if changed
if state.file_size_bytes != scanned_state.file_size_bytes:
db_state_handler.update_state(
state.id, {"file_size_bytes": scanned_state.file_size_bytes}
)
continue
scanned_state.emulator = fs_emulator
if rom:
scanned_state.rom_id = rom.id
db_state_handler.add_state(scanned_state)
# Scanning screenshots
fs_screenshots = fs_asset_handler.get_assets(
platform.fs_slug, rom.file_name_no_ext, Asset.SCREENSHOTS
)
if len(fs_screenshots) > 0:
log.info(f"\t · {len(fs_screenshots)} screenshots found")
for _, fs_screenshot_filename in fs_screenshots:
scanned_screenshot = scan_screenshot(
file_name=fs_screenshot_filename, platform_slug=platform.fs_slug
)
screenshot = db_screenshot_handler.get_screenshot_by_filename(
fs_screenshot_filename
)
if screenshot:
# Update file size if changed
if screenshot.file_size_bytes != scanned_screenshot.file_size_bytes:
db_screenshot_handler.update_screenshot(
screenshot.id,
{"file_size_bytes": scanned_screenshot.file_size_bytes},
)
continue
if rom:
scanned_screenshot.rom_id = rom.id
db_screenshot_handler.add_screenshot(scanned_screenshot)
db_save_handler.purge_saves(rom.id, [s for _e, s in fs_saves])
db_state_handler.purge_states(rom.id, [s for _e, s in fs_states])
db_screenshot_handler.purge_screenshots(
rom.id, [s for _e, s in fs_screenshots]
)
db_rom_handler.purge_roms(
platform.id, [rom["file_name"] for rom in fs_roms]
)
# Scanning screenshots outside platform folders
fs_screenshots = fs_asset_handler.get_screenshots()
log.info("Screenshots")
log.info(f" · {len(fs_screenshots)} screenshots found")
for fs_platform, fs_screenshot_filename in fs_screenshots:
scanned_screenshot = scan_screenshot(
file_name=fs_screenshot_filename, platform_slug=fs_platform
)
screenshot = db_screenshot_handler.get_screenshot_by_filename(
fs_screenshot_filename
)
if screenshot:
# Update file size if changed
if screenshot.file_size_bytes != scanned_screenshot.file_size_bytes:
db_screenshot_handler.update_screenshot(
screenshot.id,
{"file_size_bytes": scanned_screenshot.file_size_bytes},
)
continue
rom = db_rom_handler.get_rom_by_filename_no_tags(
scanned_screenshot.file_name_no_tags
)
if rom:
scanned_screenshot.rom_id = rom.id
db_screenshot_handler.add_screenshot(scanned_screenshot)
# db_screenshot_handler.purge_screenshots([s for _e, s in fs_screenshots])
db_platform_handler.purge_platforms(fs_platforms)
log.info(emoji.emojize(":check_mark: Scan completed "))

View File

@@ -1,12 +1,15 @@
from config.config_manager import config_manager as cm
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.assets import UploadedStatesResponse, StateSchema
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from handler import db_state_handler, db_rom_handler, fs_asset_handler, db_screenshot_handler
from handler.scan_handler import scan_state, build_asset_file_path
from handler import (
db_state_handler,
db_rom_handler,
fs_asset_handler,
db_screenshot_handler,
)
from handler.scan_handler import scan_state
from logger.logger import log
from config import LIBRARY_BASE_PATH
router = APIRouter()
@@ -26,20 +29,19 @@ def add_states(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="No states were uploaded",
)
states_path = build_asset_file_path(
rom.platform.fs_slug, folder=cm.config.STATES_FOLDER_NAME, emulator=emulator
states_path = fs_asset_handler.build_states_file_path(
user=request.user, platform_fs_slug=rom.platform.fs_slug, emulator=emulator
)
for state in states:
fs_asset_handler._write_file(
file=state, path=f"{LIBRARY_BASE_PATH}/{states_path}"
)
fs_asset_handler.write_file(file=state, path=states_path)
# Scan or update state
scanned_state = scan_state(
file_name=state.filename,
platform_slug=rom.platform.fs_slug,
user=request.user,
platform_fs_slug=rom.platform.fs_slug,
emulator=emulator,
)
db_state = db_state_handler.get_state_by_filename(rom.id, state.filename)
@@ -50,6 +52,7 @@ def add_states(
continue
scanned_state.rom_id = rom.id
scanned_state.user_id = request.user.id
scanned_state.emulator = emulator
db_state_handler.add_state(scanned_state)
@@ -79,9 +82,7 @@ async def update_state(request: Request, id: int) -> StateSchema:
if "file" in data:
file: UploadFile = data["file"]
fs_asset_handler._write_file(
file=file, path=f"{LIBRARY_BASE_PATH}/{db_state.file_path}"
)
fs_asset_handler.write_file(file=file, path=db_state.file_path)
db_state_handler.update_state(db_state.id, {"file_size_bytes": file.size})
db_state = db_state_handler.get_state(id)

View File

@@ -18,6 +18,3 @@ def test_config():
assert config.get('EXCLUDED_MULTI_PARTS_FILES') == []
assert config.get('PLATFORMS_BINDING') == {}
assert config.get('ROMS_FOLDER_NAME') == 'roms'
assert config.get('SAVES_FOLDER_NAME') == 'saves'
assert config.get('STATES_FOLDER_NAME') == 'states'
assert config.get('SCREENSHOTS_FOLDER_NAME') == 'screenshots'

View File

@@ -6,13 +6,15 @@ from endpoints.forms.identity import UserForm
from endpoints.responses import MessageResponse
from endpoints.responses.identity import UserSchema
from fastapi import APIRouter, Depends, HTTPException, Request, status
from handler import auth_handler, db_user_handler, fs_resource_handler
from handler import auth_handler, db_user_handler, fs_asset_handler
from models.user import Role, User
router = APIRouter()
@protected_route(router.post, "/users", ["users.write"], status_code=status.HTTP_201_CREATED)
@protected_route(
router.post, "/users", ["users.write"], status_code=status.HTTP_201_CREATED
)
def add_user(request: Request, username: str, password: str, role: str) -> UserSchema:
"""Create user endpoint
@@ -131,7 +133,9 @@ def update_user(
cleaned_data["username"] = form_data.username.lower()
if form_data.password:
cleaned_data["hashed_password"] = auth_handler.get_password_hash(form_data.password)
cleaned_data["hashed_password"] = auth_handler.get_password_hash(
form_data.password
)
# You can't change your own role
if form_data.role and request.user.id != id:
@@ -142,10 +146,9 @@ def update_user(
cleaned_data["enabled"] = form_data.enabled # type: ignore[assignment]
if form_data.avatar is not None:
cleaned_data["avatar_path"], avatar_user_path = fs_resource_handler.build_avatar_path(
form_data.avatar.filename, form_data.username
)
file_location = f"{avatar_user_path}/{form_data.avatar.filename}"
user_avatar_path = fs_asset_handler.build_avatar_path(user=user)
file_location = f"{user_avatar_path}/{form_data.avatar.filename}"
cleaned_data["avatar_path"] = file_location
with open(file_location, "wb+") as file_object:
file_object.write(form_data.avatar.file.read())

View File

@@ -53,8 +53,7 @@ class AuthHandler:
def get_password_hash(self, password):
return self.pwd_context.hash(password)
@staticmethod
def clear_session(req: HTTPConnection | Request):
def clear_session(self, req: HTTPConnection | Request):
session_id = req.session.get("session_id")
if session_id:
cache.delete(f"romm:{session_id}") # type: ignore[attr-defined]

View File

@@ -6,8 +6,7 @@ from sqlalchemy.orm import Session
class DBRomsHandler(DBHandler):
@staticmethod
def _filter(data, platform_id, search_term):
def _filter(self, data, platform_id, search_term):
if platform_id:
data = data.filter_by(platform_id=platform_id)
@@ -21,8 +20,7 @@ class DBRomsHandler(DBHandler):
return data
@staticmethod
def _order(data, order_by, order_dir):
def _order(self, data, order_by, order_dir):
if order_by == "id":
_column = Rom.id
else:

View File

@@ -1,14 +1,8 @@
import os
import re
import shutil
from abc import ABC
from enum import Enum
from typing import Final
from config import LIBRARY_BASE_PATH, ROMM_BASE_PATH
from config.config_manager import config_manager as cm
RESOURCES_BASE_PATH: Final = f"{ROMM_BASE_PATH}/resources"
DEFAULT_WIDTH_COVER_L: Final = 264 # Width of big cover of IGDB
DEFAULT_HEIGHT_COVER_L: Final = 352 # Height of big cover of IGDB
DEFAULT_WIDTH_COVER_S: Final = 90 # Width of small cover of IGDB
@@ -90,37 +84,13 @@ class FSHandler(ABC):
def __init__(self) -> None:
pass
@staticmethod
def get_fs_structure(fs_slug: str, folder: str = cm.config.ROMS_FOLDER_NAME):
return (
f"{folder}/{fs_slug}"
if os.path.exists(cm.config.HIGH_PRIO_STRUCTURE_PATH)
else f"{fs_slug}/{folder}"
)
@staticmethod
def get_file_name_with_no_extension(file_name: str) -> str:
def get_file_name_with_no_extension(self, file_name: str) -> str:
return re.sub(EXTENSION_REGEX, "", file_name).strip()
@staticmethod
def get_file_name_with_no_tags(file_name: str) -> str:
file_name_no_extension = FSHandler.get_file_name_with_no_extension(file_name)
def get_file_name_with_no_tags(self, file_name: str) -> str:
file_name_no_extension = self.get_file_name_with_no_extension(file_name)
return re.split(TAG_REGEX, file_name_no_extension)[0].strip()
@staticmethod
def parse_file_extension(file_name) -> str:
def parse_file_extension(self, file_name) -> str:
match = re.search(EXTENSION_REGEX, file_name)
return match.group(1) if match else ""
@staticmethod
def remove_file(file_name: str, file_path: str):
try:
os.remove(f"{LIBRARY_BASE_PATH}/{file_path}/{file_name}")
except IsADirectoryError:
shutil.rmtree(f"{LIBRARY_BASE_PATH}/{file_path}/{file_name}")
def build_upload_file_path(
self, fs_slug: str, folder: str = cm.config.ROMS_FOLDER_NAME
):
file_path = self.get_fs_structure(fs_slug, folder=folder)
return f"{LIBRARY_BASE_PATH}/{file_path}"

View File

@@ -1,130 +1,66 @@
import os
import shutil
from pathlib import Path
from urllib.parse import quote
import requests
from config import LIBRARY_BASE_PATH
from config.config_manager import config_manager as cm
from fastapi import UploadFile
from handler.fs_handler import RESOURCES_BASE_PATH, Asset, FSHandler
from handler.fs_handler import FSHandler
from logger.logger import log
from models.user import User
from config import ASSETS_BASE_PATH
class FSAssetsHandler(FSHandler):
def __init__(self) -> None:
pass
@staticmethod
def _write_file(file: UploadFile, path: str) -> None:
def remove_file(self, file_name: str, file_path: str):
try:
os.remove(os.path.join(ASSETS_BASE_PATH, file_path, file_name))
except IsADirectoryError:
shutil.rmtree(os.path.join(ASSETS_BASE_PATH, file_path, file_name))
def write_file(self, file: UploadFile, path: str) -> None:
log.info(f" - Uploading {file.filename}")
file_location = f"{path}/{file.filename}"
file_location = os.path.join(ASSETS_BASE_PATH, path, file.filename)
Path(path).mkdir(parents=True, exist_ok=True)
with open(file_location, "wb") as f:
shutil.copyfileobj(file.file, f)
@staticmethod
def _store_screenshot(fs_slug: str, rom_name: str, url: str, idx: int):
"""Store roms resources in filesystem
def get_asset_size(self, file_name: str, asset_path: str) -> int:
return os.path.getsize(os.path.join(ASSETS_BASE_PATH, asset_path, file_name))
Args:
fs_slug: short name of the platform
file_name: name of rom
url: url to get the screenshot
"""
screenshot_file: str = f"{idx}.jpg"
screenshot_path: str = f"{RESOURCES_BASE_PATH}/{fs_slug}/{rom_name}/screenshots"
res = requests.get(url, stream=True, timeout=120)
if res.status_code == 200:
Path(screenshot_path).mkdir(parents=True, exist_ok=True)
with open(f"{screenshot_path}/{screenshot_file}", "wb") as f:
shutil.copyfileobj(res.raw, f)
def user_folder_path(self, user: User):
return os.path.join("users", user.fs_safe_folder_name)
@staticmethod
def _get_screenshot_path(fs_slug: str, rom_name: str, idx: str):
"""Returns rom cover filesystem path adapted to frontend folder structure
Args:
fs_slug: short name of the platform
file_name: name of rom
idx: index number of screenshot
"""
return f"{fs_slug}/{rom_name}/screenshots/{idx}.jpg"
def get_rom_screenshots(
self, platform_fs_slug: str, rom_name: str, url_screenshots: list
) -> dict:
q_rom_name = quote(rom_name)
path_screenshots: list[str] = []
for idx, url in enumerate(url_screenshots):
self._store_screenshot(platform_fs_slug, rom_name, url, idx)
path_screenshots.append(
self._get_screenshot_path(platform_fs_slug, q_rom_name, str(idx))
)
return {"path_screenshots": path_screenshots}
def get_assets(
self, platform_slug: str, rom_file_name_no_ext: str, asset_type: Asset
):
asset_folder_name = {
Asset.SAVES: cm.config.SAVES_FOLDER_NAME,
Asset.STATES: cm.config.STATES_FOLDER_NAME,
Asset.SCREENSHOTS: cm.config.SCREENSHOTS_FOLDER_NAME,
}
assets_path = self.get_fs_structure(
platform_slug, folder=asset_folder_name[asset_type]
# /users/557365723a31/profile
def build_avatar_path(self, user: User):
user_avatar_path = os.path.join(
ASSETS_BASE_PATH, self.user_folder_path(user), "profile"
)
Path(user_avatar_path).mkdir(parents=True, exist_ok=True)
return user_avatar_path
assets_file_path = f"{LIBRARY_BASE_PATH}/{assets_path}"
def _build_asset_file_path(
self, user: User, folder: str, platform_fs_slug, emulator: str = None
):
user_folder_path = self.user_folder_path(user)
if emulator:
return os.path.join(user_folder_path, folder, platform_fs_slug, emulator)
return os.path.join(user_folder_path, folder, platform_fs_slug)
assets: list[str] = []
# /users/557365723a31/saves/n64/mupen64plus
def build_saves_file_path(
self, user: User, platform_fs_slug: str, emulator: str = None
):
return self._build_asset_file_path(user, "saves", platform_fs_slug, emulator)
try:
emulators = list(os.walk(assets_file_path))[0][1]
for emulator in emulators:
assets += [
(emulator, file)
for file in list(os.walk(f"{assets_file_path}/{emulator}"))[0][2]
if rom_file_name_no_ext
== self.get_file_name_with_no_extension(file)
]
# /users/557365723a31/states/n64/mupen64plus
def build_states_file_path(
self, user: User, platform_fs_slug: str, emulator: str = None
):
return self._build_asset_file_path(user, "states", platform_fs_slug, emulator)
assets += [
(None, file)
for file in list(os.walk(assets_file_path))[0][2]
if rom_file_name_no_ext == self.get_file_name_with_no_extension(file)
]
except IndexError:
pass
return assets
@staticmethod
def get_screenshots():
screenshots_path = f"{LIBRARY_BASE_PATH}/{cm.config.SCREENSHOTS_FOLDER_NAME}"
fs_screenshots = []
try:
platforms = list(os.walk(screenshots_path))[0][1]
for platform in platforms:
fs_screenshots += [
(platform, file)
for file in list(os.walk(f"{screenshots_path}/{platform}"))[0][2]
]
fs_screenshots += [
(None, file) for file in list(os.walk(screenshots_path))[0][2]
]
except IndexError:
pass
return fs_screenshots
@staticmethod
def get_asset_size(asset_path: str, file_name: str):
return os.stat(f"{LIBRARY_BASE_PATH}/{asset_path}/{file_name}").st_size
# /users/557365723a31/screenshots/n64
def build_screenshots_file_path(self, user: User, platform_fs_slug: str):
return self._build_asset_file_path(user, "screenshots", platform_fs_slug)

View File

@@ -10,8 +10,7 @@ class FSPlatformsHandler(FSHandler):
def __init__(self) -> None:
pass
@staticmethod
def _exclude_platforms(platforms: list):
def _exclude_platforms(self, platforms: list):
return [
platform
for platform in platforms

View File

@@ -10,13 +10,13 @@ from config import (
DEFAULT_PATH_COVER_S,
DEFAULT_URL_COVER_L,
DEFAULT_URL_COVER_S,
RESOURCES_BASE_PATH,
)
from handler.fs_handler import (
DEFAULT_HEIGHT_COVER_L,
DEFAULT_HEIGHT_COVER_S,
DEFAULT_WIDTH_COVER_L,
DEFAULT_WIDTH_COVER_S,
RESOURCES_BASE_PATH,
CoverSize,
FSHandler,
)
@@ -27,8 +27,7 @@ class FSResourceHandler(FSHandler):
def __init__(self) -> None:
pass
@staticmethod
def _cover_exists(fs_slug: str, rom_name: str, size: CoverSize):
def _cover_exists(self, fs_slug: str, rom_name: str, size: CoverSize):
"""Check if rom cover exists in filesystem
Args:
@@ -44,8 +43,7 @@ class FSResourceHandler(FSHandler):
)
)
@staticmethod
def _resize_cover(cover_path: str, size: CoverSize) -> None:
def _resize_cover(self, cover_path: str, size: CoverSize) -> None:
"""Resizes the cover image to the standard size
Args:
@@ -99,8 +97,7 @@ class FSResourceHandler(FSHandler):
shutil.copyfileobj(res.raw, f)
self._resize_cover(f"{cover_path}/{cover_file}", size)
@staticmethod
def _get_cover_path(fs_slug: str, rom_name: str, size: CoverSize):
def _get_cover_path(self, fs_slug: str, rom_name: str, size: CoverSize):
"""Returns rom cover filesystem path adapted to frontend folder structure
Args:
@@ -152,8 +149,7 @@ class FSResourceHandler(FSHandler):
if not self._cover_exists("default", "default", cover["size"]):
self._store_cover("default", "default", cover["url"], cover["size"])
@staticmethod
def build_artwork_path(rom_name: str, fs_slug: str, file_ext: str):
def build_artwork_path(self, rom_name: str, fs_slug: str, file_ext: str):
q_rom_name = quote(rom_name)
strtime = str(datetime.datetime.now().timestamp())
@@ -162,9 +158,43 @@ class FSResourceHandler(FSHandler):
artwork_path = f"{RESOURCES_BASE_PATH}/{fs_slug}/{rom_name}/cover"
Path(artwork_path).mkdir(parents=True, exist_ok=True)
return path_cover_l, path_cover_s, artwork_path
def _store_screenshot(self, fs_slug: str, rom_name: str, url: str, idx: int):
"""Store roms resources in filesystem
@staticmethod
def build_avatar_path(avatar_path: str, username: str):
avatar_user_path = f"{RESOURCES_BASE_PATH}/users/{username}"
Path(avatar_user_path).mkdir(parents=True, exist_ok=True)
return f"users/{username}/{avatar_path}", avatar_user_path
Args:
fs_slug: short name of the platform
file_name: name of rom
url: url to get the screenshot
"""
screenshot_file = f"{idx}.jpg"
screenshot_path = f"{RESOURCES_BASE_PATH}/{fs_slug}/{rom_name}/screenshots"
res = requests.get(url, stream=True, timeout=120)
if res.status_code == 200:
Path(screenshot_path).mkdir(parents=True, exist_ok=True)
with open(f"{screenshot_path}/{screenshot_file}", "wb") as f:
shutil.copyfileobj(res.raw, f)
def _get_screenshot_path(self, fs_slug: str, rom_name: str, idx: str):
"""Returns rom cover filesystem path adapted to frontend folder structure
Args:
fs_slug: short name of the platform
file_name: name of rom
idx: index number of screenshot
"""
return f"{fs_slug}/{rom_name}/screenshots/{idx}.jpg"
def get_rom_screenshots(
self, platform_fs_slug: str, rom_name: str, url_screenshots: list
) -> dict:
q_rom_name = quote(rom_name)
path_screenshots: list[str] = []
for idx, url in enumerate(url_screenshots):
self._store_screenshot(platform_fs_slug, rom_name, url, idx)
path_screenshots.append(
self._get_screenshot_path(platform_fs_slug, q_rom_name, str(idx))
)
return {"path_screenshots": path_screenshots}

View File

@@ -2,6 +2,7 @@ import fnmatch
import os
import re
from pathlib import Path
import shutil
from config import LIBRARY_BASE_PATH
from config.config_manager import config_manager as cm
@@ -21,8 +22,20 @@ class FSRomsHandler(FSHandler):
def __init__(self) -> None:
pass
@staticmethod
def parse_tags(file_name: str) -> tuple:
def get_fs_structure(self, fs_slug: str):
return (
f"{cm.config.ROMS_FOLDER_NAME}/{fs_slug}"
if os.path.exists(cm.config.HIGH_PRIO_STRUCTURE_PATH)
else f"{fs_slug}/{cm.config.ROMS_FOLDER_NAME}"
)
def remove_file(self, file_name: str, file_path: str):
try:
os.remove(f"{LIBRARY_BASE_PATH}/{file_path}/{file_name}")
except IsADirectoryError:
shutil.rmtree(f"{LIBRARY_BASE_PATH}/{file_path}/{file_name}")
def parse_tags(self, file_name: str) -> tuple:
rev = ""
regs = []
langs = []
@@ -91,8 +104,7 @@ class FSRomsHandler(FSHandler):
# Return files that are not in the filtered list.
return [f for f in files if f not in excluded_files]
@staticmethod
def _exclude_multi_roms(roms) -> list[str]:
def _exclude_multi_roms(self, roms) -> list[str]:
excluded_names = cm.config.EXCLUDED_MULTI_FILES
filtered_files: list = []
@@ -148,9 +160,8 @@ class FSRomsHandler(FSHandler):
for rom in fs_roms
]
@staticmethod
def get_rom_file_size(
roms_path: str, file_name: str, multi: bool, multi_files: list = []
self, roms_path: str, file_name: str, multi: bool, multi_files: list = []
):
files = (
[f"{LIBRARY_BASE_PATH}/{roms_path}/{file_name}"]
@@ -162,8 +173,7 @@ class FSRomsHandler(FSHandler):
)
return sum([os.stat(file).st_size for file in files])
@staticmethod
def file_exists(path: str, file_name: str):
def file_exists(self, path: str, file_name: str):
"""Check if file exists in filesystem
Args:
@@ -183,3 +193,7 @@ class FSRomsHandler(FSHandler):
f"{LIBRARY_BASE_PATH}/{file_path}/{old_name}",
f"{LIBRARY_BASE_PATH}/{file_path}/{new_name}",
)
def build_upload_file_path(self, fs_slug: str):
file_path = self.get_fs_structure(fs_slug)
return f"{LIBRARY_BASE_PATH}/{file_path}"

View File

@@ -11,8 +11,7 @@ class GHHandler:
def __init__(self) -> None:
pass
@staticmethod
def get_version() -> str:
def get_version(self) -> str:
"""Returns current version or branch name."""
if not __version__ == "<version>":
return __version__

View File

@@ -82,8 +82,7 @@ class IGDBHandler:
return wrapper
@staticmethod
def normalize_search_term(search_term: str) -> str:
def _normalize_search_term(self, search_term: str) -> str:
return (
search_term.replace("\u2122", "") # Remove trademark symbol
.replace("\u00ae", "") # Remove registered symbol
@@ -143,8 +142,7 @@ class IGDBHandler:
return pydash.get(exact_matches or roms, "[0]", {})
@staticmethod
def _normalize_cover_url(url: str) -> str:
def _normalize_cover_url(self, url: str) -> str:
return f"https:{url.replace('https:', '')}"
def _search_cover(self, rom_id: int) -> str:
@@ -172,8 +170,7 @@ class IGDBHandler:
if "url" in r.keys()
]
@staticmethod
async def _ps2_opl_format(match: re.Match[str], search_term: str) -> str:
async def _ps2_opl_format(self, match: re.Match[str], search_term: str) -> str:
serial_code = match.group(1)
with open(PS2_OPL_INDEX_FILE, "r") as index_json:
@@ -184,8 +181,7 @@ class IGDBHandler:
return search_term
@staticmethod
async def _switch_titledb_format(match: re.Match[str], search_term: str) -> str:
async def _switch_titledb_format(self, match: re.Match[str], search_term: str) -> str:
titledb_index = {}
title_id = match.group(1)
@@ -207,8 +203,7 @@ class IGDBHandler:
return search_term
@staticmethod
async def _switch_productid_format(match: re.Match[str], search_term: str) -> str:
async def _switch_productid_format(self, match: re.Match[str], search_term: str) -> str:
product_id_index = {}
product_id = match.group(1)
@@ -317,7 +312,7 @@ class IGDBHandler:
if platform_idgb_id in ARCADE_IGDB_IDS:
search_term = await self._mame_format(search_term)
search_term = self.normalize_search_term(search_term)
search_term = self._normalize_search_term(search_term)
res = (
self._search_rom(uc(search_term), platform_idgb_id, MAIN_GAME_CATEGORY)

View File

@@ -1,4 +1,3 @@
import os
from typing import Any
import emoji
@@ -14,6 +13,7 @@ from logger.logger import log
from models.assets import Save, Screenshot, State
from models.platform import Platform
from models.rom import Rom
from models.user import User
SWAPPED_PLATFORM_BINDINGS = dict((v, k) for k, v in cm.config.PLATFORMS_BINDING.items())
@@ -160,7 +160,7 @@ async def scan_rom(
)
)
rom_attrs.update(
fs_asset_handler.get_rom_screenshots(
fs_resource_handler.get_rom_screenshots(
platform_fs_slug=platform.slug,
rom_name=rom_attrs["name"],
url_screenshots=rom_attrs["url_screenshots"],
@@ -185,34 +185,28 @@ def _scan_asset(file_name: str, path: str):
}
def build_asset_file_path(fs_slug: str, folder: str, emulator: str = None):
saves_path = fs_asset_handler.get_fs_structure(fs_slug, folder=folder)
if emulator:
return os.path.join(saves_path, emulator)
return saves_path
def scan_save(file_name: str, platform_slug: str, emulator: str = None) -> Save:
saves_path = build_asset_file_path(
platform_slug, folder=cm.config.SAVES_FOLDER_NAME, emulator=emulator
def scan_save(
file_name: str, user: User, platform_fs_slug: str, emulator: str = None
) -> Save:
saves_path = fs_asset_handler.build_saves_file_path(
user=user, platform_fs_slug=platform_fs_slug, emulator=emulator
)
return Save(**_scan_asset(file_name, saves_path))
def scan_state(file_name: str, platform_slug: str, emulator: str = None) -> State:
states_path = build_asset_file_path(
platform_slug, folder=cm.config.STATES_FOLDER_NAME, emulator=emulator
def scan_state(
file_name: str, user: User, platform_fs_slug: str, emulator: str = None
) -> State:
states_path = fs_asset_handler.build_states_file_path(
user=user, platform_fs_slug=platform_fs_slug, emulator=emulator
)
return State(**_scan_asset(file_name, states_path))
def scan_screenshot(file_name: str, platform_slug: str = None) -> Screenshot:
if not platform_slug:
return Screenshot(**_scan_asset(file_name, cm.config.SCREENSHOTS_FOLDER_NAME))
screenshots_path = build_asset_file_path(
platform_slug, folder=cm.config.SCREENSHOTS_FOLDER_NAME
def scan_screenshot(
file_name: str, user: User, platform_fs_slug: str = None
) -> Screenshot:
screenshots_path = fs_asset_handler.build_screenshots_file_path(
user=user, platform_fs_slug=platform_fs_slug
)
return Screenshot(**_scan_asset(file_name, screenshots_path))

View File

@@ -27,6 +27,9 @@ class BaseAsset(BaseModel):
rom_id = Column(
Integer(), ForeignKey("roms.id", ondelete="CASCADE"), nullable=False
)
user_id = Column(
Integer(), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
@cached_property
def full_path(self) -> str:
@@ -34,7 +37,7 @@ class BaseAsset(BaseModel):
@cached_property
def download_path(self) -> str:
return f"/api/raw/{self.full_path}?timestamp={self.updated_at}"
return f"/api/raw/assets/{self.full_path}?timestamp={self.updated_at}"
class Save(BaseAsset):
@@ -44,6 +47,7 @@ class Save(BaseAsset):
emulator = Column(String(length=50), nullable=True)
rom = relationship("Rom", lazy="selectin", back_populates="saves")
user = relationship("User", lazy="selectin", back_populates="saves")
@cached_property
def screenshot(self) -> Optional["Screenshot"]:
@@ -64,6 +68,7 @@ class State(BaseAsset):
emulator = Column(String(length=50), nullable=True)
rom = relationship("Rom", lazy="selectin", back_populates="states")
user = relationship("User", lazy="selectin", back_populates="states")
@cached_property
def screenshot(self) -> Optional["Screenshot"]:
@@ -82,3 +87,4 @@ class Screenshot(BaseAsset):
__table_args__ = {"extend_existing": True}
rom = relationship("Rom", lazy="selectin", back_populates="screenshots")
user = relationship("User", lazy="selectin", back_populates="screenshots")

View File

@@ -8,7 +8,16 @@ from config import (
)
from models.assets import Save, Screenshot, State
from models.base import BaseModel
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String, Text, BigInteger
from sqlalchemy import (
JSON,
Boolean,
Column,
ForeignKey,
Integer,
String,
Text,
BigInteger,
)
from sqlalchemy.orm import Mapped, relationship
SORT_COMPARE_REGEX = r"^([Tt]he|[Aa]|[Aa]nd)\s"
@@ -86,7 +95,7 @@ class Rom(BaseModel):
@cached_property
def download_path(self) -> str:
return f"/api/raw/{self.full_path}"
return f"/api/raw/roms/{self.full_path}"
@cached_property
def has_cover(self) -> bool:

View File

@@ -1,8 +1,10 @@
import enum
from models.base import BaseModel
from models.assets import Save, Screenshot, State
from sqlalchemy import Boolean, Column, Enum, Integer, String
from starlette.authentication import SimpleUser
from sqlalchemy.orm import Mapped, relationship
class Role(enum.Enum):
@@ -23,6 +25,18 @@ class User(BaseModel, SimpleUser):
role: Role = Column(Enum(Role), default=Role.VIEWER)
avatar_path: str = Column(String(length=255), default="")
saves: Mapped[list[Save]] = relationship(
"Save",
lazy="selectin",
back_populates="user",
)
states: Mapped[list[State]] = relationship(
"State", lazy="selectin", back_populates="user"
)
screenshots: Mapped[list[Screenshot]] = relationship(
"Screenshot", lazy="selectin", back_populates="user"
)
@property
def oauth_scopes(self):
from handler.auth_handler import DEFAULT_SCOPES, FULL_SCOPES, WRITE_SCOPES
@@ -34,3 +48,8 @@ class User(BaseModel, SimpleUser):
return WRITE_SCOPES
return DEFAULT_SCOPES
@property
def fs_safe_folder_name(self):
# Uses the ID to avoid issues with username changes
return f'User:{self.id}'.encode("utf-8").hex()

View File

@@ -14,6 +14,7 @@ COPY ./frontend/assets/platforms ${WEBSERVER_FOLDER}/assets/platforms
COPY ./frontend/assets/webrcade/feed ${WEBSERVER_FOLDER}/assets/webrcade/feed
RUN mkdir -p ${WEBSERVER_FOLDER}/assets/romm && \
ln -s /romm/resources ${WEBSERVER_FOLDER}/assets/romm/resources
ln -s /romm/assets ${WEBSERVER_FOLDER}/assets/romm/assets
# install generall required packages
RUN apk add --upgrade \

View File

@@ -43,6 +43,3 @@ system:
filesystem:
roms_folder: 'roms' # The folder where your roms are located
saves_folder: 'saves' # The folder where your save files are located
states_folder: 'states' # The folder where your states are located
screenshots_folder: 'screenshots' # The folder where your screenshots are located

View File

@@ -40,7 +40,8 @@ services:
- SCHEDULED_UPDATE_MAME_XML_CRON=0 5 * * * # Cron expression for the scheduled update (default: 0 5 * * * - At 5:00 AM every day)
volumes:
- "/path/to/library:/romm/library"
- "/path/to/resources:/romm/resources" # [Optional] Path where roms metadata (covers) are stored
- "/path/to/assets:/romm/assets" # Path where saves, states and other assets are stored
- "/path/to/resources:/romm/resources" # Path where roms metadata (covers) are stored
- "/path/to/config:/romm/config" # [Optional] Path where config is stored
- "/path/to/database:/romm/database" # [Optional] Only needed if ROMM_DB_DRIVER=sqlite or not set
- "/path/to/logs:/romm/logs" # [Optional] Path where logs are stored

View File

@@ -13,9 +13,6 @@ export type ConfigResponse = {
PLATFORMS_BINDING: Record<string, string>;
PLATFORMS_VERSIONS: Record<string, string>;
ROMS_FOLDER_NAME: string;
SAVES_FOLDER_NAME: string;
STATES_FOLDER_NAME: string;
SCREENSHOTS_FOLDER_NAME: string;
HIGH_PRIO_STRUCTURE_PATH: string;
};

View File

@@ -41,7 +41,6 @@ export type EnhancedRomSchema = {
url_screenshots: Array<string>;
merged_screenshots: Array<string>;
full_path: string;
download_path: string;
sibling_roms: Array<RomSchema>;
};

View File

@@ -40,6 +40,5 @@ export type RomSchema = {
url_screenshots: Array<string>;
merged_screenshots: Array<string>;
full_path: string;
download_path: string;
};

View File

@@ -122,7 +122,7 @@ function closeDialog() {
<v-img
:src="
user.avatar_path
? `/assets/romm/resources/${user.avatar_path}`
? `/assets/romm/assets/${user.avatar_path}`
: defaultAvatarPath
"
/>

View File

@@ -61,7 +61,7 @@ async function logout() {
<v-img
:src="
auth.user?.avatar_path
? `/assets/romm/resources/${auth.user?.avatar_path}`
? `/assets/romm/assets/${auth.user?.avatar_path}`
: defaultAvatarPath
"
/>

View File

@@ -123,7 +123,7 @@ onMounted(() => {
<v-img
:src="
item.raw.avatar_path
? `/assets/romm/resources/${item.raw.avatar_path}`
? `/assets/romm/assets/${item.raw.avatar_path}`
: defaultAvatarPath
"
/>