Skip to content

Architecture

iPodRocks is an Electron desktop app that syncs your music library to Rockbox and other mountable players. Like every Electron app, it runs as two processes — a privileged main process that talks to disk, devices, and the network, and a sandboxed renderer process that draws the UI. This page is a tour of what lives on each side and how they talk to each other.

Overview

Here is the bird's-eye view. The main process owns the database and everything that touches the outside world; the renderer is pure React on top of Zustand state.

iPodRocks architecture overview

Main process

Runs in Node.js. Has full filesystem and OS access. Everything that touches the disk, the device, the network, or the database lives here.

To keep the picture manageable, modules are grouped by area below — but they all live under src/main/ and share a single better-sqlite3 database instance.

Library and metadata

This is where your music catalog comes from. Files get walked, tagged, hashed, and (optionally) mirrored into a transcoded shadow library.

ModuleResponsibility
LibraryCoreCRUD for tracks, artists, albums, genres, and library folders.
LibraryScannerWalks library folders, extracts metadata, writes to DB.
MetadataExtractorReads tags via music-metadata — title, artist, album, genre, year, replay gain, embedded art.
HashManagerSHA-256 content hashing for change detection (mtime + hash).
ShadowLibraryManages pre-transcoded mirror libraries (e.g. FLAC → MPC) so devices can copy without on-the-fly conversion.
TaggingAPEv2 and MPC tag writers — used when iPodRocks needs to stamp metadata back into files.

Devices and sync

Adding a device profile, comparing it against the library, and pushing the right files (transcoded if needed) to the right folders on disk.

ModuleResponsibility
DevicesCoreStores and retrieves device profiles, codec configs, transfer modes.
SyncCoreCompares library against device and produces a sync plan.
SyncExecutorCarries out the plan — copies, transcodes, removes files.
SyncConversionTranscodes audio via FFmpeg or mpcenc.
DeviceSyncPreferencesPer-device rules: which content types, which playlists, orphan policy.
TagcacheIOReads and writes the Rockbox database_changelog.txt on the device mount. Handles Phase 1 (ingest) and Phase 3 (propagate) of the ratings sync cycle.

Ratings

A track's rating can change in two places — your library and your device — so reconciling them needs more than a last-write-wins.

ModuleResponsibility
RatingMerge3-way merge that reconciles ratings between device and library. Detects device changes, resolves conflicts, and computes what to propagate back.

Playlists

ModuleResponsibility
PlaylistCoreCRUD for all playlist types; export to Rockbox playlist files.
SmartPlaylistsRule-based playlists (genre/artist/album/etc.), with optional Rockbox tagnavi mode.
GeniusEngineBuilds playlists from playback history using graph-based scoring (Most Played, Forgotten Gems, and others).
PlaybackLogIngestParses Rockbox playback.log files off the device into playback_logs and playback_stats.

AI (Savant and Rocksy)

Two LLM-powered surfaces share one OpenRouter client. Savant generates playlists from mood; Rocksy is the floating chat that knows your library.

ModuleResponsibility
OpenRouterClientCalls the OpenRouter API with timeout, rate limiting, and prompt-injection mitigation.
SavantEngineLLM orchestration for AI playlist generation.
MoodChatConversational mood capture for Savant playlists.
SavantPlaylistChatMulti-turn playlist refinement loop.
HarmonicSequencerReorders Savant playlists along the Camelot wheel for harmonic mixing.
AssistantChatLLM orchestration for Rocksy. Runs an OpenAI-compatible tool-calling loop (up to 5 rounds per message), feeding each tool result back to the model before the final reply.
AssistantTools (tools.ts)The tool registry Rocksy calls. Each tool declares a tier — read (run inline), write-safe (mutations, run inline), or write-destructive (deletes/syncs/scans/folder changes, gated behind a renderer confirm). Covers library, playlists, podcasts (incl. add-by-URL), audiobooks, and devices.

Destructive tools are not executed inline: Rocksy proposes the action and the renderer shows a Confirm / Cancel gate. The assistant:confirmAction IPC channel executes the proposed tool only after the user confirms.

