From 8ea532fa262fdcdfbecf897b177268d691440b68 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 10 Nov 2023 18:11:24 +0100 Subject: [PATCH] Added a release generator tool to stress test everything --- src/tools/CMakeLists.txt | 1 + src/tools/db-generator/CMakeLists.txt | 10 ++ src/tools/db-generator/LmsDbGenerator.cpp | 210 ++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 src/tools/db-generator/CMakeLists.txt create mode 100644 src/tools/db-generator/LmsDbGenerator.cpp diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 8b305be3..df12a1c7 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(db-generator) add_subdirectory(cover) add_subdirectory(metadata) add_subdirectory(recommendation) diff --git a/src/tools/db-generator/CMakeLists.txt b/src/tools/db-generator/CMakeLists.txt new file mode 100644 index 00000000..1794b2c9 --- /dev/null +++ b/src/tools/db-generator/CMakeLists.txt @@ -0,0 +1,10 @@ + +add_executable(lms-db-generator + LmsDbGenerator.cpp + ) + +target_link_libraries(lms-db-generator PRIVATE + lmsdatabase + lmsutils + Boost::program_options + ) diff --git a/src/tools/db-generator/LmsDbGenerator.cpp b/src/tools/db-generator/LmsDbGenerator.cpp new file mode 100644 index 00000000..f4700fe3 --- /dev/null +++ b/src/tools/db-generator/LmsDbGenerator.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2023 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "services/database/Db.hpp" +#include "services/database/Artist.hpp" +#include "services/database/Cluster.hpp" +#include "services/database/Release.hpp" +#include "services/database/Session.hpp" +#include "services/database/Track.hpp" +#include "services/database/TrackArtistLink.hpp" + +#include "utils/IConfig.hpp" +#include "utils/Random.hpp" +#include "utils/StreamLogger.hpp" +#include "utils/Service.hpp" + +namespace +{ + struct GeneratorParameters + { + std::size_t releaseCount{ 100 }; + std::size_t trackCountPerRelease{ 10 }; + float compilationRatio{ 0.1 }; + std::size_t genreCountPerTrack{ 3 }; + std::size_t moodCountPerTrack{ 3 }; + std::size_t genreCount{ 50 }; + std::size_t moodCount{ 25 }; + std::filesystem::path trackPath; + }; + + struct GenerationContext + { + Database::Session& session; + std::vector genres; + std::vector moods; + GenerationContext(Database::Session& _session) : session{ _session } {} + }; + + Database::Cluster::pointer generateCluster(Database::Session& session, Database::ClusterType::pointer clusterType) + { + const std::string clusterName{ clusterType->getName() + "-" + std::string{ UUID::generate().getAsString() } }; + return session.create(clusterType, clusterName); + } + + Database::Artist::pointer generateArtist(Database::Session& session) + { + const UUID artistMBID{ UUID::generate() }; + const std::string artistName{ "Artist-" + std::string{ UUID::generate().getAsString() } }; + return session.create(artistName, artistMBID); + } + + void generateRelease(const GeneratorParameters& params, GenerationContext& context) + { + using namespace Database; + + const UUID releaseMBID{ UUID::generate() }; + const std::string releaseName{ "Release-" + std::string{ UUID::generate().getAsString() } }; + Release::pointer release{ context.session.create(releaseName, releaseMBID) }; + + Artist::pointer artist{ generateArtist(context.session) }; + + for (std::size_t i{}; i < params.trackCountPerRelease; ++i) + { + Track::pointer track{ context.session.create(params.trackPath) }; + + track.modify()->setName("Track-" + std::string{ UUID::generate().getAsString() }); + track.modify()->setDiscNumber(1); + track.modify()->setTrackNumber(i); + track.modify()->setDuration(std::chrono::seconds{ Random::getRandom(30, 300) }); + track.modify()->setRelease(release); + track.modify()->setTrackMBID(UUID::generate()); + track.modify()->setRecordingMBID(UUID::generate()); + track.modify()->setTotalTrack(params.trackCountPerRelease); + + TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::Artist); + TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::ReleaseArtist); + + std::vector> clusters; + clusters.push_back(*Random::pickRandom(context.genres)); + clusters.push_back(*Random::pickRandom(context.moods)); + track.modify()->setClusters(clusters); + } + } + + void generate(const GeneratorParameters& params, GenerationContext& context) + { + for (std::size_t i{}; i < params.releaseCount; ++i) + { + if ((i % 1000) == 0) + std::cout << "Generating album #" << i << std::endl; + + generateRelease(params, context); + } + } + + void prepareContext(const GeneratorParameters& params, GenerationContext& context) + { + // create some random genres/moods + { + Database::ClusterType::pointer genre{ Database::ClusterType::find(context.session, "GENRE") }; + if (!genre) + genre = context.session.create("GENRE"); + + for (std::size_t i{}; i < params.genreCount; ++i) + context.genres.push_back(generateCluster(context.session, genre)); + } + + { + Database::ClusterType::pointer mood{ Database::ClusterType::find(context.session, "MOOD") }; + if (!mood) + mood = context.session.create("MOOD"); + + for (std::size_t i{}; i < params.moodCount; ++i) + context.moods.push_back(generateCluster(context.session, mood)); + } + } +} + +int main(int argc, char* argv[]) +{ + try + { + // log to stdout + Service logger{ std::make_unique(std::cout) }; + + namespace po = boost::program_options; + + const GeneratorParameters defaultParams; + + po::options_description options{ "Options" }; + options.add_options() + ("conf,c", po::value()->default_value("/etc/lms.conf"), "lms config file") + ("release-count", po::value()->default_value(defaultParams.releaseCount), "Number of releases to generate") + ("track-count-per-release", po::value()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release") + ("compilation-ratio", po::value()->default_value(defaultParams.compilationRatio), "Compilation ratio (compilation means all tracks have a different artist)") + ("track-path", po::value()->required(), "Path of a valid track file, that will be used for all generated tracks") + ("genre-count", po::value()->default_value(defaultParams.genreCount), "Number of genres to generate") + ("genre-count-per-track", po::value()->default_value(defaultParams.genreCountPerTrack), "Number of genres to assign to each track") + ("mood-count", po::value()->default_value(defaultParams.moodCount), "Number of moods to generate") + ("mood-count-per-track", po::value()->default_value(defaultParams.moodCountPerTrack), "Number of moods to assign to each track") + ("help,h", "produce help message") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, options), vm); + + if (vm.count("help")) { + std::cout << options << "\n"; + return EXIT_SUCCESS; + } + + // notify required params + po::notify(vm); + + GeneratorParameters genParams; + genParams.releaseCount = vm["release-count"].as(); + genParams.trackCountPerRelease = vm["track-count-per-release"].as(); + genParams.compilationRatio = vm["compilation-ratio"].as(); + genParams.trackPath = std::filesystem::path{ vm["track-path"].as() }; + + if (!std::filesystem::exists(genParams.trackPath)) + throw std::runtime_error{ "File '" + genParams.trackPath.string() + "' does not exist!" }; + + Service config{ createConfig(vm["conf"].as()) }; + Database::Db db{ config->getPath("working-dir") / "lms.db" }; + Database::Session session{ db }; + auto transaction{ session.createUniqueTransaction() }; + std::cout << "Starting generation..." << std::endl; + + GenerationContext genContext{ session }; + prepareContext(genParams, genContext); + generate(genParams, genContext); + + std::cout << "Generation complete!" << std::endl; + } + catch (std::exception& e) + { + std::cerr << "Caught exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}