Skip to content

Playlists and History

Joseph Chacko edited this page Jul 22, 2026 · 2 revisions

Playlist, Liked Songs & History Persistence

Relevant source files

The following files were used as context for generating this wiki page:

This section documents the persistence layer of the Aurora daemon, specifically how user-generated data such as playlists, liked songs, and playback history are stored in SQLite and managed through the StateStruct.

Database Schema & Initialization

Aurora uses a SQLite database located at ~/.config/aurora-player/aurora.db daemon/src/helpers/db.rs:9-13. The database is initialized with PRAGMA journal_mode=WAL and foreign_keys=ON to ensure performance and referential integrity daemon/src/helpers/db.rs:16-17.

Table Definitions

The migrate function daemon/src/helpers/db.rs:23-54 defines the following schema:

Table Purpose Key Columns
songs Cache of indexed file metadata. id (PK), path, mtime, art_path
playlists Metadata for user-created playlists. id (PK), title
playlist_songs Junction table for songs in playlists. playlist_id (FK), song_id, position
liked_songs Set of song IDs marked as favorites. song_id (PK)
history Recently played tracks. song_id (PK), played_at

Sources: daemon/src/helpers/db.rs:24-54

Playlist Management

Playlist operations are split between high-level handlers that communicate with the client and low-level helpers that interact with the rusqlite connection.

Implementation Flow: Playlist Retrieval

When a user requests a playlist, the system performs a multi-stage join between the database and the in-memory index.

Title: Playlist Data Retrieval Flow

sequenceDiagram
    participant H as handlers/playlist.rs
    participant S as StateStruct
    participant DB as SQLite (playlists/playlist_songs)
    participant I as In-Memory Index

    H->>S: get_playlist(uuid)
    S->>DB: SELECT title, song_ids WHERE playlist_id
    DB-->>S: (title, Vec<song_id>)
    loop For each song_id
        S->>I: get(song_id)
        I-->>S: SongMeta
        S->>S: get_art(song_id)
    end
    S-->>H: Playlist { title, songs: Vec<Song> }
Loading

Sources: daemon/src/helpers/playlist.rs:11-60, daemon/src/handlers/playlist.rs:152-177

CRUD Operations

Playlist modification functions are defined in helpers/playlist.rs and typically wrapped in tokio::task::spawn_blocking to prevent blocking the async executor during disk I/O daemon/src/helpers/playlist.rs:154-163.

Sources: daemon/src/helpers/playlist.rs:1-242, daemon/src/handlers/playlist.rs:1-178

Liked Songs Store

Liked songs are managed as a HashSet<Uuid> in the StateStruct for O(1) lookups during playback (to show the "liked" status of the current song) and persisted in the liked_songs table.

Operations

  1. load_liked: Called during daemon startup to populate the in-memory set daemon/src/helpers/liked_store.rs:6-30.
  2. like_song: Inserts the ID into the in-memory liked_ids and the database liked_songs table daemon/src/handlers/liked.rs:9-17.
  3. unlike_song: Removes the ID from both the set and the database daemon/src/handlers/liked.rs:19-31.

Sources: daemon/src/helpers/liked_store.rs:1-63, daemon/src/handlers/liked.rs:1-51

Playback History

The history system tracks recently played songs, capped at a maximum of 30 entries daemon/src/helpers/history.rs:6.

History Logic

  • push_history: Adds a song to the front of a VecDeque. If the song was already at the front, it is ignored. If it existed elsewhere in the history, it is moved to the front to avoid duplicates daemon/src/helpers/history.rs:58-65.
  • save_history: Persists the history by clearing the history table and re-inserting the current deque. It uses a transaction (BEGIN / COMMIT) and calculates a played_at value based on i64::MAX minus the index to maintain order daemon/src/helpers/history.rs:36-56.

Title: Persistence Layer Code Entities

classDiagram
    class StateStruct {
        +Db db
        +HashMap index
        +HashSet liked_ids
        +VecDeque history
        +get_playlist(Uuid)
        +get_all_playlists()
    }
    class PlaylistHandlers {
        +playlist_create()
        +playlist_add_songs()
        +playlist_get()
    }
    class LikedStore {
        +load_liked(Db)
        +add_liked(Db, Uuid)
        +remove_liked(Db, Uuid)
    }
    class HistoryHelpers {
        +load_history(Db)
        +save_history(Db, VecDeque)
        +push_history(VecDeque, Uuid)
    }

    PlaylistHandlers ..> StateStruct : locks & accesses
    StateStruct ..> LikedStore : uses for persistence
    StateStruct ..> HistoryHelpers : uses for persistence
Loading

Sources: daemon/src/helpers/history.rs:1-66, daemon/src/types/mod.rs:1-14, daemon/src/helpers/liked_store.rs:1-63

Error Handling in Persistence

Persistence operations return std::io::Result or anyhow::Result. The to_io helper daemon/src/helpers/db.rs:56-58 converts rusqlite::Error into std::io::Error for consistency. If a database operation fails in a handler, an aurora_protocol::Response::Error is sent back to the client with a specific error ID (e.g., ID 8 for rename failures, ID 7 for delete failures) daemon/src/handlers/playlist.rs:19-32.

Sources: daemon/src/helpers/db.rs:56-58, daemon/src/handlers/playlist.rs:19-55

Clone this wiki locally