Skip to main content

rekordcrate/pdb/
mod.rs

1// Copyright (c) 2026 Jan Holthuis <jan.holthuis@rub.de>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy
4// of the MPL was not distributed with this file, You can obtain one at
5// http://mozilla.org/MPL/2.0/.
6//
7// SPDX-License-Identifier: MPL-2.0
8
9//! Parser for Pioneer DeviceSQL database exports (PDB).
10//!
11//! The Rekordbox DJ software uses writes PDB files to `/PIONEER/rekordbox/export.pdb`.
12//!
13//! Most of the file format has been reverse-engineered by Henry Betts, Fabian Lesniak and James
14//! Elliott.
15//!
16//! - <https://github.com/Deep-Symmetry/crate-digger/blob/master/doc/Analysis.pdf>
17//! - <https://djl-analysis.deepsymmetry.org/rekordbox-export-analysis/exports.html>
18//! - <https://github.com/henrybetts/Rekordbox-Decoding>
19//! - <https://github.com/flesniak/python-prodj-link/tree/master/prodj/pdblib>
20
21pub mod bitfields;
22pub mod ext;
23pub mod io;
24pub mod offset_array;
25pub mod string;
26
27use bitfields::{PackedRowCounts, PageFlags};
28use offset_array::{OffsetArrayContainer, OffsetArrayItems};
29
30#[cfg(test)]
31mod test_roundtrip;
32
33#[cfg(test)]
34mod test_modification;
35
36use std::collections::BTreeMap;
37use std::fmt;
38
39use crate::pdb::ext::{ExtPageType, ExtRow};
40use crate::pdb::offset_array::OffsetSize;
41use crate::pdb::string::DeviceSQLString;
42use crate::util::{parse_at_offsets, write_at_offsets, ColorIndex, FileType, TableIndex};
43use binrw::{binrw, BinRead, BinResult, BinWrite, Endian};
44use std::io::{Read, Seek, SeekFrom, Write};
45use thiserror::Error;
46
47/// An error that can occur when parsing a PDB file.
48#[derive(Debug, Error)]
49pub enum PdbError {
50    /// An invalid value was passed when creating a `PageIndex`.
51    #[error("Invalid page index value: {0:#X}")]
52    InvalidPageIndex(u32),
53    /// Invalid flags were passed when creating an `IndexEntry`.
54    #[error("Invalid index flags (expected max 3 bits): {0:#b}")]
55    InvalidIndexFlags(u8),
56}
57
58/// The type of the database were looking at.
59/// This influences the meaning of the the pagetypes found in tables.
60#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
61pub enum DatabaseType {
62    #[default] // use plain by default for use of migration
63    /// Standard export.pdb files.
64    Plain,
65    /// Extended exportExt.pdb files.
66    Ext,
67}
68
69/// The type of pages found inside a `Table`.
70#[binrw]
71#[derive(Debug, PartialEq, Eq, Clone, Copy)]
72#[brw(little)]
73#[br(import(db_type: DatabaseType))]
74pub enum PageType {
75    #[br(pre_assert(db_type == DatabaseType::Plain))]
76    /// Pagetypes present in `export.pdb` files.
77    Plain(PlainPageType),
78    #[br(pre_assert(db_type == DatabaseType::Ext))]
79    /// Pagetypes present in `exportExt.pdb` files.
80    Ext(ExtPageType),
81    /// Unknown page type.
82    Unknown(u32),
83}
84
85/// The type of pages found inside a `Table` of export.pdb files.
86#[binrw]
87#[derive(Debug, PartialEq, Eq, Clone, Copy)]
88#[brw(little)]
89pub enum PlainPageType {
90    /// Holds rows of track metadata, such as title, artist, genre, artwork ID, playing time, etc.
91    #[brw(magic = 0u32)]
92    Tracks,
93    /// Holds rows of musical genres, for reference by tracks and searching.
94    #[brw(magic = 1u32)]
95    Genres,
96    /// Holds rows of artists, for reference by tracks and searching.
97    #[brw(magic = 2u32)]
98    Artists,
99    /// Holds rows of albums, for reference by tracks and searching.
100    #[brw(magic = 3u32)]
101    Albums,
102    /// Holds rows of music labels, for reference by tracks and searching.
103    #[brw(magic = 4u32)]
104    Labels,
105    /// Holds rows of musical keys, for reference by tracks, searching, and key matching.
106    #[brw(magic = 5u32)]
107    Keys,
108    /// Holds rows of color labels, for reference  by tracks and searching.
109    #[brw(magic = 6u32)]
110    Colors,
111    /// Holds rows that describe the hierarchical tree structure of available playlists and folders
112    /// grouping them.
113    #[brw(magic = 7u32)]
114    PlaylistTree,
115    /// Holds rows that links tracks to playlists, in the right order.
116    #[brw(magic = 8u32)]
117    PlaylistEntries,
118    /// Holds rows of history playlists, i.e. playlists that are recorded every time the device is
119    /// mounted by a player.
120    #[brw(magic = 11u32)]
121    HistoryPlaylists,
122    /// Holds rows that links tracks to history playlists, in the right order.
123    #[brw(magic = 12u32)]
124    HistoryEntries,
125    /// Holds rows pointing to album artwork images.
126    #[brw(magic = 13u32)]
127    Artwork,
128    /// Contains the metadata categories by which Tracks can be browsed by.
129    #[brw(magic = 16u32)]
130    Columns,
131    /// Manages the active menus on the CDJ.
132    #[brw(magic = 17u32)]
133    Menu,
134    /// Holds information about synchronization of the USB with Rekordbox or a device.
135    #[brw(magic = 19u32)]
136    History,
137}
138
139/// A row variant that can be extracted from a generic `Row`.
140pub trait RowVariant {
141    /// The page type that contains rows of this variant.
142    const PAGE_TYPE: PageType;
143
144    /// Extracts a reference to this row variant from a generic `Row`.
145    fn from_row(row: &Row) -> Option<&Self>;
146    /// Extracts a mutable reference to this row variant from a generic `Row`.
147    fn from_row_mut(row: &mut Row) -> Option<&mut Self>;
148}
149
150/// Points to a table page and can be used to calculate the page's file offset by multiplying it
151/// with the page size (found in the file header).
152#[binrw]
153#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)]
154#[brw(little)]
155pub struct PageIndex(pub(crate) u32);
156
157impl TryFrom<u32> for PageIndex {
158    type Error = PdbError;
159
160    fn try_from(value: u32) -> Result<Self, Self::Error> {
161        if value < 0x03FF_FFFF {
162            Ok(Self(value))
163        } else {
164            Err(PdbError::InvalidPageIndex(value))
165        }
166    }
167}
168
169impl PageIndex {
170    /// Calculate the absolute file offset of the page in the PDB file for the given `page_size`.
171    #[must_use]
172    pub fn offset(&self, page_size: u32) -> u64 {
173        u64::from(self.0) * u64::from(page_size)
174    }
175}
176
177/// Tables are linked lists of pages containing rows of a single type, which are organized
178/// into groups.
179#[binrw]
180#[derive(Debug, PartialEq, Eq, Clone)]
181#[brw(little)]
182#[brw(import(db_type: DatabaseType))]
183pub struct Table {
184    /// Identifies the type of rows that this table contains.
185    #[br(args(db_type))]
186    pub page_type: PageType,
187    /// Unknown field, maybe links to a chain of empty pages if the database is ever garbage
188    /// collected (?).
189    #[allow(dead_code)]
190    empty_candidate: u32,
191    /// Index of the first page that belongs to this table.
192    ///
193    /// *Note:* The first page apparently does not contain any rows. If the table is non-empty, the
194    /// actual row data can be found in the pages after.
195    pub first_page: PageIndex,
196    /// Index of the last page that belongs to this table.
197    pub last_page: PageIndex,
198}
199
200/// The PDB header structure, including the list of tables.
201#[binrw]
202#[derive(Debug, PartialEq, Eq, Clone)]
203#[brw(little)]
204#[brw(import(db_type: DatabaseType))]
205pub struct Header {
206    // Unknown purpose, perhaps an unoriginal signature, seems to always have the value 0.
207    #[brw(magic = 0u32)]
208    /// Size of a single page in bytes.
209    ///
210    /// The byte offset of a page can be calculated by multiplying a page index with this value.
211    pub page_size: u32,
212    /// Number of tables.
213    pub num_tables: u32,
214    /// Unknown field, not used as any `empty_candidate`, points past end of file.
215    pub next_unused_page: PageIndex,
216    /// Unknown field.
217    pub unknown: u32,
218    /// Always incremented by at least one, sometimes by two or three.
219    pub sequence: u32,
220    // The gap seems to be always zero.
221    #[brw(magic = 0u32)]
222    /// Each table is a linked list of pages containing rows of a particular type.
223    #[br(count = num_tables, args {inner: (db_type,)})]
224    #[bw(args(db_type))]
225    pub tables: Vec<Table>,
226}
227
228impl Header {
229    /// Finds the table for a given page type.
230    #[must_use]
231    pub fn find_table(&self, page_type: PageType) -> Option<(TableIndex, &Table)> {
232        self.tables
233            .iter()
234            .enumerate()
235            .find(|(_, table)| table.page_type == page_type)
236            .map(|(i, table)| (TableIndex::from(i), table))
237    }
238
239    /// Finds the table for a given page type.
240    #[must_use]
241    pub fn find_table_mut(&mut self, page_type: PageType) -> Option<(TableIndex, &mut Table)> {
242        self.tables
243            .iter_mut()
244            .enumerate()
245            .find(|(_, table)| table.page_type == page_type)
246            .map(|(i, table)| (TableIndex::from(i), table))
247    }
248}
249
250/// An entry in an index page.
251#[binrw]
252#[derive(PartialEq, Eq, Clone, Copy)]
253#[brw(little)]
254pub struct IndexEntry(u32);
255
256impl IndexEntry {
257    /// Size of the index entry in bytes.
258    pub const BINARY_SIZE: u32 = 4;
259}
260
261impl TryFrom<(PageIndex, u8)> for IndexEntry {
262    type Error = PdbError;
263
264    fn try_from(value: (PageIndex, u8)) -> Result<Self, Self::Error> {
265        let (page_index, index_flags) = value;
266        if index_flags & 0b111 != index_flags {
267            return Err(PdbError::InvalidIndexFlags(index_flags));
268        }
269        Ok(Self((page_index.0 << 3) | (index_flags & 0b111) as u32))
270    }
271}
272
273impl IndexEntry {
274    /// Returns bits 31-3 as a `PageIndex` which points to a page containing
275    /// data rows, with `page_flags=0x34` and same `page_type` as this page.
276    pub fn page_index(&self) -> Result<PageIndex, PdbError> {
277        PageIndex::try_from(self.0 >> 3)
278    }
279
280    /// Returns the index flags from bits 2-0. Their meaning is currently
281    /// unknown.
282    #[must_use]
283    pub fn index_flags(&self) -> u8 {
284        (self.0 & 0b111) as u8
285    }
286
287    /// Returns `true` if the entry is an empty slot.
288    #[must_use]
289    pub fn is_empty(&self) -> bool {
290        self.0 == 0x1FFF_FFF8
291    }
292
293    /// Creates a new empty `IndexEntry`.
294    #[must_use]
295    pub const fn empty() -> Self {
296        Self(0x1FFF_FFF8)
297    }
298}
299
300impl fmt::Debug for IndexEntry {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        if self.is_empty() {
303            f.debug_struct("IndexEntry")
304                .field("is_empty", &self.is_empty())
305                .finish()
306        } else {
307            f.debug_struct("IndexEntry")
308                .field("is_empty", &self.is_empty())
309                .field("page_index", &self.page_index().unwrap())
310                .field("index_flags", &self.index_flags())
311                .finish()
312        }
313    }
314}
315
316/// The header of the index-containing part of a page.
317#[binrw]
318#[derive(Debug, PartialEq, Eq, Clone)]
319pub struct IndexPageHeader {
320    /// Unknown field, usually `0x1fff` or `0x0001`.
321    pub unknown_a: u16,
322    /// Unknown field, usually `0x1fff` or `0x0000`.
323    pub unknown_b: u16,
324    // Magic value `0x03ec`.
325    #[brw(magic = 0x03ecu16)]
326    /// Offset where the next index entry will be written from the beginning
327    /// of the entries array, i.e. if this is 4 it means the next entry should
328    /// be written at byte `entries+4*4`. We still do not know why this value
329    /// is sometimes different than num_entries.
330    pub next_offset: u16,
331    /// Redundant page index.
332    pub page_index: PageIndex,
333    /// Redundant next page index.
334    pub next_page: PageIndex,
335    // Magic value `0x0000000003ffffff`.
336    #[brw(magic = 0x0000_0000_03ff_ffffu64)]
337    /// Number of index entries in this page.
338    pub num_entries: u16,
339    /// Points to the first empty index entry, or `0x1fff` if none.
340    ///
341    /// In real databases, this has been found to be one of three things:
342    /// 1. The same value as `num_entries`.
343    /// 2. `0x1fff`. We assume this has the same meaning as **1.**
344    /// 3. A number smaller than `num_entries`, indicating the first empty
345    /// slot.
346    pub first_empty: u16,
347}
348
349impl IndexPageHeader {
350    /// Size of the index page header in bytes.
351    pub const BINARY_SIZE: u32 = 28;
352}
353
354/// The content of an index page.
355#[binrw]
356#[derive(Debug, PartialEq, Eq, Clone)]
357#[br(little)]
358#[bw(little, import { page_size: u32 })]
359pub struct IndexPageContent {
360    /// The header of the index page.
361    pub header: IndexPageHeader,
362
363    /// The index entries.
364    #[br(count = header.num_entries)]
365    pub entries: Vec<IndexEntry>,
366
367    // Write empty entries to pad out the rest of the page, except the last
368    // 20 bytes which are zeros instead.
369    #[br(temp)]
370    #[bw(calc = EmptyIndexEntries(
371        Self::total_entries(page_size) - usize::from(header.num_entries)
372    ))]
373    #[bw(pad_after = 20)]
374    _empty_entries: EmptyIndexEntries,
375}
376
377impl IndexPageContent {
378    fn total_entries(page_size: u32) -> usize {
379        // The last 20 bytes in an index page are zeros.
380        let entries_space = page_size - PageHeader::BINARY_SIZE - IndexPageHeader::BINARY_SIZE - 20;
381        (entries_space / IndexEntry::BINARY_SIZE)
382            .try_into()
383            .unwrap()
384    }
385}
386
387/// Helper struct to write empty index entries while reading nothing.
388struct EmptyIndexEntries(usize);
389
390impl BinRead for EmptyIndexEntries {
391    type Args<'a> = ();
392
393    fn read_options<Reader>(_: &mut Reader, _: Endian, (): Self::Args<'_>) -> BinResult<Self>
394    where
395        Reader: Read + Seek,
396    {
397        Ok(Self(0))
398    }
399}
400
401impl BinWrite for EmptyIndexEntries {
402    type Args<'a> = ();
403
404    fn write_options<Writer>(
405        &self,
406        writer: &mut Writer,
407        endian: Endian,
408        (): Self::Args<'_>,
409    ) -> BinResult<()>
410    where
411        Writer: Write + Seek,
412    {
413        const EMPTY: IndexEntry = IndexEntry::empty();
414        for _ in 0..self.0 {
415            EMPTY.write_options(writer, endian, ())?;
416        }
417        Ok(())
418    }
419}
420
421/// The content of a page, which can be of different types.
422///
423/// Does not implement `Eq` due to the `Unknown` variant.
424#[binrw]
425#[derive(Debug, PartialEq, Clone)]
426#[br(little, import { page_size: u32, header: &PageHeader })]
427#[bw(little, import { page_size: u32 })]
428pub enum PageContent {
429    /// The page contains data rows.
430    #[br(pre_assert(!header.page_flags.is_index_page()))]
431    Data(
432        #[br(args { page_size, page_header: header })]
433        #[bw(args { page_size })]
434        DataPageContent,
435    ),
436    /// The page is an index page.
437    #[br(pre_assert(header.page_flags.is_index_page()))]
438    Index(#[bw(args { page_size })] IndexPageContent),
439}
440
441impl PageContent {
442    /// Returns the data content of the page if it is a data page.
443    #[must_use]
444    pub fn into_data(self) -> Option<DataPageContent> {
445        match self {
446            PageContent::Data(data) => Some(data),
447            _ => None,
448        }
449    }
450
451    /// Returns a reference to the data content of the page if it is a data page.
452    #[must_use]
453    pub fn as_data(&self) -> Option<&DataPageContent> {
454        match self {
455            PageContent::Data(data) => Some(data),
456            _ => None,
457        }
458    }
459
460    /// Returns a mutable reference to the data content of the page if it is a data page.
461    #[must_use]
462    pub fn as_data_mut(&mut self) -> Option<&mut DataPageContent> {
463        match self {
464            PageContent::Data(data) => Some(data),
465            _ => None,
466        }
467    }
468
469    /// Returns the index content of the page if it is an index page.
470    #[must_use]
471    pub fn into_index(self) -> Option<IndexPageContent> {
472        match self {
473            PageContent::Index(index) => Some(index),
474            _ => None,
475        }
476    }
477
478    /// Returns a reference to the index content of the page if it is an index page.
479    #[must_use]
480    pub fn as_index(&self) -> Option<&IndexPageContent> {
481        match self {
482            PageContent::Index(index) => Some(index),
483            _ => None,
484        }
485    }
486
487    /// Returns a mutable reference to the index content of the page if it is an index page.
488    #[must_use]
489    pub fn as_index_mut(&mut self) -> Option<&mut IndexPageContent> {
490        match self {
491            PageContent::Index(index) => Some(index),
492            _ => None,
493        }
494    }
495}
496
497/// The header of a page.
498#[binrw]
499#[derive(Debug, PartialEq, Eq, Clone)]
500#[brw(little)]
501#[br(import(db_type: DatabaseType))]
502pub struct PageHeader {
503    // Magic signature for pages (must be 0).
504    #[brw(magic = 0u32)]
505    /// Index of the page.
506    ///
507    /// Should match the index used for lookup and can be used to verify that the correct page was loaded.
508    pub page_index: PageIndex,
509    /// Type of information that the rows of this page contain.
510    ///
511    /// Should match the page type of the table that this page belongs to.
512    #[br(args(db_type))]
513    pub page_type: PageType,
514    /// Index of the next page with the same page type.
515    ///
516    /// If this page is the last one of that type, the page index stored in the field will point
517    /// past the end of the file.
518    pub next_page: PageIndex,
519    /// Unknown field.
520    /// Appears to be a number between 1 and ~2500.
521    pub unknown1: u32,
522    /// Unknown field.
523    /// Appears to always be zero.
524    pub unknown2: u32,
525    /// Packed field containing:
526    /// - number of used row offsets in the page (13 bits).
527    /// - number of valid rows in the page (11 bits).
528    pub packed_row_counts: PackedRowCounts,
529    /// Page flags.
530    ///
531    /// According to [@flesniak](https://github.com/flesniak):
532    /// > strange pages: 0x44, 0x64; otherwise seen: 0x24, 0x34
533    pub page_flags: PageFlags,
534    /// Free space in bytes in the data section of the page (excluding the row offsets in the page footer).
535    pub free_size: u16,
536    /// Used space in bytes in the data section of the page.
537    pub used_size: u16,
538}
539
540impl PageHeader {
541    /// Size of the page header in bytes.
542    pub const BINARY_SIZE: u32 = 0x20;
543}
544
545/// A table page.
546///
547/// Each page consists of a header that contains information about the type, number of rows, etc.,
548/// followed by the data section that holds the row data. Each row needs to be located using an
549/// offset found in the page footer at the end of the page.
550#[binrw]
551#[derive(Debug, PartialEq, Clone)]
552#[brw(little)]
553#[br(import(page_size: u32, db_type: DatabaseType))]
554#[bw(import(page_size: u32))]
555pub struct Page {
556    /// The page header.
557    #[br(args(db_type))]
558    pub header: PageHeader,
559
560    /// The content of the page.
561    #[br(args { page_size, header: &header })]
562    #[bw(args { page_size })]
563    pub content: PageContent,
564}
565
566impl Page {
567    /// Allocate space for a new row in the page heap and return a function to
568    /// insert the row at the allocated offset. Returns `None` if there is
569    /// insufficient free space in the page.
570    ///
571    /// We do this allocate-then-insert dance so that we only take ownership of
572    /// the Row once we know we can insert it, avoiding unnecessary copies.
573    pub fn allocate_row<'a>(&'a mut self, bytes: u16) -> Option<impl FnOnce(Row) + 'a> {
574        match self.content {
575            PageContent::Index(_) => None,
576            PageContent::Data(ref mut dpc) => {
577                // Always align rows to 4 bytes.
578                let bytes = bytes.next_multiple_of(4);
579
580                // Assume the upper bound of required space.
581                let required_bytes = bytes + RowGroup::HEADER_SIZE + RowGroup::OFFSET_SIZE;
582                if self.header.free_size < required_bytes {
583                    return None;
584                }
585
586                let offset = self.header.used_size;
587                self.header.used_size += bytes;
588                self.header.free_size -= bytes;
589                let row_counts = &mut self.header.packed_row_counts;
590                row_counts.increment_num_rows();
591                let (row_group_index, row_subindex) = row_counts.last_row_index().unwrap();
592
593                if dpc.row_groups.get(row_group_index as usize).is_none() {
594                    dpc.row_groups.push(RowGroup::empty());
595                    self.header.free_size -= RowGroup::HEADER_SIZE;
596                }
597
598                let row_group = dpc.row_groups.get_mut(row_group_index as usize).unwrap();
599                row_group.insert_offset(row_subindex, offset);
600                self.header.free_size -= RowGroup::OFFSET_SIZE;
601
602                let rows = &mut dpc.rows;
603
604                Some(move |row: Row| {
605                    let prev_entry = rows.insert(offset, row);
606                    if let Some(prev_entry) = prev_entry {
607                        panic!(
608                            "Offset {} was already occupied by row {:?}",
609                            offset, prev_entry
610                        );
611                    }
612                    row_group.mark_offset_present(row_subindex);
613                    row_counts.increment_num_rows_valid();
614                })
615            }
616        }
617    }
618}
619
620/// The header of the data-containing part of a page.
621#[binrw]
622#[derive(Debug, PartialEq, Eq, Clone)]
623pub struct DataPageHeader {
624    /// Unknown field.
625    /// Often 1 or 0x1fff; also observed: 8, 27, 22, 17, 2.
626    ///
627    /// According to [@flesniak](https://github.com/flesniak):
628    /// > (0->1: 2)
629    pub unknown5: u16,
630    /// Unknown field related to the number of rows in the table,
631    /// but not equal to it.
632    pub unknown_not_num_rows_large: u16,
633    /// Unknown field (usually zero).
634    pub unknown6: u16,
635    /// Unknown field (usually zero).
636    ///
637    /// According to [@flesniak](https://github.com/flesniak):
638    /// > always 0, except 1 for history pages, num entries for strange pages?"
639    /// @RobinMcCorkell: I don't think this is correct, my DB only has zeros for all pages.
640    pub unknown7: u16,
641}
642
643impl DataPageHeader {
644    /// Size of the page header in bytes.
645    pub const BINARY_SIZE: u32 = 0x8;
646}
647
648/// The data-containing part of a page.
649#[binrw]
650#[derive(Debug, PartialEq, Eq, Clone)]
651#[br(little, import { page_size: u32, page_header: &PageHeader })]
652#[bw(little, import { page_size: u32 })]
653pub struct DataPageContent {
654    /// The header of the data page.
655    pub header: DataPageHeader,
656
657    /// Row groups at the end of the page.
658    // Seek to the end of the page as we read/write row groups backwards,
659    // but restore the position after to read/write the actual rows.
660    #[brw(seek_before(SeekFrom::Current(Self::page_heap_size(page_size) as i64)), restore_position)]
661    #[br(args {count: page_header.packed_row_counts.num_row_groups().into()})]
662    pub row_groups: Vec<RowGroup>,
663
664    /// Rows belonging to this page by the heap offset at which each is stored.
665    ///
666    /// The offsets here should match those in `row_groups`.
667    #[br(args(page_header.page_type))]
668    #[br(parse_with = parse_at_offsets(row_groups.iter().flat_map(RowGroup::present_rows_offsets)))]
669    // `write_at_offsets` restores the writer position after writing.
670    #[bw(write_with = write_at_offsets)]
671    #[br(assert(rows.len() == page_header.packed_row_counts.num_rows_valid().into(), "parsing page {:?}: num_rows_valid {} does not match parsed row count {}", page_header.page_index, page_header.packed_row_counts.num_rows_valid(), rows.len()))]
672    pub rows: BTreeMap<u16, Row>,
673
674    // Seek to the end of the data content area.
675    //
676    // It's tempting to use `bw(align_after = ...)` or `bw(pad_size_to = ...)`
677    // instead of this manual seek, but those cause binrw to write zeros over the top
678    // of the row groups and rows above!
679    #[br(temp)]
680    #[bw(calc = ())]
681    #[brw(seek_before = SeekFrom::Current(Self::page_heap_size(page_size) as i64))]
682    _dummy: (),
683}
684
685impl DataPageContent {
686    fn page_heap_size(page_size: u32) -> u32 {
687        page_size - PageHeader::BINARY_SIZE - DataPageHeader::BINARY_SIZE
688    }
689}
690
691// Usage of PageHeapObject is coming in a future PR.
692#[allow(dead_code)]
693trait PageHeapObject {
694    type Args<'a>;
695
696    /// Required page heap space in bytes to store the object.
697    fn heap_bytes_required(&self, args: Self::Args<'_>) -> u16;
698}
699
700impl PageHeapObject for u8 {
701    type Args<'a> = ();
702    fn heap_bytes_required(&self, _: ()) -> u16 {
703        std::mem::size_of::<u8>() as u16
704    }
705}
706
707impl PageHeapObject for u16 {
708    type Args<'a> = ();
709    fn heap_bytes_required(&self, _: ()) -> u16 {
710        std::mem::size_of::<u16>() as u16
711    }
712}
713
714impl PageHeapObject for u32 {
715    type Args<'a> = ();
716    fn heap_bytes_required(&self, _: ()) -> u16 {
717        std::mem::size_of::<u32>() as u16
718    }
719}
720
721impl PageHeapObject for ColorIndex {
722    type Args<'a> = ();
723    fn heap_bytes_required(&self, _: ()) -> u16 {
724        (0u8).heap_bytes_required(())
725    }
726}
727
728impl PageHeapObject for FileType {
729    type Args<'a> = ();
730    fn heap_bytes_required(&self, _: ()) -> u16 {
731        (0u16).heap_bytes_required(())
732    }
733}
734
735/// A group of row indices, which are built backwards from the end of the page. Holds up to sixteen
736/// row offsets, along with a bit mask that indicates whether each row is actually present in the
737/// table.
738#[binrw]
739#[derive(Debug, Clone, Eq)]
740pub struct RowGroup {
741    /// An offset which points to a row in the table, whose actual presence is controlled by one of the
742    /// bits in `row_present_flags`. This instance allows the row itself to be lazily loaded, unless it
743    /// is not present, in which case there is no content to be loaded.
744    ///
745    /// Row groups are read backwards so first seek backwards.
746    ///
747    /// **Note:** Offsets are filled from the end and may only be partially present, i.e. earlier offsets
748    /// may be "uninitialized" and used as part of the page heap instead. We only start writing offsets
749    /// from the first present row to avoid clobbering page heap data.
750    #[brw(seek_before = SeekFrom::Current(-i64::from(Self::BINARY_SIZE)))]
751    #[bw(write_with = Self::write_row_offsets, args(*row_presence_flags))]
752    pub row_offsets: [u16; Self::MAX_ROW_COUNT],
753    /// A bit mask that indicates which rows in this group are actually present.
754    pub row_presence_flags: u16,
755    /// Unknown field.
756    /// Often zero, sometimes a multiple of 2, rarely something else.
757    /// When a multiple of 2, the set bit often aligns with the last present row
758    /// in the group, so maybe this is a bitset like the flags.
759    ///
760    /// E.g. for a full Artist rowgroup, this is usually zero.
761    /// For the last Artist rowgroup in the page with flags 0x003f, this is often 0x0020.
762    pub unknown: u16,
763
764    // Seek to the start of the row group to prepare for reading the next one.
765    #[br(temp)]
766    #[bw(calc = ())]
767    #[brw(seek_before = SeekFrom::Current(-i64::from(Self::BINARY_SIZE)))]
768    _dummy: (),
769}
770
771impl RowGroup {
772    /// Maximum number of rows in a row group.
773    pub const MAX_ROW_COUNT: usize = 16;
774    const HEADER_SIZE: u16 = 4; // row_presence_flags and unknown fields.
775    const OFFSET_SIZE: u16 = 2;
776    const BINARY_SIZE: u16 = (Self::MAX_ROW_COUNT as u16) * Self::OFFSET_SIZE + Self::HEADER_SIZE;
777
778    fn empty() -> Self {
779        Self {
780            row_offsets: [0; Self::MAX_ROW_COUNT],
781            row_presence_flags: 0,
782            unknown: 0,
783        }
784    }
785
786    fn present_rows_offsets(&self) -> impl Iterator<Item = u16> + '_ {
787        self.row_offsets
788            .iter()
789            .rev()
790            .enumerate()
791            .filter_map(move |(i, row_offset)| {
792                (self.row_presence_flags & (1 << i) != 0).then_some(*row_offset)
793            })
794    }
795
796    fn write_row_offsets<Writer>(
797        row_offsets: &[u16; 16],
798        writer: &mut Writer,
799        endian: Endian,
800        (row_presence_flags,): (u16,),
801    ) -> BinResult<()>
802    where
803        Writer: Write + Seek,
804    {
805        const U16_SIZE: u32 = std::mem::size_of::<u16>() as u32;
806        let skip = row_presence_flags.leading_zeros();
807        writer.seek(SeekFrom::Current((skip * U16_SIZE).into()))?;
808        for offset in row_offsets.iter().skip(skip.try_into().unwrap()) {
809            offset.write_options(writer, endian, ())?;
810        }
811        Ok(())
812    }
813
814    /// Insert a row offset into the group at the given subindex
815    /// but do not mark it present yet (see `mark_offset_present`).
816    fn insert_offset(&mut self, subindex: u16, offset: u16) {
817        self.row_offsets[(Self::MAX_ROW_COUNT as u16 - 1 - subindex) as usize] = offset;
818    }
819
820    /// Mark an inserted row offset as present.
821    fn mark_offset_present(&mut self, subindex: u16) {
822        self.row_presence_flags |= 1 << subindex;
823    }
824}
825
826impl Default for RowGroup {
827    fn default() -> Self {
828        Self::empty()
829    }
830}
831
832impl PartialEq for RowGroup {
833    fn eq(&self, other: &Self) -> bool {
834        self.unknown == other.unknown
835            && self.present_rows_offsets().eq(other.present_rows_offsets())
836    }
837}
838
839/// Carries additional information about a row (if present, always as the first field of a row)
840#[binrw]
841#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
842#[brw(little)]
843pub struct Subtype(pub u16);
844
845impl PageHeapObject for Subtype {
846    type Args<'a> = ();
847    fn heap_bytes_required(&self, _: ()) -> u16 {
848        self.0.heap_bytes_required(())
849    }
850}
851
852impl Subtype {
853    /// Returns the offset size (`OffsetSize`) used for this subtype.
854    ///
855    /// If the 0x04 bit is not set in the subtype, returns `OffsetSize::U8`,
856    /// otherwise returns `OffsetSize::U16`.
857    #[must_use]
858    pub fn get_offset_size(&self) -> OffsetSize {
859        if self.0 & 0x04 == 0 {
860            OffsetSize::U8
861        } else {
862            OffsetSize::U16
863        }
864    }
865}
866
867/// Identifies a track.
868#[binrw]
869#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
870#[brw(little)]
871pub struct TrackId(pub u32);
872
873impl PageHeapObject for TrackId {
874    type Args<'a> = ();
875    fn heap_bytes_required(&self, _: ()) -> u16 {
876        self.0.heap_bytes_required(())
877    }
878}
879
880/// Identifies an artwork item.
881#[binrw]
882#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
883#[brw(little)]
884pub struct ArtworkId(pub u32);
885
886impl PageHeapObject for ArtworkId {
887    type Args<'a> = ();
888    fn heap_bytes_required(&self, _: ()) -> u16 {
889        self.0.heap_bytes_required(())
890    }
891}
892
893/// Identifies an album.
894#[binrw]
895#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
896#[brw(little)]
897pub struct AlbumId(pub u32);
898
899impl PageHeapObject for AlbumId {
900    type Args<'a> = ();
901    fn heap_bytes_required(&self, _: ()) -> u16 {
902        self.0.heap_bytes_required(())
903    }
904}
905
906/// Identifies an artist.
907#[binrw]
908#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
909#[brw(little)]
910pub struct ArtistId(pub u32);
911
912impl PageHeapObject for ArtistId {
913    type Args<'a> = ();
914    fn heap_bytes_required(&self, _: ()) -> u16 {
915        self.0.heap_bytes_required(())
916    }
917}
918
919/// Identifies a genre.
920#[binrw]
921#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
922#[brw(little)]
923pub struct GenreId(pub u32);
924
925impl PageHeapObject for GenreId {
926    type Args<'a> = ();
927    fn heap_bytes_required(&self, _: ()) -> u16 {
928        self.0.heap_bytes_required(())
929    }
930}
931
932/// Identifies a key.
933#[binrw]
934#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
935#[brw(little)]
936pub struct KeyId(pub u32);
937
938impl PageHeapObject for KeyId {
939    type Args<'a> = ();
940    fn heap_bytes_required(&self, _: ()) -> u16 {
941        self.0.heap_bytes_required(())
942    }
943}
944
945/// Identifies a label.
946#[binrw]
947#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
948#[brw(little)]
949pub struct LabelId(pub u32);
950
951impl PageHeapObject for LabelId {
952    type Args<'a> = ();
953    fn heap_bytes_required(&self, _: ()) -> u16 {
954        self.0.heap_bytes_required(())
955    }
956}
957
958/// Identifies a playlist tree node.
959#[binrw]
960#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
961#[brw(little)]
962pub struct PlaylistTreeNodeId(pub u32);
963
964impl PageHeapObject for PlaylistTreeNodeId {
965    type Args<'a> = ();
966    fn heap_bytes_required(&self, _: ()) -> u16 {
967        self.0.heap_bytes_required(())
968    }
969}
970
971/// Identifies a history playlist.
972#[binrw]
973#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
974#[brw(little)]
975pub struct HistoryPlaylistId(pub u32);
976
977impl PageHeapObject for HistoryPlaylistId {
978    type Args<'a> = ();
979    fn heap_bytes_required(&self, _: ()) -> u16 {
980        self.0.heap_bytes_required(())
981    }
982}
983
984#[derive(Debug, PartialEq, Clone, Eq)]
985/// Represents a trailing name field at the end of a row, used for album and artist names.
986pub struct TrailingName {
987    /// The name a the end of the row this is used in
988    pub name: DeviceSQLString,
989}
990
991impl OffsetArrayItems<1> for TrailingName {
992    type Item = DeviceSQLString;
993
994    fn as_items(&self) -> [&Self::Item; 1] {
995        [&self.name]
996    }
997
998    fn from_items(items: [Self::Item; 1]) -> Self {
999        let [name] = items;
1000        Self { name }
1001    }
1002}
1003
1004/// Contains the album name, along with an ID of the corresponding artist.
1005#[binrw]
1006#[derive(Debug, PartialEq, Eq, Clone)]
1007#[brw(little)]
1008pub struct Album {
1009    /// Unknown field, usually `80 00`.
1010    subtype: Subtype,
1011    /// Unknown field, called `index_shift` by [@flesniak](https://github.com/flesniak).
1012    /// Appears to always be 0x20 * row index.
1013    index_shift: u16,
1014    /// Unknown field.
1015    unknown2: u32,
1016    /// ID of the artist row associated with this row.
1017    artist_id: ArtistId,
1018    /// ID of this row.
1019    id: AlbumId,
1020    /// Unknown field.
1021    unknown3: u32,
1022    /// The offsets and its data and the end of this row
1023    #[brw(args(20, subtype.get_offset_size(), ()))]
1024    offsets: OffsetArrayContainer<TrailingName, 1>,
1025}
1026
1027impl RowVariant for Album {
1028    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Albums);
1029
1030    fn from_row(row: &Row) -> Option<&Self> {
1031        match row {
1032            Row::Plain(PlainRow::Album(row)) => Some(row),
1033            _ => None,
1034        }
1035    }
1036    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1037        match row {
1038            Row::Plain(PlainRow::Album(row)) => Some(row),
1039            _ => None,
1040        }
1041    }
1042}
1043
1044impl PageHeapObject for Album {
1045    type Args<'a> = ();
1046    fn heap_bytes_required(&self, _: ()) -> u16 {
1047        [
1048            self.subtype.heap_bytes_required(()),
1049            self.index_shift.heap_bytes_required(()),
1050            self.unknown2.heap_bytes_required(()),
1051            self.artist_id.heap_bytes_required(()),
1052            self.id.heap_bytes_required(()),
1053            self.unknown3.heap_bytes_required(()),
1054            self.offsets
1055                .heap_bytes_required(self.subtype.get_offset_size()),
1056        ]
1057        .iter()
1058        .sum()
1059    }
1060}
1061
1062/// Contains the artist name and ID.
1063#[binrw]
1064#[derive(Debug, PartialEq, Eq, Clone)]
1065#[brw(little)]
1066pub struct Artist {
1067    /// Determines if the `name` string is located at the 8-bit offset (0x60) or the 16-bit offset (0x64).
1068    subtype: Subtype,
1069    /// Unknown field, called `index_shift` by [@flesniak](https://github.com/flesniak).
1070    /// Appears to always be 0x20 * row index.
1071    index_shift: u16,
1072    /// ID of this row.
1073    pub id: ArtistId,
1074    /// offsets at the row end
1075    #[brw(args(8, subtype.get_offset_size(), ()))]
1076    pub offsets: OffsetArrayContainer<TrailingName, 1>,
1077}
1078
1079impl RowVariant for Artist {
1080    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Artists);
1081
1082    fn from_row(row: &Row) -> Option<&Self> {
1083        match row {
1084            Row::Plain(PlainRow::Artist(row)) => Some(row),
1085            _ => None,
1086        }
1087    }
1088    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1089        match row {
1090            Row::Plain(PlainRow::Artist(row)) => Some(row),
1091            _ => None,
1092        }
1093    }
1094}
1095
1096impl PageHeapObject for Artist {
1097    type Args<'a> = ();
1098    fn heap_bytes_required(&self, _: ()) -> u16 {
1099        [
1100            self.subtype.heap_bytes_required(()),
1101            self.index_shift.heap_bytes_required(()),
1102            self.id.heap_bytes_required(()),
1103            self.offsets
1104                .heap_bytes_required(self.subtype.get_offset_size()),
1105        ]
1106        .iter()
1107        .sum()
1108    }
1109}
1110
1111/// Contains the artwork path and ID.
1112#[binrw]
1113#[derive(Debug, PartialEq, Eq, Clone)]
1114#[brw(little)]
1115pub struct Artwork {
1116    /// ID of this row.
1117    id: ArtworkId,
1118    /// Path to the album art file.
1119    path: DeviceSQLString,
1120}
1121
1122impl RowVariant for Artwork {
1123    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Artwork);
1124
1125    fn from_row(row: &Row) -> Option<&Self> {
1126        match row {
1127            Row::Plain(PlainRow::Artwork(row)) => Some(row),
1128            _ => None,
1129        }
1130    }
1131    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1132        match row {
1133            Row::Plain(PlainRow::Artwork(row)) => Some(row),
1134            _ => None,
1135        }
1136    }
1137}
1138
1139impl PageHeapObject for Artwork {
1140    type Args<'a> = ();
1141    fn heap_bytes_required(&self, _: ()) -> u16 {
1142        [
1143            self.id.heap_bytes_required(()),
1144            self.path.heap_bytes_required(()),
1145        ]
1146        .iter()
1147        .sum()
1148    }
1149}
1150
1151/// Contains numeric color ID
1152#[binrw]
1153#[derive(Debug, PartialEq, Eq, Clone)]
1154#[brw(little)]
1155pub struct Color {
1156    /// Unknown field.
1157    unknown1: u32,
1158    /// Unknown field.
1159    unknown2: u8,
1160    /// Numeric color ID
1161    color: ColorIndex,
1162    /// Unknown field.
1163    unknown3: u16,
1164    /// User-defined name of the color.
1165    name: DeviceSQLString,
1166}
1167
1168impl RowVariant for Color {
1169    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Colors);
1170
1171    fn from_row(row: &Row) -> Option<&Self> {
1172        match row {
1173            Row::Plain(PlainRow::Color(row)) => Some(row),
1174            _ => None,
1175        }
1176    }
1177    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1178        match row {
1179            Row::Plain(PlainRow::Color(row)) => Some(row),
1180            _ => None,
1181        }
1182    }
1183}
1184
1185impl PageHeapObject for Color {
1186    type Args<'a> = ();
1187    fn heap_bytes_required(&self, _: ()) -> u16 {
1188        [
1189            self.unknown1.heap_bytes_required(()),
1190            self.unknown2.heap_bytes_required(()),
1191            self.color.heap_bytes_required(()),
1192            self.unknown3.heap_bytes_required(()),
1193            self.name.heap_bytes_required(()),
1194        ]
1195        .iter()
1196        .sum()
1197    }
1198}
1199
1200/// Represents a musical genre.
1201#[binrw]
1202#[derive(Debug, PartialEq, Eq, Clone)]
1203#[brw(little)]
1204pub struct Genre {
1205    /// ID of this row.
1206    id: GenreId,
1207    /// Name of the genre.
1208    name: DeviceSQLString,
1209}
1210
1211impl RowVariant for Genre {
1212    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Genres);
1213
1214    fn from_row(row: &Row) -> Option<&Self> {
1215        match row {
1216            Row::Plain(PlainRow::Genre(row)) => Some(row),
1217            _ => None,
1218        }
1219    }
1220    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1221        match row {
1222            Row::Plain(PlainRow::Genre(row)) => Some(row),
1223            _ => None,
1224        }
1225    }
1226}
1227
1228impl PageHeapObject for Genre {
1229    type Args<'a> = ();
1230    fn heap_bytes_required(&self, _: ()) -> u16 {
1231        [
1232            self.id.heap_bytes_required(()),
1233            self.name.heap_bytes_required(()),
1234        ]
1235        .iter()
1236        .sum()
1237    }
1238}
1239
1240/// Represents a history playlist.
1241#[binrw]
1242#[derive(Debug, PartialEq, Eq, Clone)]
1243#[brw(little)]
1244pub struct HistoryPlaylist {
1245    /// ID of this row.
1246    id: HistoryPlaylistId,
1247    /// Name of the playlist.
1248    name: DeviceSQLString,
1249}
1250
1251impl RowVariant for HistoryPlaylist {
1252    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::HistoryPlaylists);
1253
1254    fn from_row(row: &Row) -> Option<&Self> {
1255        match row {
1256            Row::Plain(PlainRow::HistoryPlaylist(row)) => Some(row),
1257            _ => None,
1258        }
1259    }
1260    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1261        match row {
1262            Row::Plain(PlainRow::HistoryPlaylist(row)) => Some(row),
1263            _ => None,
1264        }
1265    }
1266}
1267
1268impl PageHeapObject for HistoryPlaylist {
1269    type Args<'a> = ();
1270    fn heap_bytes_required(&self, _: ()) -> u16 {
1271        [
1272            self.id.heap_bytes_required(()),
1273            self.name.heap_bytes_required(()),
1274        ]
1275        .iter()
1276        .sum()
1277    }
1278}
1279
1280/// Represents a history playlist.
1281#[binrw]
1282#[derive(Debug, PartialEq, Eq, Clone)]
1283#[brw(little)]
1284pub struct HistoryEntry {
1285    /// ID of the track played at this position in the playlist.
1286    track_id: TrackId,
1287    /// ID of the history playlist.
1288    playlist_id: HistoryPlaylistId,
1289    /// Position within the playlist.
1290    entry_index: u32,
1291}
1292
1293impl RowVariant for HistoryEntry {
1294    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::HistoryEntries);
1295
1296    fn from_row(row: &Row) -> Option<&Self> {
1297        match row {
1298            Row::Plain(PlainRow::HistoryEntry(row)) => Some(row),
1299            _ => None,
1300        }
1301    }
1302    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1303        match row {
1304            Row::Plain(PlainRow::HistoryEntry(row)) => Some(row),
1305            _ => None,
1306        }
1307    }
1308}
1309
1310impl PageHeapObject for HistoryEntry {
1311    type Args<'a> = ();
1312    fn heap_bytes_required(&self, _: ()) -> u16 {
1313        [
1314            self.track_id.heap_bytes_required(()),
1315            self.playlist_id.heap_bytes_required(()),
1316            self.entry_index.heap_bytes_required(()),
1317        ]
1318        .iter()
1319        .sum()
1320    }
1321}
1322
1323/// Represents a sync log row, used to track synchronization events.
1324///
1325/// At least one entry is written every time a synchronization event (e.g. a track is added
1326/// or removed) occurs. Previous rows are marked as deleted, but rekordbox doesn't write over
1327/// them, so they can be used to track the history of changes to the database.
1328#[binrw]
1329#[derive(Debug, PartialEq, Eq, Clone)]
1330#[brw(little)]
1331pub struct History {
1332    /// Subtype field, in this case usually `80 02` (hex) or `640` (decimal).
1333    subtype: Subtype,
1334    /// Unknown field. I'm assuming this is the `index_shift` found in other row types.
1335    index_shift: u16,
1336    /// Tracks present in the database after this sync event.
1337    num_tracks: u32,
1338    // Magic value, always zero.
1339    #[brw(magic = 0u32)]
1340    /// Sync date, e.g. "2022-02-02".
1341    date: DeviceSQLString,
1342    // Magic value, always `7705` -> `0x1E19`.
1343    #[brw(magic = 0x1E19u16)]
1344    /// Format/protocol version string. In all known exports this is the string "1000".
1345    // We could make this magic, but for now this seems fine.
1346    version: DeviceSQLString,
1347    /// Device or backup label. Can be empty.
1348    label: DeviceSQLString,
1349}
1350
1351impl PageHeapObject for History {
1352    type Args<'a> = ();
1353    fn heap_bytes_required(&self, _: ()) -> u16 {
1354        [
1355            self.subtype.heap_bytes_required(()),
1356            self.index_shift.heap_bytes_required(()),
1357            self.num_tracks.heap_bytes_required(()),
1358            (0u32).heap_bytes_required(()),
1359            self.date.heap_bytes_required(()),
1360            (0u16).heap_bytes_required(()),
1361            self.version.heap_bytes_required(()),
1362            self.label.heap_bytes_required(()),
1363        ]
1364        .iter()
1365        .sum()
1366    }
1367}
1368
1369impl RowVariant for History {
1370    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::History);
1371
1372    fn from_row(row: &Row) -> Option<&Self> {
1373        match row {
1374            Row::Plain(PlainRow::History(row)) => Some(row),
1375            _ => None,
1376        }
1377    }
1378    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1379        match row {
1380            Row::Plain(PlainRow::History(row)) => Some(row),
1381            _ => None,
1382        }
1383    }
1384}
1385
1386/// Represents a musical key.
1387#[binrw]
1388#[derive(Debug, PartialEq, Eq, Clone)]
1389#[brw(little)]
1390pub struct Key {
1391    /// ID of this row.
1392    id: KeyId,
1393    /// Apparently a second copy of the row ID.
1394    id2: u32,
1395    /// Name of the key.
1396    name: DeviceSQLString,
1397}
1398
1399impl RowVariant for Key {
1400    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Keys);
1401
1402    fn from_row(row: &Row) -> Option<&Self> {
1403        match row {
1404            Row::Plain(PlainRow::Key(row)) => Some(row),
1405            _ => None,
1406        }
1407    }
1408    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1409        match row {
1410            Row::Plain(PlainRow::Key(row)) => Some(row),
1411            _ => None,
1412        }
1413    }
1414}
1415
1416impl PageHeapObject for Key {
1417    type Args<'a> = ();
1418    fn heap_bytes_required(&self, _: ()) -> u16 {
1419        [
1420            self.id.heap_bytes_required(()),
1421            self.id2.heap_bytes_required(()),
1422            self.name.heap_bytes_required(()),
1423        ]
1424        .iter()
1425        .sum()
1426    }
1427}
1428
1429/// Represents a record label.
1430#[binrw]
1431#[derive(Debug, PartialEq, Eq, Clone)]
1432#[brw(little)]
1433pub struct Label {
1434    /// ID of this row.
1435    id: LabelId,
1436    /// Name of the record label.
1437    name: DeviceSQLString,
1438}
1439
1440impl RowVariant for Label {
1441    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Labels);
1442
1443    fn from_row(row: &Row) -> Option<&Self> {
1444        match row {
1445            Row::Plain(PlainRow::Label(row)) => Some(row),
1446            _ => None,
1447        }
1448    }
1449    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1450        match row {
1451            Row::Plain(PlainRow::Label(row)) => Some(row),
1452            _ => None,
1453        }
1454    }
1455}
1456
1457impl PageHeapObject for Label {
1458    type Args<'a> = ();
1459    fn heap_bytes_required(&self, _: ()) -> u16 {
1460        [
1461            self.id.heap_bytes_required(()),
1462            self.name.heap_bytes_required(()),
1463        ]
1464        .iter()
1465        .sum()
1466    }
1467}
1468
1469/// Represents a node in the playlist tree (either a folder or a playlist).
1470#[binrw]
1471#[derive(Debug, PartialEq, Eq, Clone)]
1472#[brw(little)]
1473pub struct PlaylistTreeNode {
1474    /// ID of parent row of this row (which means that the parent is a folder).
1475    pub parent_id: PlaylistTreeNodeId,
1476    /// Unknown field.
1477    unknown: u32,
1478    /// Sort order indicastor.
1479    sort_order: u32,
1480    /// ID of this row.
1481    pub id: PlaylistTreeNodeId,
1482    /// Indicates if the node is a folder. Non-zero if it's a leaf node, i.e. a playlist.
1483    node_is_folder: u32,
1484    /// Name of this node, as shown when navigating the menu.
1485    pub name: DeviceSQLString,
1486}
1487
1488impl RowVariant for PlaylistTreeNode {
1489    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::PlaylistTree);
1490
1491    fn from_row(row: &Row) -> Option<&Self> {
1492        match row {
1493            Row::Plain(PlainRow::PlaylistTreeNode(row)) => Some(row),
1494            _ => None,
1495        }
1496    }
1497    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1498        match row {
1499            Row::Plain(PlainRow::PlaylistTreeNode(row)) => Some(row),
1500            _ => None,
1501        }
1502    }
1503}
1504
1505impl PlaylistTreeNode {
1506    /// Indicates whether the node is a folder or a playlist.
1507    #[must_use]
1508    pub fn is_folder(&self) -> bool {
1509        self.node_is_folder > 0
1510    }
1511}
1512
1513impl PageHeapObject for PlaylistTreeNode {
1514    type Args<'a> = ();
1515    fn heap_bytes_required(&self, _: ()) -> u16 {
1516        [
1517            self.parent_id.heap_bytes_required(()),
1518            self.unknown.heap_bytes_required(()),
1519            self.sort_order.heap_bytes_required(()),
1520            self.id.heap_bytes_required(()),
1521            self.node_is_folder.heap_bytes_required(()),
1522            self.name.heap_bytes_required(()),
1523        ]
1524        .iter()
1525        .sum()
1526    }
1527}
1528
1529/// Represents a track entry in a playlist.
1530#[binrw]
1531#[derive(Debug, PartialEq, Eq, Clone)]
1532#[brw(little)]
1533pub struct PlaylistEntry {
1534    /// Position within the playlist.
1535    pub entry_index: u32,
1536    /// ID of the track played at this position in the playlist.
1537    pub track_id: TrackId,
1538    /// ID of the playlist.
1539    pub playlist_id: PlaylistTreeNodeId,
1540}
1541
1542impl RowVariant for PlaylistEntry {
1543    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::PlaylistEntries);
1544
1545    fn from_row(row: &Row) -> Option<&Self> {
1546        match row {
1547            Row::Plain(PlainRow::PlaylistEntry(row)) => Some(row),
1548            _ => None,
1549        }
1550    }
1551    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1552        match row {
1553            Row::Plain(PlainRow::PlaylistEntry(row)) => Some(row),
1554            _ => None,
1555        }
1556    }
1557}
1558
1559impl PageHeapObject for PlaylistEntry {
1560    type Args<'a> = ();
1561    fn heap_bytes_required(&self, _: ()) -> u16 {
1562        [
1563            self.entry_index.heap_bytes_required(()),
1564            self.track_id.heap_bytes_required(()),
1565            self.playlist_id.heap_bytes_required(()),
1566        ]
1567        .iter()
1568        .sum()
1569    }
1570}
1571
1572/// Contains the kinds of Metadata Categories tracks can be browsed by
1573/// on CDJs.
1574#[binrw]
1575#[derive(Debug, PartialEq, Eq, Clone)]
1576#[brw(little)]
1577pub struct ColumnEntry {
1578    // Possibly the primary key, though I don't know if that would
1579    // make sense as I don't think there are references to these
1580    // rows anywhere else. This could be a stable ID to identify
1581    // a category by in hardware (instead of by name).
1582    id: u16,
1583    // Maybe a bitfield containing infos on sort order and which
1584    // columns are displayed.
1585    unknown0: u16,
1586    /// TODO Contained string is prefixed by the "interlinear annotation"
1587    /// characters "\u{fffa}" and postfixed with "\u{fffb}" for some reason?!
1588    /// Contained strings are actually `DeviceSQLString::LongBody` even though
1589    /// they only contain ascii (apart from their unicode annotations)
1590    // TODO since there are only finite many categories, it would make sense
1591    // to encode those as an enum as part of the high-level api.
1592    pub column_name: DeviceSQLString,
1593}
1594
1595impl RowVariant for ColumnEntry {
1596    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Columns);
1597
1598    fn from_row(row: &Row) -> Option<&Self> {
1599        match row {
1600            Row::Plain(PlainRow::ColumnEntry(row)) => Some(row),
1601            _ => None,
1602        }
1603    }
1604    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1605        match row {
1606            Row::Plain(PlainRow::ColumnEntry(row)) => Some(row),
1607            _ => None,
1608        }
1609    }
1610}
1611
1612impl PageHeapObject for ColumnEntry {
1613    type Args<'a> = ();
1614    fn heap_bytes_required(&self, _: ()) -> u16 {
1615        [
1616            self.id.heap_bytes_required(()),
1617            self.unknown0.heap_bytes_required(()),
1618            self.column_name.heap_bytes_required(()),
1619        ]
1620        .iter()
1621        .sum()
1622    }
1623}
1624
1625#[derive(Debug, PartialEq, Clone, Eq)]
1626/// String fields stored via the offset table in Track rows
1627pub struct TrackStrings {
1628    /// International Standard Recording Code (ISRC), in mangled format.
1629    isrc: DeviceSQLString,
1630    /// Lyricist of the track.
1631    lyricist: DeviceSQLString,
1632    /// Unknown string field containing a number.
1633    /// Appears to increment when the track is exported or modified in Rekordbox.
1634    unknown_string2: DeviceSQLString,
1635    /// Unknown string field containing a number.
1636    unknown_string3: DeviceSQLString,
1637    /// Unknown string field.
1638    unknown_string4: DeviceSQLString,
1639    /// Track "message", a field in the Rekordbox UI.
1640    message: DeviceSQLString,
1641    /// "Publish track information" in Rekordbox, value is either "ON" or empty string.
1642    /// Appears related to the Stagehand product to control DJ equipment remotely.
1643    publish_track_information: DeviceSQLString,
1644    /// Determines if hotcues should be autoloaded. Value is either "ON" or empty string.
1645    autoload_hotcues: DeviceSQLString,
1646    /// Unknown string field (usually empty).
1647    unknown_string5: DeviceSQLString,
1648    /// Unknown string field (usually empty).
1649    unknown_string6: DeviceSQLString,
1650    /// Date when the track was added to the Rekordbox collection (YYYY-MM-DD).
1651    date_added: DeviceSQLString,
1652    /// Date when the track was released (YYYY-MM-DD).
1653    release_date: DeviceSQLString,
1654    /// Name of the remix (if any).
1655    mix_name: DeviceSQLString,
1656    /// Unknown string field (usually empty).
1657    unknown_string7: DeviceSQLString,
1658    /// File path of the track analysis file.
1659    analyze_path: DeviceSQLString,
1660    /// Date when the track analysis was performed (YYYY-MM-DD).
1661    analyze_date: DeviceSQLString,
1662    /// Track comment.
1663    comment: DeviceSQLString,
1664    /// Track title.
1665    pub title: DeviceSQLString,
1666    /// Unknown string field (usually empty).
1667    unknown_string8: DeviceSQLString,
1668    /// Name of the file.
1669    filename: DeviceSQLString,
1670    /// Path of the file.
1671    pub file_path: DeviceSQLString,
1672}
1673
1674impl OffsetArrayItems<21> for TrackStrings {
1675    type Item = DeviceSQLString;
1676
1677    fn as_items(&self) -> [&Self::Item; 21] {
1678        [
1679            &self.isrc,
1680            &self.lyricist,
1681            &self.unknown_string2,
1682            &self.unknown_string3,
1683            &self.unknown_string4,
1684            &self.message,
1685            &self.publish_track_information,
1686            &self.autoload_hotcues,
1687            &self.unknown_string5,
1688            &self.unknown_string6,
1689            &self.date_added,
1690            &self.release_date,
1691            &self.mix_name,
1692            &self.unknown_string7,
1693            &self.analyze_path,
1694            &self.analyze_date,
1695            &self.comment,
1696            &self.title,
1697            &self.unknown_string8,
1698            &self.filename,
1699            &self.file_path,
1700        ]
1701    }
1702
1703    fn from_items(items: [Self::Item; 21]) -> Self {
1704        let [isrc, lyricist, unknown_string2, unknown_string3, unknown_string4, message, publish_track_information, autoload_hotcues, unknown_string5, unknown_string6, date_added, release_date, mix_name, unknown_string7, analyze_path, analyze_date, comment, title, unknown_string8, filename, file_path] =
1705            items;
1706        Self {
1707            isrc,
1708            lyricist,
1709            unknown_string2,
1710            unknown_string3,
1711            unknown_string4,
1712            message,
1713            publish_track_information,
1714            autoload_hotcues,
1715            unknown_string5,
1716            unknown_string6,
1717            date_added,
1718            release_date,
1719            mix_name,
1720            unknown_string7,
1721            analyze_path,
1722            analyze_date,
1723            comment,
1724            title,
1725            unknown_string8,
1726            filename,
1727            file_path,
1728        }
1729    }
1730}
1731
1732/// Contains the album name, along with an ID of the corresponding artist.
1733#[binrw]
1734#[derive(Debug, PartialEq, Eq, Clone)]
1735#[brw(little)]
1736pub struct Track {
1737    /// Unknown field, usually `24 00`.
1738    subtype: Subtype,
1739    /// Unknown field, called `index_shift` by [@flesniak](https://github.com/flesniak).
1740    /// Appears to always be 0x20 * row index.
1741    index_shift: u16,
1742    /// Unknown field, called `bitmask` by [@flesniak](https://github.com/flesniak).
1743    /// Appears to always be 0x000c0700.
1744    bitmask: u32,
1745    /// Sample Rate in Hz.
1746    sample_rate: u32,
1747    /// Composer of this track as artist row ID (non-zero if set).
1748    composer_id: ArtistId,
1749    /// File size in bytes.
1750    file_size: u32,
1751    /// Unknown field; observed values are effectively random.
1752    unknown2: u32,
1753    /// Unknown field; observed values: 19048, 64128, 31844.
1754    /// Appears to be the same for all tracks in a given DB.
1755    unknown3: u16,
1756    /// Unknown field; observed values: 30967, 1511, 9043.
1757    /// Appears to be the same for all tracks in a given DB.
1758    unknown4: u16,
1759    /// Artwork row ID for the cover art (non-zero if set),
1760    artwork_id: ArtworkId,
1761    /// Key row ID for the cover art (non-zero if set).
1762    key_id: KeyId,
1763    /// Artist row ID of the original performer (non-zero if set).
1764    orig_artist_id: ArtistId,
1765    /// Label row ID of the original performer (non-zero if set).
1766    label_id: LabelId,
1767    /// Artist row ID of the remixer (non-zero if set).
1768    remixer_id: ArtistId,
1769    /// Bitrate of the track.
1770    bitrate: u32,
1771    /// Track number of the track.
1772    track_number: u32,
1773    /// Track tempo in centi-BPM (= 1/100 BPM).
1774    tempo: u32,
1775    /// Genre row ID for this track (non-zero if set).
1776    genre_id: GenreId,
1777    /// Album row ID for this track (non-zero if set).
1778    album_id: AlbumId,
1779    /// Artist row ID for this track (non-zero if set).
1780    pub artist_id: ArtistId,
1781    /// Row ID of this track (non-zero if set).
1782    pub id: TrackId,
1783    /// Disc number of this track (non-zero if set).
1784    disc_number: u16,
1785    /// Number of times this track was played.
1786    play_count: u16,
1787    /// Year this track was released.
1788    year: u16,
1789    /// Bits per sample of the track aduio file.
1790    sample_depth: u16,
1791    /// Playback duration of this track in seconds (at normal speed).
1792    duration: u16,
1793    /// Unknown field, apparently always "0x29".
1794    unknown5: u16,
1795    /// Color row ID for this track (non-zero if set).
1796    color: ColorIndex,
1797    /// User rating of this track (0 to 5 starts).
1798    pub rating: u8,
1799    /// Format of the file.
1800    file_type: FileType,
1801    /// offsets (strings) at row end
1802    #[brw(args(0x5C, subtype.get_offset_size(), ()))]
1803    pub offsets: OffsetArrayContainer<TrackStrings, 21>,
1804}
1805
1806impl RowVariant for Track {
1807    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Tracks);
1808
1809    fn from_row(row: &Row) -> Option<&Self> {
1810        match row {
1811            Row::Plain(PlainRow::Track(track)) => Some(track),
1812            _ => None,
1813        }
1814    }
1815    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1816        match row {
1817            Row::Plain(PlainRow::Track(track)) => Some(track),
1818            _ => None,
1819        }
1820    }
1821}
1822
1823impl PageHeapObject for Track {
1824    type Args<'a> = ();
1825    fn heap_bytes_required(&self, _: ()) -> u16 {
1826        [
1827            self.subtype.heap_bytes_required(()),
1828            self.index_shift.heap_bytes_required(()),
1829            self.bitmask.heap_bytes_required(()),
1830            self.sample_rate.heap_bytes_required(()),
1831            self.composer_id.heap_bytes_required(()),
1832            self.file_size.heap_bytes_required(()),
1833            self.unknown2.heap_bytes_required(()),
1834            self.unknown3.heap_bytes_required(()),
1835            self.unknown4.heap_bytes_required(()),
1836            self.artwork_id.heap_bytes_required(()),
1837            self.key_id.heap_bytes_required(()),
1838            self.orig_artist_id.heap_bytes_required(()),
1839            self.label_id.heap_bytes_required(()),
1840            self.remixer_id.heap_bytes_required(()),
1841            self.bitrate.heap_bytes_required(()),
1842            self.track_number.heap_bytes_required(()),
1843            self.tempo.heap_bytes_required(()),
1844            self.genre_id.heap_bytes_required(()),
1845            self.album_id.heap_bytes_required(()),
1846            self.artist_id.heap_bytes_required(()),
1847            self.id.heap_bytes_required(()),
1848            self.disc_number.heap_bytes_required(()),
1849            self.play_count.heap_bytes_required(()),
1850            self.year.heap_bytes_required(()),
1851            self.sample_depth.heap_bytes_required(()),
1852            self.duration.heap_bytes_required(()),
1853            self.unknown5.heap_bytes_required(()),
1854            self.color.heap_bytes_required(()),
1855            self.rating.heap_bytes_required(()),
1856            self.file_type.heap_bytes_required(()),
1857            self.offsets
1858                .heap_bytes_required(self.subtype.get_offset_size()),
1859        ]
1860        .iter()
1861        .sum()
1862    }
1863}
1864
1865/// Visibility state for a Menu on the CDJ.
1866#[binrw]
1867#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1868#[brw(little)]
1869pub enum MenuVisibility {
1870    /// The menu is visible.
1871    #[brw(magic = 0x00u8)]
1872    Visible,
1873    /// The menu is hidden.
1874    #[brw(magic = 0x01u8)]
1875    Hidden,
1876    /// Unknown visibility flag.
1877    Unknown(u8),
1878}
1879
1880impl PageHeapObject for MenuVisibility {
1881    type Args<'a> = ();
1882    fn heap_bytes_required(&self, _: ()) -> u16 {
1883        (0u8).heap_bytes_required(())
1884    }
1885}
1886
1887/// This table defines the active menus on the CDJ.
1888#[binrw]
1889#[derive(Debug, PartialEq, Eq, Clone)]
1890#[brw(little)]
1891pub struct Menu {
1892    /// Determines the Label (e.g. "ARTIST").
1893    /// Matches IDs in the COLUMN table.
1894    pub category_id: u16,
1895
1896    /// Points to the data source, i.e. the list of artists is 0x02.
1897    pub content_pointer: u16,
1898    /// Unknown field. Swapping values here appears to have no effect on CDJ-350 behavior.
1899    ///
1900    /// Some observed values:
1901    /// - 0x01: Track
1902    /// - 0x02: Artist
1903    /// - 0x03: Album
1904    /// - 0x05: BPM
1905    /// - 0x63 (99): Generic List (Playlist, Genre, Key, History)
1906    pub unknown: u8,
1907
1908    /// Visibility state of the menu item.
1909    ///
1910    /// Experiments confirmed that changing this from `Hidden` to `Visible` makes hidden menus
1911    /// (like Genre) appear, although some menus do not show in the CDJ-350 even when made
1912    /// visible here.
1913    pub visibility: MenuVisibility,
1914
1915    /// Visual position in the menu list.
1916    /// 0 is valid and places the item at the very top (if visible).
1917    pub sort_order: u16,
1918}
1919
1920impl PageHeapObject for Menu {
1921    type Args<'a> = ();
1922    fn heap_bytes_required(&self, _: ()) -> u16 {
1923        [
1924            self.category_id.heap_bytes_required(()),
1925            self.content_pointer.heap_bytes_required(()),
1926            self.unknown.heap_bytes_required(()),
1927            self.visibility.heap_bytes_required(()),
1928            self.sort_order.heap_bytes_required(()),
1929        ]
1930        .iter()
1931        .sum()
1932    }
1933}
1934
1935impl RowVariant for Menu {
1936    const PAGE_TYPE: PageType = PageType::Plain(PlainPageType::Menu);
1937
1938    fn from_row(row: &Row) -> Option<&Self> {
1939        match row {
1940            Row::Plain(PlainRow::Menu(row)) => Some(row),
1941            _ => None,
1942        }
1943    }
1944    fn from_row_mut(row: &mut Row) -> Option<&mut Self> {
1945        match row {
1946            Row::Plain(PlainRow::Menu(row)) => Some(row),
1947            _ => None,
1948        }
1949    }
1950}
1951
1952/// A table row contains the actual data.
1953#[binrw]
1954#[derive(Debug, PartialEq, Eq, Clone)]
1955#[brw(little)]
1956#[br(import(page_type: PlainPageType))]
1957// The large enum size is unfortunate, but since users of this library will probably use iterators
1958// to consume the results on demand, we can live with this. The alternative of using a `Box` would
1959// require a heap allocation per row, which is arguably worse. Hence, the warning is disabled for
1960// this enum.
1961#[allow(clippy::large_enum_variant)]
1962pub enum PlainRow {
1963    /// Contains the album name, along with an ID of the corresponding artist.
1964    // FIXME: Fresh album rows typically have about 6 bytes of padding,
1965    // presumably to allow edits on DJ gear.
1966    #[br(pre_assert(page_type == PlainPageType::Albums))]
1967    Album(Album),
1968    /// Contains the artist name and ID.
1969    // FIXME: Fresh artist rows typically have about 6 bytes of padding,
1970    // presumably to allow edits on DJ gear.
1971    #[br(pre_assert(page_type == PlainPageType::Artists))]
1972    Artist(Artist),
1973    /// Contains the artwork path and ID.
1974    #[br(pre_assert(page_type == PlainPageType::Artwork))]
1975    Artwork(Artwork),
1976    /// Contains numeric color ID
1977    #[br(pre_assert(page_type == PlainPageType::Colors))]
1978    Color(Color),
1979    /// Represents a musical genre.
1980    #[br(pre_assert(page_type == PlainPageType::Genres))]
1981    Genre(Genre),
1982    /// Represents a history playlist.
1983    #[br(pre_assert(page_type == PlainPageType::HistoryPlaylists))]
1984    HistoryPlaylist(HistoryPlaylist),
1985    /// Represents a history playlist.
1986    #[br(pre_assert(page_type == PlainPageType::HistoryEntries))]
1987    HistoryEntry(HistoryEntry),
1988    /// Represents a history sync row.
1989    #[br(pre_assert(page_type == PlainPageType::History))]
1990    History(History),
1991    /// Represents a musical key.
1992    #[br(pre_assert(page_type == PlainPageType::Keys))]
1993    Key(Key),
1994    /// Represents a record label.
1995    #[br(pre_assert(page_type == PlainPageType::Labels))]
1996    Label(Label),
1997    /// Represents a node in the playlist tree (either a folder or a playlist).
1998    #[br(pre_assert(page_type == PlainPageType::PlaylistTree))]
1999    PlaylistTreeNode(PlaylistTreeNode),
2000    /// Represents a track entry in a playlist.
2001    #[br(pre_assert(page_type == PlainPageType::PlaylistEntries))]
2002    PlaylistEntry(PlaylistEntry),
2003    /// Contains the metadata categories by which Tracks can be browsed by.
2004    #[br(pre_assert(page_type == PlainPageType::Columns))]
2005    ColumnEntry(ColumnEntry),
2006    /// Manages the active menus on the CDJ.
2007    #[br(pre_assert(page_type == PlainPageType::Menu))]
2008    Menu(Menu),
2009    /// Contains a track entry.
2010    // FIXME: Fresh track rows typically have about 48 bytes of padding,
2011    // presumably to allow edits on DJ gear.
2012    #[br(pre_assert(page_type == PlainPageType::Tracks))]
2013    Track(Track),
2014}
2015
2016impl PageHeapObject for PlainRow {
2017    type Args<'a> = ();
2018    fn heap_bytes_required(&self, _: ()) -> u16 {
2019        match self {
2020            PlainRow::Album(album) => album.heap_bytes_required(()),
2021            PlainRow::Artist(artist) => artist.heap_bytes_required(()),
2022            PlainRow::Artwork(artwork) => artwork.heap_bytes_required(()),
2023            PlainRow::Color(color) => color.heap_bytes_required(()),
2024            PlainRow::Genre(genre) => genre.heap_bytes_required(()),
2025            PlainRow::HistoryPlaylist(history_playlist) => history_playlist.heap_bytes_required(()),
2026            PlainRow::HistoryEntry(history_entry) => history_entry.heap_bytes_required(()),
2027            PlainRow::History(history) => history.heap_bytes_required(()),
2028            PlainRow::Key(key) => key.heap_bytes_required(()),
2029            PlainRow::Label(label) => label.heap_bytes_required(()),
2030            PlainRow::PlaylistTreeNode(playlist_tree_node) => {
2031                playlist_tree_node.heap_bytes_required(())
2032            }
2033            PlainRow::PlaylistEntry(playlist_entry) => playlist_entry.heap_bytes_required(()),
2034            PlainRow::ColumnEntry(column_entry) => column_entry.heap_bytes_required(()),
2035            PlainRow::Menu(menu) => menu.heap_bytes_required(()),
2036            PlainRow::Track(track) => track.heap_bytes_required(()),
2037        }
2038    }
2039}
2040
2041/// A table row contains the actual data.
2042#[binrw]
2043#[derive(Debug, PartialEq, Eq, Clone)]
2044#[brw(little)]
2045#[br(import(page_type: PageType))]
2046// The large enum size is unfortunate, but since users of this library will probably use iterators
2047// to consume the results on demand, we can live with this. The alternative of using a `Box` would
2048// require a heap allocation per row, which is arguably worse. Hence, the warning is disabled for
2049// this enum.
2050//
2051// FIXME: Rows must always be aligned to 4 bytes, and certain row types typically have padding
2052// after each row too (see PlainRow). This is irrelevant while we write rows to precise offsets
2053// but needs to be considered when we generate row offsets from scratch.
2054#[allow(clippy::large_enum_variant)]
2055pub enum Row {
2056    // TODO(Swiftb0y: come up with something prettier than the match hell below)
2057    #[br(pre_assert(matches!(page_type, PageType::Plain(_))))]
2058    /// A row in a "plain" database (export.pdb), which contains one of the known row types.
2059    Plain(
2060        #[br(args(match page_type {
2061            PageType::Plain(v) => v,
2062            _ => unreachable!("by above pre_assert")
2063        }))]
2064        PlainRow,
2065    ),
2066    #[br(pre_assert(matches!(page_type, PageType::Ext(_))))]
2067    /// A row in an "ext" database (exportExt.pdb), which contains extended track information.
2068    Ext(
2069        #[br(args(match page_type {
2070            PageType::Ext(v) => v,
2071            _ => unreachable!("by above pre_assert")
2072        }))]
2073        ExtRow,
2074    ),
2075    /// The row format (and also its size) is unknown, which means it can't be parsed.
2076    #[br(pre_assert(matches!(page_type, PageType::Unknown(_))))]
2077    Unknown,
2078}
2079
2080impl Row {
2081    /// Attempt to convert this row into a reference to the given variant type.
2082    #[must_use]
2083    pub fn as_variant<T: RowVariant>(&self) -> Option<&T> {
2084        T::from_row(self)
2085    }
2086    /// Attempt to convert this row into a mutable reference to the given variant type.
2087    #[must_use]
2088    pub fn as_variant_mut<T: RowVariant>(&mut self) -> Option<&mut T> {
2089        T::from_row_mut(self)
2090    }
2091}
2092
2093impl PageHeapObject for Row {
2094    type Args<'a> = ();
2095    fn heap_bytes_required(&self, _: ()) -> u16 {
2096        match self {
2097            Row::Plain(plain_row) => plain_row.heap_bytes_required(()),
2098            Row::Ext(ext_row) => ext_row.heap_bytes_required(()),
2099            Row::Unknown => panic!("Unable to determine required bytes for unknown row type"),
2100        }
2101    }
2102}