Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ add_library(
src/djinterop/engine/schema/schema_2_21_2.hpp
src/djinterop/engine/schema/schema_3_0_0.cpp
src/djinterop/engine/schema/schema_3_0_0.hpp
src/djinterop/engine/schema/schema_3_0_1.cpp
src/djinterop/engine/schema/schema_3_0_1.hpp
src/djinterop/engine/schema/schema.cpp
src/djinterop/engine/schema/schema.hpp
src/djinterop/engine/schema/schema_validate_utils.hpp
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ What is supported:

The library supports the following firmware and application versions:

* SC5000 Firmware from 1.0.3 to 4.1.0.
* Other players (e.g. SC6000/M) may work, but this is currently untested.
* Engine DJ Desktop (aka Engine Prime) from 1.0.1 to 4.1.0.
* Engine DJ OS from 1.0.3 to 4.3.3.
* Tested on Denon SC5000 and Numark Mixstream Pro. Other players (e.g. SC6000/M) may work, but this is currently untested.
* Engine DJ Desktop (aka Engine Prime) from 1.0.1 to 4.3.0.

What is not supported (yet):

Expand Down
11 changes: 7 additions & 4 deletions include/djinterop/engine/engine_schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum class DJINTEROP_PUBLIC engine_schema
schema_2_21_2,

schema_3_0_0,
schema_3_0_1,
};

/// Set of supported schema versions.
Expand All @@ -64,7 +65,7 @@ constexpr std::array supported_schemas{
engine_schema::schema_2_20_1, engine_schema::schema_2_20_2,
engine_schema::schema_2_20_3, engine_schema::schema_2_21_0,
engine_schema::schema_2_21_1, engine_schema::schema_2_21_2,
engine_schema::schema_3_0_0};
engine_schema::schema_3_0_0, engine_schema::schema_3_0_1};

/// Set of supported schema 1.x versions.
constexpr std::array supported_v1_schemas{
Expand All @@ -84,10 +85,10 @@ constexpr std::array supported_v2_schemas{

/// Set of supported schema 3.x versions.
constexpr std::array supported_v3_schemas{
engine_schema::schema_3_0_0};
engine_schema::schema_3_0_0, engine_schema::schema_3_0_1};

/// The most recent schema version supported by the library.
constexpr engine_schema latest_schema = engine_schema::schema_3_0_0;
constexpr engine_schema latest_schema = engine_schema::schema_3_0_1;

/// The most recent schema 1.x version supported by the library.
constexpr engine_schema latest_v1_schema = engine_schema::schema_1_18_0_os;
Expand All @@ -96,7 +97,7 @@ constexpr engine_schema latest_v1_schema = engine_schema::schema_1_18_0_os;
constexpr engine_schema latest_v2_schema = engine_schema::schema_2_21_2;

/// The most recent schema 3.x version supported by the library.
constexpr engine_schema latest_v3_schema = engine_schema::schema_3_0_0;
constexpr engine_schema latest_v3_schema = engine_schema::schema_3_0_1;

/// Get a string representation of the schema.
inline std::string to_string(const engine_schema& v)
Expand All @@ -122,6 +123,7 @@ inline std::string to_string(const engine_schema& v)
case engine_schema::schema_2_21_1: return "2.21.1";
case engine_schema::schema_2_21_2: return "2.21.2";
case engine_schema::schema_3_0_0: return "3.0.0";
case engine_schema::schema_3_0_1: return "3.0.1";
}

return "(unknown schema with ordinal " +
Expand Down Expand Up @@ -159,6 +161,7 @@ inline std::string to_application_version_string(const engine_schema& v)
case engine_schema::schema_2_21_1: return "Engine DJ Desktop/OS 4.0.0";
case engine_schema::schema_2_21_2: return "Engine DJ Desktop/OS 4.0.1";
case engine_schema::schema_3_0_0: return "Engine DJ Desktop/OS 4.1.0 to 4.2.1";
case engine_schema::schema_3_0_1: return "Engine DJ Desktop/OS 4.3.x";
}

return "Engine versions unknown (" + to_string(v) + ")";
Expand Down
12 changes: 9 additions & 3 deletions src/djinterop/engine/schema/schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "schema_2_21_1.hpp"
#include "schema_2_21_2.hpp"
#include "schema_3_0_0.hpp"
#include "schema_3_0_1.hpp"

