diff --git a/.gitmodules b/.gitmodules index 1633779ed..a8897ce21 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "submodules/externals/minizip"] - path = submodules/externals/minizip - url = ../../public/external/minizip.git [submodule "linphone-sdk"] - path = linphone-sdk - url = ../../public/linphone-sdk.git +path = linphone-sdk + url = https://gitlab.linphone.org/BC/public/linphone-sdk.git +[submodule "plugins/contacts/contacts-api"] + path = plugins/contacts/contacts-api + url = https://gitlab.linphone.org/BC/public/linphone-desktop-plugins/contacts/contacts-api.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d2c0be0b4..dac846312 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ set(LINPHONE_OUTPUT_DIR "${CMAKE_BINARY_DIR}/linphone-sdk/desktop") set(APPLICATION_OUTPUT_DIR "${CMAKE_BINARY_DIR}/OUTPUT") -set(CMAKE_PREFIX_PATH "${LINPHONE_OUTPUT_DIR};${APPLICATION_OUTPUT_DIR}${PREFIX_PATH}") +set(CMAKE_PREFIX_PATH "${LINPHONE_OUTPUT_DIR};${APPLICATION_OUTPUT_DIR};${APPLICATION_OUTPUT_DIR}/include${PREFIX_PATH}") string(REPLACE ";" "|" PREFIX_PATH "${CMAKE_PREFIX_PATH}") #set(PREFIX_PATH "${LINPHONE_OUTPUT_DIR}|${APPLICATION_OUTPUT_DIR}${PREFIX_PATH}") @@ -95,6 +95,10 @@ option(ENABLE_FFMPEG "Build mediastreamer2 with ffmpeg video support." YES) option(ENABLE_BUILD_VERBOSE "Enable the build generation to be more verbose" NO) option(ENABLE_OPENH264 "Enable the use of OpenH264 codec" YES) option(ENABLE_NON_FREE_CODECS "Enable the use of non free codecs" YES) +option(ENABLE_BUILD_APP_PLUGINS "Enable the build of plugins" YES) +option(ENABLE_BUILD_EXAMPLES "Enable the build of examples" NO) + + if(WIN32 OR APPLE) else() @@ -114,6 +118,7 @@ list(APPEND APP_OPTIONS "-DENABLE_FFMPEG=${ENABLE_FFMPEG}") list(APPEND APP_OPTIONS "-DENABLE_BUILD_VERBOSE=${ENABLE_BUILD_VERBOSE}") list(APPEND APP_OPTIONS "-DENABLE_OPENH264=${ENABLE_OPENH264}") list(APPEND APP_OPTIONS "-DENABLE_NON_FREE_CODECS=${ENABLE_NON_FREE_CODECS}") +list(APPEND APP_OPTIONS "-DENABLE_BUILD_EXAMPLES=${ENABLE_BUILD_EXAMPLES}") if(ENABLE_V4L) list(APPEND APP_OPTIONS "-DENABLE_V4L=${ENABLE_V4L}") @@ -176,7 +181,6 @@ find_package(belcard CONFIG QUIET) find_package(Mediastreamer2 CONFIG QUIET) find_package(ortp CONFIG QUIET) - if(NOT (LinphoneCxx_FOUND) OR NOT (Linphone_FOUND) OR NOT (bctoolbox_FOUND) OR NOT (belcard_FOUND) OR NOT (Mediastreamer2_FOUND) OR NOT (ortp_FOUND) OR FORCE_APP_EXTERNAL_PROJECTS) message("Projects are set as External projects. You can start building them by using for example : cmake --build . --target install") ExternalProject_Add(linphone-qt PREFIX "${CMAKE_BINARY_DIR}/linphone-app" @@ -191,16 +195,38 @@ if(NOT (LinphoneCxx_FOUND) OR NOT (Linphone_FOUND) OR NOT (bctoolbox_FOUND) OR N # ${APP_OPTIONS} BUILD_ALWAYS ON ) + if( ENABLE_BUILD_APP_PLUGINS) + ExternalProject_Add(app-plugins PREFIX "${CMAKE_BINARY_DIR}/plugins-app" + SOURCE_DIR "${CMAKE_SOURCE_DIR}/plugins" + INSTALL_DIR "${APPLICATION_OUTPUT_DIR}" + BINARY_DIR "${CMAKE_BINARY_DIR}/plugins-app" + DEPENDS ${APP_DEPENDS} linphone-qt + BUILD_COMMAND ${CMAKE_COMMAND} --build --config $ ${PROJECT_BUILD_COMMAND} + INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Install step is already done at build time." + LIST_SEPARATOR | # Use the alternate list separator + CMAKE_ARGS ${APP_OPTIONS} ${USER_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${PREFIX_PATH} + ) + endif() install(CODE "message(STATUS Running install)") set(AUTO_REGENERATION auto_regeneration) - add_custom_target(${AUTO_REGENERATION} ALL - COMMAND ${CMAKE_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR} - DEPENDS linphone-qt) + if( ENABLE_BUILD_APP_PLUGINS) + add_custom_target(${AUTO_REGENERATION} ALL + COMMAND ${CMAKE_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS app-plugins) + else() + add_custom_target(${AUTO_REGENERATION} ALL + COMMAND ${CMAKE_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS linphone-qt) + endif() else() message("Adding Linphone Desktop in an IDE-friendly state") set(CMAKE_INSTALL_PREFIX "${APPLICATION_OUTPUT_DIR}") add_subdirectory(${CMAKE_SOURCE_DIR}/linphone-app) add_dependencies(app-library ${APP_DEPENDS}) + if( ENABLE_BUILD_APP_PLUGINS) + add_custom_command(TARGET sdk PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/linphone-app/include/" "${CMAKE_INSTALL_PREFIX}/include/") + add_subdirectory(${CMAKE_SOURCE_DIR}/plugins "plugins-app") + endif() endif() ExternalProject_Add(linphone-qt-only PREFIX "${CMAKE_BINARY_DIR}/linphone-app" SOURCE_DIR "${CMAKE_SOURCE_DIR}/linphone-app" diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt index 71a41fbf4..73f8e70b7 100644 --- a/linphone-app/CMakeLists.txt +++ b/linphone-app/CMakeLists.txt @@ -132,6 +132,10 @@ set(SOURCES src/components/conference/ConferenceModel.cpp src/components/contact/ContactModel.cpp src/components/contact/VcardModel.cpp + src/components/contacts/ContactsImporterModel.cpp + src/components/contacts/ContactsImporterPluginsManager.cpp + src/components/contacts/ContactsImporterListModel.cpp + src/components/contacts/ContactsImporterListProxyModel.cpp src/components/contacts/ContactsListModel.cpp src/components/contacts/ContactsListProxyModel.cpp src/components/core/CoreHandlers.cpp @@ -161,6 +165,11 @@ set(SOURCES src/utils/MediastreamerUtils.cpp src/utils/QExifImageHeader.cpp src/utils/Utils.cpp + src/utils/plugins/PluginDataAPI.cpp + src/utils/plugins/PluginNetworkHelper.cpp + src/utils/plugins/LinphonePlugin.cpp + src/utils/plugins/PluginsManager.cpp + ) set(HEADERS @@ -194,6 +203,10 @@ set(HEADERS src/components/conference/ConferenceModel.hpp src/components/contact/ContactModel.hpp src/components/contact/VcardModel.hpp + src/components/contacts/ContactsImporterModel.hpp + src/components/contacts/ContactsImporterPluginsManager.hpp + src/components/contacts/ContactsImporterListModel.hpp + src/components/contacts/ContactsImporterListProxyModel.hpp src/components/contacts/ContactsListModel.hpp src/components/contacts/ContactsListProxyModel.hpp src/components/core/CoreHandlers.hpp @@ -224,8 +237,12 @@ set(HEADERS src/utils/MediastreamerUtils.hpp src/utils/QExifImageHeader.hpp src/utils/Utils.hpp + src/utils/plugins/PluginsManager.hpp + include/LinphoneApp/PluginDataAPI.hpp + include/LinphoneApp/PluginNetworkHelper.hpp + include/LinphoneApp/LinphonePlugin.hpp ) - +list(APPEND SOURCES include/LinphoneApp/PluginExample.json) set(MAIN_FILE src/app/main.cpp) if (APPLE) @@ -336,8 +353,8 @@ list(APPEND _QML_IMPORT_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/ui/scripts") list(APPEND _QML_IMPORT_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/ui/views") -set(QML_IMPORT_PATH ${_QML_IMPORT_PATHS} CACHE STRING "Path used to locate CMake modules by Qt Creator" FORCE) -set(QML2_IMPORT_PATH ${_QML_IMPORT_PATHS} CACHE STRING "Path used to locate CMake modules by Qt Creator" FORCE) +set(QML_IMPORT_PATH "${_QML_IMPORT_PATHS}" CACHE STRING "Path used to locate CMake modules by Qt Creator" FORCE) +set(QML2_IMPORT_PATH "${_QML_IMPORT_PATHS}" CACHE STRING "Path used to locate CMake modules by Qt Creator" FORCE) set(QML_SOURCES_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/ui/") set(QML_MODULES_PATHS ${QML_SOURCES_PATHS}) if(ENABLE_BUILD_VERBOSE)#useful to copy these Paths to QML previewers @@ -421,9 +438,12 @@ endif() #add_dependencies(project_b_exe project_a) #target_link_libraries(project_b_exe ${install_dir}/lib/alib.lib) - +if(ENABLE_APP_EXPORT_PLUGIN) + add_definitions(-DENABLE_APP_EXPORT_PLUGIN) +endif() set(INCLUDED_DIRECTORIES "${LINPHONECXX_INCLUDE_DIRS}" ) +list(APPEND INCLUDED_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/include") set(LIBRARIES_LIST ${BCTOOLBOX_CORE_LIBRARIES} ${BELCARD_LIBRARIES} ${LINPHONE_LIBRARIES} ${LINPHONECXX_LIBRARIES} ${MEDIASTREAMER2_LIBRARIES} ${ORTP_LIBRARIES} ${OPUS_LIBRARIES}) if(WIN32) set(LIBRARIES) @@ -439,7 +459,6 @@ else() set(LIBRARIES ${LIBRARIES_LIST}) endif() - if(ENABLE_BUILD_VERBOSE) message("LIBRARIES : ${LIBRARIES}") endif() @@ -465,6 +484,9 @@ foreach (package ${QT5_PACKAGES_OPTIONAL}) endif () endforeach () +#find_library(CONTACTS_PLUGIN_LIBRARY linphoneAppContacts HINTS "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +#list(APPEND LIBRARIES ${CONTACTS_PLUGIN_LIBRARY}) + if (APPLE) list(APPEND LIBRARIES "-framework Cocoa -framework IOKit -framework AVFoundation") # -framework linphone") #This doesn't work yet @@ -478,6 +500,7 @@ target_link_libraries(${TARGET_NAME} ${LIBRARIES}) if(WIN32) target_link_libraries(${TARGET_NAME} wsock32 ws2_32) endif() +target_compile_definitions(${APP_LIBRARY} PUBLIC ENABLE_APP_EXPORT_PLUGIN) add_dependencies(${APP_LIBRARY} update_translations ${TARGET_NAME}-git-version) add_dependencies(${TARGET_NAME} ${APP_LIBRARY}) @@ -488,6 +511,10 @@ add_dependencies(${TARGET_NAME} ${APP_LIBRARY}) # ------------------------------------------------------------------------------ set(TOOLS_DIR "${CMAKE_BINARY_DIR}/programs") set(LINPHONE_BUILDER_SIGNING_IDENTITY ${LINPHONE_BUILDER_SIGNING_IDENTITY}) +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/include/" "${CMAKE_INSTALL_PREFIX}/include/") +#add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/include/LinphoneApp/*" "${CMAKE_INSTALL_PREFIX}/include/LinphoneApp/") +#configure_file("${CMAKE_CURRENT_SOURCE_DIR}/include/*" "${CMAKE_INSTALL_PREFIX}/include/LinphoneApp/" COPYONLY) +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" DESTINATION ${CMAKE_INSTALL_PREFIX}) add_subdirectory(build) add_subdirectory(cmake_builder/linphone_package) @@ -496,7 +523,9 @@ add_subdirectory(cmake_builder/linphone_package) # ------------------------------------------------------------------------------ # To start better integration into IDE. # ------------------------------------------------------------------------------ - +source_group( + "Json" REGULAR_EXPRESSION ".+\.json$" +) source_group( "Qml" REGULAR_EXPRESSION ".+\.qml$" ) diff --git a/linphone-app/assets/languages/da.ts b/linphone-app/assets/languages/da.ts index 85cd71e93..3bf45ec0e 100644 --- a/linphone-app/assets/languages/da.ts +++ b/linphone-app/assets/languages/da.ts @@ -1183,6 +1183,10 @@ Klik her: <a href="%1">%1</a> logsMailerSuccess Logfiler blev uploadet til %1 + + contactsTitle + Kontakter + SettingsAudio diff --git a/linphone-app/assets/languages/de.ts b/linphone-app/assets/languages/de.ts index 8982bf638..a825c9fb0 100644 --- a/linphone-app/assets/languages/de.ts +++ b/linphone-app/assets/languages/de.ts @@ -1183,6 +1183,10 @@ Klicken Sie hier: <a href="%1">%1</a> logsMailerSuccess Protokolle wurden auf %1 hochgeladen + + contactsTitle + Kontakte + SettingsAudio diff --git a/linphone-app/assets/languages/en.ts b/linphone-app/assets/languages/en.ts index 97ff91265..c193225a7 100644 --- a/linphone-app/assets/languages/en.ts +++ b/linphone-app/assets/languages/en.ts @@ -1199,6 +1199,10 @@ Click here: <a href="%1">%1</a> logsMailerSuccess Logs were uploaded to %1 + + contactsTitle + Address Book Connector + SettingsAudio diff --git a/linphone-app/assets/languages/es.ts b/linphone-app/assets/languages/es.ts index 135fd448e..9637897a3 100644 --- a/linphone-app/assets/languages/es.ts +++ b/linphone-app/assets/languages/es.ts @@ -1183,6 +1183,10 @@ Haga clic aquí: <a href="%1">%1 </a> logsMailerSuccess Los registros se cargaron a %1 + + contactsTitle + Contactos + SettingsAudio diff --git a/linphone-app/assets/languages/fr_FR.ts b/linphone-app/assets/languages/fr_FR.ts index 20189cca2..104ed1f6a 100644 --- a/linphone-app/assets/languages/fr_FR.ts +++ b/linphone-app/assets/languages/fr_FR.ts @@ -1183,6 +1183,10 @@ Cliquez ici : <a href="%1">%1</a> logsMailerSuccess Les logs ont été uploadé sur %1 + + contactsTitle + Contacts + SettingsAudio diff --git a/linphone-app/assets/languages/hu.ts b/linphone-app/assets/languages/hu.ts index 5ad0c775d..0350f4a95 100644 --- a/linphone-app/assets/languages/hu.ts +++ b/linphone-app/assets/languages/hu.ts @@ -1182,6 +1182,10 @@ Kattintson ide: <a href="%1">%1</a> logsMailerSuccess A naplókat feltöltötték a %1 + + contactsTitle + Névjegyek + SettingsAudio diff --git a/linphone-app/assets/languages/it.ts b/linphone-app/assets/languages/it.ts index 1a9f01e21..ea5e72501 100644 --- a/linphone-app/assets/languages/it.ts +++ b/linphone-app/assets/languages/it.ts @@ -1183,6 +1183,10 @@ Clicca: <a href="%1">%1</a> logsMailerSuccess I log sono stati caricati a %1 + + contactsTitle + Contatti + SettingsAudio diff --git a/linphone-app/assets/languages/ja.ts b/linphone-app/assets/languages/ja.ts index 6338d2fa7..c5a583e9a 100644 --- a/linphone-app/assets/languages/ja.ts +++ b/linphone-app/assets/languages/ja.ts @@ -1183,6 +1183,10 @@ logsMailerSuccess ログが %1 にアップロードされました + + contactsTitle + 連絡先 + SettingsAudio diff --git a/linphone-app/assets/languages/lt.ts b/linphone-app/assets/languages/lt.ts index ae608b5d8..34d90b7e8 100644 --- a/linphone-app/assets/languages/lt.ts +++ b/linphone-app/assets/languages/lt.ts @@ -1183,6 +1183,10 @@ Spustelėkite čia: <a href="%1">%1</a> logsMailerSuccess Žurnalai buvo įkelti į %1 + + contactsTitle + Kontaktai + SettingsAudio diff --git a/linphone-app/assets/languages/pt_BR.ts b/linphone-app/assets/languages/pt_BR.ts index 5b6dc6ef5..2a365c02f 100644 --- a/linphone-app/assets/languages/pt_BR.ts +++ b/linphone-app/assets/languages/pt_BR.ts @@ -1182,6 +1182,10 @@ Clique aqui: <a href="%1">%1 </a> logsMailerSuccess Os registos foram enviados para %1 + + contactsTitle + Contatos + SettingsAudio diff --git a/linphone-app/assets/languages/ru.ts b/linphone-app/assets/languages/ru.ts index 637344a99..ac4dee80a 100644 --- a/linphone-app/assets/languages/ru.ts +++ b/linphone-app/assets/languages/ru.ts @@ -1183,6 +1183,10 @@ logsMailerSuccess Журналы были загружены в %1 + + contactsTitle + Контакты + SettingsAudio diff --git a/linphone-app/assets/languages/sv.ts b/linphone-app/assets/languages/sv.ts index 746517a2d..5514d2e34 100644 --- a/linphone-app/assets/languages/sv.ts +++ b/linphone-app/assets/languages/sv.ts @@ -1183,6 +1183,10 @@ Klicka här: <a href="%1">%1</a> logsMailerSuccess Loggar laddades upp till %1 + + contactsTitle + Kontakter + SettingsAudio diff --git a/linphone-app/assets/languages/tr.ts b/linphone-app/assets/languages/tr.ts index e09a23dfb..c2c750b81 100644 --- a/linphone-app/assets/languages/tr.ts +++ b/linphone-app/assets/languages/tr.ts @@ -1183,6 +1183,10 @@ Buraya tıklayın: <a href="%1">%1</a> logsMailerSuccess Günlükler %1 dosyasına yüklendi + + contactsTitle + Kişiler + SettingsAudio diff --git a/linphone-app/assets/languages/uk.ts b/linphone-app/assets/languages/uk.ts index 4408c75e1..1dd41d53a 100644 --- a/linphone-app/assets/languages/uk.ts +++ b/linphone-app/assets/languages/uk.ts @@ -1183,6 +1183,10 @@ logsMailerSuccess Журнали було вивантажено до %1 + + contactsTitle + Контакти + SettingsAudio diff --git a/linphone-app/assets/languages/zh_CN.ts b/linphone-app/assets/languages/zh_CN.ts index 4c8d1b304..25651ca21 100644 --- a/linphone-app/assets/languages/zh_CN.ts +++ b/linphone-app/assets/languages/zh_CN.ts @@ -1183,6 +1183,10 @@ logsMailerSuccess 日志已上传到 %1 + + contactsTitle + 联系人 + SettingsAudio diff --git a/linphone-app/cmake_builder/linphone_package/CMakeLists.txt b/linphone-app/cmake_builder/linphone_package/CMakeLists.txt index baea03cf0..ce2166e09 100644 --- a/linphone-app/cmake_builder/linphone_package/CMakeLists.txt +++ b/linphone-app/cmake_builder/linphone_package/CMakeLists.txt @@ -129,6 +129,7 @@ if (WIN32) install(FILES ${GRAMMAR_FILES} DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/belr/grammars/" ) install(DIRECTORY "${LINPHONE_OUTPUT_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/images" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}" USE_SOURCE_PERMISSIONS OPTIONAL) install(DIRECTORY "${LINPHONE_OUTPUT_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/sounds" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}" USE_SOURCE_PERMISSIONS) + install(DIRECTORY "${CMAKE_INSTALL_PREFIX}/plugins/" DESTINATION "plugins" USE_SOURCE_PERMISSIONS OPTIONAL) install(FILES "${LINPHONE_OUTPUT_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/Linphone/rootca.pem" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${EXECUTABLE_NAME}/") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../../assets/linphonerc-factory" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${EXECUTABLE_NAME}") set(APP_QT_CONF_DPI "0") diff --git a/linphone-app/include/LinphoneApp/LinphonePlugin.hpp b/linphone-app/include/LinphoneApp/LinphonePlugin.hpp new file mode 100644 index 000000000..e6ec15cfd --- /dev/null +++ b/linphone-app/include/LinphoneApp/LinphonePlugin.hpp @@ -0,0 +1,44 @@ +#ifndef LINPHONE_APP_PLUGIN_H +#define LINPHONE_APP_PLUGIN_H +#include +#include +#include +// Overload this class to make a plugin for the address book importer + +#if defined(_MSC_VER) + #ifdef ENABLE_APP_EXPORT_PLUGIN + #define LINPHONEAPP_DLL_API __declspec(dllexport) + #else + #define LINPHONEAPP_DLL_API __declspec(dllimport) + #endif +#else + #ifdef ENABLE_APP_EXPORT_PLUGIN + #define LINPHONEAPP_DLL_API __attribute__((visibility("default"))) + #else + #define LINPHONEAPP_DLL_API + #endif +#endif + +class QPluginLoader; +class PluginDataAPI; + +class LinphonePlugin +{ +// These macro are an example to use in the custom plugin +//Q_OBJECT +//Q_PLUGIN_METADATA(IID LinphonePlugin_iid FILE "PluginExample.json")// You have to set the Capabilities for your plugin +//Q_INTERFACES(LinphonePlugin) +//----------------------------------------------------------- +public: + virtual ~LinphonePlugin() {} + +// Specific to DataAPI. See their section + virtual QString getGUIDescriptionToJson() const = 0;// Describe the GUI to be used for the plugin. Json are in Utf8 + + virtual PluginDataAPI * createInstance(void* core, QPluginLoader * pluginLoader) = 0;// Create an instance of the plugin in LinphoneAppPluginType. +}; + +#define LinphonePlugin_iid "linphoneApp.LinphonePlugin/1.0" +Q_DECLARE_INTERFACE(LinphonePlugin, LinphonePlugin_iid) + +#endif // LINPHONE_APP_PLUGIN_H diff --git a/linphone-app/include/LinphoneApp/PluginDataAPI.hpp b/linphone-app/include/LinphoneApp/PluginDataAPI.hpp new file mode 100644 index 000000000..010215e48 --- /dev/null +++ b/linphone-app/include/LinphoneApp/PluginDataAPI.hpp @@ -0,0 +1,55 @@ +#ifndef LINPHONE_APP_PLUGIN_DATA_H +#define LINPHONE_APP_PLUGIN_DATA_H + +#include + +#ifdef ENABLE_APP_EXPORT_PLUGIN + #include "include/LinphoneApp/LinphonePlugin.hpp" +#else + #include +#endif + +class QPluginLoader; +class LinphonePlugin; + +// This class regroup Data interface for importing contacts +class LINPHONEAPP_DLL_API PluginDataAPI : public QObject { +Q_OBJECT +public: + typedef enum{ALL=-1, NOTHING=0, CONTACTS=1, LAST} PluginCapability;// LAST must not be used. It is for internal process only. + PluginDataAPI(LinphonePlugin * plugin, void * linphoneCore, QPluginLoader * pluginLoader); + virtual ~PluginDataAPI(); + + virtual bool isValid(const bool &pRequestData=true, QString * pError= nullptr) = 0; // Test if the passed data is valid. Used for saving. + virtual void setInputFields(const PluginCapability& capability = ALL, const QVariantMap &inputFields = QVariantMap());// Set all inputs for the selected capability + virtual QMap getInputFields(const PluginCapability& capability);// Get all inputs + virtual QMap getInputFieldsToSave(const PluginCapability& capability = ALL);// Get all inputs to save in config file. + +// Configuration management + void setSectionConfiguration(const QString& section); + virtual void loadConfiguration(const PluginCapability& capability = ALL); + virtual void saveConfiguration(const PluginCapability& capability = ALL); + virtual void cleanAllConfigurations();// Remove all saved configuration + + QPluginLoader * getPluginLoader();// Used to retrieve the loader that created this instance, in order to unload it + + virtual void run(const PluginCapability& actionType)=0; + +signals: + void dataReceived(const PluginCapability& actionType, QVector > data); +//------------------------------------ + + void inputFieldsChanged(const PluginCapability&, const QVariantMap &inputFields); // Input fields have been changed + void message(const QtMsgType& type, const QString &message); // Send a message to GUI + + +protected: + QMap mInputFields; + void * mLinphoneCore; + LinphonePlugin * mPlugin; + QPluginLoader * mPluginLoader; +private: + QString mSectionConfigurationName; +}; + +#endif // LINPHONE_APP_PLUGIN_DATA_H diff --git a/linphone-app/include/LinphoneApp/PluginExample.json b/linphone-app/include/LinphoneApp/PluginExample.json new file mode 100644 index 000000000..c198042d5 --- /dev/null +++ b/linphone-app/include/LinphoneApp/PluginExample.json @@ -0,0 +1,6 @@ +{ + "Name" : "ExamplePlugin", + "Version" : "1.0.0", + "Capabilities" : "Contacts", + "Description" : "This is an example for describing your plugin. Replace all fields above to be usable by the Application." +} diff --git a/linphone-app/include/LinphoneApp/PluginNetworkHelper.hpp b/linphone-app/include/LinphoneApp/PluginNetworkHelper.hpp new file mode 100644 index 000000000..6bfb62574 --- /dev/null +++ b/linphone-app/include/LinphoneApp/PluginNetworkHelper.hpp @@ -0,0 +1,40 @@ +#ifndef LINPHONE_APP_NETWORK_HELPER_H +#define LINPHONE_APP_NETWORK_HELPER_H + +#include +#include +// This class is used to define network operation to retrieve Addresses from Network + + +#ifdef ENABLE_APP_EXPORT_PLUGIN + #include "include/LinphoneApp/LinphonePlugin.hpp" +#else + #include +#endif + +class LINPHONEAPP_DLL_API PluginNetworkHelper : public QObject +{ +Q_OBJECT +public: + PluginNetworkHelper(); + virtual ~PluginNetworkHelper(); + virtual QString prepareRequest()const=0; // Called when requesting an Url. + + void request(); + + QPointer mNetworkReply; + QNetworkAccessManager mManager; +signals: + void requestFinished(const QByteArray &data); // The request is over and have data + void message(const QtMsgType &type, const QString &message); + +private: + void handleReadyData(); + void handleFinished (); + void handleError (QNetworkReply::NetworkError code); + void handleSslErrors (const QList &sslErrors); + + QByteArray mBuffer; +}; + +#endif // LINPHONE_APP_NETWORK_HELPER_H diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp index a933ac9de..24b1e9e07 100644 --- a/linphone-app/src/app/App.cpp +++ b/linphone-app/src/app/App.cpp @@ -583,6 +583,7 @@ void App::registerTypes () { registerType("ConferenceHelperModel"); registerType("ConferenceModel"); registerType("ContactsListProxyModel"); + registerType("ContactsImporterListProxyModel"); registerType("FileDownloader"); registerType("FileExtractor"); registerType("HistoryProxyModel"); @@ -601,6 +602,7 @@ void App::registerTypes () { registerUncreatableType("ChatModel"); registerUncreatableType("ConferenceAddModel"); registerUncreatableType("ContactModel"); + registerUncreatableType("ContactsImporterModel"); registerUncreatableType("HistoryModel"); registerUncreatableType("SipAddressObserver"); registerUncreatableType("VcardModel"); @@ -616,6 +618,7 @@ void App::registerSharedTypes () { registerSharedSingletonType("SipAddressesModel"); registerSharedSingletonType("CallsListModel"); registerSharedSingletonType("ContactsListModel"); + registerSharedSingletonType("ContactsImporterListModel"); } void App::registerToolTypes () { @@ -625,6 +628,7 @@ void App::registerToolTypes () { registerToolType("DesktopTools"); registerToolType("TextToSpeech"); registerToolType("Units"); + registerToolType("ContactsImporterPluginsManager"); } void App::registerSharedToolTypes () { diff --git a/linphone-app/src/app/paths/Paths.cpp b/linphone-app/src/app/paths/Paths.cpp index 485bd828b..bcefcf272 100644 --- a/linphone-app/src/app/paths/Paths.cpp +++ b/linphone-app/src/app/paths/Paths.cpp @@ -41,7 +41,12 @@ namespace { constexpr char PathCodecs[] = "/codecs/"; constexpr char PathTools[] = "/tools/"; constexpr char PathLogs[] = "/logs/"; - //constexpr char PathPlugins[] = "/plugins/"; // Unused +#ifdef APPLE + constexpr char PathPlugins[] = "/Plugins/"; +#else + constexpr char PathPlugins[] = "/plugins/"; +#endif + constexpr char PathPluginsApp[] = "app/"; constexpr char PathThumbnails[] = "/thumbnails/"; constexpr char PathUserCertificates[] = "/usr-crt/"; @@ -159,6 +164,10 @@ static inline QString getAppPackageMsPluginsDirPath () { return dir.absolutePath(); } +static inline QString getAppPackagePluginsDirPath () { + return getAppPackageDir().absolutePath() + PathPlugins; +} + static inline QString getAppAssistantConfigDirPath () { return getAppPackageDataDirPath() + PathAssistantConfig; } @@ -187,6 +196,9 @@ static inline QString getAppMessageHistoryFilePath () { return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + PathMessageHistoryList; } +static inline QString getAppPluginsDirPath () { + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)+ PathPlugins; +} // ----------------------------------------------------------------------------- bool Paths::filePathExists (const string &path) { @@ -265,6 +277,21 @@ string Paths::getPackageMsPluginsDirPath () { return getReadableDirPath(getAppPackageMsPluginsDirPath()); } +string Paths::getPackagePluginsAppDirPath () { + return getReadableDirPath(getAppPackagePluginsDirPath()+PathPluginsApp); +} + +string Paths::getPluginsAppDirPath () { + return getWritableDirPath(getAppPluginsDirPath()+PathPluginsApp); +} + +QStringList Paths::getPluginsAppFolders() { + QStringList pluginPaths; + pluginPaths << Utils::coreStringToAppString(Paths::getPluginsAppDirPath()); + pluginPaths << Utils::coreStringToAppString(Paths::getPackagePluginsAppDirPath()); + return pluginPaths; +} + string Paths::getRootCaFilePath () { return getReadableFilePath(getAppRootCaFilePath()); } diff --git a/linphone-app/src/app/paths/Paths.hpp b/linphone-app/src/app/paths/Paths.hpp index 54c8e34a5..1b29624ae 100644 --- a/linphone-app/src/app/paths/Paths.hpp +++ b/linphone-app/src/app/paths/Paths.hpp @@ -28,6 +28,7 @@ namespace Paths { bool filePathExists (const std::string &path); + std::string getAssistantConfigDirPath (); std::string getAvatarsDirPath (); std::string getCallHistoryFilePath (); @@ -42,6 +43,9 @@ namespace Paths { std::string getMessageHistoryFilePath (); std::string getPackageDataDirPath (); std::string getPackageMsPluginsDirPath (); + std::string getPackagePluginsAppDirPath (); + std::string getPluginsAppDirPath (); + QStringList getPluginsAppFolders(); std::string getRootCaFilePath (); std::string getThumbnailsDirPath (); std::string getToolsDirPath (); diff --git a/linphone-app/src/components/Components.hpp b/linphone-app/src/components/Components.hpp index 05feab2c9..9848d6465 100644 --- a/linphone-app/src/components/Components.hpp +++ b/linphone-app/src/components/Components.hpp @@ -37,6 +37,10 @@ #include "contact/VcardModel.hpp" #include "contacts/ContactsListModel.hpp" #include "contacts/ContactsListProxyModel.hpp" +#include "contacts/ContactsImporterModel.hpp" +#include "contacts/ContactsImporterPluginsManager.hpp" +#include "contacts/ContactsImporterListModel.hpp" +#include "contacts/ContactsImporterListProxyModel.hpp" #include "core/CoreHandlers.hpp" #include "core/CoreManager.hpp" #include "file/FileDownloader.hpp" diff --git a/linphone-app/src/components/contacts/ContactsImporterListModel.cpp b/linphone-app/src/components/contacts/ContactsImporterListModel.cpp new file mode 100644 index 000000000..aff335950 --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterListModel.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "app/App.hpp" +#include "ContactsImporterModel.hpp" +#include "ContactsImporterListModel.hpp" +#include "ContactsImporterPluginsManager.hpp" +#include "components/core/CoreManager.hpp" +#include "utils/Utils.hpp" + +// ============================================================================= + +using namespace std; + +ContactsImporterListModel::ContactsImporterListModel (QObject *parent) : QAbstractListModel(parent) { + // Init contacts with linphone friends list. + mMaxContactsImporterId = -1; + QQmlEngine *engine = App::getInstance()->getEngine(); + auto config = CoreManager::getInstance()->getCore()->getConfig(); + PluginsManager::getPlugins();// Initialize list +// Read configuration file + std::list sections = config->getSectionsNamesList(); + for(auto section : sections){ + QString qtSection = Utils::coreStringToAppString(section); + QStringList parse = qtSection.split("_");// PluginsManager::gPluginsConfigSection_id_capability + if( parse.size() > 2){ + QVariantMap importData; + if( parse[2].toInt() == PluginDataAPI::CONTACTS){// We only care about Contacts + int id = parse[1].toInt(); + mMaxContactsImporterId = qMax(id, mMaxContactsImporterId); + std::list keys = config->getKeysNamesList(section); + auto keyName = std::find(keys.begin(), keys.end(), "pluginID"); + if( keyName != keys.end()){ + QString pluginID = Utils::coreStringToAppString(config->getString(section, *keyName, "")); + PluginDataAPI* data = static_cast(PluginsManager::createInstance(pluginID)); + if(data) { + ContactsImporterModel * model = new ContactsImporterModel(data, this); + // See: http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership + // The returned value must have a explicit parent or a QQmlEngine::CppOwnership. + engine->setObjectOwnership(model, QQmlEngine::CppOwnership); + model->setIdentity(id); + model->loadConfiguration();// Read the configuration contacts inside the plugin + addContactsImporter(model); + } + } + } + } + + } +} + +// GUI methods + +int ContactsImporterListModel::rowCount (const QModelIndex &) const { + return mList.count(); +} + +QHash ContactsImporterListModel::roleNames () const { + QHash roles; + roles[Qt::DisplayRole] = "$contactsImporter"; + return roles; +} + +QVariant ContactsImporterListModel::data (const QModelIndex &index, int role) const { + int row = index.row(); + + if (!index.isValid() || row < 0 || row >= mList.count()) + return QVariant(); + + if (role == Qt::DisplayRole) + return QVariant::fromValue(mList[row]); + + return QVariant(); +} + +bool ContactsImporterListModel::removeRow (int row, const QModelIndex &parent) { + return removeRows(row, 1, parent); +} + +bool ContactsImporterListModel::removeRows (int row, int count, const QModelIndex &parent) { + int limit = row + count - 1; + + if (row < 0 || count < 0 || limit >= mList.count()) + return false; + + beginRemoveRows(parent, row, limit); + + for (int i = 0; i < count; ++i) { + ContactsImporterModel *contactsImporter = dynamic_cast(mList.takeAt(row)); + emit contactsImporterRemoved(contactsImporter); + contactsImporter->deleteLater(); + } + + endRemoveRows(); + + return true; +} + +// ----------------------------------------------------------------------------- + +ContactsImporterModel *ContactsImporterListModel::findContactsImporterModelFromId (const int &id) const { + auto it = find_if(mList.begin(), mList.end(), [id](PluginsModel *contactsImporterModel) { + return contactsImporterModel->getIdentity() == id; + }); + return it != mList.end() ? dynamic_cast(*it) : nullptr; +} + +QList ContactsImporterListModel::getList(){ + return mList; +} + +// ----------------------------------------------------------------------------- + +ContactsImporterModel *ContactsImporterListModel::createContactsImporter(QVariantMap data){ + ContactsImporterModel *contactsImporter = nullptr; + if( data.contains("pluginID")){ + PluginDataAPI * dataInstance = static_cast(PluginsManager::createInstance(data["pluginID"].toString())); + if(dataInstance) { +// get default values + contactsImporter = new ContactsImporterModel(dataInstance, this); + App::getInstance()->getEngine()->setObjectOwnership(contactsImporter, QQmlEngine::CppOwnership); + QVariantMap newData = ContactsImporterPluginsManager::getDefaultValues(data["pluginID"].toString());// Start with defaults from plugin + QVariantMap InstanceFields = contactsImporter->getFields(); + for(auto field = InstanceFields.begin() ; field != InstanceFields.end() ; ++field)// Insert or Update with the defaults of an instance + newData[field.key()] = field.value(); + for(auto field = data.begin() ; field != data.end() ; ++field)// Insert or Update with Application data + newData[field.key()] = field.value(); + contactsImporter->setIdentity(++mMaxContactsImporterId); + contactsImporter->setFields(newData); + + int row = mList.count(); + + beginInsertRows(QModelIndex(), row, row); + addContactsImporter(contactsImporter); + endInsertRows(); + + emit contactsImporterAdded(contactsImporter); + } + } + + return contactsImporter; + +} +ContactsImporterModel *ContactsImporterListModel::addContactsImporter (QVariantMap data, int pId) { + ContactsImporterModel *contactsImporter = findContactsImporterModelFromId(pId); + if (contactsImporter) { + contactsImporter->setFields(data); + return contactsImporter; + }else + return createContactsImporter(data); +} + +void ContactsImporterListModel::removeContactsImporter (ContactsImporterModel *contactsImporter) { + int index = mList.indexOf(contactsImporter); + if (index >=0){ + if( contactsImporter->getIdentity() >=0 ){// Remove from configuration + int id = contactsImporter->getIdentity(); + string section = Utils::appStringToCoreString(PluginsManager::gPluginsConfigSection+"_"+QString::number(id)+"_"+QString::number(PluginDataAPI::CONTACTS)); + CoreManager::getInstance()->getCore()->getConfig()->cleanSection(section); + if( id == mMaxContactsImporterId)// Decrease mMaxContactsImporterId in a safe way + --mMaxContactsImporterId; + } + removeRow(index); + } +} + +void ContactsImporterListModel::importContacts(const int &pId){ + if( pId >=0) { + ContactsImporterModel *contactsImporter = findContactsImporterModelFromId(pId); + if( contactsImporter) + contactsImporter->importContacts(); + }else // Import from all current connectors + for(auto importer : mList) + dynamic_cast(importer)->importContacts(); +} + +// ----------------------------------------------------------------------------- + +void ContactsImporterListModel::addContactsImporter (ContactsImporterModel *contactsImporter) { + // Connect all update signals + QObject::connect(contactsImporter, &ContactsImporterModel::fieldsChanged, this, [this, contactsImporter]() { + emit contactsImporterUpdated(contactsImporter); + }); + QObject::connect(contactsImporter, &ContactsImporterModel::identityChanged, this, [this, contactsImporter]() { + emit contactsImporterUpdated(contactsImporter); + }); + mList << contactsImporter; +} +//----------------------------------------------------------------------------------- + diff --git a/linphone-app/src/components/contacts/ContactsImporterListModel.hpp b/linphone-app/src/components/contacts/ContactsImporterListModel.hpp new file mode 100644 index 000000000..8d6cd3ae7 --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterListModel.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONTACTS_IMPORTER_LIST_MODEL_H_ +#define CONTACTS_IMPORTER_LIST_MODEL_H_ + +#include + +#include + +// ============================================================================= + +class ContactsImporterModel; +class PluginsModel; + +// Store all connectors + +class ContactsImporterListModel : public QAbstractListModel { + + Q_OBJECT; + +public: + ContactsImporterListModel (QObject *parent = Q_NULLPTR); + + int rowCount (const QModelIndex &index = QModelIndex()) const override; + + QHash roleNames () const override; + QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override; + + bool removeRow (int row, const QModelIndex &parent = QModelIndex()); + bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override; + + ContactsImporterModel *findContactsImporterModelFromId (const int &id) const; + QList getList(); + + Q_INVOKABLE ContactsImporterModel *createContactsImporter(QVariantMap data); + Q_INVOKABLE ContactsImporterModel *addContactsImporter (QVariantMap data, int id=-1); + Q_INVOKABLE void removeContactsImporter (ContactsImporterModel *importer); + Q_INVOKABLE void importContacts(const int &id = -1); // Import contacts for all enabled importer if -1 +//----------------------------------------------------------------------------------- + +signals: + void contactsImporterAdded (ContactsImporterModel *contact); + void contactsImporterRemoved (const ContactsImporterModel *contact); + void contactsImporterUpdated (ContactsImporterModel *contact); + +private: + void addContactsImporter (ContactsImporterModel *contactsImporter); + + QList mList; + int mMaxContactsImporterId; // Used to ensure unicity on ID when creating a connector +}; + +#endif // CONTACTS_IMPORTER_LIST_MODEL_H_ diff --git a/linphone-app/src/components/contacts/ContactsImporterListProxyModel.cpp b/linphone-app/src/components/contacts/ContactsImporterListProxyModel.cpp new file mode 100644 index 000000000..21c8fe7b5 --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterListProxyModel.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "components/contacts/ContactsImporterModel.hpp" +#include "components/core/CoreManager.hpp" +#include "utils/Utils.hpp" + +#include "ContactsImporterListModel.hpp" +#include "ContactsImporterListProxyModel.hpp" + +// ============================================================================= + +using namespace std; + + + +// ----------------------------------------------------------------------------- + +ContactsImporterListProxyModel::ContactsImporterListProxyModel (QObject *parent) : QSortFilterProxyModel(parent) { + setSourceModel(CoreManager::getInstance()->getContactsImporterListModel()); + sort(0);// Sort by identity +} + +// ----------------------------------------------------------------------------- + +bool ContactsImporterListProxyModel::filterAcceptsRow ( + int sourceRow, + const QModelIndex &sourceParent +) const { + Q_UNUSED(sourceRow) + Q_UNUSED(sourceParent) + return true; +} + +bool ContactsImporterListProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const { + const ContactsImporterModel *contactA = sourceModel()->data(left).value(); + const ContactsImporterModel *contactB = sourceModel()->data(right).value(); + + return contactA->getIdentity() <= contactB->getIdentity(); +} + +// ----------------------------------------------------------------------------- diff --git a/linphone-app/src/components/contacts/ContactsImporterListProxyModel.hpp b/linphone-app/src/components/contacts/ContactsImporterListProxyModel.hpp new file mode 100644 index 000000000..7e7b0c46a --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterListProxyModel.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONTACTS_IMPORTER_LIST_PROXY_MODEL_H_ +#define CONTACTS_IMPORTER_LIST_PROXY_MODEL_H_ + +#include + +// ============================================================================= + +class ContactsImporterModel; +class ContactsImporterListModel; + +// Manage the list of connectors + +class ContactsImporterListProxyModel : public QSortFilterProxyModel { + Q_OBJECT; + +public: + ContactsImporterListProxyModel (QObject *parent = Q_NULLPTR); + +protected: + bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan (const QModelIndex &left, const QModelIndex &right) const override; + +}; + +#endif // CONTACTS_IMPORTER_LIST_PROXY_MODEL_H_ diff --git a/linphone-app/src/components/contacts/ContactsImporterModel.cpp b/linphone-app/src/components/contacts/ContactsImporterModel.cpp new file mode 100644 index 000000000..6fc1ec3de --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterModel.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ContactsImporterModel.hpp" +#include "ContactsImporterPluginsManager.hpp" +#include "../../utils/Utils.hpp" + +//#include +#include "include/LinphoneApp/PluginDataAPI.hpp" + +#include +#include + +// ============================================================================= + +using namespace std; + + +ContactsImporterModel::ContactsImporterModel (PluginDataAPI * data, QObject *parent) : PluginsModel(parent) { + mIdentity = -1; + mData = nullptr; + setDataAPI(data); +} + +// ----------------------------------------------------------------------------- + +void ContactsImporterModel::setDataAPI(PluginDataAPI *data){ + if(mData){// Unload the current plugin loader and delete it from memory + QPluginLoader * loader = mData->getPluginLoader(); + delete mData; + if(loader){ + loader->unload(); + delete loader; + } + mData = data; + }else + mData = data; + if( mData){ + connect(mData, &PluginDataAPI::inputFieldsChanged, this, &ContactsImporterModel::fieldsChanged); + connect(mData, &PluginDataAPI::message, this, &ContactsImporterModel::messageReceived); + connect(mData, &PluginDataAPI::dataReceived, this, &ContactsImporterModel::parsedContacts); + } +} +PluginDataAPI *ContactsImporterModel::getDataAPI(){ + return mData; +} +bool ContactsImporterModel::isUsable(){ + if( mData){ + if( !mData->getPluginLoader()->isLoaded()) + mData->getPluginLoader()->load(); + return mData->getPluginLoader()->isLoaded(); + }else + return false; +} + +QVariantMap ContactsImporterModel::getFields(){ + return (isUsable()?mData->getInputFields(PluginDataAPI::CONTACTS)[PluginDataAPI::CONTACTS] :QVariantMap()); +} + +void ContactsImporterModel::setFields(const QVariantMap &pFields){ + if( isUsable()) + mData->setInputFields(PluginDataAPI::CONTACTS, pFields); +} + +int ContactsImporterModel::getIdentity()const{ + return mIdentity; +} + +void ContactsImporterModel::setIdentity(const int &pIdentity){ + if( mIdentity != pIdentity){ + mIdentity = pIdentity; + if(mData && mData->getPluginLoader()->isLoaded()) + mData->setSectionConfiguration(PluginsManager::gPluginsConfigSection+"_"+QString::number(mIdentity)); + emit identityChanged(mIdentity); + } +} + +void ContactsImporterModel::loadConfiguration(){ + if(isUsable()) + mData->loadConfiguration(PluginDataAPI::CONTACTS); +} + +void ContactsImporterModel::importContacts(){ + if(isUsable()){ + qInfo() << "Importing contacts with " << mData->getInputFields(PluginDataAPI::CONTACTS)[PluginDataAPI::CONTACTS]["pluginTitle"]; + QPluginLoader * loader = mData->getPluginLoader(); + if( !loader) + qWarning() << "Loader is NULL"; + else{ + qWarning() << "Plugin loaded Status : " << loader->isLoaded() << " for " << loader->fileName(); + } + mData->run(PluginDataAPI::CONTACTS); + }else + qWarning() << "Cannot import contacts, mData is NULL or plugin cannot be loaded "; +} + +void ContactsImporterModel::parsedContacts(const PluginDataAPI::PluginCapability& actionType, QVector > contacts){ + if(actionType == PluginDataAPI::CONTACTS) + ContactsImporterPluginsManager::importContacts(contacts); +} + +void ContactsImporterModel::updateInputs(const PluginDataAPI::PluginCapability& capability, const QVariantMap &inputs){ + if(capability == PluginDataAPI::CONTACTS) + setFields(inputs); +} + +void ContactsImporterModel::messageReceived(const QtMsgType& type, const QString &message){ + if( type == QtMsgType::QtInfoMsg) + emit statusMessage(message); + else + emit errorMessage(message); +} diff --git a/linphone-app/src/components/contacts/ContactsImporterModel.hpp b/linphone-app/src/components/contacts/ContactsImporterModel.hpp new file mode 100644 index 000000000..69834fea7 --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterModel.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONTACTS_IMPORTER_MODEL_H_ +#define CONTACTS_IMPORTER_MODEL_H_ + +#include +#include + +#include "utils/plugins/PluginsManager.hpp" +#include "include/LinphoneApp/PluginDataAPI.hpp" + +// ============================================================================= + +class ContactsImporterModel : public PluginsModel { + Q_OBJECT + + Q_PROPERTY(QVariantMap fields READ getFields WRITE setFields NOTIFY fieldsChanged) + Q_PROPERTY(int identity READ getIdentity WRITE setIdentity NOTIFY identityChanged) + +public: + ContactsImporterModel (PluginDataAPI * data, QObject *parent = nullptr); + + void setDataAPI(PluginDataAPI *data); + PluginDataAPI *getDataAPI(); + bool isUsable(); // Return true if the plugin can be load and has been loaded. + + QVariantMap getFields(); + void setFields(const QVariantMap &pFields); + + int getIdentity()const; + void setIdentity(const int &pIdentity); + + void loadConfiguration(); + Q_INVOKABLE void importContacts(); + +public slots: + void parsedContacts(const PluginDataAPI::PluginCapability& actionType, QVector > contacts); + void updateInputs(const PluginDataAPI::PluginCapability&, const QVariantMap &inputs); + void messageReceived(const QtMsgType& type, const QString &message); + +signals: + void fieldsChanged (const PluginDataAPI::PluginCapability&, QVariantMap fields); + void identityChanged(int identity); + void errorMessage(const QString& message); + void statusMessage(const QString& message); + +private: + int mIdentity; // The identity of the model in configuration. It must be unique between all contact plugins. + PluginDataAPI *mData; // The instance of the plugin with its plugin Loader. +}; + +Q_DECLARE_METATYPE(ContactsImporterModel *); + +#endif // CONTACTS_IMPORTER_MODEL_H_ diff --git a/linphone-app/src/components/contacts/ContactsImporterPluginsManager.cpp b/linphone-app/src/components/contacts/ContactsImporterPluginsManager.cpp new file mode 100644 index 000000000..91c026bcd --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterPluginsManager.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ContactsImporterPluginsManager.hpp" +#include "ContactsImporterModel.hpp" +#include "include/LinphoneApp/PluginNetworkHelper.hpp" + +#include "utils/Utils.hpp" +#include "app/paths/Paths.hpp" +#include "components/contact/VcardModel.hpp" +#include "components/contacts/ContactsListModel.hpp" +#include "components/contacts/ContactsImporterListModel.hpp" +#include "components/core/CoreManager.hpp" +#include "components/sip-addresses/SipAddressesModel.hpp" + + +#include +#include +#include +#include +#include +#include + + +// ============================================================================= + +ContactsImporterPluginsManager::ContactsImporterPluginsManager(QObject * parent) : PluginsManager(parent){ +} + +QVariantMap ContactsImporterPluginsManager::getContactsImporterPluginDescription(const QString& pluginID) { + QVariantMap description; + QJsonDocument doc = getJson(pluginID); + + description = doc.toVariant().toMap(); + + if(description.contains("fields")){ + auto fields = description["fields"].toList(); + auto removedFields = std::remove_if(fields.begin(), fields.end(), + [](const QVariant& f){ + auto field = f.toMap(); + return field.contains("capability") && ((field["capability"].toInt() & PluginDataAPI::CONTACTS) != PluginDataAPI::CONTACTS); + }); + fields.erase(removedFields, fields.end()); + description["fields"] = fields; + } + return description; +} + +void ContactsImporterPluginsManager::openNewPlugin(){ + PluginsManager::openNewPlugin("Import Address Book Connector"); +} + +QVariantList ContactsImporterPluginsManager::getPlugins(){ + return PluginsManager::getPlugins(PluginDataAPI::CONTACTS); +} + +void ContactsImporterPluginsManager::importContacts(ContactsImporterModel * model) { + if(model){ + QString pluginID = model->getFields()["pluginID"].toString(); + if(!pluginID.isEmpty()){ + if( !PluginsManager::gPluginsMap.contains(pluginID)) + qInfo() << "Unknown " << pluginID; + model->importContacts(); + }else + qWarning() << "Error : Cannot import contacts : pluginID is empty"; + } +} + +void ContactsImporterPluginsManager::importContacts(const QVector >& pContacts ){ + for(int i = 0 ; i < pContacts.size() ; ++i){ + VcardModel * card = CoreManager::getInstance()->createDetachedVcardModel(); + SipAddressesModel * sipConvertion = CoreManager::getInstance()->getSipAddressesModel(); + QString domain = pContacts[i].values("sipDomain").at(0); + //if(pContacts[i].contains("phoneNumber")) + // card->addSipAddress(sipConvertion->interpretSipAddress(pContacts[i].values("phoneNumber").at(0)+"@"+domain, false)); + if(pContacts[i].contains("displayName") && pContacts[i].values("displayName").size() > 0) + card->setUsername(pContacts[i].values("displayName").at(0)); + if(pContacts[i].contains("sipUsername") && pContacts[i].values("sipUsername").size() > 0){ + QString sipUsername = pContacts[i].values("sipUsername").at(0); + QString convertedUsername = sipConvertion->interpretSipAddress(sipUsername, domain); + if(!convertedUsername.contains(domain)){ + convertedUsername = convertedUsername.replace('@',"%40")+"@"+domain; + } + card->addSipAddress(convertedUsername); + if( sipUsername.contains('@')){ + card->addEmail(sipUsername); + } + } + if(pContacts[i].contains("email")) + for(auto email : pContacts[i].values("email")) + card->addEmail(email); + if(pContacts[i].contains("organization")) + for(auto company : pContacts[i].values("organization")) + card->addCompany(company); + if( card->getSipAddresses().size()>0){ + CoreManager::getInstance()->getContactsListModel()->addContact(card); + }else + delete card; + } +} diff --git a/linphone-app/src/components/contacts/ContactsImporterPluginsManager.hpp b/linphone-app/src/components/contacts/ContactsImporterPluginsManager.hpp new file mode 100644 index 000000000..e03214120 --- /dev/null +++ b/linphone-app/src/components/contacts/ContactsImporterPluginsManager.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONTACTS_IMPORTER_PLUGINS_MANAGER_MODEL_H_ +#define CONTACTS_IMPORTER_PLUGINS_MANAGER_MODEL_H_ + +#include +#include + +// ============================================================================= +#include "utils/plugins/PluginsManager.hpp" + +class ContactsImporterModel; +class PluginContactsDataAPI; + +class QPluginLoader; + +class ContactsImporterPluginsManager : public PluginsManager{ +Q_OBJECT +public: + ContactsImporterPluginsManager (QObject *parent = Q_NULLPTR); + + Q_INVOKABLE static void openNewPlugin(); // Open a File Dialog. Test if the file can be load and have a matched version. Replace old plugins from custom paths and with the same plugin title. + Q_INVOKABLE static QVariantList getPlugins(); // Get a list of all available plugins + Q_INVOKABLE static QVariantMap getContactsImporterPluginDescription(const QString& pluginID); // Get the description of the plugin. It is used for GUI to create dynamically items + Q_INVOKABLE static void importContacts(ContactsImporterModel * model); // Request the import of the model + static void importContacts(const QVector >& contacts ); // Merge these data into contacts + +}; + + +#endif // CONTACTS_IMPORTER_PLUGINS_MANAGER_MODEL_H_ diff --git a/linphone-app/src/components/core/CoreManager.cpp b/linphone-app/src/components/core/CoreManager.cpp index fae5d0ae4..8f6e031e3 100644 --- a/linphone-app/src/components/core/CoreManager.cpp +++ b/linphone-app/src/components/core/CoreManager.cpp @@ -32,10 +32,12 @@ #include "components/chat/ChatModel.hpp" #include "components/contact/VcardModel.hpp" #include "components/contacts/ContactsListModel.hpp" +#include "components/contacts/ContactsImporterListModel.hpp" #include "components/history/HistoryModel.hpp" #include "components/settings/AccountSettingsModel.hpp" #include "components/settings/SettingsModel.hpp" #include "components/sip-addresses/SipAddressesModel.hpp" + #include "utils/Utils.hpp" #if defined(Q_OS_MACOS) @@ -46,6 +48,7 @@ #include "CoreHandlers.hpp" #include "CoreManager.hpp" +#include #include @@ -78,7 +81,6 @@ CoreManager::CoreManager (QObject *parent, const QString &configPath) : QObject::connect(coreHandlers, &CoreHandlers::coreStarted, this, &CoreManager::initCoreManager, Qt::QueuedConnection); QObject::connect(coreHandlers, &CoreHandlers::coreStopped, this, &CoreManager::stopIterate, Qt::QueuedConnection); QObject::connect(coreHandlers, &CoreHandlers::logsUploadStateChanged, this, &CoreManager::handleLogsUploadStateChanged); - createLinphoneCore(configPath); } @@ -93,6 +95,7 @@ CoreManager::~CoreManager(){ void CoreManager::initCoreManager(){ mCallsListModel = new CallsListModel(this); mContactsListModel = new ContactsListModel(this); + mContactsImporterListModel = new ContactsImporterListModel(this); mAccountSettingsModel = new AccountSettingsModel(this); mSettingsModel = new SettingsModel(this); mSipAddressesModel = new SipAddressesModel(this); @@ -225,8 +228,7 @@ void CoreManager::setDatabasesPaths () { SET_DATABASE_PATH(Friends, Paths::getFriendsListFilePath()); SET_DATABASE_PATH(CallLogs, Paths::getCallHistoryFilePath()); if(QFile::exists(Utils::coreStringToAppString(Paths::getMessageHistoryFilePath()))){ - linphone_core_set_chat_database_path(mCore->cPtr(), Paths::getMessageHistoryFilePath().c_str()); - //SET_DATABASE_PATH(Chat, Paths::getMessageHistoryFilePath());// Setting the message database let SDK to migrate data + linphone_core_set_chat_database_path(mCore->cPtr(), Paths::getMessageHistoryFilePath().c_str());// Setting the message database let SDK to migrate data QFile::remove(Utils::coreStringToAppString(Paths::getMessageHistoryFilePath())); } } diff --git a/linphone-app/src/components/core/CoreManager.hpp b/linphone-app/src/components/core/CoreManager.hpp index 74b45193b..66360b380 100644 --- a/linphone-app/src/components/core/CoreManager.hpp +++ b/linphone-app/src/components/core/CoreManager.hpp @@ -35,6 +35,7 @@ class AccountSettingsModel; class CallsListModel; class ChatModel; class ContactsListModel; +class ContactsImporterListModel; class CoreHandlers; class EventCountNotifier; class HistoryModel; @@ -93,6 +94,13 @@ public: Q_CHECK_PTR(mContactsListModel); return mContactsListModel; } + + ContactsImporterListModel *getContactsImporterListModel () const { + Q_CHECK_PTR(mContactsImporterListModel); + return mContactsImporterListModel; + } + + SipAddressesModel *getSipAddressesModel () const { Q_CHECK_PTR(mSipAddressesModel); @@ -180,6 +188,8 @@ private: CallsListModel *mCallsListModel = nullptr; ContactsListModel *mContactsListModel = nullptr; + ContactsImporterListModel *mContactsImporterListModel = nullptr; + SipAddressesModel *mSipAddressesModel = nullptr; SettingsModel *mSettingsModel = nullptr; AccountSettingsModel *mAccountSettingsModel = nullptr; diff --git a/linphone-app/src/components/settings/SettingsModel.cpp b/linphone-app/src/components/settings/SettingsModel.cpp index e9900d9e8..c5e1d6dd9 100644 --- a/linphone-app/src/components/settings/SettingsModel.cpp +++ b/linphone-app/src/components/settings/SettingsModel.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include @@ -27,6 +29,7 @@ #include "app/logger/Logger.hpp" #include "app/paths/Paths.hpp" #include "components/core/CoreManager.hpp" +#include "include/LinphoneApp/PluginNetworkHelper.hpp" #include "utils/Utils.hpp" #include "utils/MediastreamerUtils.hpp" #include "SettingsModel.hpp" @@ -41,6 +44,7 @@ namespace { } const string SettingsModel::UiSection("ui"); +const string SettingsModel::ContactsSection("contacts_import"); SettingsModel::SettingsModel (QObject *parent) : QObject(parent) { CoreManager *coreManager = CoreManager::getInstance(); @@ -102,6 +106,7 @@ void SettingsModel::onSettingsTabChanged(int idx) { case 4://ui break; case 5://advanced + accessAdvancedSettings(); break; default: break; @@ -1187,6 +1192,12 @@ void SettingsModel::setExitOnClose (bool value) { // Advanced. // ============================================================================= +void SettingsModel::accessAdvancedSettings() { + emit contactImporterChanged(); +} + +//------------------------------------------------------------------------------ + QString SettingsModel::getLogsFolder () const { return getLogsFolder(mConfig); } @@ -1263,7 +1274,6 @@ bool SettingsModel::getLogsEnabled (const shared_ptr &config) } // --------------------------------------------------------------------------- - bool SettingsModel::getDeveloperSettingsEnabled () const { #ifdef DEBUG return !!mConfig->getInt(UiSection, "developer_settings", 0); diff --git a/linphone-app/src/components/settings/SettingsModel.hpp b/linphone-app/src/components/settings/SettingsModel.hpp index 2e71a0976..20b0dfe65 100644 --- a/linphone-app/src/components/settings/SettingsModel.hpp +++ b/linphone-app/src/components/settings/SettingsModel.hpp @@ -27,6 +27,7 @@ #include #include "components/core/CoreHandlers.hpp" +#include "components/contacts/ContactsImporterModel.hpp" // ============================================================================= @@ -425,7 +426,10 @@ public: bool getExitOnClose () const; void setExitOnClose (bool value); - // --------------------------------------------------------------------------- + // Advanced. --------------------------------------------------------------------------- + + + void accessAdvancedSettings(); QString getLogsFolder () const; void setLogsFolder (const QString &folder); @@ -456,6 +460,7 @@ public: bool getIsInCall() const; static const std::string UiSection; + static const std::string ContactsSection; // =========================================================================== // SIGNALS. @@ -532,7 +537,7 @@ signals: void fileTransferUrlChanged (const QString &url); void mediaEncryptionChanged (MediaEncryption encryption); - void limeStateChanged (bool state); + void limeStateChanged (bool state); void contactsEnabledChanged (bool status); @@ -586,6 +591,8 @@ signals: void logsUploadUrlChanged (const QString &url); void logsEnabledChanged (bool status); void logsEmailChanged (const QString &email); + + void contactImporterChanged(); bool developerSettingsEnabledChanged (bool status); diff --git a/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp b/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp index 31cb49424..b1eaca2da 100644 --- a/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp +++ b/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp @@ -196,6 +196,32 @@ QString SipAddressesModel::interpretSipAddress (const QString &sipAddress, bool return Utils::coreStringToAppString(lAddress->asStringUriOnly()); return QString(""); } +QString SipAddressesModel::interpretSipAddress (const QString &sipAddress, const QString &domain) { + auto core = CoreManager::getInstance()->getCore(); + if(!core){ + qWarning() << "No core to interpret address"; + }else{ + auto proxyConfig = CoreManager::getInstance()->getCore()->createProxyConfig(); + if( !proxyConfig) { + }else{ + shared_ptr lAddressTemp = core->createPrimaryContactParsed();// Create an address + if( lAddressTemp ){ + lAddressTemp->setDomain(Utils::appStringToCoreString(domain)); // Set the domain and use the address into proxy + proxyConfig->setIdentityAddress(lAddressTemp); + shared_ptr lAddress = proxyConfig->normalizeSipUri(Utils::appStringToCoreString(sipAddress)); + if (lAddress) { + return Utils::coreStringToAppString(lAddress->asStringUriOnly()); + } else { + qWarning() << "Cannot normalize Sip Uri : " << sipAddress << " / " << domain; + return QString(""); + } + }else{ + qWarning() << "Cannot create a Primary Contact Parsed"; + } + } + } + return QString(""); +} QString SipAddressesModel::interpretSipAddress (const QUrl &sipAddress) { return sipAddress.toString(); diff --git a/linphone-app/src/components/sip-addresses/SipAddressesModel.hpp b/linphone-app/src/components/sip-addresses/SipAddressesModel.hpp index 841b7c43e..daf774d40 100644 --- a/linphone-app/src/components/sip-addresses/SipAddressesModel.hpp +++ b/linphone-app/src/components/sip-addresses/SipAddressesModel.hpp @@ -74,6 +74,7 @@ public: Q_INVOKABLE static QString interpretSipAddress (const QString &sipAddress, bool checkUsername = true); Q_INVOKABLE static QString interpretSipAddress (const QUrl &sipAddress); + Q_INVOKABLE static QString interpretSipAddress (const QString &sipAddress, const QString &domain); Q_INVOKABLE static bool addressIsValid (const QString &address); Q_INVOKABLE static bool sipAddressIsValid (const QString &sipAddress); diff --git a/linphone-app/src/utils/plugins/LinphonePlugin.cpp b/linphone-app/src/utils/plugins/LinphonePlugin.cpp new file mode 100644 index 000000000..a677f6aed --- /dev/null +++ b/linphone-app/src/utils/plugins/LinphonePlugin.cpp @@ -0,0 +1 @@ +#include "include/LinphoneApp/LinphonePlugin.hpp" diff --git a/linphone-app/src/utils/plugins/PluginDataAPI.cpp b/linphone-app/src/utils/plugins/PluginDataAPI.cpp new file mode 100644 index 000000000..818855537 --- /dev/null +++ b/linphone-app/src/utils/plugins/PluginDataAPI.cpp @@ -0,0 +1,118 @@ +#include "include/LinphoneApp/PluginDataAPI.hpp" + +#include +#include + +#include +#include +#include +// This class regroup Data interface for importing contacts +#include "include/LinphoneApp/LinphonePlugin.hpp" + +PluginDataAPI::PluginDataAPI(LinphonePlugin * plugin, void* linphoneCore, QPluginLoader * pluginLoader) : mPlugin(plugin), mLinphoneCore(linphoneCore), mPluginLoader(pluginLoader){ + QVariantMap defaultValues; + QJsonDocument doc = QJsonDocument::fromJson(mPlugin->getGUIDescriptionToJson().toUtf8()); + QVariantMap description = doc.toVariant().toMap(); + mPluginLoader->setLoadHints(0); +// First, get all fields where their target is ALL. It will be act as a "default field" + for(auto field : description["fields"].toList()){ + auto details = field.toMap(); + if( details.contains("fieldId") && details.contains("defaultData")){ + int fieldCapability = details["capability"].toInt(); + if( fieldCapability == PluginCapability::ALL){ + for(int capability = PluginCapability::CONTACTS ; capability != PluginCapability::LAST ; ++capability){ + mInputFields[static_cast(capability)][details["fieldId"].toString()] = details["defaultData"].toString(); + } + } + } + } +// Second, get all fields that are not for ALL and add them + for(auto field : description["fields"].toList()){ + auto details = field.toMap(); + if( details.contains("fieldId") && details.contains("defaultData")){ + int fieldCapability = details["capability"].toInt(); + if( fieldCapability> PluginCapability::NOTHING) + mInputFields[static_cast(fieldCapability)][details["fieldId"].toString()] = details["defaultData"].toString(); + } + } + for(auto inputFields : mInputFields) + inputFields["enabled"] = 0; +} +PluginDataAPI::~PluginDataAPI(){ +} + +void PluginDataAPI::setInputFields(const PluginCapability& pCapability, const QVariantMap &inputFields){ + for(int capabilityIndex = (pCapability == PluginCapability::ALL?PluginCapability::CONTACTS:pCapability); capabilityIndex != (pCapability == PluginCapability::ALL?PluginCapability::LAST:pCapability+1) ; ++capabilityIndex){ + PluginCapability selectedCapability = static_cast(capabilityIndex); + if(mInputFields[selectedCapability] != inputFields) { + mInputFields[selectedCapability] = inputFields; + if( isValid(false)) + saveConfiguration(selectedCapability); + emit inputFieldsChanged(selectedCapability, mInputFields[selectedCapability]); + } + } +} + +QMap PluginDataAPI::getInputFields(const PluginCapability& capability){ + if( capability == PluginCapability::ALL) + return mInputFields; + else{ + QMap data; + data[capability] = mInputFields[capability]; + return data; + } +} +QMap PluginDataAPI::getInputFieldsToSave(const PluginCapability& capability) { + return getInputFields(capability); +} +//----------------------------- CONFIGURATION --------------------------------------- + +void PluginDataAPI::setSectionConfiguration(const QString& section){ + mSectionConfigurationName = section; +} + +void PluginDataAPI::loadConfiguration(const PluginCapability& pCapability){ + if( mSectionConfigurationName != "") { + for(int capabilityIndex = (pCapability == PluginCapability::ALL?PluginCapability::CONTACTS:pCapability); capabilityIndex != (pCapability == PluginCapability::ALL?PluginCapability::LAST:pCapability+1) ; ++capabilityIndex){ + PluginCapability currentCapability = static_cast(capabilityIndex); + std::shared_ptr config = static_cast(mLinphoneCore)->getConfig(); + QVariantMap importData; + std::string sectionName = (mSectionConfigurationName+"_"+QString::number(capabilityIndex)).toStdString(); + std::list keys = config->getKeysNamesList(sectionName); + for(auto key : keys){ + std::string value = config->getString(sectionName, key, ""); + importData[QString::fromLocal8Bit(key.c_str(), int(key.size()))] = QString::fromLocal8Bit(value.c_str(), int(value.size())); + } + //Do not use setInputFields(importData); as we don't want to save the configuration + mInputFields[currentCapability] = importData; + emit inputFieldsChanged(currentCapability, mInputFields[currentCapability]); + } + } +} + +void PluginDataAPI::saveConfiguration(const PluginCapability& pCapability){ + if( mSectionConfigurationName != "") { + auto inputs = getInputFieldsToSave(pCapability); + for(QMap::Iterator input = inputs.begin() ; input != inputs.end() ; ++input){ + PluginCapability currentCapability = input.key(); + std::string sectionName = (mSectionConfigurationName+"_"+QString::number(currentCapability)).toStdString(); + std::shared_ptr config = static_cast(mLinphoneCore)->getConfig(); + QVariantMap inputsToSave = inputs[currentCapability]; + config->cleanSection(sectionName);// Remove fields that doesn't exist anymore (like temporary variables) + for(auto field = inputsToSave.begin() ; field != inputsToSave.end() ; ++field) + config->setString(sectionName, qPrintable(field.key()), qPrintable(field.value().toString())); + } + } +} +void PluginDataAPI::cleanAllConfigurations(){ + for(int capabilityIndex = PluginCapability::ALL ; capabilityIndex != PluginCapability::LAST ; ++capabilityIndex){ + std::string sectionName = (mSectionConfigurationName+"_"+QString::number(capabilityIndex)).toStdString(); + std::shared_ptr config = static_cast(mLinphoneCore)->getConfig(); + config->cleanSection(sectionName); + } +} +//----------------------------- ------------------------------------------------------- + +QPluginLoader * PluginDataAPI::getPluginLoader(){ + return mPluginLoader; +} diff --git a/linphone-app/src/utils/plugins/PluginNetworkHelper.cpp b/linphone-app/src/utils/plugins/PluginNetworkHelper.cpp new file mode 100644 index 000000000..d6ec9ca7b --- /dev/null +++ b/linphone-app/src/utils/plugins/PluginNetworkHelper.cpp @@ -0,0 +1,55 @@ +#include "include/LinphoneApp/PluginNetworkHelper.hpp" +#include +#include +// This class is used to define network operation to retrieve Addresses from Network + +PluginNetworkHelper::PluginNetworkHelper(){} +PluginNetworkHelper::~PluginNetworkHelper(){} +void PluginNetworkHelper::request(){ // Create QNetworkReply and make network requests + QNetworkRequest request(prepareRequest()); + + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + mNetworkReply = mManager.get(request); + +#if QT_CONFIG(ssl) + mNetworkReply->ignoreSslErrors(); +#endif + + QNetworkReply *data = mNetworkReply.data(); + + QObject::connect(data, &QNetworkReply::readyRead, this, &PluginNetworkHelper::handleReadyData); + QObject::connect(data, &QNetworkReply::finished, this, &PluginNetworkHelper::handleFinished); + QObject::connect(data, QNonConstOverload::of(&QNetworkReply::error), this, &PluginNetworkHelper::handleError); + +#if QT_CONFIG(ssl) + QObject::connect(data, &QNetworkReply::sslErrors, this, &PluginNetworkHelper::handleSslErrors); +#endif +} +void PluginNetworkHelper::handleReadyData(){ + mBuffer.append(mNetworkReply->readAll()); +} +void PluginNetworkHelper::handleFinished (){ + if (mNetworkReply->error() == QNetworkReply::NoError){ + mBuffer.append(mNetworkReply->readAll()); + emit requestFinished(mBuffer); + }else { + qWarning() << mNetworkReply->errorString(); + emit message(QtWarningMsg, "Error while dealing with network. See logs for details."); + } + mBuffer.clear(); +} +void PluginNetworkHelper::handleError (QNetworkReply::NetworkError code) { + if (code != QNetworkReply::OperationCanceledError) { + QString url = mNetworkReply->url().host(); + QString errorString = mNetworkReply->errorString(); + qWarning() << QStringLiteral("Download failed: %1 from %2").arg(errorString).arg(url); + } +} +void PluginNetworkHelper::handleSslErrors (const QList &sslErrors){ +#if QT_CONFIG(ssl) + for (const QSslError &error : sslErrors) + qWarning() << QStringLiteral("SSL error %1 : %2").arg(error.error()).arg(error.errorString()); +#else + Q_UNUSED(sslErrors); +#endif +} diff --git a/linphone-app/src/utils/plugins/PluginsManager.cpp b/linphone-app/src/utils/plugins/PluginsManager.cpp new file mode 100644 index 000000000..63143e3c5 --- /dev/null +++ b/linphone-app/src/utils/plugins/PluginsManager.cpp @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "PluginsManager.hpp" +//#include "ContactsImporterModel.hpp" +#include "include/LinphoneApp/LinphonePlugin.hpp" +#include "include/LinphoneApp/PluginNetworkHelper.hpp" + +#include "utils/Utils.hpp" +#include "app/paths/Paths.hpp" +#include "components/contact/VcardModel.hpp" +#include "components/contacts/ContactsListModel.hpp" +#include "components/contacts/ContactsImporterListModel.hpp" +#include "components/contacts/ContactsImporterModel.hpp" +#include "components/core/CoreManager.hpp" +#include "components/sip-addresses/SipAddressesModel.hpp" + + +#include +#include +#include +#include +#include +#include + + +// ============================================================================= + +QMap PluginsManager::gPluginsMap; +QString PluginsManager::gPluginsConfigSection = "AppPlugin"; + +PluginsManager::PluginsManager(QObject * parent) : QObject(parent){ +} + +QPluginLoader * PluginsManager::getPlugin(const QString &pluginIdentity){ + QStringList pluginPaths = Paths::getPluginsAppFolders();// Get all paths + if( gPluginsMap.contains(pluginIdentity)){ + for(int i = 0 ; i < pluginPaths.size() ; ++i) { + QString pluginPath = pluginPaths[i] +gPluginsMap[pluginIdentity]; + QPluginLoader * loader = new QPluginLoader(pluginPath); + loader->setLoadHints(0); // this force Qt to unload the plugin from memory when we request it. Be carefull by not having a plugin instance or data created inside the plugin after the unload. + if( auto instance = loader->instance()) { + auto plugin = qobject_cast< LinphonePlugin* >(instance); + if (plugin ) + return loader; + else{ + qWarning() << loader->errorString(); + loader->unload(); + } + } + delete loader; + } + } + return nullptr; +} + +void * PluginsManager::createInstance(const QString &pluginIdentity){ + void * dataInstance = nullptr; + LinphonePlugin * plugin = nullptr; + if( gPluginsMap.contains(pluginIdentity)){ + QStringList pluginPaths = Paths::getPluginsAppFolders(); + for(int i = 0 ; i < pluginPaths.size() ; ++i) { + QString pluginPath = pluginPaths[i] +gPluginsMap[pluginIdentity]; + QPluginLoader * loader = new QPluginLoader(pluginPath); + loader->setLoadHints(0); // this force Qt to unload the plugin from memory when we request it. Be carefull by not having a plugin instance or data created inside the plugin after the unload. + if( auto instance = loader->instance()) { + plugin = qobject_cast< LinphonePlugin* >(instance); + if (plugin) { + try{ + dataInstance = plugin->createInstance(CoreManager::getInstance()->getCore().get(), loader); + return dataInstance; + }catch(...){ + loader->unload(); + } + }else + loader->unload(); + } + delete loader; + } + } + return dataInstance; +} + +QJsonDocument PluginsManager::getJson(const QString &pluginIdentity){ + QJsonDocument doc; + QPluginLoader * pluginLoader = getPlugin(pluginIdentity); + if( pluginLoader ){ + auto instance = pluginLoader->instance(); + if( instance ){ + LinphonePlugin * plugin = qobject_cast< LinphonePlugin* >(instance); + if( plugin ){ + doc = QJsonDocument::fromJson(plugin->getGUIDescriptionToJson().toUtf8()); + } + } + pluginLoader->unload(); + delete pluginLoader; + } + return doc; +} +QList PluginsManager::getImporterModels(const QStringList &capabilities){ + QList models; + for(int i = 0 ; i < capabilities.size() ; ++i){ + if( capabilities[i] == "CONTACTS") + models += CoreManager::getInstance()->getContactsImporterListModel()->getList(); + } + return models; +} +void PluginsManager::openNewPlugin(const QString &pTitle){ + + QString fileName = QFileDialog::getOpenFileName(nullptr, pTitle); + QString pluginIdentity; + QStringList capabilities; + QList modelsToReset; + int doCopy = QMessageBox::Yes; + bool cannotRemovePlugin = false; + //QVersionNumber pluginVersion, apiVersion = LinphonePlugin::gPluginVersion; + if(fileName != ""){ + QFileInfo fileInfo(fileName); + QString path = Utils::coreStringToAppString(Paths::getPluginsAppDirPath()); + if( !QLibrary::isLibrary(fileName)){ + QMessageBox::information(nullptr, pTitle, "The file is not a plugin"); + }else{ + QPluginLoader loader(fileName); + loader.setLoadHints(0); + QJsonObject metaData = loader.metaData()["MetaData"].toObject(); + if( metaData.contains("ID") && metaData.contains("Capabilities")){ + capabilities = metaData["Capabilities"].toString().toUpper().remove(' ').split(","); + pluginIdentity = metaData["ID"].toString(); + } + if(!pluginIdentity.isEmpty()){// Check all plugins that have this title + QStringList oldPlugins; + if( gPluginsMap.contains(pluginIdentity)) + oldPlugins << gPluginsMap[pluginIdentity]; + if( QFile::exists(path+fileInfo.fileName())) + oldPlugins << path+fileInfo.fileName(); + if(oldPlugins.size() > 0){ + doCopy = QMessageBox::question(nullptr, pTitle, "The plugin already exists. Do you want to overwrite it?\n"+oldPlugins.join('\n'), QMessageBox::Yes, QMessageBox::No); + if( doCopy == QMessageBox::Yes){ + if(gPluginsMap.contains(pluginIdentity)){ + auto importers = CoreManager::getInstance()->getContactsImporterListModel()->getList(); + for(auto importer : importers){ + QJsonObject pluginMetaData(importer->getDataAPI()->getPluginLoader()->metaData()); + if( pluginMetaData.contains("ID") && pluginMetaData["ID"].toString() == pluginIdentity){ + importer->setDataAPI(nullptr); + modelsToReset.append(importer); + } + } + QStringList pluginPaths = Paths::getPluginsAppFolders(); + for(int i = 0 ; !cannotRemovePlugin && i < pluginPaths.size()-1 ; ++i) {// Ignore the last path as it is the app folder + QString pluginPath = pluginPaths[i]; + if(QFile::exists(pluginPath+gPluginsMap[pluginIdentity])){ + cannotRemovePlugin = !QFile::remove(pluginPath+gPluginsMap[pluginIdentity]); + } + } + } + if(!cannotRemovePlugin && QFile::exists(path+fileInfo.fileName())) + cannotRemovePlugin = !QFile::remove(path+fileInfo.fileName()); + if(!cannotRemovePlugin) + gPluginsMap[pluginIdentity] = ""; + } + } + }else + doCopy = QMessageBox::No; + if(doCopy == QMessageBox::Yes ){ + + if( cannotRemovePlugin)// Qt will not unload library from memory so files cannot be removed. See https://bugreports.qt.io/browse/QTBUG-68880 + QMessageBox::information(nullptr, pTitle, "The plugin cannot be replaced. You have to exit the application and delete manually the plugin file in\n"+path); + else if( !QFile::copy(fileName, path+fileInfo.fileName())) + QMessageBox::information(nullptr, pTitle, "The plugin cannot be copied. You have to copy manually the plugin file to\n"+path); + else { + gPluginsMap[pluginIdentity] = fileInfo.fileName(); + for(auto importer : modelsToReset) + importer->setDataAPI(static_cast(createInstance(pluginIdentity))); + } + } + } + } +} + +QVariantList PluginsManager::getPlugins(const int& capabilities) { + QVariantList plugins; + QStringList pluginPaths = Paths::getPluginsAppFolders(); + if(capabilities<0) + gPluginsMap.clear(); + for(int pathIndex = pluginPaths.size()-1 ; pathIndex >= 0 ; --pathIndex) {// Start from app package. This sort ensure the priority on user plugins + QString pluginPath = pluginPaths[pathIndex]; + QDir dir(pluginPath); + QStringList pluginFiles = dir.entryList(QDir::Files); + for(int i = 0 ; i < pluginFiles.size() ; ++i) { + if( QLibrary::isLibrary(pluginPath+pluginFiles[i])){ + QPluginLoader loader(pluginPath+pluginFiles[i]); + loader.setLoadHints(0); // this force Qt to unload the plugin from memory when we request it. Be carefull by not having a plugin instance or data created inside the plugin after the unload. + if (auto instance = loader.instance()) { + LinphonePlugin * plugin = qobject_cast< LinphonePlugin* >(instance); + if ( plugin){ + QJsonObject metaData = loader.metaData()["MetaData"].toObject(); + if( metaData.contains("ID")){ + bool getIt = false; + if(capabilities>=0 ){ + if(metaData.contains("Capabilities")){ + QString pluginCapabilities = metaData["Capabilities"].toString().toUpper().remove(' '); + if( (capabilities & PluginDataAPI::CONTACTS) == PluginDataAPI::CONTACTS && pluginCapabilities.contains("CONTACTS")){ + getIt = true; + } + }else + qWarning()<< "The plugin " << pluginFiles[i] << " must have Capabilities in its metadata"; + }else + getIt = true; + if(getIt){ + QJsonDocument doc = QJsonDocument::fromJson(plugin->getGUIDescriptionToJson().toUtf8()); + QVariantMap desc; + desc["pluginTitle"] = doc["pluginTitle"]; + desc["pluginID"] = metaData["ID"].toString(); + if(!doc["pluginTitle"].toString().isEmpty()){ + gPluginsMap[metaData["ID"].toString()] = pluginFiles[i]; + plugins.push_back(desc); + } + } + }else + qWarning()<< "The plugin " << pluginFiles[i] << " must have ID in its metadata"; + } else { + qWarning()<< "The plugin " << pluginFiles[i] << " should be updated to this version of API : " << loader.metaData()["IID"].toString(); + } + loader.unload(); + } else { + qWarning()<< "The plugin " << pluginFiles[i] << " cannot be used : " << loader.errorString(); + } + } + } + std::sort(plugins.begin(), plugins.end()); + } + return plugins; +} +QVariantMap PluginsManager::getPluginDescription(const QString& pluginIdentity) { + QVariantMap description; + QJsonDocument doc = getJson(pluginIdentity); + description = doc.toVariant().toMap(); + return description; +} + + +QVariantMap PluginsManager::getDefaultValues(const QString& pluginIdentity){ + QVariantMap defaultValues; + QVariantMap description; + QJsonDocument doc = getJson(pluginIdentity); + description = doc.toVariant().toMap(); + for(auto field : description["fields"].toList()){ + auto details = field.toMap(); + if( details.contains("fieldId") && details.contains("defaultData")){ + defaultValues[details["fieldId"].toString()] = details["defaultData"].toString(); + } + } + return defaultValues; +} diff --git a/linphone-app/src/utils/plugins/PluginsManager.hpp b/linphone-app/src/utils/plugins/PluginsManager.hpp new file mode 100644 index 000000000..098d060e3 --- /dev/null +++ b/linphone-app/src/utils/plugins/PluginsManager.hpp @@ -0,0 +1,47 @@ +//const QVersionNumber ContactsImporterPlugin::gPluginVersion = QVersionNumber::fromString(PLUGIN_CONTACT_VERSION); +//const QVersionNumber ContactsImporterPlugin::gPluginVersion = QVersionNumber::fromString("1.0.0"); +//const QVersionNumber _ContactsImporterPlugin::gPluginVersion = QVersionNumber::fromString("1.0.0"); +#ifndef PLUGINS_MANAGER_MODEL_H_ +#define PLUGINS_MANAGER_MODEL_H_ + +#include +#include + +// ============================================================================= + +class ContactsImporterModel; +class PluginDataAPI; +class QPluginLoader; + +class PluginsModel : public QObject{ +public: + PluginsModel(QObject *parent = nullptr) : QObject(parent){} + virtual ~PluginsModel(){} + virtual void setDataAPI(PluginDataAPI*) = 0; + virtual PluginDataAPI* getDataAPI() = 0; + virtual int getIdentity()const = 0; + virtual QVariantMap getFields() = 0; +}; +class PluginsManager : public QObject{ +Q_OBJECT +public: + PluginsManager (QObject *parent = Q_NULLPTR); + + static QPluginLoader * getPlugin(const QString &pluginIdentity); // Return a plugin loader with Hints to 0 (unload will force Qt to remove the plugin from memory). + static QVariantList getPlugins(const int& capabilities = -1); // Return all loaded plugins that have selected capabilities (PluginCapability flags) + static void * createInstance(const QString &pluginIdentity); //Return a data instance from a plugin name. + static QJsonDocument getJson(const QString &pluginIdentity); // Get the description of the plugin int the Json format. + + Q_INVOKABLE static void openNewPlugin(const QString &pTitle); // Open a File Dialog. Test if the file can be load and have a matched version. Replace old plugins from custom paths and with the same plugin title. + static QVariantMap getDefaultValues(const QString& pluginIdentity); // Get the default values of each fields for th eplugin + QVariantMap getPluginDescription(const QString& pluginIdentity); + + + + QList getImporterModels(const QStringList &capabilities); + static QMap gPluginsMap; // Map between Identity and plugin path + static QString gPluginsConfigSection; // The root name of the plugin's section in configuration file +}; + + +#endif // CONTACTS_IMPORTER_PLUGINS_MANAGER_MODEL_H_ diff --git a/linphone-app/ui/modules/Common/Form/Placements/FormTable.qml b/linphone-app/ui/modules/Common/Form/Placements/FormTable.qml index 95c168de9..2228133ed 100644 --- a/linphone-app/ui/modules/Common/Form/Placements/FormTable.qml +++ b/linphone-app/ui/modules/Common/Form/Placements/FormTable.qml @@ -6,24 +6,37 @@ import Common.Styles 1.0 Column { id: formTable + + width: parent.width property alias titles: header.model property bool disableLineTitle: false property int legendLineWidth: FormTableStyle.entry.width + + property var maxWidthStyle : FormTableStyle.entry.maxWidth - readonly property double maxItemWidth: { - var n = titles.length - var curWidth = (width - FormTableStyle.entry.width) / n - (n - 1) * FormTableLineStyle.spacing - var maxWidth = FormTableStyle.entry.maxWidth - - return curWidth < maxWidth ? curWidth : maxWidth - } + readonly property double maxItemWidth: computeMaxItemWidth() // --------------------------------------------------------------------------- - + function updateMaxItemWidth(){ + maxItemWidth = computeMaxItemWidth(); + } + function computeMaxItemWidth(){ + var n = 1; +// if( titles) +// n = titles.length +// else{ + for(var line = 0 ; line < formTable.visibleChildren.length ; ++line){ + var column = formTable.visibleChildren[line].visibleChildren.length; + n = Math.max(n, column-1); + } +// } + var curWidth = (width - (disableLineTitle?0:legendLineWidth) ) /n - FormTableLineStyle.spacing + var maxWidth = maxWidthStyle + return curWidth < maxWidth ? curWidth : maxWidth + } spacing: FormTableStyle.spacing - width: parent.width // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/modules/Common/Form/Switch.qml b/linphone-app/ui/modules/Common/Form/Switch.qml index 0e174b6e4..c83ffca56 100644 --- a/linphone-app/ui/modules/Common/Form/Switch.qml +++ b/linphone-app/ui/modules/Common/Form/Switch.qml @@ -100,5 +100,6 @@ Switch { anchors.fill: parent onClicked: control.enabled && control.clicked() + onPressed: control.enabled && control.forceActiveFocus() } } diff --git a/linphone-app/ui/views/App/Settings/SettingsAdvanced.qml b/linphone-app/ui/views/App/Settings/SettingsAdvanced.qml index 50798f723..5ca27a6ab 100644 --- a/linphone-app/ui/views/App/Settings/SettingsAdvanced.qml +++ b/linphone-app/ui/views/App/Settings/SettingsAdvanced.qml @@ -1,120 +1,319 @@ import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 import Common 1.0 import Linphone 1.0 import App.Styles 1.0 +import Linphone.Styles 1.0 +import Common.Styles 1.0 + +import ContactsImporterPluginsManager 1.0 import 'SettingsAdvanced.js' as Logic // ============================================================================= TabContainer { - Column { - spacing: SettingsWindowStyle.forms.spacing - width: parent.width - - // ------------------------------------------------------------------------- - // Logs. - // ------------------------------------------------------------------------- - - Form { - title: qsTr('logsTitle') - width: parent.width - - FormLine { - FormGroup { - label: qsTr('logsFolderLabel') - - FileChooserButton { - selectedFile: SettingsModel.logsFolder - selectFolder: true - - onAccepted: SettingsModel.logsFolder = selectedFile - } + Column { + spacing: SettingsWindowStyle.forms.spacing + width: parent.width + + // ------------------------------------------------------------------------- + // Logs. + // ------------------------------------------------------------------------- + + Form { + title: qsTr('logsTitle') + width: parent.width + + FormLine { + FormGroup { + label: qsTr('logsFolderLabel') + + FileChooserButton { + selectedFile: SettingsModel.logsFolder + selectFolder: true + + onAccepted: SettingsModel.logsFolder = selectedFile + } + } + } + + FormLine { + FormGroup { + label: qsTr('logsUploadUrlLabel') + + TextField { + readOnly: true + text: SettingsModel.logsUploadUrl + + onEditingFinished: SettingsModel.logsUploadUrl = text + } + } + } + + FormLine { + FormGroup { + label: qsTr('logsEnabledLabel') + + Switch { + checked: SettingsModel.logsEnabled + + onClicked: SettingsModel.logsEnabled = !checked + } + } + } } - } - - FormLine { - FormGroup { - label: qsTr('logsUploadUrlLabel') - - TextField { - readOnly: true - text: SettingsModel.logsUploadUrl - - onEditingFinished: SettingsModel.logsUploadUrl = text - } + Row { + anchors.right: parent.right + spacing: SettingsAdvancedStyle.buttons.spacing + + TextButtonB { + text: qsTr('cleanLogs') + + onClicked: Logic.cleanLogs() + } + + TextButtonB { + enabled: !sendLogsBlock.loading && SettingsModel.logsEnabled + text: qsTr('sendLogs') + + onClicked: sendLogsBlock.execute() + } } - } - - FormLine { - FormGroup { - label: qsTr('logsEnabledLabel') - - Switch { - checked: SettingsModel.logsEnabled - - onClicked: SettingsModel.logsEnabled = !checked - } + RequestBlock { + id: sendLogsBlock + + action: CoreManager.sendLogs + width: parent.width + + + Connections { + target: CoreManager + + onLogsUploaded: Logic.handleLogsUploaded(url) + } } - } - - FormEmptyLine {} - } - - Row { - anchors.right: parent.right - spacing: SettingsAdvancedStyle.buttons.spacing - - TextButtonB { - text: qsTr('cleanLogs') - - onClicked: Logic.cleanLogs() - } - - TextButtonB { - enabled: !sendLogsBlock.loading && SettingsModel.logsEnabled - text: qsTr('sendLogs') - - onClicked: sendLogsBlock.execute() - } - } - - RequestBlock { - id: sendLogsBlock - - action: CoreManager.sendLogs - width: parent.width - - - Connections { - target: CoreManager - - onLogsUploaded: Logic.handleLogsUploaded(url) - } - } - onVisibleChanged: sendLogsBlock.setText('') - // ------------------------------------------------------------------------- - // Developer settings. - // ------------------------------------------------------------------------- - - Form { - title: qsTr('developerSettingsTitle') - visible: SettingsModel.developerSettingsEnabled - width: parent.width - - FormLine { - FormGroup { - label: qsTr('developerSettingsEnabledLabel') - - Switch { - checked: SettingsModel.developerSettingsEnabled - - onClicked: SettingsModel.developerSettingsEnabled = !checked - } + onVisibleChanged: sendLogsBlock.setText('') + + // ------------------------------------------------------------------------- + // ADDRESS BOOK + // ------------------------------------------------------------------------- + + Form { + title: qsTr('contactsTitle') + width: parent.width + FormTable { + id:contactsImporterTable + width :parent.width + legendLineWidth:0 + disableLineTitle:importerRepeater.count!=1 + titles: (importerRepeater.count==1? getTitles(): []) + function getTitles(repeaterModel){ + var fields = importerRepeater.itemAt(0).pluginDescription['fields']; + var t = ['']; + for(var i = 0 ; i < fields.length ; ++i){ + t.push(fields[i]['placeholder']); + } + return t; + } + Repeater{ + id:importerRepeater + model:ContactsImporterListProxyModel{id:contactsImporterList} + + delegate : FormTable{ + //readonly property double maxItemWidth: contactsImporterTable.maxItemWidth + property var pluginDescription : importerLine.pluginDescription + width :parent.width + legendLineWidth:80 + disableLineTitle:true + FormTableLine { + id:importerLine + property var fields : modelData.fields + property int identity : modelData.identity + property var pluginDescription : ContactsImporterPluginsManager.getContactsImporterPluginDescription(fields["pluginID"]) // Plugin definition + + FormTableEntry { + Row{ + width :parent.width + ActionButton { + id:removeImporter + icon: 'cancel' + iconSize:CallsStyle.entry.iconActionSize + onClicked:ContactsImporterListModel.removeContactsImporter(modelData) + } + Text{ + height:parent.height + width:parent.width-removeImporter.width + text:importerLine.pluginDescription['pluginTitle'] + horizontalAlignment:Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + wrapMode: Text.WordWrap + font { + bold: true + pointSize: FormTableStyle.entry.text.pointSize + } + TooltipArea{ + text:importerLine.pluginDescription['pluginDescription'] + } + } + } + } + + Repeater{ + model:importerLine.pluginDescription['fields'] + delegate: FormTableEntry { + Loader{ + sourceComponent: (modelData['type']==0 ? textComponent:textFieldComponent) + active:true + width:parent.width + Component{ + id: textComponent + Text { + id: text + color: FormTableStyle.entry.text.color + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: importerLine.fields[modelData['fieldId']]?importerLine.fields[modelData['fieldId']]:'' + height: FormTableStyle.entry.height + width: parent.width + font { + bold: true + pointSize: FormTableStyle.entry.text.pointSize + } + } + } + Component{ + id: textFieldComponent + TextField { + readOnly: false + width:parent.width + placeholderText : modelData['placeholder'] + text: importerLine.fields[modelData['fieldId']]?importerLine.fields[modelData['fieldId']]:'' + echoMode: (modelData['hiddenText']?TextInput.Password:TextInput.Normal) + onEditingFinished:{ + importerLine.fields[modelData['fieldId']] = text + } + Component.onCompleted: importerLine.fields[modelData['fieldId']] = text + } + } + } + } + }// Repeater : Fields + FormTableEntry { + Switch { + checked: modelData.fields["enabled"]>0 + onClicked: { + checked = !checked + importerLine.fields["enabled"] = (checked?1:0) + ContactsImporterListModel.addContactsImporter(importerLine.fields, importerLine.identity) + + if(checked){ + ContactsImporterListModel.importContacts(importerLine.identity) + }else + contactsImporterStatus.text = '' + } + } + }//FormTableEntry + }//FormTableLine + FormTableLine{ + width:parent.width-parent.legendLineWidth + FormTableEntry { + id:contactsImporterStatusEntry + visible:contactsImporterStatus.text!=='' + width:parent.width + TextEdit{ + id:contactsImporterStatus + property bool isError:false + selectByMouse: true + readOnly:true + color: (isError?SettingsAdvancedStyle.error.color:SettingsAdvancedStyle.info.color) + width:parent.width + horizontalAlignment:Text.AlignRight + font { + italic: true + pointSize: SettingsAdvancedStyle.info.pointSize + } + Connections{ + target:modelData + onStatusMessage:{contactsImporterStatus.isError=false;contactsImporterStatus.text=message;} + onErrorMessage:{contactsImporterStatus.isError=true;contactsImporterStatus.text=message;} + } + } + } + } + }//Column + }// Repeater : Importer + + } + Row{ + spacing:SettingsAdvancedStyle.buttons.spacing + anchors.horizontalCenter: parent.horizontalCenter + ActionButton { + icon: 'options' + iconSize:CallsStyle.entry.iconActionSize + onClicked:{ + ContactsImporterPluginsManager.openNewPlugin(); + pluginChoice.model = ContactsImporterPluginsManager.getPlugins(); + } + } + ComboBox{ + id: pluginChoice + model:ContactsImporterPluginsManager.getPlugins() + textRole: "pluginTitle" + displayText: currentIndex === -1 ? 'No Plugins to load' : currentText + Text{// Hack, combobox show empty text when empty + anchors.fill:parent + visible:pluginChoice.currentIndex===-1 + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + text: 'No Plugins to load' + font { + bold:false + italic: true + pointSize: FormTableStyle.entry.text.pointSize + } + } + Connections{ + target:SettingsModel + onContactImporterChanged:pluginChoice.model=ContactsImporterPluginsManager.getPlugins() + } + } + ActionButton { + icon: 'add' + iconSize:CallsStyle.entry.iconActionSize + visible:pluginChoice.currentIndex>=0 + onClicked:{ + if( pluginChoice.currentIndex >= 0) + ContactsImporterListModel.createContactsImporter({"pluginID":pluginChoice.model[pluginChoice.currentIndex]["pluginID"]}) + } + } + } + } + + // ------------------------------------------------------------------------- + // Developer settings. + // ------------------------------------------------------------------------- + + Form { + title: qsTr('developerSettingsTitle') + visible: SettingsModel.developerSettingsEnabled + width: parent.width + + FormLine { + FormGroup { + label: qsTr('developerSettingsEnabledLabel') + + Switch { + checked: SettingsModel.developerSettingsEnabled + + onClicked: SettingsModel.developerSettingsEnabled = !checked + } + } + } } - } } - } } diff --git a/linphone-app/ui/views/App/Styles/Settings/SettingsAdvancedStyle.qml b/linphone-app/ui/views/App/Styles/Settings/SettingsAdvancedStyle.qml index 357fd0bbc..ef9dbb36f 100644 --- a/linphone-app/ui/views/App/Styles/Settings/SettingsAdvancedStyle.qml +++ b/linphone-app/ui/views/App/Styles/Settings/SettingsAdvancedStyle.qml @@ -1,10 +1,20 @@ pragma Singleton import QtQml 2.2 +import Colors 1.0 +import Units 1.0 // ============================================================================= QtObject { property QtObject buttons: QtObject { property int spacing: 10 } + + property QtObject error: QtObject { + property color color: Colors.error + } + property QtObject info: QtObject { + property color color: Colors.j + property int pointSize: Units.dp * 11 + } } diff --git a/linphone-sdk b/linphone-sdk index 47a49990b..4a3c4b2cb 160000 --- a/linphone-sdk +++ b/linphone-sdk @@ -1 +1 @@ -Subproject commit 47a49990b579ecbd5b01c81e6e8e2f00cd662a89 +Subproject commit 4a3c4b2cb39181eb2e3eaaed1617b2aa866c8d33 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 000000000..1a07fc2a7 --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,42 @@ +################################################################################ +# +# Copyright (c) 2017-2020 Belledonne Communications SARL. +# +# This file is part of linphone-desktop +# (see https://www.linphone.org). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################################################################################ +cmake_minimum_required(VERSION 3.1) + +## Add custom plugins +macro(get_all_subdirs result curdir) + file(GLOB children RELATIVE ${curdir} ${curdir}/*) + set(dirlist "") + foreach(child ${children}) + if(IS_DIRECTORY ${curdir}/${child} AND (ENABLE_BUILD_EXAMPLES OR NOT ${child} MATCHES "example")) + list(APPEND dirlist ${child}) + endif() + endforeach() + set(${result} ${dirlist}) +endmacro() +get_all_subdirs(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) + +set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${CMAKE_INSTALL_PREFIX}/include") + +foreach(subdir ${SUBDIRS}) + message("Adding ${subdir} plugin") + add_subdirectory(${subdir}) +endforeach() diff --git a/plugins/example/CMakeLists.txt b/plugins/example/CMakeLists.txt new file mode 100644 index 000000000..7b90d3958 --- /dev/null +++ b/plugins/example/CMakeLists.txt @@ -0,0 +1,133 @@ +################################################################################ +# +# Copyright (c) 2017-2020 Belledonne Communications SARL. +# +# This file is part of linphone-desktop +# (see https://www.linphone.org). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################################################################################ +cmake_minimum_required(VERSION 3.1) +#------------------------------------------------- +# Customizable data +set(SOURCES + src/Plugin.cpp + src/NetworkAPI.cpp + src/DataAPI.cpp + ) + +set(HEADERS + src/Plugin.hpp + src/NetworkAPI.hpp + src/DataAPI.hpp + ) + +list(APPEND SOURCES + src/PluginMetaData.json) + +set(TARGET_NAME linphonePluginExample ) +#------------------------------------------------- + +find_package(bctoolbox CONFIG) +set(FULL_VERSION ) +bc_compute_full_version(FULL_VERSION) +set(version_major ) +set(version_minor ) +set(version_patch ) +set(identifiers ) +set(metadata ) +bc_parse_full_version("${FULL_VERSION}" version_major version_minor version_patch identifiers metadata) +set(PLUGIN_VERSION "${version_major}.${version_minor}.${version_patch}") + +project(${TARGET_NAME} VERSION ${PLUGIN_VERSION}) + +include(GNUInstallDirs) +include(CheckCXXCompilerFlag) + +message("${TARGET_NAME} version : ${PLUGIN_VERSION}") + +set(CMAKE_CXX_STANDARD 11) +SET_PROPERTY(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS true) + +if(UNIX AND NOT APPLE) + set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/lib64;$ORIGIN/../lib64;$ORIGIN/lib;$ORIGIN/../lib") + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +endif() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") +list(APPEND CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/include) + +if(WIN32) + set(EXECUTABLE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_DIR} ) + set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_DIR} ) + set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_DIR} ) + foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )# Apply to all configurations + string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) + set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${EXECUTABLE_OUTPUT_DIR} ) + set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${EXECUTABLE_OUTPUT_DIR} ) + set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${EXECUTABLE_OUTPUT_DIR} ) + endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) +endif() + +# Build configuration +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -DQT_NO_DEBUG") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG" ) + +if( WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WINSOCKAPI_")#remove error from windows headers order +endif() +set(CMAKE_INCLUDE_CURRENT_DIR ON)#useful for config.h + +set(QT5_PACKAGES Core Widgets Network) + +set(CMAKE_AUTOMOC ON) + +find_package(Qt5 COMPONENTS ${QT5_PACKAGES} REQUIRED) +find_package(Qt5 COMPONENTS ${QT5_PACKAGES_OPTIONAL} QUIET) + +find_package(LinphoneCxx CONFIG) +find_package(bctoolbox CONFIG) + + +# ------------------------------------------------------------------------------ +# Build. +# ------------------------------------------------------------------------------ + +add_library(${TARGET_NAME} SHARED ${SOURCES} ${HEADERS}) + +target_link_libraries(${TARGET_NAME} PRIVATE Qt5::Widgets Qt5::Network ${LINPHONECXX_LIBRARIES}) +target_compile_options(${TARGET_NAME} PRIVATE ${COMPILE_OPTIONS}) +set_source_files_properties( ${TARGET_NAME} PROPERTIES EXTERNAL_OBJECT true GENERATED true ) +set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) #Need by Qt +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_PREFIX_PATH} ${LINPHONECXX_INCLUDE_DIRS}) +set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME "${TARGET_NAME}-${PLUGIN_VERSION}") + +#------------------------------------------------------------------------------- +# IDE +#------------------------------------------------------------------------------- + +source_group( + "Json" REGULAR_EXPRESSION ".+\.json$" +) + +#------------------------------------------------------------------------------- +# Install. +#------------------------------------------------------------------------------- +set(LINPHONE_APP_CONTACT_PLUGINS_PATH "plugins/app") +install(TARGETS ${TARGET_NAME} DESTINATION "${LINPHONE_APP_CONTACT_PLUGINS_PATH}") + + + diff --git a/plugins/example/src/DataAPI.cpp b/plugins/example/src/DataAPI.cpp new file mode 100644 index 000000000..39eed0965 --- /dev/null +++ b/plugins/example/src/DataAPI.cpp @@ -0,0 +1,157 @@ +#include "DataAPI.hpp" +#include "NetworkAPI.hpp" +#include "Plugin.hpp" + +#include +#include +#include + +DataAPI::DataAPI(Plugin *plugin, void * core, QPluginLoader * pluginLoader) :PluginDataAPI(plugin, core, pluginLoader){ + auto proxyConfig = static_cast(mLinphoneCore)->getDefaultProxyConfig(); + QVariantMap account; + std::string domain; + if(proxyConfig) + domain = proxyConfig->getDomain(); + else{ + proxyConfig = static_cast(mLinphoneCore)->createProxyConfig(); + if(proxyConfig) + domain = proxyConfig->getDomain(); + if(domain == "") + domain = "sip.linphone.org"; + } + mInputFields[CONTACTS]["SIP_Domain"] = QString::fromLocal8Bit(domain.c_str(), int(domain.size())); +} + +QString DataAPI::getUrl()const{ + return mInputFields[CONTACTS]["URL"].toString(); +} +QString DataAPI::getDomain()const{ + return mInputFields[CONTACTS]["SIP_Domain"].toString(); +} +QString DataAPI::getUsername()const{ + return mInputFields[CONTACTS]["Username"].toString(); +} +QString DataAPI::getPassword()const{ + return mInputFields[CONTACTS]["Password"].toString(); +} +QString DataAPI::getKey()const{ + return mInputFields[CONTACTS]["Key"].toString(); +} +bool DataAPI::isEnabled()const{ + return mInputFields[CONTACTS]["enabled"].toInt()>0; +} +void DataAPI::setPassword(const QString &password){ + mInputFields[CONTACTS]["Password"] = password; +} + +bool DataAPI::isValid(const bool &pRequestData, QString * pError){ + QStringList errors; + if( getDomain().isEmpty()) + errors << "Domain is empty."; + if( getUrl().isEmpty()) + errors << "Url is empty."; + if( getUsername().isEmpty()) + errors << "Username is empty."; + if( getPassword().isEmpty() && getKey().isEmpty()){ + if(pRequestData) + setPassword(QInputDialog::getText(nullptr, "Linphone example Address Book","Password",QLineEdit::EchoMode::Password)); + if( getPassword().isEmpty()) + errors << "Password is empty."; + } + if( errors.size() > 0){ + if(pError) + *pError = "Data is invalid : " + errors.join(" "); + return false; + }else + return true; +} + +QMap DataAPI::getInputFieldsToSave(const PluginCapability& capability){// Remove Password from config file + QMap data = mInputFields; + data[CONTACTS].remove("Password"); + return data; +} + +void DataAPI::run(const PluginCapability& actionType){ + if( actionType == PluginCapability::CONTACTS){ + NetworkAPI * network = new NetworkAPI(this); + QObject::connect(this, &PluginDataAPI::dataReceived, network, &DataAPI::deleteLater); + network->startRequest(); + } +} +//----------------------------------------------------------------------------------------- + +void DataAPI::parse(const QByteArray& p_data){ + QVector > parsedData; + QString statusText; + if(!p_data.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(p_data); + QJsonObject responses = doc.object(); + QString status = responses["status"].toString(); + QString comment = responses["comment"].toString(); + if( responses.size() == 0){ + statusText = "Contacts are not in Json format."; + }else if( status != "OK"){ + statusText = status; + if( statusText.isEmpty()) + statusText = "Cannot parse the request: The URL may not be valid."; + if(!comment.isEmpty()) + statusText += " "+comment; + if( mInputFields[CONTACTS].contains("Key")){ + QVariantMap newInputs = mInputFields[CONTACTS]; + newInputs.remove("Key");// Reset key on error + setInputFields(CONTACTS, newInputs); + } + }else{ + if( responses.contains("key")){ + QVariantMap newInputs = mInputFields[CONTACTS]; + newInputs["Key"] = responses["key"].toString(); + setInputFields(CONTACTS, newInputs); + } + if( responses.contains("contacts")){ + QJsonArray contacts = responses["contacts"].toArray(); + int contactCount = 0; + for(int i = 0 ; i < contacts.size() ; ++i){ + QMultiMap cardData; + QJsonObject contact = contacts[i].toObject(); + QString phoneNumber = contact["number"].toString(); + QStringList name; + bool haveData = false; + QString company = contact["company"].toString(); + + + if( contact.contains("firstname") && contact["firstname"].toString() != "") + name << contact["firstname"].toString(); + if( contact.contains("surname") && contact["surname"].toString() != "") + name << contact["surname"].toString(); + + if(name.size() > 0){ + QString username = name.join(" "); + cardData.insert("displayName", username); + } + if(!phoneNumber.isEmpty()) { + cardData.insert("phoneNumber", phoneNumber); + cardData.insert("sipUsername", phoneNumber); + haveData = true; + } + if(!company.isEmpty()) + cardData.insert("organization", company); + if( haveData){ + cardData.insert("sipDomain", mInputFields[CONTACTS]["SIP_Domain"].toString()); + parsedData.push_back(cardData); + ++contactCount; + } + } + QString messageStatus = QString::number(contactCount) +" contact"+(contactCount>1?"s":"")+" have been synchronized at "+QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); + emit message(QtInfoMsg, messageStatus); + qInfo() << messageStatus; + } + } + }else + statusText = "Cannot parse the request: The URL may not be valid."; + if( !statusText.isEmpty()) + emit message(QtWarningMsg, statusText); + emit dataReceived(PluginDataAPI::CONTACTS, parsedData); +} + + diff --git a/plugins/example/src/DataAPI.hpp b/plugins/example/src/DataAPI.hpp new file mode 100644 index 000000000..3e226994c --- /dev/null +++ b/plugins/example/src/DataAPI.hpp @@ -0,0 +1,43 @@ +#ifndef DATAAPI_HPP +#define DATAAPI_HPP + +#include +#include +#include + +#include +#include + +class Plugin; +class QPluginLoader; + +// Example of address book importer + +class DataAPI : public PluginDataAPI +{ +Q_OBJECT +public: + DataAPI(Plugin *plugin, void *core, QPluginLoader * pluginLoader); + virtual ~DataAPI(){} + + QString getUrl()const; + QString getDomain()const; + QString getUsername()const; + QString getPassword()const; + QString getKey()const; + bool isEnabled()const; + + void setPassword(const QString &password); + + virtual bool isValid(const bool &requestData, QString * pError= nullptr);// Test data and send signal. Used to get feedback + + virtual QMap getInputFieldsToSave(const PluginCapability& capability); + + virtual void run(const PluginCapability& actionType); +public slots: + virtual void parse(const QByteArray& p_data); +signals: + void inputFieldsChanged(const PluginCapability& capability, const QVariantMap &inputs); // The plugin made updates on input +}; + +#endif // DATAAPI_HPP diff --git a/plugins/example/src/NetworkAPI.cpp b/plugins/example/src/NetworkAPI.cpp new file mode 100644 index 000000000..b5ce48b76 --- /dev/null +++ b/plugins/example/src/NetworkAPI.cpp @@ -0,0 +1,58 @@ +#include "NetworkAPI.hpp" +#include "DataAPI.hpp" + +#include +#include + + +NetworkAPI::NetworkAPI(DataAPI * data) : mData(data){ + if(mData ) { + connect(this, SIGNAL(requestFinished(const QByteArray&)), mData, SLOT(parse(const QByteArray&))); + connect(this, &NetworkAPI::message, mData, &DataAPI::message); + } +} + +NetworkAPI::~NetworkAPI(){ +} + +bool NetworkAPI::isEnabled()const{ + return mData && mData->isEnabled(); +} +bool NetworkAPI::isValid(PluginDataAPI * pData, const bool &pShowError){ + QString errorMessage; + DataAPI * data = dynamic_cast(pData); + bool ok = data; + if(!ok) + errorMessage = "These data are invalid"; + else + ok = pData->isValid(true, &errorMessage); + if(!ok && pShowError){ + qWarning() << errorMessage; + emit message(QtMsgType::QtWarningMsg, errorMessage); + } + return ok; +} +//----------------------------------------------------------------------------------------- + +QString NetworkAPI::prepareRequest()const{ + QString url = mData->getUrl()+"?user="+mData->getUsername()+"&"; + if( mData->getKey() != "") + url += "key="+mData->getKey(); + else + url += "password="+mData->getPassword(); + return url; +} + +void NetworkAPI::startRequest() { + bool doRequest = false; + if(isValid(mData)){ + if(isEnabled()){ + mCurrentStep=0; + doRequest = true; + } + } + if(doRequest) + request(); + else + mData->parse(QByteArray()); +} diff --git a/plugins/example/src/NetworkAPI.hpp b/plugins/example/src/NetworkAPI.hpp new file mode 100644 index 000000000..7e74a0791 --- /dev/null +++ b/plugins/example/src/NetworkAPI.hpp @@ -0,0 +1,33 @@ +#ifndef NETWORKAPI_HPP +#define NETWORKAPI_HPP + +#include +#include + + +#include + +class DataAPI; +class PluginDataAPI; +// Interface between Network API and Data. +class NetworkAPI : public PluginNetworkHelper +{ +Q_OBJECT +public: + NetworkAPI(DataAPI * data); + virtual ~NetworkAPI(); + + bool isEnabled()const; // Interface to test if data is enabled + bool isValid(PluginDataAPI * pData, const bool &pShowError = true);// Test if data is valid + + virtual QString prepareRequest()const;// Prepare request for URL + + void startRequest(); + +// Data + DataAPI * mData; + int mCurrentStep; + +}; + +#endif // NETWORKAPI_HPP diff --git a/plugins/example/src/Plugin.cpp b/plugins/example/src/Plugin.cpp new file mode 100644 index 000000000..3d06f14a6 --- /dev/null +++ b/plugins/example/src/Plugin.cpp @@ -0,0 +1,76 @@ +/****************************************************************************** +* +* Copyright (c) 2017-2020 Belledonne Communications SARL. +* +* This file is part of linphone-desktop +* (see https://www.linphone.org). +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*******************************************************************************/ +#include "Plugin.hpp" +#include +#include + +#include "DataAPI.hpp" +#include "NetworkAPI.hpp" + +QString Plugin::getGUIDescriptionToJson()const{ + QJsonObject description; + description["pluginTitle"] = "Plugin Example"; + description["pluginDescription"] = "This is a test plugin to import an address book from an URL"; + + QJsonObject field; + QJsonArray fields; + field["placeholder"] = "SIP Domain"; + field["fieldId"] = "SIP_Domain"; + field["defaultData"] = ""; // Set by the Data instance from Core + field["type"] = 1; + field["capability"] = PluginDataAPI::CONTACTS; + fields.append(field); + + field = QJsonObject(); + field["placeholder"] = "URL"; + field["fieldId"] = "URL"; + field["defaultData"] = ""; + field["type"] = 1; + field["capability"] = PluginDataAPI::CONTACTS; + fields.append(field); + + field = QJsonObject(); + field["placeholder"] = "Username"; + field["fieldId"] = "Username"; + field["defaultData"] = "username@domain.com"; + field["type"] = 1; + field["capability"] = PluginDataAPI::CONTACTS; + fields.append(field); + + field = QJsonObject(); + field["placeholder"] = "Password"; + field["fieldId"] = "Password"; + field["defaultData"] = "This is a pass"; + field["type"] = 1; + field["hiddenText"] = true; + field["capability"] = PluginDataAPI::CONTACTS; + fields.append(field); + + description["fields"] = fields; + + QJsonDocument document(description); + return document.toJson(); +} + +PluginDataAPI * Plugin::createInstance(void * core, QPluginLoader *pluginLoader){ + return new DataAPI(this, core, pluginLoader); +} diff --git a/plugins/example/src/Plugin.hpp b/plugins/example/src/Plugin.hpp new file mode 100644 index 000000000..81bc680c6 --- /dev/null +++ b/plugins/example/src/Plugin.hpp @@ -0,0 +1,45 @@ +/****************************************************************************** +* +* Copyright (c) 2017-2020 Belledonne Communications SARL. +* +* This file is part of linphone-desktop +* (see https://www.linphone.org). +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*******************************************************************************/ +#ifndef PLUGIN_HPP +#define PLUGIN_HPP + +#include +#include +#include +#include +#include +//----------------------------- + +class QPluginLoader; + +class Plugin : public QObject, public LinphonePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID LinphonePlugin_iid FILE "PluginMetaData.json") + Q_INTERFACES(LinphonePlugin) +public: + Plugin(){} + virtual QString getGUIDescriptionToJson() const; + virtual PluginDataAPI * createInstance(void* core, QPluginLoader * pluginLoader); +}; + +#endif diff --git a/plugins/example/src/PluginMetaData.json b/plugins/example/src/PluginMetaData.json new file mode 100644 index 000000000..91612443f --- /dev/null +++ b/plugins/example/src/PluginMetaData.json @@ -0,0 +1,6 @@ +{ + "ID" : "ExamplePlugin. This ID must be unique from all plugins.", + "Version" : "1.0.0", + "Capabilities" : "Contacts", + "Description" : "This is an example for describing your plugin. Replace all fields above to be usable by the Application." +}