Podcasts

A full subscribe-and-sync pipeline backed by the Podcast Index API, plus a direct add-by-URL path that needs no API key.

ModuleResponsibility
PodcastSubscriptionsManages subscribed shows in the database.
PodcastIndexClientTalks to the Podcast Index API for search and feed lookup.
PodcastFeedImportAdd-by-URL pipeline: classifies input (RSS vs website), discovers feeds from HTML (<link rel="alternate"> and common feed paths), and parses RSS/Atom into a normalized feed. Reused by the audiobook flow to read LibriVox chapter feeds.
PodcastRefreshPolls feeds for new episodes and updates the DB.
PodcastDownloaderDownloads episode audio and writes metadata.
PodcastCoverExtractorExtracts and caches episode/show artwork.
PodcastSchedulerAuto-refresh and auto-download cadence.
PodcastStorageOn-disk layout for downloaded episodes.
PodcastDeviceSyncPicks the right episodes per device and copies them during sync.

Audiobooks (Extra)

Free public-domain audiobooks from LibriVox, downloaded on demand at sync time. No account or API key required.

ModuleResponsibility
AudiobookSubscriptionsCRUD for subscribed books and their chapters; pulls the chapter list from each book's RSS feed via PodcastFeedImport.
LibrivoxClientSearches the LibriVox catalog by title/author and maps results.
AudiobookDownloaderDownloads chapter audio on demand.
AudiobookCover / CoverClientResolves cover artwork from Google Books / Open Library (LibriVox feeds rarely carry art); supports a candidate search for the manual cover picker.
AudiobookStorageOn-disk layout for downloaded chapters and covers.
AudiobookDeviceSyncDownloads-on-sync and copies chapters (and the cover) to <device>/<Audiobooks>/<Author - Title>/, idempotent via device_audiobook_synced.

Playback

Lets the renderer play local tracks without exposing filesystem paths.

ModuleResponsibility
PlayerSourceResolves a track ID to a streamable source.
MediaProtocolCustom Electron protocol that streams audio bytes to the renderer's <audio> element.

Harmonic analysis

Optional key/BPM analysis that powers Savant's harmonic sequencing.

ModuleResponsibility
Essentia.jsWASM module for key and BPM detection.
CamelotWheelMaps musical keys to Camelot notation and computes compatibility between tracks.

Foundation

The plumbing every other module sits on top of.

