diff --git a/.github/actions/build.sh b/.github/actions/build.sh index 6ee7208cc..ea469a440 100755 --- a/.github/actions/build.sh +++ b/.github/actions/build.sh @@ -1,6 +1,6 @@ -if [[ $GIT_BRANCH = 'develop' ]]; then +if [[ $GIT_BRANCH != 'master' ]]; then docker buildx build --push\ - --tag zurdi15/romm:dev-${VERSION}\ + --tag zurdi15/romm:dev-latest --tag zurdi15/romm:dev-${VERSION}\ --platform linux/arm64 . --file ./docker/Dockerfile else docker buildx build --push\ diff --git a/.gitignore b/.gitignore index ea62aef3b..a47e7fe79 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ envs.env mariadb # data test -library \ No newline at end of file +library + +# config test +romm \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 5e3007f53..d932e3c95 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,4 +2,5 @@ requests==2.28.2 fastapi==0.92.0 uvicorn==0.20.0 mariadb==1.1.6 -SQLAlchemy==2.0.7 \ No newline at end of file +SQLAlchemy==2.0.7 +PyYAML==6.0 \ No newline at end of file diff --git a/backend/src/config/__init__.py b/backend/src/config/__init__.py new file mode 100644 index 000000000..5d3d7f6b0 --- /dev/null +++ b/backend/src/config/__init__.py @@ -0,0 +1,39 @@ +import os +import pathlib +import yaml +from yaml.loader import SafeLoader + + +# Uvicorn +DEV_PORT: int = 5000 +DEV_HOST: str = "0.0.0.0" + +# PATHS +LIBRARY_BASE_PATH: str = f"{pathlib.Path(__file__).parent.parent.parent.parent.resolve()}/library" +ROMM_USER_CONFIG_PATH: str = f"{pathlib.Path(__file__).parent.parent.parent.parent.resolve()}/romm/config.yml" + +# ROMM RESERVED FOLDERS +RESERVED_FOLDERS: list = ['resources', 'database'] + +# DEFAULT RESOURCES +DEFAULT_URL_COVER_L: str = "https://images.igdb.com/igdb/image/upload/t_cover_big/nocover.png" +DEFAULT_PATH_COVER_L: str = f"/assets/library/resources/default/cover_l.png" +DEFAULT_URL_COVER_S: str = "https://images.igdb.com/igdb/image/upload/t_cover_small/nocover.png" +DEFAULT_PATH_COVER_S: str = f"/assets/library/resources/default/cover_s.png" + +# IGDB +CLIENT_ID: str = os.getenv('CLIENT_ID') +CLIENT_SECRET: str = os.getenv('CLIENT_SECRET') +# STEAMGRIDDB +STEAMGRIDDB_API_KEY: str = os.getenv('STEAMGRIDDB_API_KEY') + +# USER CONFIG +try: + with open(ROMM_USER_CONFIG_PATH) as config: config = yaml.load(config, Loader=SafeLoader) +except FileNotFoundError: + config = None +user_config: dict = {} if not config else config + +# DB DRIVERS +SUPPORTED_DB_DRIVERS: list = ['sqlite', 'mariadb'] +ROMM_DB_DRIVER: str = os.getenv('ROMM_DB_DRIVER', 'sqlite') diff --git a/backend/src/config/config.py b/backend/src/config/config_loader.py similarity index 50% rename from backend/src/config/config.py rename to backend/src/config/config_loader.py index f3ffa72e0..6aa703eea 100644 --- a/backend/src/config/config.py +++ b/backend/src/config/config_loader.py @@ -1,38 +1,9 @@ import os import sys -import pathlib - from urllib.parse import quote_plus -from logger.logger import log - -# Uvicorn -DEV_PORT: int = 5000 -DEV_HOST: str = "0.0.0.0" - -# PATHS -LIBRARY_BASE_PATH: str = f"{pathlib.Path(__file__).parent.parent.parent.parent.resolve()}/library" - -DEFAULT_URL_LOGO: str = "https://images.igdb.com/igdb/image/upload/t_cover_big/nocover.png" -DEFAULT_PATH_LOGO: str = f"/assets/library/resources/default/logo_l.png" - -DEFAULT_URL_COVER_L: str = "https://images.igdb.com/igdb/image/upload/t_cover_big/nocover.png" -DEFAULT_PATH_COVER_L: str = f"/assets/library/resources/default/cover_l.png" -DEFAULT_URL_COVER_S: str = "https://images.igdb.com/igdb/image/upload/t_cover_small/nocover.png" -DEFAULT_PATH_COVER_S: str = f"/assets/library/resources/default/cover_s.png" -# IGDB -CLIENT_ID: str = os.getenv('CLIENT_ID') -CLIENT_SECRET: str = os.getenv('CLIENT_SECRET') -# STEAMGRIDDB -STEAMGRIDDB_API_KEY: str = os.getenv('STEAMGRIDDB_API_KEY') - - -RESERVED_FOLDERS: list = ['resources', 'database'] - - -# DB DRIVERS -SUPPORTED_DB_DRIVERS: list = ['sqlite', 'mariadb'] -ROMM_DB_DRIVER: str = os.getenv('ROMM_DB_DRIVER', 'sqlite') +from config import ROMM_DB_DRIVER, SUPPORTED_DB_DRIVERS, LIBRARY_BASE_PATH +from logger.logger import log def get_db_engine(): diff --git a/backend/src/handler/__init__.py b/backend/src/handler/__init__.py index e69de29bb..138b37a28 100644 --- a/backend/src/handler/__init__.py +++ b/backend/src/handler/__init__.py @@ -0,0 +1,7 @@ +from handler.db_handler import DBHandler +from handler.igdb_handler import IGDBHandler +from handler.sgdb_handler import SGDBHandler + +igdbh: IGDBHandler = IGDBHandler() +sgdbh: SGDBHandler = SGDBHandler() +dbh: DBHandler = DBHandler() diff --git a/backend/src/handler/db_handler.py b/backend/src/handler/db_handler.py index 64124cd8b..b8f1f8e52 100644 --- a/backend/src/handler/db_handler.py +++ b/backend/src/handler/db_handler.py @@ -1,4 +1,5 @@ import functools +import json from fastapi import HTTPException from sqlalchemy import select @@ -23,70 +24,54 @@ def wrapper(*args): return wrapper - def add_platform(self, **kargs) -> None: - with Session.begin() as session: - session.merge(Platform(**kargs)) - - - def add_platform(self, **kargs) -> None: - with Session.begin() as session: - session.merge(Platform(**kargs)) - - - def get_platforms(self) -> list[Platform]: + def add_platform(self, Platform: Platform) -> None: try: with Session.begin() as session: - return session.scalars(select(Platform).order_by(Platform.slug.asc())).all() + session.merge(Platform) except ProgrammingError as e: raise HTTPException(status_code=404, detail=f"Platforms table not found: {e}") - - def add_platform(self, **kargs) -> None: + def get_platforms(self) -> list[Platform]: try: with Session.begin() as session: - session.merge(Platform(**kargs)) + return session.scalars(select(Platform).order_by(Platform.slug.asc())).all() except ProgrammingError as e: - raise HTTPException(status_code=404, detail=f"Roms table not found: {e}") - + raise HTTPException(status_code=404, detail=f"Platforms table not found: {e}") - def get_roms(self, p_slug: str) -> list[Rom]: + def add_rom(self, rom: Rom) -> None: with Session.begin() as session: - return session.scalars(select(Rom).filter_by(p_slug=p_slug).order_by(Rom.filename.asc())).all() + session.merge(rom) - - def get_rom(self, p_slug: str, filename: str) -> Rom: + def get_roms(self, p_slug: str) -> list[Rom]: with Session.begin() as session: - return session.scalars(select(Rom).filter_by(p_slug=p_slug, filename=filename)).first() + return session.scalars(select(Rom).filter_by(p_slug=p_slug).order_by(Rom.file_name.asc())).all() - - def add_rom(self, **kargs) -> None: + def get_rom(self, p_slug: str, file_name: str) -> Rom: with Session.begin() as session: - session.merge(Rom(**kargs)) - + return session.scalars(select(Rom).filter_by(p_slug=p_slug, file_name=file_name)).first() - def update_rom(self, p_slug: str, filename: str, data: dict) -> None: + def update_rom(self, p_slug: str, file_name: str, data: dict) -> None: with Session.begin() as session: session.query(Rom) \ - .filter(Rom.p_slug==p_slug, Rom.filename==filename) \ + .filter(Rom.p_slug==p_slug, Rom.file_name==file_name) \ .update(data, synchronize_session='evaluate') - - def delete_rom(self, p_slug: str, filename: str) -> None: + def delete_rom(self, p_slug: str, file_name: str) -> None: with Session.begin() as session: session.query(Rom) \ - .filter(Rom.p_slug==p_slug, Rom.filename==filename) \ + .filter(Rom.p_slug==p_slug, Rom.file_name==file_name) \ .delete(synchronize_session='evaluate') - - def purge_platforms(self, platforms: list) -> None: + def purge_platforms(self, platforms: list[str]) -> None: + log.info("Purging platforms") with Session.begin() as session: session.query(Platform) \ .filter(Platform.slug.not_in(platforms)) \ .delete(synchronize_session='evaluate') - - def purge_roms(self, p_slug: str, roms: list) -> None: + def purge_roms(self, p_slug: str, roms: list[dict]) -> None: + log.info(f"Purging {p_slug} roms") with Session.begin() as session: session.query(Rom) \ - .filter(Rom.p_slug==p_slug, Rom.filename.not_in([rom['filename'] for rom in roms])) \ + .filter(Rom.p_slug==p_slug, Rom.file_name.not_in([rom['file_name'] for rom in roms])) \ .delete(synchronize_session='evaluate') diff --git a/backend/src/handler/igdb_handler.py b/backend/src/handler/igdb_handler.py index f9161f3b6..bca3762cb 100644 --- a/backend/src/handler/igdb_handler.py +++ b/backend/src/handler/igdb_handler.py @@ -5,7 +5,7 @@ import requests -from config.config import CLIENT_ID, CLIENT_SECRET, DEFAULT_URL_COVER_L +from config import CLIENT_ID, CLIENT_SECRET, DEFAULT_URL_COVER_L from logger.logger import log @@ -32,31 +32,30 @@ def wrapper(*args): def get_platform_details(self, slug: str) -> tuple: igdb_id: str = "" name: str = "" - url_logo: str = "" try: res_details: dict = requests.post("https://api.igdb.com/v4/platforms/", headers=self.headers, - data=f"fields id, name, platform_logo; where slug=\"{slug}\";").json()[0] + data=f"fields id, name; where slug=\"{slug}\";").json()[0] igdb_id = res_details['id'] name = res_details['name'] except IndexError: log.warning("platform not found in igdb") if not name: name = slug - return igdb_id, name, url_logo + return {'igdb_id': igdb_id, 'name': name} @check_twitch_token - def get_rom_details(self, filename: str, p_igdb_id: int, r_igdb_id: str) -> dict: - filename_no_ext: str = filename.split('.')[0] - igdb_id: str = "" + def get_rom_details(self, file_name: str, p_igdb_id: int, r_igdb_id_search: str) -> dict: + file_name_no_tags: str = re.sub('[\(\[].*?[\)\]]', '', file_name.split('.')[0]) + r_igdb_id: str = "" slug: str = "" name: str = "" summary: str = "" url_cover: str = "" - if r_igdb_id: + if r_igdb_id_search: res_details: dict = requests.post("https://api.igdb.com/v4/games/", headers=self.headers, - data=f"fields id, slug, name, summary; where id={r_igdb_id};").json()[0] - igdb_id = res_details['id'] + data=f"fields id, slug, name, summary; where id={r_igdb_id_search};").json()[0] + r_igdb_id = res_details['id'] slug = res_details['slug'] name = res_details['name'] try: @@ -66,12 +65,11 @@ def get_rom_details(self, filename: str, p_igdb_id: int, r_igdb_id: str) -> dict else: if p_igdb_id: - search_term: str = re.sub('[\(\[].*?[\)\]]', '', filename_no_ext) try: res_details: dict = requests.post("https://api.igdb.com/v4/games/", headers=self.headers, - data=f"search \"{search_term}\";fields id, slug, name, summary; where platforms=[{p_igdb_id}] & category=0;").json()[0] - igdb_id = res_details['id'] + data=f"search \"{file_name_no_tags}\";fields id, slug, name, summary; where platforms=[{p_igdb_id}] & category=0;").json()[0] + r_igdb_id = res_details['id'] slug = res_details['slug'] name = res_details['name'] try: @@ -81,8 +79,8 @@ def get_rom_details(self, filename: str, p_igdb_id: int, r_igdb_id: str) -> dict except IndexError: try: res_details: dict = requests.post("https://api.igdb.com/v4/games/", headers=self.headers, - data=f"search \"{search_term}\";fields name, id, slug, summary; where platforms=[{p_igdb_id}] & category=10;").json()[0] - igdb_id = res_details['id'] + data=f"search \"{file_name_no_tags}\";fields name, id, slug, summary; where platforms=[{p_igdb_id}] & category=10;").json()[0] + r_igdb_id = res_details['id'] slug = res_details['slug'] name = res_details['name'] try: @@ -92,8 +90,8 @@ def get_rom_details(self, filename: str, p_igdb_id: int, r_igdb_id: str) -> dict except IndexError: try: res_details: dict = requests.post("https://api.igdb.com/v4/games/", headers=self.headers, - data=f"search \"{search_term}\";fields name, id, slug, summary; where platforms=[{p_igdb_id}];").json()[0] - igdb_id = res_details['id'] + data=f"search \"{file_name_no_tags}\";fields name, id, slug, summary; where platforms=[{p_igdb_id}];").json()[0] + r_igdb_id = res_details['id'] slug = res_details['slug'] name = res_details['name'] try: @@ -101,24 +99,24 @@ def get_rom_details(self, filename: str, p_igdb_id: int, r_igdb_id: str) -> dict except KeyError: pass except IndexError: - log.warning(f"{filename} rom not found in igdb") - if igdb_id: + log.warning(f"{file_name} rom not found in igdb") + if r_igdb_id: try: res_details: dict = requests.post("https://api.igdb.com/v4/covers/", headers=self.headers, - data=f"fields url; where game={igdb_id};").json()[0] + data=f"fields url; where game={r_igdb_id};").json()[0] url_cover: str = f"https:{res_details['url']}" except IndexError: log.warning(f"{name} cover not found in igdb") - if not name: name = filename_no_ext - return (igdb_id, filename_no_ext, slug, name, summary, url_cover) + if not name: name = file_name_no_tags + return (r_igdb_id, file_name_no_tags, slug, name, summary, url_cover) @check_twitch_token - def get_matched_roms(self, filename: str, p_igdb_id: int) -> list: - search_term: str = re.sub('[\(\[].*?[\)\]]', '', filename.split('.')[0]) + def get_matched_roms(self, file_name: str, p_igdb_id: int) -> list: + search_term: str = re.sub('[\(\[].*?[\)\]]', '', file_name.split('.')[0]) matched_roms: list = requests.post("https://api.igdb.com/v4/games/", headers=self.headers, data=f"search \"{search_term}\";fields name, id, slug, summary; where platforms=[{p_igdb_id}];").json() - log.info(f"Matched roms for {filename}: {matched_roms}") + log.info(f"Matched roms for {file_name}: {matched_roms}") for rom in matched_roms: try: res_details: dict = requests.post("https://api.igdb.com/v4/covers/", headers=self.headers, @@ -126,6 +124,8 @@ def get_matched_roms(self, filename: str, p_igdb_id: int) -> list: rom['url_cover'] = f"https:{res_details['url']}".replace('t_thumb', f't_cover_big') except IndexError: rom['url_cover'] = DEFAULT_URL_COVER_L + rom['r_igdb_id'] = rom.pop('id') + rom['r_slug'] = rom.pop('slug') return matched_roms diff --git a/backend/src/handler/sgdb_handler.py b/backend/src/handler/sgdb_handler.py index 647a9849b..1f5490a3e 100644 --- a/backend/src/handler/sgdb_handler.py +++ b/backend/src/handler/sgdb_handler.py @@ -1,6 +1,6 @@ import requests -from config.config import STEAMGRIDDB_API_KEY +from config import STEAMGRIDDB_API_KEY from logger.logger import log diff --git a/backend/src/main.py b/backend/src/main.py index ea63a005a..b270fa634 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,101 +1,104 @@ +import os from fastapi import FastAPI, Request import uvicorn from logger.logger import log -from handler.igdb_handler import IGDBHandler -from handler.sgdb_handler import SGDBHandler -from handler.db_handler import DBHandler -from config.config import DEV_PORT, DEV_HOST +from handler import igdbh, dbh +from config import DEV_PORT, DEV_HOST +from models.platform import Platform from utils import fs, fastapi app = FastAPI() fastapi.allow_cors(app) -igdbh: IGDBHandler = IGDBHandler() -sgdbh: SGDBHandler = SGDBHandler() -dbh: DBHandler = DBHandler() +@app.get("/platforms/{p_slug}/roms/{file_name}") +async def rom(p_slug: str, file_name: str) -> dict: + """Returns one rom data of the desired platform""" + + return {'data': dbh.get_rom(p_slug, file_name)} -@app.patch("/platforms/{p_slug}/roms/{filename}") -async def updateRom(req: Request, p_slug: str, filename: str): + +@app.patch("/platforms/{p_slug}/roms") +async def updateRom(req: Request, p_slug: str) -> dict: """Updates rom details""" data: dict = await req.json() - if 'r_igdb_id' in data: - r_igdb_id, filename_no_ext, r_slug, r_name, summary, url_cover = igdbh.get_rom_details(filename, data['p_igdb_id'], data['r_igdb_id']) - path_cover_s, path_cover_l, has_cover = fs.get_cover_details(True, p_slug, filename_no_ext, url_cover) - data['r_igdb_id'] = r_igdb_id - data['filename_no_ext'] = filename_no_ext - data['r_slug'] = r_slug - data['name'] = r_name - data['summary'] = summary - data['path_cover_s'] = path_cover_s - data['path_cover_l'] = path_cover_l - data['has_cover'] = has_cover - data['p_slug'] = p_slug - else: - fs.rename_rom(p_slug, filename, data) - data['filename_no_ext'] = data['filename'].split('.')[0] - dbh.update_rom(p_slug, filename, data) - return {'data': data} - - -@app.delete("/platforms/{p_slug}/roms/{filename}") -async def delete_rom(p_slug: str, filename: str, filesystem: bool=False): + rom: dict = data['rom'] + updatedRom: dict = data['updatedRom'] + r_igdb_id, file_name_no_tags, r_slug, r_name, summary, url_cover = igdbh.get_rom_details(updatedRom['file_name'], rom['p_igdb_id'], updatedRom['r_igdb_id']) + path_cover_s, path_cover_l, has_cover = fs.get_cover_details(True, p_slug, updatedRom['file_name'], url_cover) + updatedRom['file_name_no_tags'] = file_name_no_tags + updatedRom['r_igdb_id'] = r_igdb_id + updatedRom['p_igdb_id'] = rom['p_igdb_id'] + updatedRom['r_slug'] = r_slug + updatedRom['p_slug'] = p_slug + updatedRom['name'] = r_name + updatedRom['summary'] = summary + updatedRom['path_cover_s'] = path_cover_s + updatedRom['path_cover_l'] = path_cover_l + updatedRom['has_cover'] = has_cover + updatedRom['file_path'] = rom['file_path'] + updatedRom['file_size'] = rom['file_size'] + updatedRom['file_extension'] = updatedRom['file_name'].split('.')[-1] if '.' in updatedRom['file_name'] else "" + reg, rev, other_tags = fs.parse_tags(updatedRom['file_name']) + updatedRom.update({'region': reg, 'revision': rev, 'tags': other_tags}) + if 'url_cover' in updatedRom.keys(): del updatedRom['url_cover'] + fs.rename_rom(p_slug, rom['file_name'], updatedRom['file_name']) + dbh.update_rom(p_slug, rom['file_name'], updatedRom) + return {'data': updatedRom} + + +@app.delete("/platforms/{p_slug}/roms") +async def delete_rom(p_slug: str, file_name: str, filesystem: bool=False) -> dict: """Detele rom from filesystem and database""" log.info("deleting rom...") - if filesystem: fs.delete_rom(p_slug, filename) - dbh.delete_rom(p_slug, filename) + if filesystem: fs.delete_rom(p_slug, file_name) + dbh.delete_rom(p_slug, file_name) return {'msg': 'success'} -@app.get("/platforms/{p_slug}/roms/{filename}") -async def rom(p_slug: str, filename: str): - """Returns one rom data of the desired platform""" - - return {'data': dbh.get_rom(p_slug, filename)} - - @app.get("/platforms/{p_slug}/roms") -async def roms(p_slug: str): +async def roms(p_slug: str) -> dict: """Returns all roms of the desired platform""" return {'data': dbh.get_roms(p_slug)} @app.get("/platforms") -async def platforms(): +async def platforms() -> dict: """Returns platforms data""" return {'data': dbh.get_platforms()} @app.put("/scan") -async def scan(req: Request, overwrite: bool=False): +async def scan(req: Request, overwrite: bool=False) -> dict: """Scan platforms and roms and write them in database.""" log.info("complete scaning...") fs.store_default_resources(overwrite) data: dict = await req.json() - platforms = data['platforms'] if data['platforms'] else fs.get_platforms() + platforms: list[str] = data['platforms'] if data['platforms'] else fs.get_platforms() for p_slug in platforms: - p_igdb_id: str = fastapi.scan_platform(overwrite, p_slug, igdbh, dbh) - for rom in fs.get_roms(p_slug): - fastapi.scan_rom(overwrite, rom, p_igdb_id, p_slug, igdbh, dbh) - fastapi.purge(dbh, p_slug=p_slug) - fastapi.purge(dbh) + platform: Platform = fastapi.scan_platform(p_slug) + roms: list[dict] = fs.get_roms(p_slug) + for rom in roms: + fastapi.scan_rom(platform, rom) + dbh.purge_roms(p_slug, roms) + dbh.purge_platforms(fs.get_platforms()) return {'msg': 'success'} @app.put("/search/roms/igdb") -async def search_rom_igdb(req: Request): +async def search_rom_igdb(req: Request) -> dict: """Get all the roms matched from igdb.""" data: dict = await req.json() - log.info(f"getting {data['filename']} roms from {data['p_igdb_id']} igdb ...") - return {'data': igdbh.get_matched_roms(data['filename'], data['p_igdb_id'])} + log.info(f"getting {data['file_name']} roms from {data['p_igdb_id']} igdb ...") + return {'data': igdbh.get_matched_roms(data['file_name'], data['p_igdb_id'])} if __name__ == '__main__': diff --git a/backend/src/models/base.py b/backend/src/models/base.py index 91ea2247e..098b4e914 100644 --- a/backend/src/models/base.py +++ b/backend/src/models/base.py @@ -2,10 +2,9 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker -from config.config import get_db_engine +from config.config_loader import get_db_engine BaseModel = declarative_base() -# engine = create_engine(DB_DRIVERS[ROMM_DB_DRIVER], pool_pre_ping=True) engine = create_engine(get_db_engine(), pool_pre_ping=True) Session = sessionmaker(bind=engine, expire_on_commit=False) diff --git a/backend/src/models/platform.py b/backend/src/models/platform.py index afa2788de..753af2e4c 100644 --- a/backend/src/models/platform.py +++ b/backend/src/models/platform.py @@ -1,14 +1,17 @@ from sqlalchemy import Column, String, Integer, Text -from config.config import DEFAULT_PATH_LOGO +from config import DEFAULT_PATH_COVER_S from models.base import BaseModel class Platform(BaseModel): __tablename__ = 'platforms' - igdb_id = Column(String(length=50), default="") - sgdb_id = Column(String(length=50), default="") - slug = Column(String(length=500), primary_key=True) - name = Column(String(length=500), default="") - path_logo = Column(Text, default=DEFAULT_PATH_LOGO) + igdb_id = Column(String(length=10), default="") + sgdb_id = Column(String(length=10), default="") + + slug = Column(String(length=50), primary_key=True) + name = Column(String(length=400), default="") + + logo_path = Column(String(length=1000), default=DEFAULT_PATH_COVER_S) + n_roms = Column(Integer, default=0) diff --git a/backend/src/models/rom.py b/backend/src/models/rom.py index 4c013bbec..3be7cb30a 100644 --- a/backend/src/models/rom.py +++ b/backend/src/models/rom.py @@ -1,22 +1,33 @@ -from sqlalchemy import Column, String, Text, Boolean +from sqlalchemy import Column, String, Text, Boolean, Float, JSON -from config.config import DEFAULT_PATH_COVER_S, DEFAULT_PATH_COVER_L +from config import DEFAULT_PATH_COVER_S, DEFAULT_PATH_COVER_L from models.base import BaseModel class Rom(BaseModel): __tablename__ = 'roms' - filename = Column(String(length=500), primary_key=True) - filename_no_ext = Column(String(length=500), default="") - r_igdb_id = Column(String(length=50), default="") - p_igdb_id = Column(String(length=50), default="") - r_sgdb_id = Column(String(length=50), default="") - p_sgdb_id = Column(String(length=50), default="") - name = Column(String(length=500), default="") - r_slug = Column(String(length=500), default="") - p_slug = Column(String(length=500), default="") + r_igdb_id = Column(String(length=10), default="") + p_igdb_id = Column(String(length=10), default="") + r_sgdb_id = Column(String(length=10), default="") + p_sgdb_id = Column(String(length=10), default="") + + p_slug = Column(String(length=50), primary_key=True) + + file_name = Column(String(length=450), primary_key=True) + file_name_no_tags = Column(String(length=450), default="") + file_extension = Column(String(length=10), default="") + file_path = Column(String(length=1000), default="") + file_size = Column(Float, default="") + + name = Column(String(length=350), default="") + r_slug = Column(String(length=100), default="") + summary = Column(Text, default="") + path_cover_s = Column(Text, default=DEFAULT_PATH_COVER_S) path_cover_l = Column(Text, default=DEFAULT_PATH_COVER_L) has_cover = Column(Boolean, default=False) - size = Column(String(length=20), default="") + + region = Column(String(20), default="") + revision = Column(String(20), default="") + tags = Column(JSON, default=[]) diff --git a/backend/src/utils/fastapi.py b/backend/src/utils/fastapi.py index 76f71d10f..43140ffa0 100644 --- a/backend/src/utils/fastapi.py +++ b/backend/src/utils/fastapi.py @@ -1,9 +1,10 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from handler.db_handler import DBHandler -from handler.igdb_handler import IGDBHandler +from handler import igdbh, dbh from utils import fs +from models.platform import Platform +from models.rom import Rom from logger.logger import log @@ -18,47 +19,38 @@ def allow_cors(app: FastAPI) -> None: log.info("CORS enabled") -def scan_platform(overwrite: bool, p_slug: str, igdbh: IGDBHandler, dbh: DBHandler) -> str: - platform: dict = {} - log.info(f"Getting {p_slug} details") - p_igdb_id, p_name, url_logo = igdbh.get_platform_details(p_slug) - platform['slug'] = p_slug - platform['igdb_id'] = p_igdb_id - platform['name'] = p_name - #TODO: refactor logo details logic - if (overwrite or not fs.p_logo_exists(p_slug)) and url_logo: - fs.store_p_logo(p_slug, url_logo) - if fs.p_logo_exists(p_slug): - platform['path_logo'] = fs.get_p_path_logo(p_slug) - platform['n_roms'] = len(fs.get_roms(p_slug)) - dbh.add_platform(**platform) - return p_igdb_id - - -def scan_rom(overwrite: bool, rom, p_igdb_id: str, p_slug: str, igdbh: IGDBHandler, dbh: DBHandler, r_igbd_id: str = '') -> None: - log.info(f"Getting {rom['filename']} details") - r_igdb_id, filename_no_ext, r_slug, r_name, summary, url_cover = igdbh.get_rom_details(rom['filename'], p_igdb_id, r_igbd_id) - path_cover_s, path_cover_l, has_cover = fs.get_cover_details(overwrite, p_slug, filename_no_ext, url_cover) - rom: dict = { - 'filename': rom['filename'], 'filename_no_ext': filename_no_ext, 'size': rom['size'], - 'r_igdb_id': r_igdb_id, 'p_igdb_id': p_igdb_id, - 'name': r_name, 'r_slug': r_slug, 'p_slug': p_slug, - 'summary': summary, - 'path_cover_s': path_cover_s, 'path_cover_l': path_cover_l, 'has_cover': has_cover - } - dbh.add_rom(**rom) +def scan_platform(p_slug: str) -> Platform: + """Get platform details from IGDB if possible - -def purge(dbh: DBHandler, p_slug: str = '') -> None: - """Clean the database from non existent platforms or roms""" - if p_slug: - # Purge only roms in platform - log.info(f"Purge {p_slug}") - dbh.purge_roms(p_slug, fs.get_roms(p_slug)) - else: - # Purge all platforms / delete non existent platforms and non existen roms - platforms: list = fs.get_platforms() - dbh.purge_platforms(platforms) - for p_slug in platforms: - log.info(f"Purge {p_slug}") - dbh.purge_roms(p_slug, fs.get_roms(p_slug)) + Args: + p_slug: short name of the platform + Returns + Platform object + """ + log.info(f"Getting {p_slug} details") + platform_attrs: dict = igdbh.get_platform_details(p_slug) + platform_attrs['slug'] = p_slug + platform_attrs['logo_path'] = '' + platform_attrs['n_roms'] = fs.get_roms(p_slug, only_amount=True) + log.info(f"Platform n_roms: {platform_attrs['n_roms']}") + platform = Platform(**platform_attrs) + dbh.add_platform(platform) + return platform + + +def scan_rom(platform: Platform, rom: dict, r_igbd_id_search: str = '', overwrite: bool = False) -> None: + log.info(f"Getting {rom['file_name']} details") + r_igdb_id, file_name_no_tags, r_slug, r_name, summary, url_cover = igdbh.get_rom_details(rom['file_name'], platform.igdb_id, r_igbd_id_search) + path_cover_s, path_cover_l, has_cover = fs.get_cover_details(overwrite, platform.slug, rom['file_name'], url_cover) + rom['file_name_no_tags'] = file_name_no_tags + rom['r_igdb_id'] = r_igdb_id + rom['p_igdb_id'] = platform.igdb_id + rom['r_slug'] = r_slug + rom['p_slug'] = platform.slug + rom['name'] = r_name + rom['summary'] = summary + rom['path_cover_s'] = path_cover_s + rom['path_cover_l'] = path_cover_l + rom['has_cover'] = has_cover + rom = Rom(**rom) + dbh.add_rom(rom) diff --git a/backend/src/utils/fs.py b/backend/src/utils/fs.py index db21f40ea..b72cbc9a9 100644 --- a/backend/src/utils/fs.py +++ b/backend/src/utils/fs.py @@ -1,11 +1,12 @@ import os import shutil +import re from pathlib import Path import requests from fastapi import HTTPException -from config.config import LIBRARY_BASE_PATH, RESERVED_FOLDERS, DEFAULT_URL_LOGO, DEFAULT_URL_COVER_L, DEFAULT_PATH_COVER_L, DEFAULT_URL_COVER_S, DEFAULT_PATH_COVER_S +from config import user_config, LIBRARY_BASE_PATH, RESERVED_FOLDERS, DEFAULT_URL_COVER_L, DEFAULT_PATH_COVER_L, DEFAULT_URL_COVER_S, DEFAULT_PATH_COVER_S from logger.logger import log @@ -16,106 +17,172 @@ def store_default_resources(overwrite: bool) -> None: Args: overwrite: flag to overwrite or not default resources """ - if overwrite or not p_logo_exists('default'): - store_p_logo('default', DEFAULT_URL_LOGO) - if overwrite or not r_cover_exists('default', 'cover', 'l'): - store_r_cover('default', 'cover', DEFAULT_URL_COVER_L, 'l') - if overwrite or not r_cover_exists('default', 'cover', 's'): - store_r_cover('default', 'cover', DEFAULT_URL_COVER_S, 's') + if overwrite or not _cover_exists('default', 'cover', 'l'): + _store_cover('default', 'cover', DEFAULT_URL_COVER_L, 'l') + if overwrite or not _cover_exists('default', 'cover', 's'): + _store_cover('default', 'cover', DEFAULT_URL_COVER_S, 's') # ========= Platforms utils ========= -def p_logo_exists(slug: str) -> bool: - """Check if platform logo exists in filesystem +def get_platforms() -> list[str]: + """Gets all filesystem platforms - Args: - slug: shor name of the platform - Returns - True if logo exists in filesystem else False + Returns list with all the filesystem platforms found in the LIBRARY_BASE_PATH. + Automatically discards the reserved directories such resources or database directory. """ - logo_path: str = f"{LIBRARY_BASE_PATH}/resources/{slug}/logo.png" - return True if os.path.exists(logo_path) else False + try: + if os.path.exists(f"{LIBRARY_BASE_PATH}/roms"): + platforms: list[str] = list(os.walk(f"{LIBRARY_BASE_PATH}/roms"))[0][1] + else: + platforms: list[str] = list(os.walk(LIBRARY_BASE_PATH))[0][1] + [platforms.remove(reserved) for reserved in RESERVED_FOLDERS if reserved in platforms] + try: + excluded_folders: list = user_config['exclude']['folders'] + try: + [platforms.remove(excluded) for excluded in excluded_folders if excluded in platforms] + except TypeError: + pass + except KeyError: + pass + log.info(f"filesystem platforms found: {platforms}") + return platforms + except IndexError: + raise HTTPException(status_code=404, detail="Platforms not found.") -def get_p_path_logo(slug: str) -> str: - """Returns platform logo filesystem path +# ========= Roms utils ========= +def _check_folder_structure(p_slug) -> tuple: + if os.path.exists(f"{LIBRARY_BASE_PATH}/roms"): + roms_path: str = f"{LIBRARY_BASE_PATH}/roms/{p_slug}" + roms_files = list(os.walk(f"{LIBRARY_BASE_PATH}/roms/{p_slug}"))[0][2] + else: + roms_path: str = f"{LIBRARY_BASE_PATH}/{p_slug}/roms" + roms_files = list(os.walk(f"{LIBRARY_BASE_PATH}/{p_slug}/roms"))[0][2] + return roms_path, roms_files + +def _exclude_files(roms_files) -> list[str]: + try: + excluded_files: list = user_config['exclude']['files'] + filtered_files: list = [] + for file_name in roms_files: + if file_name.split('.')[-1] in excluded_files: + filtered_files.append(file_name) + roms_files = [f for f in roms_files if f not in filtered_files] + except (TypeError, KeyError): + pass + return roms_files + + +def parse_tags(file_name: str) -> tuple: + reg='' + rev='' + other_tags=[] + tags = re.findall('\(([^)]+)', file_name) + for t in tags: + if t.split('-')[0].lower() == 'reg': + try: reg=t.split('-', 1)[1] + except IndexError: pass + elif t.split('-')[0].lower() == 'rev': + try: rev=t.split('-', 1)[1] + except IndexError: pass + else: + other_tags.append(t) + return reg, rev, other_tags + + +def get_roms(p_slug: str, only_amount: bool = False) -> list[dict]: + """Gets all filesystem roms for a platform + Args: - slug: shor name of the platform + p_slug: short name of the platform + only_amount: flag to return only amount of roms instead of all info + Returns: list with all the filesystem roms for a platform found in the LIBRARY_BASE_PATH. Just the amount of them if only_amount=True """ - return f"/assets/library/resources/{slug}/logo.png" + try: + roms: list[dict] = [] + roms_path, roms_files = _check_folder_structure(p_slug) + roms_files = _exclude_files(roms_files) + + if only_amount: return len(roms_files) + + for rom in roms_files: + file_size: str = str(round(os.stat(f"{roms_path}/{rom}").st_size / (1024 * 1024), 2)) + file_extension: str = rom.split('.')[-1] if '.' in rom else "" + reg, rev, other_tags = parse_tags(rom) + roms.append({'file_name': rom, 'file_path': roms_path, 'file_size': file_size, 'file_extension': file_extension, + 'region': reg, 'revision': rev, 'tags': other_tags}) + log.info(f"Roms found for {p_slug}: {roms}") + except IndexError: + log.warning(f"Roms not found for {p_slug}") + if only_amount: return 0 + return roms -def store_p_logo(slug: str, url_logo: str) -> None: - """Store platform resources in filesystem +def _rom_exists(p_slug: str, file_name: str) -> bool: + """Check if rom exists in filesystem Args: - slug: shor name of the platform - url_logo: url to get logo + p_slug: short name of the platform + file_name: rom file_name + Returns + True if rom exists in filesystem else False """ - logo_file: str = f"logo.png" - logo_path: str = f"{LIBRARY_BASE_PATH}/resources/{slug}" - res = requests.get(url_logo, stream=True) - if res.status_code == 200: - Path(logo_path).mkdir(parents=True, exist_ok=True) - with open(f"{logo_path}/{logo_file}", 'wb') as f: - shutil.copyfileobj(res.raw, f) - log.info(f"{slug} logo downloaded successfully!") - else: - log.warning(f"{slug} logo couldn't be downloaded") + rom_path, _ = _check_folder_structure(p_slug) + exists: bool = True if os.path.exists(f"{rom_path}/{file_name}") else False + return exists -def get_platforms() -> list: - """Gets all filesystem platforms +def rename_rom(p_slug: str, old_name: str, new_name: str) -> None: + if new_name != old_name: + rom_path, _ = _check_folder_structure(p_slug) + if _rom_exists(p_slug, new_name): raise HTTPException(status_code=500, detail=f"Can't rename: {new_name} already exists.") + os.rename(f"{rom_path}/{old_name}", f"{rom_path}/{new_name}") - Returns list with all the filesystem platforms found in the LIBRARY_BASE_PATH. - Automatically discards the default directory. - """ + +def delete_rom(p_slug: str, file_name: str) -> None: try: - platforms: list[str] = list(os.walk(LIBRARY_BASE_PATH))[0][1] - [platforms.remove(reserved) for reserved in RESERVED_FOLDERS if reserved in platforms] - log.info(f"filesystem platforms found: {platforms}") - return platforms - except IndexError: - raise HTTPException(status_code=404, detail="Platforms not found.") + rom_path, _ = _check_folder_structure(p_slug) + os.remove(f"{rom_path}/{file_name}") + except FileNotFoundError: + log.warning(f"Rom not found in filesystem: {rom_path}/{file_name}") -# ========= Roms utils ========= -def r_cover_exists(p_slug: str, filename_no_ext: str, size: str) -> bool: +def _cover_exists(p_slug: str, file_name: str, size: str) -> bool: """Check if rom cover exists in filesystem Args: p_slug: short name of the platform - filename_no_ext: name of rom file without extension + file_name: name of rom file size: size of the cover -> big as 'l' | small as 's' Returns True if cover exists in filesystem else False """ - logo_path: str = f"{LIBRARY_BASE_PATH}/resources/{p_slug}/{filename_no_ext}_{size}.png" + logo_path: str = f"{LIBRARY_BASE_PATH}/resources/{p_slug}/{file_name}_{size}.png" return True if os.path.exists(logo_path) else False -def get_r_cover_path(p_slug: str, filename_no_ext: str, size: str) -> str: +def _get_cover_path(p_slug: str, file_name: str, size: str) -> str: """Returns platform logo filesystem path Args: p_slug: short name of the platform - filename_no_ext: name of rom file without extension + file_name: name of rom file size: size of the cover -> big as 'l' | small as 's' """ - return f"/assets/library/resources/{p_slug}/{filename_no_ext}_{size}.png" + return f"/assets/library/resources/{p_slug}/{file_name}_{size}.png" -def store_r_cover(p_slug: str, filename_no_ext: str, url_cover: str, size: str) -> None: +def _store_cover(p_slug: str, file_name: str, url_cover: str, size: str) -> None: """Store roms resources in filesystem Args: p_slug: short name of the platform - filename_no_ext: name of rom file without extension + file_name: name of rom file url_cover: url to get the cover size: size of the cover -> big as 'l' | small as 's' """ - cover_file: str = f"{filename_no_ext}_{size}.png" + cover_file: str = f"{file_name}_{size}.png" cover_path: str = f"{LIBRARY_BASE_PATH}/resources/{p_slug}/" sizes: dict = {'l': 'big', 's': 'small'} res = requests.get(url_cover.replace('t_thumb', f't_cover_{sizes[size]}'), stream=True) @@ -123,72 +190,24 @@ def store_r_cover(p_slug: str, filename_no_ext: str, url_cover: str, size: str) Path(cover_path).mkdir(parents=True, exist_ok=True) with open(f"{cover_path}/{cover_file}", 'wb') as f: shutil.copyfileobj(res.raw, f) - log.info(f"{filename_no_ext} {sizes[size]} cover downloaded successfully!") + log.info(f"{file_name} {sizes[size]} cover downloaded successfully!") else: - log.warning(f"{filename_no_ext} {sizes[size]} cover couldn't be downloaded") + log.warning(f"{file_name} {sizes[size]} cover couldn't be downloaded") -def get_cover_details(overwrite: bool, p_slug: str, filename_no_ext: str, url_cover: str) -> tuple: +def get_cover_details(overwrite: bool, p_slug: str, file_name: str, url_cover: str) -> tuple: path_cover_s: str = DEFAULT_PATH_COVER_S path_cover_l: str = DEFAULT_PATH_COVER_L has_cover: int = 0 - if (overwrite or not r_cover_exists(p_slug, filename_no_ext, 's')) and url_cover: - store_r_cover(p_slug, filename_no_ext, url_cover, 's') - if r_cover_exists(p_slug, filename_no_ext, 's'): - path_cover_s = get_r_cover_path(p_slug, filename_no_ext, 's') + if (overwrite or not _cover_exists(p_slug, file_name, 's')) and url_cover: + _store_cover(p_slug, file_name, url_cover, 's') + if _cover_exists(p_slug, file_name, 's'): + path_cover_s = _get_cover_path(p_slug, file_name, 's') - if (overwrite or not r_cover_exists(p_slug, filename_no_ext, 'l')) and url_cover: - store_r_cover(p_slug, filename_no_ext, url_cover, 'l') - if r_cover_exists(p_slug, filename_no_ext, 'l'): - path_cover_l = get_r_cover_path(p_slug, filename_no_ext, 'l') + if (overwrite or not _cover_exists(p_slug, file_name, 'l')) and url_cover: + _store_cover(p_slug, file_name, url_cover, 'l') + if _cover_exists(p_slug, file_name, 'l'): + path_cover_l = _get_cover_path(p_slug, file_name, 'l') has_cover = 1 return path_cover_s, path_cover_l, has_cover - -def get_roms(p_slug: str) -> list: - """Gets all filesystem roms for a platform - - Args: - p_slug: short name of the platform - Returns: list with all the filesystem roms for a platform found in the LIBRARY_BASE_PATH. - Automatically discards the default directory. - """ - try: - roms_filename = list(os.walk(f"{LIBRARY_BASE_PATH}/{p_slug}/roms/"))[0][2] - roms: list[dict] = [ - {'filename': rom, - 'size': str(round(os.stat(f"{LIBRARY_BASE_PATH}/{p_slug}/roms/{rom}").st_size / (1024 * 1024), 2))} - for rom in roms_filename] - log.info(f"filesystem roms found for {p_slug}: {roms}") - except IndexError: - log.warning(f"roms not found for {p_slug}") - roms: list[dict] = [] - return roms - - -def r_exists(p_slug: str, filename: str) -> bool: - """Check if rom exists in filesystem - - Args: - p_slug: short name of the platform - filename: rom filename - Returns - True if rom exists in filesystem else False - """ - rom_path: str = f"{LIBRARY_BASE_PATH}/{p_slug}/roms/{filename}" - exists: bool = True if os.path.exists(rom_path) else False - return exists - - -def rename_rom(p_slug: str, filename: str, data: dict) -> None: - if data['filename'] != filename: - if r_exists(p_slug, data['filename']): raise HTTPException(status_code=500, detail=f"Can't rename: {data['filename']} already exists.") - os.rename(f"{LIBRARY_BASE_PATH}/{p_slug}/roms/{filename}", - f"{LIBRARY_BASE_PATH}/{p_slug}/roms/{data['filename']}") - - -def delete_rom(p_slug: str, filename: str) -> None: - try: - os.remove(f"{LIBRARY_BASE_PATH}/{p_slug}/roms/{filename}") - except FileNotFoundError: - log.warning(f"Rom not found in filesystem: {p_slug}/{filename}") diff --git a/changelog.md b/changelog.md index eda5d6478..7487083ce 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,18 @@ +# v1.5 (_30-03-2023_) + +**`Breaking change`** + +In order to make the new features structure 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 + - Now RomM folder structure is more flexible to match two different patrons by priority. This change makes RomM Emudeck compatible at least with single file games platforms. Check [folder structure](readme.md#โš ๏ธ-folder-structure) + + - Added config file support to exclude folders and specific extension files to be scanned. To reload config file RomM reload is needed. Check [config](readme.md#configuration). + + - Added tags support for region, revision/version and generic tags. Tags must have the right prefix to allow RomM scan them properly. Check [tags](readme.md#๐Ÿ“‘-tags-support). + # v1.4.1 (_29-03-2023_) ## Added @@ -7,6 +22,7 @@ ## Added - Gamecube support [platforms support](https://github.com/zurdi15/romm#platforms-support) + - PC support added (only for single file games like zip, iso, etc) [platforms support](https://github.com/zurdi15/romm#platforms-support) ## Changed @@ -17,23 +33,25 @@ ## Fixed **`Breaking change`** - **This breaking change only applies for mariaDB users**: -Some users reported errors when scanning files with large names because filenames 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. I am so sorry for any inconvenience this can generate. Columns to modify (examples in case that you set it with database name as romm, in other case just change the database name in the {db_name}.roms part): - - alter table romm.roms modify column filename varchar(500); - - alter table romm.roms modify column filename_no_ext varchar(500); - - alter table romm.roms modify column name varchar(500); - - alter table romm.roms modify column r_slug varchar(500); - - alter table romm.roms modify column p_slug varchar(500); - - alter table romm.roms modify column path_cover_l text; - - alter table romm.roms modify column path_cover_s text; - - alter table romm.platforms modify column slug varchar(500); - - alter table romm.platforms modify column name varchar(500); - - alter table romm.platforms modify column path_logo text; +``` + alter table romm.roms modify column file_name varchar(500); + alter table romm.roms modify column file_name_no_ext varchar(500); + alter table romm.roms modify column name varchar(500); + alter table romm.roms modify column r_slug varchar(500); + alter table romm.roms modify column p_slug varchar(500); + alter table romm.roms modify column path_cover_l text; + alter table romm.roms modify column path_cover_s text; + alter table romm.platforms modify column slug varchar(500); + alter table romm.platforms modify column name varchar(500); + alter table romm.platforms modify column path_logo text; +``` # v1.2.2 (_28-03-2023_) diff --git a/docker/docker-compose.example.yml b/docker/docker-compose.example.yml index d034b163e..51d4fd8a4 100644 --- a/docker/docker-compose.example.yml +++ b/docker/docker-compose.example.yml @@ -15,6 +15,7 @@ services: - STEAMGRIDDB_API_KEY=WIP volumes: - '/path/to/library:/library' + - '/path/to/config.yml:/romm/config.yml' ports: - '80:80' depends_on: diff --git a/docker/init_scripts/init_back b/docker/init_scripts/init_back index 492db50df..0f0918fba 100755 --- a/docker/init_scripts/init_back +++ b/docker/init_scripts/init_back @@ -1,4 +1,4 @@ #!/bin/bash cd /back -uvicorn main:app --proxy-headers --host 0.0.0.0 --port 5000 --workers 4 \ No newline at end of file +uvicorn main:app --proxy-headers --host 0.0.0.0 --port 5000 --workers 2 \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index a564af429..311fc8da4 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,14 +5,17 @@ import Navigation from '@/components/Navigation.vue' // Props useTheme().global.name.value = localStorage.getItem('theme') || 'dark' -const refresh = ref(false) +const refreshPlatforms = ref(false) const refreshRoms = ref(false) const snackbarShow = ref(false) const snackbarStatus = ref({}) // Event listeners bus const emitter = inject('emitter') -emitter.on('refresh', () => { refresh.value = !refresh.value }) +emitter.on('refresh', () => { + refreshPlatforms.value = !refreshPlatforms.value + refreshRoms.value = !refreshRoms.value +}) emitter.on('refreshRoms', () => { refreshRoms.value = !refreshRoms.value }) emitter.on('snackbarScan', (snackbar) => { snackbarShow.value = true @@ -23,7 +26,7 @@ emitter.on('snackbarScan', (snackbar) => {