ArtworkService class manages the artwork and metadata pipeline: hashing ROMs, querying ScreenScraper, downloading images, and updating the library. It extends EventEmitter to report progress to the renderer.
Constructor
LibraryService instance for accessing game data
Credentials management
hasCredentials()
Check whether ScreenScraper user credentials are configured.boolean
validateCredentials()
Validate credentials against ScreenScraper before storing them. Makes a lightweight API call to check if the credentials are accepted.ScreenScraper username
ScreenScraper password
Promise<{ valid: boolean; error?: string; errorCode?: ArtworkErrorCode }>
Whether credentials are valid
Error message (if validation failed)
Structured error code:
'auth-failed', 'rate-limited', 'config-error', 'network-error', 'not-found'setCredentials()
Store ScreenScraper user credentials to disk.ScreenScraper username
ScreenScraper password
Promise<void>
Credentials are stored in plain text in
artwork-config.json. For production use, consider encrypting them with Electron’s safeStorage API.clearCredentials()
Remove stored credentials.Promise<void>
Artwork syncing
syncGame()
Run the full artwork pipeline for a single game: query ScreenScraper by hash, fall back to name search, download artwork, and update metadata.Game ID (from LibraryService)
Force re-sync even if artwork already exists (default: false)
Promise<boolean> - True if artwork was found and downloaded
Throws: ScreenScraperError with errorCode: 'auth-failed' if credentials are invalid
This method respects rate limiting (1.1s between requests) and includes automatic retry with backoff on rate limit errors.
syncAllGames()
Sync artwork for all games that don’t have cover art yet. Processes games serially with rate limiting.Promise<ArtworkSyncStatus>
progress- For each game (see ArtworkProgress below)syncComplete- When batch finishes (with final status)
syncGames()
Sync artwork for a specific list of game IDs. Used for auto-sync after ROM import to avoid re-syncing the entire library.Array of game IDs to sync
Promise<ArtworkSyncStatus>
Emits: Same events as syncAllGames()
Automatically filters out games that already have cover art or don’t exist in the library.
cancelSync()
Cancel an in-progress bulk sync.void
The current game will finish processing, but no additional games will be synced.
getSyncStatus()
Get current sync status.{ inProgress: boolean }
Artwork storage
getArtworkDirectory()
Returns the artwork directory path, creating it if needed.string
downloadArtwork()
Download an image from a URL to the local artwork directory. Includes a 30-second timeout and automatic redirect following.Source image URL
Destination filename (relative to artwork directory)
Promise<string> - Full local path to the saved file
Throws: Error if download fails or times out
Events
ArtworkService extends EventEmitter and emits the following events:Emitted for each game during batch sync
Game ID
Game title
Current phase:
'hashing', 'querying', 'downloading', 'done', 'not-found', 'error'Current game index (1-based)
Total games in batch
Cover art URL (only in
'done' phase)Aspect ratio (only in
'done' phase)Error message (only in
'error' phase)Error code (only in
'error' phase)Emitted when a batch sync finishes (successfully or after cancellation)
Rate limiting
ArtworkService enforces ScreenScraper’s rate limits:- 1.1 seconds minimum delay between API requests
- 10 seconds backoff after a 429 (rate limited) response
- 30 seconds timeout for image downloads
Rate limits are automatically managed internally. You don’t need to implement throttling in your own code.
Error handling
All errors from ScreenScraper include a structurederrorCode:
Usage example
Implementation details
Aspect ratio clamping
Cover art aspect ratios are clamped to [0.4, 1.8] to accommodate both portrait and landscape box art:Hash matching priority
The service queries ScreenScraper in this order:- MD5 hash (fastest, most reliable)
- Fallback to name search (if hash lookup fails)
game.romHashes.md5.
Artwork URL priority
When multiple artwork types are available, the service prefers:boxArt2d(front cover)boxArt3d(3D box render)screenshot(in-game screenshot)
Source reference
- Implementation:
apps/desktop/src/main/services/ArtworkService.ts:36 - Usage example:
apps/desktop/src/main/ipc/handlers.ts:23