From ada50b3d5c4f45a440bf31dbf0ef4365c3d2d3be Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Thu, 5 Sep 2019 20:57:13 -0400 Subject: [PATCH] Still in the process of switching to prepared statements. Left TODO for removing the make_shared_ptr for the raw song data. Added feature to retrieve a sing cover art record in json format --- CMakeLists.txt | 2 + include/controller/CoverArtController.hpp | 118 ++++++++++++++++++++++ include/controller/SongController.hpp | 2 + include/dto/CoverArtDto.hpp | 22 ++++ src/Main.cpp | 5 +- src/database/AlbumRepository.cpp | 3 +- src/database/ArtistRepository.cpp | 77 ++++++++++++-- src/database/CoverArtRepository.cpp | 108 +++++++++++++++----- 8 files changed, 303 insertions(+), 34 deletions(-) create mode 100644 include/controller/CoverArtController.hpp create mode 100644 include/dto/CoverArtDto.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c2f0e9a..d604782 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES ) set(HEADERS include/component/AppComponent.hpp + include/controller/CoverArtController.hpp include/controller/LoginController.hpp include/controller/SongController.hpp include/database/AlbumRepository.h @@ -36,6 +37,7 @@ set(HEADERS include/database/GenreRepository.h include/database/SongRepository.h include/database/YearRepository.h + include/dto/CoverArtDto.hpp include/dto/LoginResultDto.hpp include/dto/SongDto.hpp include/manager/AlbumManager.h diff --git a/include/controller/CoverArtController.hpp b/include/controller/CoverArtController.hpp new file mode 100644 index 0000000..7597787 --- /dev/null +++ b/include/controller/CoverArtController.hpp @@ -0,0 +1,118 @@ +#ifndef COVERARTCONTROLLER_H_ +#define COVERARTCONTROLLER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "oatpp/core/data/stream/ChunkedBuffer.hpp" +#include "oatpp/core/data/stream/FileStream.hpp" +#include "oatpp/core/macro/codegen.hpp" +#include "oatpp/core/macro/component.hpp" +#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp" +#include "oatpp/web/mime/multipart/Reader.hpp" +#include "oatpp/web/server/api/ApiController.hpp" + +#include "database/CoverArtRepository.h" +#include "dto/CoverArtDto.hpp" +#include "manager/CoverArtManager.h" +#include "model/Models.h" +#include "type/Scopes.h" +#include "type/CoverFilter.h" + +namespace fs = std::filesystem; + +namespace controller +{ + class CoverArtController : public oatpp::web::server::api::ApiController + { + public: + CoverArtController(std::string p, OATPP_COMPONENT(std::shared_ptr, objectMapper)) + : oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p) + { } + + CoverArtController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr, objectMapper)) + : oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf) + { } + + #include OATPP_CODEGEN_BEGIN(ApiController) + + /** + // endpoint for retrieving all cover art records in json format + ENDPOINT("GET", "/api/v1/coverart", coverArtRecords, + REQUEST(std::shared_ptr, request)) + { + std::cout << "starting process of retrieving cover art" << std::endl; + database::CoverArtRepository covRepo(m_bConf); + auto covsDb = covRepo.retrieveRecords(); + auto coverArts = oatpp::data::mapping::type::List::createShared(); + + for (auto& covDb : covsDb) { + auto cov = dto::CoverArtDto::createShared(); + cov->id = covDb.id; + cov->Title = covDb.songTitle.c_str(); + + coverArts->pushBack(cov); + } + + return createDtoResponse(Status::CODE_200, songs); + } + */ + + // endpoint for retrieving single cover art record by the cover art id in json format + ENDPOINT("GET", "/api/v1/coverart/{id}", coverArtRecord, + PATH(Int32, id)) { + + database::CoverArtRepository covRepo(m_bConf); + model::Cover covDb; + covDb.id = id; + + OATPP_ASSERT_HTTP(covRepo.doesCoverArtExist(covDb, type::CoverFilter::id) , Status::CODE_403, "song does not exist"); + + std::cout << "cover art exists" << std::endl; + covDb = covRepo.retrieveRecord(covDb, type::CoverFilter::id); + + auto coverArt = dto::CoverArtDto::createShared(); + coverArt->id = covDb.id; + coverArt->songTitle = covDb.songTitle.c_str(); + + return createDtoResponse(Status::CODE_200, coverArt); + } + + ENDPOINT("GET", "/api/v1/coverart/download/{id}", downloadCoverArt, + PATH(Int32, id)) { + + database::CoverArtRepository covRepo(m_bConf); + model::Cover covDb; + covDb.id = id; + covDb = covRepo.retrieveRecord(covDb, type::CoverFilter::id); + + std::ifstream fl(covDb.imagePath.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + fl.seekg(0); + + std::stringstream buf; + std::copy(std::istreambuf_iterator(fl), + std::istreambuf_iterator(), + std::ostreambuf_iterator(buf)); + fl.close(); + + auto rawCover = oatpp::String(buf.str().data(), (v_int32)buf.str().size(), true); + auto response = createResponse(Status::CODE_200, rawCover); + + response->putHeader(Header::CONTENT_TYPE, "image/*"); + + return response; + } + + #include OATPP_CODEGEN_END(ApiController) + private: + std::string m_exe_path; + model::BinaryPath m_bConf; + const long m_dataSize = std::numeric_limits::max(); + }; +} +#endif diff --git a/include/controller/SongController.hpp b/include/controller/SongController.hpp index 55066ef..80fa93f 100644 --- a/include/controller/SongController.hpp +++ b/include/controller/SongController.hpp @@ -153,6 +153,7 @@ namespace controller std::ostreambuf_iterator(buf)); fl.close(); + // TODO: no need to make this shared auto rawSong = std::make_shared(oatpp::String(buf.str().data(), (v_int32)buf.str().size(), true)); auto response = createResponse(Status::CODE_200, *rawSong); @@ -163,6 +164,7 @@ namespace controller // TODO: create endpoint for updating songs + // TODO: work on this to handle the database records ENDPOINT("DELETE", "api/v1/song/data/{id}", songDelete, PATH(Int32, id)) { model::Song song; diff --git a/include/dto/CoverArtDto.hpp b/include/dto/CoverArtDto.hpp new file mode 100644 index 0000000..1d8d706 --- /dev/null +++ b/include/dto/CoverArtDto.hpp @@ -0,0 +1,22 @@ +#ifndef COVERARTDTO_H_ +#define COVERARTDTO_H_ + +#include "oatpp/core/data/mapping/type/Object.hpp" +#include "oatpp/core/macro/codegen.hpp" + +namespace dto +{ + #include OATPP_CODEGEN_BEGIN(DTO) + + class CoverArtDto : public oatpp::data::mapping::type::Object + { + DTO_INIT(CoverArtDto, Object) + + DTO_FIELD(Int32, id); + DTO_FIELD(String, songTitle); + }; + + #include OATPP_CODEGEN_END(DTO) +} + +#endif diff --git a/src/Main.cpp b/src/Main.cpp index a54bc72..d9023d0 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -10,9 +10,9 @@ #include "oatpp/web/server/HttpConnectionHandler.hpp" #include "component/AppComponent.hpp" +#include "controller/CoverArtController.hpp" #include "controller/LoginController.hpp" #include "controller/SongController.hpp" -//#include "database/base_repository.h" #include "model/Models.h" namespace fs = std::filesystem; @@ -24,8 +24,11 @@ void run(const model::BinaryPath& bConf) OATPP_COMPONENT(std::shared_ptr, router); + auto coverArtController = std::make_shared(bConf); auto logController = std::make_shared(bConf); auto sngController = std::make_shared(bConf); + + coverArtController->addEndpointsToRouter(router); logController->addEndpointsToRouter(router); sngController->addEndpointsToRouter(router); diff --git a/src/database/AlbumRepository.cpp b/src/database/AlbumRepository.cpp index 2c35f84..9b89165 100644 --- a/src/database/AlbumRepository.cpp +++ b/src/database/AlbumRepository.cpp @@ -34,7 +34,7 @@ model::Album database::AlbumRepository::retrieveRecord(model::Album& album, type auto titleLength = album.title.size(); switch (filter) { case type::AlbumFilter::id: - qry << "alb.AlbumId = " << album.id; + qry << "alb.AlbumId = ?"; params[0].buffer_type = MYSQL_TYPE_LONG; params[0].buffer = (char*)&album.id; @@ -64,6 +64,7 @@ model::Album database::AlbumRepository::retrieveRecord(model::Album& album, type mysql_stmt_close(stmt); mysql_close(conn); + std::cout << "done" << std::endl; return album; diff --git a/src/database/ArtistRepository.cpp b/src/database/ArtistRepository.cpp index 2142c70..f2ca7ec 100644 --- a/src/database/ArtistRepository.cpp +++ b/src/database/ArtistRepository.cpp @@ -17,29 +17,46 @@ model::Artist database::ArtistRepository::retrieveRecord(model::Artist& artist, std::cout << "retrieving artist record" << std::endl; std::stringstream qry; auto conn = setupMysqlConnection(); + auto stmt = mysql_stmt_init(conn); qry << "SELECT art.* FROM Artist art WHERE "; - std::unique_ptr param; + MYSQL_BIND params[1]; + memset(params, 0, sizeof(params)); + + //std::unique_ptr param; + auto artistLength = artist.artist.size(); switch (filter) { case type::ArtistFilter::id: - qry << "art.ArtistId = " << artist.id; + qry << "art.ArtistId = ?"; + + params[0].buffer_type = MYSQL_TYPE_LONG; + params[0].buffer = (char*)&artist.id; + params[0].length = 0; + params[0].is_null = 0; break; case type::ArtistFilter::artist: - param = std::make_unique(new char[artist.artist.size()]); - mysql_real_escape_string(conn, *param, artist.artist.c_str(), artist.artist.size()); - qry << "art.Artist ='" << *param << "'"; + qry << "art.Artist = ?"; + //param = std::make_unique(new char[artist.artist.size()]); + //mysql_real_escape_string(conn, *param, artist.artist.c_str(), artist.artist.size()); + params[0].buffer_type = MYSQL_TYPE_STRING; + params[0].buffer = (char*)artist.artist.c_str(); + params[0].length = &artistLength; + params[0].is_null = 0; break; default: break; } - qry << " ORDER BY ArtistId DESC LIMIT 1"; + qry << " LIMIT 1"; const auto query = qry.str(); - auto results = performMysqlQuery(conn, query); + auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); + status = mysql_stmt_bind_param(stmt, params); + status = mysql_stmt_execute(stmt); - artist = parseRecord(results); + artist = parseRecord(stmt); + mysql_stmt_close(stmt); mysql_close(conn); std::cout << "retrieved record" << std::endl; @@ -154,8 +171,50 @@ model::Artist database::ArtistRepository::parseRecord(MYSQL_RES* results) model::Artist database::ArtistRepository::parseRecord(MYSQL_STMT *stmt) { - // TODO: implement this + std::cout << "parsing artist record" << std::endl; + mysql_stmt_store_result(stmt); + model::Artist art; + auto status = 0; + auto time = 0; + auto valAmt = 2; + unsigned long len[valAmt]; + my_bool nullRes[valAmt]; + + while (status == 0) { + if (mysql_stmt_field_count(stmt) > 0) { + auto res = mysql_stmt_result_metadata(stmt); + auto fields = mysql_fetch_fields(res); + auto strLen = 1024; + + MYSQL_BIND val[valAmt]; + memset(val, 0, sizeof(val)); + + char artist[strLen]; + + val[0].buffer_type = MYSQL_TYPE_LONG; + val[0].buffer = (char*)&art.id; + val[0].length = &len[0]; + val[0].is_null = &nullRes[0]; + + val[1].buffer_type = MYSQL_TYPE_STRING; + val[1].buffer = (char*)&artist; + val[1].buffer_length = strLen; + val[1].length = &len[1]; + val[1].is_null = &nullRes[1]; + + status = mysql_stmt_bind_result(stmt, val); + mysql_stmt_store_result(stmt); + + status = mysql_stmt_fetch(stmt); + + art.artist = artist; + } + + status = mysql_stmt_next_result(stmt); + } + + std::cout << "done parsing artist record" << std::endl; return art; } diff --git a/src/database/CoverArtRepository.cpp b/src/database/CoverArtRepository.cpp index cdd3eeb..8a6fba1 100644 --- a/src/database/CoverArtRepository.cpp +++ b/src/database/CoverArtRepository.cpp @@ -15,33 +15,47 @@ model::Cover database::CoverArtRepository::retrieveRecord(model::Cover& cov, typ { std::stringstream qry; auto conn = setupMysqlConnection(); + auto stmt = mysql_stmt_init(conn); + qry << "SELECT * FROM CoverArt WHERE "; - std::unique_ptr param; + MYSQL_BIND params[1]; + memset(params, 0, sizeof(params)); + + auto songTitleLength = cov.songTitle.size(); switch (filter) { case type::CoverFilter::id: - qry << "CoverArtId = " << cov.id; + qry << "CoverArtId = ?"; + + params[0].buffer_type = MYSQL_TYPE_LONG; + params[0].buffer = (char*)&cov.id; + params[0].length = 0; + params[0].is_null = 0; break; case type::CoverFilter::songTitle: - param = std::make_unique(new char[cov.songTitle.size()]); - mysql_real_escape_string(conn, *param, cov.songTitle.c_str(), cov.songTitle.size()); - qry << "SongTitle = '" << *param << "'"; + qry << "SongTitle = ?"; + + params[0].buffer_type = MYSQL_TYPE_STRING; + params[0].buffer = (char*)cov.songTitle.c_str(); + params[0].length = &songTitleLength; + params[0].is_null = 0; break; - case type::CoverFilter::imagePath: - param = std::make_unique(new char[cov.imagePath.size()]); - mysql_real_escape_string(conn, *param, cov.imagePath.c_str(), cov.imagePath.size()); - qry << "ImagePath = '" << *param << "'"; + default: break; } + qry << " LIMIT 1"; + const std::string query = qry.str(); - auto results = performMysqlQuery(conn, query); - std::cout << "the query has been performed" << std::endl; + auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); + status = mysql_stmt_bind_param(stmt, params); + status = mysql_stmt_execute(stmt); - auto covDb = parseRecord(results); + auto covDb = parseRecord(stmt); + mysql_stmt_close(stmt); mysql_close(conn); - std::cout << "done" << std::endl; + std::cout << "retrieved cover art record" << std::endl; return covDb; } @@ -97,6 +111,7 @@ bool database::CoverArtRepository::doesCoverArtExist(const model::Cover& cover, return (rowCount > 0) ? true : false; } +// TODO: change to prepared statement void database::CoverArtRepository::deleteRecord(const model::Cover& cov) { auto conn = setupMysqlConnection(); @@ -109,20 +124,17 @@ void database::CoverArtRepository::deleteRecord(const model::Cover& cov) void database::CoverArtRepository::saveRecord(const model::Cover& cov) { + std::cout << "saving cover art record"; auto conn = setupMysqlConnection(); + auto stmt = mysql_stmt_init(conn); - MYSQL_STMT *stmt; MYSQL_BIND params[2]; + memset(params, 0, sizeof(params)); my_bool isNull; - int status; - - stmt = mysql_stmt_init(conn); const std::string query = "INSERT INTO CoverArt(SongTitle, ImagePath) VALUES(?, ?)"; - status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); - - memset(params, 0, sizeof(params)); + auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); params[0].buffer_type = MYSQL_TYPE_STRING; params[0].buffer = (char*)cov.songTitle.c_str(); @@ -142,7 +154,7 @@ void database::CoverArtRepository::saveRecord(const model::Cover& cov) mysql_stmt_close(stmt); mysql_close(conn); - std::cout << "done" << std::endl; + std::cout << "saved cover art record" << std::endl; } model::Cover database::CoverArtRepository::parseRecord(MYSQL_RES *results) @@ -173,9 +185,59 @@ model::Cover database::CoverArtRepository::parseRecord(MYSQL_RES *results) model::Cover database::CoverArtRepository::parseRecord(MYSQL_STMT *stmt) { - // TODO: implement this - + std::cout << "parsing cover art record" << std::endl; + + mysql_stmt_store_result(stmt); model::Cover cover; + auto status = 0; + auto time = 0; + auto valAmt = 3; + unsigned long len[valAmt]; + my_bool nullRes[valAmt]; + + while (status == 0) { + if (mysql_stmt_field_count(stmt) > 0) { + auto res = mysql_stmt_result_metadata(stmt); + auto fields = mysql_fetch_fields(res); + auto strLen = 1024; + + MYSQL_BIND val[valAmt]; + memset(val, 0, sizeof(val)); + + char songTitle[strLen]; + char imagePath[strLen]; + + val[0].buffer_type = MYSQL_TYPE_LONG; + val[0].buffer = (char*)&cover.id; + val[0].length = &len[0]; + val[0].is_null = &nullRes[0]; + + val[1].buffer_type = MYSQL_TYPE_STRING; + val[1].buffer = (char*)&songTitle; + val[1].buffer_length = strLen; + val[1].length = &len[1]; + val[1].is_null = &nullRes[1]; + + val[2].buffer_type = MYSQL_TYPE_STRING; + val[2].buffer = (char*)&imagePath; + val[2].buffer_length = strLen; + val[2].length = &len[2]; + val[2].is_null = &nullRes[2]; + + status = mysql_stmt_bind_result(stmt, val); + mysql_stmt_store_result(stmt); + + status = mysql_stmt_fetch(stmt); + + cover.songTitle = songTitle; + cover.imagePath = imagePath; + } + + status = mysql_stmt_next_result(stmt); + } + + std::cout << "done parsing cover art record" << std::endl; + return cover; }