From eb3712753f09be4c2588a2bd9a6257b6b7b676dd Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sat, 28 Oct 2023 08:24:30 -0400 Subject: [PATCH 01/43] Fixes enum not matching UI --- atlas/ui/importer/simpleImporter/SIModel.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index a01321ed..cfc03bf9 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -13,7 +13,7 @@ enum class SupportingType { - NoSupportingType, + NoSupportingType = -1, TITLE, CREATOR, VERSION, From 9c5a9ed169bd4bcbc90f1e7205fcbee6a538284c Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sat, 28 Oct 2023 08:25:03 -0400 Subject: [PATCH 02/43] Add back basics for menu --- .../simpleImporter/SimpleImporter.cpp | 124 +++++++++++++++++- atlas/ui/mainwindow.cpp | 5 +- 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 0c3aec84..66bec3aa 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -21,7 +21,8 @@ SimpleImporter::SimpleImporter( QWidget* parent ) : QDialog( parent ), ui( new U ui->dirView->setModel( new SIModel() ); ui->dirView->setContextMenuPolicy( Qt::CustomContextMenu ); - //connect( ui->dirView, &QTreeView::customContextMenuRequested, this, &SimpleImporter::onCustomContextMenuRequested ); + connect( ui->dirView, &QTreeView::customContextMenuRequested, this, &SimpleImporter::onCustomContextMenuRequested ); + connect( ui->dirView->selectionModel(), &QItemSelectionModel::selectionChanged, @@ -53,8 +54,121 @@ void SimpleImporter::dirView_itemSelectionChanged( updateSidebar(); } +int depthOfIndex( const QModelIndex& index ) +{ + int depth { 0 }; + QModelIndex parent { index.parent() }; + while ( parent.isValid() ) + { + ++depth; + parent = parent.parent(); + } + return depth; +} + void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint& point ) -{} +{ + QMenu menu; + + const QModelIndex item { ui->dirView->indexAt( point ) }; + int idx_depth { depthOfIndex( item ) }; + + Node* node { static_cast< Node* >( item.internalPointer() ) }; + const bool is_folder { std::holds_alternative< DirInfo >( node->m_info ) }; + + menu.addSection( QString( "Depth: %1" ).arg( idx_depth ) ); + + if ( is_folder ) + { + auto* dir_info { &std::get< DirInfo >( node->m_info ) }; + + auto this_item_menu { menu.addMenu( "This Item" ) }; + + this_item_menu->addAction( + "Set nothing", + [ dir_info, &item, this ]() + { + dir_info->is_supporting_name = false; + dir_info->is_game_dir = false; + dir_info->supporting_type = SupportingType::TITLE; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + + this_item_menu->addAction( + "Set game root", + [ dir_info, &item, this ]() + { + dir_info->is_game_dir = true; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + + auto this_item_supporting_menu { this_item_menu->addMenu( "Set supporting" ) }; + this_item_supporting_menu->addAction( + "None", + [ dir_info, &item, this ]() + { + dir_info->is_supporting_name = false; + dir_info->supporting_type = SupportingType::TITLE; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + this_item_supporting_menu->addAction( + "Title", + [ dir_info, &item, this ]() + { + dir_info->is_supporting_name = true; + dir_info->supporting_type = SupportingType::TITLE; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + this_item_supporting_menu->addAction( + "Creator", + [ dir_info, &item, this ]() + { + dir_info->is_supporting_name = true; + dir_info->supporting_type = SupportingType::CREATOR; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + this_item_supporting_menu->addAction( + "Version", + [ dir_info, &item, this ]() + { + dir_info->is_supporting_name = true; + dir_info->supporting_type = SupportingType::VERSION; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + this_item_supporting_menu->addAction( + "Engine", + [ dir_info, &item, this ]() + { + dir_info->is_supporting_name = true; + dir_info->supporting_type = SupportingType::ENGINE; + this->ui->dirView->model()->dataChanged( item, item ); + } ); + + auto this_level { menu.addMenu( "This Depth" ) }; + this_level->addAction( "Set nothing", []() {} ); + this_level->addAction( "Set game root", []() {} ); + auto this_level_supporting_menu { this_level->addMenu( "Set supporting" ) }; + this_level_supporting_menu->addAction( "None", []() {} ); + this_level_supporting_menu->addAction( "Title", []() {} ); + this_level_supporting_menu->addAction( "Creator", []() {} ); + this_level_supporting_menu->addAction( "Version", []() {} ); + this_level_supporting_menu->addAction( "Engine", []() {} ); + + menu.addAction( "Set preview folder", []() {} ); + } + else + { + menu.addAction( "Set preview", []() {} ); + + auto banner_actions { menu.addMenu( "Set banner" ) }; + banner_actions->addAction( "Normal", []() {} ); + banner_actions->addAction( "Wide", []() {} ); + banner_actions->addAction( "Logo", []() {} ); + banner_actions->addAction( "Cover", []() {} ); + } + + menu.exec( QCursor::pos() ); +} std::vector< QPersistentModelIndex > SimpleImporter::selected() const { @@ -253,10 +367,10 @@ void SimpleImporter::updateSidebar() else ui->cIsGameRoot->setCheckState( Qt::Unchecked ); - if ( checked_support < count && checked_support > 0 ) - ui->cIsSupporting->setCheckState( Qt::PartiallyChecked ); - else if ( checked_support == count ) + if ( checked_support == count ) ui->cIsSupporting->setCheckState( Qt::Checked ); + else if ( checked_support > 0 ) + ui->cIsSupporting->setCheckState( Qt::PartiallyChecked ); else ui->cIsSupporting->setCheckState( Qt::Unchecked ); diff --git a/atlas/ui/mainwindow.cpp b/atlas/ui/mainwindow.cpp index 7ff0147d..529818e6 100644 --- a/atlas/ui/mainwindow.cpp +++ b/atlas/ui/mainwindow.cpp @@ -90,11 +90,12 @@ MainWindow::MainWindow( QWidget* parent ) : QMainWindow( parent ), ui( new Ui::M ui->actionCoverView->setVisible( false ); ui->actionListView->setVisible( false ); ui->actionManage->setVisible( false ); - ui->actionSimpleImporter->setVisible( false ); - ui->actionSingleImporter->setVisible( false ); ui->actionGameListImporter->setVisible( false ); ui->actionDownload->setVisible( false ); + //ui->actionSimpleImporter->setVisible( false ); + //ui->actionSingleImporter->setVisible( false ); + connect( &atlas::import::internal::getNotifier(), &atlas::import::ImportNotifier::notification, From 88fe365fc668acd3f89b0d231a812122b7514b9e Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sat, 28 Oct 2023 08:25:51 -0400 Subject: [PATCH 03/43] Add sidebar update for menu operations --- atlas/ui/importer/simpleImporter/SimpleImporter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 66bec3aa..8c532871 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -448,6 +448,7 @@ void SimpleImporter::updateSidebar() ui->stackedWidget->setCurrentIndex( BlankPage ); } + updateSidebar(); no_modification = false; } From 71e3ae3a9e43c08d1fae68ab72b351800ea51a61 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 29 Oct 2023 13:00:02 -0400 Subject: [PATCH 04/43] Setup depth based setting --- atlas/ui/importer/simpleImporter/SIModel.hpp | 78 +++++++- .../simpleImporter/SimpleImporter.cpp | 184 +++++++++++++++--- 2 files changed, 224 insertions(+), 38 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index cfc03bf9..a43738c3 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -8,6 +8,8 @@ #include #include +#include + #include "core/config/config.hpp" #include "core/logging/logging.hpp" @@ -47,7 +49,8 @@ struct Node std::variant< DirInfo, FileInfo > m_info { DirInfo {} }; QString m_path; - private: + std::vector< Node* > m_children {}; + Node* m_parent { nullptr }; bool m_scanned { false }; @@ -89,8 +92,74 @@ struct Node return 0; } + Node* root() + { + Node* ptr { this }; + if ( this->parent() == nullptr ) return ptr; + + while ( ptr->parent() != nullptr ) + { + ptr = ptr->parent(); + } + + return ptr; + } + + const Node* root() const + { + const Node* ptr { this }; + if ( this->parent() == nullptr ) return ptr; + + while ( ptr->parent() != nullptr ) + { + ptr = ptr->parent(); + } + + return ptr; + } + + int depth() const + { + int counter { 0 }; + const Node* ptr { this }; + + if ( this->parent() == nullptr ) return 0; + + while ( ptr != nullptr ) + { + ++counter; + ptr = ptr->parent(); + } + + return counter; + } + + std::vector< Node* > childrenAtDepth( const int target_depth ) + { + if ( target_depth == 0 ) + return { this }; + else if ( target_depth > 0 ) + { + if ( !m_scanned ) scan(); + + std::vector< Node* > nodes; + + for ( auto child : m_children ) + { + auto child_data { child->childrenAtDepth( target_depth - 1 ) }; + std::copy( child_data.begin(), child_data.end(), std::back_inserter( nodes ) ); + } + + return nodes; + } + else + return {}; + } + const Node* parent() const { return m_parent; } + Node* parent() { return m_parent; } + const Node* child( const int idx ) const { if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) @@ -107,15 +176,12 @@ struct Node return m_children[ static_cast< std::size_t >( idx ) ]; } + std::vector< Node* > children() const { return m_children; } + ~Node() { for ( auto& child : m_children ) delete child; } - - private: - - std::vector< Node* > m_children {}; - Node* m_parent { nullptr }; }; class SIModel final : public QAbstractItemModel diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 8c532871..5f94da34 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -56,7 +56,7 @@ void SimpleImporter::dirView_itemSelectionChanged( int depthOfIndex( const QModelIndex& index ) { - int depth { 0 }; + int depth { 1 }; QModelIndex parent { index.parent() }; while ( parent.isValid() ) { @@ -80,81 +80,201 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint if ( is_folder ) { - auto* dir_info { &std::get< DirInfo >( node->m_info ) }; + DirInfo& dir_info { std::get< DirInfo >( node->m_info ) }; auto this_item_menu { menu.addMenu( "This Item" ) }; this_item_menu->addAction( "Set nothing", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_supporting_name = false; - dir_info->is_game_dir = false; - dir_info->supporting_type = SupportingType::TITLE; + dir_info.is_supporting_name = false; + dir_info.is_game_dir = false; + dir_info.supporting_type = SupportingType::TITLE; this->ui->dirView->model()->dataChanged( item, item ); } ); this_item_menu->addAction( "Set game root", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_game_dir = true; + dir_info.is_game_dir = true; this->ui->dirView->model()->dataChanged( item, item ); } ); auto this_item_supporting_menu { this_item_menu->addMenu( "Set supporting" ) }; this_item_supporting_menu->addAction( "None", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_supporting_name = false; - dir_info->supporting_type = SupportingType::TITLE; + dir_info.is_supporting_name = false; + dir_info.supporting_type = SupportingType::TITLE; this->ui->dirView->model()->dataChanged( item, item ); } ); this_item_supporting_menu->addAction( "Title", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_supporting_name = true; - dir_info->supporting_type = SupportingType::TITLE; + dir_info.is_supporting_name = true; + dir_info.supporting_type = SupportingType::TITLE; this->ui->dirView->model()->dataChanged( item, item ); } ); this_item_supporting_menu->addAction( "Creator", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_supporting_name = true; - dir_info->supporting_type = SupportingType::CREATOR; + dir_info.is_supporting_name = true; + dir_info.supporting_type = SupportingType::CREATOR; this->ui->dirView->model()->dataChanged( item, item ); } ); this_item_supporting_menu->addAction( "Version", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_supporting_name = true; - dir_info->supporting_type = SupportingType::VERSION; + dir_info.is_supporting_name = true; + dir_info.supporting_type = SupportingType::VERSION; this->ui->dirView->model()->dataChanged( item, item ); } ); this_item_supporting_menu->addAction( "Engine", - [ dir_info, &item, this ]() + [ &dir_info, &item, this ]() { - dir_info->is_supporting_name = true; - dir_info->supporting_type = SupportingType::ENGINE; + dir_info.is_supporting_name = true; + dir_info.supporting_type = SupportingType::ENGINE; this->ui->dirView->model()->dataChanged( item, item ); } ); + Node* root { node->root() }; + auto this_level { menu.addMenu( "This Depth" ) }; - this_level->addAction( "Set nothing", []() {} ); - this_level->addAction( "Set game root", []() {} ); + this_level->addAction( + "Set nothing", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_supporting_name = false; + info.is_game_dir = false; + + info.supporting_type = SupportingType::NoSupportingType; + } + } + } ); + + this_level->addAction( + "Set game root", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_game_dir = true; + } + } + + qDebug() << "Set root for " << children.size() << " games"; + } ); + auto this_level_supporting_menu { this_level->addMenu( "Set supporting" ) }; - this_level_supporting_menu->addAction( "None", []() {} ); - this_level_supporting_menu->addAction( "Title", []() {} ); - this_level_supporting_menu->addAction( "Creator", []() {} ); - this_level_supporting_menu->addAction( "Version", []() {} ); - this_level_supporting_menu->addAction( "Engine", []() {} ); - menu.addAction( "Set preview folder", []() {} ); + this_level_supporting_menu->addAction( + "None", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_supporting_name = false; + info.supporting_type = SupportingType::NoSupportingType; + } + } + } ); + this_level_supporting_menu->addAction( + "Title", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_supporting_name = false; + info.supporting_type = SupportingType::TITLE; + } + } + } ); + this_level_supporting_menu->addAction( + "Creator", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_supporting_name = false; + info.supporting_type = SupportingType::CREATOR; + } + } + } ); + this_level_supporting_menu->addAction( + "Version", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_supporting_name = false; + info.supporting_type = SupportingType::VERSION; + } + } + } ); + this_level_supporting_menu->addAction( + "Engine", + [ idx_depth, root ]() + { + auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) + { + if ( std::holds_alternative< DirInfo >( child->m_info ) ) + { + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + info.is_supporting_name = false; + info.supporting_type = SupportingType::ENGINE; + } + } + } ); + + menu.addAction( + "Set preview folder", + [ node ]() + { + auto children { node->children() }; + + for ( auto child : children ) + { + if ( std::holds_alternative< FileInfo >( child->m_info ) ) + { + auto& info { std::get< FileInfo >( child->m_info ) }; + info.is_preview = true; + } + } + } ); } else { @@ -168,6 +288,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint } menu.exec( QCursor::pos() ); + updateSidebar(); } std::vector< QPersistentModelIndex > SimpleImporter::selected() const @@ -448,7 +569,6 @@ void SimpleImporter::updateSidebar() ui->stackedWidget->setCurrentIndex( BlankPage ); } - updateSidebar(); no_modification = false; } From 3beb16b028b5d7ff90f834be0099d464abb9534a Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sat, 18 Nov 2023 11:36:24 -0500 Subject: [PATCH 05/43] Some database code cleanup --- atlas/core/database/Binder.cpp | 2 +- atlas/core/database/Binder.hpp | 33 ++++++++++++++----- .../database/migrations/migration-run.cpp | 4 ++- atlas/core/database/record/GameData.cpp | 21 +++++++----- atlas/core/database/record/tags.cpp | 8 +++-- atlas/core/remote/AtlasRemote.cpp | 24 +++++++++++--- 6 files changed, 66 insertions(+), 26 deletions(-) diff --git a/atlas/core/database/Binder.cpp b/atlas/core/database/Binder.cpp index 983e9574..3d9df81b 100644 --- a/atlas/core/database/Binder.cpp +++ b/atlas/core/database/Binder.cpp @@ -31,4 +31,4 @@ Binder::~Binder() noexcept( false ) } sqlite3_finalize( stmt ); -} +} \ No newline at end of file diff --git a/atlas/core/database/Binder.hpp b/atlas/core/database/Binder.hpp index cf0ea8eb..b586c12a 100644 --- a/atlas/core/database/Binder.hpp +++ b/atlas/core/database/Binder.hpp @@ -87,6 +87,7 @@ class Binder return *this; } + // Feed into value directly template < typename T > requires( (!is_optional< T >) && (!is_tuple< T >)) void operator>>( T& t ) @@ -95,9 +96,13 @@ class Binder executeQuery( tpl ); - if ( tpl.has_value() ) t = std::move( std::get< 0, T >( tpl.value() ) ); + if ( tpl.has_value() ) + t = std::move( std::get< 0, T >( tpl.value() ) ); + else + throw DatabaseRowMismatch( format_ns::format( "No rows returned for query \"{}\"", sqlite3_sql( stmt ) ) ); } + // Feed output into optional template < typename T > requires( !is_optional< T > && (!is_tuple< T >)) void operator>>( std::optional< T >& t ) @@ -112,6 +117,7 @@ class Binder t = std::nullopt; } + // Call function using output template < typename Function > requires( (!is_optional< Function >) && (!is_tuple< Function >)) void operator>>( Function&& func ) @@ -129,6 +135,7 @@ class Binder } } + // Feed output into tuple template < typename... Ts > requires( !( is_optional< Ts > && ... ) ) && ( !( is_tuple< Ts > && ... ) ) void operator>>( std::tuple< Ts... >& tpl ) @@ -136,12 +143,13 @@ class Binder ran = true; std::optional< std::tuple< Ts... > > opt_tpl { std::nullopt }; - executeQuery( opt_tpl ); + executeQuery< Ts... >( opt_tpl ); if ( opt_tpl.has_value() ) - { tpl = std::move( opt_tpl.value() ); - } + else + throw DatabaseRowMismatch( "No rows returned for query" ); + return; } @@ -149,7 +157,7 @@ class Binder template < typename... Ts > requires( !( is_optional< Ts > || ... ) && !( is_tuple< Ts > || ... ) ) - void executeQuery( std::optional< std::tuple< Ts... > >& tpl_opt ) + void executeQuery( [[maybe_unused]] std::optional< std::tuple< Ts... > >& tpl_opt ) { if ( param_counter != max_param_count ) throw AtlasException( format_ns::format( @@ -158,13 +166,16 @@ class Binder max_param_count, param_counter, max_param_count, - std::string( sqlite3_sql( stmt ) ) ) ); + std::string_view( sqlite3_sql( + stmt ) ) ) ); // String view is safe here since the string is owned by sqlite3 and not freed until the statement is finalized ran = true; if ( stmt == nullptr ) throw DatabaseException( "stmt was nullptr" ); +#ifdef LOG_SQL_QUERIES atlas::logging::debug( "Executing query {}", sqlite3_expanded_sql( stmt ) ); +#endif const auto step_ret { sqlite3_step( stmt ) }; @@ -182,14 +193,18 @@ class Binder } else { - tpl_opt = std::nullopt; - return; + throw AtlasException( format_ns::format( + "No rows were expected but rows were returned for query: \"{}\". Is this intentional?", + sqlite3_expanded_sql( stmt ) ) ); } } case SQLITE_DONE: { +#ifdef LOG_SQL_QUERIES atlas::logging::debug( "Finished query {}", sqlite3_expanded_sql( stmt ) ); - tpl_opt = std::nullopt; +#endif + //Help hint to the compiler that it shouldn't keep an empty tuple around + if constexpr ( sizeof...( Ts ) > 0 ) tpl_opt = std::nullopt; return; default: diff --git a/atlas/core/database/migrations/migration-run.cpp b/atlas/core/database/migrations/migration-run.cpp index 08d617ef..4d9e3011 100644 --- a/atlas/core/database/migrations/migration-run.cpp +++ b/atlas/core/database/migrations/migration-run.cpp @@ -52,8 +52,10 @@ namespace atlas::database::migrations } int current_migration { -1 }; + std::optional< int > last_migration; RapidTransaction() << "SELECT migration_id FROM migrations ORDER BY migration_id DESC limit 1" - >> current_migration; + >> last_migration; + if ( last_migration.has_value() ) current_migration = last_migration.value(); try { diff --git a/atlas/core/database/record/GameData.cpp b/atlas/core/database/record/GameData.cpp index 22375aad..e837960c 100644 --- a/atlas/core/database/record/GameData.cpp +++ b/atlas/core/database/record/GameData.cpp @@ -44,13 +44,13 @@ namespace atlas::records RapidTransaction() << "SELECT count(*) FROM previews WHERE record_id = ?" << m_game_id >> m_preview_count; - AtlasID atlas_id { INVALID_ATLAS_ID }; + std::optional< AtlasID > atlas_id; RapidTransaction() << "SELECT atlas_id FROM atlas_mappings WHERE record_id = ? " << m_game_id >> atlas_id; - if ( atlas_id != INVALID_ATLAS_ID ) atlas_data = { atlas_id }; + if ( atlas_id.has_value() ) atlas_data = { atlas_id.value() }; - F95ID f95_id { INVALID_F95_ID }; + std::optional< F95ID > f95_id; RapidTransaction() << "SELECT f95_id FROM f95_zone_mappings WHERE record_id = ?" << m_game_id >> f95_id; - if ( f95_id != INVALID_F95_ID ) f95_data = { f95_id }; + if ( f95_id.has_value() ) f95_data = { f95_id.value() }; RapidTransaction() << "SELECT version FROM versions WHERE record_id = ?" << m_game_id >> [ & ]( const QString version ) { m_versions.emplace_back( Version( this->m_game_id, version ) ); }; @@ -67,14 +67,14 @@ namespace atlas::records { ZoneScoped; RapidTransaction transaction; - RecordID record_id { INVALID_RECORD_ID }; + std::optional< RecordID > record_id; transaction << "SELECT record_id FROM games WHERE title = ? AND creator = ? AND engine = ?" << title_in << creator_in << engine_in >> record_id; - if ( record_id != INVALID_RECORD_ID ) + if ( record_id.has_value() ) { - Game game { record_id }; + Game game { record_id.value() }; throw RecordAlreadyExists( game ); } @@ -95,14 +95,17 @@ namespace atlas::records RecordID recordID( const QString& title, const QString& creator, const QString& engine ) { ZoneScoped; - RecordID record_id { INVALID_RECORD_ID }; + std::optional< RecordID > record_id; RapidTransaction transaction; transaction << "SELECT record_id FROM games WHERE title = ? AND creator = ? AND engine = ?" << title << creator << engine >> record_id; - return record_id; + if ( record_id.has_value() ) + return record_id.value(); + else + return INVALID_RECORD_ID; } //! Helper function. Returns if a title,creator,engine combo can be found. diff --git a/atlas/core/database/record/tags.cpp b/atlas/core/database/record/tags.cpp index 2d15f40e..0c7e3c8e 100644 --- a/atlas/core/database/record/tags.cpp +++ b/atlas/core/database/record/tags.cpp @@ -32,9 +32,13 @@ namespace atlas::tags TagID resolve( const QString& str ) { - TagID tag_id { INVALID_TAG_ID }; + std::optional< TagID > tag_id; RapidTransaction() << "SELECT tag_id FROM tags WHERE tag = ?" << str >> tag_id; - return tag_id; + + if ( tag_id.has_value() ) + return tag_id.value(); + else + return INVALID_TAG_ID; } } // namespace atlas::tags \ No newline at end of file diff --git a/atlas/core/remote/AtlasRemote.cpp b/atlas/core/remote/AtlasRemote.cpp index 0131e061..ecce2fd7 100644 --- a/atlas/core/remote/AtlasRemote.cpp +++ b/atlas/core/remote/AtlasRemote.cpp @@ -79,7 +79,7 @@ namespace atlas const QString path { REMOTE "api/updates" }; atlas::logging::info( "Checking remote for updates at {}", path.toStdString() ); QNetworkRequest request { QUrl { path } }; - request.setTransferTimeout( 2000 ); + request.setTransferTimeout( 5000 ); auto* reply { m_manager.get( request ) }; connect( @@ -171,13 +171,24 @@ namespace atlas } const QByteArray response_data { reply->readAll() }; + atlas::logging::debug( "Response returned {} bytes", response_data.size() ); const QJsonDocument doc { QJsonDocument::fromJson( response_data ) }; + + if ( doc.isNull() ) + { + logging::warn( + "Failed to handle json response from {}. The json response was null!", + reply->url().path().toStdString() ); + return; + } + if ( !doc.isArray() ) { logging::warn( "Failed to handle json response from {}. The json response was not an array!", reply->url().path().toStdString() ); logging::warn( "{}", response_data.toStdString() ); + return; } const QJsonArray& array = doc.array(); @@ -196,6 +207,8 @@ namespace atlas const std::uint64_t update_time { static_cast< std::uint64_t >( obj[ "date" ].toInteger() ) }; + atlas::logging::debug( "Processing update for timestamp: {}", update_time ); + if ( update_time == 1686886200 || update_time == 1687918793 ) continue; const auto& md5_str { obj[ "md5" ].toString() }; @@ -205,11 +218,11 @@ namespace atlas memcpy( md5_data_c.data(), md5.data(), static_cast< size_t >( md5.size() ) ); const auto updates { getUpdatesList() }; - const auto it = std::find_if( + const auto it { std::find_if( updates.begin(), updates.end(), [ update_time ]( const auto& pair ) - { return pair.first == static_cast< std::uint64_t >( update_time ); } ); + { return pair.first == static_cast< std::uint64_t >( update_time ); } ) }; RapidTransaction t {}; if ( it == updates.end() ) @@ -247,6 +260,7 @@ namespace atlas for ( const auto& [ update_time, processed_time ] : updates ) { + atlas::logging::debug( "Update time: {}, processed time: {}", update_time, processed_time ); if ( processed_time != 0 ) continue; return update_time; } @@ -366,11 +380,13 @@ namespace atlas void AtlasRemote::processPendingUpdates() try { + atlas::logging::debug( "Starting to process pending updates" ); auto update_time { getNextUpdateTime() }; while ( update_time != 0 ) { - const auto path { format_ns::format( "./data/updates/{}.update", update_time ) }; + const std::filesystem::path path { format_ns::format( "./data/updates/{}.update", update_time ) }; + atlas::logging::debug( "Processing update: {}", path ); if ( !std::filesystem::exists( path ) ) return; processUpdateFile( update_time ); From 31203c4bfaccc8a568a95b60fe14ee7321e0ca70 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 14:21:59 -0500 Subject: [PATCH 06/43] Fix some of the logging --- atlas/core/logging/logging.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atlas/core/logging/logging.cpp b/atlas/core/logging/logging.cpp index 4664a0ec..1191172e 100644 --- a/atlas/core/logging/logging.cpp +++ b/atlas/core/logging/logging.cpp @@ -6,7 +6,6 @@ #include "core/config/config.hpp" - #ifdef __GNUC__ #pragma GCC diagnostic push @@ -65,6 +64,10 @@ namespace atlas::logging spdlog::set_default_logger( logger ); setFormat(); +#ifndef NDEBUG + spdlog::set_level( spdlog::level::debug ); +#endif + spdlog::enable_backtrace( 32 ); spdlog::debug( "Default logger set" ); } From d4e81487d48ad0847ac5f625ba3260fbca7eb4f1 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 15:48:49 -0500 Subject: [PATCH 07/43] Cleanup and implement various styling for flags set on an item --- atlas/ui/importer/simpleImporter/SIModel.cpp | 35 +++- atlas/ui/importer/simpleImporter/SIModel.hpp | 113 +++++++++++-- .../simpleImporter/SimpleImporter.cpp | 152 ++++++++++++++---- .../simpleImporter/SimpleImporter.hpp | 2 + .../importer/simpleImporter/SimpleImporter.ui | 104 ++++++++---- 5 files changed, 318 insertions(+), 88 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.cpp b/atlas/ui/importer/simpleImporter/SIModel.cpp index 52c47964..a48630ae 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.cpp +++ b/atlas/ui/importer/simpleImporter/SIModel.cpp @@ -85,18 +85,37 @@ int SIModel::columnCount( [[maybe_unused]] const QModelIndex& parent ) const QVariant SIModel::data( const QModelIndex& index, int role ) const { - Node* node { static_cast< Node* >( index.internalPointer() ) }; - switch ( role ) { case Qt::DisplayRole: { - const auto str { node->m_path }; - const auto itter { str.lastIndexOf( QDir::separator() ) }; - return str.mid( itter + 1 ); + const Node* const node { static_cast< Node* >( index.internalPointer() ) }; + + return node->name(); + } + case Qt::FontRole: + { + QFont font; + const Node* const node { static_cast< Node* >( index.internalPointer() ) }; + if ( node->isFolder() ) + { + const auto& dir_info { std::get< DirInfo >( node->m_info ) }; + + font.setItalic( dir_info.is_supporting_name ); + font.setBold( dir_info.is_game_dir ); + return font; + } + else if ( node->isFile() ) + { + const auto& file_info { std::get< FileInfo >( node->m_info ) }; + + font.setUnderline( file_info.is_banner || file_info.is_preview ); + } + + return font; } default: - return QVariant(); + return SIModel::data( index, role ); } } @@ -105,11 +124,11 @@ SIModel::~SIModel() delete m_root; } -Node::Node( const QString str, Node* parent, const bool scan_immediate ) : m_path( str ), m_parent( parent ) +Node::Node( const QString str, Node* parent, const bool scan_immediate ) : m_name( str ), m_parent( parent ) { if ( scan_immediate ) scan(); - QFileInfo info { str }; + const QFileInfo info { pathStr() }; if ( info.isDir() ) m_info = DirInfo(); else diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index a43738c3..5217cf9e 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -15,11 +15,19 @@ enum class SupportingType { - NoSupportingType = -1, - TITLE, - CREATOR, - VERSION, - ENGINE + TITLE = 0, + CREATOR = 1, + VERSION = 2, + ENGINE = 3 +}; + +enum SupportingMask : int +{ + NO_SUPPORTING_MASK = 0, + TITLE = 1 << 0, + CREATOR = 1 << 1, + VERSION = 1 << 2, + ENGINE = 1 << 3 }; struct DirInfo @@ -31,7 +39,8 @@ struct DirInfo QString engine { "" }; bool is_supporting_name { false }; - SupportingType supporting_type { SupportingType::NoSupportingType }; + SupportingType supporting_type { SupportingType::TITLE }; + int supporting_mask { SupportingMask::NO_SUPPORTING_MASK }; }; struct FileInfo @@ -40,6 +49,7 @@ struct FileInfo BannerType banner_type { BannerType::Normal }; bool is_preview { false }; + bool is_executable { false }; }; struct Node @@ -47,7 +57,7 @@ struct Node Q_DISABLE_COPY_MOVE( Node ) std::variant< DirInfo, FileInfo > m_info { DirInfo {} }; - QString m_path; + QString m_name; std::vector< Node* > m_children {}; Node* m_parent { nullptr }; @@ -56,23 +66,74 @@ struct Node public: + DirInfo filledInfo() const + { + if ( !std::holds_alternative< DirInfo >( m_info ) ) + return {}; + else + { + auto info { std::get< DirInfo >( m_info ) }; + + //Check for any fields populated by the parents + const Node* ptr { this }; + + while ( ptr != nullptr ) + { + if ( !std::holds_alternative< DirInfo >( ptr->m_info ) ) //The fuck? + throw std::runtime_error( "Expected dir info but got file info instead!" ); + + const auto& parent_info { std::get< DirInfo >( ptr->m_info ) }; + + if ( parent_info.is_supporting_name ) + { + switch ( parent_info.supporting_type ) + { + case SupportingType::TITLE: + info.title = ptr->name(); + info.supporting_mask |= SupportingMask::TITLE; + break; + case SupportingType::CREATOR: + info.creator = ptr->name(); + info.supporting_mask |= SupportingMask::CREATOR; + break; + case SupportingType::VERSION: + info.version = ptr->name(); + info.supporting_mask |= SupportingMask::VERSION; + break; + case SupportingType::ENGINE: + info.engine = ptr->name(); + info.supporting_mask |= SupportingMask::ENGINE; + break; + } + } + + ptr = ptr->parent(); + } + + return info; + } + } + Node( const QString str, Node* parent = nullptr, const bool scan_immediate = false ); QString name() const { - const auto split_pos { m_path.lastIndexOf( QDir::separator() ) }; - return m_path.mid( split_pos + 1 ); + return m_name; + //const auto split_pos { m_path.lastIndexOf( QDir::separator() ) }; + //return m_path.mid( split_pos + 1 ); } void scan() { - QDir dir { m_path }; - QFileInfo info { m_path }; + const QString path_str { this->pathStr() }; + QFileInfo info { path_str }; + if ( info.isFile() ) return; + QDir dir { path_str }; //Scan all files and directories for ( const auto& entry : dir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot ) ) - m_children.push_back( new Node( entry.absoluteFilePath(), this ) ); + m_children.push_back( new Node( entry.fileName(), this ) ); m_scanned = true; } @@ -89,12 +150,14 @@ struct Node return static_cast< int >( std::distance( parent_children.begin(), std::find( parent_children.begin(), parent_children.end(), this ) ) ); } + return 0; } Node* root() { Node* ptr { this }; + if ( this->parent() == nullptr ) return ptr; while ( ptr->parent() != nullptr ) @@ -176,11 +239,35 @@ struct Node return m_children[ static_cast< std::size_t >( idx ) ]; } + bool isFile() const { return std::holds_alternative< FileInfo >( m_info ); } + + bool isFolder() const { return std::holds_alternative< DirInfo >( m_info ); } + + DirInfo& dirInfo() { return std::get< DirInfo >( m_info ); } + + FileInfo& fileInfo() { return std::get< FileInfo >( m_info ); } + std::vector< Node* > children() const { return m_children; } + std::filesystem::path path() const + { + if ( m_parent == nullptr ) + return std::filesystem::path( name().toStdString() ); + else + return m_parent->path() / name().toStdString(); + } + + QString pathStr() const + { + if ( m_parent == nullptr ) + return name(); + else + return m_parent->pathStr() + QDir::separator() + name(); + } + ~Node() { - for ( auto& child : m_children ) delete child; + for ( auto child : m_children ) delete child; } }; diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 5f94da34..ae1cd0a5 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "SIModel.hpp" #include "ui_SimpleImporter.h" @@ -66,6 +67,50 @@ int depthOfIndex( const QModelIndex& index ) return depth; } +void SimpleImporter::setGameRoot( Node* node ) +{ + if ( node->isFolder() ) + { + auto& node_info { node->dirInfo() }; + node_info.is_game_dir = true; + + //Detect for any banners or preview folders + + QProgressDialog progress_dialog { "Scanning...", "", 0, 1, this }; + + progress_dialog.show(); + node->scan(); + progress_dialog.setValue( 1 ); + + auto children { node->children() }; + + progress_dialog.setMaximum( children.size() ); + + for ( auto child : children ) + { + progress_dialog.setValue( progress_dialog.value() + 1 ); + QApplication::processEvents(); + if ( child->isFolder() && child->name() == "previews" ) + { + child->scan(); + + //Take all files within that folder and mark as previews + for ( const auto& previews_children : child->children() ) + { + if ( previews_children->isFile() ) previews_children->fileInfo().is_preview = true; + } + } + else if ( child->isFile() && child->name().startsWith( "banner" ) ) + { + //If the child is a file and starts with 'banner' then mark as banner + child->fileInfo().is_banner = true; + } + } + } + else + return; +} + void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint& point ) { QMenu menu; @@ -74,13 +119,12 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint int idx_depth { depthOfIndex( item ) }; Node* node { static_cast< Node* >( item.internalPointer() ) }; - const bool is_folder { std::holds_alternative< DirInfo >( node->m_info ) }; menu.addSection( QString( "Depth: %1" ).arg( idx_depth ) ); - if ( is_folder ) + if ( node->isFolder() ) { - DirInfo& dir_info { std::get< DirInfo >( node->m_info ) }; + DirInfo& dir_info { node->dirInfo() }; auto this_item_menu { menu.addMenu( "This Item" ) }; @@ -96,9 +140,9 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint this_item_menu->addAction( "Set game root", - [ &dir_info, &item, this ]() + [ node, &item, this ]() { - dir_info.is_game_dir = true; + setGameRoot( node ); this->ui->dirView->model()->dataChanged( item, item ); } ); @@ -152,34 +196,30 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint [ idx_depth, root ]() { auto children { root->childrenAtDepth( idx_depth ) }; + for ( auto child : children ) { + QApplication::processEvents(); + if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; info.is_supporting_name = false; info.is_game_dir = false; - - info.supporting_type = SupportingType::NoSupportingType; } } } ); this_level->addAction( "Set game root", - [ idx_depth, root ]() + [ idx_depth, root, this ]() { auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - if ( std::holds_alternative< DirInfo >( child->m_info ) ) - { - DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_game_dir = true; - } + QApplication::processEvents(); + setGameRoot( child ); } - - qDebug() << "Set root for " << children.size() << " games"; } ); auto this_level_supporting_menu { this_level->addMenu( "Set supporting" ) }; @@ -191,11 +231,11 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { + QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; info.is_supporting_name = false; - info.supporting_type = SupportingType::NoSupportingType; } } } ); @@ -206,10 +246,11 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { + QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = false; + info.is_supporting_name = true; info.supporting_type = SupportingType::TITLE; } } @@ -221,10 +262,11 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { + QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = false; + info.is_supporting_name = true; info.supporting_type = SupportingType::CREATOR; } } @@ -236,10 +278,11 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { + QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = false; + info.is_supporting_name = true; info.supporting_type = SupportingType::VERSION; } } @@ -251,10 +294,11 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { + QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = false; + info.is_supporting_name = true; info.supporting_type = SupportingType::ENGINE; } } @@ -268,6 +312,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint for ( auto child : children ) { + QApplication::processEvents(); if ( std::holds_alternative< FileInfo >( child->m_info ) ) { auto& info { std::get< FileInfo >( child->m_info ) }; @@ -313,8 +358,6 @@ void SimpleImporter::on_cIsGameRoot_toggled( bool checked ) { std::get< DirInfo >( node->m_info ).is_game_dir = checked; } - - ui->dirView->model()->dataChanged( node_idx, node_idx ); } updateSidebar(); } @@ -370,6 +413,7 @@ void SimpleImporter::on_leCreator_textChanged( const QString& text ) { std::get< DirInfo >( node->m_info ).creator = text; } + ui->dirView->model()->dataChanged( node_idx, node_idx ); } updateSidebar(); } @@ -387,6 +431,7 @@ void SimpleImporter::on_leVersion_textChanged( const QString& text ) { std::get< DirInfo >( node->m_info ).version = text; } + ui->dirView->model()->dataChanged( node_idx, node_idx ); } updateSidebar(); } @@ -404,6 +449,7 @@ void SimpleImporter::on_leTitle_textChanged( const QString& text ) { std::get< DirInfo >( node->m_info ).title = text; } + ui->dirView->model()->dataChanged( node_idx, node_idx ); } updateSidebar(); } @@ -421,15 +467,27 @@ void SimpleImporter::on_leEngine_textChanged( const QString& text ) { std::get< DirInfo >( node->m_info ).engine = text; } + ui->dirView->model()->dataChanged( node_idx, node_idx ); } updateSidebar(); } void SimpleImporter::updateSidebar() { - const auto current { selected() }; no_modification = true; // Prevent the GUI slots from making changes to the record. + //Wipe UI + ui->stackedWidget->setCurrentIndex( BlankPage ); + ui->leTitle->clear(); + ui->leCreator->clear(); + ui->leEngine->clear(); + ui->leVersion->clear(); + ui->cIsGameRoot->setCheckState( Qt::Unchecked ); + ui->cIsSupporting->setCheckState( Qt::Unchecked ); + ui->cbSupportingSelection->setCurrentIndex( static_cast< int >( SupportingType::TITLE ) ); + + const std::vector< QPersistentModelIndex > current { selected() }; + //Check if current selection is just made of files or directories. if ( current.empty() ) @@ -470,6 +528,7 @@ void SimpleImporter::updateSidebar() // Count up how many are checked and how many are not. for ( const auto& idx : current ) { + QApplication::processEvents(); const Node* node { static_cast< Node* >( idx.internalPointer() ) }; if ( std::holds_alternative< DirInfo >( node->m_info ) ) { @@ -504,10 +563,12 @@ void SimpleImporter::updateSidebar() { //Only one selection if we are here. Node* node { static_cast< Node* >( current.front().internalPointer() ) }; - const auto& dir_info { std::get< DirInfo >( node->m_info ) }; - const auto& [ is_game, title, creator, version, engine, supporting, type ] { dir_info }; + const DirInfo dir_info { node->filledInfo() }; + const auto& [ is_game_dir, title, creator, version, engine, supporting, supporting_type, supporting_mask ] { + dir_info + }; - if ( is_game ) + if ( is_game_dir ) { ui->gameBasicInfo->setEnabled( true ); @@ -515,9 +576,22 @@ void SimpleImporter::updateSidebar() ui->leCreator->setText( creator ); ui->leEngine->setText( engine ); ui->leVersion->setText( version ); + + //Lock depending on mask + ui->leTitle->setEnabled( !( supporting_mask & SupportingMask::TITLE ) ); + ui->leCreator->setEnabled( !( supporting_mask & SupportingMask::CREATOR ) ); + ui->leEngine->setEnabled( !( supporting_mask & SupportingMask::ENGINE ) ); + ui->leVersion->setEnabled( !( supporting_mask & SupportingMask::VERSION ) ); + } + else + { + ui->leTitle->setEnabled( false ); + ui->leCreator->setEnabled( false ); + ui->leEngine->setEnabled( false ); + ui->leVersion->setEnabled( false ); } - if ( supporting ) ui->cbSupportingSelection->setCurrentIndex( static_cast< int >( type ) ); + if ( supporting ) ui->cbSupportingSelection->setCurrentIndex( static_cast< int >( supporting_type ) ); } } else if ( file_count > 0 && dir_count == 0 ) // Only files @@ -526,20 +600,25 @@ void SimpleImporter::updateSidebar() std::size_t checked_banner { 0 }; std::size_t checked_preview { 0 }; + std::size_t checked_executables { 0 }; std::size_t total { 0 }; for ( const auto& index : current ) { + QApplication::processEvents(); Node* node { static_cast< Node* >( index.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { const auto& file_info { std::get< FileInfo >( node->m_info ) }; if ( file_info.is_banner ) ++checked_banner; if ( file_info.is_preview ) ++checked_preview; + if ( file_info.is_executable ) ++checked_executables; ++total; } } + ui->cbBannerType->setDisabled( checked_banner < total && checked_banner > 0 ); + if ( checked_banner < total && checked_banner > 0 ) ui->cIsBanner->setCheckState( Qt::PartiallyChecked ); else if ( checked_banner == total ) @@ -554,15 +633,12 @@ void SimpleImporter::updateSidebar() else ui->cIsPreview->setCheckState( Qt::Unchecked ); - if ( total > 1 ) - ui->cbBannerType->setCurrentIndex( 0 ); + if ( checked_executables < total && checked_executables > 0 ) + ui->cbIsExecutable->setCheckState( Qt::PartiallyChecked ); + else if ( checked_executables == total ) + ui->cbIsExecutable->setCheckState( Qt::Checked ); else - { - Node* first { static_cast< Node* >( current.front().internalPointer() ) }; - const auto& file_info { std::get< FileInfo >( first->m_info ) }; - const auto& [ is_banner, type, is_preview ] { file_info }; - if ( is_banner ) ui->cbBannerType->setCurrentIndex( static_cast< int >( type ) ); - } + ui->cbIsExecutable->setCheckState( Qt::Unchecked ); } else // Mix { @@ -582,12 +658,14 @@ void SimpleImporter::on_cIsBanner_toggled( bool checked ) for ( const auto& index : current ) { + QApplication::processEvents(); Node* node { static_cast< Node* >( index.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { auto& file_info { std::get< FileInfo >( node->m_info ) }; file_info.is_banner = checked; } + ui->dirView->model()->dataChanged( index, index ); } } @@ -601,12 +679,14 @@ void SimpleImporter::on_cIsPreview_toggled( bool checked ) for ( const auto& index : current ) { + QApplication::processEvents(); Node* node { static_cast< Node* >( index.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { auto& file_info { std::get< FileInfo >( node->m_info ) }; file_info.is_preview = checked; } + ui->dirView->model()->dataChanged( index, index ); } } @@ -620,11 +700,13 @@ void SimpleImporter::on_cbBannerType_currentIndexChanged( int index ) for ( const auto& node_idx : current ) { + QApplication::processEvents(); Node* node { static_cast< Node* >( node_idx.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { auto& file_info { std::get< FileInfo >( node->m_info ) }; file_info.banner_type = static_cast< BannerType >( index ); } + ui->dirView->model()->dataChanged( node_idx, node_idx ); } } diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.hpp b/atlas/ui/importer/simpleImporter/SimpleImporter.hpp index f17a0380..cfd5fac4 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.hpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.hpp @@ -41,6 +41,8 @@ class SimpleImporter final : public QDialog bool no_modification { false }; + void setGameRoot( Node* node ); + private slots: void onCustomContextMenuRequested( const QPoint& point ); void dirView_itemSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected ); diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.ui b/atlas/ui/importer/simpleImporter/SimpleImporter.ui index b37cb508..37a04a22 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.ui +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.ui @@ -13,8 +13,38 @@ SimpleImporter - - + + + + + false + + + Import + + + + + + + Reselect root + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + Qt::Horizontal @@ -83,6 +113,9 @@ + + 0 + Title @@ -188,36 +221,6 @@ - - - - Is Preview - - - true - - - - - - - - - - Qt::AlignBottom|Qt::AlignHCenter - - - - - - - Is Banner - - - true - - - @@ -242,6 +245,43 @@ + + + + + + + Qt::AlignBottom|Qt::AlignHCenter + + + + + + + Is Banner + + + false + + + + + + + Is Preview + + + false + + + + + + + Is Executable + + + From 92224a60f26dcfa6660dc3f62de59725f936a47b Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 18:23:29 -0500 Subject: [PATCH 08/43] Hopefully fixes logging for windows --- atlas/core/logging/logging.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/atlas/core/logging/logging.cpp b/atlas/core/logging/logging.cpp index 1191172e..44fef22d 100644 --- a/atlas/core/logging/logging.cpp +++ b/atlas/core/logging/logging.cpp @@ -18,7 +18,7 @@ #pragma GCC diagnostic ignored "-Wsuggest-final-methods" #endif -#include +#include #include #include @@ -43,14 +43,10 @@ namespace atlas::logging #endif // file sink will print out to a log file and rotate it out when getting too big. - auto file_sink { - std::make_shared< spdlog::sinks::rotating_file_sink_mt >( "./data/logs/log.txt", 1024 * 1024 * 1, 3 ) - }; + auto file_sink { std::make_shared< spdlog::sinks::basic_file_sink_mt >( "./data/logs/log.txt" ) }; file_sink->set_level( spdlog::level::info ); - auto error_file_sink { - std::make_shared< spdlog::sinks::rotating_file_sink_mt >( "./data/logs/error.txt", 1024 * 1024 * 1, 3 ) - }; + auto error_file_sink { std::make_shared< spdlog::sinks::basic_file_sink_mt >( "./data/logs/error.txt" ) }; error_file_sink->set_level( spdlog::level::err ); //TODO: Hook into UI and make a sink for spdlog to output into. @@ -61,7 +57,7 @@ namespace atlas::logging logger->debug( "Logger setup" ); - spdlog::set_default_logger( logger ); + spdlog::set_default_logger( std::move( logger ) ); setFormat(); #ifndef NDEBUG From 3dd41213658bea65a165d4d30b6de251379463b6 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 18:39:01 -0500 Subject: [PATCH 09/43] Fixes update time being wrong --- atlas/core/remote/AtlasRemote.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/atlas/core/remote/AtlasRemote.cpp b/atlas/core/remote/AtlasRemote.cpp index ecce2fd7..ebb8a6cb 100644 --- a/atlas/core/remote/AtlasRemote.cpp +++ b/atlas/core/remote/AtlasRemote.cpp @@ -402,15 +402,16 @@ namespace atlas void AtlasRemote::markComplete( const std::uint64_t update_time, const bool yes ) { - RapidTransaction() - << "UPDATE updates SET processed_time = ? WHERE update_time = ?" - << ( yes ? std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::steady_clock::now() - .time_since_epoch() ) - .count() : - 0 ) - << update_time; - - atlas::logging::info( "Processed update for time {}", update_time ); + const std::uint64_t update_now { + static_cast< uint64_t >( std::chrono::duration_cast< + std::chrono::seconds >( std::chrono::system_clock::now().time_since_epoch() ) + .count() ) + }; + + RapidTransaction() << "UPDATE updates SET processed_time = ? WHERE update_time = ?" << ( yes ? update_now : 0 ) + << update_time; + + atlas::logging::info( "Processed update for time {} at {}", update_time, update_now ); } void AtlasRemote::handleManifestError( QNetworkReply::NetworkError error, QNetworkReply* reply ) From 3c17a8e567c6429401f86982f6be203d0927c8a3 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 18:50:44 -0500 Subject: [PATCH 10/43] Adds some missing error and null checks --- atlas/ui/importer/simpleImporter/SIModel.cpp | 60 +++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.cpp b/atlas/ui/importer/simpleImporter/SIModel.cpp index a48630ae..b2dd00fe 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.cpp +++ b/atlas/ui/importer/simpleImporter/SIModel.cpp @@ -34,6 +34,12 @@ QModelIndex SIModel::index( int row, int column, const QModelIndex& parent_idx ) if ( !parent_idx.isValid() ) { + if ( m_root == nullptr ) + { + atlas::logging::error( "m_root is nullptr" ); + return QModelIndex(); + } + const Node* child { m_root->child( row ) }; if ( child ) @@ -44,6 +50,12 @@ QModelIndex SIModel::index( int row, int column, const QModelIndex& parent_idx ) else { const Node* parent { static_cast< Node* >( parent_idx.internalPointer() ) }; + if ( parent == nullptr ) + { + atlas::logging::error( "parent is nullptr" ); + return QModelIndex(); + } + const Node* child { parent->child( row ) }; if ( child ) return createIndex( row, column, child ); @@ -57,8 +69,21 @@ QModelIndex SIModel::parent( const QModelIndex& index ) const if ( !index.isValid() ) return QModelIndex(); const Node* child { static_cast< Node* >( index.internalPointer() ) }; + + if ( child == nullptr ) + { + atlas::logging::error( "child parent is nullptr" ); + return QModelIndex(); + } + const Node* parent { child->parent() }; + if ( parent == nullptr ) + { + atlas::logging::error( "parent is nullptr" ); + return QModelIndex(); + } + if ( parent == m_root ) return QModelIndex(); return createIndex( parent->row(), 0, parent ); @@ -69,10 +94,23 @@ int SIModel::rowCount( const QModelIndex& index ) const if ( index.column() > 0 ) return 0; if ( !index.isValid() ) - return m_root->childCount(); + { + if ( m_root == nullptr ) + { + atlas::logging::error( "m_root is nullptr" ); + return 0; + } + else + return m_root->childCount(); + } else { Node* ptr { static_cast< Node* >( index.internalPointer() ) }; + if ( ptr == nullptr ) + { + atlas::logging::error( "ptr is nullptr" ); + return 0; + } if ( !ptr->scanned() ) ptr->scan(); return ptr->childCount(); } @@ -91,12 +129,25 @@ QVariant SIModel::data( const QModelIndex& index, int role ) const { const Node* const node { static_cast< Node* >( index.internalPointer() ) }; + if ( node == nullptr ) + { + atlas::logging::error( "node is nullptr" ); + return {}; + } + return node->name(); } case Qt::FontRole: { QFont font; const Node* const node { static_cast< Node* >( index.internalPointer() ) }; + + if ( node == nullptr ) + { + atlas::logging::error( "node is nullptr" ); + return {}; + } + if ( node->isFolder() ) { const auto& dir_info { std::get< DirInfo >( node->m_info ) }; @@ -115,13 +166,16 @@ QVariant SIModel::data( const QModelIndex& index, int role ) const return font; } default: - return SIModel::data( index, role ); + return {}; } } SIModel::~SIModel() { - delete m_root; + if ( m_root == nullptr ) + return; + else + delete m_root; } Node::Node( const QString str, Node* parent, const bool scan_immediate ) : m_name( str ), m_parent( parent ) From f5afee8c0835df2c7d0cdaa95f53eb9504bff80b Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 18:58:23 -0500 Subject: [PATCH 11/43] Add some more logging stuff to SIModel --- atlas/ui/importer/simpleImporter/SIModel.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 5217cf9e..5f53f3a2 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -226,7 +226,11 @@ struct Node const Node* child( const int idx ) const { if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) + { + atlas::logging:: + error( "Tried to access child at index {} but there are only {} children", idx, m_children.size() ); return nullptr; + } else return m_children[ static_cast< std::size_t >( idx ) ]; } @@ -234,7 +238,11 @@ struct Node Node* child( const int idx ) { if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) + { + atlas::logging:: + error( "Tried to access child at index {} but there are only {} children", idx, m_children.size() ); return nullptr; + } else return m_children[ static_cast< std::size_t >( idx ) ]; } From 12edebddef2643cdfcd39e149965afb76a46aed0 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 19 Nov 2023 20:54:23 -0500 Subject: [PATCH 12/43] Remove intermediate processing of QApplication --- .../simpleImporter/SimpleImporter.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index ae1cd0a5..4d5e3df2 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -69,7 +69,7 @@ int depthOfIndex( const QModelIndex& index ) void SimpleImporter::setGameRoot( Node* node ) { - if ( node->isFolder() ) + if ( node && node->isFolder() ) { auto& node_info { node->dirInfo() }; node_info.is_game_dir = true; @@ -89,7 +89,7 @@ void SimpleImporter::setGameRoot( Node* node ) for ( auto child : children ) { progress_dialog.setValue( progress_dialog.value() + 1 ); - QApplication::processEvents(); + //QApplication::processEvents(); if ( child->isFolder() && child->name() == "previews" ) { child->scan(); @@ -199,7 +199,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { @@ -217,7 +217,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); setGameRoot( child ); } } ); @@ -231,7 +231,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; @@ -246,7 +246,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; @@ -262,7 +262,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; @@ -278,7 +278,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; @@ -294,7 +294,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint auto children { root->childrenAtDepth( idx_depth ) }; for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< DirInfo >( child->m_info ) ) { DirInfo& info { std::get< DirInfo >( child->m_info ) }; @@ -312,7 +312,7 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint for ( auto child : children ) { - QApplication::processEvents(); + // QApplication::processEvents(); if ( std::holds_alternative< FileInfo >( child->m_info ) ) { auto& info { std::get< FileInfo >( child->m_info ) }; @@ -528,7 +528,7 @@ void SimpleImporter::updateSidebar() // Count up how many are checked and how many are not. for ( const auto& idx : current ) { - QApplication::processEvents(); + // QApplication::processEvents(); const Node* node { static_cast< Node* >( idx.internalPointer() ) }; if ( std::holds_alternative< DirInfo >( node->m_info ) ) { @@ -605,7 +605,7 @@ void SimpleImporter::updateSidebar() for ( const auto& index : current ) { - QApplication::processEvents(); + // QApplication::processEvents(); Node* node { static_cast< Node* >( index.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { @@ -658,7 +658,7 @@ void SimpleImporter::on_cIsBanner_toggled( bool checked ) for ( const auto& index : current ) { - QApplication::processEvents(); + // QApplication::processEvents(); Node* node { static_cast< Node* >( index.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { @@ -679,7 +679,7 @@ void SimpleImporter::on_cIsPreview_toggled( bool checked ) for ( const auto& index : current ) { - QApplication::processEvents(); + // QApplication::processEvents(); Node* node { static_cast< Node* >( index.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { @@ -700,7 +700,7 @@ void SimpleImporter::on_cbBannerType_currentIndexChanged( int index ) for ( const auto& node_idx : current ) { - QApplication::processEvents(); + // QApplication::processEvents(); Node* node { static_cast< Node* >( node_idx.internalPointer() ) }; if ( std::holds_alternative< FileInfo >( node->m_info ) ) { From 5a2d060bd7e845a2847d4410fcbaa84da8af5d80 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Mon, 20 Nov 2023 14:35:31 -0500 Subject: [PATCH 13/43] Fixup a bunch of direct insertions failing due to 0 rows --- atlas/core/database/Binder.cpp | 3 +-- atlas/core/database/record/game/previews.cpp | 4 +++- atlas/core/database/remote/AtlasData.cpp | 7 +++++-- atlas/core/database/remote/F95Data.cpp | 4 ++-- atlas/core/import/GameScanner.cpp | 4 ++-- atlas/ui/importer/simpleImporter/SimpleImporter.cpp | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/atlas/core/database/Binder.cpp b/atlas/core/database/Binder.cpp index 3d9df81b..79165e8a 100644 --- a/atlas/core/database/Binder.cpp +++ b/atlas/core/database/Binder.cpp @@ -23,9 +23,8 @@ Binder::Binder( const std::string_view sql ) Binder::~Binder() noexcept( false ) { - if ( !ran ) [[unlikely]] + if ( !ran ) { - atlas::logging::debug( "Binder falloff. Running query" ); std::optional< std::tuple<> > tpl; executeQuery( tpl ); } diff --git a/atlas/core/database/record/game/previews.cpp b/atlas/core/database/record/game/previews.cpp index 4affbb98..ffa8d1be 100644 --- a/atlas/core/database/record/game/previews.cpp +++ b/atlas/core/database/record/game/previews.cpp @@ -54,9 +54,11 @@ namespace atlas::records //Get the highest position if ( index == 0 ) { + std::optional< std::uint64_t > result; RapidTransaction() << "SELECT position FROM previews WHERE record_id = ? ORDER BY position DESC LIMIT 1" << m_id - >> index; + >> result; + if ( result.has_value() ) index = result.value(); } RapidTransaction() << "INSERT INTO previews (record_id, path, position) VALUES (?,?,?) ON CONFLICT DO NOTHING" diff --git a/atlas/core/database/remote/AtlasData.cpp b/atlas/core/database/remote/AtlasData.cpp index 8c186611..4f3b08ff 100644 --- a/atlas/core/database/remote/AtlasData.cpp +++ b/atlas/core/database/remote/AtlasData.cpp @@ -118,9 +118,12 @@ namespace atlas::remote [[nodiscard]] AtlasID atlasIDFromF95Thread( const F95ID thread_id ) { - AtlasID id { INVALID_ATLAS_ID }; + std::optional< AtlasID > id; RapidTransaction() << "SELECT atlas_id FROM f95_zone_data WHERE f95_id = ?" << thread_id >> id; - return id; + if ( id.has_value() ) + return id.value(); + else + return INVALID_ATLAS_ID; } //Test functions diff --git a/atlas/core/database/remote/F95Data.cpp b/atlas/core/database/remote/F95Data.cpp index c77feda2..131fff58 100644 --- a/atlas/core/database/remote/F95Data.cpp +++ b/atlas/core/database/remote/F95Data.cpp @@ -78,9 +78,9 @@ namespace atlas::remote bool hasF95DataFor( const F95ID f95_id ) { - F95ID id { INVALID_F95_ID }; + std::optional< F95ID > id; RapidTransaction() << "SELECT f95_id FROM f95_zone_data WHERE f95_id = ?" << f95_id >> id; - return id != INVALID_F95_ID; + return id.has_value(); } void createDummyF95Record( const F95ID f95_id ) diff --git a/atlas/core/import/GameScanner.cpp b/atlas/core/import/GameScanner.cpp index f4b66a18..560b37c6 100644 --- a/atlas/core/import/GameScanner.cpp +++ b/atlas/core/import/GameScanner.cpp @@ -223,9 +223,9 @@ try TracyCZoneEnd( regex_Tracy ); //Is the directory we just found already in the database? - RecordID path_id { INVALID_RECORD_ID }; + std::optional< RecordID > path_id; RapidTransaction() << "SELECT record_id FROM versions WHERE game_path = ?" << itter->path() >> path_id; - if ( path_id != INVALID_RECORD_ID ) continue; + if ( path_id.has_value() ) continue; if ( result ) { diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 4d5e3df2..e4fc1524 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -84,7 +84,7 @@ void SimpleImporter::setGameRoot( Node* node ) auto children { node->children() }; - progress_dialog.setMaximum( children.size() ); + progress_dialog.setMaximum( static_cast< int >( children.size() ) ); for ( auto child : children ) { From 1dda154d40e9bb11b6aa467555fec1d70f4a7865 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Tue, 21 Nov 2023 15:10:44 -0500 Subject: [PATCH 14/43] Cleanup Binder's dtor and ctor --- atlas/core/database/Binder.cpp | 27 +++++++++++++++++++++------ atlas/core/database/Binder.hpp | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/atlas/core/database/Binder.cpp b/atlas/core/database/Binder.cpp index 79165e8a..4d5f77ad 100644 --- a/atlas/core/database/Binder.cpp +++ b/atlas/core/database/Binder.cpp @@ -12,6 +12,10 @@ Binder::Binder( const std::string_view sql ) sqlite3_prepare_v2( &Database::ref(), sql.data(), static_cast< int >( sql.size() + 1 ), &stmt, nullptr ) }; + if ( stmt == nullptr ) + throw DatabaseException( format_ns:: + format( "Failed to prepare stmt, {}", sqlite3_errmsg( &Database::ref() ) ) ); + if ( prepare_ret != SQLITE_OK ) { throw DatabaseException( format_ns::format( @@ -21,13 +25,24 @@ Binder::Binder( const std::string_view sql ) max_param_count = sqlite3_bind_parameter_count( stmt ); } -Binder::~Binder() noexcept( false ) +Binder::~Binder() { - if ( !ran ) + try { - std::optional< std::tuple<> > tpl; - executeQuery( tpl ); - } + if ( !ran ) + { + std::optional< std::tuple<> > tpl; + executeQuery( tpl ); + } - sqlite3_finalize( stmt ); + sqlite3_finalize( stmt ); + } + catch ( std::exception& e ) + { + atlas::logging::critical( "Binder's dtor has thrown!, {}", e.what() ); + } + catch ( ... ) + { + atlas::logging::critical( "Binder's dtor has thrown!, ..." ); + } } \ No newline at end of file diff --git a/atlas/core/database/Binder.hpp b/atlas/core/database/Binder.hpp index b586c12a..cdf4bf70 100644 --- a/atlas/core/database/Binder.hpp +++ b/atlas/core/database/Binder.hpp @@ -226,7 +226,7 @@ class Binder public: - ~Binder() noexcept( false ); + ~Binder(); }; #endif //ATLASGAMEMANAGER_BINDER_HPP From cd0364d0346e1ce68e5c3d2ead4bad402c3ab506 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Wed, 22 Nov 2023 19:30:41 -0500 Subject: [PATCH 15/43] Fixup database to use views when providing strings. Also moves to preventing std::string and moves to recomending std::string_view instead. Also prevents non-movable types --- atlas/core/database/extractors.hpp | 56 ++++++++++++++++++++++-------- atlas/core/images/blurhash.cpp | 2 +- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/atlas/core/database/extractors.hpp b/atlas/core/database/extractors.hpp index 507f3d73..18e919de 100644 --- a/atlas/core/database/extractors.hpp +++ b/atlas/core/database/extractors.hpp @@ -12,6 +12,20 @@ template < std::uint64_t, typename T > void extract( sqlite3_stmt*, T& ) = delete; +template < std::uint64_t index, typename T > + requires std::same_as< std::string, T > +void extract( [[maybe_unused]] sqlite3_stmt* stmt, [[maybe_unused]] std::string& t ) noexcept +{ + static_assert( false, "You should use std::string_view instead of std::string to reduce the need to memove/copy" ); +} + +template < std::uint64_t index, typename T > + requires( !std::move_constructible< T > ) +void extract( [[maybe_unused]] sqlite3_stmt* stmt, [[maybe_unused]] std::string& t ) noexcept +{ + static_assert( false, "T is not move constructable" ); +} + template < std::uint64_t index, typename T > requires std::is_integral_v< T > void extract( sqlite3_stmt* stmt, T& t ) noexcept @@ -34,8 +48,8 @@ void extract( sqlite3_stmt* stmt, T& t ) noexcept } template < std::uint64_t index, typename T > - requires std::is_same_v< T, std::u8string > -void extract( sqlite3_stmt* stmt, std::u8string& t ) noexcept + requires std::is_same_v< T, std::u8string_view > +void extract( sqlite3_stmt* stmt, std::u8string_view& t ) noexcept { const unsigned char* const txt { sqlite3_column_text( stmt, index ) }; @@ -47,16 +61,20 @@ void extract( sqlite3_stmt* stmt, std::u8string& t ) noexcept else { const auto len { strlen( reinterpret_cast< const char* const >( txt ) ) }; - t = { reinterpret_cast< const char8_t* >( txt ), len }; + t = std::u8string_view( reinterpret_cast< const char8_t* const >( txt ), len ); return; } } template < std::uint64_t index, typename T > - requires std::is_same_v< T, QString > -void extract( sqlite3_stmt* stmt, QString& t ) noexcept + requires std::is_same_v< T, std::string_view > +void extract( sqlite3_stmt* stmt, std::string_view& t ) noexcept { +#ifdef __linux__ const unsigned char* const txt { sqlite3_column_text( stmt, index ) }; +#else + const unsigned char* const txt { sqlite3_column_text16( stmt, index ) }; +#endif if ( txt == nullptr ) { @@ -65,7 +83,8 @@ void extract( sqlite3_stmt* stmt, QString& t ) noexcept } else { - t = QString::fromUtf8( txt ); + const auto len { strlen( reinterpret_cast< const char* const >( txt ) ) }; + t = std::string_view( reinterpret_cast< const char* const >( txt ), len ); return; } } @@ -74,18 +93,27 @@ template < std::uint64_t index, typename T > requires std::is_same_v< T, std::filesystem::path > void extract( sqlite3_stmt* stmt, std::filesystem::path& t ) noexcept { - std::u8string str; - extract< index, std::u8string >( stmt, str ); - t = { std::move( str ) }; + std::string_view path_str; + extract< index, std::string_view >( stmt, path_str ); + t = path_str; } template < std::uint64_t index, typename T > - requires std::is_same_v< T, std::string > -void extract( sqlite3_stmt* stmt, std::string& t ) noexcept + requires std::is_same_v< T, QString > +void extract( sqlite3_stmt* stmt, QString& t ) noexcept { - QString str; - extract< index, QString >( stmt, str ); - t = { str.toStdString() }; + const unsigned char* const txt { sqlite3_column_text( stmt, index ) }; + + if ( txt == nullptr ) + { + t = {}; + return; + } + else + { + t = QString::fromUtf8( txt ); + return; + } } template < std::uint64_t index, typename T > diff --git a/atlas/core/images/blurhash.cpp b/atlas/core/images/blurhash.cpp index dc286590..687a4383 100644 --- a/atlas/core/images/blurhash.cpp +++ b/atlas/core/images/blurhash.cpp @@ -106,7 +106,7 @@ namespace atlas::images RapidTransaction() << "SELECT blurhash, image_height, image_width FROM image_blurhash WHERE image_sha256 = ?" << path.stem().u8string() - >> [ & ]( const std::string hash, int height, int width ) + >> [ & ]( const std::string_view hash, int height, int width ) { blur_hash = hash; image_height = height; From d63061a2a983a26a54b9a57e67274492b2947651 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Wed, 22 Nov 2023 19:39:38 -0500 Subject: [PATCH 16/43] Hide debug message templates behind !NDEBUG --- atlas/core/database/extractors.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/atlas/core/database/extractors.hpp b/atlas/core/database/extractors.hpp index 18e919de..90413e93 100644 --- a/atlas/core/database/extractors.hpp +++ b/atlas/core/database/extractors.hpp @@ -12,6 +12,8 @@ template < std::uint64_t, typename T > void extract( sqlite3_stmt*, T& ) = delete; +#ifndef NDEBUG + template < std::uint64_t index, typename T > requires std::same_as< std::string, T > void extract( [[maybe_unused]] sqlite3_stmt* stmt, [[maybe_unused]] std::string& t ) noexcept @@ -26,6 +28,8 @@ void extract( [[maybe_unused]] sqlite3_stmt* stmt, [[maybe_unused]] std::string& static_assert( false, "T is not move constructable" ); } +#endif + template < std::uint64_t index, typename T > requires std::is_integral_v< T > void extract( sqlite3_stmt* stmt, T& t ) noexcept From b6879a7f63ad3544f7a725f96f3b108f1c9d677d Mon Sep 17 00:00:00 2001 From: kj16609 Date: Wed, 22 Nov 2023 19:57:07 -0500 Subject: [PATCH 17/43] Fixes fpermissive error on windows build and removes templates that should not be instantiated on linux --- atlas/core/database/extractors.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/atlas/core/database/extractors.hpp b/atlas/core/database/extractors.hpp index 90413e93..bb082d53 100644 --- a/atlas/core/database/extractors.hpp +++ b/atlas/core/database/extractors.hpp @@ -12,8 +12,7 @@ template < std::uint64_t, typename T > void extract( sqlite3_stmt*, T& ) = delete; -#ifndef NDEBUG - +/* template < std::uint64_t index, typename T > requires std::same_as< std::string, T > void extract( [[maybe_unused]] sqlite3_stmt* stmt, [[maybe_unused]] std::string& t ) noexcept @@ -27,8 +26,7 @@ void extract( [[maybe_unused]] sqlite3_stmt* stmt, [[maybe_unused]] std::string& { static_assert( false, "T is not move constructable" ); } - -#endif +*/ template < std::uint64_t index, typename T > requires std::is_integral_v< T > @@ -77,7 +75,7 @@ void extract( sqlite3_stmt* stmt, std::string_view& t ) noexcept #ifdef __linux__ const unsigned char* const txt { sqlite3_column_text( stmt, index ) }; #else - const unsigned char* const txt { sqlite3_column_text16( stmt, index ) }; + const unsigned char* const txt { reinterpret_cast< const unsigned char* >( sqlite3_column_text16( stmt, index ) ) }; #endif if ( txt == nullptr ) From 85fca4b02353d3db13ec040ce5ee097a871978c4 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 00:38:15 -0500 Subject: [PATCH 18/43] Add back in the processEvents in between scanning for children in the root directory on first load --- atlas/ui/importer/simpleImporter/SimpleImporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index e4fc1524..e3783d38 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -89,7 +89,7 @@ void SimpleImporter::setGameRoot( Node* node ) for ( auto child : children ) { progress_dialog.setValue( progress_dialog.value() + 1 ); - //QApplication::processEvents(); + QApplication::processEvents( QEventLoop::AllEvents, 25 ); if ( child->isFolder() && child->name() == "previews" ) { child->scan(); From aebd6a5e541ed8680d10684631af0ceb1f551529 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 00:47:48 -0500 Subject: [PATCH 19/43] Fixes ENIGNES_END not being at the bottom --- atlas/core/utils/engineDetection/engineDetection.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/atlas/core/utils/engineDetection/engineDetection.hpp b/atlas/core/utils/engineDetection/engineDetection.hpp index 9598dea3..3b6d2b49 100644 --- a/atlas/core/utils/engineDetection/engineDetection.hpp +++ b/atlas/core/utils/engineDetection/engineDetection.hpp @@ -35,12 +35,13 @@ enum Engine : int HTML, QSP, BAT, - ENGINES_END, MonoGame, GamesforLive, XNA, Adobe_AIR, - UNKNOWN + + ENGINES_END, // This stays at the bottom + UNKNOWN, // This stays under ENGINES_END }; //! Function to be specialized for each Engine to return true if the engine is valid. From befa6ee6fc903eb17df115dc51791916f90ec07e Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 00:56:59 -0500 Subject: [PATCH 20/43] Fixes other issues in engine detection --- .../utils/engineDetection/engineDetection.cpp | 33 +++++++++++-------- .../utils/engineDetection/engineDetection.hpp | 6 ++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/atlas/core/utils/engineDetection/engineDetection.cpp b/atlas/core/utils/engineDetection/engineDetection.cpp index 956b5bd8..3a482ca8 100644 --- a/atlas/core/utils/engineDetection/engineDetection.cpp +++ b/atlas/core/utils/engineDetection/engineDetection.cpp @@ -28,6 +28,12 @@ bool isEngineT< ENGINES_BEGIN >( [[maybe_unused]] atlas::utils::FileScanner& sca return false; } +template <> +bool isEngineT< Engine::UNKNOWN >( [[maybe_unused]] atlas::utils::FileScanner& scanner ) +{ + return true; +} + template <> bool isEngineT< ENGINES_END >( [[maybe_unused]] atlas::utils::FileScanner& scanner ) { @@ -74,13 +80,12 @@ std::vector< std::filesystem::path > detectExecutables( atlas::utils::FileScanne { ZoneScoped; std::vector< std::filesystem::path > potential_executables; - std::vector< std::string > extensions { ".exe", ".html", ".sh", ".swf", ".flv", ".jar", ".qsp", ".bat", ".rag" }; + const std::vector< std::string_view > extensions { ".exe", ".html", ".sh", ".swf", ".flv", + ".jar", ".qsp", ".bat", ".rag" }; //Check for a valid game executable in the folder for ( const auto& [ filename, ext, path, size, depth, relative ] : scanner ) { - ZoneScopedN( "Process file" ); - if ( depth > 1 ) break; if ( std::filesystem::is_regular_file( path ) ) @@ -165,7 +170,6 @@ std::vector< std::filesystem::path > detectExecutables( atlas::utils::FileScanne std::vector< std::filesystem::path > scoreExecutables( std::vector< std::filesystem::path > paths, [[maybe_unused]] const Engine engine_type ) { - ZoneScoped; std::vector< std::pair< std::filesystem::path, int > > execs; for ( auto& path : paths ) @@ -504,9 +508,9 @@ QString engineNameT< BAT >() bool checkEngineType( std::string engine, atlas::utils::FileScanner& scanner ) { //get current directory - bool isEngine = false; - std::filesystem::path engine_path = - std::filesystem::current_path() / "engine" / "types" / ( "Engine." + engine + ".txt" ); + bool isEngine { false }; + const std::filesystem::path engine_path { std::filesystem::current_path() / "engine" / "types" + / ( "Engine." + engine + ".txt" ) }; if ( std::ifstream ifs( engine_path ); ifs ) { @@ -516,9 +520,9 @@ bool checkEngineType( std::string engine, atlas::utils::FileScanner& scanner ) while ( getline( ifs, line ) ) { //Check if first item in string is a period for a file type - std::vector< char > charArry; - std::copy( line.begin(), line.end(), std::back_inserter( charArry ) ); - if ( charArry[ 0 ] == '.' ) //file type check + //std::vector< char > charArry; + //std::copy( line.begin(), line.end(), std::back_inserter( charArry ) ); + if ( line[ 0 ] == '.' ) //file type check { //Go through all files and check if extention exist for ( const auto& file : scanner ) @@ -534,7 +538,7 @@ bool checkEngineType( std::string engine, atlas::utils::FileScanner& scanner ) else { //Check if there is a / at begining of string. add if missing - if ( charArry[ 0 ] != '/' ) + if ( line[ 0 ] != '/' ) { line = "\\" + line; } @@ -552,8 +556,9 @@ bool checkEngineType( std::string engine, atlas::utils::FileScanner& scanner ) } } }; - ifs.close(); - } - return isEngine; + return isEngine; + } + else + return false; } \ No newline at end of file diff --git a/atlas/core/utils/engineDetection/engineDetection.hpp b/atlas/core/utils/engineDetection/engineDetection.hpp index 3b6d2b49..67c06ede 100644 --- a/atlas/core/utils/engineDetection/engineDetection.hpp +++ b/atlas/core/utils/engineDetection/engineDetection.hpp @@ -36,12 +36,12 @@ enum Engine : int QSP, BAT, MonoGame, - GamesforLive, + //GamesforLive, XNA, Adobe_AIR, - ENGINES_END, // This stays at the bottom - UNKNOWN, // This stays under ENGINES_END + UNKNOWN, // This needs to stay as the 2nd to last + ENGINES_END, // This stays as the last item }; //! Function to be specialized for each Engine to return true if the engine is valid. From 1f09334fb1eb7ce3f1519eceb76b3ef4dd9f5504 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 01:03:30 -0500 Subject: [PATCH 21/43] Fixup blacklist and add new item --- atlas/core/utils/engineDetection/engineDetection.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/atlas/core/utils/engineDetection/engineDetection.cpp b/atlas/core/utils/engineDetection/engineDetection.cpp index 3a482ca8..10ef1b6e 100644 --- a/atlas/core/utils/engineDetection/engineDetection.cpp +++ b/atlas/core/utils/engineDetection/engineDetection.cpp @@ -54,14 +54,16 @@ constexpr std::tuple blacklist_execs { std::string_view( "UnityCrashHandler32.ex std::string_view( "python.exe" ), std::string_view( "dxwebsetup" ), std::string_view( "UE4PrereqSetup_X64.exe" ), - std::string_view( "UEPrereqSetup_x64.exe" ) }; + std::string_view( "UEPrereqSetup_x64.exe" ), + std::string_view( "dxwebsetup.exe" ) }; bool isBlacklistT( const std::string& name, const std::string_view comp ) { return name == comp; } -bool isBlacklistT( const std::string& name, std::string_view comp, std::same_as< std::string_view > auto... comps ) +bool isBlacklistT( + const std::string& name, const std::string_view comp, const std::same_as< std::string_view > auto... comps ) { return name == comp || isBlacklistT( name, comps... ); } From 2f04bd85a361be1915283e1d131b6726144462fc Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 01:08:50 -0500 Subject: [PATCH 22/43] Make help message actually helpful --- atlas/ui/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/ui/mainwindow.cpp b/atlas/ui/mainwindow.cpp index 529818e6..d52a8654 100644 --- a/atlas/ui/mainwindow.cpp +++ b/atlas/ui/mainwindow.cpp @@ -143,7 +143,7 @@ void MainWindow::readSettings() void MainWindow::on_actionSimpleImporter_triggered() { - QMessageBox::information( this, "Importer", "Please select the game directory" ); + QMessageBox::information( this, "Importer", "Please select the directory where your games are located" ); if ( const auto dir = QFileDialog::getExistingDirectory( this, "Open directory", QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks ); !dir.isEmpty() ) From ad1e6d50c6806db758d53d4a474693e435250c7d Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 04:12:56 -0500 Subject: [PATCH 23/43] Fixes up some of the scanning and executable detection --- atlas/core/utils/FileScanner.hpp | 6 +- .../utils/engineDetection/engineDetection.cpp | 37 +++++----- atlas/ui/importer/simpleImporter/SIModel.cpp | 50 ++++++++++++++ atlas/ui/importer/simpleImporter/SIModel.hpp | 4 ++ .../simpleImporter/SimpleImporter.cpp | 67 +++++++++++++++++-- 5 files changed, 135 insertions(+), 29 deletions(-) diff --git a/atlas/core/utils/FileScanner.hpp b/atlas/core/utils/FileScanner.hpp index abf64f55..480e2871 100644 --- a/atlas/core/utils/FileScanner.hpp +++ b/atlas/core/utils/FileScanner.hpp @@ -28,7 +28,7 @@ namespace atlas::utils FileInfo() = delete; FileInfo( - std::filesystem::path path_in, + const std::filesystem::path& path_in, const std::filesystem::path& source, const std::size_t filesize, const std::uint8_t file_depth ) : @@ -37,7 +37,7 @@ namespace atlas::utils path( path_in ), size( filesize ), depth( file_depth ), - relative( std::filesystem::relative( std::move( path_in ), source ) ) + relative( std::filesystem::relative( path_in, source ) ) {} }; @@ -91,8 +91,6 @@ namespace atlas::utils class FileScanner { - private: - std::filesystem::path m_path; FileScannerGenerator file_scanner; std::vector< FileInfo > files; diff --git a/atlas/core/utils/engineDetection/engineDetection.cpp b/atlas/core/utils/engineDetection/engineDetection.cpp index 10ef1b6e..9e7b51f1 100644 --- a/atlas/core/utils/engineDetection/engineDetection.cpp +++ b/atlas/core/utils/engineDetection/engineDetection.cpp @@ -99,21 +99,15 @@ std::vector< std::filesystem::path > detectExecutables( atlas::utils::FileScanne extensions.end(), QString::fromStdString( path.extension().string() ).toLower().toStdString() ) != extensions.end() ) - { - TracyCZoneN( mimeInfo_Tracy, "Mime info gathering", true ); QMimeDatabase mime_db; const auto type { mime_db.mimeTypeForFile( QString::fromStdString( path.string() ) ) }; - TracyCZoneEnd( mimeInfo_Tracy ); //General executables //.exe if ( type.inherits( "application/x-ms-dos-executable" ) ) { - //prioritize AMD64 - path.string().find( "32" ) ? - potential_executables.insert( potential_executables.begin(), relative ) : - potential_executables.insert( potential_executables.end(), relative ); + potential_executables.push_back( relative ); continue; } //.html @@ -161,6 +155,8 @@ std::vector< std::filesystem::path > detectExecutables( atlas::utils::FileScanne } } + atlas::logging::debug( "Found {} executables at {}", potential_executables.size(), scanner.path() ); + return scoreExecutables( std::move( potential_executables ) ); } @@ -176,18 +172,21 @@ std::vector< std::filesystem::path > for ( auto& path : paths ) { - std::string extension { QString::fromStdString( path.extension().string() ).toLower().toStdString() }; - - if constexpr ( sys::is_linux ) - if ( extension == ".sh" ) execs.emplace_back( std::move( path ), 20 ); - - if ( extension == ".exe" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); - if ( extension == ".html" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); - if ( extension == ".swf" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); - if ( extension == ".qsp" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); - if ( extension == ".jar" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); - if ( extension == ".bat" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); - if ( extension == ".rag" ) execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); + const std::string extension { QString::fromStdString( path.extension().string() ).toLower().toStdString() }; + + if ( extension == ".exe" ) + { + if ( std::find( path.begin(), path.end(), "32" ) != path.end() ) // Executable is likely 32 bit + execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 15 ); + else + execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 20 ); + } + else if ( extension == ".bat" ) + execs.emplace_back( std::move( path ), sys::is_linux ? 10 : 25 ); + else if ( extension == ".sh" ) + execs.emplace_back( std::move( path ), sys::is_linux ? 25 : 10 ); + else + execs.emplace_back( std::move( path ), 10 ); } std::sort( diff --git a/atlas/ui/importer/simpleImporter/SIModel.cpp b/atlas/ui/importer/simpleImporter/SIModel.cpp index b2dd00fe..240f7374 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.cpp +++ b/atlas/ui/importer/simpleImporter/SIModel.cpp @@ -188,3 +188,53 @@ Node::Node( const QString str, Node* parent, const bool scan_immediate ) : m_nam else m_info = FileInfo(); } + +Node* Node::find( const QString filename ) +{ + if ( m_children.size() == 0 ) this->scan(); + + auto itter { std::find_if( + m_children.begin(), m_children.end(), [ &filename ]( Node* node ) { return node->name() == filename; } ) }; + + if ( itter == m_children.end() ) + return nullptr; + else + return *itter; +} + +Node* Node::findPath( const std::filesystem::path path ) +{ + //Create relative from root node then find relative from that + const Node* root { this->root() }; + if ( root == nullptr ) + return nullptr; + else + return findRelative( std::filesystem::relative( root->path(), path ) ); +} + +Node* Node::findRelative( std::filesystem::path relative_path ) +{ + atlas::logging::debug( "Finding relative path at {} starting at {}", relative_path, this->path() ); + + std::vector< QString > pieces; + + while ( !relative_path.empty() ) + { + pieces.emplace_back( QString::fromStdString( relative_path.filename() ) ); + relative_path = relative_path.parent_path(); + } + + //Reverse order + std::reverse( pieces.begin(), pieces.end() ); + + Node* current { this }; + + //Find each item + while ( !pieces.empty() && current != nullptr ) + { + current = current->find( pieces[ pieces.size() - 1 ] ); + pieces.pop_back(); + } + + return current; +} \ No newline at end of file diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 5f53f3a2..48e2ffe5 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -273,6 +273,10 @@ struct Node return m_parent->pathStr() + QDir::separator() + name(); } + Node* findRelative( const std::filesystem::path relative_path ); + Node* findPath( const std::filesystem::path path ); + Node* find( const QString file_name ); + ~Node() { for ( auto child : m_children ) delete child; diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index e3783d38..db81cef8 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -13,6 +13,8 @@ #include #include "SIModel.hpp" +#include "core/utils/FileScanner.hpp" +#include "core/utils/engineDetection/engineDetection.hpp" #include "ui_SimpleImporter.h" SimpleImporter::SimpleImporter( QWidget* parent ) : QDialog( parent ), ui( new Ui::SimpleImporter ) @@ -106,6 +108,34 @@ void SimpleImporter::setGameRoot( Node* node ) child->fileInfo().is_banner = true; } } + + //Detect executables + atlas::utils::FileScanner scanner { node->path() }; + std::vector< std::filesystem::path > executables { detectExecutables( scanner ) }; + + if ( executables.size() <= 0 ) + { + atlas::logging::warn( "Failed to find any executables for game at {}", node->path() ); + return; + } + + //Set highest in list as the executable + auto* executable_node { node->findRelative( executables.at( 0 ) ) }; + + if ( executable_node == nullptr ) + { + atlas::logging::warn( "Failed to find game node with path {}", executables.at( 0 ) ); + return; + } + else if ( executable_node->isFile() ) + { + executable_node->fileInfo().is_executable = true; + } + else + atlas::logging::error( + "Somehow the executable search gave us a directory. Report as bug. Info:\n\tPath: {}\n\tFlags:{}", + node->path(), + ( node->isFile() << 1 ) | ( node->isFolder() ) ); } else return; @@ -321,15 +351,40 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint } } ); } - else + else if ( node->isFile() ) { - menu.addAction( "Set preview", []() {} ); + menu.addAction( "Set preview", [ node ]() { node->fileInfo().is_preview = true; } ); auto banner_actions { menu.addMenu( "Set banner" ) }; - banner_actions->addAction( "Normal", []() {} ); - banner_actions->addAction( "Wide", []() {} ); - banner_actions->addAction( "Logo", []() {} ); - banner_actions->addAction( "Cover", []() {} ); + banner_actions->addAction( "None", [ node ]() { node->fileInfo().is_banner = false; } ); + banner_actions->addAction( + "Normal", + [ node ]() + { + node->fileInfo().is_banner = true; + node->fileInfo().banner_type = Normal; + } ); + banner_actions->addAction( + "Wide", + [ node ]() + { + node->fileInfo().is_banner = true; + node->fileInfo().banner_type = Wide; + } ); + banner_actions->addAction( + "Logo", + [ node ]() + { + node->fileInfo().is_banner = true; + node->fileInfo().banner_type = Logo; + } ); + banner_actions->addAction( + "Cover", + [ node ]() + { + node->fileInfo().is_banner = true; + node->fileInfo().banner_type = Cover; + } ); } menu.exec( QCursor::pos() ); From 30f077403aafa1aacff17da55bef85cf99842850 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 04:35:30 -0500 Subject: [PATCH 24/43] Properly convert std::filesystem to std::string --- atlas/ui/importer/simpleImporter/SIModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.cpp b/atlas/ui/importer/simpleImporter/SIModel.cpp index 240f7374..04426a0b 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.cpp +++ b/atlas/ui/importer/simpleImporter/SIModel.cpp @@ -220,7 +220,7 @@ Node* Node::findRelative( std::filesystem::path relative_path ) while ( !relative_path.empty() ) { - pieces.emplace_back( QString::fromStdString( relative_path.filename() ) ); + pieces.emplace_back( QString::fromStdString( relative_path.filename().string() ) ); relative_path = relative_path.parent_path(); } From a8e4e22e1e7594e31339ee7e135e5a23b6dfb20e Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 26 Nov 2023 05:20:09 -0500 Subject: [PATCH 25/43] Adds powershell as a valid extension --- atlas/core/utils/engineDetection/engineDetection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/core/utils/engineDetection/engineDetection.cpp b/atlas/core/utils/engineDetection/engineDetection.cpp index 9e7b51f1..267d9f38 100644 --- a/atlas/core/utils/engineDetection/engineDetection.cpp +++ b/atlas/core/utils/engineDetection/engineDetection.cpp @@ -83,7 +83,7 @@ std::vector< std::filesystem::path > detectExecutables( atlas::utils::FileScanne ZoneScoped; std::vector< std::filesystem::path > potential_executables; const std::vector< std::string_view > extensions { ".exe", ".html", ".sh", ".swf", ".flv", - ".jar", ".qsp", ".bat", ".rag" }; + ".jar", ".qsp", ".bat", ".rag", ".ps1" }; //Check for a valid game executable in the folder for ( const auto& [ filename, ext, path, size, depth, relative ] : scanner ) From 7c2893e45c07d13317b127eb25dc41b1e914365f Mon Sep 17 00:00:00 2001 From: kj16609 Date: Wed, 29 Nov 2023 12:18:48 -0500 Subject: [PATCH 26/43] Small fixes in SIModel and more progress to importing in the simple importer --- atlas/ui/importer/simpleImporter/SIModel.cpp | 2 +- atlas/ui/importer/simpleImporter/SIModel.hpp | 13 +++++++++++++ atlas/ui/importer/simpleImporter/SimpleImporter.cpp | 11 +++++++++++ atlas/ui/importer/simpleImporter/SimpleImporter.hpp | 3 +++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.cpp b/atlas/ui/importer/simpleImporter/SIModel.cpp index 04426a0b..240f7374 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.cpp +++ b/atlas/ui/importer/simpleImporter/SIModel.cpp @@ -220,7 +220,7 @@ Node* Node::findRelative( std::filesystem::path relative_path ) while ( !relative_path.empty() ) { - pieces.emplace_back( QString::fromStdString( relative_path.filename().string() ) ); + pieces.emplace_back( QString::fromStdString( relative_path.filename() ) ); relative_path = relative_path.parent_path(); } diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 48e2ffe5..00a1c112 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -116,6 +116,19 @@ struct Node Node( const QString str, Node* parent = nullptr, const bool scan_immediate = false ); + std::vector< Node* > findGameRoots() + { + if ( this->isFolder() && this->dirInfo().is_game_dir ) + return { this }; + else + { + if ( !this->scanned() ) + return {}; + else + {} + } + } + QString name() const { return m_name; diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index db81cef8..f373ffa8 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -389,6 +389,17 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint menu.exec( QCursor::pos() ); updateSidebar(); + verifyGames(); +} + +Node* SimpleImporter::root() +{ + return reinterpret_cast< Node* >( ui->dirView->model()->index( 0, 0 ).internalPointer() ); +} + +void SimpleImporter::verifyGames() +{ + std::vector< Node* > game_roots { root()->findGameRoots() }; } std::vector< QPersistentModelIndex > SimpleImporter::selected() const diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.hpp b/atlas/ui/importer/simpleImporter/SimpleImporter.hpp index cfd5fac4..238c2b7b 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.hpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.hpp @@ -43,6 +43,9 @@ class SimpleImporter final : public QDialog void setGameRoot( Node* node ); + void verifyGames(); + Node* root(); + private slots: void onCustomContextMenuRequested( const QPoint& point ); void dirView_itemSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected ); From f70de193e7f5cc02124dd396645282f6cdcdc042 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 3 Dec 2023 09:32:02 -0500 Subject: [PATCH 27/43] Add in verification step for data --- atlas/ui/importer/simpleImporter/SIModel.hpp | 72 +++++++++++++- .../simpleImporter/SimpleImporter.cpp | 99 ++++++++++++++++++- .../simpleImporter/SimpleImporter.hpp | 2 + .../importer/simpleImporter/SimpleImporter.ui | 4 +- dependencies/blurhash | 2 +- 5 files changed, 171 insertions(+), 8 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 00a1c112..79d5ab7a 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -41,6 +41,9 @@ struct DirInfo bool is_supporting_name { false }; SupportingType supporting_type { SupportingType::TITLE }; int supporting_mask { SupportingMask::NO_SUPPORTING_MASK }; + + QString executable_relative_path { "" }; + int preview_count { 0 }; }; struct FileInfo @@ -66,6 +69,8 @@ struct Node public: + bool isRoot() const { return m_parent == nullptr; } + DirInfo filledInfo() const { if ( !std::holds_alternative< DirInfo >( m_info ) ) @@ -77,6 +82,7 @@ struct Node //Check for any fields populated by the parents const Node* ptr { this }; + //Collect info from parent while ( ptr != nullptr ) { if ( !std::holds_alternative< DirInfo >( ptr->m_info ) ) //The fuck? @@ -110,22 +116,63 @@ struct Node ptr = ptr->parent(); } + std::queue< Node* > children_to_scan {}; + + for ( auto* child : m_children ) children_to_scan.push( child ); + + while ( !children_to_scan.empty() ) + { + const auto* child_node { children_to_scan.front() }; + children_to_scan.pop(); + + if ( child_node->isFolder() && child_node->scanned() ) + { + for ( auto* child : child_node->m_children ) children_to_scan.push( child ); + } + else if ( child_node->isFile() ) + { + const auto& file_info { child_node->fileInfo() }; + + if ( file_info.is_executable ) info.executable_relative_path = child_node->pathStr( this ); + if ( file_info.is_preview ) ++info.preview_count; + } + else + continue; + } + return info; } } Node( const QString str, Node* parent = nullptr, const bool scan_immediate = false ); + //! Returns any node marked as a 'game root' std::vector< Node* > findGameRoots() { if ( this->isFolder() && this->dirInfo().is_game_dir ) return { this }; else { - if ( !this->scanned() ) + if ( !this->scanned() ) // Don't bother going deeper. return {}; else - {} + { + std::vector< Node* > games {}; + + //Check each child and see if it is a game root + for ( Node* child : m_children ) + { + if ( child->isFile() ) continue; + + if ( child->isFolder() && child->scanned() ) + { + const auto& child_games { child->findGameRoots() }; + std::copy( child_games.begin(), child_games.end(), std::back_inserter( games ) ); + } + } + + return games; + } } } @@ -268,6 +315,8 @@ struct Node FileInfo& fileInfo() { return std::get< FileInfo >( m_info ); } + const FileInfo& fileInfo() const { return std::get< FileInfo >( m_info ); } + std::vector< Node* > children() const { return m_children; } std::filesystem::path path() const @@ -278,12 +327,31 @@ struct Node return m_parent->path() / name().toStdString(); } + /** + * If target is nullptr then we will go to the root. + * /home/kj16609/Desktop/Projects/Atlas + * if target->name() == 'Desktop' then we will return 'Projects/Atlas' + * @param target Where to stop in the node list. + * @return + */ + QString pathStr( const Node* target ) const + { + if ( m_parent == nullptr || target == m_parent ) + return name(); + else + { + return m_parent->pathStr( target ) + QDir::separator() + name(); + } + } + QString pathStr() const { if ( m_parent == nullptr ) return name(); else + { return m_parent->pathStr() + QDir::separator() + name(); + } } Node* findRelative( const std::filesystem::path relative_path ); diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index f373ffa8..ecb1f5fc 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "SIModel.hpp" @@ -389,17 +390,94 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint menu.exec( QCursor::pos() ); updateSidebar(); - verifyGames(); } Node* SimpleImporter::root() { - return reinterpret_cast< Node* >( ui->dirView->model()->index( 0, 0 ).internalPointer() ); + return reinterpret_cast< Node* >( ui->dirView->model()->index( 0, 0 ).internalPointer() )->root(); +} + +bool requiredFieldsFilled( QWidget* parent, Node* node ) +{ + if ( node->isFile() ) + { + atlas::logging::error( "Somehow got a file" ); + //How? We should only have dirs at this point + return false; + } + + DirInfo data { node->filledInfo() }; + + if ( data.version.isEmpty() ) + { + QMessageBox::information( parent, "Missing field info", "Missing required field information: Version" ); + return false; + } + + if ( data.creator.isEmpty() ) + { + QMessageBox::information( parent, "Missing field info", "Missing required field information: Creator" ); + return false; + } + + if ( data.title.isEmpty() ) + { + QMessageBox::information( parent, "Missing field info", "Missing required field information: Title" ); + return false; + } + + //Check that an executable is set + if ( data.executable_relative_path.isEmpty() ) + { + QMessageBox::information( parent, "Missing field info", "Missing executable. Please set a file as executable" ); + return false; + } + + return true; +} + +QModelIndex SimpleImporter::indexFromNode( Node* node ) +{ + if ( node == nullptr ) return {}; + if ( node->isRoot() ) + return {}; + else + { + //Create index from parent + QModelIndex parent_index { indexFromNode( node->parent() ) }; + return ui->dirView->model()->index( node->row(), 0, parent_index ); + } } void SimpleImporter::verifyGames() { std::vector< Node* > game_roots { root()->findGameRoots() }; + + bool good { true }; + + for ( Node* game : game_roots ) + { + //Check that the required fields are filled + if ( !requiredFieldsFilled( this, game ) ) + { + //Scroll to the game + auto idx { indexFromNode( game ) }; + ui->dirView->scrollTo( idx, QAbstractItemView::PositionAtCenter ); + ui->dirView->selectionModel()->select( idx, QItemSelectionModel::SelectionFlag::ClearAndSelect ); + + good = false; + break; + } + } + + if ( good ) + { + ui->btnImport->setText( "Import" ); + } + else + { + ui->btnImport->setText( "Check" ); + } } std::vector< QPersistentModelIndex > SimpleImporter::selected() const @@ -630,7 +708,7 @@ void SimpleImporter::updateSidebar() //Only one selection if we are here. Node* node { static_cast< Node* >( current.front().internalPointer() ) }; const DirInfo dir_info { node->filledInfo() }; - const auto& [ is_game_dir, title, creator, version, engine, supporting, supporting_type, supporting_mask ] { + const auto& [ is_game_dir, title, creator, version, engine, supporting, supporting_type, supporting_mask, executable_rel_path, preview_count ] { dir_info }; @@ -776,3 +854,18 @@ void SimpleImporter::on_cbBannerType_currentIndexChanged( int index ) ui->dirView->model()->dataChanged( node_idx, node_idx ); } } + +void SimpleImporter::on_btnImport_clicked() +{ + verifyGames(); + + if ( ui->btnImport->text() == "Import" ) + { + const std::vector< Node* > games { root()->findGameRoots() }; + + for ( const auto& game : games ) + { + //Do import process. + } + } +} diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.hpp b/atlas/ui/importer/simpleImporter/SimpleImporter.hpp index 238c2b7b..e9e7939a 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.hpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.hpp @@ -45,10 +45,12 @@ class SimpleImporter final : public QDialog void verifyGames(); Node* root(); + QModelIndex indexFromNode( Node* node ); private slots: void onCustomContextMenuRequested( const QPoint& point ); void dirView_itemSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected ); + void on_btnImport_clicked(); //Dir page void on_cIsGameRoot_toggled( bool checked ); diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.ui b/atlas/ui/importer/simpleImporter/SimpleImporter.ui index 37a04a22..bb8b4a74 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.ui +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.ui @@ -17,10 +17,10 @@ - false + true - Import + Check diff --git a/dependencies/blurhash b/dependencies/blurhash index 07ae383b..a323f161 160000 --- a/dependencies/blurhash +++ b/dependencies/blurhash @@ -1 +1 @@ -Subproject commit 07ae383badd1aa3dacd638199a74d423f4b82014 +Subproject commit a323f161adc73efc19c4cfd1a82399dce70df457 From 3057bb0d643e91a93fb71c784a0d9a7cc72f11d5 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 3 Dec 2023 09:38:07 -0500 Subject: [PATCH 28/43] Fix stupid windows thing --- atlas/ui/importer/simpleImporter/SIModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.cpp b/atlas/ui/importer/simpleImporter/SIModel.cpp index 240f7374..04426a0b 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.cpp +++ b/atlas/ui/importer/simpleImporter/SIModel.cpp @@ -220,7 +220,7 @@ Node* Node::findRelative( std::filesystem::path relative_path ) while ( !relative_path.empty() ) { - pieces.emplace_back( QString::fromStdString( relative_path.filename() ) ); + pieces.emplace_back( QString::fromStdString( relative_path.filename().string() ) ); relative_path = relative_path.parent_path(); } From 3019cde6721ac3de516a56dc071022a4f9acdccf Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 3 Dec 2023 12:41:03 -0500 Subject: [PATCH 29/43] Fixup progress bar for simple importer --- atlas/ui/importer/simpleImporter/SIModel.hpp | 3 + .../simpleImporter/SimpleImporter.cpp | 120 ++++++------------ 2 files changed, 44 insertions(+), 79 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 79d5ab7a..134f16de 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -15,6 +15,7 @@ enum class SupportingType { + NONE = -1, TITLE = 0, CREATOR = 1, VERSION = 2, @@ -94,6 +95,8 @@ struct Node { switch ( parent_info.supporting_type ) { + case SupportingType::NONE: + [[fallthrough]]; case SupportingType::TITLE: info.title = ptr->name(); info.supporting_mask |= SupportingMask::TITLE; diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index ecb1f5fc..621e93cb 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -78,21 +78,11 @@ void SimpleImporter::setGameRoot( Node* node ) node_info.is_game_dir = true; //Detect for any banners or preview folders - - QProgressDialog progress_dialog { "Scanning...", "", 0, 1, this }; - - progress_dialog.show(); - node->scan(); - progress_dialog.setValue( 1 ); - auto children { node->children() }; - progress_dialog.setMaximum( static_cast< int >( children.size() ) ); - for ( auto child : children ) { - progress_dialog.setValue( progress_dialog.value() + 1 ); - QApplication::processEvents( QEventLoop::AllEvents, 25 ); + //QApplication::processEvents( QEventLoop::AllEvents, 25 ); if ( child->isFolder() && child->name() == "previews" ) { child->scan(); @@ -246,94 +236,66 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint [ idx_depth, root, this ]() { auto children { root->childrenAtDepth( idx_depth ) }; + + QProgressDialog prog_dialog { this }; + prog_dialog.setLabelText( "Setting game root(s)" ); + prog_dialog.setRange( 0, static_cast< int >( children.size() ) ); + prog_dialog.show(); + for ( auto child : children ) { - // QApplication::processEvents(); + prog_dialog.setLabelText( "Setting game root(s)\n" + child->pathStr() ); + prog_dialog.setValue( prog_dialog.value() + 1 ); + QApplication::processEvents(); setGameRoot( child ); } } ); auto this_level_supporting_menu { this_level->addMenu( "Set supporting" ) }; - this_level_supporting_menu->addAction( - "None", - [ idx_depth, root ]() + auto setSupportingDirsAtDepth = [ idx_depth, root, this ]( const SupportingType type ) + { + auto children { root->childrenAtDepth( idx_depth ) }; + + QProgressDialog prog_dialog { this }; + prog_dialog.setLabelText( "Setting supporting folder(s)" ); + prog_dialog.setRange( 0, static_cast< int >( children.size() ) ); + prog_dialog.show(); + + for ( auto child : children ) { - auto children { root->childrenAtDepth( idx_depth ) }; - for ( auto child : children ) + prog_dialog.setLabelText( "Setting supporting folder(s)\n" + child->pathStr() ); + prog_dialog.setValue( prog_dialog.value() + 1 ); + QApplication::processEvents(); + + // QApplication::processEvents(); + if ( std::holds_alternative< DirInfo >( child->m_info ) ) { - // QApplication::processEvents(); - if ( std::holds_alternative< DirInfo >( child->m_info ) ) + DirInfo& info { std::get< DirInfo >( child->m_info ) }; + + if ( type == SupportingType::NONE ) { - DirInfo& info { std::get< DirInfo >( child->m_info ) }; info.is_supporting_name = false; } - } - } ); - this_level_supporting_menu->addAction( - "Title", - [ idx_depth, root ]() - { - auto children { root->childrenAtDepth( idx_depth ) }; - for ( auto child : children ) - { - // QApplication::processEvents(); - if ( std::holds_alternative< DirInfo >( child->m_info ) ) + else { - DirInfo& info { std::get< DirInfo >( child->m_info ) }; info.is_supporting_name = true; - info.supporting_type = SupportingType::TITLE; + info.supporting_type = type; } } - } ); + } + }; + this_level_supporting_menu->addAction( - "Creator", - [ idx_depth, root ]() - { - auto children { root->childrenAtDepth( idx_depth ) }; - for ( auto child : children ) - { - // QApplication::processEvents(); - if ( std::holds_alternative< DirInfo >( child->m_info ) ) - { - DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = true; - info.supporting_type = SupportingType::CREATOR; - } - } - } ); + "None", [ &setSupportingDirsAtDepth ]() { setSupportingDirsAtDepth( SupportingType::NONE ); } ); this_level_supporting_menu->addAction( - "Version", - [ idx_depth, root ]() - { - auto children { root->childrenAtDepth( idx_depth ) }; - for ( auto child : children ) - { - // QApplication::processEvents(); - if ( std::holds_alternative< DirInfo >( child->m_info ) ) - { - DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = true; - info.supporting_type = SupportingType::VERSION; - } - } - } ); + "Title", [ &setSupportingDirsAtDepth ]() { setSupportingDirsAtDepth( SupportingType::TITLE ); } ); this_level_supporting_menu->addAction( - "Engine", - [ idx_depth, root ]() - { - auto children { root->childrenAtDepth( idx_depth ) }; - for ( auto child : children ) - { - // QApplication::processEvents(); - if ( std::holds_alternative< DirInfo >( child->m_info ) ) - { - DirInfo& info { std::get< DirInfo >( child->m_info ) }; - info.is_supporting_name = true; - info.supporting_type = SupportingType::ENGINE; - } - } - } ); + "Creator", [ &setSupportingDirsAtDepth ]() { setSupportingDirsAtDepth( SupportingType::CREATOR ); } ); + this_level_supporting_menu->addAction( + "Version", [ &setSupportingDirsAtDepth ]() { setSupportingDirsAtDepth( SupportingType::VERSION ); } ); + this_level_supporting_menu->addAction( + "Engine", [ &setSupportingDirsAtDepth ]() { setSupportingDirsAtDepth( SupportingType::ENGINE ); } ); menu.addAction( "Set preview folder", From 82c6c08e89186a7eb60d329ab5a987ddf9ab857a Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 3 Dec 2023 13:18:53 -0500 Subject: [PATCH 30/43] Add many more checks for checking that we aren't dereferencing null --- atlas/ui/importer/simpleImporter/SIModel.hpp | 29 ++++++++++++-- .../simpleImporter/SimpleImporter.cpp | 39 +++++++++++++++++-- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 134f16de..6574461e 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -75,7 +75,10 @@ struct Node DirInfo filledInfo() const { if ( !std::holds_alternative< DirInfo >( m_info ) ) + { + atlas::logging::error( "Attempted to get dir info from a file node!" ); return {}; + } else { auto info { std::get< DirInfo >( m_info ) }; @@ -128,6 +131,12 @@ struct Node const auto* child_node { children_to_scan.front() }; children_to_scan.pop(); + if ( child_node == nullptr ) + { + atlas::logging::error( "child_node is nullptr" ); + continue; + } + if ( child_node->isFolder() && child_node->scanned() ) { for ( auto* child : child_node->m_children ) children_to_scan.push( child ); @@ -268,11 +277,11 @@ struct Node { if ( !m_scanned ) scan(); - std::vector< Node* > nodes; + std::vector< Node* > nodes {}; for ( auto child : m_children ) { - auto child_data { child->childrenAtDepth( target_depth - 1 ) }; + std::vector< Node* > child_data { child->childrenAtDepth( target_depth - 1 ) }; std::copy( child_data.begin(), child_data.end(), std::back_inserter( nodes ) ); } @@ -288,6 +297,8 @@ struct Node const Node* child( const int idx ) const { + if ( !m_scanned ) return nullptr; + if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) { atlas::logging:: @@ -300,6 +311,8 @@ struct Node Node* child( const int idx ) { + if ( !m_scanned ) scan(); + if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) { atlas::logging:: @@ -320,7 +333,17 @@ struct Node const FileInfo& fileInfo() const { return std::get< FileInfo >( m_info ); } - std::vector< Node* > children() const { return m_children; } + std::vector< Node* > children() const + { + assert( m_scanned && "Attempted to access children before scanning!" ); + return m_children; + } + + std::vector< Node* > children() + { + if ( !m_scanned ) scan(); + return m_children; + } std::filesystem::path path() const { diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 621e93cb..33586478 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -356,11 +356,23 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint Node* SimpleImporter::root() { - return reinterpret_cast< Node* >( ui->dirView->model()->index( 0, 0 ).internalPointer() )->root(); + auto* internal_ptr { ui->dirView->model()->index( 0, 0 ).internalPointer() }; + if ( internal_ptr == nullptr ) + { + atlas::logging::error( "Internal pointer is nullptr" ); + return nullptr; + } + else + return static_cast< Node* >( internal_ptr )->root(); } bool requiredFieldsFilled( QWidget* parent, Node* node ) { + if ( node == nullptr ) + { + atlas::logging::error( "Node was nullptr!" ); + } + if ( node->isFile() ) { atlas::logging::error( "Somehow got a file" ); @@ -413,12 +425,25 @@ QModelIndex SimpleImporter::indexFromNode( Node* node ) void SimpleImporter::verifyGames() { - std::vector< Node* > game_roots { root()->findGameRoots() }; + auto* tree_root { root() }; + if ( tree_root == nullptr ) + { + atlas::logging::error( "Tree root is nullptr" ); + return; + } + + std::vector< Node* > game_roots { tree_root->findGameRoots() }; bool good { true }; for ( Node* game : game_roots ) { + if ( game == nullptr ) + { + atlas::logging::error( "Game root is nullptr" ); + continue; + } + //Check that the required fields are filled if ( !requiredFieldsFilled( this, game ) ) { @@ -823,11 +848,19 @@ void SimpleImporter::on_btnImport_clicked() if ( ui->btnImport->text() == "Import" ) { - const std::vector< Node* > games { root()->findGameRoots() }; + auto* tree_root { root() }; + if ( tree_root == nullptr ) + { + atlas::logging::error( "Tree root is nullptr" ); + return; + } + + const std::vector< Node* > games { tree_root->findGameRoots() }; for ( const auto& game : games ) { //Do import process. + atlas::logging::debug( "STUB: Should be processing game: {}", game->path() ); } } } From 457b8a02f148033f772fdfcd7d1e4bf63c7c58e2 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 3 Dec 2023 13:26:57 -0500 Subject: [PATCH 31/43] Flush on warnings --- atlas/core/logging/logging.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atlas/core/logging/logging.cpp b/atlas/core/logging/logging.cpp index 1cd20471..cb8a9d09 100644 --- a/atlas/core/logging/logging.cpp +++ b/atlas/core/logging/logging.cpp @@ -69,6 +69,8 @@ namespace atlas::logging spdlog::set_default_logger( std::move( logger ) ); setFormat(); + spdlog::flush_on( spdlog::level::warn ); + #ifndef NDEBUG spdlog::set_level( spdlog::level::debug ); #endif From f95327ec4b80bbe06b4f2948d6cb9e7fd728a765 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Wed, 6 Dec 2023 20:09:30 -0500 Subject: [PATCH 32/43] Add in selection for importer --- .../ImportSelectionPopup.cpp | 122 +++++++++++++++++ .../ImportSelectionPopup.hpp | 61 +++++++++ .../importBasicDialog/ImportSelectionPopup.ui | 124 ++++++++++++++++++ .../batchImporter/BatchImportDialog.cpp | 5 + .../batchImporter/BatchImportDialog.hpp | 2 + .../singleImporter/SingleImporter.cpp | 5 + .../singleImporter/SingleImporter.hpp | 2 + atlas/ui/mainwindow.cpp | 6 +- atlas/ui/mainwindow.ui | 4 +- 9 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.cpp create mode 100644 atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.hpp create mode 100644 atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.ui diff --git a/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.cpp b/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.cpp new file mode 100644 index 00000000..3ef8af6c --- /dev/null +++ b/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.cpp @@ -0,0 +1,122 @@ +// +// Created by kj16609 on 12/6/23. +// + +// You may need to build the project (run Qt uic code generator) to get "ui_ImportSelectionPopup.h" resolved + +#include "ImportSelectionPopup.hpp" + +#include +#include + +#include "core/logging/logging.hpp" +#include "ui/importer/batchImporter/BatchImportDialog.hpp" +#include "ui/importer/simpleImporter/SimpleImporter.hpp" +#include "ui/importer/singleImporter/SingleImporter.hpp" +#include "ui_ImportSelectionPopup.h" + +namespace atlas::ui::imports +{ + enum ImportSelectionStackPages + { + GameCountSelection = 0, + FormatSelection, + SelectPage, + }; + + ImportSelectionPopup::ImportSelectionPopup( QWidget* parent ) : + QDialog( parent ), + ui( new Ui::ImportSelectionPopup ) + { + ui->setupUi( this ); + } + + ImportSelectionPopup::~ImportSelectionPopup() + { + delete ui; + } + + void ImportSelectionPopup::on_btnOneGame_pressed() + { + use_id = SINGLE; + + ui->stackedWidget->setCurrentIndex( SelectPage ); + } + + void ImportSelectionPopup::on_btnMultipleGames_pressed() + { + ui->stackedWidget->setCurrentIndex( FormatSelection ); + ui->btnSelectFolder->setText( " Click to select top level folder " ); + } + + void ImportSelectionPopup::on_btnLaidRandom_pressed() + { + use_id = TREE; + ui->stackedWidget->setCurrentIndex( SelectPage ); + } + + void ImportSelectionPopup::on_btnLaidSorted_pressed() + { + use_id = BULK; + ui->stackedWidget->setCurrentIndex( SelectPage ); + } + + void ImportSelectionPopup::on_btnSelectFolder_pressed() + { + //Open folder prompt + const auto filepath { QFileDialog::getExistingDirectory( this, "Select folder to import" ) }; + + //Check if filepath is empty or if it doesn't exist then return + + if ( filepath.isEmpty() ) return; + + if ( !std::filesystem::exists( filepath.toStdString() ) ) + { + atlas::logging::warn( "Invalid filepath given: {}. Aborting import process", filepath ); + return; + } + + this->hide(); + + switch ( use_id ) + { + default: + [[fallthrough]]; + case NONE: + { + atlas::logging::error( + "Somehow managed to get to folder selection without a use_id set.", + static_cast< int >( use_id ) ); + return; + } + break; + case SINGLE: + { + SingleImporter importer { this->parentWidget() }; + importer.setPath( filepath ); + importer.exec(); + } + break; + case BULK: + { + BatchImportDialog importer { this->parentWidget() }; + importer.setPath( filepath ); + importer.exec(); + } + break; + case TREE: + { + SimpleImporter importer { this->parentWidget() }; + importer.setRoot( filepath ); + importer.exec(); + } + break; + } + } + + void ImportSelectionPopup::on_lblDragHere_dragEnterEvent( QDragEnterEvent* event ) + { + //TODO: Handle + } + +} // namespace atlas::ui::imports diff --git a/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.hpp b/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.hpp new file mode 100644 index 00000000..c06111db --- /dev/null +++ b/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.hpp @@ -0,0 +1,61 @@ +// +// Created by kj16609 on 12/6/23. +// + +#pragma once + +#include +#include + +namespace atlas::ui::imports +{ + QT_BEGIN_NAMESPACE + + namespace Ui + { + class ImportSelectionPopup; + } + + QT_END_NAMESPACE + + class ImportSelectionPopup final : public QDialog + { + enum UseID + { + NONE = -1, + SINGLE = 0, + BULK, + TREE + }; + + Q_OBJECT + + UseID use_id { NONE }; + + public: + + explicit ImportSelectionPopup( QWidget* parent = nullptr ); + ~ImportSelectionPopup() override; + + Q_DISABLE_COPY_MOVE( ImportSelectionPopup ) + + private: + + Ui::ImportSelectionPopup* ui; + + private slots: + //Stage 1 + void on_btnOneGame_pressed(); + void on_btnMultipleGames_pressed(); + + //Stage 2 - Multiple + void on_btnLaidRandom_pressed(); + void on_btnLaidSorted_pressed(); + + //Stage 3 - Select folder + void on_btnSelectFolder_pressed(); + + //Drag to lblDragHere + void on_lblDragHere_dragEnterEvent( QDragEnterEvent* event ); + }; +} // namespace atlas::ui::imports diff --git a/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.ui b/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.ui new file mode 100644 index 00000000..b986046e --- /dev/null +++ b/atlas/ui/dialog/importBasicDialog/ImportSelectionPopup.ui @@ -0,0 +1,124 @@ + + + atlas::ui::imports::ImportSelectionPopup + + + + 0 + 0 + 529 + 199 + + + + ImportSelectionPopup + + + + + + + + + + + 0 + 0 + + + + One Game + + + + + + + + 0 + 0 + + + + Multiple Games + + + + + + + + + + + + 0 + 0 + + + + Sorted +(Can use Regex) + + + + + + + + 0 + 0 + + + + Randomly +(Random depths/structure) + + + + + + + + 0 + 0 + + + + How are the games laid out? + + + + + + + + + + + Click to select folder/archive + + + + + + + Qt::LeftToRight + + + Or it drag here + + + Qt::AlignCenter + + + + + + + + + + + + diff --git a/atlas/ui/importer/batchImporter/BatchImportDialog.cpp b/atlas/ui/importer/batchImporter/BatchImportDialog.cpp index 512416bf..ed34598b 100644 --- a/atlas/ui/importer/batchImporter/BatchImportDialog.cpp +++ b/atlas/ui/importer/batchImporter/BatchImportDialog.cpp @@ -346,3 +346,8 @@ void BatchImportDialog::keyPressEvent( QKeyEvent* event ) else QDialog::keyPressEvent( event ); } + +void BatchImportDialog::setPath( const QString& str ) +{ + ui->tbPath->setText( str ); +} diff --git a/atlas/ui/importer/batchImporter/BatchImportDialog.hpp b/atlas/ui/importer/batchImporter/BatchImportDialog.hpp index fd78a5cd..16b7cd7f 100644 --- a/atlas/ui/importer/batchImporter/BatchImportDialog.hpp +++ b/atlas/ui/importer/batchImporter/BatchImportDialog.hpp @@ -25,6 +25,8 @@ class BatchImportDialog final : public QDialog explicit BatchImportDialog( QWidget* parent = nullptr ); ~BatchImportDialog(); + void setPath( const QString& str ); + private: GameScanner scanner {}; diff --git a/atlas/ui/importer/singleImporter/SingleImporter.cpp b/atlas/ui/importer/singleImporter/SingleImporter.cpp index 1e056a3d..5a9b5ad9 100644 --- a/atlas/ui/importer/singleImporter/SingleImporter.cpp +++ b/atlas/ui/importer/singleImporter/SingleImporter.cpp @@ -557,3 +557,8 @@ void SingleImporter::fillIn() ui->previews->setPaths( previews ); } + +void SingleImporter::setPath( const QString& path ) +{ + ui->leRootPath->setText( path ); +} diff --git a/atlas/ui/importer/singleImporter/SingleImporter.hpp b/atlas/ui/importer/singleImporter/SingleImporter.hpp index 8f466644..c1190377 100644 --- a/atlas/ui/importer/singleImporter/SingleImporter.hpp +++ b/atlas/ui/importer/singleImporter/SingleImporter.hpp @@ -29,6 +29,8 @@ class SingleImporter final : public QDialog explicit SingleImporter( QWidget* parent = nullptr ); ~SingleImporter() override; + void setPath( const QString& path ); + private: Ui::SingleImporter* ui; diff --git a/atlas/ui/mainwindow.cpp b/atlas/ui/mainwindow.cpp index b3249f26..677a35ee 100644 --- a/atlas/ui/mainwindow.cpp +++ b/atlas/ui/mainwindow.cpp @@ -2,6 +2,8 @@ #include +#include + #include "core/config/config.hpp" #include "core/database/RapidTransaction.hpp" #include "core/import/ImportNotifier.hpp" @@ -202,8 +204,8 @@ void MainWindow::on_homeButton_pressed() void MainWindow::on_btnAddGame_pressed() { - SingleImporter importer { this }; - importer.exec(); + atlas::ui::imports::ImportSelectionPopup popup { this }; + popup.exec(); } void MainWindow::resizeEvent( QResizeEvent* event ) diff --git a/atlas/ui/mainwindow.ui b/atlas/ui/mainwindow.ui index 06cb2562..cc6de3f3 100644 --- a/atlas/ui/mainwindow.ui +++ b/atlas/ui/mainwindow.ui @@ -370,7 +370,7 @@ border-top-left-radius:0px; - Add Game + Import @@ -723,7 +723,7 @@ margin-right:5px; 0 0 1043 - 21 + 27 From ae6dbd778caad633046ed36bd74697aae755a9c7 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 17 Dec 2023 12:27:18 -0500 Subject: [PATCH 33/43] Fixes bug with search_started flag being checked too early and preventing the bulk importer from proceeding after a failed validation check --- atlas/ui/importer/batchImporter/BatchImportDialog.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/atlas/ui/importer/batchImporter/BatchImportDialog.cpp b/atlas/ui/importer/batchImporter/BatchImportDialog.cpp index 1fc3d6b2..7e01243a 100644 --- a/atlas/ui/importer/batchImporter/BatchImportDialog.cpp +++ b/atlas/ui/importer/batchImporter/BatchImportDialog.cpp @@ -165,7 +165,7 @@ void BatchImportDialog::on_btnNext_pressed() if ( import_triggered ) return; - atlas::logging::debug( "next pressed" ); + atlas::logging::debug( "Next pressed" ); if ( ui->btnNext->text() == "Import" ) { import_triggered = true; @@ -173,9 +173,8 @@ void BatchImportDialog::on_btnNext_pressed() } else { - if ( search_started ) return; + atlas::logging::debug( "Checking validity of data" ); - search_started = true; //Verify that the path is set const auto& path { ui->tbPath->text() }; if ( path.isEmpty() || !QFile::exists( path ) ) @@ -202,6 +201,12 @@ void BatchImportDialog::on_btnNext_pressed() return; } + if ( search_started ) + { + atlas::logging::error( "Search already running. Possibly a bug" ); + return; + } + ui->swImportGames->setCurrentIndex( 1 ); ui->btnBack->setEnabled( true ); ui->btnNext->setDisabled( true ); From 624953b2020d343484c685fc4d76e8b7a9175a24 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Sun, 17 Dec 2023 13:17:51 -0500 Subject: [PATCH 34/43] Fixes issue with bulk importer crashing when canceling --- atlas/core/import/GameScanner.cpp | 42 +++++++++++++++++-- atlas/core/import/GameScanner.hpp | 1 + .../batchImporter/BatchImportDialog.cpp | 15 ++++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/atlas/core/import/GameScanner.cpp b/atlas/core/import/GameScanner.cpp index 4357c2a5..c27cbf19 100644 --- a/atlas/core/import/GameScanner.cpp +++ b/atlas/core/import/GameScanner.cpp @@ -224,7 +224,10 @@ try ZoneScopedN( "Process directory" ); promise.suspendIfRequested(); - if ( promise.isCanceled() ) return; + if ( promise.isCanceled() ) + { + break; + } if ( itter->is_directory() ) { @@ -266,13 +269,13 @@ try for ( auto& future : futures | std::views::reverse ) { + if ( promise.isCanceled() ) break; + while ( true ) { promise.suspendIfRequested(); if ( promise.isCanceled() ) { - future.cancel(); - future.waitForFinished(); break; } @@ -282,6 +285,37 @@ try std::this_thread::sleep_for( 10ms ); } } + + if ( promise.isCanceled() ) + { + //We need to cancel all the futures we have running + for ( auto& future : futures | std::views::reverse ) + { + future.cancel(); + } + + //Wait for them to finish + for ( auto& future : futures ) + { + future.waitForFinished(); + } + } + + done = true; +} +catch ( QUnhandledException& e ) +{ + if ( promise.isCanceled() ) //We don't care about the error if we're canceled + return; + + try + { + std::rethrow_exception( e.exception() ); + } + catch ( std::exception& e_2 ) + { + atlas::logging::error( "Main runner ate error before entering Qt space! {}", e_2.what() ); + } } catch ( std::exception& e ) { @@ -324,7 +358,7 @@ void GameScanner::abort() bool GameScanner::isRunning() { - return m_runner_future.isRunning(); + return m_runner_future.isRunning() || !done; } bool GameScanner::isPaused() diff --git a/atlas/core/import/GameScanner.hpp b/atlas/core/import/GameScanner.hpp index eba0cfe5..05cbbef5 100644 --- a/atlas/core/import/GameScanner.hpp +++ b/atlas/core/import/GameScanner.hpp @@ -24,6 +24,7 @@ class GameScanner final : public QObject public: + std::atomic< bool > done { false }; std::atomic< uint64_t > directories_left { 0 }; void start( const std::filesystem::path path, const QString regex, const bool size_folders ); diff --git a/atlas/ui/importer/batchImporter/BatchImportDialog.cpp b/atlas/ui/importer/batchImporter/BatchImportDialog.cpp index 7e01243a..d1666de8 100644 --- a/atlas/ui/importer/batchImporter/BatchImportDialog.cpp +++ b/atlas/ui/importer/batchImporter/BatchImportDialog.cpp @@ -296,9 +296,20 @@ void BatchImportDialog::reject() == QMessageBox::Yes ) { scanner.abort(); - } - QDialog::reject(); + QMessageBox box { this }; + box.setText( "Cancelling import" ); + box.setInformativeText( "Please wait while we cancel the import" ); + box.show(); + + while ( scanner.isRunning() ) QApplication::processEvents(); + + box.close(); + + QDialog::reject(); + } + else + return; } void BatchImportDialog::importFailure( const QString top, const QString bottom ) From 95b6b10c240bc6d537311fa0d16a73e8b007b1ad Mon Sep 17 00:00:00 2001 From: kj16609 Date: Mon, 18 Dec 2023 03:03:46 -0500 Subject: [PATCH 35/43] Set the import prescanner thread count to be higher --- atlas/core/config/config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas/core/config/config.hpp b/atlas/core/config/config.hpp index 84d51a9d..63e4d1ab 100644 --- a/atlas/core/config/config.hpp +++ b/atlas/core/config/config.hpp @@ -328,7 +328,7 @@ SETTINGS_D( SETTINGS_D( threads, import_threads, int, 2 ) SETTINGS_D( threads, image_import_threads, int, 4 ) SETTINGS_D( threads, image_loader_threads, int, 2 ) -SETTINGS_D( threads, import_pre_loader_threads, int, 4 ) +SETTINGS_D( threads, import_pre_loader_threads, int, 8 ) SETTINGS_D( experimental, local_match, bool, false ) SETTINGS_D( experimental, loading_preview, bool, false ) From fc252227d84236efeafce8764e183ab0f6d1a1e0 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Mon, 18 Dec 2023 03:03:59 -0500 Subject: [PATCH 36/43] Performance logging --- atlas/core/import/GameScanner.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atlas/core/import/GameScanner.cpp b/atlas/core/import/GameScanner.cpp index c27cbf19..cd67090c 100644 --- a/atlas/core/import/GameScanner.cpp +++ b/atlas/core/import/GameScanner.cpp @@ -44,6 +44,7 @@ void runner( auto gl_info { [ &folder ]() -> gl::GameListInfos { + ZoneScopedN( "Scan for GL data" ); //Check if we have a GL_Infos.ini file if ( gl::dirHasGLInfo( folder ) ) { @@ -57,6 +58,7 @@ void runner( auto [ title, creator, version, engine ] = [ & ]() -> regex::GroupsOutput { + ZoneScopedN( "Scan for Remote data" ); if ( gl_info.f95_thread_id == INVALID_F95_ID ) { //atlas::logging::warn( "Found GL info but it had an invalid F95 id!" ); From e9c1525938632665ed9f4b442e6b3028c45e642e Mon Sep 17 00:00:00 2001 From: kj16609 Date: Tue, 19 Dec 2023 10:07:19 -0500 Subject: [PATCH 37/43] Have set game root at level now properly use multiple threads via pool --- atlas/core/utils/threading/pools.cpp | 5 ++++ atlas/core/utils/threading/pools.hpp | 2 ++ atlas/ui/importer/simpleImporter/SIModel.hpp | 17 ++++++++++++-- .../simpleImporter/SimpleImporter.cpp | 23 ++++++++++++------- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/atlas/core/utils/threading/pools.cpp b/atlas/core/utils/threading/pools.cpp index 05a1ec66..1accc8b1 100644 --- a/atlas/core/utils/threading/pools.cpp +++ b/atlas/core/utils/threading/pools.cpp @@ -20,6 +20,11 @@ void ThreadPools::reloadConfig() pre_importers.setMaxThreadCount( config::threads::import_pre_loader_threads::get() ); } +ThreadPools& ThreadPools::getInstance() +{ + return internal::global_pools; +} + ThreadPools& globalPools() { return internal::global_pools; diff --git a/atlas/core/utils/threading/pools.hpp b/atlas/core/utils/threading/pools.hpp index 56178b13..4edeb3a4 100644 --- a/atlas/core/utils/threading/pools.hpp +++ b/atlas/core/utils/threading/pools.hpp @@ -18,6 +18,8 @@ struct ThreadPools ThreadPools() { reloadConfig(); } void reloadConfig(); + + static ThreadPools& getInstance(); }; ThreadPools& globalPools(); diff --git a/atlas/ui/importer/simpleImporter/SIModel.hpp b/atlas/ui/importer/simpleImporter/SIModel.hpp index 0480da29..6dbb2ec2 100644 --- a/atlas/ui/importer/simpleImporter/SIModel.hpp +++ b/atlas/ui/importer/simpleImporter/SIModel.hpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include "core/config/config.hpp" @@ -75,6 +77,7 @@ struct Node DirInfo filledInfo() const { + ZoneScoped; if ( !std::holds_alternative< DirInfo >( m_info ) ) { atlas::logging::error( "Attempted to get dir info from a file node!" ); @@ -162,6 +165,7 @@ struct Node //! Returns any node marked as a 'game root' std::vector< Node* > findGameRoots() { + ZoneScoped; if ( this->isFolder() && this->dirInfo().is_game_dir ) return { this }; else @@ -192,12 +196,11 @@ struct Node QString name() const { return m_name; - //const auto split_pos { m_path.lastIndexOf( QDir::separator() ) }; - //return m_path.mid( split_pos + 1 ); } void scan() { + ZoneScoped; const QString path_str { this->pathStr() }; QFileInfo info { path_str }; @@ -217,6 +220,7 @@ struct Node int row() const { + ZoneScoped; if ( m_parent ) { const auto& parent_children { m_parent->m_children }; @@ -229,6 +233,7 @@ struct Node Node* root() { + ZoneScoped; Node* ptr { this }; if ( this->parent() == nullptr ) return ptr; @@ -243,6 +248,7 @@ struct Node const Node* root() const { + ZoneScoped; const Node* ptr { this }; if ( this->parent() == nullptr ) return ptr; @@ -256,6 +262,7 @@ struct Node int depth() const { + ZoneScoped; int counter { 0 }; const Node* ptr { this }; @@ -272,6 +279,7 @@ struct Node std::vector< Node* > childrenAtDepth( const int target_depth ) { + ZoneScoped; if ( target_depth == 0 ) return { this }; else if ( target_depth > 0 ) @@ -298,6 +306,7 @@ struct Node const Node* child( const int idx ) const { + ZoneScoped; if ( !m_scanned ) return nullptr; if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) @@ -312,6 +321,7 @@ struct Node Node* child( const int idx ) { + ZoneScoped; if ( !m_scanned ) scan(); if ( m_children.size() < static_cast< std::size_t >( idx ) || idx < 0 ) @@ -348,6 +358,7 @@ struct Node std::filesystem::path path() const { + ZoneScoped; if ( m_parent == nullptr ) return std::filesystem::path( name().toStdString() ); else @@ -363,6 +374,7 @@ struct Node */ QString pathStr( const Node* target ) const { + ZoneScoped; if ( m_parent == nullptr || target == m_parent ) return name(); else @@ -373,6 +385,7 @@ struct Node QString pathStr() const { + ZoneScoped; if ( m_parent == nullptr ) return name(); else diff --git a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp index 33586478..37e92c7e 100644 --- a/atlas/ui/importer/simpleImporter/SimpleImporter.cpp +++ b/atlas/ui/importer/simpleImporter/SimpleImporter.cpp @@ -12,6 +12,10 @@ #include #include #include +#include + +#include +#include #include "SIModel.hpp" #include "core/utils/FileScanner.hpp" @@ -72,6 +76,7 @@ int depthOfIndex( const QModelIndex& index ) void SimpleImporter::setGameRoot( Node* node ) { + ZoneScoped; if ( node && node->isFolder() ) { auto& node_info { node->dirInfo() }; @@ -134,6 +139,7 @@ void SimpleImporter::setGameRoot( Node* node ) void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint& point ) { + ZoneScoped; QMenu menu; const QModelIndex item { ui->dirView->indexAt( point ) }; @@ -235,20 +241,21 @@ void SimpleImporter::onCustomContextMenuRequested( [[maybe_unused]] const QPoint "Set game root", [ idx_depth, root, this ]() { + ZoneScopedN( "Set game root" ); auto children { root->childrenAtDepth( idx_depth ) }; QProgressDialog prog_dialog { this }; prog_dialog.setLabelText( "Setting game root(s)" ); - prog_dialog.setRange( 0, static_cast< int >( children.size() ) ); + prog_dialog.setRange( 0, 0 ); prog_dialog.show(); - for ( auto child : children ) - { - prog_dialog.setLabelText( "Setting game root(s)\n" + child->pathStr() ); - prog_dialog.setValue( prog_dialog.value() + 1 ); - QApplication::processEvents(); - setGameRoot( child ); - } + auto& pool { ThreadPools::getInstance().pre_importers }; + + QtConcurrent::blockingMap( + &pool, + children.begin(), + children.end(), + [ this ]( Node* child ) -> void { setGameRoot( child ); } ); } ); auto this_level_supporting_menu { this_level->addMenu( "Set supporting" ) }; From 1b2b7c0b8fce671e9e940962be927c0f87be348a Mon Sep 17 00:00:00 2001 From: kj16609 Date: Tue, 19 Dec 2023 10:09:06 -0500 Subject: [PATCH 38/43] Comment out printout leftover --- atlas/core/database/remote/AtlasData.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atlas/core/database/remote/AtlasData.cpp b/atlas/core/database/remote/AtlasData.cpp index fb6d29ec..5a210745 100644 --- a/atlas/core/database/remote/AtlasData.cpp +++ b/atlas/core/database/remote/AtlasData.cpp @@ -154,10 +154,10 @@ namespace atlas::remote RapidTransaction() << query >> [ &data ]( const AtlasID atlas_id ) { data = { atlas_id }; }; - if ( !data.has_value() ) - { - qInfo() << QString::fromStdString( query ); - } + //if ( !data.has_value() ) + //{ + // qInfo() << QString::fromStdString( query ); + //} return data; } From 45a2bebfc9943fcf9dcb3b06ca29597e1a59b8b9 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Tue, 19 Dec 2023 10:57:00 -0500 Subject: [PATCH 39/43] Profiling and cleanup for pre-importer runner --- atlas/core/database/Binder.cpp | 2 + atlas/core/database/Binder.hpp | 3 + atlas/core/database/Transaction.hpp | 2 + atlas/core/database/remote/AtlasData.cpp | 37 +++++-- atlas/core/database/remote/AtlasData.hpp | 2 + atlas/core/database/remote/F95Data.cpp | 6 +- atlas/core/database/remote/F95Data.hpp | 2 +- atlas/core/import/GameScanner.cpp | 124 +++++++++++------------ atlas/core/import/Importer.cpp | 2 +- atlas/ui/mainwindow.cpp | 4 +- 10 files changed, 102 insertions(+), 82 deletions(-) diff --git a/atlas/core/database/Binder.cpp b/atlas/core/database/Binder.cpp index 4d5f77ad..19bf1d1e 100644 --- a/atlas/core/database/Binder.cpp +++ b/atlas/core/database/Binder.cpp @@ -8,6 +8,7 @@ Binder::Binder( const std::string_view sql ) { + ZoneScoped; const auto prepare_ret { sqlite3_prepare_v2( &Database::ref(), sql.data(), static_cast< int >( sql.size() + 1 ), &stmt, nullptr ) }; @@ -27,6 +28,7 @@ Binder::Binder( const std::string_view sql ) Binder::~Binder() { + ZoneScoped; try { if ( !ran ) diff --git a/atlas/core/database/Binder.hpp b/atlas/core/database/Binder.hpp index cdf4bf70..2862dd7f 100644 --- a/atlas/core/database/Binder.hpp +++ b/atlas/core/database/Binder.hpp @@ -6,6 +6,8 @@ #ifndef ATLASGAMEMANAGER_BINDER_HPP #define ATLASGAMEMANAGER_BINDER_HPP +#include + #include #include @@ -159,6 +161,7 @@ class Binder requires( !( is_optional< Ts > || ... ) && !( is_tuple< Ts > || ... ) ) void executeQuery( [[maybe_unused]] std::optional< std::tuple< Ts... > >& tpl_opt ) { + ZoneScoped; if ( param_counter != max_param_count ) throw AtlasException( format_ns::format( "Not enough parameters given for query! Given {}, Expected {}. param_counter != max_param_count = {} != {} for query \"{}\"", diff --git a/atlas/core/database/Transaction.hpp b/atlas/core/database/Transaction.hpp index 44755273..fa59d409 100644 --- a/atlas/core/database/Transaction.hpp +++ b/atlas/core/database/Transaction.hpp @@ -35,6 +35,7 @@ namespace atlas::database inline Binder operator<<( std::string_view sql ) { + ZoneScopedN( "TransactionBase::operator<<" ); if constexpr ( is_commitable ) sqlite3_exec( &Database::ref(), "BEGIN TRANSACTION;", nullptr, nullptr, nullptr ); @@ -44,6 +45,7 @@ namespace atlas::database template < std::uint64_t size > inline Binder operator<<( const char ( &raw_str )[ size - 1 ] ) { + ZoneScopedN( "TransactionBase::operator<<" ); const std::string_view str_view { std::string_view( raw_str, size - 1 ) }; return *this << str_view; } diff --git a/atlas/core/database/remote/AtlasData.cpp b/atlas/core/database/remote/AtlasData.cpp index 5a210745..04e7400e 100644 --- a/atlas/core/database/remote/AtlasData.cpp +++ b/atlas/core/database/remote/AtlasData.cpp @@ -4,6 +4,8 @@ #include "AtlasData.hpp" +#include + #include "core/database/RapidTransaction.hpp" namespace atlas::remote @@ -126,19 +128,34 @@ namespace atlas::remote return INVALID_ATLAS_ID; } + std::optional< atlas::remote::AtlasRemoteData > findAtlasData( const AtlasID atlas_id ) + { + ZoneScoped; + std::optional< atlas::remote::AtlasRemoteData > data; + RapidTransaction() << "SELECT atlas_id FROM atlas_data WHERE atlas_id = ?" << atlas_id >> + [ &data ]( const AtlasID atlas_id ) { data = { atlas_id }; }; + return data; + } + // Find Altas ID from Record Title and Creator name. Only use first letter from creator std::optional< atlas::remote::AtlasRemoteData > findAtlasData( QString title, QString creator ) { + ZoneScoped; + + TracyCZoneN( generate_zone, "Generate query", true ); + //REPLACE ' from query. Not done yet - std::optional< atlas::remote::AtlasRemoteData > data; - title = title.toUtf8() - .toUpper() - .replace( " ", "" ) - .replace( "'", "" ) - .replace( ".", "" ) - .replace( "-", "" ) - .replace( ":", "" ); //Convert to caps and remove spaces - QString creator_fl = creator.toUpper().replace( " ", "" ).mid( 0, 1 ); //Get first letter and convert to caps + std::optional< atlas::remote::AtlasRemoteData > data { std::nullopt }; + + //Blacklisted characters + constexpr std::array< QChar, 5 > blacklist { ' ', '\'', '.', '-', ':' }; + + title = title.toUtf8().toUpper(); + title.removeIf( [ &blacklist ]( const QChar c ) + { return std::find( blacklist.begin(), blacklist.end(), c ) != blacklist.end(); } ); + + const QString creator_fl = + creator.toUpper().replace( " ", "" ).mid( 0, 1 ); //Get first letter and convert to caps //std::string query count = ""; std::string query = @@ -152,6 +169,8 @@ namespace atlas::remote //Check if creator is empty //RapidTransaction() << "SELECT * FROM atlas_data WHERE id_name=(UPPER(REPLACE(?,' ','') || \"_\" || ?))" << title << creator >> [ &data ]( const AtlasID atlas_id ) { data = { atlas_id }; }; + TracyCZoneEnd( generate_zone ); + RapidTransaction() << query >> [ &data ]( const AtlasID atlas_id ) { data = { atlas_id }; }; //if ( !data.has_value() ) diff --git a/atlas/core/database/remote/AtlasData.hpp b/atlas/core/database/remote/AtlasData.hpp index 7d84729a..00b440ad 100644 --- a/atlas/core/database/remote/AtlasData.hpp +++ b/atlas/core/database/remote/AtlasData.hpp @@ -65,6 +65,8 @@ namespace atlas::remote const internal::AtlasData* operator->() const { return data_ptr.get(); } }; + std::optional< atlas::remote::AtlasRemoteData > findAtlasData( const AtlasID atlas_id ); + std::optional< atlas::remote::AtlasRemoteData > findAtlasData( QString title, QString developer ); } // namespace atlas::remote diff --git a/atlas/core/database/remote/F95Data.cpp b/atlas/core/database/remote/F95Data.cpp index ca277d71..3398e83b 100644 --- a/atlas/core/database/remote/F95Data.cpp +++ b/atlas/core/database/remote/F95Data.cpp @@ -78,6 +78,7 @@ namespace atlas::remote bool hasF95DataFor( const F95ID f95_id ) { + ZoneScoped; std::optional< F95ID > id; RapidTransaction() << "SELECT f95_id FROM f95_zone_data WHERE f95_id = ?" << f95_id >> id; return id.has_value(); @@ -96,12 +97,13 @@ namespace atlas::remote internal::releasePtr( id ); } - std::optional< atlas::remote::F95RemoteData > findF95Data( QString atlas_id ) + std::optional< atlas::remote::F95RemoteData > findF95Data( AtlasID atlas_id ) { + ZoneScoped; //std::vector< std::string > data; std::optional< atlas::remote::F95RemoteData > data; //spdlog::info( "{}{}", title, developer ); - RapidTransaction() << "SELECT * FROM f95_zone_data WHERE atlas_id=?" << atlas_id >> + RapidTransaction() << "SELECT f95_id FROM f95_zone_data WHERE atlas_id = ?" << atlas_id >> [ &data ]( const F95ID f95_id ) { data = { f95_id }; }; return data; } diff --git a/atlas/core/database/remote/F95Data.hpp b/atlas/core/database/remote/F95Data.hpp index 0a36b4c7..4fafd03c 100644 --- a/atlas/core/database/remote/F95Data.hpp +++ b/atlas/core/database/remote/F95Data.hpp @@ -53,7 +53,7 @@ namespace atlas::remote const internal::F95Data* operator->() const { return data_ptr.get(); } }; - std::optional< atlas::remote::F95RemoteData > findF95Data( QString atlas_id ); + std::optional< atlas::remote::F95RemoteData > findF95Data( AtlasID atlas_id ); } // namespace atlas::remote #endif //ATLASGAMEMANAGER_F95DATA_HPP diff --git a/atlas/core/import/GameScanner.cpp b/atlas/core/import/GameScanner.cpp index cd67090c..3373d796 100644 --- a/atlas/core/import/GameScanner.cpp +++ b/atlas/core/import/GameScanner.cpp @@ -25,6 +25,20 @@ #include "core/utils/regex/regex.hpp" #include "core/utils/threading/pools.hpp" +std::optional< gl::GameListInfos > findGLInfo( std::filesystem::path folder ) +{ + ZoneScopedN( "Scan for GL data" ); + //Check if we have a GL_Infos.ini file + if ( gl::dirHasGLInfo( folder ) ) + { + atlas::logging::debug( "Found GL info for {}", folder ); + //We have one. + return gl::parse( folder / GL_INFO_FILENAME ); + } + else + return {}; +} + void runner( QPromise< GameImportData >& promise, const QString regex, @@ -35,66 +49,52 @@ void runner( ZoneScoped; if ( promise.isCanceled() ) return; atlas::utils::FileScanner scanner { folder }; - std::vector< std::filesystem::path > potential_executables { detectExecutables( scanner ) }; + const std::vector< std::filesystem::path > potential_executables { detectExecutables( scanner ) }; atlas::logging::debug( "Importing folder {} with base {} using regex {}", folder, base, regex ); if ( promise.isCanceled() ) return; if ( potential_executables.size() <= 0 ) throw NoExecutablesFound( folder ); - auto gl_info { [ &folder ]() -> gl::GameListInfos - { - ZoneScopedN( "Scan for GL data" ); - //Check if we have a GL_Infos.ini file - if ( gl::dirHasGLInfo( folder ) ) - { - atlas::logging::debug( "Found GL info for {}", folder ); - //We have one. - return gl::parse( folder / GL_INFO_FILENAME ); - } - else - return {}; - }() }; - - auto [ title, creator, version, engine ] = [ & ]() -> regex::GroupsOutput + const auto gl_info { findGLInfo( folder ) }; + const auto regex_output { regex::extractGroups( regex, QString::fromStdString( folder.string() ) ) }; + + const bool gl_has_f95_thread { gl_info.has_value() && gl_info->f95_thread_id != INVALID_F95_ID }; + + std::optional< atlas::remote::F95RemoteData > f95_data { std::nullopt }; + std::optional< atlas::remote::AtlasRemoteData > atlas_data { std::nullopt }; + + if ( gl_has_f95_thread ) { - ZoneScopedN( "Scan for Remote data" ); - if ( gl_info.f95_thread_id == INVALID_F95_ID ) - { - //atlas::logging::warn( "Found GL info but it had an invalid F95 id!" ); - //Unable to do anything with this - //TODO: Try the SHORT_ID from the atlas_id stuff to see if we can get a name match from the title. - return regex::extractGroups( regex, QString::fromStdString( folder.string() ) ); - } - else - { - //Try to find the thread info - if ( !atlas::remote::hasF95DataFor( gl_info.f95_thread_id ) ) - { - return regex::extractGroups( regex, QString::fromStdString( folder.string() ) ); - } - else - { - try - { - atlas::remote::F95RemoteData f95_data { gl_info.f95_thread_id }; - atlas::remote::AtlasRemoteData atlas_data { f95_data->atlas_id }; - - //Grab version info from gl_infos directly - regex::GroupsOutput output { - atlas_data->title, atlas_data->creator, gl_info.version, atlas_data->engine - }; - - return output; - } - catch ( std::exception& e ) - { - atlas::logging::warn( "Failed to get remote data in scanner: {}", e.what() ); - return regex::extractGroups( regex, QString::fromStdString( folder.string() ) ); - } - } - } - }(); + ZoneScopedN( "Check for remote data - GLInfos.ini" ); + const F95ID f95_id { gl_info->f95_thread_id }; + + f95_data = atlas::remote::findF95Data( f95_id ); + + if ( f95_data.has_value() ) atlas_data = atlas::remote::findAtlasData( f95_data.value()->atlas_id ); + } + + //Did GL have a f95 thread id? + if ( !atlas_data.has_value() ) + { + ZoneScopedN( "Check for remote data" ); + //Nope. Try the alternative search method. + const auto& [ title, creator, version, engine ] = regex_output; + atlas_data = atlas::remote::findAtlasData( title, creator ); + } + + regex::GroupsOutput final_output { regex_output }; + + auto& [ title, creator, version, engine ] = final_output; + + if ( atlas_data.has_value() ) + { + auto& a_data { atlas_data.value() }; + title = a_data->title; + creator = a_data->creator; + version = a_data->version; + engine = a_data->engine; + } //Search for banners std::array< QString, BannerType::SENTINEL > banners {}; @@ -132,11 +132,8 @@ void runner( //Check if images are available locally, if not, get the url //Download image so we can store it later - std::optional< atlas::remote::AtlasRemoteData > atlas_data = atlas::remote::findAtlasData( title, creator ); if ( atlas_data.has_value() ) { - std::optional< atlas::remote::F95RemoteData > f95_data = - atlas::remote::findF95Data( QString::number( atlas_data.value()->atlas_id ) ); banners[ Normal ] = f95_data.value()->banner_url; } } @@ -173,12 +170,6 @@ void runner( //If the gl_info has a f95_id then we can use that. auto atlas_id { INVALID_ATLAS_ID }; - if ( gl_info.f95_thread_id != INVALID_F95_ID ) - { - //We can try to get the atlas_id from the f95 thread if it's valid. - atlas_id = atlas::remote::atlasIDFromF95Thread( gl_info.f95_thread_id ); - } - if ( engine.isEmpty() ) { //Set engine if it's not set already via the regex @@ -198,7 +189,7 @@ void runner( potential_executables.at( 0 ), std::move( banners ), std::move( previews ), - std::move( gl_info ), + gl_info.has_value() ? std::move( gl_info.value() ) : gl::GameListInfos(), game_id, atlas_id, }; @@ -238,12 +229,13 @@ try TracyCZoneEnd( regex_Tracy ); //Is the directory we just found already in the database? - std::optional< RecordID > path_id; - RapidTransaction() << "SELECT record_id FROM versions WHERE game_path = ?" << itter->path() >> path_id; - if ( path_id.has_value() ) continue; if ( result ) { + std::optional< RecordID > path_id; + RapidTransaction() << "SELECT record_id FROM versions WHERE game_path = ?" << itter->path() >> path_id; + if ( path_id.has_value() ) continue; + ++directories_left; //The regex was a match. We can now process this directory further futures.emplace_back( QtConcurrent:: diff --git a/atlas/core/import/Importer.cpp b/atlas/core/import/Importer.cpp index bf83f229..0d50e42f 100644 --- a/atlas/core/import/Importer.cpp +++ b/atlas/core/import/Importer.cpp @@ -144,7 +144,7 @@ namespace internal { atlas_id = atlas_data.value()->atlas_id; std::optional< atlas::remote::F95RemoteData > f95_data = - atlas::remote::findF95Data( QString::number( atlas_data.value()->atlas_id ) ); + atlas::remote::findF95Data( atlas_data.value()->atlas_id ); gl_infos.f95_thread_id = f95_data.value()->f95_id; record.connectAtlasData( atlas_id ); diff --git a/atlas/ui/mainwindow.cpp b/atlas/ui/mainwindow.cpp index e92edff7..a7b992f0 100644 --- a/atlas/ui/mainwindow.cpp +++ b/atlas/ui/mainwindow.cpp @@ -417,9 +417,7 @@ void MainWindow::on_actionUpdateMeta_triggered() //const atlas::remote::AtlasRemoteData& atlas_data { game->atlas_data.value() }; const AtlasID atlas_id { atlas_data.value()->atlas_id }; - std::optional< atlas::remote::F95RemoteData > f95_data { - atlas::remote::findF95Data( QString::number( atlas_id ) ) - }; + std::optional< atlas::remote::F95RemoteData > f95_data { atlas::remote::findF95Data( atlas_id ) }; if ( !f95_data.has_value() ) continue; From 4d1c2492a9b87b8004e3d0f30b8f64e7c107b6fd Mon Sep 17 00:00:00 2001 From: kj16609 Date: Tue, 19 Dec 2023 11:29:36 -0500 Subject: [PATCH 40/43] Fixup some of the error handling for sqlite_prepare_v2 --- atlas/core/database/Binder.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/atlas/core/database/Binder.cpp b/atlas/core/database/Binder.cpp index 19bf1d1e..838dd494 100644 --- a/atlas/core/database/Binder.cpp +++ b/atlas/core/database/Binder.cpp @@ -9,10 +9,15 @@ Binder::Binder( const std::string_view sql ) { ZoneScoped; + const char* unused { nullptr }; const auto prepare_ret { - sqlite3_prepare_v2( &Database::ref(), sql.data(), static_cast< int >( sql.size() + 1 ), &stmt, nullptr ) + sqlite3_prepare_v2( &Database::ref(), sql.data(), static_cast< int >( sql.size() + 1 ), &stmt, &unused ) }; + if ( unused != nullptr && strlen( unused ) > 0 ) + throw DatabaseException( format_ns:: + format( "Query had unused portions of the input. Unused: \"{}\"", unused ) ); + if ( stmt == nullptr ) throw DatabaseException( format_ns:: format( "Failed to prepare stmt, {}", sqlite3_errmsg( &Database::ref() ) ) ); From a8c8ac9585b56e1ce1fdd07a0b83e489cce2f0ed Mon Sep 17 00:00:00 2001 From: kj16609 Date: Tue, 19 Dec 2023 11:29:50 -0500 Subject: [PATCH 41/43] bump tracy version --- dependencies/tracy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/tracy b/dependencies/tracy index 897aec5b..37aff70d 160000 --- a/dependencies/tracy +++ b/dependencies/tracy @@ -1 +1 @@ -Subproject commit 897aec5b062664d2485f4f9a213715d2e527e0ca +Subproject commit 37aff70dfa50cf6307b3fee6074d627dc2929143 From 3c45e938a04e4629dbfc0583ec03065ff7b79bd1 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Thu, 14 Mar 2024 11:20:53 -0400 Subject: [PATCH 42/43] Fixes bug with leftover check after query thinking \t and \n were valid characters --- atlas/core/database/Binder.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/atlas/core/database/Binder.cpp b/atlas/core/database/Binder.cpp index 838dd494..46259a51 100644 --- a/atlas/core/database/Binder.cpp +++ b/atlas/core/database/Binder.cpp @@ -15,8 +15,22 @@ Binder::Binder( const std::string_view sql ) }; if ( unused != nullptr && strlen( unused ) > 0 ) - throw DatabaseException( format_ns:: - format( "Query had unused portions of the input. Unused: \"{}\"", unused ) ); + { + //Check if the string is just empty (\n or \t) + const std::string_view leftovers { unused }; + auto itter { leftovers.begin() }; + while ( itter != leftovers.end() ) + { + if ( *itter == '\n' || *itter == '\t' ) + { + ++itter; + continue; + } + else + throw DatabaseException( + format_ns::format( "Query had unused portions of the input. Unused: \"{}\"", unused ) ); + } + } if ( stmt == nullptr ) throw DatabaseException( format_ns:: From 23c6c0145c7fa8be11bc427f25d9e0d502793a01 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Fri, 15 Mar 2024 09:44:09 -0400 Subject: [PATCH 43/43] Fixes bug with File Scanner not properly catching and rethrowing exceptions --- atlas/core/utils/FileScanner.cpp | 119 ++++++++++++++++++------------- atlas/core/utils/FileScanner.hpp | 8 ++- 2 files changed, 75 insertions(+), 52 deletions(-) diff --git a/atlas/core/utils/FileScanner.cpp b/atlas/core/utils/FileScanner.cpp index 1e816371..f73a6918 100644 --- a/atlas/core/utils/FileScanner.cpp +++ b/atlas/core/utils/FileScanner.cpp @@ -17,9 +17,13 @@ namespace atlas::utils FileInfo FileScannerGenerator::operator()() { if ( m_h.done() ) throw AtlasException( "FileScannerGenerator is done but operator was still called" ); + + if ( m_h.promise().exception ) std::rethrow_exception( m_h.promise().exception ); + m_h(); if ( m_h.promise().exception ) std::rethrow_exception( m_h.promise().exception ); + if ( m_h.promise().value.has_value() ) return m_h.promise().value.value(); else @@ -35,73 +39,86 @@ namespace atlas::utils FileScannerGenerator scan_files( const std::filesystem::path path ) { - if ( !std::filesystem::exists( path ) ) + try { - atlas::logging::error( "Expected path does not exist: {}", path.string() ); - throw AtlasException( format_ns::format( "Path {} does not exist.", path ).c_str() ); - } - - auto dir_empty = []( const std::filesystem::path& dir_path ) -> bool - { return std::filesystem::directory_iterator( dir_path ) == std::filesystem::directory_iterator(); }; + if ( !std::filesystem::exists( path ) ) + { + atlas::logging::error( "Expected path does not exist: {}", path.string() ); + throw AtlasException( format_ns::format( "Path {} does not exist.", path ).c_str() ); + } - if ( dir_empty( path ) ) co_return FileInfo { path, path, 0, 0 }; + auto dir_empty = []( const std::filesystem::path& dir_path ) -> bool + { return std::filesystem::directory_iterator( dir_path ) == std::filesystem::directory_iterator(); }; - std::queue< std::pair< std::filesystem::path, std::uint8_t > > dirs {}; + if ( dir_empty( path ) ) co_return FileInfo { path, path, 0, 0 }; - dirs.push( { path, 0 } ); + std::queue< std::pair< std::filesystem::path, std::uint8_t > > dirs {}; - while ( dirs.size() > 0 ) - { - const auto [ dir, depth ] { std::move( dirs.front() ) }; - dirs.pop(); - std::vector< std::filesystem::path > nested_dirs; + dirs.push( { path, 0 } ); - //Recurse through the directory. - for ( auto itter = std::filesystem::directory_iterator( dir ); - itter != std::filesystem::directory_iterator(); ) + while ( dirs.size() > 0 ) { - if ( itter->is_directory() ) - { - //Add directory to scan list. - nested_dirs.emplace_back( *itter ); - ++itter; - continue; - } - - FileInfo info { - *itter, path, itter->is_regular_file() ? itter->file_size() : 0, std::uint8_t( depth + 1 ) - }; + const auto [ dir, depth ] { std::move( dirs.front() ) }; + dirs.pop(); + std::vector< std::filesystem::path > nested_dirs; - ++itter; + //Recurse through the directory. + for ( auto itter = std::filesystem::directory_iterator( dir ); + itter != std::filesystem::directory_iterator(); ) + { + if ( itter->is_directory() ) + { + //Add directory to scan list. + nested_dirs.emplace_back( *itter ); + ++itter; + continue; + } + + FileInfo info { + *itter, path, itter->is_regular_file() ? itter->file_size() : 0, std::uint8_t( depth + 1 ) + }; - //If we are at the last file and there are no more directories to scan then return. - if ( itter == std::filesystem::directory_iterator() && dirs.size() == 0 && nested_dirs.size() == 0 ) - co_return std::move( info ); - else - co_yield std::move( info ); - } + ++itter; - //Add the nested dirs to the scanlist and yield them - for ( std::size_t i = 0; i < nested_dirs.size(); ++i ) - { - // Check if the directory is empty and if it's not then add it to the scan queue. - if ( !dir_empty( nested_dirs.at( i ) ) ) - { - dirs.push( { nested_dirs.at( i ), depth + 1 } ); - co_yield FileInfo { nested_dirs.at( i ), path, 0, std::uint8_t( depth + 1 ) }; + //If we are at the last file and there are no more directories to scan then return. + if ( itter == std::filesystem::directory_iterator() && dirs.size() == 0 && nested_dirs.size() == 0 ) + co_return std::move( info ); + else + co_yield std::move( info ); } - else + + //Add the nested dirs to the scanlist and yield them + for ( std::size_t i = 0; i < nested_dirs.size(); ++i ) { - //Dir is empty. If we don't have anything else to process then return. Else yield. - if ( i == nested_dirs.size() - 1 && dirs.size() == 0 ) - co_return FileInfo { nested_dirs.at( i ), path, 0, std::uint8_t( depth + 1 ) }; - else + // Check if the directory is empty and if it's not then add it to the scan queue. + if ( !dir_empty( nested_dirs.at( i ) ) ) + { + dirs.push( { nested_dirs.at( i ), depth + 1 } ); co_yield FileInfo { nested_dirs.at( i ), path, 0, std::uint8_t( depth + 1 ) }; + } + else + { + //Dir is empty. If we don't have anything else to process then return. Else yield. + if ( i == nested_dirs.size() - 1 && dirs.size() == 0 ) + co_return FileInfo { nested_dirs.at( i ), path, 0, std::uint8_t( depth + 1 ) }; + else + co_yield FileInfo { nested_dirs.at( i ), path, 0, std::uint8_t( depth + 1 ) }; + } } } - } - throw AtlasException( "Managed to escape loop in coroutine scan_files" ); + atlas::logging::critical( "Managed to escape loop in coroutine scan_files" ); + } + catch ( std::exception& e ) + { + atlas::logging::error( "Exception caught in coroutine: {}", e.what() ); + co_return std::current_exception(); + } + catch ( ... ) + { + atlas::logging::error( "Exception caught in coroutine: ..." ); + co_return std::current_exception(); + } } #ifdef __GNUC__ diff --git a/atlas/core/utils/FileScanner.hpp b/atlas/core/utils/FileScanner.hpp index 9e515770..bb404ec8 100644 --- a/atlas/core/utils/FileScanner.hpp +++ b/atlas/core/utils/FileScanner.hpp @@ -61,7 +61,7 @@ namespace atlas::utils std::suspend_always final_suspend() noexcept { return {}; } - void unhandled_exception() { std::rethrow_exception( exception ); } + void unhandled_exception() { atlas::logging::critical( "Unhandled exception!" ); } void return_value( FileInfo&& from ) { @@ -71,6 +71,12 @@ namespace atlas::utils value = std::move( from ); } + void return_value( std::exception_ptr&& from ) + { + exception = from; + atlas::logging::error( "Exception thrown in file scanner coroutine!" ); + } + std::suspend_always yield_value( FileInfo from ) { if ( from.filename == "" )