Newer
Older
bremer-ios-app / Pods / Realm / core / realm-monorepo.xcframework / macos-x86_64_arm64 / Headers / realm / object-store / sync / sync_manager.hpp
////////////////////////////////////////////////////////////////////////////
//
// 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.
//
////////////////////////////////////////////////////////////////////////////

#ifndef REALM_OS_SYNC_MANAGER_HPP
#define REALM_OS_SYNC_MANAGER_HPP

#include <realm/object-store/shared_realm.hpp>

#include <realm/util/checked_mutex.hpp>
#include <realm/util/logger.hpp>
#include <realm/util/optional.hpp>
#include <realm/sync/binding_callback_thread_observer.hpp>
#include <realm/sync/config.hpp>
#include <realm/sync/socket_provider.hpp>

#include <memory>
#include <mutex>
#include <unordered_map>

class TestAppSession;
class TestSyncManager;

namespace realm {

class DB;
struct SyncConfig;
class SyncSession;
class SyncUser;
class SyncFileManager;
class SyncMetadataManager;
class SyncFileActionMetadata;
class SyncAppMetadata;

namespace _impl {
struct SyncClient;
}

namespace app {
class App;
}

struct SyncClientTimeouts {
    SyncClientTimeouts();
    // See sync::Client::Config for the meaning of these fields.
    uint64_t connect_timeout;
    uint64_t connection_linger_time;
    uint64_t ping_keepalive_period;
    uint64_t pong_keepalive_timeout;
    uint64_t fast_reconnect_limit;
};

struct SyncClientConfig {
    enum class MetadataMode {
        NoEncryption, // Enable metadata, but disable encryption.
        Encryption,   // Enable metadata, and use encryption (automatic if possible).
        NoMetadata,   // Disable metadata.
    };

    std::string base_file_path;
    MetadataMode metadata_mode = MetadataMode::Encryption;
    util::Optional<std::vector<char>> custom_encryption_key;

    using LoggerFactory = std::function<std::shared_ptr<util::Logger>(util::Logger::Level)>;
    LoggerFactory logger_factory;
    util::Logger::Level log_level = util::Logger::Level::info;
    ReconnectMode reconnect_mode = ReconnectMode::normal; // For internal sync-client testing only!
#if REALM_DISABLE_SYNC_MULTIPLEXING
    bool multiplex_sessions = false;
#else
    bool multiplex_sessions = true;
#endif

    // The SyncSocket instance used by the Sync Client for event synchronization
    // and creating WebSockets. If not provided the default implementation will be used.
    std::shared_ptr<sync::SyncSocketProvider> socket_provider;

    // Optional thread observer for event loop thread events in the default SyncSocketProvider
    // implementation. It is not used for custom SyncSocketProvider implementations.
    std::shared_ptr<BindingCallbackThreadObserver> default_socket_provider_thread_observer;

    // {@
    // Optional information about the binding/application that is sent as part of the User-Agent
    // when establishing a connection to the server. These values are only used by the default
    // SyncSocket implementation. Custom SyncSocket implementations must update the User-Agent
    // directly, if supported by the platform APIs.
    std::string user_agent_binding_info;
    std::string user_agent_application_info;
    // @}

    SyncClientTimeouts timeouts;
};

class SyncManager : public std::enable_shared_from_this<SyncManager> {
    friend class SyncSession;
    friend class ::TestSyncManager;
    friend class ::TestAppSession;

public:
    using MetadataMode = SyncClientConfig::MetadataMode;

    // Immediately run file actions for a single Realm at a given original path.
    // Returns whether or not a file action was successfully executed for the specified Realm.
    // Preconditions: all references to the Realm at the given path must have already been invalidated.
    // The metadata and file management subsystems must also have already been configured.
    bool immediately_run_file_actions(const std::string& original_name) REQUIRES(!m_file_system_mutex);

    // Enables/disables using a single connection for all sync sessions for each host/port/user rather
    // than one per session.
    // This must be called before any sync sessions are created, cannot be
    // disabled afterwards, and currently is incompatible with automatic failover.
    void set_session_multiplexing(bool allowed) REQUIRES(!m_mutex);

    // Destroys the sync manager, terminates all sessions created by it, and stops its SyncClient.
    ~SyncManager();

    // Sets the log level for the Sync Client.
    // The log level can only be set up until the point the Sync Client is
    // created (when the first Session is created) or an App operation is
    // performed (e.g. log in).
    void set_log_level(util::Logger::Level) noexcept REQUIRES(!m_mutex);
    void set_logger_factory(SyncClientConfig::LoggerFactory) REQUIRES(!m_mutex);

