<commit_before>////////////////////////////////////////////////////////////////////////////
//
// 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"
#include "sync/app.hpp"
#include "util/uuid.hpp"

#include <realm/util/sha_crypto.hpp>
#include <realm/util/hex_dump.hpp>

using namespace realm;
using namespace realm::_impl;

void SyncManager::configure(std::shared_ptr<app::App> app,
                            const std::string& sync_route,
                            const SyncClientConfig& config)
{
    m_app = app;
    m_sync_route = sync_route;
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_config = std::move(config);
        if (m_sync_client)
            return;
    }

    struct UserCreationData {
        std::string identity;
        std::string refresh_token;
        std::string access_token;
        std::string provider_type;
        std::vector<SyncUserIdentity> identities;
        SyncUser::State state;
        std::string device_id;
    };

    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) {
            // Changing the base path for tests requires calling reset_for_testing()
            // first, and otherwise isn't supported
            REALM_ASSERT(m_file_manager->base_path() == m_config.base_file_path);
        } else {
            m_file_manager = std::make_unique<SyncFileManager>(m_config.base_file_path, app->config().app_id);
        }

        // Set up the metadata manager, and perform initial loading/purging work.
        if (m_metadata_manager || m_config.metadata_mode == MetadataMode::NoMetadata) {
            // No metadata means we use a new client uuid each time
            if (!m_metadata_manager)
                m_client_uuid = util::uuid_string();
            return;
        }

        bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
        try {
            m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
                                                                       encrypt,
                                                                       m_config.custom_encryption_key);
        } catch (RealmFileException const& ex) {
            if (m_config.reset_metadata_on_error && m_file_manager->remove_metadata_realm()) {
                m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
                                                                           encrypt,
                                                                           std::move(m_config.custom_encryption_key));
            } else {
                throw;
            }
        }

        REALM_ASSERT(m_metadata_manager);
        m_client_uuid = m_metadata_manager->client_uuid();

        // Perform our "on next startup" actions such as deleting Realm files
        // which we couldn't delete immediately due to them being in use
        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++) {
            auto user_data = users.get(i);
            auto refresh_token = user_data.refresh_token();
            auto access_token = user_data.access_token();
            auto device_id = user_data.device_id();
            if (!refresh_token.empty() && !access_token.empty()) {
                users_to_add.push_back(UserCreationData{
                    user_data.identity(),
                    std::move(refresh_token),
                    std::move(access_token),
                    user_data.provider_type(),
                    user_data.identities(),
                    user_data.state(),
                    device_id
                });
            }
        }

        // 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.identity());
                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& provider_type = user_data.provider_type;
            auto user = std::make_shared<SyncUser>(user_data.refresh_token,
                                                   identity,
                                                   provider_type,
                                                   user_data.access_token,
                                                   user_data.state,
                                                   user_data.device_id,
                                                   shared_from_this());
            user->update_identities(user_data.identities);
            m_users.emplace_back(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);
    if (m_file_manager)
        util::try_remove_dir_recursive(m_file_manager->base_path());
    m_file_manager = nullptr;
    m_metadata_manager = nullptr;
    m_client_uuid = util::none;

    {
        // Destroy all the users.
        std::lock_guard<std::mutex> lock(m_user_mutex);
        m_users.clear();
        m_current_user = nullptr;
    }
    {
        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 existing sessions
            // prior to calling `reset_for_testing`.
            bool no_sessions = !do_has_existing_sessions();
            REALM_ASSERT_RELEASE(no_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.
        m_config = {};

        m_sync_route = "";
    }
}

void SyncManager::set_log_level(util::Logger::Level level) noexcept
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_config.log_level = level;
}

