-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Developer Guide Library
The library backend is responsible for storing, retrieving, and synchronizing all track metadata in Mixxx. It manages an internal SQLite database, an in-memory track cache, and a plug-in interface for external track collections (Rekordbox, Serato, etc.).
The entry point for the rest of the application is the Library class
(src/library/library.h), which owns the TrackCollectionManager and
wires up all the library features and sidebar models. Most code outside
the library should interact with TrackCollectionManager rather than
the lower-level components directly.
Mixxx stores all library state in a single SQLite file (mixxxdb.sqlite
in the user's Mixxx configuration directory). The connection is pooled
via mixxx::DbConnectionPool, which lets background threads (the
scanner, analysis workers) open their own connections without blocking
the main thread.
The two core tables are:
-
library-- one row per track. Contains all metadata columns (artist,title,bpm,key,rating,color, etc.) plus internal housekeeping columns such asmixxx_deleted(soft-delete flag) andheader_parsed. -
track_locations-- one row per distinct file path. Stores the absolutelocation,filename,directory,filesize,fs_deleted(whether the file was absent on the last scan), andneeds_verification. Thelibrarytable referencestrack_locationsvia a foreign key.
Separating the two tables allows Mixxx to detect moved files: the same
track_locations row can be re-pointed to a new path without losing any
metadata stored in library.
Column name constants are defined in src/library/dao/trackschema.h.
TrackCollectionManager is the authoritative gateway for all mutating
library operations. Every add, hide, purge, relocate, or metadata-save
operation must go through this class so that both the internal
collection and any connected external collections stay in sync.
class TrackCollectionManager : public QObject,
public virtual GlobalTrackCacheSaver {
public:
TrackCollection* internalCollection() const;
const QList<ExternalTrackCollection*>& externalCollections() const;
TrackPointer getTrackById(TrackId trackId) const;
TrackPointer getTrackByRef(const TrackRef& trackRef) const;
bool hideTracks(const QList<TrackId>& trackIds) const;
bool unhideTracks(const QList<TrackId>& trackIds) const;
void purgeTracks(const QList<TrackRef>& trackRefs) const;
DirectoryDAO::AddResult addDirectory(const mixxx::FileInfo& newDir) const;
DirectoryDAO::RemoveResult removeDirectory(const mixxx::FileInfo& oldDir) const;
DirectoryDAO::RelocateResult relocateDirectory(
const QString& oldDir, const QString& newDir) const;
TrackPointer getOrAddTrack(
const TrackRef& trackRef,
bool* pAlreadyInLibrary = nullptr) const;
SaveTrackResult saveTrack(const TrackPointer& pTrack) const;
void startLibraryScan();
};TrackCollectionManager also implements GlobalTrackCacheSaver, which
means it is the object responsible for flushing Track objects back to
the database when they are evicted from the GlobalTrackCache.
TrackCollectionManager and TrackCollection (and all DAOs inside it)
enforce main-thread-only access via DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY.
Background threads (scanner, analysis) use their own database connections
obtained from the shared DbConnectionPool and communicate results back
via Qt signals.
TrackCollection owns the main-thread QSqlDatabase connection and
all of the Data Access Objects (DAOs). It also owns CrateStorage and
a QSharedPointer<BaseTrackCache>.
class TrackCollection : public QObject,
public virtual SqlStorage {
public:
TrackDAO& getTrackDAO();
PlaylistDAO& getPlaylistDAO();
const DirectoryDAO& getDirectoryDAO() const;
AnalysisDao& getAnalysisDAO();
const CrateStorage& crates() const;
// Crate write helpers (delegate to CrateStorage inside a transaction
// and emit change signals):
bool insertCrate(const Crate& crate, CrateId* pCrateId = nullptr);
bool updateCrate(const Crate& crate);
bool deleteCrate(CrateId crateId);
bool addCrateTracks(CrateId crateId, const QList<TrackId>& trackIds);
bool removeCrateTracks(CrateId crateId, const QList<TrackId>& trackIds);
signals:
void tracksAdded(const QSet<TrackId>& trackIds);
void tracksChanged(const QSet<TrackId>& trackIds);
void tracksRemoved(const QSet<TrackId>& trackIds);
void crateInserted(CrateId id);
void crateUpdated(CrateId id);
void crateDeleted(CrateId id);
void crateTracksChanged(CrateId, const QList<TrackId>& added,
const QList<TrackId>& removed);
};The private interface (only accessible to TrackCollectionManager) is
where the actual mutations live: addTrack, hideTracks, purgeTracks,
relocateDirectory, etc. This ensures that all mutations flow through
TrackCollectionManager.
All DAOs live under
src/library/dao/
and inherit from the DAO marker interface (dao.h). They each receive
a QSqlDatabase reference via initialize() and operate directly
against the SQLite database.
The most important DAO. Key responsibilities:
-
Resolving tracks --
resolveTrackIds(fileInfos, flags)maps a list ofFileInfoobjects toTrackIds. Theflagsparameter controls whether hidden tracks are un-hidden (UnhideHidden) and whether new tracks are added to the database (AddMissing). -
Loading tracks --
getTrackByRef(TrackRef)fetches a track from the database (or theGlobalTrackCache) and returns aTrackPointer. TheTrackRefcan be constructed from aTrackIdor a file location. -
Saving tracks --
saveTrack(Track*)writes dirty track metadata back to the database. Normally called byTrackCollectionManagerwhen theGlobalTrackCacheevicts a track. -
Detecting moved files --
detectMovedTracks()cross-references a list of newly-seen paths against the set of paths that were missing on the last scan, and rebuilds thetrack_locationsrows accordingly. -
Play counter --
updatePlayCounterFromPlayedHistory()updatestimesplayedandlast_played_atfrom the history table.
TrackDAO emits tracksAdded, tracksChanged, and tracksRemoved
signals when the database is modified.
Manages the Playlists and PlaylistTracks tables.
Playlists have a HiddenType column that repurposes the playlist
mechanism for internal features:
HiddenType |
Meaning |
|---|---|
PLHT_NOT_HIDDEN (0) |
User-visible playlist |
PLHT_AUTO_DJ (1) |
Auto DJ queue |
PLHT_SET_LOG (2) |
History (set log) |
Key API:
int createPlaylist(const QString& name, HiddenType type);
void deletePlaylist(int playlistId);
void renamePlaylist(int playlistId, const QString& newName);
bool setPlaylistLocked(int playlistId, bool locked);
bool appendTracksToPlaylist(const QList<TrackId>& trackIds, int playlistId);
bool insertTrackIntoPlaylist(TrackId trackId, int playlistId, int position);
void removeTrackFromPlaylist(int playlistId, int position);
void moveTrack(int playlistId, int oldPosition, int newPosition);
QList<TrackId> getTrackIdsInPlaylistOrder(int playlistId) const;
QList<QPair<int,QString>> getPlaylists(HiddenType hidden) const;Track positions within a playlist are stored as an integer position
column in PlaylistTracks and are contiguous but not necessarily
sequential after insertions and deletions — use orderTracksByCurrPos()
to compact them when needed.
Unlike the DAOs, CrateStorage is accessed through TrackCollection
which wraps the write operations in transactions and emits the
appropriate signals. Do not call CrateStorage write methods
directly — use TrackCollection::insertCrate(),
addCrateTracks(), etc. instead.
CrateStorage exposes read-only query results as forward-only iterator
objects rather than materializing full lists:
// Read a single crate by id or name:
bool readCrateById(CrateId id, Crate* pCrate) const;
bool readCrateByName(const QString& name, Crate* pCrate) const;
// Iterate all crates (ordered by name, locale-aware):
CrateSelectResult selectCrates() const;
// Usage:
CrateSelectResult result = crates.selectCrates();
Crate crate;
while (result.populateNext(&crate)) { /* ... */ }
// Iterate crate contents:
CrateTrackSelectResult selectCrateTracksSorted(CrateId crateId) const;
// Summary view (includes track count and total duration):
CrateSummarySelectResult selectCrateSummaries() const;
// Which crates contain a given track:
CrateTrackSelectResult selectTrackCratesSorted(TrackId trackId) const;
// Auto DJ sources:
CrateSelectResult selectAutoDjCrates(bool autoDjSource = true) const;The Crate value type (crateid.h, crate.h) carries: CrateId id,
QString name, bool locked, bool autoDjSource.
| DAO | Table(s) | Purpose |
|---|---|---|
DirectoryDAO |
directories |
Watched root directories; used by scanner |
AnalysisDao |
track_analysis |
Waveform summary data and BPM/key analysis blobs |
CueDAO |
cues |
Hot cues, loop cues, intro/outro markers per track |
LibraryHashDAO |
LibraryHashes |
Directory content hashes for fast change detection |
The GlobalTrackCache is a process-wide singleton that ensures at most
one Track object exists in memory for any given track at a time. All
code that needs a Track goes through the cache so that metadata edits
made in one place are immediately visible everywhere else.
Tracks are indexed by both TrackId and canonical file path. A lookup
that hits the cache increments the shared_ptr reference count. When
the count drops to zero (all callers have released their
TrackPointer), the cache calls GlobalTrackCacheSaver::saveEvictedTrack()
(implemented by TrackCollectionManager) to flush the Track to the
database.
The cache is protected by an internal mutex, making it safe to access from multiple threads — though the actual database write-back occurs on the main thread.
TrackRef (src/track/trackref.h) is a lightweight identifier that
can hold either a TrackId or a canonical file location (or both). It
is used to look up or create tracks without requiring a full Track
object to be loaded.
TrackPointer is simply QSharedPointer<Track>. Code that holds a
TrackPointer keeps the track alive in the cache. Releasing all
TrackPointers to a track triggers save-and-eviction.
LibraryScanner runs in a dedicated QThread and uses a QThreadPool
of ScannerTask workers to parallelise directory traversal.
The scan pipeline:
-
LibraryScanner::scan()signalsstartScan, launching the scan in the scanner thread's event loop. -
RecursiveScanDirectoryTaskwalks each watched root directory, hashing directory contents withLibraryHashDAOto skip unchanged directories on incremental scans. -
ImportFilesTaskprocesses changed directories: new files are passed toTrackDAOwithAddMissing|UnhideHiddenflags; modified files have their metadata refreshed. - After traversal,
TrackDAO::detectMovedTracks()cross-references newly-seen paths with missing paths to resolve file moves. -
scanFinished()andscanSummary()signals are emitted. The summary reports counts of added, modified, relocated, and removed tracks.
The scanner uses its own QSqlDatabase connection from the shared
DbConnectionPool, so it never blocks the main thread's database
operations.
The library uses the Qt model/view pattern. The abstract
TrackModel interface (src/library/trackmodel.h) extends
QAbstractTableModel with library-specific concepts:
-
Capabilityflags control what context-menu operations are available for a given model:Reorder,ReceiveDrops,AddToTrackSet,AddToAutoDJ,Locked,EditMetadata,LoadToDeck,LoadToSampler,Hide,Purge, etc. -
SortColumnIdis a stable enum (values must never change, as controller scripts reference them numerically) that identifies sortable columns independently of their display order. -
getTrack(QModelIndex)andgetTrackId(QModelIndex)map a view row back to aTrackPointerorTrackId.
BaseTrackCache is a shared, in-memory column cache that sits between
table models and the database. Because the base SQL view for the main
track list involves a join between library and track_locations, and
because multiple models would otherwise each maintain their own copy of
the same data, BaseTrackCache caches all column values keyed by
TrackId and exposes filterAndSort() for efficient search and
multi-column sort without touching the database.
A single BaseTrackCache instance is created by TrackCollection and
shared (as a QSharedPointer) among all table models that display the
main library.
| Class | What it shows |
|---|---|
LibraryTableModel |
All non-deleted tracks in the main library |
PlaylistTableModel |
Tracks in a specific playlist |
CrateTableModel |
Tracks in a specific crate |
BaseSqlTableModel |
Base class for SQL-backed models |
BaseExternalTrackModel |
Tracks from an external collection |
BaseExternalPlaylistModel |
Playlists from an external collection |
ExternalTrackCollection is a pure-virtual interface for synchronising
an external DJ application's library with Mixxx's internal one.
Implementations exist for Rekordbox (src/library/rekordbox/), Serato
(src/library/serato/), Traktor (src/library/traktor/), iTunes
(src/library/itunes/), Rhythmbox (src/library/rhythmbox/), and
Banshee (src/library/banshee/).
The contract for implementors:
class ExternalTrackCollection : public QObject {
public:
// Identifying information:
virtual QString name() const = 0;
virtual QString description() const = 0;
// Lifecycle:
virtual void establishConnection() = 0; // async
virtual void finishPendingTasksAndDisconnect() = 0; // sync/blocking
enum class ConnectionState {
Connecting, Connected, Disconnecting, Disconnected,
};
virtual ConnectionState connectionState() const = 0;
// Change notifications (called by TrackCollectionManager after
// the corresponding operation succeeds on the internal collection):
virtual void relocateDirectory(const QString& oldRoot,
const QString& newRoot) = 0;
virtual void updateTracks(const QList<TrackRef>& updatedTracks) = 0;
virtual void purgeTracks(const QList<TrackRef>& purgedTracks) = 0;
virtual void purgeAllTracks(const QDir& rootDir) = 0;
};All notification methods must be non-blocking — implementations should queue work to a background thread. Mixxx calls them after the internal database operation has already committed, so they are always best-effort notifications rather than part of the same transaction.
TrackCollectionManager holds a QList<ExternalTrackCollection*> and
iterates it inside every mutating method to keep all collections in
sync.
Several external libraries (Traktor, Rhythmbox, Banshee, iTunes) are
read-only: Mixxx can import and browse their tracks but does not
write back to their databases. These are implemented as
LibraryFeature subclasses that parse the external library's database
or XML export into a local view. They do not implement
ExternalTrackCollection and are not registered with
TrackCollectionManager.
The library backend communicates changes upward via Qt signals on
TrackCollection:
| Signal | When emitted |
|---|---|
tracksAdded(QSet<TrackId>) |
New tracks inserted into the database |
tracksChanged(QSet<TrackId>) |
Metadata updated for existing tracks |
tracksRemoved(QSet<TrackId>) |
Tracks hidden or purged |
crateInserted(CrateId) |
A new crate was created |
crateUpdated(CrateId) |
Crate name or properties changed |
crateDeleted(CrateId) |
A crate was deleted |
crateTracksChanged(CrateId, added, removed) |
Tracks added to or removed from a crate |
crateSummaryChanged(QSet<CrateId>) |
Track count / duration summary needs refresh |
Table models connect to these signals (via BaseTrackCache for the
track-level ones) to invalidate and refresh their views without
re-running a full SQL query.
Mixxx is a free and open-source DJ software.
Manual
Hardware Compatibility
Reporting Bugs
Getting Involved
Contribution Guidelines
Coding Guidelines
Using Git
Developer Guide
Creating Skins
Contributing Mappings
Mixxx Controls
MIDI Scripting
Components JS
HID Scripting