    // Sets the application level user agent string.
    // This should have the format specified here:
    // https://github.com/realm/realm-sync/blob/develop/src/realm/sync/client.hpp#L126 The user agent can only be set
    // up  until the  point the Sync Client is created. This happens when the first Session is created.
    void set_user_agent(std::string user_agent) REQUIRES(!m_mutex);

    // Sets client timeout settings.
    // The timeout settings can only be set up until the point the Sync Client is created.
    // This happens when the first Session is created.
    void set_timeouts(SyncClientTimeouts timeouts) REQUIRES(!m_mutex);

    /// Ask all valid sync sessions to perform whatever tasks might be necessary to
    /// re-establish connectivity with the Realm Object Server. It is presumed that
    /// the caller knows that network connectivity has been restored.
    ///
    /// Refer to `SyncSession::handle_reconnect()` to see what sort of work is done
    /// on a per-session basis.
    void reconnect() const REQUIRES(!m_session_mutex);

    util::Logger::Level log_level() const noexcept REQUIRES(!m_mutex);

    std::vector<std::shared_ptr<SyncSession>> get_all_sessions() const REQUIRES(!m_session_mutex);
    std::shared_ptr<SyncSession> get_session(std::shared_ptr<DB> db, const RealmConfig& config)
        REQUIRES(!m_mutex, !m_session_mutex);
    std::shared_ptr<SyncSession> get_existing_session(const std::string& path) const REQUIRES(!m_session_mutex);
    std::shared_ptr<SyncSession> get_existing_active_session(const std::string& path) const
        REQUIRES(!m_session_mutex);

    // Returns `true` if the SyncManager still contains any existing sessions not yet fully cleaned up.
    // This will return true as long as there is an external reference to a session object, no matter
    // the state of that session.
    bool has_existing_sessions() REQUIRES(!m_session_mutex);

    // Blocking call that only return once all sessions have been terminated.
    // Due to the async nature of the SyncClient, even with `SyncSessionStopPolicy::Immediate`, a
    // session is not guaranteed to stop immediately when a Realm is closed. Using this method
    // makes it possible to guarantee that all sessions have, in fact, been closed.
    void wait_for_sessions_to_terminate() REQUIRES(!m_mutex);

    // If the metadata manager is configured, perform an update. Returns `true` if the code was run.
    bool perform_metadata_update(util::FunctionRef<void(SyncMetadataManager&)> update_function) const
        REQUIRES(!m_file_system_mutex);

    // Get a sync user for a given identity, or create one if none exists yet, and set its token.
    // If a logged-out user exists, it will marked as logged back in.
    std::shared_ptr<SyncUser> get_user(const std::string& id, std::string refresh_token, std::string access_token,
                                       const std::string provider_type, std::string device_id)
        REQUIRES(!m_user_mutex, !m_file_system_mutex);

    // Get an existing user for a given identifier, if one exists and is logged in.
    std::shared_ptr<SyncUser> get_existing_logged_in_user(const std::string& user_id) const REQUIRES(!m_user_mutex);

    // Get all the users that are logged in and not errored out.
    std::vector<std::shared_ptr<SyncUser>> all_users() REQUIRES(!m_user_mutex);

    // Gets the currently active user.
    std::shared_ptr<SyncUser> get_current_user() const REQUIRES(!m_user_mutex, !m_file_system_mutex);

    // Log out a given user
    void log_out_user(const std::string& user_id) REQUIRES(!m_user_mutex, !m_file_system_mutex);

    // Sets the currently active user.
    void set_current_user(const std::string& user_id) REQUIRES(!m_user_mutex, !m_file_system_mutex);

    // Removes a user
    void remove_user(const std::string& user_id) REQUIRES(!m_user_mutex, !m_file_system_mutex);

    // Permanently deletes a user.
    void delete_user(const std::string& user_id) REQUIRES(!m_user_mutex, !m_file_system_mutex);

    // Get the default path for a Realm for the given configuration.
    // The default value is `<rootDir>/<appId>/<userId>/<partitionValue>.realm`.
    // If the file cannot be created at this location, for example due to path length restrictions,
    // this function may pass back `<rootDir>/<hashedFileName>.realm`
    std::string path_for_realm(const SyncConfig& config, util::Optional<std::string> custom_file_name = none) const
        REQUIRES(!m_file_system_mutex);

    // Get the path of the recovery directory for backed-up or recovered Realms.
    std::string recovery_directory_path(util::Optional<std::string> const& custom_dir_name = none) const
        REQUIRES(!m_file_system_mutex);

    // Reset the singleton state for testing purposes. DO NOT CALL OUTSIDE OF TESTING CODE.
    // Precondition: any synced Realms or `SyncSession`s must be closed or rendered inactive prior to
    // calling this method.
    void reset_for_testing() REQUIRES(!m_mutex, !m_file_system_mutex, !m_user_mutex, !m_session_mutex);