namespace djinterop::engine::schema
{
Expand Down Expand Up @@ -110,6 +111,8 @@ std::unique_ptr<schema_creator_validator> make_schema_creator_validator(
return std::make_unique<schema_2_21_2>();
case engine_schema::schema_3_0_0:
return std::make_unique<schema_3_0_0>();
case engine_schema::schema_3_0_1:
return std::make_unique<schema_3_0_1>();
}

throw unsupported_operation{
Expand Down Expand Up @@ -233,9 +236,12 @@ engine_schema detect_schema(
case 3:
switch (version.min)
{
case 0:
REQUIRE_PATCH_VERSION(version, 0);
return engine_schema::schema_3_0_0;
case 0:
switch (version.pat)
{
case 0: return engine_schema::schema_3_0_0;
case 1: return engine_schema::schema_3_0_1;
}
default: throw unsupported_database{make_err_message(version)};
}
default: throw unsupported_database{make_err_message(version)};
Expand Down
228 changes: 228 additions & 0 deletions src/djinterop/engine/schema/schema_3_0_1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
This file is part of libdjinterop.

libdjinterop is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

libdjinterop is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libdjinterop. If not, see <http://www.gnu.org/licenses/>.
*/

#include <sqlite_modern_cpp.h>

#include "../../util/random.hpp"
#include "schema_3_0_1.hpp"

namespace djinterop::engine::schema
{
void schema_3_0_1::create(sqlite::database& db)
{
// Schema 3.0.1 modifies trigger definitions on the Track and
// PerformanceData tables.
db << "CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, "
" uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor "
"INTEGER, schemaVersionPatch INTEGER, "
"currentPlayedIndiciator INTEGER, "
"lastRekordBoxLibraryImportReadCounter INTEGER);";
db << "CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, "
"hash TEXT, albumArt BLOB );";
db << "CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId "
"TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, "
"lastPackTime DATETIME );";
db << "CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, "
"title TEXT, parentListId INTEGER, isPersisted BOOLEAN, "
"nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported "
"BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, "
"parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE "
"(parentListId, nextListId) );";
db << "CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY "
"AUTOINCREMENT, listId INTEGER, trackId INTEGER, "
"databaseUuid TEXT, nextEntityId INTEGER, membershipReference "
"INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, "
"databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist "
"(id) ON DELETE CASCADE );";
db << "CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, "
" title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, "
" nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, "
"CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, "
"parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT "
"UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) );";
db << "CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, "
"playOrder INTEGER, length INTEGER, bpm INTEGER, year "
"INTEGER, path TEXT, filename TEXT, bitrate INTEGER, "
"bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, "
"title TEXT, artist TEXT, album TEXT, genre TEXT, "
"comment TEXT, label TEXT, composer TEXT, remixer TEXT, "
"key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed "
"DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed "
"BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, "
"isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, "
" isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator "
"INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, "
" streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, "
"originDatabaseUuid TEXT, originTrackId INTEGER, streamingFlags "
"INTEGER, explicitLyrics BOOLEAN, lastEditTime DATETIME, "
"CONSTRAINT C_originDatabaseUuid_originTrackId UNIQUE "
"(originDatabaseUuid, originTrackId), CONSTRAINT C_path UNIQUE "
"(path), FOREIGN KEY (albumArtId) REFERENCES AlbumArt (id) ON "
"DELETE RESTRICT );";
db << "CREATE TABLE PerformanceData ( trackId INTEGER PRIMARY KEY, "
"trackData BLOB, overviewWaveFormData BLOB, beatData BLOB, "
"quickCues BLOB, loops BLOB, thirdPartySourceId INTEGER, "
"activeOnLoadLoops INTEGER, FOREIGN KEY(trackId) REFERENCES "
"Track(id) ON DELETE CASCADE ON UPDATE CASCADE );";
db << "CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY "
"AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, "
"FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE );";
db << "DELETE FROM sqlite_sequence;";
db << "CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash);";
db << "CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON "
"PlaylistEntity(nextEntityId, listId);";
db << "CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON "
"Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE "
"Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; "
"END;";
db << "CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT "
"ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack "
"SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END;";
db << "CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE;";
db << "CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist "
"FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + "
"nextListId) WHERE nextListId = NEW.nextListId AND parentListId = "
"NEW.parentListId; END;";
db << "CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist "
"FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = "
"NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND "
"parentListId = NEW.parentListId; END;";
db << "CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist "
"FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = "
"OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist "
" WHERE parentListId = OLD.id; END;";
db << "CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON "
"Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) "
" OR (old.parentListId != new.parentListId AND new.isPersisted = "
"1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE "
"id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); "
"END;";
db << "CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON "
"Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 "
"BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id "
"IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); "
"END;";
db << "CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON "
"Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET "
" isPersisted = 1 WHERE id IN (SELECT parentListId FROM "
"PlaylistAllParent WHERE id=new.id); END;";
db << "CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT "
"id, parentListId FROM Playlist UNION ALL SELECT "
"recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN "
"FindAllParent recursiveCTE ON recursiveCTE.parentListId = "
"Plist.id ) SELECT * FROM FindAllParent;";
db << "CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT "
"id, id as childListId FROM Playlist UNION ALL SELECT "
"recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN "
"FindAllChild recursiveCTE ON recursiveCTE.childListId = "
"Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> "
"childListId;";
db << "CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT "
"id AS child, parentListId AS parent, title AS name, 1 AS depth FROM "
"Playlist UNION ALL SELECT child, parentListId AS parent, "
"title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy "
"h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( "
" SELECT id , nextListId, 1 AS position FROM Playlist WHERE "
"nextListId = 0 UNION ALL SELECT c.id , c.nextListId , "
"l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON "
"c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, "
" GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT "
"child, name FROM Heirarchy ORDER BY depth DESC ) "
"GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( "
" ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy "
"WHERE child = id) ) DESC, (SELECT position FROM OrderedList "
"ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT "
"JOIN NameConcat g USING (id);";
db << "CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE "
"ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE "
"PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE "
"nextEntityId = OLD.id AND listId = OLD.listId; END;";
db << "CREATE INDEX index_Track_filename ON Track (filename);";
db << "CREATE INDEX index_Track_albumArtId ON Track (albumArtId);";
db << "CREATE INDEX index_Track_uri ON Track (uri);";
db << "CREATE INDEX index_Track_title ON Track(title);";
db << "CREATE INDEX index_Track_length ON Track(length);";
db << "CREATE INDEX index_Track_rating ON Track(rating);";
db << "CREATE INDEX index_Track_year ON Track(year);";
db << "CREATE INDEX index_Track_dateAdded ON Track(dateAdded);";
db << "CREATE INDEX index_Track_genre ON Track(genre);";
db << "CREATE INDEX index_Track_artist ON Track(artist);";
db << "CREATE INDEX index_Track_album ON Track(album);";
db << "CREATE INDEX index_Track_key ON Track(key);";
db << "CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + "
"0.5 AS int));";
db << "CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON "
"Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name "
"= 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track "
"id''s are not allowed'); END;";
db << "CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON "
"Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, "
"'Changing track id''s are not allowed'); END;";
db << "CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT "
"ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR "
"IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET "
" originTrackId = NEW.id, originDatabaseUuid = (SELECT "
"uuid FROM Information) WHERE track.id = NEW.id; END;";
db << "CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE "
"ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR "
"IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET "
" originTrackId = NEW.id, originDatabaseUuid = (SELECT "
"uuid FROM Information) WHERE track.id = NEW.id; END;";
db << "CREATE TRIGGER trigger_after_update_only_Track_timestamp "
"AFTER UPDATE OF length,"
"bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, title, "
"artist,"
"album, genre, comment, label, composer, remixer, key, rating, "
"albumArt,"
"fileType, isAnalyzed, isBeatgridLocked,"
"explicitLyrics ON Track FOR EACH ROW BEGIN UPDATE Track SET"
" lastEditTime = strftime('%s') WHERE ROWID = NEW.ROWID;"
"END;";
db << "CREATE TRIGGER trigger_PerformanceData_after_update_Track_timestamp "
"AFTER UPDATE OF trackData, isAnalyzed, overviewWaveFormData, "
"beatData, quickCues, loops, activeOnLoadLoops ON PerformanceData "
"FOR EACH ROW BEGIN UPDATE Track SET lastEditTime = strftime('%s') "
"WHERE id = NEW.trackId; "
"END;";
db << "CREATE TRIGGER trigger_after_insert_Track_insert_performance_data "
"AFTER INSERT ON Track BEGIN INSERT INTO "
"PerformanceData(trackId) "
"VALUES(NEW.id); END;";
db << "CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity "
"(trackId);";

// Generate UUID for the Information table.
auto uuid_str = djinterop::util::generate_random_uuid();

// Not yet sure how the "currentPlayedIndiciator" (typo deliberate) value
// is formed.
auto current_played_indicator_fake_value =
djinterop::util::generate_random_int64();

// Insert row into Information
db << "INSERT INTO Information ([uuid], [schemaVersionMajor], "
"[schemaVersionMinor], [schemaVersionPatch], "
"[currentPlayedIndiciator], [lastRekordBoxLibraryImportReadCounter]) "
"VALUES (?, ?, ?, ?, ?, ?)"
<< uuid_str << schema_version.maj << schema_version.min
<< schema_version.pat << current_played_indicator_fake_value << 0;

// Insert default album art entry
db << "INSERT INTO AlbumArt VALUES (1, '', NULL)";
}

} // namespace djinterop::engine::schema
36 changes: 36 additions & 0 deletions src/djinterop/engine/schema/schema_3_0_1.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
This file is part of libdjinterop.

libdjinterop is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

libdjinterop is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libdjinterop. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <sqlite_modern_cpp.h>

#include <djinterop/semantic_version.hpp>

#include "schema_3_0_0.hpp"

namespace djinterop::engine::schema
{
class schema_3_0_1 : public schema_3_0_0
{
public:
static constexpr const semantic_version schema_version{3, 1, 0};

void create(sqlite::database& db) override;
};

} // namespace djinterop::engine::schema
4 changes: 4 additions & 0 deletions test/djinterop/engine/database_reference_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ const std::vector<std::string> ref_script_dirs{
"/ref/engine/desktop/desktop-4.2.0",
"/ref/engine/sc5000/firmware-4.2.0",
"/ref/engine/desktop/desktop-4.2.1",
"/ref/engine/desktop/desktop-4.3.0",
"/ref/engine/sc5000/firmware-4.3.0",
"/ref/engine/sc5000/firmware-4.3.1",
"/ref/engine/sc5000/firmware-4.3.2",
};
} // anonymous namespace

Expand Down