diff --git a/SpeckleConnector/Connector/Database/Model/Card/ModelCardDatabase.h b/SpeckleConnector/Connector/Database/Model/Card/ModelCardDatabase.h index 8ff7d31..39a61f3 100644 --- a/SpeckleConnector/Connector/Database/Model/Card/ModelCardDatabase.h +++ b/SpeckleConnector/Connector/Database/Model/Card/ModelCardDatabase.h @@ -2,6 +2,7 @@ #define CONNECTOR_DATABASE_MODEL_CARD_DATABASE #include "Active/Serialise/Package/Package.h" +#include "Speckle/Database/Storage/DocumentStore/DocumentStoreEngine.h" #include "Speckle/Utility/String.h" namespace connector::database { diff --git a/SpeckleLib/Speckle/Database/Storage/DocumentStore/DocumentStoreCore.cpp b/SpeckleLib/Speckle/Database/Storage/DocumentStore/DocumentStoreCore.cpp new file mode 100644 index 0000000..36cbee2 --- /dev/null +++ b/SpeckleLib/Speckle/Database/Storage/DocumentStore/DocumentStoreCore.cpp @@ -0,0 +1,175 @@ +#include "Speckle/Database/Storage/DocumentStore/DocumentStoreCore.h" + +#include "Active/Utility/Memory.h" +#include "Active/Utility/String.h" +#include "Speckle/Environment/Addon.h" +#include "Speckle/Utility/Guid.h" +#include "Speckle/Utility/String.h" + +#ifdef ARCHICAD +#include +#include +#endif + +using namespace active::setting; +using namespace speckle::database; +using namespace speckle::environment; +using namespace speckle::utility; + +using enum DocumentStoreCore::Status; + +namespace { + +#ifdef ARCHICAD + /*-------------------------------------------------------------------- + Convert an Archicad API error code to a DocumentStoreCore status + + acErrorCode: The API error code + + return: An equivalent status code + --------------------------------------------------------------------*/ + DocumentStoreCore::Status convertArchicadError(long acErrorCode) { + using enum DocumentStoreCore::Status; + switch (acErrorCode) { + case NoError: + return nominal; + case APIERR_BADPARS: + return badParameter; + case APIERR_BADID: + return badID; + default: + break; + } + return error; + } //convertArchicadError + + + /*-------------------------------------------------------------------- + Determine if a specified data store exists + + id: The store identity + + return: True if the store exists + --------------------------------------------------------------------*/ + bool isExistingStore(active::utility::NameID& storeID) { + if (storeID.id) + return true; //We must have a store if the ID is populated + bool isStoreFound = false; +#ifdef ARCHICAD + API_Guid acID; + if (auto statusCode = convertArchicadError(ACAPI_AddOnObject_GetObjectGuidFromName(String{storeID.name}, &acID)); statusCode != nominal) + throw std::system_error(DocumentStoreCore::makeError(statusCode)); + storeID.id = Guid{acID}; + isStoreFound = true; +#endif + return isStoreFound; + } //isExistingStore + +#endif + + ///Category for DocumentStore processing errors + class DocumentStoreCategory : public std::error_category { + public: + ///Category name + const char* name() const noexcept override { + return "active::database::sqlite::category"; + } + /*! + Get a message for a specified error code + @param errorCode A DocumentStore processing code + @return The error message for the specified code + */ + std::string message(int errorCode) const override { + //TODO: These error messages are ok for developers - but can we help users more? + switch (static_cast(errorCode)) { + case nominal: + return ""; + case badParameter: + return "An internal function has been incorrectly used"; + case badID: + return "Internal data has been requested using an invalid identity"; + case error: + return "A non-specific error occurred"; + default: + return "An unknown error occurred"; + } + } + }; + + ///DocumentStore processing category error instance + static DocumentStoreCategory instance; + +} + +/*-------------------------------------------------------------------- + Make an error code for DocumentStore processing + + return: An STL error code + --------------------------------------------------------------------*/ +std::error_code DocumentStoreCore::makeError(DocumentStoreCore::Status code) { + return std::error_code(static_cast(code), instance); +} //DocumentStoreCore::makeError + + +/*-------------------------------------------------------------------- + Read the data stored in the document + + return: The stored data (empty if the data doesn't exist) + --------------------------------------------------------------------*/ +active::utility::Memory DocumentStoreCore::readStore() const { + active::utility::Memory result; + //First establish that we actually have stored data to read + if (!isExistingStore(m_id)) + return result; + //Read the stored data +#ifdef ARCHICAD + GS::UniString storeName{String{m_id.name}}; + GSHandle storedData; + if (auto statusCode = convertArchicadError(ACAPI_AddOnObject_GetObjectContent(Guid{m_id.id}, &storeName, &storedData)); statusCode != nominal) + throw std::system_error(makeError(statusCode)); + //Copy the stored data into the result + auto storeSize = BMGetHandleSize(storedData); + result.resize(storeSize); + active::utility::Memory::copy(result.data(), *storedData, storeSize, storeSize); + BMKillHandle(&storedData); +#endif + return result; +} //DocumentStoreCore::readStore + + +/*-------------------------------------------------------------------- + Write the data to document storage + + toWrite: The data to write to storage + --------------------------------------------------------------------*/ +void DocumentStoreCore::writeStore() { +#ifdef ARCHICAD + //Ensure a suitable data store exists + if (!isExistingStore(m_id)) { + //Create when missing + API_Guid acID; + if (auto statusCode = convertArchicadError(ACAPI_AddOnObject_CreateUniqueObject(String{m_id.name}, &acID)); statusCode != nominal) + throw std::system_error(makeError(statusCode)); + m_id = Guid{acID}; + } + //Reserve the storage object in TW + if (addon()->isSharedDocument()) { + GS::HashTable conflicts; + if (auto statusCode = convertArchicadError(ACAPI_AddOnObject_ReserveObjects({Guid{m_id.id}}, &conflicts)); statusCode != nominal) + throw std::system_error(makeError(statusCode)); + //TODO: Implamenent handling for conflicts + } + auto toWrite = buildStore(); + //Write the new data + GSHandle output = BMAllocateHandle(static_cast(toWrite.size()), ALLOCATE_CLEAR, 0); + active::utility::Memory::copy(*output, toWrite.data(), toWrite.size(), toWrite.size()); + if (auto statusCode = convertArchicadError(ACAPI_AddOnObject_ModifyObject({Guid{m_id.id}}, nullptr, &output)); statusCode != nominal) + throw std::system_error(makeError(statusCode)); + BMKillHandle(&output); + //Release the storage object in TW + if (addon()->isSharedDocument()) { + if (auto statusCode = convertArchicadError(ACAPI_AddOnObject_ReleaseObjects({Guid{m_id.id}})); statusCode != nominal) + throw std::system_error(makeError(statusCode)); + } +#endif +} //DocumentStoreCore::writeStore diff --git a/SpeckleLib/Speckle/Database/Storage/DocumentStore/DocumentStoreCore.h b/SpeckleLib/Speckle/Database/Storage/DocumentStore/DocumentStoreCore.h new file mode 100644 index 0000000..7a81aa1 --- /dev/null +++ b/SpeckleLib/Speckle/Database/Storage/DocumentStore/DocumentStoreCore.h @@ -0,0 +1,91 @@ +#ifndef SPECKLE_DATABASE_DOCUMENT_STORE_CORE +#define SPECKLE_DATABASE_DOCUMENT_STORE_CORE + +#include "Active/File/Path.h" +#include "Active/Setting/SettingList.h" +#include "Active/Database/Storage/DBaseSchema.h" +#include "Active/Utility/NameID.h" + +namespace speckle::database { + + /*! + Core functionality and definitions for a mechanism to store data in a BIM (3rd-party) document/database + + Currently implement for Archicad Add-On Objects + */ + class DocumentStoreCore { + public: + + // MARK: - Types + + ///Status of of the DocumentStore database + enum class Status { + nominal, /// +#include + +namespace speckle::database { + + ///Concept for the ability to store objects in a document + template + concept DocumentStorable = std::is_base_of_v && + std::is_base_of_v; + + /*! + A database engine to store records in a 3rd-party BIM document + + Due to the fact that these records are intended to represent a single table and are stored in a document, the concepts of 'table' and + 'document' aren't currently applicable (this could be extended in future if there is a use-case) + @tparam Obj Interface for the stored object. NB: This can be a base class for an object hierarchy, not necessarily a concrete class + @tparam Transport The serialisation transport mechanism for objects + @tparam ObjID The object identifier type, e.g. Guid + */ + template + requires DocumentStorable + class DocumentStoreEngine : public DocumentStoreCore, public active::database::DBaseEngine { + public: + + // MARK: - Types + + using base = active::database::DBaseEngine; + using Filter = base::Filter; + using Cache = active::database::RecordCache; + + // MARK: - Constructors + + /*! + Constructor + @param id The document storage identifier + */ + DocumentStoreEngine(const active::utility::NameID& id) : DocumentStoreCore{id} {} + /*! + Copy constructor + @param source The object to copy + */ + DocumentStoreEngine(const DocumentStoreEngine& source) : DocumentStoreCore{source}, base{source} { + m_cache = (source.m_cache) ? std::make_unique(*source.m_cache) : nullptr; + } + /*! + Object cloning + @return A clone of this object + */ + DocumentStoreEngine* clonePtr() const override { return new DocumentStoreEngine{*this}; } + + // MARK: - Functions (const) + + /*! + Get an object by index + @param ID The object ID + @param tableID Optional table ID (defaults to the first table) + @param documentID Optional document ID (when the object is bound to a specific document) + @return The requested object (nullptr on failure) + */ + std::unique_ptr getObject(const ObjID& ID, utility::String::Option tableID = std::nullopt, utility::String::Option documentID = std::nullopt) const override; + /*! + Get all objects + @param tableID Optional table ID (defaults to the first table) + @param documentID Optional document ID (filter for this document only - nullopt = all objects) + @return The requested objects (nullptr on failure) + */ + active::container::Vector getObjects(utility::String::Option tableID = std::nullopt, utility::String::Option documentID = std::nullopt) const override; + /*! + Get a filtered list of objects + @param filter The object filter + @param tableID Optional table ID (defaults to the first table) + @param documentID Optional document ID (filter for this document only - nullopt = all objects) + @return The filtered objects (nullptr on failure) + */ + active::container::Vector getObjects(const Filter& filter, utility::String::Option tableID = std::nullopt, + utility::String::Option documentID = std::nullopt) const override; + /*! + Erase an object by index + @param ID The object ID + @param tableID Optional table ID (defaults to the first table) + @param documentID Optional document ID (when the object is bound to a specific document) + @throw Exception thrown on SQL error + */ + void erase(const ObjID& ID, utility::String::Option tableID = std::nullopt, utility::String::Option documentID = std::nullopt) const override; + /*! + Erase all objects + @param tableID Optional table ID (defaults to the first table) + @param documentID Optional document ID (when the object is bound to a specific document) + @throw Exception thrown on SQL error + */ + void erase(utility::String::Option tableID = std::nullopt, utility::String::Option documentID = std::nullopt) const override; + protected: + /*! + Get the cache of records built from the data stored in the document + @return The cached records + */ + Cache* getCache() const; + /*! + Build new store data from the latest records + @return Data to store from the latest records + */ + active::utility::Memory buildStore() override; + /*! + Merge existing stored data with incoming stored data (from an external source) + @param toMerge The external stored data to merge + @return The merged data to be stored + */ + active::utility::Memory mergeStore(const active::utility::Memory& toMerge) override; + /*! + Reset the stored data (some external change has invalidated previous data, e.g. the document was closed) + */ + void resetStore() override { m_cache.reset(); } + + private: + //Cached records from the document store + std::unique_ptr m_cache; + }; + + /*-------------------------------------------------------------------- + Get the cache of records built from the data stored in the document + + return: The cached records (nullptr on failure) + --------------------------------------------------------------------*/ + template + requires DocumentStorable + typename DocumentStoreEngine::Cache* DocumentStoreEngine::getCache() const { + if (m_cache) + return m_cache; + //Read the data stored in the document + auto storedData = readStore(); + m_cache = std::make_unique(); + if (!storedData) + return m_cache; //Return an empty container if there's no data + //Import the document data into the record cache + Transport().receive(std::forward(*m_cache), active::serialise::Identity{}, storedData); + return m_cache; + } //DocumentStoreEngine::getCache + + + /*-------------------------------------------------------------------- + Get an object by index + + index: The object index + tableID: Optional table ID (defaults to the first table) + documentID: Optional document ID (when the object is bound to a specific document) + + return: The requested object (nullptr on failure) + --------------------------------------------------------------------*/ + template + requires DocumentStorable + std::unique_ptr DocumentStoreEngine::getObject(const ObjID& ID, utility::String::Option tableID, + utility::String::Option documentID) const { + auto cache = getCache(); + return cache ? cache->read(ID) : nullptr; + } //DocumentStoreEngine::getObject + + + /*-------------------------------------------------------------------- + Get all objects + + tableID: Optional table ID (defaults to the first table) + documentID: Optional document ID (filter for this document only - nullopt = all objects) + + return: The requested objects (nullptr on failure) + --------------------------------------------------------------------*/ + template + requires DocumentStorable + active::container::Vector DocumentStoreEngine::getObjects(utility::String::Option tableID, + utility::String::Option documentID) const { + auto cache = getCache(); + return cache ? cache->read() : nullptr; + } //DocumentStoreEngine::getObjects + + + /*-------------------------------------------------------------------- + Get all objects + + filter: The object filter + tableID: Optional table ID (defaults to the first table) + documentID: Optional document ID (filter for this document only - nullopt = all objects) + + return: The requested objects (nullptr on failure) + --------------------------------------------------------------------*/ + template + requires DocumentStorable + active::container::Vector DocumentStoreEngine::getObjects(const Filter& filter, utility::String::Option tableID, + utility::String::Option documentID) const { + auto cache = getCache(); + return cache ? cache->read(filter) : nullptr; + } //DocumentStoreEngine::getObjects + + + /*-------------------------------------------------------------------- + Erase an object by index + + objID: The object ID + tableID: Optional table ID (defaults to the first table) + documentID: Optional document ID (when the object is bound to a specific document) + + return: True if the object was successfully erased + --------------------------------------------------------------------*/ + template + requires DocumentStorable + void DocumentStoreEngine::erase(const ObjID& ID, utility::String::Option tableID, + utility::String::Option documentID) const { + auto cache = getCache(); + if (cache) + cache->erase(ID); + } //DocumentStoreEngine::erase + + + /*-------------------------------------------------------------------- + Erase all objects + + tableID: Optional table ID (defaults to the first table) + documentID: Optional document ID (filter for this document only - nullopt = all objects) + --------------------------------------------------------------------*/ + template + requires DocumentStorable + void DocumentStoreEngine::erase(utility::String::Option tableID, utility::String::Option documentID) const { + auto cache = getCache(); + if (cache) + cache->erase(); + } //DocumentStoreEngine::erase + + + /*-------------------------------------------------------------------- + Build new store data from the latest records + + return: Data to store from the latest records + --------------------------------------------------------------------*/ + template + requires DocumentStorable + active::utility::Memory DocumentStoreEngine::buildStore() { + active::utility::Memory result; + auto cache = getCache(); + if (!cache) + cache = std::make_unique(); //We still want to export an empty cache object even if it contains no records + //Export the cached records + Transport().send(std::forward(*m_cache), active::serialise::Identity{}, result); + return result; + } //DocumentStoreEngine::buildStore + + + /*-------------------------------------------------------------------- + Merge existing stored data with incoming stored data (from an external source) + + toMerge: The external stored data to merge + + return: The merged data to be stored + --------------------------------------------------------------------*/ + template + requires DocumentStorable + active::utility::Memory DocumentStoreEngine::mergeStore(const active::utility::Memory& toMerge) { + //Import the incoming records from the data to merge + Cache incoming; + Transport().receive(std::forward(incoming), active::serialise::Identity{}, toMerge); + //Get the data currently stored in the document + auto cache = getCache(); + if (!cache) + cache = std::make_unique(); //We still want to export an empty cache object even if it contains no records + cache->merge(incoming); + return buildStore(); + } //DocumentStoreEngine::mergeStore + +} + +#endif //SPECKLE_DATABASE_DOCUMENT_STORE_ENGINE diff --git a/SpeckleLib/Speckle/Environment/Addon.cpp b/SpeckleLib/Speckle/Environment/Addon.cpp index 99a360c..015e643 100644 --- a/SpeckleLib/Speckle/Environment/Addon.cpp +++ b/SpeckleLib/Speckle/Environment/Addon.cpp @@ -46,11 +46,27 @@ String Addon::getLocalString(short itemIndex, short resourceIndex) const { RSGetIndString(&string, itemIndex, resourceIndex, ACAPI_GetOwnResModule()); return string; #else - return String{} + return String{}; #endif } //Addon::getLocalString +/*-------------------------------------------------------------------- + Determine if the active document is shared (in collaborative environments) + + return: True if the active document is shared + --------------------------------------------------------------------*/ +bool Addon::isSharedDocument() const { +#ifdef ARCHICAD + API_ProjectInfo pi{}; + ACAPI_ProjectOperation_Project(&pi); + return pi.teamwork; +#else + return false; +#endif +} //Addon::isSharedDocument + + /*-------------------------------------------------------------------- Publish an event from an external source to subscribers diff --git a/SpeckleLib/Speckle/Environment/Addon.h b/SpeckleLib/Speckle/Environment/Addon.h index 476ae98..7f52e71 100644 --- a/SpeckleLib/Speckle/Environment/Addon.h +++ b/SpeckleLib/Speckle/Environment/Addon.h @@ -38,6 +38,11 @@ namespace speckle::environment { @return The localised string */ speckle::utility::String getLocalString(short itemIndex, short resourceIndex) const; + /*! + Determine if the active document is shared (in collaborative environments) + @return True if the active document is shared + */ + bool isSharedDocument() const; // MARK: - Functions (mutating) diff --git a/SpeckleLib/Speckle/Interface/Browser/JSPortal.h b/SpeckleLib/Speckle/Interface/Browser/JSPortal.h index b610291..14bcc26 100644 --- a/SpeckleLib/Speckle/Interface/Browser/JSPortal.h +++ b/SpeckleLib/Speckle/Interface/Browser/JSPortal.h @@ -102,8 +102,8 @@ namespace speckle::interfac::browser { --------------------------------------------------------------------*/ template bool JSPortal::install(std::shared_ptr> object) { -#ifdef ARCHICAD try { +#ifdef ARCHICAD auto engine = getJSEngine(); if (!engine) return false; @@ -127,10 +127,10 @@ namespace speckle::interfac::browser { object->setPortal(*this); return true; } +#endif } catch(...) { ///TODO: Need to discuss the best course of action to notify of a failure } -#endif return false; } //JSPortal::install diff --git a/SpeckleLib/Speckle/Utility/Guid.h b/SpeckleLib/Speckle/Utility/Guid.h index b94b5c9..87de071 100644 --- a/SpeckleLib/Speckle/Utility/Guid.h +++ b/SpeckleLib/Speckle/Utility/Guid.h @@ -22,6 +22,12 @@ namespace speckle::utility { // MARK: - Constructors using base::base; + + /*! + Constructor + @param source The guid to copy + */ + Guid(const active::utility::Guid& source) : base{source} {} #ifdef ARCHICAD /*! diff --git a/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj b/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj index b47d85c..f6d024e 100644 --- a/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj +++ b/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj @@ -37,6 +37,9 @@ 21D0BD562C890B1C0077E104 /* ServerMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 21D0BD542C890B1C0077E104 /* ServerMigration.h */; }; 21D0BD592C8910400077E104 /* UserInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21D0BD572C8910400077E104 /* UserInfo.cpp */; }; 21D0BD5A2C8910400077E104 /* UserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 21D0BD582C8910400077E104 /* UserInfo.h */; }; + 21D0BDB32C8F8AB60077E104 /* DocumentStoreCore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21D0BDAC2C8F8AB60077E104 /* DocumentStoreCore.cpp */; }; + 21D0BDB42C8F8AB60077E104 /* DocumentStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 21D0BDAD2C8F8AB60077E104 /* DocumentStoreCore.h */; }; + 21D0BDB52C8F8AB60077E104 /* DocumentStoreEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 21D0BDAE2C8F8AB60077E104 /* DocumentStoreEngine.h */; }; 21F69F3B2C6B880C008B6A06 /* JSBaseTransport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21F69F382C6B880B008B6A06 /* JSBaseTransport.cpp */; }; 21F69F512C6CCC25008B6A06 /* BrowserBridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21F69F4A2C6CCC25008B6A06 /* BrowserBridge.cpp */; }; 21F69F612C6D0286008B6A06 /* GetBindingsMethodNames.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21F69F602C6D0286008B6A06 /* GetBindingsMethodNames.cpp */; }; @@ -132,6 +135,9 @@ 21D0BD542C890B1C0077E104 /* ServerMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerMigration.h; sourceTree = ""; }; 21D0BD572C8910400077E104 /* UserInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserInfo.cpp; sourceTree = ""; }; 21D0BD582C8910400077E104 /* UserInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserInfo.h; sourceTree = ""; }; + 21D0BDAC2C8F8AB60077E104 /* DocumentStoreCore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DocumentStoreCore.cpp; sourceTree = ""; }; + 21D0BDAD2C8F8AB60077E104 /* DocumentStoreCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocumentStoreCore.h; sourceTree = ""; }; + 21D0BDAE2C8F8AB60077E104 /* DocumentStoreEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocumentStoreEngine.h; sourceTree = ""; }; 21F69F012C66C229008B6A06 /* Doxyfile */ = {isa = PBXFileReference; lastKnownFileType = text; name = Doxyfile; path = Documentation/Doxyfile; sourceTree = ""; }; 21F69F192C6A0FE2008B6A06 /* JSBinding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBinding.h; sourceTree = ""; }; 21F69F352C6AA9B3008B6A06 /* JSFunction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSFunction.h; sourceTree = ""; }; @@ -293,8 +299,9 @@ children = ( 21D0BD1D2C86F0280077E104 /* AccountDatabase.cpp */, 21D0BD1E2C86F0280077E104 /* AccountDatabase.h */, - 21D0BD302C86FE090077E104 /* Identity */, 21D0BD272C86FC350077E104 /* Content */, + 21D0BD302C86FE090077E104 /* Identity */, + 21D0BDB02C8F8AB60077E104 /* Storage */, ); path = Database; sourceTree = ""; @@ -318,6 +325,24 @@ path = Identity; sourceTree = ""; }; + 21D0BDAF2C8F8AB60077E104 /* DocumentStore */ = { + isa = PBXGroup; + children = ( + 21D0BDAC2C8F8AB60077E104 /* DocumentStoreCore.cpp */, + 21D0BDAD2C8F8AB60077E104 /* DocumentStoreCore.h */, + 21D0BDAE2C8F8AB60077E104 /* DocumentStoreEngine.h */, + ); + path = DocumentStore; + sourceTree = ""; + }; + 21D0BDB02C8F8AB60077E104 /* Storage */ = { + isa = PBXGroup; + children = ( + 21D0BDAF2C8F8AB60077E104 /* DocumentStore */, + ); + path = Storage; + sourceTree = ""; + }; 21F69F1A2C6A0FE2008B6A06 /* Browser */ = { isa = PBXGroup; children = ( @@ -447,8 +472,10 @@ 210CC88F2C81A98500610F58 /* Guid64.h in Headers */, 21B67D002C7CE15100FD64FC /* Exception.h in Headers */, 21D0BD2C2C86FC350077E104 /* Record.h in Headers */, + 21D0BDB42C8F8AB60077E104 /* DocumentStoreCore.h in Headers */, 210CC8802C80CD2A00610F58 /* BridgeChild.h in Headers */, 21D0BD4D2C8901A00077E104 /* ServerInfo.h in Headers */, + 21D0BDB52C8F8AB60077E104 /* DocumentStoreEngine.h in Headers */, 2193518C2C62655700E5A69C /* MenuEvent.h in Headers */, 21D0BD312C86FE090077E104 /* Index.h in Headers */, ); @@ -581,6 +608,7 @@ 21F69F812C6FF3B0008B6A06 /* BridgeArgumentWrap.cpp in Sources */, 2193517B2C624FC100E5A69C /* MenuSubscriber.cpp in Sources */, 21F69F612C6D0286008B6A06 /* GetBindingsMethodNames.cpp in Sources */, + 21D0BDB32C8F8AB60077E104 /* DocumentStoreCore.cpp in Sources */, 21F93AEC2B2F406E009A2C5B /* Addon.cpp in Sources */, 21D0BD4E2C8901A00077E104 /* ServerInfo.cpp in Sources */, 21B67D0E2C7E0E8D00FD64FC /* ErrorReport.cpp in Sources */,