Merge branch 'master' into fix/ui_tweaks

This commit is contained in:
Zurdi
2024-05-24 14:25:26 +02:00
committed by GitHub
132 changed files with 511 additions and 424 deletions

View File

@@ -6,5 +6,3 @@ labels: other
assignees: ''
---

View File

@@ -6,5 +6,3 @@ labels: ui/ux
assignees: ''
---

View File

@@ -228,4 +228,4 @@
</g>
</g>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -256,4 +256,4 @@
</g>
</g>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

21
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,21 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: 'https://github.com/pre-commit/pre-commit-hooks'
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: 'https://github.com/astral-sh/ruff-pre-commit'
rev: v0.4.4
hooks:
- id: ruff
args:
- '--fix'
- id: ruff-format
# - repo: 'https://github.com/pre-commit/mirrors-mypy'
# rev: v1.10.0
# hooks:
# - id: mypy

View File

@@ -1,13 +1,13 @@
# v3.0.3 (_25-03-2024_)
## Fixed
## Fixed
- Removed ``PUID/GUID`` environment variables. Instead, use the ``user: "XXXX:XXXX"`` entry in docker-compose to set the owner of newly created file/folders and mounted volumes
<br>
# v3.0.2 (_25-03-2024_)
> [!WARNING]
> [!WARNING]
> RomM has been organized in ``github`` and ``dockerhub``! New images will be upload to ``rommapp/romm`` repository on dockerhub and ghcr.
## Added
@@ -29,7 +29,7 @@
# v3.0.1 (_13-03-2024_)
> [!WARNING]
> [!WARNING]
> This hotfix requires a few changes:
* A new volume has to be bound to `/redis-data`, check the [docker-compose.example.yml file](https://github.com/rommapp/romm/blob/3.0.1/examples/docker-compose.example.yml)
@@ -89,7 +89,7 @@
## Changed
- Platforms can only be deleted from RomM's database. Checkbox to delete platform from filesystem have been removed.
<br>
# v2.3.0 (_08-01-2024_)
@@ -123,7 +123,7 @@
## Changed
- Now region and language tags are case insensitive to show ``emojis``.
<br>
# v2.2.0 (_31-12-2023_)
@@ -132,7 +132,7 @@
- Support for ``productID`` in the file name for ``switch`` titles.
- Rom name sorting now sorts smarter, avoiding leading articles such ``The`` or ``A``, like in ``The Legend of Zelda``. Closes [#449](https://github.com/rommapp/romm/issues/449) and [#450](https://github.com/rommapp/romm/issues/450)
- Support for file names with multiple ``regions`` and ``languages``. Also uses ``emojis`` to display them. Closes [#473](https://github.com/rommapp/romm/issues/473)
- Added a button to manually ``run`` all tasks. Closes [#437](https://github.com/rommapp/romm/issues/437)
- Added a button to manually ``run`` all tasks. Closes [#437](https://github.com/rommapp/romm/issues/437)
- Now if a game doesn't have cover, it will show a screenshot if available. Closes [#455](https://github.com/rommapp/romm/issues/455)
- Added a little warning icon in the platform selector if the platform is not found by IGDB.
- Now if a platform is not found by IGDB, the platform name is ``titleized``. Ex: ``pocket-challenge-v2 -> Pocket Challenge V2``. Closes [#486](https://github.com/rommapp/romm/issues/486)
@@ -140,7 +140,7 @@
- Support for support for AES/MVS. Closes [#503](https://github.com/rommapp/romm/issues/503)
- Added [Helm Chart](https://artifacthub.io/packages/helm/crystalnet/romm) to deploy on Kubernetes by @psych0d0g
- ``Rescan unidentified`` added to the scan view, allowing to rescan only those entries that IGDB couldn't identify in previous scans. Closes [#519](https://github.com/rommapp/romm/issues/519)
- ``Config file`` visualization added to the new ``Config`` tab in the ``Control Panel``. Partially implements some concepts of [#457](https://github.com/rommapp/romm/issues/457)
- ``Config file`` visualization added to the new ``Config`` tab in the ``Control Panel``. Partially implements some concepts of [#457](https://github.com/rommapp/romm/issues/457)
## Fixed
- Now sorting by size in the gallery table view works as expected. Closes [#423](https://github.com/rommapp/romm/issues/423)
@@ -156,7 +156,7 @@
- Fixed multi-part games download when any part of the game contains a ``comma`` in the name. Closes [#520](https://github.com/rommapp/romm/issues/520)
## Changed
- Improved the docker ``init scripts`` handling by @psych0d0g.
- Improved the docker ``init scripts`` handling by @psych0d0g.
- Now the ``scan`` can continue after failing finding roms for one platform. Closes [#460](https://github.com/rommapp/romm/issues/460)
- Logs improved a lot.
@@ -382,7 +382,7 @@
## Fixed
- Download feature is now fixed for RomM structucture 1
## Changed
- Library path binding changed from ``/library`` to ``/romm/library``. Check [docker-compose](examples/docker-compose.example.yml)
- Library path binding changed from ``/library`` to ``/romm/library``. Check [docker-compose](examples/docker-compose.example.yml)
<br>
@@ -403,7 +403,7 @@
# v1.5 (_30-03-2023_)
> [!WARNING]
> [!WARNING]
> In order to make the new features to work, it is mandatory this time to drop all the database. This will only make you need to re-scan, but you won't lose the cover changes or file changes you made. I apologize for the inconveniences this may cause, as this is a new software, it may change a little bit the first weeks, at least until I can develop a proper way to migrate between versions. I hope you can understand these initial wipes in order to make a better tool.
## Added
@@ -435,7 +435,7 @@
## Fixed
**`Breaking change`** - **This breaking change only applies for mariaDB users**:
Some users reported errors when scanning files with large names because file_names are limited to 100 characters in the database. As I want to give as much flexibility as possible I changed some database columns.
Some users reported errors when scanning files with large names because file_names are limited to 100 characters in the database. As I want to give as much flexibility as possible I changed some database columns.
If you didn't make a lot of manual changes you can just get rid of the database and recreate it, scanning your library again. If you did some changes and don't want to lose the progress, you should do this changes manually from the mariadb container (or wherever you have your mariadb database) since there is not any kind of CLI for this migration.
@@ -486,8 +486,8 @@ Columns to modify (examples in case that you set it with database name as romm,
- Game names parentheses are now omitted when searching game in IGDB, allowing game names to have tags.
<br>
# v1.0 (_27-03-2023_)
## Added
- Birth of RomM

View File

@@ -24,7 +24,7 @@ cp env.template .env
### - Install system dependencies
```sh
# https://mariadb.com/docs/skysql-previous-release/connect/programming-languages/c/install/#Installation_via_Package_Repository_(Linux):
# https://mariadb.com/docs/skysql-previous-release/connect/programming-languages/c/install/#Installation_via_Package_Repository_(Linux):
sudo apt install libmariadb3 libmariadb-dev pipx
```

View File

@@ -1,7 +1,7 @@
<div align="center">
<img src=".github/resources/romm_complete.svg" height="220px" width="auto" alt="romm logo">
<h3 style="font-size: 25px;">
A beautiful, powerful, self-hosted rom manager.
</h3>
@@ -18,7 +18,7 @@
</div>
</div>
> [!WARNING]
> [!WARNING]
> Version 3.0 introduces exciting new fetures that require some changes to how RomM is setup and configured. **If you're currently running a 2.x version, please review the [migration guide](https://github.com/rommapp/romm/wiki/Upgrading-to-3.0) before upgrading.**
# Overview
@@ -51,7 +51,7 @@ Before running the [image][docker-tags], ensure that Docker is installed and run
3. Create a docker-compose.yml file by referring to the example [docker-compose.yml][docker-compose-example] file for guidance, and customize it for your setup with [the available environment variables][wiki-env-variables].
4. Launch the container(s) with `docker-compose up -d`.
> [!NOTE]
> [!NOTE]
> **If you are having issues with RomM, please review the [wiki page][wiki-troubleshooting-url] for troubleshooting steps and common issues.**
# Configuration
@@ -92,6 +92,14 @@ As mentioned in the installation section, RomM requires a specific folder struct
│ │ ├─ my_game_cd2.iso
│ │
│ ├─ rom_1.iso
├─ bios/
│ ├─ gba/
│ │ ├─ gba_bios.bin
│ │
│ ├─ ps/
│ ├─ scph1001.bin
| ├─ scph5501.bin
| ├─ scph5502.bin
</pre>
</td>
<td>
@@ -106,6 +114,8 @@ As mentioned in the installation section, RomM requires a specific folder struct
│ ├─ roms/
│ ├─ rom_1.gba
│ ├─ rom_2.gba
| ├─ bios/
| ├─ gba_bios.bin
├─ ps/
│ ├─ roms/
@@ -114,6 +124,10 @@ As mentioned in the installation section, RomM requires a specific folder struct
│ │ ├─ my_game_cd2.iso
│ │
│ ├─ rom_1.iso
| ├─ bios/
| ├─ scph1001.bin
| ├─ scph5501.bin
| ├─ scph5502.bin
</pre>
</td>
</tr>

View File

@@ -5,6 +5,7 @@ Revises: 2.0.0
Create Date: 2023-09-12 18:18:27.158732
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.exc import OperationalError
@@ -87,7 +88,7 @@ def upgrade() -> None:
nullable=True,
existing_server_default=sa.text("'[]'"),
)
try:
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.create_foreign_key(

View File

@@ -5,6 +5,7 @@ Revises: 0009_models_refactor
Create Date: 2023-09-14 09:57:13.487331
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

View File

@@ -5,6 +5,7 @@ Revises: 0010_igdb_id_integerr
Create Date: 2023-09-16 15:28:29.221475
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

View File

@@ -5,40 +5,43 @@ Revises: 0011_drop_has_cover
Create Date: 2023-12-03 10:54:46.859106
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '0012_add_regions_languages'
down_revision = '0011_drop_has_cover'
revision = "0012_add_regions_languages"
down_revision = "0011_drop_has_cover"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('roms', schema=None) as batch_op:
batch_op.add_column(sa.Column('regions', sa.JSON(), nullable=True))
batch_op.add_column(sa.Column('languages', sa.JSON(), nullable=True))
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.add_column(sa.Column("regions", sa.JSON(), nullable=True))
batch_op.add_column(sa.Column("languages", sa.JSON(), nullable=True))
with op.batch_alter_table('roms', schema=None) as batch_op:
with op.batch_alter_table("roms", schema=None) as batch_op:
# Set default values for languages and regions
batch_op.execute("UPDATE roms SET languages = '[]'")
batch_op.execute("UPDATE roms SET regions = JSON_ARRAY(region)")
batch_op.drop_column('region')
batch_op.drop_column("region")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('roms', schema=None) as batch_op:
batch_op.add_column(sa.Column('region', mysql.VARCHAR(length=20), nullable=True))
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.add_column(
sa.Column("region", mysql.VARCHAR(length=20), nullable=True)
)
with op.batch_alter_table('roms', schema=None) as batch_op:
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.execute("UPDATE roms SET region = JSON_EXTRACT(regions, '$[0]')")
batch_op.drop_column('languages')
batch_op.drop_column('regions')
batch_op.drop_column("languages")
batch_op.drop_column("regions")
# ### end Alembic commands ###

View File

@@ -62,7 +62,9 @@ def migrate_to_mysql() -> None:
if table_name == "alembic_version":
continue
table_data = sqlite_conn.execute(text(f"SELECT * FROM {table_name}")).fetchall()
table_data = sqlite_conn.execute(
text(f"SELECT * FROM {table_name}")
).fetchall()
# Insert data into MariaDB table
for row in table_data:

View File

@@ -5,6 +5,7 @@ Revises: 0014_asset_files
Create Date: 2024-02-13 17:57:25.936825
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

View File

@@ -5,35 +5,42 @@ Revises: 0016_user_last_login_active
Create Date: 2024-04-28 11:58:18.927734
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0017_rom_notes'
down_revision = '0016_user_last_login_active'
revision = "0017_rom_notes"
down_revision = "0016_user_last_login_active"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('rom_notes',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('last_edited_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('raw_markdown', sa.Text(), nullable=False),
sa.Column('is_public', sa.Boolean(), nullable=True),
sa.Column('rom_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['rom_id'], ['roms.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('rom_id', 'user_id', name='unique_rom_user_note')
op.create_table(
"rom_notes",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column(
"last_edited_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column("raw_markdown", sa.Text(), nullable=False),
sa.Column("is_public", sa.Boolean(), nullable=True),
sa.Column("rom_id", sa.Integer(), nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["rom_id"], ["roms.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("rom_id", "user_id", name="unique_rom_user_note"),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('rom_notes')
op.drop_table("rom_notes")
# ### end Alembic commands ###

View File

@@ -1,10 +1,11 @@
"""init to 1.6.2
Revision ID: 1.6.2
Revises:
Revises:
Create Date: 2023-04-10 23:02:37.472055
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.exc import OperationalError

View File

@@ -5,6 +5,7 @@ Revises: 1.6.2
Create Date: 2023-04-10 23:13:43.591414
"""
from alembic import op
import sqlalchemy as sa

View File

@@ -5,6 +5,7 @@ Revises: 1.6.3
Create Date: 2023-04-15 02:28:24.023871
"""
from alembic import op
import sqlalchemy as sa

View File

@@ -5,6 +5,7 @@ Revises: 1.8
Create Date: 2023-04-17 12:03:19.163501
"""
from alembic import op
import sqlalchemy as sa

View File

@@ -5,6 +5,7 @@ Revises: 1.8.1
Create Date: 2023-05-09 00:00:19.143526
"""
from alembic import op
import sqlalchemy as sa

View File

@@ -5,8 +5,8 @@ Revises: 1.8.2
Create Date: 2023-05-17 12:59:44.344356
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.

View File

@@ -5,6 +5,7 @@ Revises: 1.7.1
Create Date: 2023-04-17 12:03:19.163501
"""
from alembic import op
import sqlalchemy as sa

View File

@@ -5,6 +5,7 @@ Revises: 1.8.3
Create Date: 2023-08-10 22:18:24.012779
"""
from alembic import op
import sqlalchemy as sa

View File

@@ -6,10 +6,11 @@ from dotenv import load_dotenv
load_dotenv()
# UVICORN
# GUNICORN
DEV_PORT: Final = int(os.environ.get("VITE_BACKEND_DEV_PORT", "5000"))
DEV_HOST: Final = "0.0.0.0"
ROMM_HOST: Final = os.environ.get("ROMM_HOST", DEV_HOST)
GUNICORN_WORKERS: Final = int(os.environ.get("GUNICORN_WORKERS", 2))
# PATHS
ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm")

View File

@@ -13,7 +13,7 @@ def begin_session(func):
try:
with args[0].session.begin() as s:
kwargs['session'] = s
kwargs["session"] = s
return func(*args, **kwargs)
except ProgrammingError as e:
log.critical(str(e))

View File

@@ -10,7 +10,7 @@ from endpoints.responses.heartbeat import HeartbeatResponse
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
from fastapi import APIRouter
from handler.github_handler import github_handler
from utils import get_version
router = APIRouter()
@@ -24,8 +24,7 @@ def heartbeat() -> HeartbeatResponse:
"""
return {
"VERSION": github_handler.get_version(),
"NEW_VERSION": github_handler.check_new_version(),
"VERSION": get_version(),
"ANY_SOURCE_ENABLED": IGDB_API_ENABLED or MOBY_API_ENABLED,
"METADATA_SOURCES": {
"IGDB_API_ENABLED": IGDB_API_ENABLED,
@@ -49,5 +48,5 @@ def heartbeat() -> HeartbeatResponse:
"TITLE": "Scheduled Switch TitleDB update",
"MESSAGE": "Updates the Nintendo Switch TitleDB file",
},
}
},
}

View File

@@ -88,9 +88,7 @@ def get_platform(request: Request, id: int) -> PlatformSchema:
PlatformSchema: Platform
"""
return PlatformSchema.from_orm_with_request(
db_platform_handler.get_platforms(id), request
)
return db_platform_handler.get_platforms(id)
@protected_route(router.put, "/platforms/{id}", ["platforms.write"])

View File

@@ -23,7 +23,6 @@ class MetadataSourcesDict(TypedDict):
class HeartbeatResponse(TypedDict):
VERSION: str
NEW_VERSION: str
WATCHER: WatcherDict
SCHEDULER: SchedulerDict
ANY_SOURCE_ENABLED: bool

View File

@@ -1,9 +1,9 @@
from typing import Optional
from pydantic import BaseModel, Field
from fastapi import Request
from models.platform import Platform
from .firmware import FirmwareSchema
class PlatformSchema(BaseModel):
id: int
slug: str
@@ -14,16 +14,7 @@ class PlatformSchema(BaseModel):
name: str
logo_path: Optional[str] = ""
rom_count: int
firmware_files: list[FirmwareSchema] = Field(default_factory=list)
firmware: list[FirmwareSchema] = Field(default_factory=list)
class Config:
from_attributes = True
@classmethod
def from_orm_with_request(cls, db_platform: Platform, request: Request) -> "PlatformSchema":
platform = cls.model_validate(db_platform)
platform.firmware_files = [
FirmwareSchema.model_validate(f) for f in sorted(db_platform.firmware, key=lambda x: x.file_name)
]
return platform

View File

@@ -1,5 +1,6 @@
from pydantic import BaseModel
class SearchRomSchema(BaseModel):
igdb_id: int | None = None
moby_id: int | None = None

View File

@@ -23,7 +23,7 @@ from handler.filesystem.base_handler import CoverSize
from handler.metadata import meta_igdb_handler, meta_moby_handler
from logger.logger import log
from stream_zip import ZIP_AUTO, stream_zip # type: ignore[import]
from urllib.parse import quote
router = APIRouter()
@@ -156,7 +156,7 @@ def head_rom_content(request: Request, id: int, file_name: str):
path=rom_path if not rom.multi else f"{rom_path}/{rom.files[0]}",
filename=file_name,
headers={
"Content-Disposition": f'attachment; filename="{rom.name}.zip"',
"Content-Disposition": f'attachment; filename="{quote(rom.name)}.zip"',
"Content-Type": "application/zip",
"Content-Length": str(rom.file_size_bytes),
},
@@ -236,7 +236,7 @@ def get_rom_content(
return CustomStreamingResponse(
zipped_chunks,
media_type="application/zip",
headers={"Content-Disposition": f'attachment; filename="{file_name}.zip"'},
headers={"Content-Disposition": f'attachment; filename="{quote(file_name)}.zip"'},
emit_body={"id": rom.id},
)

View File

@@ -30,9 +30,7 @@ async def run_task(request: Request, task: str) -> MessageResponse:
RunTasksResponse: Standard message response
"""
tasks = {
"switch_titledb": update_switch_titledb_task
}
tasks = {"switch_titledb": update_switch_titledb_task}
await tasks[task].run()
return {"msg": f"Task {task} run successfully!"}

View File

@@ -1,8 +1,18 @@
import pytest
from datetime import timedelta
from handler.tests.conftest import setup_database, clear_database, admin_user, editor_user, viewer_user, platform, rom, save, state # noqa
from handler.auth import oauth_handler
from handler.tests.conftest import ( # noqa
setup_database,
clear_database,
admin_user,
editor_user,
viewer_user,
platform,
rom,
save,
state,
)
from ..auth import ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS

View File

@@ -14,7 +14,7 @@ def test_delete_saves(access_token, save):
assert response.status_code == 200
body = response.json()
assert body['msg'] == "Successfully deleted 1 saves"
assert body["msg"] == "Successfully deleted 1 saves"
def test_delete_states(access_token, state):
@@ -26,4 +26,4 @@ def test_delete_states(access_token, state):
assert response.status_code == 200
body = response.json()
assert body['msg'] == "Successfully deleted 1 states"
assert body["msg"] == "Successfully deleted 1 states"

View File

@@ -10,12 +10,12 @@ def test_config():
assert response.status_code == 200
config = response.json()
assert config.get('EXCLUDED_PLATFORMS') == []
assert config.get('EXCLUDED_SINGLE_EXT') == []
assert config.get('EXCLUDED_SINGLE_FILES') == []
assert config.get('EXCLUDED_MULTI_FILES') == []
assert config.get('EXCLUDED_MULTI_PARTS_EXT') == []
assert config.get('EXCLUDED_MULTI_PARTS_FILES') == []
assert config.get('PLATFORMS_BINDING') == {}
assert config.get('ROMS_FOLDER_NAME') == 'roms'
assert config.get('FIRMWARE_FOLDER_NAME') == 'bios'
assert config.get("EXCLUDED_PLATFORMS") == []
assert config.get("EXCLUDED_SINGLE_EXT") == []
assert config.get("EXCLUDED_SINGLE_FILES") == []
assert config.get("EXCLUDED_MULTI_FILES") == []
assert config.get("EXCLUDED_MULTI_PARTS_EXT") == []
assert config.get("EXCLUDED_MULTI_PARTS_FILES") == []
assert config.get("PLATFORMS_BINDING") == {}
assert config.get("ROMS_FOLDER_NAME") == "roms"
assert config.get("FIRMWARE_FOLDER_NAME") == "bios"

View File

@@ -1,7 +1,7 @@
from fastapi.testclient import TestClient
from main import app
from handler.github_handler import github_handler
from utils import get_version
client = TestClient(app)
@@ -9,14 +9,17 @@ client = TestClient(app)
def test_heartbeat():
response = client.get("/heartbeat")
assert response.status_code == 200
heartbeat = response.json()
assert heartbeat.get('VERSION') == github_handler.get_version()
assert heartbeat.get('WATCHER').get('ENABLED')
assert heartbeat.get('WATCHER').get('TITLE') == "Rescan on filesystem change"
assert heartbeat.get('SCHEDULER').get('RESCAN').get('ENABLED')
assert heartbeat.get('SCHEDULER').get('RESCAN').get('CRON') == "0 3 * * *"
assert heartbeat.get('SCHEDULER').get('RESCAN').get('TITLE') == "Scheduled rescan"
assert heartbeat.get('SCHEDULER').get('SWITCH_TITLEDB').get('ENABLED')
assert heartbeat.get('SCHEDULER').get('SWITCH_TITLEDB').get('CRON') == "0 4 * * *"
assert heartbeat.get('SCHEDULER').get('SWITCH_TITLEDB').get('TITLE') == "Scheduled Switch TitleDB update"
assert heartbeat.get("VERSION") == get_version()
assert heartbeat.get("WATCHER").get("ENABLED")
assert heartbeat.get("WATCHER").get("TITLE") == "Rescan on filesystem change"
assert heartbeat.get("SCHEDULER").get("RESCAN").get("ENABLED")
assert heartbeat.get("SCHEDULER").get("RESCAN").get("CRON") == "0 3 * * *"
assert heartbeat.get("SCHEDULER").get("RESCAN").get("TITLE") == "Scheduled rescan"
assert heartbeat.get("SCHEDULER").get("SWITCH_TITLEDB").get("ENABLED")
assert heartbeat.get("SCHEDULER").get("SWITCH_TITLEDB").get("CRON") == "0 4 * * *"
assert (
heartbeat.get("SCHEDULER").get("SWITCH_TITLEDB").get("TITLE")
== "Scheduled Switch TitleDB update"
)

View File

@@ -6,7 +6,9 @@ client = TestClient(app)
def test_get_raw_asset(access_token):
response = client.get("/raw/assets/users/557365723a31/saves/n64/mupen64/Super Mario 64 (J) (Rev A).sav")
response = client.get(
"/raw/assets/users/557365723a31/saves/n64/mupen64/Super Mario 64 (J) (Rev A).sav"
)
assert response.status_code == 403
response = client.get(

View File

@@ -112,9 +112,7 @@ def update_user(
cleaned_data = {}
if form_data.username and form_data.username != user.username:
existing_user = db_user_handler.get_user_by_username(
form_data.username.lower()
)
existing_user = db_user_handler.get_user_by_username(form_data.username.lower())
if existing_user:
raise HTTPException(
status_code=400, detail="Username already in use by another user"

View File

@@ -19,4 +19,4 @@ OAuthCredentialsException = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
)

View File

@@ -5,7 +5,8 @@ class ConfigNotReadableException(Exception):
def __repr__(self) -> str:
return self.message
class ConfigNotWritableException(Exception):
def __init__(self):
self.message = "Config file is not writable. Check config.yml permissions"

View File

@@ -45,14 +45,18 @@ class RomAlreadyExistsException(Exception):
def __repr__(self):
return self.message
class FirmwareNotFoundException(Exception):
def __init__(self, platform: str):
self.message = f"Firmware not found for platform {platform}. {folder_struct_msg}"
self.message = (
f"Firmware not found for platform {platform}. {folder_struct_msg}"
)
super().__init__(self.message)
def __repr__(self):
return self.message
class FirmwareAlreadyExistsException(Exception):
def __init__(self, firmware_name: str):
self.message = f"Can't rename: {firmware_name} already exists"

View File

@@ -1 +1,7 @@
from handler.tests.conftest import setup_database, clear_database, admin_user, editor_user, viewer_user # noqa
from handler.tests.conftest import ( # noqa
setup_database,
clear_database,
admin_user,
editor_user,
viewer_user,
)

View File

@@ -10,8 +10,12 @@ from handler.auth.hybrid_auth import HybridAuthBackend
def test_verify_password():
assert auth_handler.verify_password("password", auth_handler.get_password_hash("password"))
assert not auth_handler.verify_password("password", auth_handler.get_password_hash("notpassword"))
assert auth_handler.verify_password(
"password", auth_handler.get_password_hash("password")
)
assert not auth_handler.verify_password(
"password", auth_handler.get_password_hash("notpassword")
)
def test_authenticate_user(admin_user: User):

View File

@@ -1,6 +1,6 @@
from decorators.database import begin_session
from models.firmware import Firmware
from sqlalchemy import update, delete, and_
from sqlalchemy import update, delete, and_, select
from sqlalchemy.orm import Session
from .base_handler import DBBaseHandler
@@ -16,19 +16,19 @@ class DBFirmwareHandler(DBBaseHandler):
self, id: int = None, platform_id: int = None, session: Session = None
):
return (
session.get(Firmware, id)
session.scalar(select(Firmware).filter_by(id=id).limit(1))
if id
else session.query(Firmware).filter_by(platform_id=platform_id).all()
else select(Firmware).filter_by(platform_id=platform_id).all()
)
@begin_session
def get_firmware_by_filename(
self, platform_id: int, file_name: str, session: Session = None
):
return (
session.query(Firmware)
return session.scalar(
select(Firmware)
.filter_by(platform_id=platform_id, file_name=file_name)
.first()
.limit(1)
)
@begin_session

View File

@@ -1,21 +1,26 @@
from sqlalchemy import delete, or_, select
from sqlalchemy.orm import Session
from decorators.database import begin_session
from models.platform import Platform
from models.rom import Rom
from sqlalchemy import delete, func, or_, select
from sqlalchemy.orm import Session
from .base_handler import DBBaseHandler
class DBPlatformsHandler(DBBaseHandler):
@begin_session
def add_platform(self, platform: Platform, session: Session = None):
def add_platform(
self, platform: Platform, session: Session = None
) -> Platform | None:
return session.merge(platform)
@begin_session
def get_platforms(self, id: int = None, session: Session = None):
def get_platforms(
self, id: int = None, session: Session = None
) -> list[Platform] | Platform | None:
return (
session.get(Platform, id)
session.scalar(select(Platform).filter_by(id=id).limit(1))
if id
else (
session.scalars(select(Platform).order_by(Platform.name.asc()))
@@ -25,13 +30,13 @@ class DBPlatformsHandler(DBBaseHandler):
)
@begin_session
def get_platform_by_fs_slug(self, fs_slug: str, session: Session = None):
return session.scalars(
select(Platform).filter_by(fs_slug=fs_slug).limit(1)
).first()
def get_platform_by_fs_slug(
self, fs_slug: str, session: Session = None
) -> Platform | None:
return session.scalar(select(Platform).filter_by(fs_slug=fs_slug).limit(1))
@begin_session
def delete_platform(self, id: int, session: Session = None):
def delete_platform(self, id: int, session: Session = None) -> int:
# Remove all roms from that platforms first
session.execute(
delete(Rom)
@@ -45,22 +50,9 @@ class DBPlatformsHandler(DBBaseHandler):
)
@begin_session
def get_rom_count(self, platform_id: int, session: Session = None):
return session.scalar(
select(func.count()).select_from(Rom).filter_by(platform_id=platform_id)
)
@begin_session
def purge_platforms(self, fs_platforms: list[str], session: Session = None):
def purge_platforms(self, fs_platforms: list[str], session: Session = None) -> int:
return session.execute(
delete(Platform)
.where(or_(Platform.fs_slug.not_in(fs_platforms), Platform.slug.is_(None)))
.where(
select(func.count())
.select_from(Rom)
.filter_by(platform_id=Platform.id)
.as_scalar()
== 0
)
.execution_options(synchronize_session="fetch")
)

View File

@@ -1,5 +1,6 @@
import os
import re
import fnmatch
from abc import ABC
from enum import Enum
from typing import Final
@@ -113,3 +114,28 @@ class FSHandler(ABC):
def parse_file_extension(self, file_name) -> str:
match = re.search(EXTENSION_REGEX, file_name)
return match.group(1) if match else ""
def _exclude_files(self, files, filetype) -> list[str]:
cnfg = cm.get_config()
excluded_extensions = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_EXT")
excluded_names = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_FILES")
excluded_files: list = []
for file_name in files:
# Split the file name to get the extension.
ext = self.parse_file_extension(file_name)
# Exclude the file if it has no extension or the extension is in the excluded list.
if not ext or ext in excluded_extensions:
excluded_files.append(file_name)
# Additionally, check if the file name mathes a pattern in the excluded list.
if len(excluded_names) > 0:
[
excluded_files.append(file_name)
for name in excluded_names
if file_name == name or fnmatch.fnmatch(file_name, name)
]
# Return files that are not in the filtered list.
return [f for f in files if f not in excluded_files]

View File

@@ -3,8 +3,8 @@ import shutil
import hashlib
import binascii
from pathlib import Path
from fastapi import UploadFile
from exceptions.fs_exceptions import (
FirmwareNotFoundException,
FirmwareAlreadyExistsException,
@@ -41,7 +41,7 @@ class FSFirmwareHandler(FSHandler):
except IndexError as exc:
raise FirmwareNotFoundException(platform.fs_slug) from exc
return fs_firmware_files
return [f for f in self._exclude_files(fs_firmware_files, "single")]
def get_firmware_file_size(self, firmware_path: str, file_name: str):
files = [f"{LIBRARY_BASE_PATH}/{firmware_path}/{file_name}"]

View File

@@ -1,4 +1,3 @@
import fnmatch
import os
import re
from pathlib import Path
@@ -72,31 +71,6 @@ class FSRomsHandler(FSHandler):
other_tags.append(tag)
return regs, rev, langs, other_tags
def _exclude_files(self, files, filetype) -> list[str]:
cnfg = cm.get_config()
excluded_extensions = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_EXT")
excluded_names = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_FILES")
excluded_files: list = []
for file_name in files:
# Split the file name to get the extension.
ext = self.parse_file_extension(file_name)
# Exclude the file if it has no extension or the extension is in the excluded list.
if not ext or ext in excluded_extensions:
excluded_files.append(file_name)
# Additionally, check if the file name mathes a pattern in the excluded list.
if len(excluded_names) > 0:
[
excluded_files.append(file_name)
for name in excluded_names
if file_name == name or fnmatch.fnmatch(file_name, name)
]
# Return files that are not in the filtered list.
return [f for f in files if f not in excluded_files]
def _exclude_multi_roms(self, roms) -> list[str]:
excluded_names = cm.get_config().EXCLUDED_MULTI_FILES
filtered_files: list = []

View File

@@ -226,8 +226,7 @@ def test_get_file_name_with_no_tags():
file_name = "007 - Agent Under Fire.nkit.iso"
assert (
fs_rom_handler.get_file_name_with_no_tags(file_name)
== "007 - Agent Under Fire"
fs_rom_handler.get_file_name_with_no_tags(file_name) == "007 - Agent Under Fire"
)
file_name = "Jimmy Houston's Bass Tournament U.S.A..zip"
@@ -306,9 +305,7 @@ def test_get_file_name_with_no_extension():
def test_get_file_extension():
assert (
fs_rom_handler.parse_file_extension("Super Mario Bros. (World).nes") == "nes"
)
assert fs_rom_handler.parse_file_extension("Super Mario Bros. (World).nes") == "nes"
assert (
fs_rom_handler.parse_file_extension("007 - Agent Under Fire.nkit.iso")
== "nkit.iso"

View File

@@ -1,60 +0,0 @@
import subprocess as sp
import requests
from __version__ import __version__
from logger.logger import log
from packaging.version import InvalidVersion, parse
from requests.exceptions import ReadTimeout
class GithubHandler:
def __init__(self) -> None:
pass
def get_version(self) -> str:
"""Returns current version or branch name."""
if not __version__ == "<version>":
return __version__
else:
try:
output = str(
sp.check_output(["git", "branch"], universal_newlines=True)
)
except (sp.CalledProcessError, FileNotFoundError):
return "1.0.0"
branch = [a for a in output.split("\n") if a.find("*") >= 0][0]
return branch[branch.find("*") + 2 :]
def check_new_version(self) -> str:
"""Check for new RomM versions
Returns:
str: New RomM version or empty if in dev mode
"""
try:
response = requests.get(
"https://api.github.com/repos/rommapp/romm/releases/latest", timeout=5
)
except ReadTimeout:
log.warning("Timeout while connecting to Github")
return ""
except requests.exceptions.ConnectionError:
log.warning("Could not connect to Github, check your internet connection")
return ""
try:
last_version = response.json()["name"][
1:
] # remove leading 'v' from 'vX.X.X'
except KeyError: # rate limit reached
return ""
try:
if parse(self.get_version()) < parse(last_version):
return last_version
except InvalidVersion:
pass
return ""
github_handler = GithubHandler()

File diff suppressed because one or more lines are too long

View File

@@ -416,7 +416,8 @@ class IGDBBaseHandler(MetadataHandler):
if not IGDB_API_ENABLED:
return []
return [self.get_rom_by_id(igdb_id)]
rom = self.get_rom_by_id(igdb_id)
return [rom] if rom["igdb_id"] else []
@check_twitch_token
def get_matched_roms_by_name(

View File

@@ -253,14 +253,14 @@ class MobyGamesHandler(MetadataHandler):
def get_rom_by_id(self, moby_id: int) -> MobyGamesRom:
if not MOBY_API_ENABLED:
return MobyGamesRom(moby_id=moby_id)
return MobyGamesRom(moby_id=None)
url = yarl.URL(self.games_url).with_query(id=moby_id)
roms = self._request(str(url)).get("games", [])
res = pydash.get(roms, "[0]", None)
if not res:
return MobyGamesRom(moby_id=moby_id)
return MobyGamesRom(moby_id=None)
rom = {
"moby_id": res["game_id"],
@@ -278,7 +278,8 @@ class MobyGamesHandler(MetadataHandler):
if not MOBY_API_ENABLED:
return []
return [self.get_rom_by_id(moby_id)]
rom = self.get_rom_by_id(moby_id)
return [rom] if rom["moby_id"] else []
def get_matched_roms_by_name(
self, search_term: str, platform_moby_id: int

View File

@@ -14,8 +14,18 @@ class QueuePrio(Enum):
LOW = "low"
redis_client = Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, username=REDIS_USERNAME, db=REDIS_DB)
redis_url = f"redis://{REDIS_USERNAME}:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}" if REDIS_PASSWORD else f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
redis_client = Redis(
host=REDIS_HOST,
port=REDIS_PORT,
password=REDIS_PASSWORD,
username=REDIS_USERNAME,
db=REDIS_DB,
)
redis_url = (
f"redis://{REDIS_USERNAME}:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
if REDIS_PASSWORD
else f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
)
high_prio_queue = Queue(name=QueuePrio.HIGH.value, connection=redis_client)
default_queue = Queue(name=QueuePrio.DEFAULT.value, connection=redis_client)

View File

@@ -308,9 +308,7 @@ def _scan_asset(file_name: str, path: str):
"file_path": path,
"file_name": file_name,
"file_name_no_tags": fs_asset_handler.get_file_name_with_no_tags(file_name),
"file_name_no_ext": fs_asset_handler.get_file_name_with_no_extension(
file_name
),
"file_name_no_ext": fs_asset_handler.get_file_name_with_no_extension(file_name),
"file_extension": fs_asset_handler.parse_file_extension(file_name),
"file_size_bytes": file_size,
}

View File

@@ -12,6 +12,9 @@ class SocketHandler:
client_manager=socketio.AsyncRedisManager(redis_url),
)
self.socket_app = socketio.ASGIApp(self.socket_server, socketio_path="/ws/socket.io")
self.socket_app = socketio.ASGIApp(
self.socket_server, socketio_path="/ws/socket.io"
)
socket_handler = SocketHandler()

View File

@@ -32,7 +32,7 @@ async def test_scan_rom():
"multi": False,
"files": ["Paper Mario (USA).z64"],
},
ScanType.QUICK
ScanType.QUICK,
)
assert rom.__class__ == Rom

View File

@@ -3,7 +3,7 @@ import sys
from logger.stdout_formatter import StdoutFormatter
# Get logger
# Set up logger
log = logging.getLogger("romm")
log.setLevel(logging.DEBUG)

View File

@@ -22,11 +22,56 @@ class StdoutFormatter(logging.Formatter):
msg: str = "%(message)s"
date: str = "[%(asctime)s] "
FORMATS: dict = {
logging.DEBUG: COLORS['pink'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier + COLORS['cyan'] + date + COLORS['reset'] + msg,
logging.INFO: COLORS['grey'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier + COLORS['cyan'] + date + COLORS['reset'] + msg,
logging.WARNING: COLORS['orange'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier_warning + COLORS['cyan'] + date + COLORS['reset'] + msg,
logging.ERROR: COLORS['red'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier + COLORS['cyan'] + date + COLORS['reset'] + msg,
logging.CRITICAL: COLORS['bold_red'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier_critical + COLORS['cyan'] + date + COLORS['reset'] + msg
logging.DEBUG: COLORS["pink"]
+ level
+ COLORS["reset"]
+ dots
+ COLORS["blue"]
+ identifier
+ COLORS["cyan"]
+ date
+ COLORS["reset"]
+ msg,
logging.INFO: COLORS["grey"]
+ level
+ COLORS["reset"]
+ dots
+ COLORS["blue"]
+ identifier
+ COLORS["cyan"]
+ date
+ COLORS["reset"]
+ msg,
logging.WARNING: COLORS["orange"]
+ level
+ COLORS["reset"]
+ dots
+ COLORS["blue"]
+ identifier_warning
+ COLORS["cyan"]
+ date
+ COLORS["reset"]
+ msg,
logging.ERROR: COLORS["red"]
+ level
+ COLORS["reset"]
+ dots
+ COLORS["blue"]
+ identifier
+ COLORS["cyan"]
+ date
+ COLORS["reset"]
+ msg,
logging.CRITICAL: COLORS["bold_red"]
+ level
+ COLORS["reset"]
+ dots
+ COLORS["blue"]
+ identifier_critical
+ COLORS["cyan"]
+ date
+ COLORS["reset"]
+ msg,
}
def format(self, record):

View File

@@ -2,17 +2,17 @@ from logger.logger import log
def test_logger(caplog):
log.debug("Testing debug message")
assert "debug message" in caplog.text
log.debug("Testing debug message")
assert "debug message" in caplog.text
log.info("Testing info message")
assert "info message" in caplog.text
log.info("Testing info message")
assert "info message" in caplog.text
log.warning("Testing warning message")
assert "warning message" in caplog.text
log.warning("Testing warning message")
assert "warning message" in caplog.text
log.error("Testing error message")
assert "error message" in caplog.text
log.error("Testing error message")
assert "error message" in caplog.text
log.critical("Testing critical message")
assert "critical message" in caplog.text
log.critical("Testing critical message")
assert "critical message" in caplog.text

View File

@@ -27,13 +27,13 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi_pagination import add_pagination
from handler.database import db_user_handler
from handler.github_handler import github_handler
from handler.socket_handler import socket_handler
from handler.auth import auth_handler
from handler.auth.base_handler import ALGORITHM
from handler.auth.hybrid_auth import HybridAuthBackend
from handler.auth.middleware import CustomCSRFMiddleware, SessionMiddleware
from starlette.middleware.authentication import AuthenticationMiddleware
from utils import get_version
@asynccontextmanager
@@ -46,7 +46,7 @@ async def lifespan(app: FastAPI):
yield
app = FastAPI(title="RomM API", version=github_handler.get_version(), lifespan=lifespan)
app = FastAPI(title="RomM API", version=get_version(), lifespan=lifespan)
app.add_middleware(
CORSMiddleware,

View File

@@ -1,8 +1,9 @@
from sqlalchemy import Column, Integer, String, select, func
from sqlalchemy.orm import Mapped, relationship, column_property
from models.base import BaseModel
from models.rom import Rom
from models.firmware import Firmware
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import Mapped, relationship
class Platform(BaseModel):
@@ -17,18 +18,14 @@ class Platform(BaseModel):
name: str = Column(String(length=400))
logo_path: str = Column(String(length=1000), default="")
roms: Mapped[set[Rom]] = relationship(
"Rom", lazy="selectin", back_populates="platform"
)
firmware: Mapped[set[Firmware]] = relationship(
"Firmware", lazy="selectin", back_populates="platform"
)
@property
def rom_count(self) -> int:
from handler.database import db_platform_handler
return db_platform_handler.get_rom_count(self.id)
# This runs a subquery to get the count of roms for the platform
rom_count = column_property(
select(func.count(Rom.id)).where(Rom.platform_id == id).scalar_subquery()
)
def __repr__(self) -> str:
return self.name

View File

@@ -66,7 +66,7 @@ class Rom(BaseModel):
nullable=False,
)
platform = relationship("Platform", lazy="selectin", back_populates="roms")
platform = relationship("Platform", lazy="immediate")
saves: Mapped[list[Save]] = relationship(
"Save",

View File

@@ -1 +1,12 @@
from handler.tests.conftest import setup_database, clear_database, rom, platform, admin_user, editor_user, viewer_user, save, state, screenshot # noqa
from handler.tests.conftest import ( # noqa
setup_database,
clear_database,
rom,
platform,
admin_user,
editor_user,
viewer_user,
save,
state,
screenshot,
)

View File

@@ -1,13 +1,25 @@
from models.assets import Save, State, Screenshot
def test_save(save: Save):
assert "test_platform_slug/saves/test_emulator/test_save.sav" in save.full_path
assert "/api/raw/assets/test_platform_slug/saves/test_emulator/test_save.sav" in save.download_path
assert (
"/api/raw/assets/test_platform_slug/saves/test_emulator/test_save.sav"
in save.download_path
)
def test_state(state: State):
assert "test_platform_slug/states/test_emulator/test_state.state" in state.full_path
assert "/api/raw/assets/test_platform_slug/states/test_emulator/test_state.state" in state.download_path
assert (
"/api/raw/assets/test_platform_slug/states/test_emulator/test_state.state"
in state.download_path
)
def test_screenshot(screenshot: Screenshot):
assert "test_platform_slug/screenshots/test_screenshot.png" in screenshot.full_path
assert "/api/raw/assets/test_platform_slug/screenshots/test_screenshot.png" in screenshot.download_path
assert (
"/api/raw/assets/test_platform_slug/screenshots/test_screenshot.png"
in screenshot.download_path
)

View File

@@ -1,3 +1,3 @@
def test_rom(rom):
assert rom.file_path == "test_platform_slug/roms"
assert rom.full_path == "test_platform_slug/roms/test_rom.zip"
assert rom.full_path == "test_platform_slug/roms/test_rom.zip"

View File

@@ -0,0 +1,8 @@
from __version__ import __version__
def get_version() -> str:
"""Returns current version tag"""
if not __version__ == "<version>":
return __version__
return "development"

View File

@@ -99,7 +99,9 @@ if __name__ == "__main__":
# Sort platforms by key
supported_platforms = dict(sorted(supported_platforms.items()))
print("Below is a list of all supported platforms/systems/consoles and their respective folder names. **The folder name is case-sensitive and must be used exactly as it appears in the list below.**")
print(
"Below is a list of all supported platforms/systems/consoles and their respective folder names. **The folder name is case-sensitive and must be used exactly as it appears in the list below.**"
)
print("\n")
print("|Platform Name|Folder Name|IGDB|Mobygames|")
print("|---|---|---|---|")

View File

@@ -46,7 +46,7 @@ start_bin_gunicorn () {
--bind=0.0.0.0:5000 \
--bind=unix:/tmp/gunicorn.sock \
--pid=/tmp/gunicorn.pid \
--workers 2 \
--workers ${GUNICORN_WORKERS:=2} \
main:app &
}

View File

@@ -7,11 +7,11 @@ events {
}
http {
client_body_temp_path /tmp/client_body 1 2;
fastcgi_temp_path /tmp/fastcgi 1 2;
proxy_temp_path /tmp/proxy;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
client_body_temp_path /tmp/client_body 1 2;
fastcgi_temp_path /tmp/fastcgi 1 2;
proxy_temp_path /tmp/proxy;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
sendfile on;
client_max_body_size 0;

View File

@@ -1,13 +1,16 @@
ROMM_BASE_PATH=/path/to/romm_mock
VITE_BACKEND_DEV_PORT=5000
# Gunicorn (optional)
ROMM_HOST=localhost
GUNICORN_WORKERS=4 # (2 × CPU cores) + 1
# IGDB credentials
IGDB_CLIENT_ID=
IGDB_CLIENT_SECRET=
# Mobygames
MOBYGAMES_API_KEY=
MOBYGAMES_API_KEY=
# Database config
DB_HOST=127.0.0.1

View File

@@ -21,7 +21,7 @@ exclude:
## Multi files games section
# Will apply to files that are in sub-folders (multi-disc roms, games with updates, DLC, patches, etc.)
multi_file:
# Exclude matched 'folder' names to be scanned (RomM identifies folders as multi file games)
# Exclude matched 'folder' names to be scanned (RomM identifies folders as multi file games)
names:
- 'my_multi_file_game'
- 'DLC'

View File

@@ -7,4 +7,3 @@ info@brittneymurphydesign.com
It is free for personal and non-profit use. If you would like to use this font commercially, please purchase a commercial license (only $5) at www.brittneymurphydesign.com.
You are free to redistribute this font as long as you include this ReadMe file with the font file and don't try to claim the font as free or as your own.

View File

@@ -228,4 +228,4 @@
</g>
</g>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,17 +1,21 @@
<script setup lang="ts">
import Notification from "@/components/Notification.vue";
import api from "@/services/api/index";
import userApi from "@/services/api/user";
import platformApi from "@/services/api/platform";
import socket from "@/services/socket";
import storeConfig from "@/stores/config";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeHeartbeat from "@/stores/heartbeat";
import storeRoms, { type Rom } from "@/stores/roms";
import storePlatforms from "@/stores/platforms";
import storeAuth from "@/stores/auth";
import storeScanning from "@/stores/scanning";
import type { Events } from "@/types/emitter";
import { normalizeString } from "@/utils";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeMount, onBeforeUnmount } from "vue";
import { inject, onMounted, onBeforeUnmount } from "vue";
// Props
const scanningStore = storeScanning();
@@ -24,6 +28,8 @@ const emitter = inject<Emitter<Events>>("emitter");
// Props
const heartbeat = storeHeartbeat();
const configStore = storeConfig();
const auth = storeAuth();
const platformsStore = storePlatforms();
socket.on(
"scan:scanning_platform",
@@ -95,13 +101,32 @@ onBeforeUnmount(() => {
socket.off("scan:done_ko");
});
onBeforeMount(() => {
onMounted(() => {
api.get("/heartbeat").then(({ data: data }) => {
heartbeat.set(data);
});
api.get("/config").then(({ data: data }) => {
configStore.set(data);
});
userApi
.fetchCurrentUser()
.then(({ data: user }) => {
auth.setUser(user);
})
.catch((error) => {
console.error(error);
});
platformApi
.getPlatforms()
.then(({ data: platforms }) => {
platformsStore.set(platforms);
})
.catch((error) => {
console.error(error);
});
});
</script>

View File

@@ -9,4 +9,3 @@ export type AddFirmwareResponse = {
uploaded: number;
firmware: Array<FirmwareSchema>;
};

View File

@@ -7,4 +7,3 @@ export type AddRomsResponse = {
uploaded_roms: Array<string>;
skipped_roms: Array<string>;
};

View File

@@ -6,4 +6,3 @@
export type Body_add_firmware_firmware_post = {
files: Array<Blob>;
};

View File

@@ -6,4 +6,3 @@
export type Body_add_roms_roms_post = {
roms: Array<Blob>;
};

View File

@@ -6,4 +6,3 @@
export type Body_add_saves_saves_post = {
saves: Array<Blob>;
};

View File

@@ -6,4 +6,3 @@
export type Body_add_screenshots_screenshots_post = {
screenshots: Array<Blob>;
};

View File

@@ -6,4 +6,3 @@
export type Body_add_states_states_post = {
states: Array<Blob>;
};

View File

@@ -12,4 +12,3 @@ export type Body_token_token_post = {
client_secret?: (string | null);
refresh_token?: (string | null);
};

View File

@@ -6,4 +6,3 @@
export type Body_update_rom_roms__id__put = {
artwork?: (Blob | null);
};

View File

@@ -6,4 +6,3 @@
export type Body_update_user_users__id__put = {
avatar?: (Blob | null);
};

View File

@@ -16,4 +16,3 @@ export type ConfigResponse = {
FIRMWARE_FOLDER_NAME: string;
HIGH_PRIO_STRUCTURE_PATH: string;
};

View File

@@ -28,4 +28,3 @@ export type CursorPage_RomSchema_ = {
*/
next_page?: (string | null);
};

View File

@@ -17,4 +17,3 @@ export type FirmwareSchema = {
md5_hash: string;
sha1_hash: string;
};

View File

@@ -8,4 +8,3 @@ import type { ValidationError } from './ValidationError';
export type HTTPValidationError = {
detail?: Array<ValidationError>;
};

View File

@@ -9,10 +9,8 @@ import type { WatcherDict } from './WatcherDict';
export type HeartbeatResponse = {
VERSION: string;
NEW_VERSION: string;
WATCHER: WatcherDict;
SCHEDULER: SchedulerDict;
ANY_SOURCE_ENABLED: boolean;
METADATA_SOURCES: MetadataSourcesDict;
};

View File

@@ -7,4 +7,3 @@ export type IGDBPlatform = {
igdb_id: number;
name?: string;
};

View File

@@ -10,4 +10,3 @@ export type IGDBRelatedGame = {
type: string;
cover_url: string;
};

View File

@@ -6,4 +6,3 @@
export type MessageResponse = {
msg: string;
};

View File

@@ -7,4 +7,3 @@ export type MetadataSourcesDict = {
IGDB_API_ENABLED: boolean;
MOBY_API_ENABLED: boolean;
};

View File

@@ -7,4 +7,3 @@ export type MobyGamesPlatform = {
moby_id: number;
name?: string;
};

View File

@@ -15,6 +15,5 @@ export type PlatformSchema = {
name: string;
logo_path?: (string | null);
rom_count: number;
firmware_files?: Array<FirmwareSchema>;
firmware?: Array<FirmwareSchema>;
};

View File

@@ -25,4 +25,3 @@ export type RomIGDBMetadata = {
ports?: Array<IGDBRelatedGame>;
similar_games?: Array<IGDBRelatedGame>;
};

Some files were not shown because too many files have changed in this diff Show More