void SyncManager::set_logger_factory(SyncLoggerFactory& factory) noexcept
{
    std::lock_guard<std::mutex> lock(m_mutex);<commit_msg>Stop the sync client before tearing down the sync file manager<commit_after>////////////////////////////////////////////////////////////////////////////
//
// 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"
#include "sync/app.hpp"
#include "util/uuid.hpp"

#include <realm/util/sha_crypto.hpp>
#include <realm/util/hex_dump.hpp>

using namespace realm;
using namespace realm::_impl;

void SyncManager::configure(std::shared_ptr<app::App> app,
                            const std::string& sync_route,
                            const SyncClientConfig& config)
{
    m_app = app;
    m_sync_route = sync_route;
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_config = std::move(config);
        if (m_sync_client)
            return;
    }

    struct UserCreationData {
        std::string identity;
        std::string refresh_token;
        std::string access_token;
        std::string provider_type;
        std::vector<SyncUserIdentity> identities;
        SyncUser::State state;
        std::string device_id;
    };

    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) {
            // Changing the base path for tests requires calling reset_for_testing()
            // first, and otherwise isn't supported
            REALM_ASSERT(m_file_manager->base_path() == m_config.base_file_path);
        } else {
            m_file_manager = std::make_unique<SyncFileManager>(m_config.base_file_path, app->config().app_id);
        }

        // Set up the metadata manager, and perform initial loading/purging work.
        if (m_metadata_manager || m_config.metadata_mode == MetadataMode::NoMetadata) {
            // No metadata means we use a new client uuid each time
            if (!m_metadata_manager)
                m_client_uuid = util::uuid_string();
            return;
        }

        bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
        try {
            m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
                                                                       encrypt,
                                                                       m_config.custom_encryption_key);
        } catch (RealmFileException const& ex) {
            if (m_config.reset_metadata_on_error && m_file_manager->remove_metadata_realm()) {
                m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(),
                                                                           encrypt,
                                                                           std::move(m_config.custom_encryption_key));
            } else {
                throw;
            }
        }

        REALM_ASSERT(m_metadata_manager);
        m_client_uuid = m_metadata_manager->client_uuid();

        // Perform our "on next startup" actions such as deleting Realm files
        // which we couldn't delete immediately due to them being in use
        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++) {
            auto user_data = users.get(i);
            auto refresh_token = user_data.refresh_token();
            auto access_token = user_data.access_token();
            auto device_id = user_data.device_id();
            if (!refresh_token.empty() && !access_token.empty()) {
                users_to_add.push_back(UserCreationData{
                    user_data.identity(),
                    std::move(refresh_token),
                    std::move(access_token),
                    user_data.provider_type(),
                    user_data.identities(),
                    user_data.state(),
                    device_id
                });
            }
        }

        // 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.identity());
                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& provider_type = user_data.provider_type;
            auto user = std::make_shared<SyncUser>(user_data.refresh_token,
                                                   identity,
                                                   provider_type,
                                                   user_data.access_token,
                                                   user_data.state,
                                                   user_data.device_id,
                                                   shared_from_this());
            user->update_identities(user_data.identities);
            m_users.emplace_back(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_metadata_manager = nullptr;
    m_client_uuid = util::none;

    {
        // Destroy all the users.
        std::lock_guard<std::mutex> lock(m_user_mutex);
        m_users.clear();
        m_current_user = nullptr;
    }
    {
        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 existing sessions
            // prior to calling `reset_for_testing`.
            bool no_sessions = !do_has_existing_sessions();
            REALM_ASSERT_RELEASE(no_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.
        m_config = {};

        m_sync_route = "";
    }

    if (m_file_manager)
        util::try_remove_dir_recursive(m_file_manager->base_path());
    m_file_manager = nullptr;
}

void SyncManager::set_log_level(util::Logger::Level level) noexcept
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_config.log_level = level;
}

void SyncManager::set_logger_factory(SyncLoggerFactory& factory) noexcept
{
    std::lock_guard<std::mutex> lock(m_mutex);
{'fraction_non_alphanumeric': 0.10707786610477923, 'fraction_numerical': 0.0010535389330523896, 'mean_word_length': 1.9012225618227285, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 48, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6169238', 'n_tokens_mistral': 5575, 'n_tokens_neox': 5430, 'n_words': 1319}