    // Get the app metadata for the active app.
    util::Optional<SyncAppMetadata> app_metadata() const REQUIRES(!m_file_system_mutex);

    // Immediately closes any open sync sessions for this sync manager
    void close_all_sessions() REQUIRES(!m_mutex, !m_session_mutex);

    void set_sync_route(std::string sync_route) REQUIRES(!m_mutex)
    {
        util::CheckedLockGuard lock(m_mutex);
        m_sync_route = std::move(sync_route);
    }

    const std::string sync_route() const REQUIRES(!m_mutex)
    {
        util::CheckedLockGuard lock(m_mutex);
        return m_sync_route;
    }

    std::weak_ptr<app::App> app() const REQUIRES(!m_mutex)
    {
        util::CheckedLockGuard lock(m_mutex);
        return m_app;
    }

    SyncClientConfig config() const REQUIRES(!m_mutex)
    {
        util::CheckedLockGuard lock(m_mutex);
        return m_config;
    }

    // Return the cached logger
    const std::shared_ptr<util::Logger>& get_logger() const REQUIRES(!m_mutex);

    SyncManager();
    SyncManager(const SyncManager&) = delete;
    SyncManager& operator=(const SyncManager&) = delete;

    struct OnlyForTesting {
        friend class TestHelper;

        static void voluntary_disconnect_all_connections(SyncManager&);
    };

protected:
    friend class SyncUser;
    friend class SyncSesson;

    using std::enable_shared_from_this<SyncManager>::shared_from_this;
    using std::enable_shared_from_this<SyncManager>::weak_from_this;

private:
    friend class app::App;

    void configure(std::shared_ptr<app::App> app, const std::string& sync_route, const SyncClientConfig& config)
        REQUIRES(!m_mutex, !m_file_system_mutex, !m_user_mutex, !m_session_mutex);

    // Stop tracking the session for the given path if it is inactive.
    // No-op if the session is either still active or in the active sessions list
    // due to someone holding a strong reference to it.
    void unregister_session(const std::string& path) REQUIRES(!m_session_mutex);

    _impl::SyncClient& get_sync_client() const REQUIRES(!m_mutex);
    std::unique_ptr<_impl::SyncClient> create_sync_client() const REQUIRES(m_mutex);

    std::shared_ptr<SyncSession> get_existing_session_locked(const std::string& path) const REQUIRES(m_session_mutex);

    std::shared_ptr<SyncUser> get_user_for_identity(std::string const& identity) const noexcept
        REQUIRES(m_user_mutex);

    mutable util::CheckedMutex m_mutex;

    bool run_file_action(SyncFileActionMetadata&) REQUIRES(m_file_system_mutex);
    void init_metadata(SyncClientConfig config, const std::string& app_id);

    // internally create a new logger - used by configure() and set_logger_factory()
    void do_make_logger() REQUIRES(m_mutex);

    // Protects m_users
    mutable util::CheckedMutex m_user_mutex;

    // A vector of all SyncUser objects.
    std::vector<std::shared_ptr<SyncUser>> m_users GUARDED_BY(m_user_mutex);
    std::shared_ptr<SyncUser> m_current_user GUARDED_BY(m_user_mutex);

    mutable std::unique_ptr<_impl::SyncClient> m_sync_client GUARDED_BY(m_mutex);

    SyncClientConfig m_config GUARDED_BY(m_mutex);
    mutable std::shared_ptr<util::Logger> m_logger_ptr GUARDED_BY(m_mutex);

    // Protects m_file_manager and m_metadata_manager
    mutable util::CheckedMutex m_file_system_mutex;
    std::unique_ptr<SyncFileManager> m_file_manager GUARDED_BY(m_file_system_mutex);
    std::unique_ptr<SyncMetadataManager> m_metadata_manager GUARDED_BY(m_file_system_mutex);

    // Protects m_sessions
    mutable util::CheckedMutex m_session_mutex;

    // Map of sessions by path name.
    // Sessions remove themselves from this map by calling `unregister_session` once they're
    // inactive and have performed any necessary cleanup work.
    std::unordered_map<std::string, std::shared_ptr<SyncSession>> m_sessions GUARDED_BY(m_session_mutex);

    // Internal method returning `true` if the SyncManager still contains sessions not yet fully closed.
    // Callers of this method should hold the `m_session_mutex` themselves.
    bool do_has_existing_sessions() REQUIRES(m_session_mutex);

    std::string m_sync_route GUARDED_BY(m_mutex);

    std::weak_ptr<app::App> m_app GUARDED_BY(m_mutex);
};

} // namespace realm

#endif // REALM_OS_SYNC_MANAGER_HPP