//////////////////////////////////////////////////////////////////////////// // // 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. // //////////////////////////////////////////////////////////////////////////// #pragma once #include #include #include "js_class.hpp" #include "js_types.hpp" #include "js_util.hpp" #include "js_realm_object.hpp" #include "js_list.hpp" #include "js_results.hpp" #include "js_schema.hpp" #include "shared_realm.hpp" #include "binding_context.hpp" #include "object_accessor.hpp" #include "platform.hpp" namespace realm { namespace js { template class Realm; template struct RealmClass; template class RealmDelegate : public BindingContext { public: using GlobalContextType = typename T::GlobalContext; using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; using Value = js::Value; using ObjectDefaultsMap = typename Schema::ObjectDefaultsMap; using ConstructorMap = typename Schema::ConstructorMap; virtual void did_change(std::vector const& observers, std::vector const& invalidated) { notify("change"); } virtual std::vector get_observed_rows() { return std::vector(); } virtual void will_change(std::vector const& observers, std::vector const& invalidated) {} RealmDelegate(std::weak_ptr realm, GlobalContextType ctx) : m_context(ctx), m_realm(realm) {} ~RealmDelegate() { // All protected values need to be unprotected while the context is retained. m_defaults.clear(); m_constructors.clear(); m_notifications.clear(); } void add_notification(FunctionType notification) { for (auto &handler : m_notifications) { if (handler == notification) { return; } } m_notifications.emplace_back(m_context, notification); } void remove_notification(FunctionType notification) { for (auto iter = m_notifications.begin(); iter != m_notifications.end(); ++iter) { if (*iter == notification) { m_notifications.erase(iter); return; } } } void remove_all_notifications() { m_notifications.clear(); } ObjectDefaultsMap m_defaults; ConstructorMap m_constructors; private: Protected m_context; std::list> m_notifications; std::weak_ptr m_realm; void notify(const char *notification_name) { SharedRealm realm = m_realm.lock(); if (!realm) { throw std::runtime_error("Realm no longer exists"); } ObjectType realm_object = create_object>(m_context, new SharedRealm(realm)); ValueType arguments[2]; arguments[0] = realm_object; arguments[1] = Value::from_string(m_context, notification_name); for (auto &callback : m_notifications) { Function::call(m_context, callback, realm_object, 2, arguments); } } friend class Realm; }; std::string default_path(); void set_default_path(std::string path); void delete_all_realms(); template class Realm { using GlobalContextType = typename T::GlobalContext; using ContextType = typename T::Context; using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; using String = js::String; using Object = js::Object; using Value = js::Value; using ReturnValue = js::ReturnValue; using NativeAccessor = realm::NativeAccessor; public: static FunctionType create_constructor(ContextType); // methods static void objects(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void create(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void delete_one(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void delete_all(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void write(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void close(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); // properties static void get_path(ContextType, ObjectType, ReturnValue &); static void get_schema_version(ContextType, ObjectType, ReturnValue &); static void get_schema(ContextType, ObjectType, ReturnValue &); static void get_read_only(ContextType, ObjectType, ReturnValue &); // static methods static void constructor(ContextType, ObjectType, size_t, const ValueType[]); static void schema_version(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void clear_test_state(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); // static properties static void get_default_path(ContextType, ObjectType, ReturnValue &); static void set_default_path(ContextType, ObjectType, ValueType value); private: static std::string validated_notification_name(ContextType ctx, const ValueType &value) { std::string name = Value::validated_to_string(ctx, value, "notification name"); if (name != "change") { throw std::runtime_error("Only the 'change' notification name is supported."); } return name; } // converts constructor object or type name to type name static std::string validated_object_type_for_value(SharedRealm &realm, ContextType ctx, const ValueType &value) { if (Value::is_constructor(ctx, value)) { FunctionType constructor = Value::to_constructor(ctx, value); auto delegate = get_delegate(realm.get()); for (auto &pair : delegate->m_constructors) { if (FunctionType(pair.second) == constructor) { return pair.first; } } throw std::runtime_error("Constructor was not registered in the schema for this Realm"); } return Value::validated_to_string(ctx, value, "objectType"); } static std::string normalize_path(std::string path) { if (path.size() && path[0] != '/') { return default_realm_file_directory() + "/" + path; } return path; } }; template struct RealmClass : ClassDefinition { using Realm = js::Realm; std::string const name = "Realm"; ConstructorType* const constructor = Realm::constructor; MethodMap const static_methods = { {"schemaVersion", wrap}, {"clearTestState", wrap}, }; PropertyMap const static_properties = { {"defaultPath", {wrap, wrap}}, }; MethodMap const methods = { {"objects", wrap}, {"create", wrap}, {"delete", wrap}, {"deleteAll", wrap}, {"write", wrap}, {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, {"close", wrap}, }; PropertyMap const properties = { {"path", {wrap, nullptr}}, {"schemaVersion", {wrap, nullptr}}, {"schema", {wrap, nullptr}}, {"readOnly", {wrap, nullptr}}, }; }; template inline typename T::Function Realm::create_constructor(ContextType ctx) { FunctionType realm_constructor = ObjectWrap>::create_constructor(ctx); FunctionType collection_constructor = ObjectWrap>::create_constructor(ctx); FunctionType list_constructor = ObjectWrap>::create_constructor(ctx); FunctionType results_constructor = ObjectWrap>::create_constructor(ctx); FunctionType realm_object_constructor = ObjectWrap>::create_constructor(ctx); PropertyAttributes attributes = PropertyAttributes(ReadOnly | DontEnum | DontDelete); Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes); Object::set_property(ctx, realm_constructor, "List", list_constructor, attributes); Object::set_property(ctx, realm_constructor, "Results", results_constructor, attributes); Object::set_property(ctx, realm_constructor, "Object", realm_object_constructor, attributes); return realm_constructor; } template void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[]) { realm::Realm::Config config; typename Schema::ObjectDefaultsMap defaults; typename Schema::ConstructorMap constructors; bool schema_updated = false; if (argc == 0) { config.path = default_path(); } else if (argc == 1) { ValueType value = arguments[0]; if (Value::is_string(ctx, value)) { config.path = Value::validated_to_string(ctx, value, "path"); } else if (Value::is_object(ctx, value)) { ObjectType object = Value::validated_to_object(ctx, value); static const String path_string = "path"; ValueType path_value = Object::get_property(ctx, object, path_string); if (!Value::is_undefined(ctx, path_value)) { config.path = Value::validated_to_string(ctx, path_value, "path"); } else { config.path = js::default_path(); } static const String read_only_string = "readOnly"; ValueType read_only_value = Object::get_property(ctx, object, read_only_string); config.read_only = Value::is_undefined(ctx, read_only_value) ? false : Value::validated_to_boolean(ctx, read_only_value, "readOnly"); static const String schema_string = "schema"; ValueType schema_value = Object::get_property(ctx, object, schema_string); if (!Value::is_undefined(ctx, schema_value)) { ObjectType schema_object = Value::validated_to_object(ctx, schema_value, "schema"); config.schema.reset(new realm::Schema(Schema::parse_schema(ctx, schema_object, defaults, constructors))); schema_updated = true; } static const String schema_version_string = "schemaVersion"; ValueType version_value = Object::get_property(ctx, object, schema_version_string); if (!Value::is_undefined(ctx, version_value)) { config.schema_version = Value::validated_to_number(ctx, version_value, "schemaVersion"); } else { config.schema_version = 0; } static const String migration_string = "migration"; ValueType migration_value = Object::get_property(ctx, object, migration_string); if (!Value::is_undefined(ctx, migration_value)) { FunctionType migration_function = Value::validated_to_function(ctx, migration_value, "migration"); config.migration_function = [=](SharedRealm old_realm, SharedRealm realm) { ValueType arguments[2] = { create_object>(ctx, new SharedRealm(old_realm)), create_object>(ctx, new SharedRealm(realm)) }; Function::call(ctx, migration_function, 2, arguments); }; } static const String encryption_key_string = "encryptionKey"; ValueType encryption_key_value = Object::get_property(ctx, object, encryption_key_string); if (!Value::is_undefined(ctx, encryption_key_value)) { std::string encryption_key = NativeAccessor::to_binary(ctx, encryption_key_value); config.encryption_key = std::vector(encryption_key.begin(), encryption_key.end()); } } } else { throw std::runtime_error("Invalid arguments when constructing 'Realm'"); } config.path = normalize_path(config.path); ensure_directory_exists_for_file(config.path); SharedRealm realm = realm::Realm::get_shared_realm(config); GlobalContextType global_context = Context::get_global_context(ctx); BindingContext *binding_context = realm->m_binding_context.get(); RealmDelegate *js_binding_context = dynamic_cast *>(binding_context); if (!binding_context) { js_binding_context = new RealmDelegate(realm, global_context); realm->m_binding_context.reset(js_binding_context); } else if (!js_binding_context || js_binding_context->m_context != global_context) { throw std::runtime_error("Realm is already open in another context on this thread: " + config.path); } // If a new schema was provided, then use its defaults and constructors. if (schema_updated) { js_binding_context->m_defaults = std::move(defaults); js_binding_context->m_constructors = std::move(constructors); } set_internal>(this_object, new SharedRealm(realm)); } template void Realm::schema_version(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1, 2); realm::Realm::Config config; config.path = normalize_path(Value::validated_to_string(ctx, arguments[0])); if (argc == 2) { auto encryptionKeyValue = arguments[1]; std::string encryptionKey = NativeAccessor::to_binary(ctx, encryptionKeyValue); config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end()); } auto version = realm::Realm::get_schema_version(config); if (version == ObjectStore::NotVersioned) { return_value.set(-1); } else { return_value.set((double)version); } } template void Realm::clear_test_state(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); delete_all_realms(); } template void Realm::get_default_path(ContextType ctx, ObjectType object, ReturnValue &return_value) { return_value.set(realm::js::default_path()); } template void Realm::set_default_path(ContextType ctx, ObjectType object, ValueType value) { js::set_default_path(Value::validated_to_string(ctx, value, "defaultPath")); } template void Realm::get_path(ContextType ctx, ObjectType object, ReturnValue &return_value) { std::string path = get_internal>(object)->get()->config().path; return_value.set(path); } template void Realm::get_schema_version(ContextType ctx, ObjectType object, ReturnValue &return_value) { double version = get_internal>(object)->get()->config().schema_version; return_value.set(version); } template void Realm::get_schema(ContextType ctx, ObjectType object, ReturnValue &return_value) { auto schema = get_internal>(object)->get()->config().schema.get(); return_value.set(Schema::object_for_schema(ctx, *schema)); } template void Realm::get_read_only(ContextType ctx, ObjectType object, ReturnValue &return_value) { return_value.set(get_internal>(object)->get()->config().read_only); } template void Realm::objects(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); std::string type = validated_object_type_for_value(realm, ctx, arguments[0]); return_value.set(Results::create_instance(ctx, realm, type)); } template void Realm::create(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2, 3); SharedRealm realm = *get_internal>(this_object); std::string className = validated_object_type_for_value(realm, ctx, arguments[0]); auto &schema = realm->config().schema; auto object_schema = schema->find(className); if (object_schema == schema->end()) { throw std::runtime_error("Object type '" + className + "' not found in schema."); } ObjectType object = Value::validated_to_object(ctx, arguments[1], "properties"); if (Value::is_array(ctx, arguments[1])) { object = Schema::dict_for_property_array(ctx, *object_schema, object); } bool update = false; if (argc == 3) { update = Value::validated_to_boolean(ctx, arguments[2], "update"); } auto realm_object = realm::Object::create(ctx, realm, *object_schema, object, update); return_value.set(RealmObject::create_instance(ctx, realm_object)); } template void Realm::delete_one(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); } ObjectType arg = Value::validated_to_object(ctx, arguments[0]); if (Object::template is_instance>(ctx, arg)) { auto object = get_internal>(arg); realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); table->move_last_over(object->row().get_index()); } else if (Value::is_array(ctx, arg)) { uint32_t length = Object::validated_get_length(ctx, arg); for (uint32_t i = length; i--;) { ObjectType object = Object::validated_get_object(ctx, arg, i); if (!Object::template is_instance>(ctx, object)) { throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); } auto realm_object = get_internal>(object); realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), realm_object->get_object_schema().name); table->move_last_over(realm_object->row().get_index()); } } else if (Object::template is_instance>(ctx, arg)) { auto results = get_internal>(arg); results->clear(); } else if (Object::template is_instance>(ctx, arg)) { auto list = get_internal>(arg); list->delete_all(); } else { throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); } } template void Realm::delete_all(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); SharedRealm realm = *get_internal>(this_object); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); } for (auto objectSchema : *realm->config().schema) { ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name)->clear(); } } template void Realm::write(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); FunctionType callback = Value::validated_to_function(ctx, arguments[0]); realm->begin_transaction(); try { Function::call(ctx, callback, this_object, 0, nullptr); } catch (std::exception &e) { realm->cancel_transaction(); throw; } realm->commit_transaction(); } template void Realm::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); __unused std::string name = validated_notification_name(ctx, arguments[0]); auto callback = Value::validated_to_function(ctx, arguments[1]); SharedRealm realm = *get_internal>(this_object); get_delegate(realm.get())->add_notification(callback); } template void Realm::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); __unused std::string name = validated_notification_name(ctx, arguments[0]); auto callback = Value::validated_to_function(ctx, arguments[1]); SharedRealm realm = *get_internal>(this_object); get_delegate(realm.get())->remove_notification(callback); } template void Realm::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0, 1); if (argc) { validated_notification_name(ctx, arguments[0]); } SharedRealm realm = *get_internal>(this_object); get_delegate(realm.get())->remove_all_notifications(); } template void Realm::close(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); SharedRealm realm = *get_internal>(this_object); realm->close(); } } // js } // realm