ModuleResponsibility
IPC Handlers (ipc.ts)Around 110 channels bridged to the renderer. Applies safe() wrapper, sanitizeErrorMessage, and rate limiting.
AppDatabaseSingle better-sqlite3 instance. All reads and writes go through here.
PrefsReads and writes prefs.json (settings, encrypted API key via Electron's safeStorage).
PathAllowlistValidates that library and shadow library paths stay within allowed directories.
ActivityLoggerAppend-only log of significant operations, surfaced in the Dashboard.
UpdateCheckerPolls GitHub Releases for new versions and prompts the user.

Renderer process

Runs in a Chromium sandbox. Cannot touch the filesystem directly — every cross-boundary operation goes through window.api.invoke().

State (Zustand stores)

Server state lives in stores; React components subscribe.

StoreHolds
useLibraryStoreTracks, artists, albums, genres, library folders.
useDeviceStoreDevice profiles, codec configs, online status.
useSyncStoreLast sync plan, sync progress, conflicts.
useUIStoreActive tab, modal stack, transient UI state.
useThemeStoreLight/dark and accent.
usePlayerStoreCurrent track, queue, playback position.
usePodcastsStoreSubscriptions, episode lists, download progress.
useAudiobooksStoreLibriVox subscriptions, chapters per book, live cover updates.
useSavantStoreMood chat session, generated playlists, backfill progress.

Top-level panels

One per primary tab in the sidebar.

PanelPurpose
WelcomePanelFirst-launch overview and quick links.
DashboardPanelLibrary stats, devices, shadow libraries, recent activity.
LibraryPanelFolders, scans, track list.
DevicePanelAdd/edit devices, codec configuration, online checks.
SyncPanelPick what to sync and run it.
PlaylistPanelSmart, Genius, Savant tabs.
AutoPodcastsPanelSubscribe to shows (search or by URL) and configure per-device auto-sync.
AutoAudiobooksPanelBrowse and subscribe to free LibriVox audiobooks ("Extra Audiobooks" tab).
SettingsPanelOpenRouter, harmonic analysis, codecs, podcasts.

Ratings UI

ComponentPurpose
RatingStars5-star input with half-star support. Displays device-source and conflict badges.
RatingConflictsModalLists unresolved rating conflicts and lets the user resolve them (keep library, use device, or set manually).

Progress and dialogs

ModalPurpose
ScanProgressModalFile-by-file scan progress; captures folders in a ref to prevent restart on re-render.
SyncProgressModalCopy progress; uses useRef counters to avoid stale-closure bugs in the onComplete callback.
BackfillProgressModalHarmonic backfill progress.
AddDeviceModal / AddFolderModalForms for new devices and library folders.
PodcastSearchModal / PodcastEpisodeModalDiscover (keyword Search or Add by URL tabs) and inspect podcasts.
AudiobookSearchModal / AudiobookDetailModal / AudiobookCoverPickerModalSearch LibriVox, inspect a book's chapters, and pick a cover.
MpcUnavailableModalSurfaced when mpcenc is missing on the host.
UpdateAvailableModalPrompts the user when a new release is detected.
ConfirmDialogGeneric confirm/cancel.

AI surfaces

ComponentPurpose
FloatChatFloating Rocksy chat, backed by AssistantChat on the main process. Renders the Confirm / Cancel gate for destructive tool calls and locks input until the user responds.
SavantInlineChatInline mood chat used inside the Playlists panel.

Playback

ComponentPurpose
PlayerBarSticky transport bar at the bottom of the app. Reads from usePlayerStore and streams audio via MediaProtocol.

IPC API layer

renderer/ipc/api.ts is a thin wrapper around window.api.invoke() — one typed function per IPC channel. No business logic lives here; it exists so React code doesn't have to remember channel names.

IPC communication

The preload script uses Electron's contextBridge to expose a strict allowlist of channels as window.api. The renderer calls window.api.invoke(channel, ...args); the main process handles it in ipcMain.handle() inside a safe() wrapper that:

  1. Catches all errors and returns { error: string } (it never throws across IPC).
  2. Strips absolute paths from error messages before they reach the renderer.
  3. Enforces per-channel rate limits — currently 10 calls per 60 seconds for LLM channels.

Database schema (high-level)

One SQLite file, around 36 tables. They group naturally by feature:

Library core — your music catalog itself.

tracks · artists · albums · genres · library_folders

Playlists — all four playlist types share the same join structure.

playlists · playlist_items · playlist_types · smart_playlist_rules · genius_playlist_configs

Shadow libraries — pre-transcoded mirrors of source tracks.

shadow_libraries · shadow_tracks

Devices and sync — device profiles, codec setup, and what is currently on each device.

devices · device_models · codecs · codec_configurations · device_transfer_modes · device_synced_tracks · device_sync_preferences · sync_configurations · sync_rules

Ratings — the three tables that make the bi-directional rating sync work.

TableDescription
device_track_ratingsPer-device baseline manifest; tracks last_seen and last_pushed.
rating_conflictsUnresolved divergences awaiting user resolution.
rating_eventsFull audit log of every rating change.

Podcasts — subscriptions, episodes, and what has been copied to each device.

podcast_subscriptions · podcast_episodes · device_podcast_synced

Audiobooks — LibriVox subscriptions, their chapters, and what has been copied to each device.

audiobook_subscriptions · audiobook_chapters · device_audiobook_synced

Playback and history — fuel for the Genius engine.

playback_logs · playback_stats

System — preferences, hashes, audit, chat memory.

app_settings · content_hashes · activity_log · assistant_chat_history

The source of truth is src/main/database/schema.ts — when in doubt, read it.