diff --git a/msvc/wallet.vcxproj b/msvc/wallet.vcxproj index d4424b6..c4f4483 100644 --- a/msvc/wallet.vcxproj +++ b/msvc/wallet.vcxproj @@ -150,6 +150,7 @@ + diff --git a/src/ISqlite3.h b/src/ISqlite3.h index 87a2750..4c31833 100644 --- a/src/ISqlite3.h +++ b/src/ISqlite3.h @@ -1,13 +1,8 @@ #pragma once #include "ILog.h" -// this is pure virtual interface base class between sqlite3, which speaks only C and utf8 char[] +// this is an interface base class between sqlite3, which speaks only C and utf8 char[] // and wxWidgets which speaks only C++ and unicode strings. -// Usage: Call the factory function std::shared_ptr sqlite3_open(const char *) to get a shared -// pointer to the // Sqlite3 database object. Then call the factory function -// sqlite3_prepare(std::shared_ptr, const char *) to get a unique pointer to -// a compiled SQL statement - // Its primary purpose is to avoid code that needs both the wxWidgets header files, // and the sqlite3.h header file. // @@ -25,8 +20,6 @@ // substantially more difficult in C++14, because one is effectively rolling one's own // unique pointer. // -// It is therefore easier to implement a pure virtual base class with a virtual destructor and -// factory function that returns a smart pointer to a member of the derived implementation // /* This code is at a low level abstraction, because it provides low level C++ interface to inherently low level C It is intended to be wrapped in higher level code that does not know about the nuts and bolts of sqlite3, but which supports throwing, templated functions, and all that.*/ diff --git a/src/db_accessors.cpp b/src/db_accessors.cpp new file mode 100644 index 0000000..c076ef9 --- /dev/null +++ b/src/db_accessors.cpp @@ -0,0 +1,45 @@ +#include "stdafx.h" +namespace ro { + static constexpr char SrcFilename[]{ "src/db_accessors.cpp" }; + + dbconnect::dbconnect(const wxFileName& filename) { + if (!filename.IsOk() || !filename.HasName() || !filename.HasExt()) throw + MyException("unexpected file name", __LINE__, __func__, SrcFilename); + if (!filename.FileExists())throw MyException( + std::string(filename.GetFullPath().ToUTF8()) + " does not exist.", + __LINE__, __func__, SrcFilename); + this->reset(Sqlite3_open(filename.GetFullPath().ToUTF8())); + } + + // move constructor + dbconnect::dbconnect(dbconnect&& p) noexcept :std::unique_ptr(p.release()) { + } + + // Move assignment + dbconnect& dbconnect::operator=(dbconnect&& p) noexcept { + std::unique_ptr::reset(p.release()); + return *this; + } + + sql::sql(const std::unique_ptr& p, const char* sz) : + std::unique_ptr(sqlite3_prepare(p.get(), sz)) { + } + + // move constructor + sql::sql(sql&& p) noexcept :std::unique_ptr(p.release()) { + } + + // Move assignment + sql& sql::operator=(sql&& p) noexcept { + std::unique_ptr::reset(p.release()); + return *this; + } + + sql::sql(Icompiled_sql* p) : + std::unique_ptr(p) { + } + + sql::sql(std::unique_ptr&& p) : + std::unique_ptr(p.release()) { + } +} \ No newline at end of file diff --git a/src/db_accessors.h b/src/db_accessors.h index a3f601b..9759d42 100644 --- a/src/db_accessors.h +++ b/src/db_accessors.h @@ -15,26 +15,42 @@ namespace ro { static_assert(is_sqlite3_field_type::value); + //Owns a database connection and closes it when it is deconstructed + //Has move semantics + struct dbconnect : std::unique_ptr { + dbconnect(const wxFileName&); //And here we wrap our Cish implementation in C++ish implementation + dbconnect() = delete; + ~dbconnect() = default; //Our base class takes care of it + // copy constructor + dbconnect(const dbconnect& a) = delete; //Move semantics + // move constructor + dbconnect(dbconnect&& p) noexcept; + // copy assignment + dbconnect& operator=(const dbconnect) = delete; //Move semantics + // Move assignment + dbconnect& operator=(dbconnect&& p) noexcept; + }; + //Owns a compiled sql statement and destroys it when it is deconstructed. //Has move semantics. class sql : public std::unique_ptr { public: class monostate {}; + sql() = delete; sql(ISqlite3* p, const char* sz) :std::unique_ptr(sqlite3_prepare(p, sz)) {} - sql(const std::unique_ptr& p, const char* sz) :std::unique_ptr(sqlite3_prepare(p.get(), sz)) {} + sql(const std::unique_ptr& p, const char* sz); // copy constructor sql(const sql& a) = delete; // move constructor - sql(sql&& p) noexcept :std::unique_ptr(p.release()) { } + sql(sql&& p) noexcept; // copy assignment sql& operator=(const sql) = delete; // Move assignment - sql& operator=(sql&& p) noexcept { - std::unique_ptr::reset(p.release()); - } - sql(Icompiled_sql* p) :std::unique_ptr(p) {} - sql(std::unique_ptr&& p) :std::unique_ptr(p.release()) { } + sql& operator=(sql&& p) noexcept; + sql(Icompiled_sql* p); + sql(std::unique_ptr&& p); ~sql() = default; + template auto column(int i) const { if constexpr (ro::blob_type) { auto st = (*this)->Isqlite3_column_blob(i); @@ -180,45 +196,13 @@ public: }; class sql_insert_name { - ro::sql csql_begin; ro::sql csql_into_names; - ro::sql csql_namekey_into_keys; - ro::sql csql_commit; - ro::sql csql_rollback; public: sql_insert_name(ISqlite3* p) : - csql_begin(p, R"|(BEGIN IMMEDIATE;)|"), - csql_into_names(p, R"|(INSERT OR FAIL INTO "Names" VALUES(NULL, ?1);)|"), - csql_namekey_into_keys(p, R"|(INSERT OR FAIL INTO "Keys" VALUES(NULL, ?1, last_insert_rowid(), 1);)|"), - /* NULL triggers special nonstandard Sqlite behavior to autogenerate a unique ROWID - Because we explicitly make the ROWID a named field in the table to avoid too much - non standard SQlite behavior, we must explicitly provide a placeholder in the INSERT - statement. So at least we only get special SQlite3 behavior when we deliberately - invoke it. */ - csql_commit(p, R"|(COMMIT;)|"), - csql_rollback(p, R"|(ROLLBACK;)|") - {} + csql_into_names(p, R"|(INSERT OR FAIL INTO "UserZookoIDs" VALUES(?1, ?2);)|"){} sql_insert_name(const std::unique_ptr& p) : sql_insert_name(p.get()) {} void operator()(const char* psz, const ristretto255::point& pt) { - csql_begin.do_one(); - try { - csql_into_names.do_one(psz); - csql_namekey_into_keys.do_one(pt); - } - catch (const MyException& e) { - csql_rollback.do_one(); - if (e.what_num() == 19) { - throw MyException("Name already in database"); - } - else { - throw; - } - } - catch (const std::exception &) { - csql_rollback.do_one(); - throw; - } - csql_commit.do_one(); + csql_into_names.do_one(psz,pt); } }; @@ -234,7 +218,5 @@ public: } }; - - constexpr auto WALLET_FILE_IDENTIFIER (0x56d34bc5a655dd1fi64); constexpr auto WALLET_FILE_SCHEMA_VERSION_0_0(1); diff --git a/src/display_wallet.cpp b/src/display_wallet.cpp index 5fbdadd..b1dbacf 100644 --- a/src/display_wallet.cpp +++ b/src/display_wallet.cpp @@ -1,75 +1,32 @@ #include "stdafx.h" using ro::base58; static constexpr char SrcFilename[]{ "src/display_wallet.cpp" }; + display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) : wxPanel(parent, myID_WALLET_UI, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, wxT("Wallet")), - m_db(nullptr), m_DisplayWalletEditMenu(1) + m_db(walletfile), m_DisplayWalletEditMenu(1), + m_read_names_and_keys(m_db, R"|(SELECT * FROM "UserZookoIDs"; )|"), + m_read_from_misc(m_db), + m_insert_name(m_db), + m_find_position(m_db, R"|( SELECT COUNT(*) FROM UserZookoIDs +WHERE LOWER("name")value() != WALLET_FILE_IDENTIFIER)throw MyException(sz_unrecognizable_wallet_file_format); - if (!(*m_read_from_misc)(2) || m_read_from_misc->value() != WALLET_FILE_SCHEMA_VERSION_0_0 || !(*m_read_from_misc)(4))throw MyException(sz_unrecognized_wallet_schema); - m_MasterSecret= *(m_read_from_misc->value()); + if (!m_read_from_misc(1) || m_read_from_misc.value() != WALLET_FILE_IDENTIFIER)throw MyException(sz_unrecognizable_wallet_file_format); + if (!m_read_from_misc(2) || m_read_from_misc.value() != WALLET_FILE_SCHEMA_VERSION_0_0 || !m_read_from_misc(4))throw MyException(sz_unrecognized_wallet_schema); + m_MasterSecret= *(m_read_from_misc.value()); ILogMessage(std::format("\t\tmaster secret: #{}", ro::base58(m_MasterSecret).operator const char* ()).c_str()); if(!m_MasterSecret.valid()) throw MyException(sz_cold_wallets_not_yet_implemented, __LINE__, __func__, SrcFilename); auto sizer = new wxBoxSizer(wxHORIZONTAL); m_lSizer = new wxBoxSizer(wxVERTICAL); m_rSizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(m_lSizer, 0, wxGROW, 4); - sizer->Add(m_rSizer, 50, wxGROW, 4); + sizer->Add(m_lSizer, 0, 0, 4); + sizer->Add(m_rSizer, 50, 0, 4); SetSizer(sizer); - try { - while (m_read_names_and_keys->step() == Icompiled_sql::ROW) { - std::string name = m_read_names_and_keys->column(0); - auto pubkey = *(m_read_names_and_keys->column(1)); - if (m_MasterSecret(name).timesBase() != pubkey)throw MyException(std::string(sz_public_key_of) + name + sz_fails_to_correspond); - m_lSizer->Add( - new wxStaticText( - this, - wxID_ANY, - name, - wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT | wxST_ELLIPSIZE_END - ), - 10, - wxEXPAND | // make horizontally stretchable - wxALL, // and make border all around - 2); - m_rSizer->Add( - new wxStaticText( - this, - wxID_ANY, - "#" + base58(pubkey).operator std::string(), - wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT | wxST_ELLIPSIZE_END - ), - 10, - wxEXPAND | // make horizontally stretchable - wxALL, // and make border all around - 2); - } - } - catch (const MyException& e) { - throw MyException(e, __LINE__, __func__, SrcFilename); - // sql exceptions tend to be unintelligible and excessively generic - // because unclear what statement provoked them. - } - catch (const std::exception& e) { - throw MyException(e, __LINE__, __func__, SrcFilename); - } - catch (...) { - szError = sz_unknown_error; - throw MyException(sz_unknown_error, __LINE__, __func__, SrcFilename); - } - Bind(wxEVT_CLOSE_WINDOW, &display_wallet::OnClose, this); + refresh_from_database(); this->SetSize(this->GetParent()->GetClientSize()); + Bind(wxEVT_CLOSE_WINDOW, &display_wallet::OnClose, this); singletonFrame->m_LastUsedWallet.Assign(walletfile); m_DisplayWalletEditMenu.Menu->Append( @@ -93,8 +50,6 @@ display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) : szError = sz_unknown_error; throw MyException(sz_unknown_error, __LINE__, __func__, SrcFilename); } - - } display_wallet::~display_wallet() { @@ -104,6 +59,39 @@ void display_wallet::close_menu_event_handler(wxCommandEvent& event) { Close(true); } + +struct display_wallet::cleanup { + cleanup() = delete; + // copy constructor + cleanup(const cleanup& a) = delete; + // move constructor + cleanup(cleanup&& p) = delete; + // copy assignment + cleanup& operator=(cleanup) = delete; + // Move assignment + cleanup& operator=(cleanup&&) = delete; + display_wallet* m_win; + wxSizer* sizer; + wxSize dirty_area; + cleanup(display_wallet* win) : + m_win(win), + sizer(win->GetSizer()), + dirty_area(sizer->ComputeFittingClientSize(singletonFrame)) + { + assert(sizer); + } + ~cleanup() { + auto desired_size = sizer->ComputeFittingClientSize(singletonFrame); + dirty_area.IncTo(desired_size); + //singletonFrame->SetMinClientSize(desired_size); + auto clientSize = singletonFrame->GetClientSize(); + desired_size.IncTo(clientSize); + if (desired_size.GetHeight() > clientSize.GetHeight() + || desired_size.GetWidth() > clientSize.GetWidth() + )singletonFrame->SetClientSize(desired_size); + } +}; + void display_wallet::add_name_event_handler(wxCommandEvent& event) { wxTextEntryDialog dialog(this, R"("A Zooko name has a human readable name, @@ -114,13 +102,37 @@ and a public key defined by the name and the wallet master secret)", if (dialog.ShowModal() == wxID_OK) { std::string zookoNickname(dialog.GetValue().ToUTF8()); - sql_insert_name insert_name(m_db); auto zookoNickname_psz = zookoNickname.c_str(); - insert_name( + auto pubkey = m_MasterSecret(zookoNickname_psz).timesBase(); + m_insert_name( zookoNickname_psz, - m_MasterSecret(zookoNickname_psz).timesBase() + pubkey ); + m_find_position.read_one(zookoNickname_psz); + auto position = m_find_position.column(0); + cleanup cln(this); + m_lSizer->Insert(position, + new wxStaticText( + this, + wxID_ANY, + zookoNickname_psz, + wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT | wxEXPAND | wxFIXED_MINSIZE | wxST_ELLIPSIZE_END + ), + 10, + wxALL, // and make border all around + 2); + m_rSizer->Insert(position, + new wxStaticText( + this, + wxID_ANY, + "#" + base58(pubkey).operator std::string(), + wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT | wxEXPAND | wxFIXED_MINSIZE | wxST_ELLIPSIZE_END + ), + 10, + wxALL, // and make border all around + 2); } + } @@ -134,3 +146,47 @@ void display_wallet::OnClose(wxCloseEvent& event) { if (singletonFrame->m_panel ==this)singletonFrame->m_panel = nullptr; } + +void display_wallet::refresh_from_database() { + cleanup cln(this); + try { + m_read_names_and_keys.reset(); + while (m_read_names_and_keys.step() == Icompiled_sql::ROW) { + std::string name = m_read_names_and_keys.column(0); + auto pubkey = *(m_read_names_and_keys.column(1)); + if (m_MasterSecret(name).timesBase() != pubkey)throw MyException(std::string(sz_public_key_of) + name + sz_fails_to_correspond); + m_lSizer->Add( + new wxStaticText( + this, + wxID_ANY, + name, + wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT | wxEXPAND| wxFIXED_MINSIZE| wxST_ELLIPSIZE_END + ), + 10, + wxALL, // and make border all around + 2); + m_rSizer->Add( + new wxStaticText( + this, + wxID_ANY, + "#" + base58(pubkey).operator std::string(), + wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT | wxEXPAND | wxFIXED_MINSIZE | wxST_ELLIPSIZE_END + ), + 10, + wxALL, // and make border all around + 2); + } + } + catch (const MyException& e) { + throw MyException(e, __LINE__, __func__, SrcFilename); + // sql exceptions tend to be unintelligible and excessively generic + // because unclear what statement provoked them. + } + catch (const std::exception& e) { + throw MyException(e, __LINE__, __func__, SrcFilename); + } + catch (...) { + szError = sz_unknown_error; + throw MyException(sz_unknown_error, __LINE__, __func__, SrcFilename); + } +} diff --git a/src/display_wallet.h b/src/display_wallet.h index 0080c5e..7290518 100644 --- a/src/display_wallet.h +++ b/src/display_wallet.h @@ -5,10 +5,14 @@ public: display_wallet(wxWindow*, wxFileName&); ~display_wallet(); private: + struct cleanup; typedef MenuLink MenuLink; - std::unique_ptr m_db; - std::unique_ptr m_read_from_misc; - std::unique_ptr m_read_names_and_keys; + ro::dbconnect m_db; + sql_read_from_misc m_read_from_misc; + ro::sql m_read_names_and_keys; + sql_insert_name m_insert_name; + ro::sql m_find_position; + ristretto255::CMasterSecret m_MasterSecret; wxBoxSizer* m_lSizer; wxBoxSizer* m_rSizer; @@ -16,4 +20,5 @@ private: void add_name_event_handler(wxCommandEvent&); void OnClose(wxCloseEvent& event); wxMenuTracker m_DisplayWalletEditMenu; + void refresh_from_database(); }; diff --git a/src/frame.cpp b/src/frame.cpp index 2ac8cb9..4ae63cf 100644 --- a/src/frame.cpp +++ b/src/frame.cpp @@ -10,27 +10,50 @@ wxMenuTracker::wxMenuTracker(const int i) : Menu(new wxMenu), MenuPosition(i) {} wxMenu* wxMenuTracker::InitialAndFinal[]{ nullptr, new wxMenu, nullptr }; void wxMenuTracker::Replace() { - singletonFrame->GetMenuBar()->Replace( - MenuPosition, - Menu, - menu_strings[MenuPosition].head - ); - singletonFrame->GetMenuBar()->EnableTop(MenuPosition, true); //enable edit menu. + auto menuBar = singletonFrame->GetMenuBar(); + if ((menuBar->GetMenu(MenuPosition) != Menu)) { + menuBar->Replace( + MenuPosition, + Menu, + menu_strings[MenuPosition].head + ); + menuBar->EnableTop(MenuPosition, true); //enable edit menu. + } }; wxMenuTracker::~wxMenuTracker() { - auto menu_bar = singletonFrame->GetMenuBar(); - if (menu_bar->GetMenu(MenuPosition) == Menu) { + auto menuBar = singletonFrame->GetMenuBar(); + if (menuBar->GetMenu(MenuPosition) == Menu) { assert(InitialAndFinal[MenuPosition]); - menu_bar->Replace( + menuBar->Replace( MenuPosition, InitialAndFinal[MenuPosition], menu_strings[MenuPosition].head ); - menu_bar->EnableTop(MenuPosition, false); + menuBar->EnableTop(MenuPosition, false); delete Menu; } }; +void wxMenuTracker::check_dynamic_menus_absent() { + if constexpr (debug_mode) { + // Check that the values of all replaceable menus + // are at their default values as if the window handling them + // had just been destroyed and not yet replaced. + auto menuBar = singletonFrame->GetMenuBar(); + for (int i = 0; i < std::size(InitialAndFinal); i++) { + assert( + !InitialAndFinal[i] + || + ( + InitialAndFinal[i] == menuBar->GetMenu(i) + && + !menuBar->IsEnabledTop(i) + ) + ); + } + } +} + // ---------------------------------------------------------------------------- // frame // ---------------------------------------------------------------------------- @@ -199,21 +222,10 @@ Frame::Frame(const wxString& wxs) SetMenuBar(menuBar); menuBar->EnableTop(1, false); //disable edit menu. if constexpr (debug_mode) { - // Check that the initial values of all replaceable menus - // are at their default values as if the window handling them - // had just been destroyed and not yet replaced. - for (int i = 0; i < std::size(wxMenuTracker::InitialAndFinal); i++) { - assert( - !wxMenuTracker::InitialAndFinal[i] - || - ( - wxMenuTracker::InitialAndFinal[i] == menuBar->GetMenu(i) - && - !menuBar->IsEnabledTop(i) - ) - ); - - } + wxMenuTracker::check_dynamic_menus_absent(); + // Because the initial values of the dynamic menus are not being deleted by the child windows + // that own the modified windows, they need to be the same as the final values, + // which is to say, the values in wxMenuTracker::InitialAndFinal[] } CreateStatusBar(); // child controls @@ -374,17 +386,16 @@ SELECT "Keys".pubkey AS pubkey FROM "Names" INNER JOIN "Keys" ON "Names"."ROWID"="Keys"."id" AND "Keys"."use"=1 -ORDER BY LOWER("name"), "name" -COLLATE BINARY; +ORDER BY LOWER("name"), "name" COLLATE BINARY; COMMIT; BEGIN IMMEDIATE TRANSACTION; CREATE TRIGGER InsertUserZookoID INSTEAD OF INSERT ON UserZookoIDs FOR EACH ROW BEGIN - INSERT OR FAIL INTO "Names" VALUES( + INSERT OR ROLLBACK INTO "Names" VALUES( NULL, NEW."name" ); - INSERT OR FAIL INTO "Keys" VALUES( + INSERT OR ROLLBACK INTO "Keys" VALUES( NULL, NEW."pubkey", last_insert_rowid(), @@ -555,7 +566,6 @@ void Frame::OnMenuOpen(wxMenuEvent& evt) { Frame::~Frame() { assert(singletonFrame == this); - singletonFrame = nullptr; wxConfigBase& Config = singletonApp->m_Config; StorePositionToConfig(); Config.SetPath(wxT("/TipOfTheDay")); @@ -567,21 +577,7 @@ Frame::~Frame() { Config.SetPath(wxT("/")); Config.Flush(); if constexpr (debug_mode) { - // Check that the final values of all replaceable menus - // are at what they should be because the window handling - // them should have been destroyed. - auto menuBar = this->GetMenuBar(); - for (int i = 0; i < std::size(wxMenuTracker::InitialAndFinal); i++) { - assert( - !wxMenuTracker::InitialAndFinal[i] - || - ( - wxMenuTracker::InitialAndFinal[i] == menuBar->GetMenu(i) - && - !menuBar->IsEnabledTop(i) - ) - ); - - } + wxMenuTracker::check_dynamic_menus_absent(); } + singletonFrame = nullptr; } diff --git a/src/frame.h b/src/frame.h index 40800b5..d882faf 100644 --- a/src/frame.h +++ b/src/frame.h @@ -21,6 +21,7 @@ struct wxMenuTracker { // the frame initializer. In the debug build, congruence between the menu bar // and InitialAndFinal should be checked with assert. static wxMenu* InitialAndFinal[3]; + static void check_dynamic_menus_absent(); wxMenuTracker(wxMenuTracker&&) = delete; // Move constructor wxMenuTracker(const wxMenuTracker&) = delete; // Copy constructor wxMenuTracker& operator=(wxMenuTracker&&) = delete; // Move assignment. diff --git a/src/welcome_to_rhocoin.cpp b/src/welcome_to_rhocoin.cpp index 449c39d..b03bbcb 100644 --- a/src/welcome_to_rhocoin.cpp +++ b/src/welcome_to_rhocoin.cpp @@ -138,6 +138,7 @@ welcome_to_rhocoin::welcome_to_rhocoin( Bind(wxEVT_CLOSE_WINDOW, &welcome_to_rhocoin::OnClose, this); assert(singletonWelcome == nullptr); singletonWelcome = this; + singletonFrame->SetMinClientSize(sizer->ComputeFittingClientSize(singletonFrame)); } welcome_to_rhocoin::~welcome_to_rhocoin() {