diff --git a/src/sync_metadata.cpp b/src/sync_metadata.cpp index 7ba3c03d0..18294d0cb 100644 --- a/src/sync_metadata.cpp +++ b/src/sync_metadata.cpp @@ -33,10 +33,30 @@ namespace realm { static const char * const c_sync_userMetadata = "UserMetadata"; +static const char * const c_sync_fileActionMetadata = "FileActionMetadata"; static const char * const c_sync_marked_for_removal = "marked_for_removal"; static const char * const c_sync_identity = "identity"; static const char * const c_sync_auth_server_url = "auth_server_url"; static const char * const c_sync_user_token = "user_token"; +static const char * const c_sync_action = "action"; +static const char * const c_sync_current_path = "current_path"; +static const char * const c_sync_future_path = "future_path"; +static const char * const c_sync_user = "user"; + +static Property nullable_string_property(std::string name) +{ + Property p = { std::move(name), PropertyType::String }; + p.is_nullable = true; + return p; +} + +static Property object_property(std::string name, std::string object_type) +{ + Property p = { std::move(name), PropertyType::Object }; + p.object_type = std::move(object_type); + p.is_nullable = true; + return p; +} SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt, @@ -44,12 +64,6 @@ SyncMetadataManager::SyncMetadataManager(std::string path, { std::lock_guard lock(m_metadata_lock); - auto nullable_string_property = [](std::string name)->Property { - Property p = { name, PropertyType::String }; - p.is_nullable = true; - return p; - }; - Property primary_key = { c_sync_identity, PropertyType::String }; primary_key.is_indexed = true; primary_key.is_primary = true; @@ -57,14 +71,8 @@ SyncMetadataManager::SyncMetadataManager(std::string path, Realm::Config config; config.path = std::move(path); Schema schema = { - { c_sync_userMetadata, - { - primary_key, - { c_sync_marked_for_removal, PropertyType::Bool }, - nullable_string_property(c_sync_auth_server_url), - nullable_string_property(c_sync_user_token), - } - } + SyncFileActionMetadata::object_schema(), + SyncUserMetadata::object_schema(), }; config.schema = std::move(schema); config.schema_mode = SchemaMode::Additive; @@ -80,18 +88,10 @@ SyncMetadataManager::SyncMetadataManager(std::string path, config.encryption_key = std::move(*encryption_key); } - // Open the Realm. + // Open the Realm and get schema information SharedRealm realm = Realm::get_shared_realm(config); - - // Get data about the (hardcoded) schema. - DescriptorRef descriptor = ObjectStore::table_for_object_type(realm->read_group(), - c_sync_userMetadata)->get_descriptor(); - m_schema = { - descriptor->get_column_index(c_sync_identity), - descriptor->get_column_index(c_sync_marked_for_removal), - descriptor->get_column_index(c_sync_user_token), - descriptor->get_column_index(c_sync_auth_server_url) - }; + SyncUserMetadata::discover_columns(realm->read_group()); + SyncFileActionMetadata::discover_columns(realm->read_group()); m_metadata_config = std::move(config); } @@ -102,44 +102,59 @@ Realm::Config SyncMetadataManager::get_configuration() const return m_metadata_config; } -SyncUserMetadataResults SyncMetadataManager::all_unmarked_users() const +SyncUserMetadataResults SyncMetadataManager::get_users(bool marked) const { - return get_users(false); -} + auto columns = SyncUserMetadata::columns(); -SyncUserMetadataResults SyncMetadataManager::all_users_marked_for_removal() const -{ - return get_users(true); + // Open the Realm. + SharedRealm realm = Realm::get_shared_realm(get_configuration()); + + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); + Query query = table->where().equal(columns.idx_marked_for_removal, marked); + + Results results(realm, std::move(query)); + return SyncUserMetadataResults(std::move(results), std::move(realm), std::move(columns)); } -SyncUserMetadataResults SyncMetadataManager::get_users(bool marked) const +SyncFileActionMetadataResults SyncMetadataManager::all_file_actions() const { - // Open the Realm. + auto columns = SyncFileActionMetadata::columns(); SharedRealm realm = Realm::get_shared_realm(get_configuration()); - TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); - Query query = table->where().equal(m_schema.idx_marked_for_removal, marked); + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata); + Query query = table->where(); Results results(realm, std::move(query)); - return SyncUserMetadataResults(std::move(results), std::move(realm), m_schema); + return SyncFileActionMetadataResults(std::move(results), std::move(realm), std::move(columns)); } -SyncUserMetadata::SyncUserMetadata(Schema schema, SharedRealm realm, RowExpr row) -: m_invalid(row.get_bool(schema.idx_marked_for_removal)) -, m_schema(std::move(schema)) +util::Optional SyncMetadataManager::get_existing_file_action(const std::string& current_path) +{ + auto columns = SyncFileActionMetadata::columns(); + SharedRealm realm = Realm::get_shared_realm(get_configuration()); + + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata); + size_t row_idx = table->find_first_string(columns.idx_current_path, current_path); + if (row_idx == not_found) { + return none; + } + return SyncFileActionMetadata(std::move(realm), std::move(table->get(row_idx))); +} + +SyncUserMetadata::Columns SyncUserMetadata::m_columns; + +SyncUserMetadata::SyncUserMetadata(SharedRealm realm, RowExpr row) +: m_invalid(row.get_bool(columns().idx_marked_for_removal)) , m_realm(std::move(realm)) , m_row(row) { } SyncUserMetadata::SyncUserMetadata(SyncMetadataManager& manager, std::string identity, bool make_if_absent) -: m_schema(manager.m_schema) +: m_realm(Realm::get_shared_realm(manager.get_configuration())) { - // Open the Realm. - m_realm = Realm::get_shared_realm(manager.get_configuration()); - // Retrieve or create the row for this object. TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_userMetadata); - size_t row_idx = table->find_first_string(m_schema.idx_identity, identity); + size_t row_idx = table->find_first_string(m_columns.idx_identity, identity); if (row_idx == not_found) { if (!make_if_absent) { m_invalid = true; @@ -147,10 +162,10 @@ SyncUserMetadata::SyncUserMetadata(SyncMetadataManager& manager, std::string ide return; } m_realm->begin_transaction(); - row_idx = table->find_first_string(m_schema.idx_identity, identity); + row_idx = table->find_first_string(m_columns.idx_identity, identity); if (row_idx == not_found) { row_idx = table->add_empty_row(); - table->set_string(m_schema.idx_identity, row_idx, identity); + table->set_string(m_columns.idx_identity, row_idx, identity); m_realm->commit_transaction(); } else { // Someone beat us to adding this user. @@ -161,75 +176,210 @@ SyncUserMetadata::SyncUserMetadata(SyncMetadataManager& manager, std::string ide if (make_if_absent) { // User existed in the table, but had been marked for deletion. Unmark it. m_realm->begin_transaction(); - table->set_bool(m_schema.idx_marked_for_removal, row_idx, false); + table->set_bool(m_columns.idx_marked_for_removal, row_idx, false); m_realm->commit_transaction(); m_invalid = false; } else { - m_invalid = m_row.get_bool(m_schema.idx_marked_for_removal); + m_invalid = m_row.get_bool(m_columns.idx_marked_for_removal); } } -bool SyncUserMetadata::is_valid() const +ObjectSchema SyncUserMetadata::object_schema() { - return !m_invalid; + Property primary_key = { c_sync_identity, PropertyType::String }; + primary_key.is_indexed = true; + primary_key.is_primary = true; + return { c_sync_userMetadata, + { + primary_key, + { c_sync_marked_for_removal, PropertyType::Bool }, + nullable_string_property(c_sync_auth_server_url), + nullable_string_property(c_sync_user_token), + } + }; +} + +void SyncUserMetadata::discover_columns(Group& read_group) +{ + DescriptorRef descriptor = ObjectStore::table_for_object_type(read_group, + c_sync_userMetadata)->get_descriptor(); + m_columns = { + descriptor->get_column_index(c_sync_identity), + descriptor->get_column_index(c_sync_marked_for_removal), + descriptor->get_column_index(c_sync_user_token), + descriptor->get_column_index(c_sync_auth_server_url), + }; } std::string SyncUserMetadata::identity() const { m_realm->verify_thread(); - StringData result = m_row.get_string(m_schema.idx_identity); + if (!m_row.is_attached()) + throw std::runtime_error("This user object has been deleted from the metadata database."); + StringData result = m_row.get_string(m_columns.idx_identity); return result; } util::Optional SyncUserMetadata::get_optional_string_field(size_t col_idx) const { - REALM_ASSERT(!m_invalid); + REALM_ASSERT(!m_invalid && m_row.is_attached()); m_realm->verify_thread(); StringData result = m_row.get_string(col_idx); return result.is_null() ? util::none : util::make_optional(std::string(result)); } -util::Optional SyncUserMetadata::server_url() const -{ - return get_optional_string_field(m_schema.idx_auth_server_url); -} - -util::Optional SyncUserMetadata::user_token() const -{ - return get_optional_string_field(m_schema.idx_user_token); -} - void SyncUserMetadata::set_state(util::Optional server_url, util::Optional user_token) { - if (m_invalid) { + if (m_invalid || !m_row.is_attached()) { return; } m_realm->verify_thread(); m_realm->begin_transaction(); - m_row.set_string(m_schema.idx_user_token, *user_token); - m_row.set_string(m_schema.idx_auth_server_url, *server_url); + m_row.set_string(m_columns.idx_user_token, *user_token); + m_row.set_string(m_columns.idx_auth_server_url, *server_url); m_realm->commit_transaction(); } void SyncUserMetadata::mark_for_removal() { - if (m_invalid) { + if (m_invalid || !m_row.is_attached()) { return; } m_realm->verify_thread(); m_realm->begin_transaction(); - m_row.set_bool(m_schema.idx_marked_for_removal, true); + m_row.set_bool(m_columns.idx_marked_for_removal, true); m_realm->commit_transaction(); } void SyncUserMetadata::remove() { m_invalid = true; + if (m_row.is_attached()) { + m_realm->begin_transaction(); + TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_userMetadata); + table->move_last_over(m_row.get_index()); + m_realm->commit_transaction(); + } + m_realm = nullptr; +} + +SyncFileActionMetadata::Columns SyncFileActionMetadata::m_columns; + +SyncFileActionMetadata::SyncFileActionMetadata(SyncMetadataManager& manager, + Action action, + const std::string& current_path, + util::Optional user, + util::Optional future_path) +: m_realm(Realm::get_shared_realm(manager.get_configuration())) +{ + if (action == Action::MoveRealmFiles && !future_path) { + throw std::invalid_argument("If action is 'MoveRealmFiles', a future path must be specified."); + } + TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_fileActionMetadata); m_realm->begin_transaction(); - TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_userMetadata); - table->move_last_over(m_row.get_index()); + size_t row_idx = table->find_first_string(m_columns.idx_current_path, current_path); + if (row_idx == not_found) { + row_idx = table->add_empty_row(); + table->set_string(m_columns.idx_current_path, row_idx, current_path); + if (user) { + table->set_link(m_columns.idx_user, row_idx, user->m_row.get_index()); + } + } + m_row = table->get(row_idx); + // Validate the user + if (user) { + TableRef user_table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_userMetadata); + auto proposed_user_identity = user->identity(); + auto current_user_identity = user_table->get_string(SyncUserMetadata::columns().idx_identity, + m_row.get_link(m_columns.idx_user)); + if (proposed_user_identity != current_user_identity) { + m_realm->cancel_transaction(); + throw std::invalid_argument("Cannot change a file action metadatum's user to a different user."); + } + } else { + table->nullify_link(m_columns.idx_user, row_idx); + } + table->set_int(m_columns.idx_action, row_idx, static_cast(action)); + table->set_string(m_columns.idx_future_path, row_idx, future_path); m_realm->commit_transaction(); +} + +SyncFileActionMetadata::SyncFileActionMetadata(SharedRealm realm, RowExpr row) +: m_realm(std::move(realm)) +, m_row(std::move(row)) { } + +void SyncFileActionMetadata::discover_columns(Group& read_group) +{ + DescriptorRef descriptor = ObjectStore::table_for_object_type(read_group, + c_sync_fileActionMetadata)->get_descriptor(); + m_columns = { + descriptor->get_column_index(c_sync_action), + descriptor->get_column_index(c_sync_user), + descriptor->get_column_index(c_sync_current_path), + descriptor->get_column_index(c_sync_future_path), + }; +} + +ObjectSchema SyncFileActionMetadata::object_schema() +{ + Property primary_key = { c_sync_current_path, PropertyType::String }; + primary_key.is_indexed = true; + primary_key.is_primary = true; + return { c_sync_fileActionMetadata, + { + primary_key, + object_property(c_sync_user, c_sync_userMetadata), + { c_sync_action, PropertyType::Int }, + nullable_string_property(c_sync_future_path), + } + }; +} + +void SyncFileActionMetadata::remove() +{ + if (m_row.is_attached()) { + m_realm->begin_transaction(); + TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_fileActionMetadata); + table->move_last_over(m_row.get_index()); + m_realm->commit_transaction(); + } m_realm = nullptr; } +std::string SyncFileActionMetadata::current_path() const +{ + m_realm->verify_thread(); + if (!m_row.is_attached()) + throw std::runtime_error("This file action has been deleted from the metadata database."); + return m_row.get_string(m_columns.idx_current_path); +} + +util::Optional SyncFileActionMetadata::future_path() const +{ + m_realm->verify_thread(); + if (!m_row.is_attached()) + throw std::runtime_error("This file action has been deleted from the metadata database."); + StringData result = m_row.get_string(m_columns.idx_future_path); + return result.is_null() ? util::none : util::make_optional(std::string(result)); +} + +SyncFileActionMetadata::Action SyncFileActionMetadata::action() const +{ + m_realm->verify_thread(); + if (!m_row.is_attached()) + throw std::runtime_error("This file action has been deleted from the metadata database."); + int64_t raw = m_row.get_int(m_columns.idx_action); + return static_cast(raw); +} + +SyncUserMetadata SyncFileActionMetadata::user() +{ + m_realm->verify_thread(); + if (!m_row.is_attached()) + throw std::runtime_error("This file action has been deleted from the metadata database."); + TableRef user_table = ObjectStore::table_for_object_type(m_realm->read_group(), c_sync_userMetadata); + size_t user_idx = m_row.get_link(m_columns.idx_user); + return SyncUserMetadata(m_realm, user_table->get(user_idx)); +} + } diff --git a/src/sync_metadata.hpp b/src/sync_metadata.hpp index 000f8238b..ab9f66826 100644 --- a/src/sync_metadata.hpp +++ b/src/sync_metadata.hpp @@ -32,19 +32,63 @@ namespace realm { template class BasicRowExpr; using RowExpr = BasicRowExpr; class SyncMetadataManager; +class SyncUserMetadata; +class SyncFileActionMetadata; + +template +class SyncMetadataResults { +public: + size_t size() const + { + return m_results.size(); + } + + T get(size_t idx) const + { + RowExpr row = m_results.get(idx); + return T(m_realm, row); + } + + SyncMetadataResults(Results results, SharedRealm realm, typename T::Columns columns) + : m_results(std::move(results)) + , m_realm(std::move(realm)) + , m_columns(std::move(columns)) { } +private: + typename T::Columns m_columns; + SharedRealm m_realm; + // FIXME: remove 'mutable' once `realm::Results` is properly annotated for const + mutable Results m_results; +}; +using SyncUserMetadataResults = SyncMetadataResults; +using SyncFileActionMetadataResults = SyncMetadataResults; class SyncUserMetadata { +friend class SyncFileActionMetadata; public: - struct Schema { + struct Columns { size_t idx_identity; size_t idx_marked_for_removal; size_t idx_user_token; size_t idx_auth_server_url; }; + static Columns columns() + { + return m_columns; + } + static void discover_columns(Group& read_group); + std::string identity() const; - util::Optional server_url() const; - util::Optional user_token() const; + + util::Optional server_url() const + { + return get_optional_string_field(m_columns.idx_auth_server_url); + } + + util::Optional user_token() const + { + return get_optional_string_field(m_columns.idx_user_token); + } void set_state(util::Optional server_url, util::Optional user_token); @@ -54,7 +98,12 @@ class SyncUserMetadata { // deletion of a user must be deferred until the next time the host application is launched. void mark_for_removal(); - bool is_valid() const; + bool is_valid() const + { + return !m_invalid; + } + + static ObjectSchema object_schema(); // Construct a new user. // @@ -64,58 +113,86 @@ class SyncUserMetadata { // If `make_if_absent` is true and the user was previously marked for deletion, it will be unmarked. SyncUserMetadata(SyncMetadataManager& manager, std::string identity, bool make_if_absent=true); - SyncUserMetadata(Schema schema, SharedRealm realm, RowExpr row); + SyncUserMetadata(SharedRealm realm, RowExpr row); private: bool m_invalid = false; util::Optional get_optional_string_field(size_t col_idx) const; - Schema m_schema; + static Columns m_columns; SharedRealm m_realm; Row m_row; }; -template -class SyncMetadataResults { +class SyncFileActionMetadata { public: - size_t size() const - { - return m_results.size(); - } + enum class Action { + DeleteRealmFiles = 0, + MoveRealmFiles = 1, + }; - T get(size_t idx) const + struct Columns { + size_t idx_action; + size_t idx_user; + size_t idx_current_path; + size_t idx_future_path; + }; + + static Columns columns() { - RowExpr row = m_results.get(idx); - return T(m_schema, m_realm, row); + return m_columns; } + static void discover_columns(Group& read_group); + static ObjectSchema object_schema(); + + /// Remove metadata from the Realm. Do not further access metadata after calling this method. + void remove(); + + std::string current_path() const; + util::Optional future_path() const; + Action action() const; + bool completed() const; + SyncUserMetadata user(); + + // Construct a new file action metadata object. + SyncFileActionMetadata(SyncMetadataManager& manager, + Action action, + const std::string& current_path, + util::Optional user=none, + util::Optional future_path=none); + + SyncFileActionMetadata(SharedRealm realm, RowExpr row); - SyncMetadataResults(Results results, SharedRealm realm, typename T::Schema schema) - : m_schema(std::move(schema)) - , m_realm(std::move(realm)) - , m_results(std::move(results)) - { } private: - typename T::Schema m_schema; + static Columns m_columns; + SharedRealm m_realm; - // FIXME: remove 'mutable' once `realm::Results` is properly annotated for const - mutable Results m_results; + Row m_row; }; -using SyncUserMetadataResults = SyncMetadataResults; class SyncMetadataManager { friend class SyncUserMetadata; public: // Return a Results object containing all users not marked for removal. - SyncUserMetadataResults all_unmarked_users() const; + SyncUserMetadataResults all_unmarked_users() const + { + return get_users(false); + } // Return a Results object containing all users marked for removal. It is the binding's responsibility to call // `remove()` on each user to actually remove it from the database. (This is so that already-open Realm files can be // safely cleaned up the next time the host is launched.) - SyncUserMetadataResults all_users_marked_for_removal() const; + SyncUserMetadataResults all_users_marked_for_removal() const + { + return get_users(true); + } - Realm::Config get_configuration() const; + SyncFileActionMetadataResults all_file_actions() const; + + util::Optional get_existing_file_action(const std::string& current_path); + Realm::Config get_configuration() const; /// Construct the metadata manager. /// @@ -131,7 +208,6 @@ friend class SyncUserMetadata; Realm::Config m_metadata_config; - SyncUserMetadata::Schema m_schema; mutable std::mutex m_metadata_lock; };