├── requirements.txt
├── deckrommsync.db
├── docs
├── deckrommsync.png
└── platform_matching.png
├── .gitignore
├── classes
├── __pycache__
│ ├── RommAPIHelper.cpython-311.pyc
│ ├── BackgroundWorker.cpython-311.pyc
│ └── DeckRommSyncDatabase.cpython-311.pyc
├── DeckRommSyncDatabase.py
├── RommAPIHelper.py
└── BackgroundWorker.py
├── config.json
├── LICENSE.md
├── templates
├── log.html
├── base.html
├── status.html
└── config.html
├── README.md
└── app.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | APScheduler==3.11.0
2 | Django==3.1
3 | Flask==3.1.0
4 | Requests==2.32.3
5 |
--------------------------------------------------------------------------------
/deckrommsync.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeriBluGaming/DeckRommSync-Standalone/HEAD/deckrommsync.db
--------------------------------------------------------------------------------
/docs/deckrommsync.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeriBluGaming/DeckRommSync-Standalone/HEAD/docs/deckrommsync.png
--------------------------------------------------------------------------------
/docs/platform_matching.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeriBluGaming/DeckRommSync-Standalone/HEAD/docs/platform_matching.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | system.log
2 | .vscode/settings.json
3 | background_worker.log
4 | test.py
5 | tests/*
6 | output/*
7 | TEST_background_worker.log
8 |
--------------------------------------------------------------------------------
/classes/__pycache__/RommAPIHelper.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeriBluGaming/DeckRommSync-Standalone/HEAD/classes/__pycache__/RommAPIHelper.cpython-311.pyc
--------------------------------------------------------------------------------
/classes/__pycache__/BackgroundWorker.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeriBluGaming/DeckRommSync-Standalone/HEAD/classes/__pycache__/BackgroundWorker.cpython-311.pyc
--------------------------------------------------------------------------------
/classes/__pycache__/DeckRommSyncDatabase.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeriBluGaming/DeckRommSync-Standalone/HEAD/classes/__pycache__/DeckRommSyncDatabase.cpython-311.pyc
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "host": "0.0.0.0",
4 | "port": 5000
5 | },
6 | "database": {
7 | "name": "deckrommsync.db",
8 | "type": "sqlite"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License (Modified - No Selling)
2 |
3 | Copyright (c) 2025 PeriBluGaming
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, and sublicense the Software,
9 | subject to the following conditions:
10 |
11 | 1. The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 | 2. The Software and any derivative works may **not** be sold or commercially distributed
14 | for monetary gain.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/templates/log.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
10 |
11 |
12 |
Background Worker Logging
13 | {% for group in log_groups %}
14 |
15 |
16 |
{{ group[0].split('- INFO')[0] }}
17 |
18 | {% for line in group %}
19 |
{{ line }}
20 | {% endfor %}
21 |
32 |
33 |
34 |
35 | {% endfor %}
36 |
37 |
38 | {% endblock content %}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DeckRommSync - Standalone
2 | DeckRomMSync - Standalone is a project that automatically synchronizes your ROMs from [RomM](https://github.com/rommapp/romm) to your Steam Deck.
3 | The games are automatically copied to the correct directory. Retrodeck is required!
4 | 
5 |
6 | ## Installation
7 | 1. Clone the Repository to your Steamdeck
8 | ```bash
9 | git clone https://github.com/PeriBluGaming/DeckRommSync-Standalone.git
10 | ```
11 |
12 | 2. Create a virtual environment and activate them
13 | ```bash
14 | python -m venv venv
15 | source venv/bin/activate
16 | ```
17 |
18 | 3. Install Requirements
19 | ```bash
20 | pip install -r requirements.txt
21 | ```
22 |
23 | 4. (Optional) Adjust the port in the config.json file. By default, the application runs on port 5000.
24 |
25 | ## Configuration
26 | Now the installation is complete and you can start the application with following command. Make sure you have activate the environment, see Installation Point 2.
27 | ```bash
28 | python3 app.py
29 | ```
30 |
31 | After starting the application open a Browser `http://{ip-steamdeck}:5000` and click on `Config`.
32 | Now you have to setup following Settings:
33 |
34 | ### RomM API Settings
35 | **RomM API URL:** API URL from RomM `http://{ip-romm}:{port-romm}/api`\
36 | **Username:** your Username of RomM\
37 | **Password:** your Password of RomM
38 |
39 | Press Save after entering the data. Then wait 2-3 minutes until the background worker has completed one cycle. Now refresh the Browser (F5)
40 | (The Background Worker will fetch your Collections / Platforms / etc.)
41 |
42 | ### Configurate Platform Matching
43 | After the Background Worker runs for the first time, you can see your Platforms and Collections on the Config-Page.\
44 | **Steamdeck System Path:** Enter the path of your RetroDeck installation under `Steamdeck System Path`.
45 |
46 | Below, you will see a table with all platforms.
47 | For each platform, enter the folder name of your RetroDeck platform. For example: `Playstation 1 -> psx` and press Save.
48 | **Note: You must press Save for every Platform your set!!!**
49 | 
50 |
51 | ### Activate Collection Sync
52 | To automatically sync the ROMs of one or more collections, you need to enable the collection.
53 |
54 | On the Config page, you'll find the "Sync Collections" section. Here, your collections from RomM are displayed. You can enable/disable synchronization using the checkboxes.
55 |
56 | **Note: Click Save after enabling it.**
57 |
58 | After that, the synchronization will automatic run!
--------------------------------------------------------------------------------
/classes/DeckRommSyncDatabase.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | from typing import List, Tuple, Any
3 |
4 | class DeckRommSyncDatabase:
5 | def __init__(self, db_name: str):
6 | """
7 | Initialisiert die Verbindung zur SQLite-Datenbank.
8 | """
9 | self.db_name = db_name
10 | self.connection = sqlite3.connect(self.db_name, check_same_thread=False)
11 | self.cursor = self.connection.cursor()
12 |
13 | def execute_query(self, query: str, params: Tuple = ()) -> None:
14 | """
15 | Führt eine SQL-Abfrage ohne Rückgabewert aus (INSERT, UPDATE, DELETE).
16 | """
17 | try:
18 | self.cursor.execute(query, params)
19 | self.connection.commit()
20 | except sqlite3.Error as e:
21 | print(f"SQLite Fehler: (0) {e}")
22 |
23 | def insert(self, table: str, columns: List[str], values: Tuple) -> None:
24 | """
25 | Führt einen INSERT in die Datenbank aus.
26 | """
27 | cols = ', '.join(columns)
28 | placeholders = ', '.join(['?' for _ in columns])
29 | query = f"INSERT INTO {table} ({cols}) VALUES ({placeholders})"
30 | # print(query)
31 | self.execute_query(query, values)
32 |
33 | def update(self, table: str, updates: dict, condition: str, condition_values: Tuple) -> None:
34 | """
35 | Führt ein UPDATE in der Datenbank aus.
36 | """
37 | set_clause = ', '.join([f"{key} = ?" for key in updates.keys()])
38 | query = f"UPDATE {table} SET {set_clause} WHERE {condition}"
39 | values = tuple(updates.values()) + condition_values
40 | self.execute_query(query, values)
41 |
42 | def fetch_query(self, query: str, params: Tuple = ()) -> List[Tuple]:
43 | """
44 | Führt eine SELECT-Abfrage aus und gibt die Ergebnisse zurück.
45 | """
46 | try:
47 | self.cursor.execute(query, params)
48 | return self.cursor.fetchall()
49 | except sqlite3.Error as e:
50 | print(f"SQLite Fehler: (1) {e}")
51 | return []
52 |
53 | def select(self, table: str, columns: List[str] = ['*'], condition: str = '', condition_values: Tuple = ()) -> List[Tuple]:
54 | """
55 | Führt ein SELECT in der Datenbank aus und gibt die Ergebnisse zurück.
56 | """
57 | cols = ', '.join(columns)
58 | query = f"SELECT {cols} FROM {table}"
59 | if condition:
60 | query += f" WHERE {condition}"
61 | return self.fetch_query(query, condition_values)
62 |
63 | def select_as_dict(self, table: str, columns: List[str] = ['*'], condition: str = '', condition_values: Tuple = ()) -> List[dict]:
64 | """
65 | Führt ein SELECT in der Datenbank aus und gibt die Ergebnisse als Liste von Dictionaries zurück.
66 | """
67 | cols = ', '.join(columns)
68 | query = f"SELECT {cols} FROM {table}"
69 | if condition:
70 | query += f" WHERE {condition}"
71 |
72 | try:
73 | self.cursor.execute(query, condition_values)
74 | rows = self.cursor.fetchall()
75 | column_names = [desc[0] for desc in self.cursor.description] # Holt die Spaltennamen
76 | return [dict(zip(column_names, row)) for row in rows] # Erstellt Dicts
77 | except sqlite3.Error as e:
78 | print(f"SQLite Fehler: (2) {e}")
79 | return []
--------------------------------------------------------------------------------
/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |