added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / sync / sync_manager.cpp
diff --git a/iOS/Pods/Realm/Realm/ObjectStore/src/sync/sync_manager.cpp b/iOS/Pods/Realm/Realm/ObjectStore/src/sync/sync_manager.cpp
new file mode 100644 (file)
index 0000000..819dfb3
--- /dev/null
@@ -0,0 +1,528 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2016 Realm Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////
+
+#include "sync/sync_manager.hpp"
+
+#include "sync/impl/sync_client.hpp"
+#include "sync/impl/sync_file.hpp"
+#include "sync/impl/sync_metadata.hpp"
+#include "sync/sync_session.hpp"
+#include "sync/sync_user.hpp"
+
+using namespace realm;
+using namespace realm::_impl;
+
+constexpr const char SyncManager::c_admin_identity[];
+
+SyncManager& SyncManager::shared()
+{
+    // The singleton is heap-allocated in order to fix an issue when running unit tests where tests would crash after
+    // they were done running because the manager was destroyed too early.
+    static SyncManager& manager = *new SyncManager;
+    return manager;
+}
+
+void SyncManager::configure_file_system(const std::string& base_file_path,
+                                        MetadataMode metadata_mode,
+                                        util::Optional<std::vector<char>> custom_encryption_key,
+                                        bool reset_metadata_on_error)
+{
+    struct UserCreationData {
+        std::string identity;
+        std::string user_token;
+        std::string server_url;
+        bool is_admin;
+    };
+
+    std::vector<UserCreationData> users_to_add;
+    {
+        std::lock_guard<std::mutex> lock(m_file_system_mutex);
+
+        // Set up the file manager.
+        if (m_file_manager) {
+            REALM_ASSERT(m_file_manager->base_path() == base_file_path);
+        } else {
+            m_file_manager = std::make_unique<SyncFileManager>(base_file_path);
+        }
+
+        // Set up the metadata manager, and perform initial loading/purging work.
+        if (m_metadata_manager) {
+            return;
+        }
+        switch (metadata_mode) {
+            case MetadataMode::NoEncryption:
+                m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
+                                                                           false);
+                break;
+            case MetadataMode::Encryption:
+                try {
+                    m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
+                                                                               true,
+                                                                               custom_encryption_key);
+                } catch (RealmFileException const& ex) {
+                    if (reset_metadata_on_error && m_file_manager->remove_metadata_realm()) {
+                        m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
+                                                                                   true,
+                                                                                   std::move(custom_encryption_key));
+                    } else {
+                        throw;
+                    }
+                }
+                break;
+            case MetadataMode::NoMetadata:
+                return;
+        }
+
+        REALM_ASSERT(m_metadata_manager);
+        // Perform any necessary file actions.
+        std::vector<SyncFileActionMetadata> completed_actions;
+        SyncFileActionMetadataResults file_actions = m_metadata_manager->all_pending_actions();
+        for (size_t i = 0; i < file_actions.size(); i++) {
+            auto file_action = file_actions.get(i);
+            if (run_file_action(file_action)) {
+                completed_actions.emplace_back(std::move(file_action));
+            }
+        }
+        for (auto& action : completed_actions) {
+            action.remove();
+        }
+        // Load persisted users into the users map.
+        SyncUserMetadataResults users = m_metadata_manager->all_unmarked_users();
+        for (size_t i = 0; i < users.size(); i++) {
+            // Note that 'admin' style users are not persisted.
+            auto user_data = users.get(i);
+            auto user_token = user_data.user_token();
+            auto identity = user_data.identity();
+            auto server_url = user_data.auth_server_url();
+            bool is_admin = user_data.is_admin();
+            if (user_token) {
+                UserCreationData data = {
+                    std::move(identity),
+                    std::move(*user_token),
+                    std::move(server_url),
+                    is_admin,
+                };
+                users_to_add.emplace_back(std::move(data));
+            }
+        }
+        // Delete any users marked for death.
+        std::vector<SyncUserMetadata> dead_users;
+        SyncUserMetadataResults users_to_remove = m_metadata_manager->all_users_marked_for_removal();
+        dead_users.reserve(users_to_remove.size());
+        for (size_t i = 0; i < users_to_remove.size(); i++) {
+            auto user = users_to_remove.get(i);
+            // FIXME: delete user data in a different way? (This deletes a logged-out user's data as soon as the app
+            // launches again, which might not be how some apps want to treat their data.)
+            try {
+                m_file_manager->remove_user_directory(user.local_uuid());
+                dead_users.emplace_back(std::move(user));
+            } catch (util::File::AccessError const&) {
+                continue;
+            }
+        }
+        for (auto& user : dead_users) {
+            user.remove();
+        }
+    }
+    {
+        std::lock_guard<std::mutex> lock(m_user_mutex);
+        for (auto& user_data : users_to_add) {
+            auto& identity = user_data.identity;
+            auto& server_url = user_data.server_url;
+            auto user = std::make_shared<SyncUser>(user_data.user_token, identity, server_url);
+            user->set_is_admin(user_data.is_admin);
+            m_users.insert({ {identity, server_url}, std::move(user) });
+        }
+    }
+}
+
+bool SyncManager::immediately_run_file_actions(const std::string& realm_path)
+{
+    if (!m_metadata_manager) {
+        return false;
+    }
+    if (auto metadata = m_metadata_manager->get_file_action_metadata(realm_path)) {
+        if (run_file_action(*metadata)) {
+            metadata->remove();
+            return true;
+        }
+    }
+    return false;
+}
+
+// Perform a file action. Returns whether or not the file action can be removed.
+bool SyncManager::run_file_action(const SyncFileActionMetadata& md)
+{
+    switch (md.action()) {
+        case SyncFileActionMetadata::Action::DeleteRealm:
+            // Delete all the files for the given Realm.
+            m_file_manager->remove_realm(md.original_name());
+            return true;
+        case SyncFileActionMetadata::Action::BackUpThenDeleteRealm:
+            // Copy the primary Realm file to the recovery dir, and then delete the Realm.
+            auto new_name = md.new_name();
+            auto original_name = md.original_name();
+            if (!util::File::exists(original_name)) {
+                // The Realm file doesn't exist anymore.
+                return true;
+            }
+            if (new_name && !util::File::exists(*new_name) && m_file_manager->copy_realm_file(original_name, *new_name)) {
+                // We successfully copied the Realm file to the recovery directory.
+                m_file_manager->remove_realm(original_name);
+                return true;
+            }
+            return false;
+    }
+    return false;
+}
+
+void SyncManager::reset_for_testing()
+{
+    std::lock_guard<std::mutex> lock(m_file_system_mutex);
+    m_file_manager = nullptr;
+    m_metadata_manager = nullptr;
+    {
+        // Destroy all the users.
+        std::lock_guard<std::mutex> lock(m_user_mutex);
+        m_users.clear();
+        m_admin_token_users.clear();
+    }
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+
+        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
+        if (m_sync_client)
+            m_sync_client->stop();
+
+        {
+            std::lock_guard<std::mutex> lock(m_session_mutex);
+
+            // Callers of `SyncManager::reset_for_testing` should ensure there are no active sessions
+            // prior to calling `reset_for_testing`.
+            auto no_active_sessions = std::none_of(m_sessions.begin(), m_sessions.end(), [](auto& element){
+                return element.second->existing_external_reference();
+            });
+            REALM_ASSERT_RELEASE(no_active_sessions);
+
+            // Destroy any inactive sessions.
+            // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to
+            // remain inactive until their final upload completes, at which point they are unregistered
+            // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions
+            // should have already been destroyed.
+            m_sessions.clear();
+        }
+
+        // Destroy the client now that we have no remaining sessions.
+        m_sync_client = nullptr;
+
+        // Reset even more state.
+        // NOTE: these should always match the defaults.
+        m_log_level = util::Logger::Level::info;
+        m_logger_factory = nullptr;
+        m_client_reconnect_mode = ReconnectMode::normal;
+        m_multiplex_sessions = false;
+    }
+}
+
+void SyncManager::set_log_level(util::Logger::Level level) noexcept
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    m_log_level = level;
+}
+
+void SyncManager::set_logger_factory(SyncLoggerFactory& factory) noexcept
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    m_logger_factory = &factory;
+}
+
+void SyncManager::set_client_should_reconnect_immediately(bool reconnect_immediately)
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    m_client_reconnect_mode = reconnect_immediately ? ReconnectMode::immediate : ReconnectMode::normal;
+}
+
+bool SyncManager::client_should_reconnect_immediately() const noexcept
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    return m_client_reconnect_mode == ReconnectMode::immediate;
+}
+
+void SyncManager::reconnect()
+{
+    std::lock_guard<std::mutex> lock(m_session_mutex);
+    for (auto& it : m_sessions) {
+        it.second->handle_reconnect();
+    }
+}
+
+util::Logger::Level SyncManager::log_level() const noexcept
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    return m_log_level;
+}
+
+bool SyncManager::perform_metadata_update(std::function<void(const SyncMetadataManager&)> update_function) const
+{
+    std::lock_guard<std::mutex> lock(m_file_system_mutex);
+    if (!m_metadata_manager) {
+        return false;
+    }
+    update_function(*m_metadata_manager);
+    return true;
+}
+
+std::shared_ptr<SyncUser> SyncManager::get_user(const SyncUserIdentifier& identifier, std::string refresh_token)
+{
+    std::lock_guard<std::mutex> lock(m_user_mutex);
+    auto it = m_users.find(identifier);
+    if (it == m_users.end()) {
+        // No existing user.
+        auto new_user = std::make_shared<SyncUser>(std::move(refresh_token),
+                                                   identifier.user_id,
+                                                   identifier.auth_server_url,
+                                                   none,
+                                                   SyncUser::TokenType::Normal);
+        m_users.insert({ identifier, new_user });
+        return new_user;
+    } else {
+        auto user = it->second;
+        if (user->state() == SyncUser::State::Error) {
+            return nullptr;
+        }
+        user->update_refresh_token(std::move(refresh_token));
+        return user;
+    }
+}
+
+std::shared_ptr<SyncUser> SyncManager::get_admin_token_user_from_identity(const std::string& identity,
+                                                                          util::Optional<std::string> server_url,
+                                                                          const std::string& token)
+{
+    if (server_url)
+        return get_admin_token_user(*server_url, token, identity);
+
+    std::lock_guard<std::mutex> lock(m_user_mutex);
+    // Look up the user based off the identity.
+    // No server URL, so no migration possible.
+    auto it = m_admin_token_users.find(identity);
+    if (it == m_admin_token_users.end()) {
+        // No existing user.
+        auto new_user = std::make_shared<SyncUser>(token,
+                                                   c_admin_identity,
+                                                   std::move(server_url),
+                                                   identity,
+                                                   SyncUser::TokenType::Admin);
+        m_admin_token_users.insert({ identity, new_user });
+        return new_user;
+    } else {
+        return it->second;
+    }
+}
+
+std::shared_ptr<SyncUser> SyncManager::get_admin_token_user(const std::string& server_url,
+                                                            const std::string& token,
+                                                            util::Optional<std::string> old_identity)
+{
+    std::shared_ptr<SyncUser> user;
+    {
+        std::lock_guard<std::mutex> lock(m_user_mutex);
+        // Look up the user based off the server URL.
+        auto it = m_admin_token_users.find(server_url);
+        if (it != m_admin_token_users.end())
+            return it->second;
+
+        // No existing user.
+        user = std::make_shared<SyncUser>(token,
+                                          c_admin_identity,
+                                          server_url,
+                                          c_admin_identity + server_url,
+                                          SyncUser::TokenType::Admin);
+        m_admin_token_users.insert({ server_url, user });
+    }
+    if (old_identity) {
+        // Try renaming the user's directory to use our new naming standard, if applicable.
+        std::lock_guard<std::mutex> fm_lock(m_file_system_mutex);
+        if (m_file_manager)
+            m_file_manager->try_rename_user_directory(*old_identity, c_admin_identity + server_url);
+    }
+    return user;
+}
+
+std::vector<std::shared_ptr<SyncUser>> SyncManager::all_logged_in_users() const
+{
+    std::lock_guard<std::mutex> lock(m_user_mutex);
+    std::vector<std::shared_ptr<SyncUser>> users;
+    users.reserve(m_users.size() + m_admin_token_users.size());
+    for (auto& it : m_users) {
+        auto user = it.second;
+        if (user->state() == SyncUser::State::Active) {
+            users.emplace_back(std::move(user));
+        }
+    }
+    for (auto& it : m_admin_token_users) {
+        users.emplace_back(std::move(it.second));
+    }
+    return users;
+}
+
+std::shared_ptr<SyncUser> SyncManager::get_current_user() const
+{
+    std::lock_guard<std::mutex> lock(m_user_mutex);
+
+    auto is_active_user = [](auto& el) { return el.second->state() == SyncUser::State::Active; };
+    auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user);
+    if (it == m_users.end())
+        return nullptr;
+
+    if (std::find_if(std::next(it), m_users.end(), is_active_user) != m_users.end())
+        throw std::logic_error("Current user is not valid if more that one valid, logged-in user exists.");
+
+    return it->second;
+}
+
+std::shared_ptr<SyncUser> SyncManager::get_existing_logged_in_user(const SyncUserIdentifier& identifier) const
+{
+    std::lock_guard<std::mutex> lock(m_user_mutex);
+    auto it = m_users.find(identifier);
+    if (it == m_users.end())
+        return nullptr;
+
+    auto user = it->second;
+    return user->state() == SyncUser::State::Active ? user : nullptr;
+}
+
+std::string SyncManager::path_for_realm(const SyncUser& user, const std::string& raw_realm_url) const
+{
+    std::lock_guard<std::mutex> lock(m_file_system_mutex);
+    REALM_ASSERT(m_file_manager);
+    const auto& user_local_identity = user.local_identity();
+    util::Optional<SyncUserIdentifier> user_info;
+    if (user.token_type() == SyncUser::TokenType::Normal)
+        user_info = SyncUserIdentifier{ user.identity(), user.server_url() };
+
+    return m_file_manager->path(user_local_identity, raw_realm_url, std::move(user_info));
+}
+
+std::string SyncManager::recovery_directory_path() const
+{
+    std::lock_guard<std::mutex> lock(m_file_system_mutex);
+    REALM_ASSERT(m_file_manager);
+    return m_file_manager->recovery_directory_path();
+}
+
+std::shared_ptr<SyncSession> SyncManager::get_existing_active_session(const std::string& path) const
+{
+    std::lock_guard<std::mutex> lock(m_session_mutex);
+    if (auto session = get_existing_session_locked(path)) {
+        if (auto external_reference = session->existing_external_reference())
+            return external_reference;
+    }
+    return nullptr;
+}
+
+std::shared_ptr<SyncSession> SyncManager::get_existing_session_locked(const std::string& path) const
+{
+    REALM_ASSERT(!m_session_mutex.try_lock());
+    auto it = m_sessions.find(path);
+    return it == m_sessions.end() ? nullptr : it->second;
+}
+
+std::shared_ptr<SyncSession> SyncManager::get_existing_session(const std::string& path) const
+{
+    std::lock_guard<std::mutex> lock(m_session_mutex);
+    if (auto session = get_existing_session_locked(path))
+        return session->external_reference();
+
+    return nullptr;
+}
+
+std::shared_ptr<SyncSession> SyncManager::get_session(const std::string& path, const SyncConfig& sync_config)
+{
+    auto& client = get_sync_client(); // Throws
+
+    std::lock_guard<std::mutex> lock(m_session_mutex);
+    if (auto session = get_existing_session_locked(path)) {
+        sync_config.user->register_session(session);
+        return session->external_reference();
+    }
+
+    auto shared_session = SyncSession::create(client, path, sync_config);
+    m_sessions[path] = shared_session;
+
+    // Create the external reference immediately to ensure that the session will become
+    // inactive if an exception is thrown in the following code.
+    auto external_reference = shared_session->external_reference();
+
+    sync_config.user->register_session(std::move(shared_session));
+
+    return external_reference;
+}
+
+void SyncManager::unregister_session(const std::string& path)
+{
+    std::lock_guard<std::mutex> lock(m_session_mutex);
+    auto it = m_sessions.find(path);
+    REALM_ASSERT(it != m_sessions.end());
+
+    // If the session has an active external reference, leave it be. This will happen if the session
+    // moves to an inactive state while still externally reference, for instance, as a result of
+    // the session's user being logged out.
+    if (it->second->existing_external_reference())
+        return;
+
+    m_sessions.erase(path);
+}
+
+void SyncManager::enable_session_multiplexing()
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    if (m_sync_client)
+        throw std::logic_error("Cannot enable session multiplexing after creating the sync client");
+    m_multiplex_sessions = true;
+}
+
+SyncClient& SyncManager::get_sync_client() const
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    if (!m_sync_client)
+        m_sync_client = create_sync_client(); // Throws
+    return *m_sync_client;
+}
+
+std::unique_ptr<SyncClient> SyncManager::create_sync_client() const
+{
+    REALM_ASSERT(!m_mutex.try_lock());
+
+    std::unique_ptr<util::Logger> logger;
+    if (m_logger_factory) {
+        logger = m_logger_factory->make_logger(m_log_level); // Throws
+    }
+    else {
+        auto stderr_logger = std::make_unique<util::StderrLogger>(); // Throws
+        stderr_logger->set_level_threshold(m_log_level);
+        logger = std::move(stderr_logger);
+    }
+    return std::make_unique<SyncClient>(std::move(logger), m_client_reconnect_mode, m_multiplex_sessions);
+}
+
+std::string SyncManager::client_uuid() const
+{
+    REALM_ASSERT(m_metadata_manager);
+    return m_metadata_manager->client_uuid();
+}