history-service-0.1+16.04.20160104/0000755000015600001650000000000012642552414017054 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/cmake/0000755000015600001650000000000012642552414020134 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/cmake/modules/0000755000015600001650000000000012642552414021604 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/cmake/modules/EnableCoverageReport.cmake0000644000015600001650000001531112642552153026645 0ustar pbuserpbgroup00000000000000# - Creates a special coverage build type and target on GCC. # # Defines a function ENABLE_COVERAGE_REPORT which generates the coverage target # for selected targets. Optional arguments to this function are used to filter # unwanted results using globbing expressions. Moreover targets with tests for # the source code can be specified to trigger regenerating the report if the # test has changed # # ENABLE_COVERAGE_REPORT(TARGETS target... [FILTER filter...] [TESTS test targets...]) # # To generate a coverage report first build the project with # CMAKE_BUILD_TYPE=coverage, then call make test and afterwards make coverage. # # The coverage report is based on gcov. Depending on the availability of lcov # a HTML report will be generated and/or an XML report of gcovr is found. # The generated coverage target executes all found solutions. Special targets # exist to create e.g. only the xml report: coverage-xml. # # Copyright (C) 2010 by Johannes Wienke # # 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 2, 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. # INCLUDE(ParseArguments) FIND_PACKAGE(Lcov) FIND_PACKAGE(gcovr) FUNCTION(ENABLE_COVERAGE_REPORT) # argument parsing PARSE_ARGUMENTS(ARG "FILTER;TARGETS;TESTS" "" ${ARGN}) SET(COVERAGE_RAW_FILE "${CMAKE_BINARY_DIR}/coverage.raw.info") SET(COVERAGE_FILTERED_FILE "${CMAKE_BINARY_DIR}/coverage.info") SET(COVERAGE_REPORT_DIR "${CMAKE_BINARY_DIR}/coveragereport") SET(COVERAGE_XML_FILE "${CMAKE_BINARY_DIR}/coverage.xml") SET(COVERAGE_XML_COMMAND_FILE "${CMAKE_BINARY_DIR}/coverage-xml.cmake") # decide if there is any tool to create coverage data SET(TOOL_FOUND FALSE) IF(LCOV_FOUND OR GCOVR_FOUND) SET(TOOL_FOUND TRUE) ENDIF() IF(NOT TOOL_FOUND) MESSAGE(STATUS "Cannot enable coverage targets because neither lcov nor gcovr are found.") ENDIF() STRING(TOLOWER "${CMAKE_BUILD_TYPE}" COVERAGE_BUILD_TYPE) IF(CMAKE_COMPILER_IS_GNUCXX AND TOOL_FOUND AND "${COVERAGE_BUILD_TYPE}" MATCHES "coverage") MESSAGE(STATUS "Coverage support enabled for targets: ${ARG_TARGETS}") # create coverage build type SET(CMAKE_CXX_FLAGS_COVERAGE ${CMAKE_CXX_FLAGS_DEBUG} PARENT_SCOPE) SET(CMAKE_C_FLAGS_COVERAGE ${CMAKE_C_FLAGS_DEBUG} PARENT_SCOPE) SET(CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES} coverage PARENT_SCOPE) # instrument targets SET_TARGET_PROPERTIES(${ARG_TARGETS} PROPERTIES COMPILE_FLAGS --coverage LINK_FLAGS --coverage) # html report IF (LCOV_FOUND) MESSAGE(STATUS "Enabling HTML coverage report") # set up coverage target ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_RAW_FILE} COMMAND ${LCOV_EXECUTABLE} -c -d ${CMAKE_BINARY_DIR} -o ${COVERAGE_RAW_FILE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Collecting coverage data" DEPENDS ${ARG_TARGETS} ${ARG_TESTS} VERBATIM) # filter unwanted stuff LIST(LENGTH ARG_FILTER FILTER_LENGTH) IF(${FILTER_LENGTH} GREATER 0) SET(FILTER COMMAND ${LCOV_EXECUTABLE}) FOREACH(F ${ARG_FILTER}) SET(FILTER ${FILTER} -r ${COVERAGE_FILTERED_FILE} ${F}) ENDFOREACH() SET(FILTER ${FILTER} -o ${COVERAGE_FILTERED_FILE}) ELSE() SET(FILTER "") ENDIF() ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_FILTERED_FILE} COMMAND ${LCOV_EXECUTABLE} -e ${COVERAGE_RAW_FILE} "${CMAKE_SOURCE_DIR}*" -o ${COVERAGE_FILTERED_FILE} ${FILTER} DEPENDS ${COVERAGE_RAW_FILE} COMMENT "Filtering recorded coverage data for project-relevant entries" VERBATIM) ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_REPORT_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${COVERAGE_REPORT_DIR} COMMAND ${GENHTML_EXECUTABLE} --legend --show-details -t "${PROJECT_NAME} test coverage" -o ${COVERAGE_REPORT_DIR} ${COVERAGE_FILTERED_FILE} DEPENDS ${COVERAGE_FILTERED_FILE} COMMENT "Generating HTML coverage report in ${COVERAGE_REPORT_DIR}" VERBATIM) ADD_CUSTOM_TARGET(coverage-html DEPENDS ${COVERAGE_REPORT_DIR}) ENDIF() # xml coverage report IF(GCOVR_FOUND) MESSAGE(STATUS "Enabling XML coverage report") # gcovr cannot write directly to a file so the execution needs to # be wrapped in a cmake file that generates the file output FILE(WRITE ${COVERAGE_XML_COMMAND_FILE} "SET(ENV{LANG} en)\n") FILE(APPEND ${COVERAGE_XML_COMMAND_FILE} "EXECUTE_PROCESS(COMMAND \"${GCOVR_EXECUTABLE}\" -x -r \"${CMAKE_SOURCE_DIR}\" OUTPUT_FILE \"${COVERAGE_XML_FILE}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n") ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_XML_FILE} COMMAND ${CMAKE_COMMAND} ARGS -P ${COVERAGE_XML_COMMAND_FILE} COMMENT "Generating coverage XML report" VERBATIM) ADD_CUSTOM_TARGET(coverage-xml DEPENDS ${COVERAGE_XML_FILE}) ENDIF() # provide a global coverage target executing both steps if available SET(GLOBAL_DEPENDS "") IF(LCOV_FOUND) LIST(APPEND GLOBAL_DEPENDS ${COVERAGE_REPORT_DIR}) ENDIF() IF(GCOVR_FOUND) LIST(APPEND GLOBAL_DEPENDS ${COVERAGE_XML_FILE}) ENDIF() IF(LCOV_FOUND OR GCOVR_FOUND) ADD_CUSTOM_TARGET(coverage DEPENDS ${GLOBAL_DEPENDS}) ENDIF() ENDIF() ENDFUNCTION() history-service-0.1+16.04.20160104/cmake/modules/Findgcovr.cmake0000644000015600001650000000170212642552153024527 0ustar pbuserpbgroup00000000000000# - Find gcovr scrip # Will define: # # GCOVR_EXECUTABLE - the gcovr script # # Uses: # # GCOVR_ROOT - root to search for the script # # Copyright (C) 2011 by Johannes Wienke # # 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 2, 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. # INCLUDE(FindPackageHandleStandardArgs) FIND_PROGRAM(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin") FIND_PACKAGE_HANDLE_STANDARD_ARGS(gcovr DEFAULT_MSG GCOVR_EXECUTABLE) # only visible in advanced view MARK_AS_ADVANCED(GCOVR_EXECUTABLE) history-service-0.1+16.04.20160104/cmake/modules/GenerateTest.cmake0000644000015600001650000001323712642552153025206 0ustar pbuserpbgroup00000000000000# # Copyright (C) 2015 Canonical, Ltd. # # Authors: # Gustavo Pichorim Boiko # # This file is part of history-service. # # history-service 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; version 3. # # history-service 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(CMakeParseArguments) find_program(DBUS_RUNNER dbus-test-runner) find_program(XVFB_RUN_BIN NAMES xvfb-run ) function(generate_test TESTNAME) set(options USE_DBUS USE_UI USE_XVFB) set(oneValueArgs TIMEOUT WORKING_DIRECTORY QML_TEST WAIT_FOR) set(multiValueArgs TASKS LIBRARIES QT5_MODULES SOURCES ENVIRONMENT) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) MESSAGE(STATUS "Adding test: ${TESTNAME}") # set reasonable defaults for the arguments if (NOT DEFINED ARG_WORKING_DIRECTORY) set(ARG_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) endif () if (NOT DEFINED ARG_TIMEOUT) set(ARG_TIMEOUT 60) endif () if (NOT DEFINED ARG_QT5_MODULES) set(ARG_QT5_MODULES Core Test) endif () if (${ARG_USE_UI}) if (${ARG_USE_DBUS}) set(PLATFORM -p -platform -p offscreen) else () set(PLATFORM -platform offscreen) endif () endif() # Generate QML tests if (DEFINED ARG_QML_TEST) add_test(NAME ${TESTNAME} WORKING_DIRECTORY ${ARG_WORKING_DIRECTORY} COMMAND qmltestrunner -platform offscreen -import ${CMAKE_BINARY_DIR} -input ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_QML_TEST}) set_tests_properties(${TESTNAME} PROPERTIES ENVIRONMENT "QT_QPA_FONTDIR=${CMAKE_BINARY_DIR}") else () # For sanity checking, make sure DBUS_RUNNER is available for DBUS tests if (${ARG_USE_DBUS} AND "${DBUS_RUNNER}" STREQUAL "") message(WARNING "Test ${TESTNAME} disabled because dbus-test-runner was not found.") return() endif () # No QML test, regular binary compiled test. add_executable(${TESTNAME} ${ARG_SOURCES}) qt5_use_modules(${TESTNAME} ${ARG_QT5_MODULES}) if (${ARG_USE_DBUS}) execute_process(COMMAND mktemp -d OUTPUT_VARIABLE TMPDIR) string(REPLACE "\n" "" TMPDIR ${TMPDIR}) if (NOT DEFINED ARG_ENVIRONMENT) set(ARG_ENVIRONMENT HOME=${TMPDIR} HISTORY_PLUGIN_PATH=${CMAKE_BINARY_DIR}/plugins/sqlite HISTORY_SQLITE_DBPATH=:memory: MC_ACCOUNT_DIR=${TMPDIR} MC_MANAGER_DIR=${TMPDIR}) endif () if (${ARG_USE_XVFB}) SET(XVFB_RUN ${XVFB_RUN_BIN} -a -s "-screen 0 1024x768x24") endif () set(TEST_COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME} ${PLATFORM} -p -o -p -,txt -p -o -p ${CMAKE_BINARY_DIR}/test_${TESTNAME}.xml,xunitxml) if (DEFINED ARG_WAIT_FOR) SET(TEST_COMMAND ${TEST_COMMAND} --wait-for ${ARG_WAIT_FOR}) endif () add_test(${TESTNAME} ${XVFB_RUN} ${DBUS_RUNNER} --keep-env --dbus-config=${CMAKE_BINARY_DIR}/tests/common/dbus-session.conf --max-wait=${ARG_TIMEOUT} ${ARG_TASKS} --task ${TEST_COMMAND} --task-name ${TESTNAME}) else () add_test(${TESTNAME} ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME} ${PLATFORM} -o -,txt -o ${CMAKE_BINARY_DIR}/test_${TESTNAME}.xml,xunitxml) endif() set_tests_properties(${TESTNAME} PROPERTIES ENVIRONMENT "${ARG_ENVIRONMENT}" TIMEOUT ${ARG_TIMEOUT}) if (DEFINED ARG_LIBRARIES) target_link_libraries(${TESTNAME} ${ARG_LIBRARIES}) endif () enable_coverage(${TESTNAME}) endif () endfunction(generate_test) function(generate_telepathy_test TESTNAME) set(options "") set(oneValueArgs "") set(multiValueArgs TASKS LIBRARIES QT5_MODULES) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) set(TASKS --task gnome-keyring-daemon -p -r -p -d --task-name gnome-keyring --ignore-return --task /usr/lib/dconf/dconf-service --task-name dconf-service --ignore-return --task dconf -p write -p /org/gnome/empathy/use-conn -p false --task-name dconf-write --wait-for ca.desrt.dconf --ignore-return --task /usr/lib/telepathy/mission-control-5 --task-name mission-control --wait-for ca.desrt.dconf --ignore-return --task ${CMAKE_BINARY_DIR}/tests/common/mock/telepathy-mock --task-name telepathy-mock --wait-for org.freedesktop.Telepathy.MissionControl5 --ignore-return ${ARG_TASKS}) if (NOT DEFINED ARG_LIBRARIES) set(ARG_LIBRARIES ${TP_QT5_LIBRARIES} historyservice mockcontroller telepathytest) endif(NOT DEFINED ARG_LIBRARIES) if (NOT DEFINED ARG_QT5_MODULES) set(ARG_QT5_MODULES Core DBus Test Qml) endif (NOT DEFINED ARG_QT5_MODULES) generate_test(${TESTNAME} ${ARGN} TASKS ${TASKS} LIBRARIES ${ARG_LIBRARIES} QT5_MODULES ${ARG_QT5_MODULES} USE_DBUS) endfunction(generate_telepathy_test) history-service-0.1+16.04.20160104/cmake/modules/qt5.cmake0000644000015600001650000000310612642552153023317 0ustar pbuserpbgroup00000000000000# shamelessly copied over from oxide’s build system # to enable ARM cross compilation if(CMAKE_CROSSCOMPILING) # QT_MOC_EXECUTABLE is set by Qt5CoreConfigExtras, but it sets it to # the target executable rather than the host executable, which is no # use for cross-compiling. For cross-compiling, we have a guess and # override it ourselves if(NOT TARGET Qt5::moc) find_program( QT_MOC_EXECUTABLE moc PATHS /usr/lib/qt5/bin /usr/lib/${HOST_ARCHITECTURE}/qt5/bin NO_DEFAULT_PATH) if(QT_MOC_EXECUTABLE STREQUAL "QT_MOC_EXECUTABLE-NOTFOUND") message(FATAL_ERROR "Can't find a moc executable for the host arch") endif() add_executable(Qt5::moc IMPORTED) set_target_properties(Qt5::moc PROPERTIES IMPORTED_LOCATION "${QT_MOC_EXECUTABLE}") endif() # Dummy targets - not used anywhere, but this stops Qt5CoreConfigExtras.cmake # from creating them and checking if the binary exists, which is broken when # cross-building because it checks for the target system binary. We need the # host system binaries installed, because they are in the same package as the # moc in Ubuntu (qtbase5-dev-tools), which is not currently multi-arch if(NOT TARGET Qt5::qmake) add_executable(Qt5::qmake IMPORTED) endif() if(NOT TARGET Qt5::rcc) add_executable(Qt5::rcc IMPORTED) endif() if(NOT TARGET Qt5::uic) add_executable(Qt5::uic IMPORTED) endif() if(NOT TARGET Qt5::DBus) add_executable(Qt5::DBus IMPORTED) endif() else() # This should be enough to initialize QT_MOC_EXECUTABLE find_package(Qt5Core) endif() history-service-0.1+16.04.20160104/cmake/modules/FindLibPhoneNumber.cmake0000644000015600001650000000141412642552153026260 0ustar pbuserpbgroup00000000000000set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) include(GNUInstallDirs) include(LibFindMacros) # Include dir find_path(LibPhoneNumber_INCLUDE_DIR NAMES phonenumberutil.h PATHS "/usr/local/${CMAKE_INSTALL_INCLUDEDIR}" ${CMAKE_INSTALL_FULL_INCLUDEDIR} PATH_SUFFIXES "phonenumbers" ) # library itself find_library(LibPhoneNumber_LIBRARY NAMES phonenumber PATHS "/usr/local/${CMAKE_INSTALL_LIBDIR}" ${CMAKE_INSTALL_FULL_LIBDIR} ) # Set the include dir variables and the libraries and let libfind_process do the rest. # NOTE: Singular variables for this library, plural for libraries this this lib depends on. set(LibPhoneNumber_PROCESS_INCLUDES LibPhoneNumber_INCLUDE_DIR) set(LibPhoneNumber_PROCESS_LIBS LibPhoneNumber_LIBRARY) libfind_process(LibPhoneNumber) history-service-0.1+16.04.20160104/cmake/modules/ParseArguments.cmake0000644000015600001650000000340612642552153025551 0ustar pbuserpbgroup00000000000000# Parse arguments passed to a function into several lists separated by # upper-case identifiers and options that do not have an associated list e.g.: # # SET(arguments # hello OPTION3 world # LIST3 foo bar # OPTION2 # LIST1 fuz baz # ) # PARSE_ARGUMENTS(ARG "LIST1;LIST2;LIST3" "OPTION1;OPTION2;OPTION3" ${arguments}) # # results in 7 distinct variables: # * ARG_DEFAULT_ARGS: hello;world # * ARG_LIST1: fuz;baz # * ARG_LIST2: # * ARG_LIST3: foo;bar # * ARG_OPTION1: FALSE # * ARG_OPTION2: TRUE # * ARG_OPTION3: TRUE # # taken from http://www.cmake.org/Wiki/CMakeMacroParseArguments MACRO(PARSE_ARGUMENTS prefix arg_names option_names) SET(DEFAULT_ARGS) FOREACH(arg_name ${arg_names}) SET(${prefix}_${arg_name}) ENDFOREACH(arg_name) FOREACH(option ${option_names}) SET(${prefix}_${option} FALSE) ENDFOREACH(option) SET(current_arg_name DEFAULT_ARGS) SET(current_arg_list) FOREACH(arg ${ARGN}) SET(larg_names ${arg_names}) LIST(FIND larg_names "${arg}" is_arg_name) IF (is_arg_name GREATER -1) SET(${prefix}_${current_arg_name} ${current_arg_list}) SET(current_arg_name ${arg}) SET(current_arg_list) ELSE (is_arg_name GREATER -1) SET(loption_names ${option_names}) LIST(FIND loption_names "${arg}" is_option) IF (is_option GREATER -1) SET(${prefix}_${arg} TRUE) ELSE (is_option GREATER -1) SET(current_arg_list ${current_arg_list} ${arg}) ENDIF (is_option GREATER -1) ENDIF (is_arg_name GREATER -1) ENDFOREACH(arg) SET(${prefix}_${current_arg_name} ${current_arg_list}) ENDMACRO(PARSE_ARGUMENTS) history-service-0.1+16.04.20160104/cmake/modules/LibFindMacros.cmake0000644000015600001650000001106012642552153025260 0ustar pbuserpbgroup00000000000000# Version 1.0 (2013-04-12) # Public Domain, originally written by Lasse Kärkkäinen # Published at http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries # If you improve the script, please modify the forementioned wiki page because # I no longer maintain my scripts (hosted as static files at zi.fi). Feel free # to remove this entire header if you use real version control instead. # Changelog: # 2013-04-12 Added version number (1.0) and this header, no other changes # 2009-10-08 Originally published # Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments # used for the current package. For this to work, the first parameter must be the # prefix of the current package, then the prefix of the new package etc, which are # passed to find_package. macro (libfind_package PREFIX) set (LIBFIND_PACKAGE_ARGS ${ARGN}) if (${PREFIX}_FIND_QUIETLY) set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET) endif (${PREFIX}_FIND_QUIETLY) if (${PREFIX}_FIND_REQUIRED) set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED) endif (${PREFIX}_FIND_REQUIRED) find_package(${LIBFIND_PACKAGE_ARGS}) endmacro (libfind_package) # CMake developers made the UsePkgConfig system deprecated in the same release (2.6) # where they added pkg_check_modules. Consequently I need to support both in my scripts # to avoid those deprecated warnings. Here's a helper that does just that. # Works identically to pkg_check_modules, except that no checks are needed prior to use. macro (libfind_pkg_check_modules PREFIX PKGNAME) if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) include(UsePkgConfig) pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS) else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_check_modules(${PREFIX} ${PKGNAME}) endif (PKG_CONFIG_FOUND) endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) endmacro (libfind_pkg_check_modules) # Do the final processing once the paths have been detected. # If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain # all the variables, each of which contain one include directory. # Ditto for ${PREFIX}_PROCESS_LIBS and library files. # Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. # Also handles errors in case library detection was required, etc. macro (libfind_process PREFIX) # Skip processing if already processed during this run if (NOT ${PREFIX}_FOUND) # Start with the assumption that the library was found set (${PREFIX}_FOUND TRUE) # Process all includes and set _FOUND to false if any are missing foreach (i ${${PREFIX}_PROCESS_INCLUDES}) if (${i}) set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}}) mark_as_advanced(${i}) else (${i}) set (${PREFIX}_FOUND FALSE) endif (${i}) endforeach (i) # Process all libraries and set _FOUND to false if any are missing foreach (i ${${PREFIX}_PROCESS_LIBS}) if (${i}) set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}}) mark_as_advanced(${i}) else (${i}) set (${PREFIX}_FOUND FALSE) endif (${i}) endforeach (i) # Print message and/or exit on fatal error if (${PREFIX}_FOUND) if (NOT ${PREFIX}_FIND_QUIETLY) message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") endif (NOT ${PREFIX}_FIND_QUIETLY) else (${PREFIX}_FOUND) if (${PREFIX}_FIND_REQUIRED) foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS}) message("${i}=${${i}}") endforeach (i) message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.") endif (${PREFIX}_FIND_REQUIRED) endif (${PREFIX}_FOUND) endif (NOT ${PREFIX}_FOUND) endmacro (libfind_process) macro(libfind_library PREFIX basename) set(TMP "") if(MSVC80) set(TMP -vc80) endif(MSVC80) if(MSVC90) set(TMP -vc90) endif(MSVC90) set(${PREFIX}_LIBNAMES ${basename}${TMP}) if(${ARGC} GREATER 2) set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2}) string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES}) set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP}) endif(${ARGC} GREATER 2) find_library(${PREFIX}_LIBRARY NAMES ${${PREFIX}_LIBNAMES} PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS} ) endmacro(libfind_library) history-service-0.1+16.04.20160104/cmake/modules/FindLcov.cmake0000644000015600001650000000172012642552153024312 0ustar pbuserpbgroup00000000000000# - Find lcov # Will define: # # LCOV_EXECUTABLE - the lcov binary # GENHTML_EXECUTABLE - the genhtml executable # # Copyright (C) 2010 by Johannes Wienke # # 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 2, 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. # INCLUDE(FindPackageHandleStandardArgs) FIND_PROGRAM(LCOV_EXECUTABLE lcov) FIND_PROGRAM(GENHTML_EXECUTABLE genhtml) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lcov DEFAULT_MSG LCOV_EXECUTABLE GENHTML_EXECUTABLE) # only visible in advanced view MARK_AS_ADVANCED(LCOV_EXECUTABLE GENHTML_EXECUTABLE) history-service-0.1+16.04.20160104/plugins/0000755000015600001650000000000012642552414020535 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/plugins/CMakeLists.txt0000644000015600001650000000003112642552153023267 0ustar pbuserpbgroup00000000000000add_subdirectory(sqlite) history-service-0.1+16.04.20160104/plugins/sqlite/0000755000015600001650000000000012642552414022036 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/plugins/sqlite/update_qrc.sh0000755000015600001650000000057312642552153024531 0ustar pbuserpbgroup00000000000000#!/bin/sh QRC_FILE=$1 FILES=`ls schema/*.sql schema/*.info` # clear the file if [ -e $QRC_FILE ]; then rm -f $QRC_FILE fi # and print the contents echo '' >> $QRC_FILE echo ' ' >> $QRC_FILE for file in $FILES; do echo " $file" >> $QRC_FILE done echo ' ' >> $QRC_FILE echo '' >> $QRC_FILE history-service-0.1+16.04.20160104/plugins/sqlite/sqlitehistoryplugin.cpp0000644000015600001650000013112312642552153026705 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "sqlitehistoryplugin.h" #include "phoneutils_p.h" #include "utils_p.h" #include "sqlitedatabase.h" #include "sqlitehistoryeventview.h" #include "sqlitehistorythreadview.h" #include "intersectionfilter.h" #include "unionfilter.h" #include "thread.h" #include "contactmatcher_p.h" #include "utils_p.h" #include #include #include #include #include QString generateThreadMapKey(const History::Thread &thread) { return thread.accountId() + thread.threadId(); } QString generateThreadMapKey(const QString &accountId, const QString &threadId) { return accountId + threadId; } SQLiteHistoryPlugin::SQLiteHistoryPlugin(QObject *parent) : QObject(parent), mInitialised(false) { // just trigger the database creation or update SQLiteDatabase::instance(); } bool SQLiteHistoryPlugin::initialised() { return mInitialised; } void SQLiteHistoryPlugin::updateGroupedThreadsCache() { History::PluginThreadView *view = queryThreads(History::EventTypeText, History::Sort("timestamp", Qt::DescendingOrder), History::Filter()); QList threads; while (view->IsValid()) { QList page = view->NextPage(); if (page.size() > 0) { threads += page; } else { break; } } addThreadsToCache(threads); } void SQLiteHistoryPlugin::addThreadsToCache(const QList &threads) { Q_FOREACH (QVariantMap properties, threads) { // FIXME: it would be better to just use UTC all the way through the client, // but that requires a lot of changes // so instead we just convert to UTC here on the cache and convert back to local time // when returning QDateTime timestamp = QDateTime::fromString(properties[History::FieldTimestamp].toString(), Qt::ISODate); properties[History::FieldTimestamp] = timestamp.toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz"); // the same for readTimestamp timestamp = QDateTime::fromString(properties[History::FieldReadTimestamp].toString(), Qt::ISODate); properties[History::FieldReadTimestamp] = timestamp.toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz"); History::Thread thread = History::Thread::fromProperties(properties); const QString &threadKey = generateThreadMapKey(thread); if (thread.type() != History::EventTypeText) { continue; } else if (!History::Utils::shouldGroupAccount(thread.accountId())) { // never group non phone accounts mConversationsCache[threadKey] = History::Threads() << thread; mConversationsCacheKeys[threadKey] = threadKey; continue; } // find conversation grouping this thread if (mConversationsCacheKeys.contains(threadKey)) { QString conversationKey = mConversationsCacheKeys[threadKey]; History::Threads groupedThreads = mConversationsCache[conversationKey]; Q_FOREACH(const History::Thread &groupedThread, groupedThreads) { mConversationsCacheKeys.remove(generateThreadMapKey(groupedThread)); } groupedThreads.removeAll(thread); groupedThreads.append(thread); mConversationsCache[conversationKey] = groupedThreads; mConversationsCacheKeys.remove(threadKey); updateDisplayedThread(conversationKey); continue; } // if not found, we have to iterate the list and compare phone numbers bool found = false; QMap::iterator it = mConversationsCache.begin(); while (it != mConversationsCache.end()) { const QString &conversationKey = it.key(); History::Threads groupedThreads = it.value(); Q_FOREACH(const History::Thread &groupedThread, groupedThreads) { found = History::Utils::compareNormalizedParticipants(thread.participants().identifiers(), groupedThread.participants().identifiers(), History::MatchPhoneNumber); if (found) { Q_FOREACH(const History::Thread &groupedThread, groupedThreads) { mConversationsCacheKeys.remove(generateThreadMapKey(groupedThread)); } mConversationsCache[conversationKey] += thread; updateDisplayedThread(conversationKey); break; } } if (found) { break; } it++; } if (!found) { mConversationsCache[threadKey] = History::Threads() << thread; mConversationsCacheKeys[threadKey] = threadKey; } } } bool SQLiteHistoryPlugin::lessThan(const QVariantMap &left, const QVariantMap &right) const { QVariant leftValue = left[History::FieldLastEventTimestamp]; QVariant rightValue = right[History::FieldLastEventTimestamp]; return leftValue < rightValue; } void SQLiteHistoryPlugin::updateDisplayedThread(const QString &displayedThreadKey) { History::Threads threads = mConversationsCache[displayedThreadKey]; History::Thread displayedThread = threads.first(); QVariantMap displayedProperties = displayedThread.properties(); Q_FOREACH(const History::Thread &other, threads) { if (lessThan(displayedProperties, other.properties())) { displayedThread = other; displayedProperties = displayedThread.properties(); } } QString newDisplayedThreadKey = generateThreadMapKey(displayedThread); mConversationsCache.remove(displayedThreadKey); mConversationsCache[newDisplayedThreadKey] = threads; // update reverse threadId -> conversationId map Q_FOREACH(const History::Thread &groupedThread, threads) { mConversationsCacheKeys[generateThreadMapKey(groupedThread)] = newDisplayedThreadKey; } } void SQLiteHistoryPlugin::removeThreadFromCache(const QVariantMap &properties) { History::Thread thread = History::Thread::fromProperties(properties); QString threadKey = generateThreadMapKey(thread); if (thread.type() != History::EventTypeText || !History::Utils::shouldGroupAccount(thread.accountId())) { mConversationsCache.remove(threadKey); mConversationsCacheKeys.remove(threadKey); return; } // check if this is a main key first if (mConversationsCache.contains(threadKey)) { // Remove itself from the list and promote the next grouped thread if any History::Threads threads = mConversationsCache[threadKey]; threads.removeAll(thread); mConversationsCache.remove(threadKey); mConversationsCacheKeys.remove(threadKey); // remove all threads from reverse map. they will be readded // in updateDisplayedThread() if needed Q_FOREACH (const History::Thread &thread, threads) { mConversationsCacheKeys.remove(generateThreadMapKey(thread)); } if (!threads.isEmpty()) { threadKey = generateThreadMapKey(threads.first()); mConversationsCache[threadKey] = threads; updateDisplayedThread(threadKey); } } else { // check if it belongs to an existing grouped thread; QMap::iterator it = mConversationsCache.begin(); while (it != mConversationsCache.end()) { const QString &threadKey = it.key(); History::Threads threads = it.value(); int pos = threads.indexOf(thread); if (pos != -1) { const QString &threadKey = generateThreadMapKey(thread); mConversationsCache.remove(threadKey); mConversationsCacheKeys.remove(threadKey); if (threads.size() == 1) { return; } else { threads.removeAll(thread); const QString &newThreadKey = generateThreadMapKey(threads.first()); mConversationsCache[newThreadKey] = threads; updateDisplayedThread(newThreadKey); return; } } it++; } } } /** * @brief Parses the cached thread properties, change fields that might be necessary and return the data * @param thread the thread to extract properties from * @return the thread properties */ QVariantMap SQLiteHistoryPlugin::cachedThreadProperties(const History::Thread &thread) const { QVariantMap properties = thread.properties(); // FIXME: now we need to convert the timestamp back to local time // remove this once we change the flow to use UTC for everything QDateTime timestamp = QDateTime::fromString(properties[History::FieldTimestamp].toString(), Qt::ISODate); timestamp.setTimeSpec(Qt::UTC); properties[History::FieldTimestamp] = toLocalTimeString(timestamp); // and the readTimestamp too timestamp = QDateTime::fromString(properties[History::FieldReadTimestamp].toString(), Qt::ISODate); timestamp.setTimeSpec(Qt::UTC); properties[History::FieldReadTimestamp] = toLocalTimeString(timestamp); return properties; } /** * @brief Generates the cache containing contact data for all known participants. * * FIXME: this should probably be done outside of the plugin, but it requires a * refactory of \ref HistoryDaemon itself. */ void SQLiteHistoryPlugin::generateContactCache() { QTime time; time.start(); qDebug() << "---- HistoryService: start generating cached content"; QSqlQuery query(SQLiteDatabase::instance()->database()); if (!query.exec("SELECT DISTINCT accountId, normalizedId FROM thread_participants")) { qWarning() << "Failed to generate contact cache:" << query.lastError().text(); return; } while (query.next()) { QString accountId = query.value(0).toString(); QString participantId = query.value(1).toString(); // we don't care about the results, as long as the contact data is present in the cache for // future usage. History::ContactMatcher::instance()->contactInfo(accountId, participantId, true); } updateGroupedThreadsCache(); qDebug() << "---- HistoryService: finished generating contact cache. elapsed time:" << time.elapsed() << "ms"; mInitialised = true; } // Reader History::PluginThreadView *SQLiteHistoryPlugin::queryThreads(History::EventType type, const History::Sort &sort, const History::Filter &filter, const QVariantMap &properties) { return new SQLiteHistoryThreadView(this, type, sort, filter, properties); } History::PluginEventView *SQLiteHistoryPlugin::queryEvents(History::EventType type, const History::Sort &sort, const History::Filter &filter) { return new SQLiteHistoryEventView(this, type, sort, filter); } QVariantMap SQLiteHistoryPlugin::threadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants, History::MatchFlags matchFlags) { if (participants.isEmpty()) { return QVariantMap(); } bool phoneCompare = (matchFlags & History::MatchPhoneNumber); QSqlQuery query(SQLiteDatabase::instance()->database()); // select all the threads the first participant is listed in, and from that list // check if any of the threads has all the other participants listed // FIXME: find a better way to do this QString queryString("SELECT threadId FROM thread_participants WHERE %1 AND type=:type AND accountId=:accountId"); // FIXME: for now we just compare differently when using MatchPhoneNumber QString firstParticipant = participants.first(); if (phoneCompare) { queryString = queryString.arg("compareNormalizedPhoneNumbers(normalizedId, :participantId)"); firstParticipant = History::PhoneUtils::normalizePhoneNumber(firstParticipant); } else { queryString = queryString.arg("participantId=:participantId"); } query.prepare(queryString); query.bindValue(":participantId", firstParticipant); query.bindValue(":type", type); query.bindValue(":accountId", accountId); if (!query.exec()) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return QVariantMap(); } QStringList threadIds; while (query.next()) { threadIds << query.value(0).toString(); } QString existingThread; QStringList normalizedParticipants; if (phoneCompare) { Q_FOREACH(const QString &participant, participants) { normalizedParticipants << History::PhoneUtils::normalizePhoneNumber(participant); } } else { normalizedParticipants = participants; } // now for each threadId, check if all the other participants are listed Q_FOREACH(const QString &threadId, threadIds) { queryString = "SELECT %1 FROM thread_participants WHERE " "threadId=:threadId AND type=:type AND accountId=:accountId"; query.prepare(queryString.arg(phoneCompare ? "normalizedId" : "participantId")); query.bindValue(":threadId", threadId); query.bindValue(":type", type); query.bindValue(":accountId", accountId); if (!query.exec()) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return QVariantMap(); } QStringList threadParticipants; while (query.next()) { threadParticipants << query.value(0).toString(); } // we can't use query.size() as it always return -1 if (threadParticipants.count() != normalizedParticipants.count()) { continue; } bool found = History::Utils::compareNormalizedParticipants(threadParticipants, normalizedParticipants, matchFlags); if (found) { existingThread = threadId; break; } } return getSingleThread(type, accountId, existingThread); } QList SQLiteHistoryPlugin::eventsForThread(const QVariantMap &thread) { QList results; QString accountId = thread[History::FieldAccountId].toString(); QString threadId = thread[History::FieldThreadId].toString(); History::EventType type = (History::EventType) thread[History::FieldType].toInt(); QString condition = QString("accountId=\"%1\" AND threadId=\"%2\"").arg(accountId, threadId); QString queryText = sqlQueryForEvents(type, condition, ""); QSqlQuery query(SQLiteDatabase::instance()->database()); if (!query.exec(queryText)) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return results; } results = parseEventResults(type, query); return results; } QVariantMap SQLiteHistoryPlugin::getSingleThread(History::EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties) { QVariantMap result; bool grouped = false; if (accountId.isEmpty() || threadId.isEmpty()) { return result; } if (properties.contains(History::FieldGroupingProperty)) { grouped = properties[History::FieldGroupingProperty].toString() == History::FieldParticipants; } if (grouped) { const QString &threadKey = generateThreadMapKey(accountId, threadId); // we have to find which conversation this thread belongs to if (mConversationsCacheKeys.contains(threadKey)) { // found the thread. // get the displayed thread now const History::Threads &groupedThreads = mConversationsCache[mConversationsCacheKeys[threadKey]]; QVariantList finalGroupedThreads; Q_FOREACH(const History::Thread &displayedThread, groupedThreads) { QVariantMap properties = cachedThreadProperties(displayedThread); finalGroupedThreads << properties; if (generateThreadMapKey(displayedThread) == threadKey) { result = properties; } } result[History::FieldGroupedThreads] = QVariant::fromValue(finalGroupedThreads); return result; } return result; } QString condition = QString("accountId=\"%1\" AND threadId=\"%2\"").arg(accountId, threadId); QString queryText = sqlQueryForThreads(type, condition, QString::null); queryText += " LIMIT 1"; QSqlQuery query(SQLiteDatabase::instance()->database()); if (!query.exec(queryText)) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return result; } QList results = parseThreadResults(type, query, properties); query.clear(); if (!results.isEmpty()) { result = results.first(); } return result; } QVariantMap SQLiteHistoryPlugin::getSingleEvent(History::EventType type, const QString &accountId, const QString &threadId, const QString &eventId) { QVariantMap result; QString condition = QString("accountId=\"%1\" AND threadId=\"%2\" AND eventId=\"%3\"").arg(accountId, threadId, eventId); QString queryText = sqlQueryForEvents(type, condition, QString::null); queryText += " LIMIT 1"; QSqlQuery query(SQLiteDatabase::instance()->database()); if (!query.exec(queryText)) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return result; } QList results = parseEventResults(type, query); query.clear(); if (!results.isEmpty()) { result = results.first(); } return result; } // Writer QVariantMap SQLiteHistoryPlugin::createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants) { // WARNING: this function does NOT test to check if the thread is already created, you should check using HistoryReader::threadForParticipants() QVariantMap thread; // Create a new thread // FIXME: define what the threadId will be QString threadId = participants.join("%"); QSqlQuery query(SQLiteDatabase::instance()->database()); query.prepare("INSERT INTO threads (accountId, threadId, type, count, unreadCount)" "VALUES (:accountId, :threadId, :type, :count, :unreadCount)"); query.bindValue(":accountId", accountId); query.bindValue(":threadId", threadId); query.bindValue(":type", (int) type); query.bindValue(":count", 0); query.bindValue(":unreadCount", 0); if (!query.exec()) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return QVariantMap(); } // and insert the participants Q_FOREACH(const QString &participant, participants) { query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId)" "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId)"); query.bindValue(":accountId", accountId); query.bindValue(":threadId", threadId); query.bindValue(":type", type); query.bindValue(":participantId", participant); query.bindValue(":normalizedId", History::Utils::normalizeId(accountId, participant)); if (!query.exec()) { qCritical() << "Error:" << query.lastError() << query.lastQuery(); return QVariantMap(); } } // and finally create the thread thread[History::FieldAccountId] = accountId; thread[History::FieldThreadId] = threadId; thread[History::FieldType] = (int) type; thread[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true); thread[History::FieldCount] = 0; thread[History::FieldUnreadCount] = 0; addThreadsToCache(QList() << thread); return thread; } bool SQLiteHistoryPlugin::removeThread(const QVariantMap &thread) { QSqlQuery query(SQLiteDatabase::instance()->database()); query.prepare("DELETE FROM threads WHERE accountId=:accountId AND threadId=:threadId AND type=:type"); query.bindValue(":accountId", thread[History::FieldAccountId]); query.bindValue(":threadId", thread[History::FieldThreadId]); query.bindValue(":type", thread[History::FieldType]); if (!query.exec()) { qCritical() << "Failed to remove the thread: Error:" << query.lastError() << query.lastQuery(); return false; } removeThreadFromCache(thread); return true; } History::EventWriteResult SQLiteHistoryPlugin::writeTextEvent(const QVariantMap &event) { QSqlQuery query(SQLiteDatabase::instance()->database()); // check if the event exists QVariantMap existingEvent = getSingleEvent((History::EventType) event[History::FieldType].toInt(), event[History::FieldAccountId].toString(), event[History::FieldThreadId].toString(), event[History::FieldEventId].toString()); History::EventWriteResult result; if (existingEvent.isEmpty()) { // create new query.prepare("INSERT INTO text_events (accountId, threadId, eventId, senderId, timestamp, newEvent, message, messageType, messageStatus, readTimestamp, subject) " "VALUES (:accountId, :threadId, :eventId, :senderId, :timestamp, :newEvent, :message, :messageType, :messageStatus, :readTimestamp, :subject)"); result = History::EventWriteCreated; } else { // update existing event query.prepare("UPDATE text_events SET senderId=:senderId, timestamp=:timestamp, newEvent=:newEvent, message=:message, messageType=:messageType," "messageStatus=:messageStatus, readTimestamp=:readTimestamp, subject=:subject WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId"); result = History::EventWriteModified; } query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); query.bindValue(":senderId", event[History::FieldSenderId]); query.bindValue(":timestamp", event[History::FieldTimestamp].toDateTime().toUTC()); query.bindValue(":newEvent", event[History::FieldNewEvent]); query.bindValue(":message", event[History::FieldMessage]); query.bindValue(":messageType", event[History::FieldMessageType]); query.bindValue(":messageStatus", event[History::FieldMessageStatus]); query.bindValue(":readTimestamp", event[History::FieldReadTimestamp].toDateTime().toUTC()); query.bindValue(":subject", event[History::FieldSubject].toString()); if (!query.exec()) { qCritical() << "Failed to save the text event: Error:" << query.lastError() << query.lastQuery(); return History::EventWriteError; } History::MessageType messageType = (History::MessageType) event[History::FieldMessageType].toInt(); if (messageType == History::MessageTypeMultiPart) { // if the writing is an update, we need to remove the previous attachments if (result == History::EventWriteModified) { query.prepare("DELETE FROM text_event_attachments WHERE accountId=:accountId AND threadId=:threadId " "AND eventId=:eventId"); query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); if (!query.exec()) { qCritical() << "Could not erase previous attachments. Error:" << query.lastError() << query.lastQuery(); return History::EventWriteError; } } // save the attachments QList attachments = qdbus_cast >(event[History::FieldAttachments]); Q_FOREACH(const QVariantMap &attachment, attachments) { query.prepare("INSERT INTO text_event_attachments VALUES (:accountId, :threadId, :eventId, :attachmentId, :contentType, :filePath, :status)"); query.bindValue(":accountId", attachment[History::FieldAccountId]); query.bindValue(":threadId", attachment[History::FieldThreadId]); query.bindValue(":eventId", attachment[History::FieldEventId]); query.bindValue(":attachmentId", attachment[History::FieldAttachmentId]); query.bindValue(":contentType", attachment[History::FieldContentType]); query.bindValue(":filePath", attachment[History::FieldFilePath]); query.bindValue(":status", attachment[History::FieldStatus]); if (!query.exec()) { qCritical() << "Failed to save attachment to database" << query.lastError() << attachment; return History::EventWriteError; } } } if (result == History::EventWriteModified || result == History::EventWriteCreated) { QVariantMap existingThread = getSingleThread((History::EventType) event[History::FieldType].toInt(), event[History::FieldAccountId].toString(), event[History::FieldThreadId].toString(), QVariantMap()); addThreadsToCache(QList() << existingThread); } return result; } bool SQLiteHistoryPlugin::removeTextEvent(const QVariantMap &event) { QSqlQuery query(SQLiteDatabase::instance()->database()); query.prepare("DELETE FROM text_events WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId"); query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); if (!query.exec()) { qCritical() << "Failed to remove the text event: Error:" << query.lastError() << query.lastQuery(); return false; } QVariantMap existingThread = getSingleThread((History::EventType) event[History::FieldType].toInt(), event[History::FieldAccountId].toString(), event[History::FieldThreadId].toString(), QVariantMap()); if (!existingThread.isEmpty()) { addThreadsToCache(QList() << existingThread); } return true; } History::EventWriteResult SQLiteHistoryPlugin::writeVoiceEvent(const QVariantMap &event) { QSqlQuery query(SQLiteDatabase::instance()->database()); // check if the event exists QVariantMap existingEvent = getSingleEvent((History::EventType) event[History::FieldType].toInt(), event[History::FieldAccountId].toString(), event[History::FieldThreadId].toString(), event[History::FieldEventId].toString()); History::EventWriteResult result; if (existingEvent.isEmpty()) { // create new query.prepare("INSERT INTO voice_events (accountId, threadId, eventId, senderId, timestamp, newEvent, duration, missed, remoteParticipant) " "VALUES (:accountId, :threadId, :eventId, :senderId, :timestamp, :newEvent, :duration, :missed, :remoteParticipant)"); result = History::EventWriteCreated; } else { // update existing event query.prepare("UPDATE voice_events SET senderId=:senderId, timestamp=:timestamp, newEvent=:newEvent, duration=:duration, " "missed=:missed, remoteParticipant=:remoteParticipant " "WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId"); result = History::EventWriteModified; } query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); query.bindValue(":senderId", event[History::FieldSenderId]); query.bindValue(":timestamp", event[History::FieldTimestamp].toDateTime().toUTC()); query.bindValue(":newEvent", event[History::FieldNewEvent]); query.bindValue(":duration", event[History::FieldDuration]); query.bindValue(":missed", event[History::FieldMissed]); query.bindValue(":remoteParticipant", event[History::FieldRemoteParticipant]); if (!query.exec()) { qCritical() << "Failed to save the voice event: Error:" << query.lastError() << query.lastQuery(); result = History::EventWriteError; } return result; } bool SQLiteHistoryPlugin::removeVoiceEvent(const QVariantMap &event) { QSqlQuery query(SQLiteDatabase::instance()->database()); query.prepare("DELETE FROM voice_events WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId"); query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); if (!query.exec()) { qCritical() << "Failed to remove the voice event: Error:" << query.lastError() << query.lastQuery(); return false; } return true; } bool SQLiteHistoryPlugin::beginBatchOperation() { return SQLiteDatabase::instance()->beginTransation(); } bool SQLiteHistoryPlugin::endBatchOperation() { return SQLiteDatabase::instance()->finishTransaction(); } bool SQLiteHistoryPlugin::rollbackBatchOperation() { return SQLiteDatabase::instance()->rollbackTransaction(); } QString SQLiteHistoryPlugin::sqlQueryForThreads(History::EventType type, const QString &condition, const QString &order) { QString modifiedCondition = condition; if (!modifiedCondition.isEmpty()) { modifiedCondition.prepend(" AND "); // FIXME: the filters should be implemented in a better way modifiedCondition.replace("accountId=", "threads.accountId="); modifiedCondition.replace("threadId=", "threads.threadId="); modifiedCondition.replace("count=", "threads.count="); modifiedCondition.replace("unreadCount=", "threads.unreadCount="); } QString modifiedOrder = order; if (!modifiedOrder.isEmpty()) { modifiedOrder.replace(" accountId", " threads.accountId"); modifiedOrder.replace(" threadId", " threads.threadId"); modifiedOrder.replace(" count", " threads.count"); modifiedOrder.replace(" unreadCount", " threads.unreadCount"); } QStringList fields; fields << "threads.accountId" << "threads.threadId" << "threads.lastEventId" << "threads.count" << "threads.unreadCount"; // get the participants in the query already fields << "(SELECT group_concat(thread_participants.participantId, \"|,|\") " "FROM thread_participants WHERE thread_participants.accountId=threads.accountId " "AND thread_participants.threadId=threads.threadId " "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as participants"; QStringList extraFields; QString table; switch (type) { case History::EventTypeText: table = "text_events"; extraFields << "text_events.message" << "text_events.messageType" << "text_events.messageStatus" << "text_events.readTimestamp"; break; case History::EventTypeVoice: table = "voice_events"; extraFields << "voice_events.duration" << "voice_events.missed" << "voice_events.remoteParticipant"; break; } fields << QString("%1.senderId").arg(table) << QString("%1.timestamp").arg(table) << QString("%1.newEvent").arg(table); fields << extraFields; QString queryText = QString("SELECT %1 FROM threads LEFT JOIN %2 ON threads.threadId=%2.threadId AND " "threads.accountId=%2.accountId AND threads.lastEventId=%2.eventId WHERE threads.type=%3 %4 %5") .arg(fields.join(", "), table, QString::number((int)type), modifiedCondition, modifiedOrder); return queryText; } QList SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties) { QList threads; QSqlQuery attachmentsQuery(SQLiteDatabase::instance()->database()); QList attachments; bool grouped = false; if (properties.contains(History::FieldGroupingProperty)) { grouped = properties[History::FieldGroupingProperty].toString() == History::FieldParticipants; } while (query.next()) { QVariantMap thread; QString accountId = query.value(0).toString(); QString threadId = query.value(1).toString(); if (threadId.trimmed().isEmpty()) { continue; } thread[History::FieldType] = (int) type; thread[History::FieldAccountId] = accountId; thread[History::FieldThreadId] = threadId; if (grouped) { const QString &threadKey = generateThreadMapKey(accountId, threadId); if (mInitialised && type == History::EventTypeText && !mConversationsCache.contains(threadKey)) { continue; } QVariantList groupedThreads; if (mConversationsCache.contains(threadKey)) { Q_FOREACH (const History::Thread &thread, mConversationsCache[threadKey]) { groupedThreads << cachedThreadProperties(thread); } } thread[History::FieldGroupedThreads] = QVariant::fromValue(groupedThreads); } thread[History::FieldEventId] = query.value(2); thread[History::FieldCount] = query.value(3); thread[History::FieldUnreadCount] = query.value(4); QStringList participants = query.value(5).toString().split("|,|"); thread[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true); // the generic event fields thread[History::FieldSenderId] = query.value(6); thread[History::FieldTimestamp] = toLocalTimeString(query.value(7).toDateTime()); thread[History::FieldNewEvent] = query.value(8).toBool(); // the next step is to get the last event switch (type) { case History::EventTypeText: attachmentsQuery.prepare("SELECT attachmentId, contentType, filePath, status FROM text_event_attachments " "WHERE accountId=:accountId and threadId=:threadId and eventId=:eventId"); attachmentsQuery.bindValue(":accountId", query.value(0)); attachmentsQuery.bindValue(":threadId", query.value(1)); attachmentsQuery.bindValue(":eventId", query.value(2)); if (!attachmentsQuery.exec()) { qCritical() << "Error:" << attachmentsQuery.lastError() << attachmentsQuery.lastQuery(); } while (attachmentsQuery.next()) { QVariantMap attachment; attachment[History::FieldAccountId] = query.value(0); attachment[History::FieldThreadId] = query.value(1); attachment[History::FieldEventId] = query.value(2); attachment[History::FieldAttachmentId] = attachmentsQuery.value(0); attachment[History::FieldContentType] = attachmentsQuery.value(1); attachment[History::FieldFilePath] = attachmentsQuery.value(2); attachment[History::FieldStatus] = attachmentsQuery.value(3); attachments << attachment; } attachmentsQuery.clear(); if (attachments.size() > 0) { thread[History::FieldAttachments] = QVariant::fromValue(attachments); attachments.clear(); } thread[History::FieldMessage] = query.value(9); thread[History::FieldMessageType] = query.value(10); thread[History::FieldMessageStatus] = query.value(11); thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(12).toDateTime()); break; case History::EventTypeVoice: thread[History::FieldMissed] = query.value(10); thread[History::FieldDuration] = query.value(9); thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(11).toString(), true); break; } threads << thread; } return threads; } QString SQLiteHistoryPlugin::sqlQueryForEvents(History::EventType type, const QString &condition, const QString &order) { QString modifiedCondition = condition; if (!modifiedCondition.isEmpty()) { modifiedCondition.prepend(" WHERE "); } QString participantsField = "(SELECT group_concat(thread_participants.participantId, \"|,|\") " "FROM thread_participants WHERE thread_participants.accountId=%1.accountId " "AND thread_participants.threadId=%1.threadId " "AND thread_participants.type=%2 GROUP BY accountId,threadId,type) as participants"; QString queryText; switch (type) { case History::EventTypeText: participantsField = participantsField.arg("text_events", QString::number(type)); queryText = QString("SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, %1, " "message, messageType, messageStatus, readTimestamp, subject FROM text_events %2 %3").arg(participantsField, modifiedCondition, order); break; case History::EventTypeVoice: participantsField = participantsField.arg("voice_events", QString::number(type)); queryText = QString("SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, %1, " "duration, missed, remoteParticipant FROM voice_events %2 %3").arg(participantsField, modifiedCondition, order); break; } return queryText; } QList SQLiteHistoryPlugin::parseEventResults(History::EventType type, QSqlQuery &query) { QList events; while (query.next()) { QVariantMap event; History::MessageType messageType; QString accountId = query.value(0).toString(); QString threadId = query.value(1).toString(); QString eventId = query.value(2).toString(); // ignore events that don't have a threadId or an eventId if (threadId.trimmed().isEmpty() || eventId.trimmed().isEmpty()) { continue; } event[History::FieldType] = (int) type; event[History::FieldAccountId] = accountId; event[History::FieldThreadId] = threadId; event[History::FieldEventId] = eventId; event[History::FieldSenderId] = query.value(3); event[History::FieldTimestamp] = toLocalTimeString(query.value(4).toDateTime()); event[History::FieldNewEvent] = query.value(5).toBool(); QStringList participants = query.value(6).toString().split("|,|"); event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true); switch (type) { case History::EventTypeText: messageType = (History::MessageType) query.value(8).toInt(); if (messageType == History::MessageTypeMultiPart) { QSqlQuery attachmentsQuery(SQLiteDatabase::instance()->database()); attachmentsQuery.prepare("SELECT attachmentId, contentType, filePath, status FROM text_event_attachments " "WHERE accountId=:accountId and threadId=:threadId and eventId=:eventId"); attachmentsQuery.bindValue(":accountId", accountId); attachmentsQuery.bindValue(":threadId", threadId); attachmentsQuery.bindValue(":eventId", eventId); if (!attachmentsQuery.exec()) { qCritical() << "Error:" << attachmentsQuery.lastError() << attachmentsQuery.lastQuery(); } QList attachments; while (attachmentsQuery.next()) { QVariantMap attachment; attachment[History::FieldAccountId] = accountId; attachment[History::FieldThreadId] = threadId; attachment[History::FieldEventId] = eventId; attachment[History::FieldAttachmentId] = attachmentsQuery.value(0); attachment[History::FieldContentType] = attachmentsQuery.value(1); attachment[History::FieldFilePath] = attachmentsQuery.value(2); attachment[History::FieldStatus] = attachmentsQuery.value(3); attachments << attachment; } attachmentsQuery.clear(); event[History::FieldAttachments] = QVariant::fromValue(attachments); } event[History::FieldMessage] = query.value(7); event[History::FieldMessageType] = query.value(8); event[History::FieldMessageStatus] = query.value(9); event[History::FieldReadTimestamp] = toLocalTimeString(query.value(10).toDateTime()); break; case History::EventTypeVoice: event[History::FieldDuration] = query.value(7).toInt(); event[History::FieldMissed] = query.value(8); event[History::FieldRemoteParticipant] = query.value(9).toString(); break; } events << event; } return events; } QString SQLiteHistoryPlugin::toLocalTimeString(const QDateTime ×tamp) { return QDateTime(timestamp.date(), timestamp.time(), Qt::UTC).toLocalTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); } QString SQLiteHistoryPlugin::filterToString(const History::Filter &filter, QVariantMap &bindValues, const QString &propertyPrefix) const { QString result; History::Filters filters; QString linking; QString value; int count; QString filterProperty = filter.filterProperty(); QVariant filterValue = filter.filterValue(); switch (filter.type()) { case History::FilterTypeIntersection: filters = History::IntersectionFilter(filter).filters(); linking = " AND "; case History::FilterTypeUnion: if (filter.type() == History::FilterTypeUnion) { filters = History::UnionFilter(filter).filters(); linking = " OR "; } if (filters.isEmpty()) { break; } result = "( "; count = filters.count(); for (int i = 0; i < count; ++i) { // run recursively through the inner filters result += QString("(%1)").arg(filterToString(filters[i], bindValues, propertyPrefix)); if (i != count-1) { result += linking; } } result += " )"; break; default: if (filterProperty.isEmpty() || filterValue.isNull()) { break; } QString bindId = QString(":filterValue%1").arg(bindValues.count()); QString propertyName = propertyPrefix.isNull() ? filterProperty : QString("%1.%2").arg(propertyPrefix, filterProperty); // FIXME: need to check for other match flags and multiple match flags if (filter.matchFlags() & History::MatchContains) { // FIXME: maybe we should use QString("%1 LIKE '\%'||%2'\%'").arg(bindId) ?? needs more time for investigating result = QString("%1 LIKE '\%%2\%' ESCAPE '\\'").arg(propertyName, escapeFilterValue(filterValue.toString())); } else { result = QString("%1=%2").arg(propertyName, bindId); bindValues[bindId] = filterValue; } } return result; } QString SQLiteHistoryPlugin::escapeFilterValue(const QString &value) const { QString escaped = value; escaped.replace("\\", "\\\\") .replace("'", "''") .replace("%", "\\%") .replace("_", "\\_"); return escaped; } history-service-0.1+16.04.20160104/plugins/sqlite/sqlitehistoryeventview.cpp0000644000015600001650000000675612642552153027440 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "sqlitehistoryeventview.h" #include "sqlitedatabase.h" #include "sqlitehistoryplugin.h" #include "sort.h" #include #include #include SQLiteHistoryEventView::SQLiteHistoryEventView(SQLiteHistoryPlugin *plugin, History::EventType type, const History::Sort &sort, const History::Filter &filter) : History::PluginEventView(), mType(type), mSort(sort), mFilter(filter), mQuery(SQLiteDatabase::instance()->database()), mPageSize(15), mPlugin(plugin), mOffset(0), mValid(true) { mTemporaryTable = QString("eventview%1%2").arg(QString::number((qulonglong)this), QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmsszzz")); mQuery.setForwardOnly(true); // FIXME: validate the filter QVariantMap filterValues; QString condition = mPlugin->filterToString(filter, filterValues); QString order; if (!sort.sortField().isNull()) { order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC"); // FIXME: check case sensitiviy } QString queryText = QString("CREATE TEMP TABLE %1 AS ").arg(mTemporaryTable); queryText += mPlugin->sqlQueryForEvents(type, condition, order); if (!mQuery.prepare(queryText)) { mValid = false; Q_EMIT Invalidated(); qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); return; } Q_FOREACH(const QString &key, filterValues.keys()) { mQuery.bindValue(key, filterValues[key]); } if (!mQuery.exec()) { mValid = false; Q_EMIT Invalidated(); qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); return; } } SQLiteHistoryEventView::~SQLiteHistoryEventView() { if (!mQuery.exec(QString("DROP TABLE IF EXISTS %1").arg(mTemporaryTable))) { qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); return; } } QList SQLiteHistoryEventView::NextPage() { QList events; // now prepare for selecting from it mQuery.prepare(QString("SELECT * FROM %1 LIMIT %2 OFFSET %3").arg(mTemporaryTable, QString::number(mPageSize), QString::number(mOffset))); if (!mQuery.exec()) { mValid = false; Q_EMIT Invalidated(); qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); return events; } events = mPlugin->parseEventResults(mType, mQuery); mOffset += mPageSize; mQuery.clear(); return events; } bool SQLiteHistoryEventView::IsValid() const { return mQuery.isActive(); } history-service-0.1+16.04.20160104/plugins/sqlite/sqlitehistoryeventview.h0000644000015600001650000000314612642552153027073 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 SQLITEHISTORYEVENTVIEW_H #define SQLITEHISTORYEVENTVIEW_H #include "plugineventview.h" #include "filter.h" #include "types.h" #include "sort.h" #include class SQLiteHistoryPlugin; class SQLiteHistoryEventView : public History::PluginEventView { Q_OBJECT public: SQLiteHistoryEventView(SQLiteHistoryPlugin *plugin, History::EventType type, const History::Sort &sort, const History::Filter &filter); ~SQLiteHistoryEventView(); QList NextPage(); bool IsValid() const; protected: private: History::EventType mType; History::Sort mSort; History::Filter mFilter; QSqlQuery mQuery; int mPageSize; SQLiteHistoryPlugin *mPlugin; QString mTemporaryTable; int mOffset; bool mValid; }; #endif // SQLITEHISTORYEVENTVIEW_H history-service-0.1+16.04.20160104/plugins/sqlite/sqlitehistoryplugin.h0000644000015600001650000001065412642552153026357 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 SQLITEHISTORYPLUGIN_H #define SQLITEHISTORYPLUGIN_H #include "plugin.h" #include "thread.h" #include #include class SQLiteHistoryReader; class SQLiteHistoryWriter; typedef QSharedPointer SQLiteHistoryReaderPtr; typedef QSharedPointer SQLiteHistoryWriterPtr; class SQLiteHistoryPlugin : public QObject, History::Plugin { Q_OBJECT Q_PLUGIN_METADATA(IID "com.canonical.historyservice.Plugin") Q_INTERFACES(History::Plugin) public: explicit SQLiteHistoryPlugin(QObject *parent = 0); bool initialised(); // Reader part of the plugin History::PluginThreadView* queryThreads(History::EventType type, const History::Sort &sort = History::Sort(), const History::Filter &filter = History::Filter(), const QVariantMap &properties = QVariantMap()); History::PluginEventView* queryEvents(History::EventType type, const History::Sort &sort = History::Sort(), const History::Filter &filter = History::Filter()); QVariantMap threadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants, History::MatchFlags matchFlags = History::MatchCaseSensitive); QList eventsForThread(const QVariantMap &thread); QVariantMap getSingleThread(History::EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap()); QVariantMap getSingleEvent(History::EventType type, const QString &accountId, const QString &threadId, const QString &eventId); // Writer part of the plugin QVariantMap createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants); bool removeThread(const QVariantMap &thread); History::EventWriteResult writeTextEvent(const QVariantMap &event); bool removeTextEvent(const QVariantMap &event); History::EventWriteResult writeVoiceEvent(const QVariantMap &event); bool removeVoiceEvent(const QVariantMap &event); bool beginBatchOperation(); bool endBatchOperation(); bool rollbackBatchOperation(); // functions to be used internally QString sqlQueryForThreads(History::EventType type, const QString &condition, const QString &order); QList parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties = QVariantMap()); QString sqlQueryForEvents(History::EventType type, const QString &condition, const QString &order); QList parseEventResults(History::EventType type, QSqlQuery &query); static QString toLocalTimeString(const QDateTime ×tamp); QString filterToString(const History::Filter &filter, QVariantMap &bindValues, const QString &propertyPrefix = QString::null) const; QString escapeFilterValue(const QString &value) const; void generateContactCache(); private: bool lessThan(const QVariantMap &left, const QVariantMap &right) const; void updateGroupedThreadsCache(); void updateDisplayedThread(const QString &displayedThreadKey); void addThreadsToCache(const QList &threads); void removeThreadFromCache(const QVariantMap &thread); QVariantMap cachedThreadProperties(const History::Thread &thread) const; QMap mConversationsCache; QMap mConversationsCacheKeys; bool mInitialised; }; #endif // SQLITEHISTORYPLUGIN_H history-service-0.1+16.04.20160104/plugins/sqlite/sqlitedatabase.h0000644000015600001650000000313012642552153025172 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 SQLITEDATABASE_H #define SQLITEDATABASE_H #include #include class SQLiteDatabase : public QObject { Q_OBJECT public: static SQLiteDatabase *instance(); bool initializeDatabase(); QSqlDatabase database() const; bool beginTransation(); bool finishTransaction(); bool rollbackTransaction(); bool reopen(); QString dumpSchema() const; QStringList parseSchemaFile(const QString &fileName); bool runMultipleStatements(const QStringList &statements, bool useTransaction = true); protected: bool createOrUpdateDatabase(); void parseVersionInfo(); // data upgrade functions bool changeTimestampsToUtc(); private: explicit SQLiteDatabase(QObject *parent = 0); QString mDatabasePath; QSqlDatabase mDatabase; int mSchemaVersion; }; #endif // SQLITEDATABASE_H history-service-0.1+16.04.20160104/plugins/sqlite/sqlitehistorythreadview.h0000644000015600001650000000330312642552153027214 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 SQLITEHISTORYTHREADVIEW_H #define SQLITEHISTORYTHREADVIEW_H #include "pluginthreadview.h" #include "filter.h" #include "types.h" #include "sort.h" #include class SQLiteHistoryPlugin; class SQLiteHistoryThreadView : public History::PluginThreadView { Q_OBJECT public: SQLiteHistoryThreadView(SQLiteHistoryPlugin *plugin, History::EventType type, const History::Sort &sort, const History::Filter &filter, const QVariantMap &properties); ~SQLiteHistoryThreadView(); QList NextPage(); bool IsValid() const; private: History::EventType mType; History::Sort mSort; History::Filter mFilter; QSqlQuery mQuery; int mPageSize; SQLiteHistoryPlugin *mPlugin; QString mTemporaryTable; int mOffset; bool mValid; QVariantMap mQueryProperties; }; #endif // SQLITEHISTORYTHREADVIEW_H history-service-0.1+16.04.20160104/plugins/sqlite/sqlitedatabase.cpp0000644000015600001650000003066612642552153025543 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "phoneutils_p.h" #include "sqlite3.h" #include "sqlitedatabase.h" #include "types.h" #include "utils_p.h" #include #include #include #include #include #include #include #include Q_DECLARE_OPAQUE_POINTER(sqlite3*) Q_DECLARE_METATYPE(sqlite3*) // custom sqlite function "comparePhoneNumbers" used to compare IDs if necessary void comparePhoneNumbers(sqlite3_context *context, int argc, sqlite3_value **argv) { QString arg1((const char*)sqlite3_value_text(argv[0])); QString arg2((const char*)sqlite3_value_text(argv[1])); sqlite3_result_int(context, (int)History::PhoneUtils::comparePhoneNumbers(arg1, arg2)); } void compareNormalizedPhoneNumbers(sqlite3_context *context, int argc, sqlite3_value **argv) { QString arg1((const char*)sqlite3_value_text(argv[0])); QString arg2((const char*)sqlite3_value_text(argv[1])); sqlite3_result_int(context, (int)History::PhoneUtils::compareNormalizedPhoneNumbers(arg1, arg2)); } void normalizeId(sqlite3_context *context, int argc, sqlite3_value **argv) { QString accountId((const char*)sqlite3_value_text(argv[0])); QString id((const char*)sqlite3_value_text(argv[1])); QString normalizedId = History::Utils::normalizeId(accountId, id); sqlite3_result_text(context, strdup(normalizedId.toUtf8().data()), -1, &free); } SQLiteDatabase::SQLiteDatabase(QObject *parent) : QObject(parent), mSchemaVersion(0) { initializeDatabase(); } SQLiteDatabase *SQLiteDatabase::instance() { static SQLiteDatabase *self = new SQLiteDatabase(); return self; } bool SQLiteDatabase::initializeDatabase() { mDatabasePath = qgetenv("HISTORY_SQLITE_DBPATH"); if (mDatabasePath.isEmpty()) { mDatabasePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); QDir dir(mDatabasePath); if (!dir.exists("history-service") && !dir.mkpath("history-service")) { qCritical() << "Failed to create dir"; return false; } dir.cd("history-service"); mDatabasePath = dir.absoluteFilePath("history.sqlite"); } mDatabase = QSqlDatabase::addDatabase("QSQLITE"); mDatabase.setDatabaseName(mDatabasePath); qDebug() << "Using database at" << mDatabasePath; // always run the createDatabase function at least during the development if (!createOrUpdateDatabase()) { qCritical() << "Failed to create or update the database"; return false; } return true; } QSqlDatabase SQLiteDatabase::database() const { return mDatabase; } bool SQLiteDatabase::beginTransation() { return mDatabase.transaction(); } bool SQLiteDatabase::finishTransaction() { return mDatabase.commit(); } bool SQLiteDatabase::rollbackTransaction() { return mDatabase.rollback(); } /// this method is to be used mainly by unit tests in order to clean up the database between /// tests. bool SQLiteDatabase::reopen() { mDatabase.close(); mDatabase.open(); // make sure the database is up-to-date after reopening. // this is mainly required for the memory backend used for testing createOrUpdateDatabase(); } QString SQLiteDatabase::dumpSchema() const { // query copied from sqlite3's shell.c QSqlQuery query(mDatabase); if (!query.exec("SELECT sql FROM " " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_master UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) " "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " "ORDER BY rowid")) { return QString::null; } QString schema; while (query.next()) { schema += query.value("sql").toString() + ";\n"; } return schema; } bool SQLiteDatabase::runMultipleStatements(const QStringList &statements, bool useTransaction) { if (statements.isEmpty()) { return false; } QSqlQuery query(mDatabase); if (useTransaction) { beginTransation(); } Q_FOREACH(const QString &statement, statements) { if (!query.exec(statement)) { if (useTransaction) { rollbackTransaction(); } qCritical() << "Failed to create or update database. SQL Statements:" << query.lastQuery() << "Error:" << query.lastError(); return false; } } if (useTransaction) { finishTransaction(); } return true; } bool SQLiteDatabase::createOrUpdateDatabase() { bool create = !QFile(mDatabasePath).exists(); if (!mDatabase.open()) { return false; } // create the comparePhoneNumbers custom sqlite functions sqlite3 *handle = database().driver()->handle().value(); sqlite3_create_function(handle, "comparePhoneNumbers", 2, SQLITE_ANY, NULL, &comparePhoneNumbers, NULL, NULL); sqlite3_create_function(handle, "compareNormalizedPhoneNumbers", 2, SQLITE_ANY, NULL, &compareNormalizedPhoneNumbers, NULL, NULL); // and also create the normalizeId function sqlite3_create_function(handle, "normalizeId", 2, SQLITE_ANY, NULL, &normalizeId, NULL, NULL); parseVersionInfo(); QSqlQuery query(mDatabase); // use memory to create temporary tables query.exec("PRAGMA temp_store = MEMORY"); QStringList statements; int existingVersion = 0; if (create) { statements = parseSchemaFile(":/database/schema/schema.sql"); } else { // if the database already exists, we don´t need to create the tables // only check if an update is needed query.exec("SELECT * FROM schema_version"); if (!query.exec() || !query.next()) { return false; } existingVersion = query.value(0).toInt(); int upgradeToVersion = existingVersion + 1; while (upgradeToVersion <= mSchemaVersion) { statements += parseSchemaFile(QString(":/database/schema/v%1.sql").arg(QString::number(upgradeToVersion))); ++upgradeToVersion; } } beginTransation(); if (!statements.isEmpty() && !runMultipleStatements(statements, false)) { rollbackTransaction(); return false; } // now set the new database schema version if (!query.exec("DELETE FROM schema_version")) { qCritical() << "Failed to remove previous schema versions. SQL Statement:" << query.lastQuery() << "Error:" << query.lastError(); rollbackTransaction(); return false; } if (!query.exec(QString("INSERT INTO schema_version VALUES (%1)").arg(mSchemaVersion))) { qCritical() << "Failed to insert new schema version. SQL Statement:" << query.lastQuery() << "Error:" << query.lastError(); rollbackTransaction(); return false; } // now check if any data updating is required if (existingVersion > 0) { // v10 - timestamps in UTC if (existingVersion > 0 && existingVersion < 10) { if (!changeTimestampsToUtc()) { qCritical() << "Failed to update existing data."; rollbackTransaction(); return false; } } } finishTransaction(); return true; } QStringList SQLiteDatabase::parseSchemaFile(const QString &fileName) { QFile schema(fileName); if (!schema.open(QFile::ReadOnly)) { qCritical() << "Failed to open " << fileName; return QStringList(); } bool parsingBlock = false; QString statement; QStringList statements; // FIXME: this parser is very basic, it needs to be improved in the future // it does a lot of assumptions based on the structure of the schema.sql file QTextStream stream(&schema); while (!stream.atEnd()) { QString line = stream.readLine(); bool statementEnded = false; statement += line; // check if we are parsing a trigger command if (line.trimmed().startsWith("CREATE TRIGGER", Qt::CaseInsensitive)) { parsingBlock = true; } else if (parsingBlock) { if (line.contains("END;")) { parsingBlock = false; statementEnded = true; } } else if (statement.contains(";")) { statementEnded = true; } statement += "\n"; if (statementEnded) { statements.append(statement); statement.clear(); } } return statements; } void SQLiteDatabase::parseVersionInfo() { QFile schema(":/database/schema/version.info"); if (!schema.open(QFile::ReadOnly)) { qDebug() << schema.error(); qCritical() << "Failed to get database version"; return; } QString version = schema.readAll(); mSchemaVersion = version.toInt(); } bool SQLiteDatabase::changeTimestampsToUtc() { // update the text events QSqlQuery query(database()); QString queryText = "SELECT accountId, threadId, eventId, timestamp, readTimestamp FROM text_events"; if (!query.exec(queryText)) { qWarning() << "Failed to update text events:" << query.lastError(); return false; } QList events; while (query.next()) { QVariantMap event; event[History::FieldAccountId] = query.value(0); event[History::FieldThreadId] = query.value(1); event[History::FieldEventId] = query.value(2); event[History::FieldTimestamp] = query.value(3); event[History::FieldReadTimestamp] = query.value(4); events << event; } query.clear(); queryText = "UPDATE text_events SET timestamp=:timestamp, readTimestamp=:readTimestamp"; queryText += " WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId"; query.prepare(queryText); Q_FOREACH (const QVariantMap &event, events) { query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); query.bindValue(":timestamp", event[History::FieldTimestamp].toDateTime().toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz")); query.bindValue(":readTimestamp", event[History::FieldReadTimestamp].toDateTime().toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz")); if (!query.exec()) { qWarning() << "Failed to update text event:" << query.lastError(); return false; } } // and do the same for voice events queryText = "SELECT accountId, threadId, eventId, timestamp FROM voice_events"; if (!query.exec(queryText)) { qWarning() << "Failed to update voice events:" << query.lastError(); return false; } events.clear(); while (query.next()) { QVariantMap event; event[History::FieldAccountId] = query.value(0); event[History::FieldThreadId] = query.value(1); event[History::FieldEventId] = query.value(2); event[History::FieldTimestamp] = query.value(3); events << event; } query.clear(); queryText = "UPDATE voice_events SET timestamp=:timestamp"; queryText += " WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId"; query.prepare(queryText); Q_FOREACH (const QVariantMap &event, events) { query.bindValue(":accountId", event[History::FieldAccountId]); query.bindValue(":threadId", event[History::FieldThreadId]); query.bindValue(":eventId", event[History::FieldEventId]); query.bindValue(":timestamp", event[History::FieldTimestamp].toDateTime().toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz")); if (!query.exec()) { qWarning() << "Failed to update voice event:" << query.lastError(); return false; } } return true; } history-service-0.1+16.04.20160104/plugins/sqlite/sqlitehistorythreadview.cpp0000644000015600001650000000751012642552153027553 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "sqlitehistorythreadview.h" #include "sqlitedatabase.h" #include "sqlitehistoryplugin.h" #include "sort.h" #include #include #include SQLiteHistoryThreadView::SQLiteHistoryThreadView(SQLiteHistoryPlugin *plugin, History::EventType type, const History::Sort &sort, const History::Filter &filter, const QVariantMap &properties) : History::PluginThreadView(), mPlugin(plugin), mType(type), mSort(sort), mFilter(filter), mPageSize(15), mQuery(SQLiteDatabase::instance()->database()), mOffset(0), mValid(true), mQueryProperties(properties) { qDebug() << __PRETTY_FUNCTION__; mTemporaryTable = QString("threadview%1%2").arg(QString::number((qulonglong)this), QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmsszzz")); mQuery.setForwardOnly(true); // FIXME: validate the filter QVariantMap filterValues; QString condition = mPlugin->filterToString(filter, filterValues); QString order; if (!sort.sortField().isNull()) { order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC"); // FIXME: check case sensitiviy } QString queryText = QString("CREATE TEMP TABLE %1 AS ").arg(mTemporaryTable); queryText += mPlugin->sqlQueryForThreads(type, condition, order); if (!mQuery.prepare(queryText)) { qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); mValid = false; Q_EMIT Invalidated(); return; } Q_FOREACH(const QString &key, filterValues.keys()) { mQuery.bindValue(key, filterValues[key]); } // create the temporary table if (!mQuery.exec()) { qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); mValid = false; Q_EMIT Invalidated(); return; } mQuery.exec(QString("SELECT count(*) FROM %1").arg(mTemporaryTable)); mQuery.next(); } SQLiteHistoryThreadView::~SQLiteHistoryThreadView() { if (!mQuery.exec(QString("DROP TABLE IF EXISTS %1").arg(mTemporaryTable))) { qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); return; } } QList SQLiteHistoryThreadView::NextPage() { qDebug() << __PRETTY_FUNCTION__; QList threads; // now prepare for selecting from it mQuery.prepare(QString("SELECT * FROM %1 LIMIT %2 OFFSET %3").arg(mTemporaryTable, QString::number(mPageSize), QString::number(mOffset))); if (!mQuery.exec()) { qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery(); mValid = false; Q_EMIT Invalidated(); return threads; } threads = mPlugin->parseThreadResults(mType, mQuery, mQueryProperties); mOffset += mPageSize; mQuery.clear(); return threads; } bool SQLiteHistoryThreadView::IsValid() const { return mValid; } history-service-0.1+16.04.20160104/plugins/sqlite/CMakeLists.txt0000644000015600001650000000243312642552153024600 0ustar pbuserpbgroup00000000000000# SQLite Plugin set(plugin_SRCS sqlitedatabase.cpp sqlitehistoryeventview.cpp sqlitehistorythreadview.cpp sqlitehistoryplugin.cpp ) set (plugin_HDRS sqlitedatabase.h sqlitehistoryeventview.h sqlitehistorythreadview.h sqlitehistoryplugin.h ) include_directories( ${CMAKE_SOURCE_DIR}/src ${SQLITE3_INCLUDE_DIRS} ) qt5_add_resources(plugin_RES sqlitehistoryplugin.qrc) add_library(sqlitehistoryplugin SHARED ${plugin_SRCS} ${plugin_HDRS} ${plugin_RES}) qt5_use_modules(sqlitehistoryplugin Core DBus Sql) # update the .qrc file automatically when there are new schema files file(GLOB QRC_RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/schema/*.sql ${CMAKE_CURRENT_SOURCE_DIR}/schema/*.info) set(QRC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/sqlitehistoryplugin.qrc) add_custom_command( OUTPUT ${QRC_FILE} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/update_qrc.sh ${QRC_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${QRC_RESOURCE_FILES} ) add_custom_target(qrc_update DEPENDS ${QRC_FILE} schema_update) add_dependencies(sqlitehistoryplugin schema_update qrc_update) target_link_libraries(sqlitehistoryplugin historyservice ${SQLITE3_LIBRARIES}) install(TARGETS sqlitehistoryplugin DESTINATION ${HISTORY_PLUGIN_PATH}) add_subdirectory(schema) history-service-0.1+16.04.20160104/plugins/sqlite/schema/0000755000015600001650000000000012642552414023276 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/plugins/sqlite/schema/v11.sql0000644000015600001650000000075212642552153024432 0ustar pbuserpbgroup00000000000000ALTER TABLE voice_events ADD COLUMN remoteParticipant varchar(255); UPDATE voice_events SET remoteParticipant=(SELECT participantId FROM thread_participants WHERE thread_participants.accountId = voice_events.accountId AND thread_participants.threadId = voice_events.threadId AND thread_participants.type = 1 LIMIT 1); history-service-0.1+16.04.20160104/plugins/sqlite/schema/v6.sql0000644000015600001650000001030212642552153024346 0ustar pbuserpbgroup00000000000000DROP TRIGGER text_events_insert_trigger; DROP TRIGGER text_events_update_trigger; DROP TRIGGER text_events_delete_trigger; ALTER TABLE text_events RENAME TO text_events_old; CREATE TABLE text_events ( accountId varchar(255), threadId varchar(255), eventId varchar(255), senderId varchar(255), timestamp datetime, newEvent bool, message varchar(512), messageType tinyint, messageStatus tinyint, readTimestamp datetime, subject varchar(256) ); INSERT INTO text_events ( accountId, threadId, eventId, senderId, timestamp, newEvent, message, messageType, messageStatus, readTimestamp, subject) SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, message, messageType, messageFlags, readTimestamp, subject FROM text_events_old; DROP TABLE text_events_old; CREATE TRIGGER text_events_insert_trigger AFTER INSERT ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_update_trigger AFTER UPDATE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='true') WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; DELETE from text_event_attachments WHERE accountId=old.accountId AND threadId=old.threadId AND eventId=old.eventId; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v1.sql0000644000015600001650000001626512642552153024357 0ustar pbuserpbgroup00000000000000CREATE TABLE schema_version ( version int ); CREATE TABLE threads ( accountId varchar(255), threadId varchar(255), type tinyint, lastEventId varchar(255), lastEventTimestamp datetime, count int, unreadCount int ); CREATE TABLE thread_participants ( accountId varchar(255), threadId varchar(255), type tinyint, participantId varchar(255) ); CREATE TABLE voice_events ( accountId varchar(255), threadId varchar(255), eventId varchar(255), senderId varchar(255), timestamp datetime, newEvent bool, duration int, missed bool ); CREATE TABLE text_events ( accountId varchar(255), threadId varchar(255), eventId varchar(255), senderId varchar(255), timestamp datetime, newEvent bool, message varchar(512), messageType tinyint, messageFlags tinyint, readTimestamp datetime ); CREATE TRIGGER voice_events_insert_trigger AFTER INSERT ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; END; CREATE TRIGGER voice_events_update_trigger AFTER UPDATE ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; END; CREATE TRIGGER voice_events_delete_trigger AFTER DELETE ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; END; CREATE TRIGGER text_events_insert_trigger AFTER INSERT ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_update_trigger AFTER UPDATE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='true') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v3.sql0000644000015600001650000000302712642552153024351 0ustar pbuserpbgroup00000000000000ALTER TABLE text_events ADD COLUMN subject varchar(256); CREATE TABLE IF NOT EXISTS text_event_attachments ( accountId varchar(255), threadId varchar(255), eventId varchar(255), attachmentId varchar(255), contentType varchar(255), filePath varchar(255), status tinyint ); DROP TRIGGER text_events_delete_trigger; CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='true') WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; DELETE from text_event_attachments WHERE accountId=old.accountId AND threadId=old.threadId AND eventId=old.eventId; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v9.sql0000644000015600001650000000730012642552153024355 0ustar pbuserpbgroup00000000000000DROP TRIGGER text_events_insert_trigger; DROP TRIGGER text_events_update_trigger; DROP TRIGGER text_events_delete_trigger; CREATE TRIGGER text_events_insert_trigger AFTER INSERT ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND messageType!=2) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='1' AND messageType!=2) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND messageType!=2 ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND messageType!=2 ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_update_trigger AFTER UPDATE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND messageType!=2) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='1' AND messageType!=2) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND messageType!=2 ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND messageType!=2 ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND messageType!=2) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='1' AND messageType!=2) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND messageType!=2 ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND messageType!=2 ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; DELETE from text_event_attachments WHERE accountId=old.accountId AND threadId=old.threadId AND eventId=old.eventId; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v12.sql0000644000015600001650000000023312642552153024425 0ustar pbuserpbgroup00000000000000ALTER TABLE thread_participants ADD COLUMN normalizedId varchar(255); UPDATE thread_participants SET normalizedId = normalizeId(accountId, participantId); history-service-0.1+16.04.20160104/plugins/sqlite/schema/v7.sql0000644000015600001650000000007612642552153024356 0ustar pbuserpbgroup00000000000000UPDATE text_events SET messageStatus=4 WHERE messageStatus=0; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v8.sql0000644000015600001650000001525212642552153024361 0ustar pbuserpbgroup00000000000000DROP TRIGGER text_events_insert_trigger; DROP TRIGGER text_events_update_trigger; DROP TRIGGER text_events_delete_trigger; DROP TRIGGER voice_events_insert_trigger; DROP TRIGGER voice_events_update_trigger; DROP TRIGGER voice_events_delete_trigger; CREATE TRIGGER text_events_insert_trigger AFTER INSERT ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='1') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_update_trigger AFTER UPDATE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='1') WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=0; END; CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='1') WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; DELETE from text_event_attachments WHERE accountId=old.accountId AND threadId=old.threadId AND eventId=old.eventId; END; CREATE TRIGGER voice_events_insert_trigger AFTER INSERT ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='1') WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; END; CREATE TRIGGER voice_events_update_trigger AFTER UPDATE ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId AND newEvent='1') WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=new.accountId AND threadId=new.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=new.accountId AND threadId=new.threadId AND type=1; END; CREATE TRIGGER voice_events_delete_trigger AFTER DELETE ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId) WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='1') WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v5.sql0000644000015600001650000000020312642552153024344 0ustar pbuserpbgroup00000000000000DELETE FROM thread_participants WHERE (SELECT count(threadId) FROM threads WHERE threads.threadId=thread_participants.threadId)=0; history-service-0.1+16.04.20160104/plugins/sqlite/schema/v2.sql0000644000015600001650000000427212642552153024353 0ustar pbuserpbgroup00000000000000DROP TRIGGER voice_events_delete_trigger; CREATE TRIGGER voice_events_delete_trigger AFTER DELETE ON voice_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId) WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='true') WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; UPDATE threads SET lastEventId=(SELECT eventId FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM voice_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=1; END; DROP TRIGGER text_events_delete_trigger; CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events FOR EACH ROW BEGIN UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId AND newEvent='true') WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE accountId=old.accountId AND threadId=old.threadId ORDER BY timestamp DESC LIMIT 1) WHERE accountId=old.accountId AND threadId=old.threadId AND type=0; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/update_schema.sh.in0000755000015600001650000000103212642552153027040 0ustar pbuserpbgroup00000000000000#!/bin/sh if [ $# -lt 3 ]; then echo "Usage: $0 " fi SOURCE_DIR=$1 TARGET_FILE=$2 VERSION_FILE=$3 VERSION="1" LATEST_VERSION="1" SCHEMA_FILES="" SCHEMA_FILE="$SOURCE_DIR/v${VERSION}.sql" while [ -e $SCHEMA_FILE ]; do SCHEMA_FILES="$SCHEMA_FILES $SCHEMA_FILE" LATEST_VERSION=$VERSION VERSION=$(($VERSION+1)) SCHEMA_FILE="$SOURCE_DIR/v${VERSION}.sql" done @CMAKE_CURRENT_BINARY_DIR@/generate_schema $SCHEMA_FILES $TARGET_FILE echo $LATEST_VERSION > $VERSION_FILE history-service-0.1+16.04.20160104/plugins/sqlite/schema/v10.sql0000644000015600001650000000004312642552153024422 0ustar pbuserpbgroup00000000000000SELECT * FROM text_events LIMIT 1; history-service-0.1+16.04.20160104/plugins/sqlite/schema/generate_schema.cpp0000644000015600001650000000377112642552153027124 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "sqlitedatabase.h" #include #include int main(int argc, char** argv) { QCoreApplication app(argc, argv); // force using a memory database setenv("HISTORY_SQLITE_DBPATH", ":memory:", 1); QStringList schemaFiles = app.arguments(); // take argv[0] out of the list schemaFiles.takeFirst(); // and store the target file too QString targetFile = schemaFiles.takeLast(); qDebug() << "**************************************************************"; qDebug() << "Starting the schema file generation process"; qDebug() << "Note: the failures seen below are known, expected and harmless"; qDebug() << "in this context."; QStringList statements; Q_FOREACH(const QString &file, schemaFiles) { statements << SQLiteDatabase::instance()->parseSchemaFile(file); } if (SQLiteDatabase::instance()->runMultipleStatements(statements)) { QFile file(targetFile); if (!file.open(QIODevice::WriteOnly)) { return 1; } file.write(SQLiteDatabase::instance()->dumpSchema().toUtf8().data()); file.close(); qDebug() << "**************************************************************"; return 0; } return 1; } history-service-0.1+16.04.20160104/plugins/sqlite/schema/v4.sql0000644000015600001650000000033612642552153024352 0ustar pbuserpbgroup00000000000000CREATE TRIGGER threads_delete_trigger AFTER DELETE ON threads FOR EACH ROW BEGIN DELETE FROM thread_participants WHERE accountId=old.accountId AND threadId=old.threadId AND type=old.type; END; history-service-0.1+16.04.20160104/plugins/sqlite/schema/CMakeLists.txt0000644000015600001650000000157312642552153026044 0ustar pbuserpbgroup00000000000000file(GLOB SCHEMA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/v*.sql) set(SCHEMA_FILE ${CMAKE_CURRENT_SOURCE_DIR}/schema.sql) set(VERSION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/version.info) configure_file(update_schema.sh.in ${CMAKE_CURRENT_BINARY_DIR}/update_schema.sh @ONLY) add_custom_command( OUTPUT ${SCHEMA_FILE} ${VERSION_FILE} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/update_schema.sh ${CMAKE_CURRENT_SOURCE_DIR} ${SCHEMA_FILE} ${VERSION_FILE} WORKING DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${SCHEMA_FILES} generate_schema ) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) add_executable(generate_schema generate_schema.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../sqlitedatabase.cpp) qt5_use_modules(generate_schema Core DBus Sql) target_link_libraries(generate_schema historyservice ${SQLITE3_LIBRARIES}) add_custom_target(schema_update DEPENDS ${SCHEMA_FILE} ${VERSION_FILE}) history-service-0.1+16.04.20160104/tests/0000755000015600001650000000000012642552414020216 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/Ubuntu.History/0000755000015600001650000000000012642552414023140 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/Ubuntu.History/HistoryGroupedThreadsModelTest.cpp0000644000015600001650000001523612642552153031776 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * This file is part of history-service. * * telephony-service 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; version 3. * * telephony-service 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 #include "manager.h" #include "historygroupedthreadsmodel.h" class HistoryGroupedThreadsModelTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCanFetchMore(); void testThreadsUpdated(); private: History::Manager *mManager; }; void HistoryGroupedThreadsModelTest::initTestCase() { mManager = History::Manager::instance(); } void HistoryGroupedThreadsModelTest::testCanFetchMore() { HistoryGroupedThreadsModel model; QSignalSpy fetchMoreChanged(&model, SIGNAL(canFetchMoreChanged())); // create a temporary thread to populate the model History::Thread textThread = mManager->threadForParticipants("accountId0", History::EventTypeText, QStringList() << QString("textParticipant"), History::MatchCaseSensitive, true); // must return false if there is no filter set QVERIFY(!model.canFetchMore()); HistoryQmlFilter *filter = new HistoryQmlFilter(this); model.setFilter(filter); model.setGroupingProperty(History::FieldParticipants); HistoryQmlSort *sort = new HistoryQmlSort(this); sort->setSortOrder(HistoryQmlSort::DescendingOrder); sort->setSortField("lastEventTimestamp"); model.setSort(sort); // force updateQuery() to be called model.componentComplete(); QVERIFY(model.canFetchMore()); model.fetchMore(); QTRY_VERIFY(fetchMoreChanged.count() >= 1); QVERIFY(!model.canFetchMore()); QTRY_COMPARE(model.rowCount(), 1); mManager->removeThreads(History::Threads() << textThread); QTRY_COMPARE(model.rowCount(), 0); } void HistoryGroupedThreadsModelTest::testThreadsUpdated() { HistoryGroupedThreadsModel model; QSignalSpy dataChanged(&model, SIGNAL(dataChanged(QModelIndex, QModelIndex))); QSignalSpy rowsRemoved(&model, SIGNAL(rowsRemoved(QModelIndex, int, int))); HistoryQmlFilter *filter = new HistoryQmlFilter(this); model.setFilter(filter); model.setGroupingProperty(History::FieldParticipants); HistoryQmlSort *sort = new HistoryQmlSort(this); sort->setSortOrder(HistoryQmlSort::DescendingOrder); sort->setSortField("lastEventTimestamp"); model.setSort(sort); // force updateQuery() to be called model.componentComplete(); // create first thread History::Thread textThread = mManager->threadForParticipants("ofono/ofono/account0", History::EventTypeText, QStringList() << QString("1234567"), History::MatchPhoneNumber, true); // insert one event History::TextEvent firstEvent = History::TextEvent(textThread.accountId(), textThread.threadId(), QString("eventId1%1").arg(QString::number(qrand() % 1024)), QString("1234567"), QDateTime::currentDateTime(), false, "Random Message", History::MessageTypeText); mManager->writeEvents(History::Events() << firstEvent); QTRY_COMPARE(dataChanged.count(), 1); QModelIndex firstIndex = dataChanged.first().first().value(); QString lastEventMessage = model.data(firstIndex, HistoryThreadModel::LastEventTextMessageRole).toString(); QCOMPARE(QString("Random Message"), lastEventMessage); dataChanged.clear(); // create another thread to be grouped, but using another kind of account textThread = mManager->threadForParticipants("multimedia/multimedia/account1", History::EventTypeText, QStringList() << QString("1234567"), History::MatchPhoneNumber, true); QTRY_COMPARE(dataChanged.count(), 1); QModelIndex index = dataChanged.first().first().value(); QCOMPARE(firstIndex, index); dataChanged.clear(); // insert another event in second thread History::TextEvent secondEvent = History::TextEvent(textThread.accountId(), textThread.threadId(), QString("eventId2%1").arg(QString::number(qrand() % 1024)), QString("1234567"), QDateTime::currentDateTime(), false, "Random Message2", History::MessageTypeText); mManager->writeEvents(History::Events() << secondEvent); QTRY_COMPARE(dataChanged.count(), 1); // make sure the index is the same, meaning both threads are grouped index = dataChanged.first().first().value(); QCOMPARE(firstIndex, index); // check if latest message is from the second event lastEventMessage = model.data(index, HistoryThreadModel::LastEventTextMessageRole).toString(); // check if count is correct given that we have two threads grouped with one message in each QCOMPARE(model.data(index, HistoryThreadModel::CountRole).toInt(), 2); QCOMPARE(QString("Random Message2"), lastEventMessage); dataChanged.clear(); // delete latest event and make sure the text displayed is from the first thread again mManager->removeEvents(History::Events() << secondEvent); QTRY_COMPARE(dataChanged.count(), 1); index = dataChanged.first().first().value(); QCOMPARE(firstIndex, index); lastEventMessage = model.data(index, HistoryThreadModel::LastEventTextMessageRole).toString(); QCOMPARE(QString("Random Message"), lastEventMessage); // check if count is correct given that we have only one thread now QCOMPARE(model.data(index, HistoryThreadModel::CountRole).toInt(), 1); // delete first event and make sure the model is cleared mManager->removeEvents(History::Events() << firstEvent); QTRY_COMPARE(rowsRemoved.count(), 1); QTRY_COMPARE(model.rowCount(), 0); } QTEST_MAIN(HistoryGroupedThreadsModelTest) #include "HistoryGroupedThreadsModelTest.moc" history-service-0.1+16.04.20160104/tests/Ubuntu.History/CMakeLists.txt0000644000015600001650000000101712642552153025677 0ustar pbuserpbgroup00000000000000include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/Ubuntu/History ${CMAKE_SOURCE_DIR}/src ) set(SOURCE_DIR ${CMAKE_SOURCE_DIR}/Ubuntu/History) generate_test(HistoryGroupedThreadsModelTest SOURCES HistoryGroupedThreadsModelTest.cpp LIBRARIES history-qml USE_DBUS USE_XVFB TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon WAIT_FOR com.canonical.HistoryService) history-service-0.1+16.04.20160104/tests/plugins/0000755000015600001650000000000012642552414021677 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/plugins/CMakeLists.txt0000644000015600001650000000003112642552153024431 0ustar pbuserpbgroup00000000000000add_subdirectory(sqlite) history-service-0.1+16.04.20160104/tests/plugins/sqlite/0000755000015600001650000000000012642552414023200 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/plugins/sqlite/SqlitePluginTest.cpp0000644000015600001650000012142412642552153027170 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "sqlitehistoryplugin.h" #include "sqlitedatabase.h" #include "sqlitehistorythreadview.h" #include "sqlitehistoryeventview.h" #include "textevent.h" #include "texteventattachment.h" #include "voiceevent.h" #include "intersectionfilter.h" #include "unionfilter.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::MatchFlags) class SqlitePluginTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCreateThread_data(); void testCreateThread(); void testThreadForParticipants_data(); void testThreadForParticipants(); void testEmptyThreadForParticipants(); void testGetSingleThread(); void testRemoveThread(); void testBatchOperation(); void testRollback(); void testQueryThreads(); void testQueryEvents(); void testWriteTextEvent_data(); void testWriteTextEvent(); void testModifyTextEvent(); void testRemoveTextEvent(); void testWriteVoiceEvent_data(); void testWriteVoiceEvent(); void testModifyVoiceEvent(); void testRemoveVoiceEvent(); void testEventsForThread(); void testGetSingleEvent_data(); void testGetSingleEvent(); void testFilterToString_data(); void testFilterToString(); void testEscapeFilterValue_data(); void testEscapeFilterValue(); private: SQLiteHistoryPlugin *mPlugin; }; void SqlitePluginTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qputenv("HISTORY_SQLITE_DBPATH", ":memory:"); mPlugin = new SQLiteHistoryPlugin(this); } void SqlitePluginTest::testCreateThread_data() { QTest::addColumn("accountId"); QTest::addColumn("eventType"); QTest::addColumn("participants"); QTest::newRow("voice thread with one participant") << "oneAccount" << History::EventTypeVoice << (QStringList() << "oneParticipant"); QTest::newRow("text thread with multiple participants") << "oneAccount" << History::EventTypeText << (QStringList() << "first" << "second"); } void SqlitePluginTest::testCreateThread() { QFETCH(QString, accountId); QFETCH(History::EventType, eventType); QFETCH(QStringList, participants); SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants(accountId, eventType, participants); // check that the variant map is properly filled QCOMPARE(thread[History::FieldAccountId].toString(), accountId); QCOMPARE(thread[History::FieldType].toInt(), (int) eventType); QCOMPARE(History::Participants::fromVariantList(thread[History::FieldParticipants].toList()).identifiers(), participants); QVERIFY(!thread[History::FieldThreadId].toString().isEmpty()); // now check that the thread is properly saved in the database QSqlQuery query(SQLiteDatabase::instance()->database()); query.prepare("SELECT * FROM threads WHERE accountId=:accountId AND type=:type"); query.bindValue(":accountId", accountId); query.bindValue(":type", (int) eventType); QVERIFY(query.exec()); int count = 0; while (query.next()) { count++; QCOMPARE(query.value("accountId"), thread[History::FieldAccountId]); QCOMPARE(query.value("threadId"), thread[History::FieldThreadId]); QCOMPARE(query.value("type"), thread[History::FieldType]); QCOMPARE(query.value("count").toInt(), 0); QCOMPARE(query.value("unreadCount").toInt(), 0); } QCOMPARE(count, 1); // and make sure all the participants are saved correctly query.prepare("SELECT * FROM thread_participants"); QVERIFY(query.exec()); count = 0; while (query.next()) { count++; QCOMPARE(query.value("accountId"), thread[History::FieldAccountId]); QCOMPARE(query.value("threadId"), thread[History::FieldThreadId]); QCOMPARE(query.value("type"), thread[History::FieldType]); QVERIFY(participants.contains(query.value("participantId").toString())); } QCOMPARE(count, participants.count()); } void SqlitePluginTest::testThreadForParticipants_data() { QTest::addColumn("accountId"); QTest::addColumn("eventType"); QTest::addColumn("participants"); QTest::addColumn("matchFlags"); QTest::addColumn("participantsToMatch"); QTest::newRow("voice thread with one participant") << "oneAccount" << History::EventTypeVoice << (QStringList() << "oneParticipant") << History::MatchFlags(History::MatchCaseSensitive) << (QStringList() << "oneParticipant"); QTest::newRow("text thread with multiple participants") << "oneAccount" << History::EventTypeText << (QStringList() << "first" << "second" << "third") << History::MatchFlags(History::MatchCaseSensitive) << (QStringList() << "second" << "first" << "third"); QTest::newRow("phone number match with one participant") << "thePhoneAccount" << History::EventTypeVoice << (QStringList() << "+12345678901") << History::MatchFlags(History::MatchPhoneNumber) << (QStringList() << "2345678901"); QTest::newRow("phone number match with multiple participants") << "phoneAccount" << History::EventTypeText << (QStringList() << "12345678" << "+19999999999") << History::MatchFlags(History::MatchPhoneNumber) << (QStringList() << "+554112345678" << "9999999"); } void SqlitePluginTest::testThreadForParticipants() { QFETCH(QString, accountId); QFETCH(History::EventType, eventType); QFETCH(QStringList, participants); QFETCH(History::MatchFlags, matchFlags); QFETCH(QStringList, participantsToMatch); SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants(accountId, eventType, participants); // there is no need to check the results of createThreadForParticipants, they are tested in another function // just check if the resulting thread is not empty QVERIFY(!thread.isEmpty()); // now try to fetch the thread for the given participants QVariantMap retrievedThread = mPlugin->threadForParticipants(accountId, eventType, participantsToMatch, matchFlags); QVERIFY(!retrievedThread.isEmpty()); QCOMPARE(retrievedThread[History::FieldAccountId], thread[History::FieldAccountId]); QCOMPARE(retrievedThread[History::FieldThreadId], thread[History::FieldThreadId]); QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]); QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]); QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]); QCOMPARE(retrievedThread[History::FieldParticipants], thread[History::FieldParticipants]); } void SqlitePluginTest::testEmptyThreadForParticipants() { QVariantMap thread = mPlugin->threadForParticipants("randomAccount", History::EventTypeText, QStringList()); QVERIFY(thread.isEmpty()); } void SqlitePluginTest::testGetSingleThread() { // reset the database SQLiteDatabase::instance()->reopen(); // create the thread that we will retrieve later QVariantMap thread = mPlugin->createThreadForParticipants("theAccountId", History::EventTypeText, QStringList() << "theParticipant"); QVERIFY(!thread.isEmpty()); // now create some other threads just to make sure the correct one is retrieved mPlugin->createThreadForParticipants("theAccountId", History::EventTypeText, QStringList() << "otherParticipant"); mPlugin->createThreadForParticipants("theAccountId", History::EventTypeVoice, QStringList() << "theParticipant"); mPlugin->createThreadForParticipants("otherAccount", History::EventTypeText, QStringList() << "theParticipant"); // and now retrieve the thread QVariantMap retrievedThread = mPlugin->getSingleThread(History::EventTypeText, "theAccountId", thread[History::FieldThreadId].toString()); QCOMPARE(retrievedThread[History::FieldAccountId], thread[History::FieldAccountId]); QCOMPARE(retrievedThread[History::FieldThreadId], thread[History::FieldThreadId]); QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]); QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]); QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]); QCOMPARE(retrievedThread[History::FieldParticipants], thread[History::FieldParticipants]); // FIXME: check that the last event data is also present } void SqlitePluginTest::testRemoveThread() { // reset the database SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants("oneAccountId", History::EventTypeText, QStringList() << "oneParticipant"); QVERIFY(!thread.isEmpty()); QVERIFY(mPlugin->removeThread(thread)); // now check that the thread was really removed QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT count(*) FROM threads")); QVERIFY(query.next()); QCOMPARE(query.value(0).toInt(), 0); // and check that the participants were also removed QVERIFY(query.exec("SELECT count(*) FROM thread_participants")); QVERIFY(query.next()); QCOMPARE(query.value(0).toInt(), 0); } void SqlitePluginTest::testBatchOperation() { // clear the database SQLiteDatabase::instance()->reopen(); QVERIFY(mPlugin->beginBatchOperation()); mPlugin->createThreadForParticipants("accountOne", History::EventTypeText, QStringList() << "participantOne"); mPlugin->createThreadForParticipants("accountTwo", History::EventTypeText, QStringList() << "participantTwo"); mPlugin->createThreadForParticipants("accountThree", History::EventTypeText, QStringList() << "participantThree"); QVERIFY(mPlugin->endBatchOperation()); // check that the data was actually written QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT count(*) FROM threads")); QVERIFY(query.next()); QCOMPARE(query.value(0).toInt(), 3); } void SqlitePluginTest::testRollback() { // clear the database SQLiteDatabase::instance()->reopen(); QVERIFY(mPlugin->beginBatchOperation()); mPlugin->createThreadForParticipants("accountOne", History::EventTypeText, QStringList() << "participantOne"); mPlugin->createThreadForParticipants("accountTwo", History::EventTypeText, QStringList() << "participantTwo"); mPlugin->createThreadForParticipants("accountThree", History::EventTypeText, QStringList() << "participantThree"); QVERIFY(mPlugin->rollbackBatchOperation()); // check that the steps were reverted QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT count(*) FROM threads")); QVERIFY(query.next()); QCOMPARE(query.value(0).toInt(), 0); } void SqlitePluginTest::testQueryThreads() { // just make sure the returned view is of the correct type. The views are going to be tested in their own tests History::PluginThreadView *view = mPlugin->queryThreads(History::EventTypeVoice); QVERIFY(view); QVERIFY(dynamic_cast(view)); view->deleteLater(); } void SqlitePluginTest::testQueryEvents() { // just make sure the returned view is of the correct type. The views are going to be tested in their own tests History::PluginEventView *view = mPlugin->queryEvents(History::EventTypeText); QVERIFY(view); QVERIFY(dynamic_cast(view)); view->deleteLater(); } void SqlitePluginTest::testWriteTextEvent_data() { QTest::addColumn("event"); // for test purposes, the threadId == senderId to make it easier QTest::newRow("new text event with pending flag") << History::TextEvent("oneAccountId", "theSender", "oneEventId", "theSender", QDateTime::currentDateTime(), true, "Hello World!", History::MessageTypeText, History::MessageStatusPending).properties(); QTest::newRow("text event with valid read timestamp") << History::TextEvent("otherAccountId", "otherSender", "otherEventId", "otherSender", QDateTime::currentDateTime(), false, "Hi Again!", History::MessageTypeText, History::MessageStatusDelivered, QDateTime::currentDateTime()).properties(); History::TextEventAttachments attachments; attachments << History::TextEventAttachment("mmsAccountId", "mmsSender", "mmsEventId", "mmsAttachment1", "text/plain", "/the/file/path", History::AttachmentDownloaded); QTest::newRow("text event with attachments") << History::TextEvent("mmsAccountId", "mmsSender", "mmsEventId", "mmsSender", QDateTime::currentDateTime(), false, "Hello with attachments", History::MessageTypeMultiPart, History::MessageStatusDelivered, QDateTime::currentDateTime(), "The Subject", attachments).properties(); } void SqlitePluginTest::testWriteTextEvent() { QFETCH(QVariantMap, event); // clear the database SQLiteDatabase::instance()->reopen(); // create the thread QVariantMap thread = mPlugin->createThreadForParticipants(event[History::FieldAccountId].toString(), History::EventTypeText, QStringList() << event[History::FieldSenderId].toString()); QVERIFY(!thread.isEmpty()); // write the text event History::EventWriteResult result = mPlugin->writeTextEvent(event); QCOMPARE(result, History::EventWriteCreated); // check that the event is properly written to the database QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT * FROM text_events")); int count = 0; while(query.next()) { count++; QCOMPARE(query.value("accountId"), event[History::FieldAccountId]); QCOMPARE(query.value("threadId"), event[History::FieldThreadId]); QCOMPARE(query.value("eventId"), event[History::FieldEventId]); QCOMPARE(query.value("senderId"), event[History::FieldSenderId]); QCOMPARE(mPlugin->toLocalTimeString(query.value("timestamp").toDateTime()), event[History::FieldTimestamp].toString()); QCOMPARE(query.value("newEvent"), event[History::FieldNewEvent]); QCOMPARE(query.value("message"), event[History::FieldMessage]); QCOMPARE(query.value("messageType"), event[History::FieldMessageType]); QCOMPARE(query.value("messageStatus"), event[History::FieldMessageStatus]); QCOMPARE(mPlugin->toLocalTimeString(query.value("readTimestamp").toDateTime()), event[History::FieldReadTimestamp].toString()); QCOMPARE(query.value("subject"), event[History::FieldSubject]); } // check that only one event got written QCOMPARE(count, 1); // check that the attachments got saved, if any if (event[History::FieldMessageType].toInt() == History::MessageTypeMultiPart) { QVariantMap attachment = event[History::FieldAttachments].value >()[0]; QVERIFY(query.exec("SELECT * FROM text_event_attachments")); int count = 0; while(query.next()) { count++; QCOMPARE(query.value("accountId"), attachment[History::FieldAccountId]); QCOMPARE(query.value("threadId"), attachment[History::FieldThreadId]); QCOMPARE(query.value("eventId"), attachment[History::FieldEventId]); QCOMPARE(query.value("attachmentId"), attachment[History::FieldAttachmentId]); QCOMPARE(query.value("contentType"), attachment[History::FieldContentType]); QCOMPARE(query.value("filePath"), attachment[History::FieldFilePath]); QCOMPARE(query.value("status"), attachment[History::FieldStatus]); } QCOMPARE(count, 1); } // and check that the thread's last item got updated thread = mPlugin->getSingleThread(History::EventTypeText, thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString()); QCOMPARE(thread[History::FieldAccountId], event[History::FieldAccountId]); QCOMPARE(thread[History::FieldThreadId], event[History::FieldThreadId]); QCOMPARE(thread[History::FieldEventId], event[History::FieldEventId]); QCOMPARE(thread[History::FieldSenderId], event[History::FieldSenderId]); QCOMPARE(thread[History::FieldTimestamp], event[History::FieldTimestamp]); QCOMPARE(thread[History::FieldNewEvent], event[History::FieldNewEvent]); QCOMPARE(thread[History::FieldMessage], event[History::FieldMessage]); QCOMPARE(thread[History::FieldMessageType], event[History::FieldMessageType]); QCOMPARE(thread[History::FieldMessageStatus], event[History::FieldMessageStatus]); QCOMPARE(thread[History::FieldReadTimestamp], event[History::FieldReadTimestamp]); } void SqlitePluginTest::testModifyTextEvent() { // clear the database SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants("theAccountId", History::EventTypeText, QStringList() << "theParticipant"); QVERIFY(!thread.isEmpty()); History::TextEventAttachment attachment(thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString(), thread[History::FieldEventId].toString(), "theAttachmentId", "text/plain", "/file/path"); History::TextEvent textEvent(thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString(), "theEventId", "theParticipant", QDateTime::currentDateTime(), true, "Hi there!", History::MessageTypeMultiPart, History::MessageStatusPending, QDateTime::currentDateTime(), "theSubject", History::TextEventAttachments() << attachment); QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteCreated); // now modify the fields that can be modified in the class textEvent.setNewEvent(false); textEvent.setMessageStatus(History::MessageStatusDelivered); textEvent.setReadTimestamp(QDateTime::currentDateTime()); QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteModified); // and check that the data is actually up-to-date in the database QVariantMap event = textEvent.properties(); QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT * FROM text_events")); int count = 0; while(query.next()) { count++; QCOMPARE(query.value("accountId"), event[History::FieldAccountId]); QCOMPARE(query.value("threadId"), event[History::FieldThreadId]); QCOMPARE(query.value("eventId"), event[History::FieldEventId]); QCOMPARE(query.value("senderId"), event[History::FieldSenderId]); QCOMPARE(mPlugin->toLocalTimeString(query.value("timestamp").toDateTime()), event[History::FieldTimestamp].toString()); QCOMPARE(query.value("newEvent"), event[History::FieldNewEvent]); QCOMPARE(query.value("message"), event[History::FieldMessage]); QCOMPARE(query.value("messageType"), event[History::FieldMessageType]); QCOMPARE(query.value("messageStatus"), event[History::FieldMessageStatus]); QCOMPARE(mPlugin->toLocalTimeString(query.value("readTimestamp").toDateTime()), event[History::FieldReadTimestamp].toString()); QCOMPARE(query.value("subject"), event[History::FieldSubject]); } // check that only one event got written QCOMPARE(count, 1); } void SqlitePluginTest::testRemoveTextEvent() { // clear the database SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants("theAccountId", History::EventTypeText, QStringList() << "theParticipant"); QVERIFY(!thread.isEmpty()); History::TextEvent textEvent(thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString(), "theSenderId", "theEventId", QDateTime::currentDateTime(), true, "Hello World!", History::MessageTypeText, History::MessageStatusPending); QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteCreated); // now remove the item and check that it is really removed from the database QVERIFY(mPlugin->removeTextEvent(textEvent.properties())); // check that the event was removed from the database QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT count(*) FROM text_events")); QVERIFY(query.next()); QCOMPARE(query.value(0).toInt(), 0); } void SqlitePluginTest::testWriteVoiceEvent_data() { QTest::addColumn("event"); // for test purposes, the threadId == senderId to make it easier QTest::newRow("missed call") << History::VoiceEvent("theAccountId", "theSenderId", "theEventId", "theSenderId", QDateTime::currentDateTime(), true, true).properties(); QTest::newRow("incoming call") << History::VoiceEvent("otherAccountId", "otherSenderId", "otherEventId", "otherSenderId", QDateTime::currentDateTime(), false, false, QTime(0,10,30)).properties(); } void SqlitePluginTest::testWriteVoiceEvent() { QFETCH(QVariantMap, event); // clear the database SQLiteDatabase::instance()->reopen(); // create the thread QVariantMap thread = mPlugin->createThreadForParticipants(event[History::FieldAccountId].toString(), History::EventTypeVoice, QStringList() << event[History::FieldSenderId].toString()); QVERIFY(!thread.isEmpty()); // write the voice event History::EventWriteResult result = mPlugin->writeVoiceEvent(event); QCOMPARE(result, History::EventWriteCreated); // check that the event is properly written to the database QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT * FROM voice_events")); int count = 0; while(query.next()) { count++; QCOMPARE(query.value("accountId"), event[History::FieldAccountId]); QCOMPARE(query.value("threadId"), event[History::FieldThreadId]); QCOMPARE(query.value("eventId"), event[History::FieldEventId]); QCOMPARE(query.value("senderId"), event[History::FieldSenderId]); QCOMPARE(mPlugin->toLocalTimeString(query.value("timestamp").toDateTime()), event[History::FieldTimestamp].toString()); QCOMPARE(query.value("newEvent"), event[History::FieldNewEvent]); QCOMPARE(query.value("missed"), event[History::FieldMissed]); QCOMPARE(query.value("duration"), event[History::FieldDuration]); } // check that only one event got written QCOMPARE(count, 1); // and check that the thread's last item got updated thread = mPlugin->getSingleThread(History::EventTypeVoice, thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString()); QCOMPARE(thread[History::FieldAccountId], event[History::FieldAccountId]); QCOMPARE(thread[History::FieldThreadId], event[History::FieldThreadId]); QCOMPARE(thread[History::FieldEventId], event[History::FieldEventId]); QCOMPARE(thread[History::FieldSenderId], event[History::FieldSenderId]); QCOMPARE(thread[History::FieldTimestamp], event[History::FieldTimestamp]); QCOMPARE(thread[History::FieldNewEvent], event[History::FieldNewEvent]); QCOMPARE(thread[History::FieldMissed], event[History::FieldMissed]); QCOMPARE(thread[History::FieldDuration], event[History::FieldDuration]); } void SqlitePluginTest::testModifyVoiceEvent() { // clear the database SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants("theAccountId", History::EventTypeVoice, QStringList() << "theParticipant"); QVERIFY(!thread.isEmpty()); History::VoiceEvent voiceEvent(thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString(), "theEventId", "theParticipant", QDateTime::currentDateTime(), true, true, QTime(0, 1, 2)); QCOMPARE(mPlugin->writeVoiceEvent(voiceEvent.properties()), History::EventWriteCreated); // now modify the fields that can be modified in the class voiceEvent.setNewEvent(false); QCOMPARE(mPlugin->writeVoiceEvent(voiceEvent.properties()), History::EventWriteModified); // and check that the data is actually up-to-date in the database QVariantMap event = voiceEvent.properties(); QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT * FROM voice_events")); int count = 0; while(query.next()) { count++; QCOMPARE(query.value("accountId"), event[History::FieldAccountId]); QCOMPARE(query.value("threadId"), event[History::FieldThreadId]); QCOMPARE(query.value("eventId"), event[History::FieldEventId]); QCOMPARE(query.value("senderId"), event[History::FieldSenderId]); QCOMPARE(mPlugin->toLocalTimeString(query.value("timestamp").toDateTime()), event[History::FieldTimestamp].toString()); QCOMPARE(query.value("newEvent"), event[History::FieldNewEvent]); QCOMPARE(query.value("missed"), event[History::FieldMissed]); QCOMPARE(query.value("duration"), event[History::FieldDuration]); } // check that only one event got written QCOMPARE(count, 1); } void SqlitePluginTest::testRemoveVoiceEvent() { // clear the database SQLiteDatabase::instance()->reopen(); QVariantMap thread = mPlugin->createThreadForParticipants("theAccountId", History::EventTypeVoice, QStringList() << "theParticipant"); QVERIFY(!thread.isEmpty()); History::VoiceEvent voiceEvent(thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString(), "theSenderId", "theEventId", QDateTime::currentDateTime(), true, true, QTime(0, 5, 10)); QCOMPARE(mPlugin->writeVoiceEvent(voiceEvent.properties()), History::EventWriteCreated); // now remove the item and check that it is really removed from the database QVERIFY(mPlugin->removeVoiceEvent(voiceEvent.properties())); // check that the event was removed from the database QSqlQuery query(SQLiteDatabase::instance()->database()); QVERIFY(query.exec("SELECT count(*) FROM voice_events")); QVERIFY(query.next()); QCOMPARE(query.value(0).toInt(), 0); } void SqlitePluginTest::testEventsForThread() { // clear the database SQLiteDatabase::instance()->reopen(); // test text events QVariantMap textThread = mPlugin->createThreadForParticipants("textAccountId", History::EventTypeText, QStringList() << "textParticipant"); QVERIFY(!textThread.isEmpty()); // now insert 50 events for (int i = 0; i < 50; ++i) { History::TextEventAttachment attachment(textThread[History::FieldAccountId].toString(), textThread[History::FieldThreadId].toString(), QString("textEventId%1").arg(QString::number(i)), QString("attachment%1").arg(i), "text/plain", "/some/file/path"); History::TextEvent textEvent(textThread[History::FieldAccountId].toString(), textThread[History::FieldThreadId].toString(), QString("textEventId%1").arg(QString::number(i)), "textParticipant", QDateTime::currentDateTime(), true, "Hello World!", History::MessageTypeMultiPart, History::MessageStatusPending, QDateTime::currentDateTime(), "theSubject", History::TextEventAttachments() << attachment); QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteCreated); } QList textEvents = mPlugin->eventsForThread(textThread); QCOMPARE(textEvents.count(), 50); Q_FOREACH(const QVariantMap &textEvent, textEvents) { QCOMPARE(textEvent[History::FieldAccountId], textThread[History::FieldAccountId]); QCOMPARE(textEvent[History::FieldThreadId], textThread[History::FieldThreadId]); QCOMPARE(textEvent[History::FieldType], textThread[History::FieldType]); } // test voice events QVariantMap voiceThread = mPlugin->createThreadForParticipants("voiceAccountId", History::EventTypeVoice, QStringList() << "voiceParticipant"); QVERIFY(!voiceThread.isEmpty()); // now insert 50 events for (int i = 0; i < 50; ++i) { History::VoiceEvent voiceEvent(voiceThread[History::FieldAccountId].toString(), voiceThread[History::FieldThreadId].toString(), QString("voiceEventId%1").arg(QString::number(i)), "voiceParticipant", QDateTime::currentDateTime(), true, false, QTime(0, i, i)); QCOMPARE(mPlugin->writeVoiceEvent(voiceEvent.properties()), History::EventWriteCreated); } QList voiceEvents = mPlugin->eventsForThread(voiceThread); QCOMPARE(voiceEvents.count(), 50); Q_FOREACH(const QVariantMap &voiceEvent, voiceEvents) { QCOMPARE(voiceEvent[History::FieldAccountId], voiceThread[History::FieldAccountId]); QCOMPARE(voiceEvent[History::FieldThreadId], voiceThread[History::FieldThreadId]); QCOMPARE(voiceEvent[History::FieldType], voiceThread[History::FieldType]); } } void SqlitePluginTest::testGetSingleEvent_data() { QTest::addColumn("event"); // for test purposes, the threadId == senderId to make it easier QTest::newRow("new text event with pending flag") << History::TextEvent("oneAccountId", "theSender", "oneEventId", "theSender", QDateTime::currentDateTime(), true, "Hello World!", History::MessageTypeText, History::MessageStatusPending).properties(); QTest::newRow("text event with valid read timestamp") << History::TextEvent("otherAccountId", "otherSender", "otherEventId", "otherSender", QDateTime::currentDateTime(), false, "Hi Again!", History::MessageTypeText, History::MessageStatusDelivered, QDateTime::currentDateTime()).properties(); History::TextEventAttachments attachments; attachments << History::TextEventAttachment("mmsAccountId", "mmsSender", "mmsEventId", "mmsAttachment1", "text/plain", "/the/file/path", History::AttachmentDownloaded); QTest::newRow("text event with attachments") << History::TextEvent("mmsAccountId", "mmsSender", "mmsEventId", "mmsSender", QDateTime::currentDateTime(), false, "Hello with attachments", History::MessageTypeMultiPart, History::MessageStatusDelivered, QDateTime::currentDateTime(), "The Subject", attachments).properties(); QTest::newRow("missed call") << History::VoiceEvent("theAccountId", "theSenderId", "theEventId", "theSenderId", QDateTime::currentDateTime(), true, true).properties(); QTest::newRow("incoming call") << History::VoiceEvent("otherAccountId", "otherSenderId", "otherEventId", "otherSenderId", QDateTime::currentDateTime(), false, false, QTime(0,10,30)).properties(); } void SqlitePluginTest::testGetSingleEvent() { QFETCH(QVariantMap, event); // clear the database SQLiteDatabase::instance()->reopen(); History::EventType type = (History::EventType) event[History::FieldType].toInt(); // create the thread QVariantMap thread = mPlugin->createThreadForParticipants(event[History::FieldAccountId].toString(), type, QStringList() << event[History::FieldSenderId].toString()); QVERIFY(!thread.isEmpty()); // write the event switch (type) { case History::EventTypeText: QCOMPARE(mPlugin->writeTextEvent(event), History::EventWriteCreated); break; case History::EventTypeVoice: QCOMPARE(mPlugin->writeVoiceEvent(event), History::EventWriteCreated); break; } QVariantMap retrievedEvent = mPlugin->getSingleEvent(type, event[History::FieldAccountId].toString(), event[History::FieldThreadId].toString(), event[History::FieldEventId].toString()); QCOMPARE(retrievedEvent[History::FieldAccountId], event[History::FieldAccountId]); QCOMPARE(retrievedEvent[History::FieldThreadId], event[History::FieldThreadId]); QCOMPARE(retrievedEvent[History::FieldEventId], event[History::FieldEventId]); QCOMPARE(retrievedEvent[History::FieldSenderId], event[History::FieldSenderId]); QCOMPARE(retrievedEvent[History::FieldTimestamp], event[History::FieldTimestamp]); QCOMPARE(retrievedEvent[History::FieldNewEvent], event[History::FieldNewEvent]); switch (type) { case History::EventTypeText: QCOMPARE(retrievedEvent[History::FieldMessage], event[History::FieldMessage]); QCOMPARE(retrievedEvent[History::FieldMessageType], event[History::FieldMessageType]); QCOMPARE(retrievedEvent[History::FieldMessageStatus], event[History::FieldMessageStatus]); QCOMPARE(retrievedEvent[History::FieldReadTimestamp], event[History::FieldReadTimestamp]); break; case History::EventTypeVoice: QCOMPARE(retrievedEvent[History::FieldMissed], event[History::FieldMissed]); QCOMPARE(retrievedEvent[History::FieldDuration], event[History::FieldDuration]); break; } } void SqlitePluginTest::testFilterToString_data() { QTest::addColumn("filterProperties"); QTest::addColumn("filterValues"); QTest::addColumn("propertyPrefix"); QTest::addColumn("resultString"); History::Filter filter; QVariantMap filterValues; filter.setFilterProperty("testProperty"); filter.setFilterValue("stringValue"); filterValues[":filterValue0"] = filter.filterValue(); QTest::newRow("simple string filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0"; filter.setFilterValue(12345); filterValues[":filterValue0"] = filter.filterValue(); QTest::newRow("simple integer filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0"; filter.setFilterValue(true); filterValues[":filterValue0"] = filter.filterValue(); QTest::newRow("simple true boolean filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0"; filter.setFilterValue(false); filterValues[":filterValue0"] = filter.filterValue(); QTest::newRow("simple false boolean filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0"; filter.setFilterValue(12345); filterValues[":filterValue0"] = filter.filterValue(); QTest::newRow("filter with a prefix") << filter.properties() << filterValues << QString("prefix") << "prefix.testProperty=:filterValue0"; filter.setMatchFlags(History::MatchContains); filter.setFilterValue("partialString"); filterValues.clear(); QTest::newRow("match contains") << filter.properties() << filterValues << QString() << "testProperty LIKE '\%partialString\%' ESCAPE '\\'"; filter.setFilterValue("%"); filterValues.clear(); QTest::newRow("partial match escaped") << filter.properties() << filterValues << QString() << "testProperty LIKE '\%\\\%\%' ESCAPE '\\'"; History::IntersectionFilter intersectionFilter; filter.setMatchFlags(History::MatchFlags()); filter.setFilterValue(12345); intersectionFilter.append(filter); filter.setFilterValue(true); intersectionFilter.append(filter); filter.setFilterValue("a string"); intersectionFilter.append(filter); filterValues.clear(); filterValues[":filterValue0"] = 12345; filterValues[":filterValue1"] = true; filterValues[":filterValue2"] = "a string"; QTest::newRow("intersection filter") << intersectionFilter.properties() << filterValues << QString() << "( (testProperty=:filterValue0) AND (testProperty=:filterValue1) AND (testProperty=:filterValue2) )"; History::UnionFilter unionFilter; filter.setFilterValue(12345); unionFilter.append(filter); filter.setFilterValue(true); unionFilter.append(filter); filter.setFilterValue("a string"); unionFilter.append(filter); filterValues.clear(); filterValues[":filterValue0"] = 12345; filterValues[":filterValue1"] = true; filterValues[":filterValue2"] = "a string"; QTest::newRow("union filter") << unionFilter.properties() << filterValues << QString() << "( (testProperty=:filterValue0) OR (testProperty=:filterValue1) OR (testProperty=:filterValue2) )"; } void SqlitePluginTest::testFilterToString() { QFETCH(QVariantMap, filterProperties); QFETCH(QVariantMap, filterValues); QFETCH(QString, propertyPrefix); QFETCH(QString, resultString); QVariantMap resultValues; QString result = mPlugin->filterToString(History::Filter::fromProperties(filterProperties), resultValues, propertyPrefix); QCOMPARE(result, resultString); QCOMPARE(resultValues, filterValues); } void SqlitePluginTest::testEscapeFilterValue_data() { QTest::addColumn("originalString"); QTest::addColumn("escapedString"); QTest::newRow("backslash") << QString("\\") << QString("\\\\"); QTest::newRow("single quote") << QString("'") << QString("''"); QTest::newRow("percent") << QString("%") << QString("\\%"); QTest::newRow("underscore") << QString("_") << QString("\\_"); QTest::newRow("string with all of that") << QString("\\0\"'%_bla") << QString("\\\\0\"''\\%\\_bla"); } void SqlitePluginTest::testEscapeFilterValue() { QFETCH(QString, originalString); QFETCH(QString, escapedString); QCOMPARE(mPlugin->escapeFilterValue(originalString), escapedString); } QTEST_MAIN(SqlitePluginTest) #include "SqlitePluginTest.moc" history-service-0.1+16.04.20160104/tests/plugins/sqlite/SqliteThreadViewTest.cpp0000644000015600001650000001220712642552153027772 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "sqlitehistoryplugin.h" #include "sqlitehistorythreadview.h" #include "sqlitedatabase.h" #include "textevent.h" #include "voiceevent.h" #include "unionfilter.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::MatchFlags) #define THREAD_COUNT 50 class SqliteThreadViewTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testNextPage(); void testFilter(); void testSort(); private: SQLiteHistoryPlugin *mPlugin; void populateDatabase(); }; void SqliteThreadViewTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qputenv("HISTORY_SQLITE_DBPATH", ":memory:"); mPlugin = new SQLiteHistoryPlugin(this); populateDatabase(); } void SqliteThreadViewTest::testNextPage() { // create a view to return all text threads and check that the right number of items get returned History::PluginThreadView *view = mPlugin->queryThreads(History::EventTypeText); QVERIFY(view->IsValid()); QList threads = view->NextPage(); QList allThreads; while (threads.count() > 0) { allThreads << threads; threads = view->NextPage(); } QCOMPARE(allThreads.count(), THREAD_COUNT); Q_FOREACH(const QVariantMap &thread, threads) { QCOMPARE(thread[History::FieldType].toInt(), (int) History::EventTypeText); } delete view; } void SqliteThreadViewTest::testFilter() { History::UnionFilter filter; filter.append(History::Filter(History::FieldAccountId, "account10")); filter.append(History::Filter(History::FieldAccountId, "account35")); History::PluginThreadView *view = mPlugin->queryThreads(History::EventTypeVoice, History::Sort(History::FieldAccountId), filter); QVERIFY(view->IsValid()); QList threads = view->NextPage(); QCOMPARE(threads.count(), 2); QCOMPARE(threads.first()[History::FieldAccountId].toString(), QString("account10")); QCOMPARE(threads.first()[History::FieldType].toInt(), (int) History::EventTypeVoice); QCOMPARE(threads.last()[History::FieldAccountId].toString(), QString("account35")); QCOMPARE(threads.last()[History::FieldType].toInt(), (int) History::EventTypeVoice); // make sure no more items are returned QVERIFY(view->NextPage().isEmpty()); delete view; } void SqliteThreadViewTest::testSort() { History::Sort ascendingSort(History::FieldAccountId, Qt::AscendingOrder); History::PluginThreadView *view = mPlugin->queryThreads(History::EventTypeText, ascendingSort); QVERIFY(view->IsValid()); QList allThreads; QList threads = view->NextPage(); while (!threads.isEmpty()) { allThreads << threads; threads = view->NextPage(); } QCOMPARE(allThreads.first()[History::FieldAccountId].toString(), QString("account00")); QCOMPARE(allThreads.last()[History::FieldAccountId].toString(), QString("account%1").arg(THREAD_COUNT-1)); delete view; History::Sort descendingSort(History::FieldAccountId, Qt::DescendingOrder); allThreads.clear(); view = mPlugin->queryThreads(History::EventTypeVoice, descendingSort); QVERIFY(view->IsValid()); threads = view->NextPage(); while (!threads.isEmpty()) { allThreads << threads; threads = view->NextPage(); } QCOMPARE(allThreads.first()[History::FieldAccountId].toString(), QString("account%1").arg(THREAD_COUNT-1)); QCOMPARE(allThreads.last()[History::FieldAccountId].toString(), QString("account00")); delete view; } void SqliteThreadViewTest::populateDatabase() { mPlugin->beginBatchOperation(); // create voice threads for (int i = 0; i < THREAD_COUNT; ++i) { mPlugin->createThreadForParticipants(QString("account%1").arg(i, 2, 10, QChar('0')), History::EventTypeVoice, QStringList() << QString("participant%1").arg(i, 2, 10, QChar('0'))); } // and the text threads for (int i = 0; i < THREAD_COUNT; ++i) { mPlugin->createThreadForParticipants(QString("account%1").arg(i, 2, 10, QChar('0')), History::EventTypeText, QStringList() << QString("participant%1").arg(i, 2, 10, QChar('0'))); } mPlugin->endBatchOperation(); } QTEST_MAIN(SqliteThreadViewTest) #include "SqliteThreadViewTest.moc" history-service-0.1+16.04.20160104/tests/plugins/sqlite/SqliteEventViewTest.cpp0000644000015600001650000001544112642552153027647 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "sqlitehistoryplugin.h" #include "sqlitedatabase.h" #include "sqlitehistoryeventview.h" #include "textevent.h" #include "voiceevent.h" #include "intersectionfilter.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::MatchFlags) #define EVENT_COUNT 50 class SqliteEventViewTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testNextPage(); void testFilter(); void testSort(); private: SQLiteHistoryPlugin *mPlugin; void populateDatabase(); }; void SqliteEventViewTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qputenv("HISTORY_SQLITE_DBPATH", ":memory:"); mPlugin = new SQLiteHistoryPlugin(this); populateDatabase(); } void SqliteEventViewTest::testNextPage() { // create a view to return all text threads and check that the right number of items get returned History::PluginEventView *view = mPlugin->queryEvents(History::EventTypeText); QVERIFY(view->IsValid()); QList events = view->NextPage(); QList allEvents; while (events.count() > 0) { allEvents << events; events = view->NextPage(); } QCOMPARE(allEvents.count(), EVENT_COUNT * 2); Q_FOREACH(const QVariantMap &event, events) { QCOMPARE(event[History::FieldType].toInt(), (int) History::EventTypeText); } delete view; } void SqliteEventViewTest::testFilter() { History::IntersectionFilter filter; filter.append(History::Filter(History::FieldAccountId, "account0")); filter.append(History::Filter(History::FieldThreadId, "participant0")); filter.append(History::Filter(History::FieldEventId, "event21")); History::PluginEventView *view = mPlugin->queryEvents(History::EventTypeVoice, History::Sort(History::FieldAccountId), filter); QVERIFY(view->IsValid()); QList events = view->NextPage(); QCOMPARE(events.count(), 1); QVariantMap event = events.first(); QCOMPARE(event[History::FieldAccountId].toString(), QString("account0")); QCOMPARE(event[History::FieldType].toInt(), (int) History::EventTypeVoice); QCOMPARE(event[History::FieldThreadId].toString(), QString("participant0")); QCOMPARE(event[History::FieldEventId].toString(), QString("event21")); // make sure no more items are returned QVERIFY(view->NextPage().isEmpty()); delete view; } void SqliteEventViewTest::testSort() { History::Sort ascendingSort(History::FieldEventId, Qt::AscendingOrder); History::PluginEventView *view = mPlugin->queryEvents(History::EventTypeText, ascendingSort); QVERIFY(view->IsValid()); QList allEvents; QList events = view->NextPage(); while (!events.isEmpty()) { allEvents << events; events = view->NextPage(); } QCOMPARE(allEvents.first()[History::FieldEventId].toString(), QString("event00")); QCOMPARE(allEvents.last()[History::FieldEventId].toString(), QString("event%1").arg(EVENT_COUNT-1)); delete view; History::Sort descendingSort(History::FieldEventId, Qt::DescendingOrder); allEvents.clear(); view = mPlugin->queryEvents(History::EventTypeVoice, descendingSort); QVERIFY(view->IsValid()); events = view->NextPage(); while (!events.isEmpty()) { allEvents << events; events = view->NextPage(); } QCOMPARE(allEvents.first()[History::FieldEventId].toString(), QString("event%1").arg(EVENT_COUNT-1)); QCOMPARE(allEvents.last()[History::FieldEventId].toString(), QString("event00")); delete view; } void SqliteEventViewTest::populateDatabase() { mPlugin->beginBatchOperation(); // create two threads of each type for (int i = 0; i < 2; ++i) { QVariantMap voiceThread = mPlugin->createThreadForParticipants(QString("account%1").arg(i), History::EventTypeVoice, QStringList() << QString("participant%1").arg(i)); // now create some events for this thread for (int j = 0; j < EVENT_COUNT; ++j) { History::VoiceEvent voiceEvent(voiceThread[History::FieldAccountId].toString(), voiceThread[History::FieldThreadId].toString(), QString("event%1").arg(j, 2, 10, QChar('0')), j % 2 ? "self" : QString("participant%1").arg(i), QDateTime::currentDateTime(), j % 2, j % 2, j % 2 ? QTime(i, j, 0) : QTime()); QCOMPARE(mPlugin->writeVoiceEvent(voiceEvent.properties()), History::EventWriteCreated); } QVariantMap textThread = mPlugin->createThreadForParticipants(QString("account%1").arg(i), History::EventTypeText, QStringList() << QString("participant%1").arg(i)); for (int j = 0; j < EVENT_COUNT; ++j) { History::TextEvent textEvent(textThread[History::FieldAccountId].toString(), textThread[History::FieldThreadId].toString(), QString("event%1").arg(j, 2, 10, QChar('0')), j % 2 ? "self" : QString("participant%1").arg(i), QDateTime::currentDateTime(), j % 2, QString("Hello %1").arg(j), History::MessageTypeText, History::MessageStatusDelivered); QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteCreated); } } mPlugin->endBatchOperation(); } QTEST_MAIN(SqliteEventViewTest) #include "SqliteEventViewTest.moc" history-service-0.1+16.04.20160104/tests/plugins/sqlite/CMakeLists.txt0000644000015600001650000000105012642552153025734 0ustar pbuserpbgroup00000000000000include_directories( ${CMAKE_SOURCE_DIR}/plugins/sqlite ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR} ) generate_test(SqlitePluginTest SOURCES SqlitePluginTest.cpp LIBRARIES historyservice sqlitehistoryplugin QT5_MODULES Core DBus Test Sql) generate_test(SqliteThreadViewTest SOURCES SqliteThreadViewTest.cpp LIBRARIES historyservice sqlitehistoryplugin QT5_MODULES Core DBus Test Sql) generate_test(SqliteEventViewTest SOURCES SqliteEventViewTest.cpp LIBRARIES historyservice sqlitehistoryplugin QT5_MODULES Core DBus Test Sql) history-service-0.1+16.04.20160104/tests/libhistoryservice/0000755000015600001650000000000012642552414023767 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/libhistoryservice/EventViewTest.cpp0000644000015600001650000002713412642552153027256 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "eventview.h" #include "intersectionfilter.h" #include "manager.h" #include "thread.h" #include "textevent.h" #include "unionfilter.h" #include "voiceevent.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::MatchFlags) #define EVENT_COUNT 50 class EventViewTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testNextPage(); void testFilter_data(); void testFilter(); void testSort(); private: void populate(); }; void EventViewTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); populate(); } void EventViewTest::testNextPage() { // create a view to return all text threads and check that the right number of items get returned History::EventViewPtr view = History::Manager::instance()->queryEvents(History::EventTypeText); QVERIFY(view->isValid()); History::Events events = view->nextPage(); History::Events allEvents; while (events.count() > 0) { allEvents << events; events = view->nextPage(); } QCOMPARE(allEvents.count(), EVENT_COUNT * 2 + 1); // include the group text event Q_FOREACH(const History::Event &event, events) { QCOMPARE(event.type(), History::EventTypeText); } } void EventViewTest::testFilter_data() { QTest::addColumn("filterProperties"); QTest::addColumn("eventType"); QTest::addColumn("resultCount"); QTest::addColumn("firstEventProperties"); History::IntersectionFilter filter; filter.append(History::Filter(History::FieldAccountId, "account0")); filter.append(History::Filter(History::FieldThreadId, "participant0")); filter.append(History::Filter(History::FieldEventId, "event21")); History::VoiceEvent voiceEvent = History::Manager::instance()->getSingleEvent(History::EventTypeVoice, "account0", "participant0", "event21"); QVERIFY(!voiceEvent.isNull()); QTest::newRow("filter by accountId, threadId and eventId") << filter.properties() << History::EventTypeVoice << 1 << voiceEvent.properties(); filter.clear(); QStringList participants; participants << "groupParticipant1" << "groupParticipant2"; History::Thread thread = History::Manager::instance()->threadForParticipants("groupAccount", History::EventTypeText, participants, History::MatchCaseSensitive); QVERIFY(!thread.isNull()); History::TextEvent textEvent = History::Manager::instance()->getSingleEvent(History::EventTypeText, thread.accountId(), thread.threadId(), "groupEvent0"); QVERIFY(!textEvent.isNull()); filter.append(History::Filter(History::FieldAccountId, thread.accountId())); filter.append(History::Filter(History::FieldThreadId, thread.threadId())); QTest::newRow("filter for a group conversation") << filter.properties() << History::EventTypeText << 1 << textEvent.properties(); } void EventViewTest::testFilter() { QFETCH(QVariantMap, filterProperties); QFETCH(History::EventType, eventType); QFETCH(int, resultCount); QFETCH(QVariantMap, firstEventProperties); History::Filter filter = History::Filter::fromProperties(filterProperties); History::EventViewPtr view = History::Manager::instance()->queryEvents(eventType, History::Sort(History::FieldAccountId), filter); QVERIFY(view->isValid()); History::Events events = view->nextPage(); QCOMPARE(events.count(), resultCount); History::Event event = events.first(); QCOMPARE(event.properties(), firstEventProperties); // make sure no more items are returned QVERIFY(view->nextPage().isEmpty()); } void EventViewTest::testSort() { History::Sort ascendingSort(History::FieldEventId, Qt::AscendingOrder); History::EventViewPtr view = History::Manager::instance()->queryEvents(History::EventTypeText, ascendingSort); QVERIFY(view->isValid()); History::Events allEvents; History::Events events = view->nextPage(); while (!events.isEmpty()) { allEvents << events; events = view->nextPage(); } QCOMPARE(allEvents.first().eventId(), QString("event00")); QCOMPARE(allEvents.last().eventId(), QString("groupEvent0")); History::Sort descendingSort(History::FieldEventId, Qt::DescendingOrder); allEvents.clear(); view = History::Manager::instance()->queryEvents(History::EventTypeVoice, descendingSort); QVERIFY(view->isValid()); events = view->nextPage(); while (!events.isEmpty()) { allEvents << events; events = view->nextPage(); } QCOMPARE(allEvents.first().eventId(), QString("groupEvent0")); QCOMPARE(allEvents.last().eventId(), QString("event00")); } void EventViewTest::populate() { // create two threads of each type for (int i = 0; i < 2; ++i) { History::Thread voiceThread = History::Manager::instance()->threadForParticipants(QString("account%1").arg(i), History::EventTypeVoice, QStringList() << QString("participant%1").arg(i), History::MatchCaseSensitive, true); QVERIFY(!voiceThread.isNull()); History::Thread textThread = History::Manager::instance()->threadForParticipants(QString("account%1").arg(i), History::EventTypeText, QStringList() << QString("participant%1").arg(i), History::MatchCaseSensitive, true); QVERIFY(!textThread.isNull()); // now create some events for the threads History::Events events; for (int j = 0; j < EVENT_COUNT; ++j) { History::VoiceEvent voiceEvent(voiceThread.accountId(), voiceThread.threadId(), QString("event%1").arg(j, 2, 10, QChar('0')), j % 2 ? "self" : QString("participant%1").arg(i), QDateTime::currentDateTime(), j % 2, j % 2, j % 2 ? QTime(i, j, 0) : QTime()); History::TextEvent textEvent(textThread.accountId(), textThread.threadId(), QString("event%1").arg(j, 2, 10, QChar('0')), j % 2 ? "self" : QString("participant%1").arg(i), QDateTime::currentDateTime(), j % 2, QString("Hello %1").arg(j), History::MessageTypeText, History::MessageStatusDelivered); events << voiceEvent << textEvent; } QVERIFY(History::Manager::instance()->writeEvents(events)); } // create a text thread with multiple participants QStringList participants; participants << "groupParticipant1" << "groupParticipant2"; History::Thread groupTextThread = History::Manager::instance()->threadForParticipants("groupAccount", History::EventTypeText, participants, History::MatchCaseSensitive, true); QVERIFY(!groupTextThread.isNull()); // and write a single event to it, just to make sure it works History::TextEvent groupTextEvent("groupAccount", groupTextThread.threadId(), "groupEvent0", "groupSender", QDateTime::currentDateTime(), true, "A group message", History::MessageTypeText); QVERIFY(History::Manager::instance()->writeEvents(History::Events() << groupTextEvent)); // create a text thread with multiple participants participants.clear(); participants << "groupParticipant1" << "groupParticipant2"; History::Thread groupVoiceThread = History::Manager::instance()->threadForParticipants("groupAccount", History::EventTypeVoice, participants, History::MatchCaseSensitive, true); QVERIFY(!groupVoiceThread.isNull()); // and write a single event to it, just to make sure it works History::VoiceEvent groupVoiceEvent("groupAccount", groupVoiceThread.threadId(), "groupEvent0", "groupSender", QDateTime::currentDateTime(), true, true); QVERIFY(History::Manager::instance()->writeEvents(History::Events() << groupVoiceEvent)); } QTEST_MAIN(EventViewTest) #include "EventViewTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/ThreadViewTest.cpp0000644000015600001650000001224212642552153027376 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "manager.h" #include "thread.h" #include "threadview.h" #include "textevent.h" #include "unionfilter.h" #include "voiceevent.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::MatchFlags) Q_DECLARE_METATYPE(History::Threads) Q_DECLARE_METATYPE(History::Events) #define THREAD_COUNT 50 class ThreadViewTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testNextPage(); void testFilter(); void testSort(); private: void populate(); }; void ThreadViewTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); populate(); } void ThreadViewTest::testNextPage() { // create a view to return all text threads and check that the right number of items get returned History::ThreadViewPtr view = History::Manager::instance()->queryThreads(History::EventTypeText); QVERIFY(view->isValid()); History::Threads threads = view->nextPage(); History::Threads allThreads; while (threads.count() > 0) { allThreads << threads; threads = view->nextPage(); } QCOMPARE(allThreads.count(), THREAD_COUNT); Q_FOREACH(const History::Thread &thread, threads) { QCOMPARE(thread.type(), History::EventTypeText); } } void ThreadViewTest::testFilter() { History::UnionFilter filter; filter.append(History::Filter(History::FieldAccountId, "account10")); filter.append(History::Filter(History::FieldAccountId, "account35")); History::ThreadViewPtr view = History::Manager::instance()->queryThreads(History::EventTypeVoice, History::Sort(History::FieldAccountId), filter); QVERIFY(view->isValid()); History::Threads threads = view->nextPage(); QCOMPARE(threads.count(), 2); QCOMPARE(threads.first().accountId(), QString("account10")); QCOMPARE(threads.first().type(), History::EventTypeVoice); QCOMPARE(threads.last().accountId(), QString("account35")); QCOMPARE(threads.last().type(), History::EventTypeVoice); // make sure no more items are returned QVERIFY(view->nextPage().isEmpty()); } void ThreadViewTest::testSort() { History::Sort ascendingSort(History::FieldAccountId, Qt::AscendingOrder); History::ThreadViewPtr view = History::Manager::instance()->queryThreads(History::EventTypeText, ascendingSort); QVERIFY(view->isValid()); History::Threads allThreads; History::Threads threads = view->nextPage(); while (!threads.isEmpty()) { allThreads << threads; threads = view->nextPage(); } QCOMPARE(allThreads.first().accountId(), QString("account00")); QCOMPARE(allThreads.last().accountId(), QString("account%1").arg(THREAD_COUNT-1)); History::Sort descendingSort(History::FieldAccountId, Qt::DescendingOrder); allThreads.clear(); view = History::Manager::instance()->queryThreads(History::EventTypeVoice, descendingSort); QVERIFY(view->isValid()); threads = view->nextPage(); while (!threads.isEmpty()) { allThreads << threads; threads = view->nextPage(); } QCOMPARE(allThreads.first().accountId(), QString("account%1").arg(THREAD_COUNT-1)); QCOMPARE(allThreads.last().accountId(), QString("account00")); } void ThreadViewTest::populate() { // create voice threads for (int i = 0; i < THREAD_COUNT; ++i) { History::Manager::instance()->threadForParticipants(QString("account%1").arg(i, 2, 10, QChar('0')), History::EventTypeVoice, QStringList() << QString("participant%1").arg(i, 2, 10, QChar('0')), History::MatchCaseSensitive, true); } // and the text threads for (int i = 0; i < THREAD_COUNT; ++i) { History::Manager::instance()->threadForParticipants(QString("account%1").arg(i, 2, 10, QChar('0')), History::EventTypeText, QStringList() << QString("participant%1").arg(i, 2, 10, QChar('0')), History::MatchCaseSensitive, true); } } QTEST_MAIN(ThreadViewTest) #include "ThreadViewTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/VoiceEventTest.cpp0000644000015600001650000002610612642552153027407 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "voiceevent.h" class VoiceEventTest : public QObject { Q_OBJECT private Q_SLOTS: void testCreateNewEvent_data(); void testCreateNewEvent(); void testCastToEventAndBack(); void testFromProperties_data(); void testFromProperties(); void testFromNullProperties(); void testProperties_data(); void testProperties(); private: History::Participants participantsFromIdentifiers(const QString &accountId, const QStringList &identifiers); }; void VoiceEventTest::testCreateNewEvent_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("senderId"); QTest::addColumn("timestamp"); QTest::addColumn("newEvent"); QTest::addColumn("missed"); QTest::addColumn("duration"); QTest::addColumn("remoteParticipant"); QTest::addColumn("participants"); QTest::newRow("unread missed call") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << true << QTime(0, 0, 0) << QString("remoteParticipant") << (QStringList() << "testSenderId"); QTest::newRow("missed call") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-5) << false << true << QTime(0, 0, 0) << QString("remoteParticipant2") << (QStringList() << "testSenderId2"); QTest::newRow("not missed call") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << false << false << QTime(1, 2, 3) << QString("remoteParticipant") << (QStringList() << "testSenderId"); } void VoiceEventTest::testCreateNewEvent() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, senderId); QFETCH(QDateTime, timestamp); QFETCH(bool, newEvent); QFETCH(bool, missed); QFETCH(QTime, duration); QFETCH(QString, remoteParticipant); QFETCH(QStringList, participants); History::VoiceEvent event(accountId, threadId, eventId, senderId, timestamp, newEvent, missed, duration, remoteParticipant, participantsFromIdentifiers(accountId, participants)); // check that the values are properly set QCOMPARE(event.accountId(), accountId); QCOMPARE(event.threadId(), threadId); QCOMPARE(event.eventId(), eventId); QCOMPARE(event.senderId(), senderId); QCOMPARE(event.timestamp(), timestamp); QCOMPARE(event.newEvent(), newEvent); QCOMPARE(event.missed(), missed); QCOMPARE(event.duration(), duration); QCOMPARE(event.remoteParticipant(), remoteParticipant); QCOMPARE(event.participants().identifiers(), participants); } void VoiceEventTest::testCastToEventAndBack() { QString accountId("oneAccountId"); History::VoiceEvent voiceEvent(accountId, "oneThreadId", "oneEventId", "oneSender", QDateTime::currentDateTime(), true, true, QTime(1,2,3), "remoteParticipant", participantsFromIdentifiers(accountId, QStringList() << "oneParticipant")); // test the copy constructor History::Event historyEvent(voiceEvent); QVERIFY(historyEvent == voiceEvent); History::VoiceEvent castBack(historyEvent); QVERIFY(castBack == voiceEvent); // and now the assignment operator History::Event anotherEvent; anotherEvent = voiceEvent; QVERIFY(anotherEvent == voiceEvent); History::VoiceEvent backAgain; backAgain = anotherEvent; QVERIFY(backAgain == voiceEvent); } void VoiceEventTest::testFromProperties_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("senderId"); QTest::addColumn("timestamp"); QTest::addColumn("newEvent"); QTest::addColumn("missed"); QTest::addColumn("duration"); QTest::addColumn("participants"); QTest::newRow("unread missed call") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << true << QTime(0, 0, 0) << (QStringList() << "testParticipant"); QTest::newRow("missed call") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-5) << false << true << QTime(0, 0, 0) << (QStringList() << "testParticipant2"); QTest::newRow("not missed call") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << false << false << QTime(1, 2, 3) << (QStringList() << "testParticipant"); } void VoiceEventTest::testFromProperties() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, senderId); QFETCH(QDateTime, timestamp); QFETCH(bool, newEvent); QFETCH(bool, missed); QFETCH(QTime, duration); QFETCH(QStringList, participants); QVariantMap properties; properties[History::FieldAccountId] = accountId; properties[History::FieldThreadId] = threadId; properties[History::FieldEventId] = eventId; properties[History::FieldSenderId] = senderId; properties[History::FieldTimestamp] = timestamp.toString(Qt::ISODate); properties[History::FieldNewEvent] = newEvent; properties[History::FieldMissed] = missed; properties[History::FieldDuration] = QTime(0,0,0,0).secsTo(duration); properties[History::FieldParticipants] = participantsFromIdentifiers(accountId, participants).toVariantList(); History::VoiceEvent voiceEvent = History::VoiceEvent::fromProperties(properties); QCOMPARE(voiceEvent.accountId(), accountId); QCOMPARE(voiceEvent.threadId(), threadId); QCOMPARE(voiceEvent.eventId(), eventId); QCOMPARE(voiceEvent.senderId(), senderId); QCOMPARE(voiceEvent.timestamp().toString(Qt::ISODate), timestamp.toString(Qt::ISODate)); QCOMPARE(voiceEvent.newEvent(), newEvent); QCOMPARE(voiceEvent.missed(), missed); QCOMPARE(voiceEvent.duration(), duration); QCOMPARE(voiceEvent.participants().identifiers(), participants); } void VoiceEventTest::testFromNullProperties() { // just to make sure, test that calling ::fromProperties() on an empty map returns a null event History::Event nullEvent = History::VoiceEvent::fromProperties(QVariantMap()); QVERIFY(nullEvent.isNull()); QCOMPARE(nullEvent.type(), History::EventTypeNull); } void VoiceEventTest::testProperties_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("senderId"); QTest::addColumn("timestamp"); QTest::addColumn("newEvent"); QTest::addColumn("missed"); QTest::addColumn("duration"); QTest::addColumn("remoteParticipant"); QTest::addColumn("participants"); QTest::newRow("unread missed call") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << true << QTime(0, 0, 0) << QString("remoteParticipant") << (QStringList() << "testParticipant"); QTest::newRow("missed call") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-5) << false << true << QTime(0, 0, 0) << QString("remoteParticipant2") << (QStringList() << "testParticipant2"); QTest::newRow("not missed call") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << false << false << QTime(1, 2, 3) << QString("remoteParticipant3") << (QStringList() << "testParticipant3"); } void VoiceEventTest::testProperties() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, senderId); QFETCH(QDateTime, timestamp); QFETCH(bool, newEvent); QFETCH(bool, missed); QFETCH(QTime, duration); QFETCH(QString, remoteParticipant); QFETCH(QStringList, participants); History::VoiceEvent event(accountId, threadId, eventId, senderId, timestamp, newEvent, missed, duration, remoteParticipant, participantsFromIdentifiers(accountId, participants)); // check that the values are properly set QVariantMap properties = event.properties(); QCOMPARE(properties[History::FieldAccountId].toString(), accountId); QCOMPARE(properties[History::FieldThreadId].toString(), threadId); QCOMPARE(properties[History::FieldEventId].toString(), eventId); QCOMPARE(properties[History::FieldSenderId].toString(), senderId); QCOMPARE(properties[History::FieldTimestamp].toString(), timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz")); QCOMPARE(properties[History::FieldNewEvent].toBool(), newEvent); QCOMPARE(properties[History::FieldMissed].toBool(), missed); QCOMPARE(QTime(0,0).addSecs(properties[History::FieldDuration].toInt()), duration); QCOMPARE(properties[History::FieldRemoteParticipant].toString(), remoteParticipant); QCOMPARE(History::Participants::fromVariant(properties[History::FieldParticipants]).identifiers(), participants); } History::Participants VoiceEventTest::participantsFromIdentifiers(const QString &accountId, const QStringList &identifiers) { History::Participants participants; Q_FOREACH(const QString &identifier, identifiers) { participants << History::Participant(accountId, identifier); } return participants; } QTEST_MAIN(VoiceEventTest) #include "VoiceEventTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/SortTest.cpp0000644000015600001650000001336712642552153026274 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "sort.h" Q_DECLARE_METATYPE(Qt::SortOrder) Q_DECLARE_METATYPE(Qt::CaseSensitivity) class SortTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCreateNewSort_data(); void testCreateNewSort(); void testCopyConstructor(); void testSetSortProperties_data(); void testSetSortProperties(); void testFromProperties_data(); void testFromProperties(); void testFromNullProperties(); void testProperties_data(); void testProperties(); }; void SortTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void SortTest::testCreateNewSort_data() { QTest::addColumn("sortField"); QTest::addColumn("sortOrder"); QTest::addColumn("caseSensitivity"); QTest::newRow("threadId field ascending case sensitive") << "threadId" << Qt::AscendingOrder << Qt::CaseSensitive; QTest::newRow("eventId field descending case insensitive") << "threadId" << Qt::DescendingOrder << Qt::CaseInsensitive; } void SortTest::testCreateNewSort() { QFETCH(QString, sortField); QFETCH(Qt::SortOrder, sortOrder); QFETCH(Qt::CaseSensitivity, caseSensitivity); History::Sort sort(sortField, sortOrder, caseSensitivity); QCOMPARE(sort.sortField(), sortField); QCOMPARE(sort.sortOrder(), sortOrder); QCOMPARE(sort.caseSensitivity(), caseSensitivity); } void SortTest::testCopyConstructor() { History::Sort sort(History::FieldCount, Qt::DescendingOrder, Qt::CaseSensitive); History::Sort otherSort(sort); QCOMPARE(otherSort.sortField(), sort.sortField()); QCOMPARE(otherSort.sortOrder(), sort.sortOrder()); QCOMPARE(otherSort.caseSensitivity(), sort.caseSensitivity()); } void SortTest::testSetSortProperties_data() { QTest::addColumn("sortField"); QTest::addColumn("sortOrder"); QTest::addColumn("caseSensitivity"); QTest::newRow("threadId field ascending case sensitive") << "threadId" << Qt::AscendingOrder << Qt::CaseSensitive; QTest::newRow("eventId field descending case insensitive") << "threadId" << Qt::DescendingOrder << Qt::CaseInsensitive; } void SortTest::testSetSortProperties() { QFETCH(QString, sortField); QFETCH(Qt::SortOrder, sortOrder); QFETCH(Qt::CaseSensitivity, caseSensitivity); History::Sort sort; sort.setSortField(sortField); QCOMPARE(sort.sortField(), sortField); sort.setSortOrder(sortOrder); QCOMPARE(sort.sortOrder(), sortOrder); sort.setCaseSensitivity(caseSensitivity); QCOMPARE(sort.caseSensitivity(), caseSensitivity); } void SortTest::testFromProperties_data() { QTest::addColumn("sortField"); QTest::addColumn("sortOrder"); QTest::addColumn("caseSensitivity"); QTest::newRow("threadId field ascending case sensitive") << "threadId" << Qt::AscendingOrder << Qt::CaseSensitive; QTest::newRow("eventId field descending case insensitive") << "threadId" << Qt::DescendingOrder << Qt::CaseInsensitive; } void SortTest::testFromProperties() { QFETCH(QString, sortField); QFETCH(Qt::SortOrder, sortOrder); QFETCH(Qt::CaseSensitivity, caseSensitivity); QVariantMap properties; properties[History::FieldSortField] = sortField; properties[History::FieldSortOrder] = (int) sortOrder; properties[History::FieldCaseSensitivity] = (int) caseSensitivity; History::Sort sort = History::Sort::fromProperties(properties); sort.setSortField(sortField); QCOMPARE(sort.sortField(), sortField); sort.setSortOrder(sortOrder); QCOMPARE(sort.sortOrder(), sortOrder); sort.setCaseSensitivity(caseSensitivity); QCOMPARE(sort.caseSensitivity(), caseSensitivity); } void SortTest::testFromNullProperties() { History::Sort nullSort; History::Sort sort = History::Sort::fromProperties(QVariantMap()); QCOMPARE(sort.sortField(), nullSort.sortField()); QCOMPARE(sort.sortOrder(), nullSort.sortOrder()); QCOMPARE(sort.caseSensitivity(), nullSort.caseSensitivity()); } void SortTest::testProperties_data() { QTest::addColumn("sortField"); QTest::addColumn("sortOrder"); QTest::addColumn("caseSensitivity"); QTest::newRow("threadId field ascending case sensitive") << "threadId" << Qt::AscendingOrder << Qt::CaseSensitive; QTest::newRow("eventId field descending case insensitive") << "threadId" << Qt::DescendingOrder << Qt::CaseInsensitive; } void SortTest::testProperties() { QFETCH(QString, sortField); QFETCH(Qt::SortOrder, sortOrder); QFETCH(Qt::CaseSensitivity, caseSensitivity); History::Sort sort(sortField, sortOrder, caseSensitivity); QVariantMap properties = sort.properties(); QCOMPARE(properties[History::FieldSortField].toString(), sortField); QCOMPARE(properties[History::FieldSortOrder].toInt(), (int) sortOrder); QCOMPARE(properties[History::FieldCaseSensitivity].toInt(), (int) caseSensitivity); } QTEST_MAIN(SortTest) #include "SortTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/ManagerTest.cpp0000644000015600001650000004436312642552153026717 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "eventview.h" #include "manager.h" #include "thread.h" #include "threadview.h" #include "textevent.h" #include "voiceevent.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::MatchFlags) Q_DECLARE_METATYPE(History::Threads) Q_DECLARE_METATYPE(History::Events) class ManagerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testThreadForParticipants_data(); void testThreadForParticipants(); void testQueryEvents(); void testQueryThreads(); void testGetSingleThread(); void testWriteEvents(); void testRemoveEvents(); void testGetSingleEvent(); void testRemoveThreads(); void cleanupTestCase(); private: History::Manager *mManager; }; void ManagerTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); mManager = History::Manager::instance(); } void ManagerTest::testThreadForParticipants_data() { QTest::addColumn("accountId"); QTest::addColumn("type"); QTest::addColumn("participants"); QTest::addColumn("matchFlags"); QTest::addColumn("participantsToMatch"); QTest::newRow("text thread with one participant") << "oneAccountId" << History::EventTypeText << (QStringList() << "oneParticipant") << History::MatchFlags(History::MatchCaseSensitive) << (QStringList() << "oneParticipant"); QTest::newRow("voice thread using phone match") << "anotherAccountId" << History::EventTypeVoice << (QStringList() << "+554198765432") << History::MatchFlags(History::MatchPhoneNumber) << (QStringList() << "98765432"); } void ManagerTest::testThreadForParticipants() { QFETCH(QString, accountId); QFETCH(History::EventType, type); QFETCH(QStringList, participants); QFETCH(History::MatchFlags, matchFlags); QFETCH(QStringList, participantsToMatch); QSignalSpy spy(mManager, SIGNAL(threadsAdded(History::Threads))); History::Thread thread = mManager->threadForParticipants(accountId, type, participants, matchFlags, true); QVERIFY(!thread.isNull()); QTRY_COMPARE(spy.count(), 1); QCOMPARE(thread.accountId(), accountId); QCOMPARE(thread.type(), type); QCOMPARE(thread.participants().identifiers(), participants); // now try to get the thread again to see if it is returned correctly History::Thread sameThread = mManager->threadForParticipants(accountId, type, participantsToMatch, matchFlags, false); QVERIFY(!sameThread.isNull()); QCOMPARE(sameThread, thread); } void ManagerTest::testQueryEvents() { // just make sure the view returned is not null // the contents of the view will be tested in its own tests History::EventViewPtr eventView = mManager->queryEvents(History::EventTypeText); QVERIFY(!eventView.isNull()); QVERIFY(eventView->isValid()); } void ManagerTest::testQueryThreads() { // just make sure the view returned is not null // the contents of the view will be tested in its own tests History::ThreadViewPtr threadView = mManager->queryThreads(History::EventTypeVoice); QVERIFY(!threadView.isNull()); QVERIFY(threadView->isValid()); } void ManagerTest::testGetSingleThread() { History::Thread thread = mManager->threadForParticipants("theAccountId", History::EventTypeText, QStringList() << "theParticipant", History::MatchCaseSensitive, true); QVERIFY(!thread.isNull()); // try getting the same thread History::Thread sameThread = mManager->getSingleThread(thread.type(), thread.accountId(), thread.threadId()); QVERIFY(sameThread == thread); } void ManagerTest::testWriteEvents() { // create two threads, one for voice and one for text History::Thread textThread = mManager->threadForParticipants("textAccountId", History::EventTypeText, QStringList()<< "textParticipant", History::MatchCaseSensitive, true); History::Thread voiceThread = mManager->threadForParticipants("voiceAccountId", History::EventTypeVoice, QStringList()<< "voiceParticipant", History::MatchCaseSensitive, true); // insert some text and voice events History::Events events; for (int i = 0; i < 50; ++i) { History::TextEvent textEvent(textThread.accountId(), textThread.threadId(), QString("eventId%1").arg(i), textThread.participants().first().identifier(), QDateTime::currentDateTime(), true, QString("Hello world %1").arg(i), History::MessageTypeText, History::MessageStatusAccepted); events.append(textEvent); History::VoiceEvent voiceEvent(voiceThread.accountId(), voiceThread.threadId(), QString("eventId%1").arg(i), voiceThread.participants().first().identifier(), QDateTime::currentDateTime(), true, true); events.append(voiceEvent); } QSignalSpy newEventsSpy(mManager, SIGNAL(eventsAdded(History::Events))); QSignalSpy threadsModifiedSpy(mManager, SIGNAL(threadsModified(History::Threads))); QVERIFY(mManager->writeEvents(events)); QTRY_COMPARE(newEventsSpy.count(), 1); QTRY_COMPARE(threadsModifiedSpy.count(), 1); // check that the signal was emitted with the correct number of events History::Events returnedEvents = newEventsSpy.first().first().value(); // get two events to modify before sorting History::TextEvent modifiedTextEvent = events[0]; History::VoiceEvent modifiedVoiceEvent = events[1]; // just in case, sort the lists before comparing qSort(events); qSort(returnedEvents); QCOMPARE(returnedEvents, events); History::Threads returnedThreads = threadsModifiedSpy.first().first().value(); QCOMPARE(returnedThreads.count(), 2); // and now modify the events modifiedTextEvent.setNewEvent(false); modifiedTextEvent.setMessageStatus(History::MessageStatusDelivered); modifiedVoiceEvent.setNewEvent(false); QSignalSpy eventsModifiedSpy(mManager, SIGNAL(eventsModified(History::Events))); threadsModifiedSpy.clear(); events.clear(); events << modifiedTextEvent << modifiedVoiceEvent; QVERIFY(mManager->writeEvents(events)); QTRY_COMPARE(eventsModifiedSpy.count(), 1); QTRY_COMPARE(threadsModifiedSpy.count(), 1); returnedEvents = eventsModifiedSpy.first().first().value(); qDebug() << returnedEvents.first().accountId(); QCOMPARE(returnedEvents.count(), 2); returnedThreads = threadsModifiedSpy.first().first().value(); QCOMPARE(returnedThreads.count(), 2); } void ManagerTest::testRemoveEvents() { // create two threads, one for voice and one for text History::Thread textThread = mManager->threadForParticipants("textRemovableAccount", History::EventTypeText, QStringList()<< "textParticipant", History::MatchCaseSensitive, true); History::Thread voiceThread = mManager->threadForParticipants("voiceRemovableAccount", History::EventTypeVoice, QStringList()<< "voiceParticipant", History::MatchCaseSensitive, true); // insert some text and voice events History::Events events; for (int i = 0; i < 50; ++i) { History::TextEvent textEvent(textThread.accountId(), textThread.threadId(), QString("eventToBeRemoved%1").arg(i), textThread.participants().first().identifier(), QDateTime::currentDateTime(), true, QString("Hello world %1").arg(i), History::MessageTypeText); events.append(textEvent); History::VoiceEvent voiceEvent(voiceThread.accountId(), voiceThread.threadId(), QString("eventToBeRemoved%1").arg(i), voiceThread.participants().first().identifier(), QDateTime::currentDateTime(), true, true); events.append(voiceEvent); } QSignalSpy eventsRemovedSpy(mManager, SIGNAL(eventsRemoved(History::Events))); QSignalSpy threadsModifiedSpy(mManager, SIGNAL(threadsModified(History::Threads))); QVERIFY(mManager->writeEvents(events)); QTRY_COMPARE(threadsModifiedSpy.count(), 1); threadsModifiedSpy.clear(); History::Events secondRemoval; secondRemoval << events.takeFirst() << events.takeLast(); QVERIFY(mManager->removeEvents(events)); QTRY_COMPARE(eventsRemovedSpy.count(), 1); QTRY_COMPARE(threadsModifiedSpy.count(), 1); History::Events removedEvents = eventsRemovedSpy.first().first().value(); History::Threads modifiedThreads = threadsModifiedSpy.first().first().value(); qSort(events); qSort(removedEvents); QCOMPARE(removedEvents, events); QCOMPARE(modifiedThreads.count(), 2); // now remove the remaining events and make sure the threads get removed too QSignalSpy threadsRemovedSpy(mManager, SIGNAL(threadsRemoved(History::Threads))); eventsRemovedSpy.clear(); QVERIFY(mManager->removeEvents(secondRemoval)); QTRY_COMPARE(eventsRemovedSpy.count(), 1); QTRY_COMPARE(threadsRemovedSpy.count(), 1); removedEvents = eventsRemovedSpy.first().first().value(); History::Threads removedThreads = threadsRemovedSpy.first().first().value(); qSort(removedEvents); qSort(secondRemoval); QCOMPARE(removedEvents, secondRemoval); QCOMPARE(removedThreads.count(), 2); } void ManagerTest::testGetSingleEvent() { // create two threads, one for voice and one for text History::Thread textThread = mManager->threadForParticipants("textSingleAccount", History::EventTypeText, QStringList()<< "textSingleParticipant", History::MatchCaseSensitive, true); History::Thread voiceThread = mManager->threadForParticipants("voiceSingleAccount", History::EventTypeVoice, QStringList()<< "voiceSingleParticipant", History::MatchCaseSensitive, true); // now add two events History::TextEvent textEvent(textThread.accountId(), textThread.threadId(), "singleEventId", "self", QDateTime::currentDateTime(), true, "Hello big world!", History::MessageTypeText, History::MessageStatusPending); History::VoiceEvent voiceEvent(voiceThread.accountId(), voiceThread.threadId(), "singleEventId", "self", QDateTime::currentDateTime(), false, false, QTime(1,2,3)); QVERIFY(mManager->writeEvents(History::Events() << textEvent << voiceEvent)); // and now try to get them History::TextEvent retrievedTextEvent = mManager->getSingleEvent(History::EventTypeText, textEvent.accountId(), textEvent.threadId(), textEvent.eventId()); QVERIFY(retrievedTextEvent == textEvent); QCOMPARE(retrievedTextEvent.newEvent(), textEvent.newEvent()); QCOMPARE(retrievedTextEvent.message(), textEvent.message()); QCOMPARE(retrievedTextEvent.messageType(), textEvent.messageType()); QCOMPARE(retrievedTextEvent.messageStatus(), textEvent.messageStatus()); History::VoiceEvent retrievedVoiceEvent = mManager->getSingleEvent(History::EventTypeVoice, voiceEvent.accountId(), voiceEvent.threadId(), voiceEvent.eventId()); QVERIFY(retrievedVoiceEvent == voiceEvent); QCOMPARE(retrievedVoiceEvent.newEvent(), voiceEvent.newEvent()); QCOMPARE(retrievedVoiceEvent.missed(), voiceEvent.missed()); QCOMPARE(retrievedVoiceEvent.duration(), voiceEvent.duration()); } void ManagerTest::testRemoveThreads() { // create two threads, one for voice and one for text History::Thread textThread = mManager->threadForParticipants("textThreadRemovalAccount", History::EventTypeText, QStringList()<< "textParticipant", History::MatchCaseSensitive, true); QVERIFY(!textThread.isNull()); History::Thread voiceThread = mManager->threadForParticipants("voiceThreadRemovalAccount", History::EventTypeVoice, QStringList()<< "voiceParticipant", History::MatchCaseSensitive, true); QVERIFY(!voiceThread.isNull()); History::Threads threads; threads << textThread << voiceThread; // insert some text and voice events History::Events events; for (int i = 0; i < 50; ++i) { History::TextEvent textEvent(textThread.accountId(), textThread.threadId(), QString("eventToBeRemoved%1").arg(i), textThread.participants().first().identifier(), QDateTime::currentDateTime(), true, QString("Hello world %1").arg(i), History::MessageTypeText); events.append(textEvent); History::VoiceEvent voiceEvent(voiceThread.accountId(), voiceThread.threadId(), QString("eventToBeRemoved%1").arg(i), voiceThread.participants().first().identifier(), QDateTime::currentDateTime(), true, true); events.append(voiceEvent); } QVERIFY(mManager->writeEvents(events)); QSignalSpy eventsRemovedSpy(mManager, SIGNAL(eventsRemoved(History::Events))); QSignalSpy threadsRemovedSpy(mManager, SIGNAL(threadsRemoved(History::Threads))); QVERIFY(mManager->removeThreads(threads)); QTRY_COMPARE(eventsRemovedSpy.count(), 1); QTRY_COMPARE(threadsRemovedSpy.count(), 1); History::Events removedEvents = eventsRemovedSpy.first().first().value(); qSort(removedEvents); qSort(events); QCOMPARE(removedEvents, events); History::Threads removedThreads = threadsRemovedSpy.first().first().value(); qSort(removedThreads); qSort(threads); QCOMPARE(removedThreads, threads); } void ManagerTest::cleanupTestCase() { delete mManager; } QTEST_MAIN(ManagerTest) #include "ManagerTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/ContactMatcherTest.cpp0000644000015600001650000002112712642552153030235 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include #include #include #include #include #include "telepathytest.h" #include "contactmatcher_p.h" #include "types.h" #include "phoneutils_p.h" QTCONTACTS_USE_NAMESPACE class ContactMatcherTest : public TelepathyTest { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void clean(); void testMatchExistingContact_data(); void testMatchExistingContact(); void testContactAdded(); void testContactRemoved(); void testSynchronousContactInfoRequest(); void testWatchIdentifier(); protected: QContact createContact(const QString &firstName, const QString &lastName, const QStringList &phoneNumbers = QStringList(), const QStringList &extendedDetails = QStringList()); private: QContactManager *mContactManager; QContact mPhoneContact; QContact mExtendedContact; }; void ContactMatcherTest::initTestCase() { initialize(); mContactManager = new QContactManager("memory"); History::ContactMatcher::instance(mContactManager); // create two contacts to test mPhoneContact = createContact("Phone", "Contact", QStringList() << "123456789" << "7654321"); mExtendedContact = createContact("Extended", "Generic Contact", QStringList(), QStringList() << "123456789"); } void ContactMatcherTest::init() { // just add two telepathy mock accounts to make sure we get the addressable fields correctly addAccount("mock", "mock", "generic account"); addAccount("mock", "ofono", "phone account"); } void ContactMatcherTest::clean() { doCleanup(); } void ContactMatcherTest::testMatchExistingContact_data() { QTest::addColumn("accountId"); QTest::addColumn("identifier"); QTest::addColumn("contactId"); QTest::addColumn("phoneNumberCompare"); QTest::newRow("match exact phone id") << QString("mock/ofono/account0") << QString("123456789") << mPhoneContact.id().toString() << false; QTest::newRow("match phone number with prefix") << QString("mock/ofono/account0") << QString("+10987654321") << mPhoneContact.id().toString() << true; QTest::newRow("match exact extra id") << QString("mock/mock/account0") << QString("123456789") << mExtendedContact.id().toString() << false; } void ContactMatcherTest::testMatchExistingContact() { QFETCH(QString, accountId); QFETCH(QString, identifier); QFETCH(QString, contactId); QFETCH(bool, phoneNumberCompare); QSignalSpy contactInfoSpy(History::ContactMatcher::instance(), SIGNAL(contactInfoChanged(QString,QString,QVariantMap))); QVariantMap info = History::ContactMatcher::instance()->contactInfo(accountId, identifier); if (phoneNumberCompare) { QVERIFY(History::PhoneUtils::comparePhoneNumbers(info[History::FieldIdentifier].toString(), identifier)); } else { QCOMPARE(info[History::FieldIdentifier].toString(), identifier); } QTRY_COMPARE(contactInfoSpy.count(), 1); QCOMPARE(contactInfoSpy.first()[0].toString(), accountId); QCOMPARE(contactInfoSpy.first()[1].toString(), identifier); info = contactInfoSpy.first()[2].toMap(); QCOMPARE(info[History::FieldContactId].toString(), contactId); if (phoneNumberCompare) { QVERIFY(History::PhoneUtils::comparePhoneNumbers(info[History::FieldIdentifier].toString(), identifier)); } else { QCOMPARE(info[History::FieldIdentifier].toString(), identifier); } } void ContactMatcherTest::testContactAdded() { QSignalSpy contactInfoSpy(History::ContactMatcher::instance(), SIGNAL(contactInfoChanged(QString,QString,QVariantMap))); QString identifier("5555555"); QString accountId("mock/ofono/account0"); QVariantMap info = History::ContactMatcher::instance()->contactInfo(accountId, identifier); QCOMPARE(info[History::FieldIdentifier].toString(), identifier); QVERIFY(!info.contains(History::FieldContactId)); // now add a contact that matches this item QContact contact = createContact("Added", "Contact", QStringList() << identifier); QTRY_COMPARE(contactInfoSpy.count(), 1); QCOMPARE(contactInfoSpy.first()[0].toString(), accountId); QCOMPARE(contactInfoSpy.first()[1].toString(), identifier); QCOMPARE(contactInfoSpy.first()[2].toMap()[History::FieldContactId].toString(), contact.id().toString()); } void ContactMatcherTest::testContactRemoved() { QSignalSpy contactInfoSpy(History::ContactMatcher::instance(), SIGNAL(contactInfoChanged(QString,QString,QVariantMap))); QString identifier("6666666"); QString accountId("mock/ofono/account0"); QVariantMap info = History::ContactMatcher::instance()->contactInfo(accountId, identifier); QCOMPARE(info[History::FieldIdentifier].toString(), identifier); // now add a contact that matches this item QContact contact = createContact("Removed", "Contact", QStringList() << identifier); QTRY_COMPARE(contactInfoSpy.count(), 1); // now that the contact info is filled, remove the contact contactInfoSpy.clear(); QVERIFY(mContactManager->removeContact(contact.id())); QTRY_COMPARE(contactInfoSpy.count(), 1); QCOMPARE(contactInfoSpy.first()[0].toString(), accountId); QCOMPARE(contactInfoSpy.first()[1].toString(), identifier); QVERIFY(!contactInfoSpy.first()[2].toMap().contains(History::FieldContactId)); } void ContactMatcherTest::testSynchronousContactInfoRequest() { QString identifier("77777777"); QString accountId("mock/ofono/account0"); // now add a contact that matches this item QContact contact = createContact("Synchronous", "Contact", QStringList() << identifier); // now that the contact info is filled, remove the contact QVariantMap info = History::ContactMatcher::instance()->contactInfo(accountId, identifier, true); QCOMPARE(info[History::FieldIdentifier].toString(), identifier); QCOMPARE(info[History::FieldAccountId].toString(), accountId); QVERIFY(!info[History::FieldContactId].toString().isEmpty()); // and remove this contact to not interfere in the other tests QVERIFY(mContactManager->removeContact(contact.id())); } void ContactMatcherTest::testWatchIdentifier() { QString identifier("88888888"); QString accountId("mock/ofono/account0"); History::ContactMatcher::instance()->watchIdentifier(accountId, identifier); // now add a contact and make sure we get the contactInfoChanged signal QSignalSpy contactInfoSpy(History::ContactMatcher::instance(), SIGNAL(contactInfoChanged(QString,QString,QVariantMap))); QContact contact = createContact("Contact", "Watched", QStringList() << identifier); QTRY_COMPARE(contactInfoSpy.count(), 1); QCOMPARE(contactInfoSpy.first()[0].toString(), accountId); QCOMPARE(contactInfoSpy.first()[1].toString(), identifier); QVariantMap info = contactInfoSpy.first()[2].toMap(); QCOMPARE(info[History::FieldContactId].toString(), contact.id().toString()); QVERIFY(mContactManager->removeContact(contact.id())); } QContact ContactMatcherTest::createContact(const QString &firstName, const QString &lastName, const QStringList &phoneNumbers, const QStringList &extendedDetails) { QContact contact; QContactName name; name.setFirstName(firstName); name.setLastName(lastName); if (!contact.saveDetail(&name)) { return contact; } Q_FOREACH(const QString &number, phoneNumbers) { QContactPhoneNumber phoneNumber; phoneNumber.setNumber(number); if (!contact.saveDetail(&phoneNumber)) { return contact; } } Q_FOREACH(const QString &extended, extendedDetails) { QContactExtendedDetail extendedDetail; extendedDetail.setName("x-mock-im"); extendedDetail.setData(extended); if (!contact.saveDetail(&extendedDetail)) { return contact; } } mContactManager->saveContact(&contact); return contact; } QTEST_MAIN(ContactMatcherTest) #include "ContactMatcherTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/ParticipantTest.cpp0000644000015600001650000002217212642552153027615 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "participant.h" #include "types.h" Q_DECLARE_METATYPE(History::Participant) class ParticipantTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testConstructor(); void testNullParticipant(); void testIsNull_data(); void testIsNull(); void testCopyConstructor(); void testAssignmentOperator(); void testEqualsOperator_data(); void testEqualsOperator(); void testProperties(); void testFromProperties(); void testIdentifiers(); void testFromVariantList(); void testToVariantList(); void testFromVariantWithVariantList(); }; void ParticipantTest::initTestCase() { qRegisterMetaType(); } void ParticipantTest::testConstructor() { QString accountId("theAccountId"); QString identifier("theParticipantId"); QString contactId("theContactId"); QString alias("theAlias"); QString avatar("theAvatar"); QVariantMap detailProperties; detailProperties["someProperty"] = "someValue"; History::Participant participant(accountId, identifier, contactId, alias, avatar, detailProperties); QCOMPARE(participant.accountId(), accountId); QCOMPARE(participant.identifier(), identifier); QCOMPARE(participant.contactId(), contactId); QCOMPARE(participant.alias(), alias); QCOMPARE(participant.avatar(), avatar); QCOMPARE(participant.detailProperties(), detailProperties); } void ParticipantTest::testNullParticipant() { // check that a null participant returns true History::Participant nullParticipant; QVERIFY(nullParticipant.isNull()); } void ParticipantTest::testIsNull_data() { QTest::addColumn("accountId"); QTest::addColumn("identifier"); QTest::addColumn("isNull"); QTest::newRow("all null") << QString() << QString() << true; QTest::newRow("null accountId") << QString() << "some identifier" << true; QTest::newRow("null identifier") << "some account ID" << QString() << true; QTest::newRow("valid account and identifier") << "theAccountId" << "theIdentifier" << false; } void ParticipantTest::testIsNull() { QFETCH(QString, accountId); QFETCH(QString, identifier); QFETCH(bool, isNull); History::Participant participant(accountId, identifier); QCOMPARE(participant.isNull(), isNull); } void ParticipantTest::testCopyConstructor() { QVariantMap detailProperties; detailProperties["theProperty"] = "theValue"; History::Participant original("accountId", "identifier", "contactId", "alias", "avatar", detailProperties); History::Participant copy(original); QCOMPARE(copy.accountId(), original.accountId()); QCOMPARE(copy.identifier(), original.identifier()); QCOMPARE(copy.contactId(), original.contactId()); QCOMPARE(copy.alias(), original.alias()); QCOMPARE(copy.avatar(), original.avatar()); QCOMPARE(copy.detailProperties(), original.detailProperties()); } void ParticipantTest::testAssignmentOperator() { QVariantMap detailProperties; detailProperties["theProperty2"] = "theValue2"; History::Participant original("accountId2", "identifier2", "contactId2", "alias2", "avatar2", detailProperties); History::Participant copy; copy = original; QCOMPARE(copy.accountId(), original.accountId()); QCOMPARE(copy.identifier(), original.identifier()); QCOMPARE(copy.contactId(), original.contactId()); QCOMPARE(copy.alias(), original.alias()); QCOMPARE(copy.avatar(), original.avatar()); QCOMPARE(copy.detailProperties(), original.detailProperties()); } void ParticipantTest::testEqualsOperator_data() { QTest::addColumn("accountId1"); QTest::addColumn("identifier1"); QTest::addColumn("accountId2"); QTest::addColumn("identifier2"); QTest::addColumn("equals"); QTest::newRow("same participant") << "theAccountId" << "theIdentifier" << "theAccountId" << "theIdentifier" << true; QTest::newRow("different identifiers") << "theAccountId" << "theIdentifier1" << "theAccountId" << "theIdentifier2" << false; QTest::newRow("different accounts") << "theAccountId1" << "theIdentifier" << "theAccountId2" << "theIdentifier" << false; QTest::newRow("all different") << "theAccountId" << "theIdentifier" << "theAccountId2" << "theIdentifier2" << false; } void ParticipantTest::testEqualsOperator() { QFETCH(QString, accountId1); QFETCH(QString, identifier1); QFETCH(QString, accountId2); QFETCH(QString, identifier2); QFETCH(bool, equals); History::Participant participant1(accountId1, identifier1); History::Participant participant2(accountId2, identifier2); QCOMPARE((participant1 == participant2), equals); } void ParticipantTest::testProperties() { QVariantMap detailProperties; detailProperties["someDetailProperty"] = "someValue"; History::Participant participant("theAccountId", "theIdentifier", "theContactId", "theAlias", "theAvatar", detailProperties); QVariantMap properties = participant.properties(); QCOMPARE(properties[History::FieldAccountId].toString(), participant.accountId()); QCOMPARE(properties[History::FieldIdentifier].toString(), participant.identifier()); QCOMPARE(properties[History::FieldContactId].toString(), participant.contactId()); QCOMPARE(properties[History::FieldAlias].toString(), participant.alias()); QCOMPARE(properties[History::FieldAvatar].toString(), participant.avatar()); QCOMPARE(properties[History::FieldDetailProperties].toMap(), participant.detailProperties()); } void ParticipantTest::testFromProperties() { QVariantMap properties; QVariantMap detailProperties; properties[History::FieldAccountId] = "someAccountId"; properties[History::FieldIdentifier] = "someIdentifier"; properties[History::FieldContactId] = "someContactId"; properties[History::FieldAlias] = "someAlias"; properties[History::FieldAvatar] = "someAvatar"; detailProperties["someDetailProperty"] = "someValue"; properties[History::FieldDetailProperties] = detailProperties; History::Participant participant = History::Participant::fromProperties(properties); QCOMPARE(participant.accountId(), properties[History::FieldAccountId].toString()); QCOMPARE(participant.identifier(), properties[History::FieldIdentifier].toString()); QCOMPARE(participant.contactId(), properties[History::FieldContactId].toString()); QCOMPARE(participant.alias(), properties[History::FieldAlias].toString()); QCOMPARE(participant.avatar(), properties[History::FieldAvatar].toString()); QCOMPARE(participant.detailProperties(), properties[History::FieldDetailProperties].toMap()); } void ParticipantTest::testIdentifiers() { QStringList identifiers; identifiers << "firstId" << "secondId" << "thirdId"; History::Participants participants; Q_FOREACH(const QString &identifier, identifiers) { participants << History::Participant("theAccountId", identifier); } QCOMPARE(participants.identifiers(), identifiers); } void ParticipantTest::testFromVariantList() { QVariantList list; for (int i = 0; i < 10; ++i) { list << History::Participant("theAccountId", QString("identifier%1").arg(QString::number(i))).properties(); } History::Participants participants = History::Participants::fromVariantList(list); QCOMPARE(participants.count(), list.count()); for (int i = 0; i < participants.count(); ++i) { QCOMPARE(participants[i].properties(), list[i].toMap()); } } void ParticipantTest::testToVariantList() { History::Participants participants; for (int i = 0; i < 10; ++i) { participants << History::Participant("theAccountId", QString("identifier%1").arg(QString::number(i))); } QVariantList list = participants.toVariantList(); QCOMPARE(list.count(), participants.count()); for (int i = 0; i < list.count(); ++i) { QCOMPARE(list[i].toMap(), participants[i].properties()); } } void ParticipantTest::testFromVariantWithVariantList() { QVariantList list; for (int i = 0; i < 10; ++i) { list << History::Participant("theAccountId", QString("identifier%1").arg(QString::number(i))).properties(); } History::Participants participants = History::Participants::fromVariant(list); QCOMPARE(participants.count(), list.count()); for (int i = 0; i < participants.count(); ++i) { QCOMPARE(participants[i].properties(), list[i].toMap()); } } QTEST_MAIN(ParticipantTest) #include "ParticipantTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/ThreadTest.cpp0000644000015600001650000003403712642552153026551 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "thread.h" #include "textevent.h" #include "voiceevent.h" Q_DECLARE_METATYPE(History::EventType) Q_DECLARE_METATYPE(History::Thread) class ThreadTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCreateNewThread_data(); void testCreateNewThread(); void testFromProperties_data(); void testFromProperties(); void testFromNullProperties(); void testProperties_data(); void testProperties(); void testIsNull_data(); void testIsNull(); void testEqualsOperator_data(); void testEqualsOperator(); void testCopyConstructor(); void testAssignmentOperator(); private: History::Participants participantsFromIdentifiers(const QString &accountId, const QStringList &identifiers); }; void ThreadTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void ThreadTest::testCreateNewThread_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("type"); QTest::addColumn("participants"); QTest::addColumn("count"); QTest::addColumn("unreadCount"); QTest::newRow("voice thread with count, unread count and one participant") << "someaccountid" << "somethreadid" << History::EventTypeVoice << (QStringList() << "someparticipant") << 10 << 4; QTest::newRow("voice thread with zero messages and two participants") << "anotheraccountid" << "anotherthreadid" << History::EventTypeVoice << (QStringList() << "someparticipant" << "anotherparticipant") << 0 << 0; QTest::newRow("text thread with count, unread count and one participant") << "somevoiceaccountid" << "somevoicethreadid" << History::EventTypeText << (QStringList() << "somevoiceparticipant") << 10 << 4; QTest::newRow("voice thread with zero messages and two participants") << "anothervoiceaccountid" << "anothervoicethreadid" << History::EventTypeText << (QStringList() << "someparticipant" << "anotherparticipant") << 0 << 0; } void ThreadTest::testCreateNewThread() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(History::EventType, type); QFETCH(QStringList, participants); QFETCH(int, count); QFETCH(int, unreadCount); History::Event event; switch (type) { case History::EventTypeText: // the eventId doesn´t really matter here, just faking a random one to not use always the same event = History::TextEvent(accountId, threadId, QString("eventId%1").arg(QString::number(qrand() % 1024)), participants[0], QDateTime::currentDateTime(), false, "Random Message", History::MessageTypeText); break; case History::EventTypeVoice: event = History::VoiceEvent(accountId, threadId, QString("eventId%1").arg(QString::number(qrand() % 1024)), participants[0], QDateTime::currentDateTime(), false, false, QTime(1,2,3)); break; } History::Thread threadItem(accountId, threadId, type, participantsFromIdentifiers(accountId, participants), event, count, unreadCount); QCOMPARE(threadItem.accountId(), accountId); QCOMPARE(threadItem.threadId(), threadId); QCOMPARE(threadItem.type(), type); QCOMPARE(threadItem.participants().identifiers(), participants); QCOMPARE(threadItem.lastEvent(), event); QCOMPARE(threadItem.count(), count); QCOMPARE(threadItem.unreadCount(), unreadCount); QVERIFY(threadItem.lastEvent() == event); } void ThreadTest::testFromProperties_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("type"); QTest::addColumn("participants"); QTest::addColumn("count"); QTest::addColumn("unreadCount"); QTest::newRow("voice thread with count, unread count and one participant") << "someaccountid" << "somethreadid" << History::EventTypeVoice << (QStringList() << "someparticipant") << 10 << 4; QTest::newRow("voice thread with zero messages and two participants") << "anotheraccountid" << "anotherthreadid" << History::EventTypeVoice << (QStringList() << "someparticipant" << "anotherparticipant") << 0 << 0; QTest::newRow("text thread with count, unread count and one participant") << "somevoiceaccountid" << "somevoicethreadid" << History::EventTypeText << (QStringList() << "somevoiceparticipant") << 10 << 4; QTest::newRow("voice thread with zero messages and two participants") << "anothervoiceaccountid" << "anothervoicethreadid" << History::EventTypeText << (QStringList() << "someparticipant" << "anotherparticipant") << 0 << 0; } void ThreadTest::testFromProperties() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(History::EventType, type); QFETCH(QStringList, participants); QFETCH(int, count); QFETCH(int, unreadCount); History::Event event; switch (type) { case History::EventTypeText: // the eventId doesn´t really matter here, just faking a random one to not use always the same event = History::TextEvent(accountId, threadId, QString("eventId%1").arg(QString::number(qrand() % 1024)), participants[0], QDateTime::currentDateTime(), false, "Random Message", History::MessageTypeText); break; case History::EventTypeVoice: event = History::VoiceEvent(accountId, threadId, QString("eventId%1").arg(QString::number(qrand() % 1024)), participants[0], QDateTime::currentDateTime(), false, false, QTime(1,2,3)); break; } QVariantMap properties = event.properties(); properties[History::FieldAccountId] = accountId; properties[History::FieldThreadId] = threadId; properties[History::FieldType] = (int) type; properties[History::FieldParticipants] = participantsFromIdentifiers(accountId, participants).toVariantList(); properties[History::FieldCount] = count; properties[History::FieldUnreadCount] = unreadCount; History::Thread thread = History::Thread::fromProperties(properties); QCOMPARE(thread.accountId(), accountId); QCOMPARE(thread.threadId(), threadId); QCOMPARE(thread.type(), type); QCOMPARE(thread.participants().identifiers(), participants); QCOMPARE(thread.count(), count); QCOMPARE(thread.unreadCount(), unreadCount); QVERIFY(thread.lastEvent() == event); } void ThreadTest::testFromNullProperties() { History::Thread thread = History::Thread::fromProperties(QVariantMap()); QVERIFY(thread.isNull()); } void ThreadTest::testProperties_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("type"); QTest::addColumn("participants"); QTest::addColumn("count"); QTest::addColumn("unreadCount"); QTest::newRow("voice thread with count, unread count and one participant") << "someaccountid" << "somethreadid" << History::EventTypeVoice << (QStringList() << "someparticipant") << 10 << 4; QTest::newRow("voice thread with zero messages and two participants") << "anotheraccountid" << "anotherthreadid" << History::EventTypeVoice << (QStringList() << "someparticipant" << "anotherparticipant") << 0 << 0; QTest::newRow("text thread with count, unread count and one participant") << "somevoiceaccountid" << "somevoicethreadid" << History::EventTypeText << (QStringList() << "somevoiceparticipant") << 10 << 4; QTest::newRow("voice thread with zero messages and two participants") << "anothervoiceaccountid" << "anothervoicethreadid" << History::EventTypeText << (QStringList() << "someparticipant" << "anotherparticipant") << 0 << 0; } void ThreadTest::testProperties() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(History::EventType, type); QFETCH(QStringList, participants); QFETCH(int, count); QFETCH(int, unreadCount); History::Event event; switch (type) { case History::EventTypeText: // the eventId doesn´t really matter here, just faking a random one to not use always the same event = History::TextEvent(accountId, threadId, QString("eventId%1").arg(QString::number(qrand() % 1024)), participants[0], QDateTime::currentDateTime(), false, "Random Message", History::MessageTypeText); break; case History::EventTypeVoice: event = History::VoiceEvent(accountId, threadId, QString("eventId%1").arg(QString::number(qrand() % 1024)), participants[0], QDateTime::currentDateTime(), false, false, QTime(1,2,3)); break; } History::Thread threadItem(accountId, threadId, type, participantsFromIdentifiers(accountId, participants), event, count, unreadCount); QVariantMap properties = threadItem.properties(); QCOMPARE(properties[History::FieldAccountId].toString(), accountId); QCOMPARE(properties[History::FieldThreadId].toString(), threadId); QCOMPARE(properties[History::FieldType].toInt(), (int)type); QCOMPARE(History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers(), participants); QCOMPARE(properties[History::FieldCount].toInt(), count); QCOMPARE(properties[History::FieldUnreadCount].toInt(), unreadCount); } void ThreadTest::testIsNull_data() { QTest::addColumn("thread"); QTest::addColumn("isNull"); History::Participants participants; participants << History::Participant("AccountId","Foo") << History::Participant("AccountId","Bar"); QTest::newRow("empty thread") << History::Thread() << true; QTest::newRow("empty accountId") << History::Thread(QString(), "threadId" , History::EventTypeText, participants) << false; QTest::newRow("empty threadId") << History::Thread("AccountId", QString(), History::EventTypeVoice, participants) << false; QTest::newRow("empty participants") << History::Thread("AccountId", "ThreadId", History::EventTypeText, History::Participants()) << false; QTest::newRow("construct empty thread") << History::Thread(QString(), QString(), History::EventTypeNull, History::Participants()) << true; } void ThreadTest::testIsNull() { QFETCH(History::Thread, thread); QFETCH(bool, isNull); QCOMPARE(thread.isNull(), isNull); } void ThreadTest::testEqualsOperator_data() { QTest::addColumn("firstAccountId"); QTest::addColumn("firstThreadId"); QTest::addColumn("firstType"); QTest::addColumn("secondAccountId"); QTest::addColumn("secondThreadId"); QTest::addColumn("secondType"); QTest::addColumn("result"); QTest::newRow("equal threads") << "theAccountId" << "theThreadId" << History::EventTypeText << "theAccountId" << "theThreadId" << History::EventTypeText << true; QTest::newRow("different types") << "oneAccountId" << "oneThreadId" << History::EventTypeVoice << "oneAccountId" << "oneThreadId" << History::EventTypeText << false; QTest::newRow("different account IDs") << "firstAccountId" << "theThreadId" << History::EventTypeVoice << "secondAccountId" << "theThreadId" << History::EventTypeVoice << false; QTest::newRow("different thread IDs") << "oneAccountId" << "firstThreadId" << History::EventTypeText << "oneAccountId" << "secondThreadId" << History::EventTypeText << false; } void ThreadTest::testEqualsOperator() { QFETCH(QString, firstAccountId); QFETCH(QString, firstThreadId); QFETCH(History::EventType, firstType); QFETCH(QString, secondAccountId); QFETCH(QString, secondThreadId); QFETCH(History::EventType, secondType); QFETCH(bool, result); History::Thread firstThread(firstAccountId, firstThreadId, firstType, History::Participants()); History::Thread secondThread(secondAccountId, secondThreadId, secondType, History::Participants()); QVERIFY((firstThread == secondThread) == result); } void ThreadTest::testCopyConstructor() { History::Thread thread("OneAccountId", "OneThreadId", History::EventTypeText, participantsFromIdentifiers("OneAccountId", QStringList() << "Foo" << "Bar")); History::Thread copy(thread); QVERIFY(thread == copy); } void ThreadTest::testAssignmentOperator() { History::Thread thread("OneAccountId", "OneThreadId", History::EventTypeText, participantsFromIdentifiers("OneAccountId", QStringList() << "Foo" << "Bar")); History::Thread other; other = thread; QVERIFY(other == thread); } History::Participants ThreadTest::participantsFromIdentifiers(const QString &accountId, const QStringList &identifiers) { History::Participants participants; Q_FOREACH(const QString &identifier, identifiers) { participants << History::Participant(accountId, identifier); } return participants; } QTEST_MAIN(ThreadTest) #include "ThreadTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/PhoneUtilsTest.cpp0000644000015600001650000000616112642552153027431 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "phoneutils_p.h" class PhoneUtilsTest : public QObject { Q_OBJECT private Q_SLOTS: void testIsPhoneNumber_data(); void testIsPhoneNumber(); void testComparePhoneNumbers_data(); void testComparePhoneNumbers(); }; void PhoneUtilsTest::testIsPhoneNumber_data() { QTest::addColumn("number"); QTest::addColumn("expectedResult"); QTest::newRow("simple number") << "12345678" << true; QTest::newRow("number with dash") << "1234-5678" << true; QTest::newRow("number with area code") << "(123)12345678" << true; QTest::newRow("number with extension") << "12345678#123" << true; QTest::newRow("short/emergency number") << "190" << true; QTest::newRow("non phone numbers") << "abcdefg" << false; } void PhoneUtilsTest::testIsPhoneNumber() { QFETCH(QString, number); QFETCH(bool, expectedResult); bool result = History::PhoneUtils::isPhoneNumber(number); QCOMPARE(result, expectedResult); } void PhoneUtilsTest::testComparePhoneNumbers_data() { QTest::addColumn("number1"); QTest::addColumn("number2"); QTest::addColumn("expectedResult"); QTest::newRow("string equal") << "12345678" << "12345678" << true; QTest::newRow("number with dash") << "1234-5678" << "12345678" << true; QTest::newRow("number with area code") << "12312345678" << "12345678" << true; QTest::newRow("number with extension") << "12345678#123" << "12345678" << true; QTest::newRow("both numbers with extension") << "(123)12345678#1" << "12345678#1" << true; QTest::newRow("numbers with different extension") << "1234567#1" << "1234567#2" << false; QTest::newRow("short/emergency numbers") << "190" << "190" << true; QTest::newRow("different numbers") << "12345678" << "1234567" << false; QTest::newRow("both non phone numbers") << "abcdefg" << "abcdefg" << true; QTest::newRow("different non phone numbers") << "abcdefg" << "bcdefg" << false; QTest::newRow("phone number and custom string") << "abc12345678" << "12345678" << true; // FIXME: check what other cases we need to test here" } void PhoneUtilsTest::testComparePhoneNumbers() { QFETCH(QString, number1); QFETCH(QString, number2); QFETCH(bool, expectedResult); bool result = History::PhoneUtils::comparePhoneNumbers(number1, number2); QCOMPARE(result, expectedResult); } QTEST_MAIN(PhoneUtilsTest) #include "PhoneUtilsTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/IntersectionFilterTest.cpp0000644000015600001650000002342712642552153031157 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "intersectionfilter.h" Q_DECLARE_METATYPE(History::MatchFlags) Q_DECLARE_METATYPE(History::IntersectionFilter) class IntersectionFilterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testSetFilters(); void testAppendFilter(); void testPrependFilter(); void testClear(); void testMatch_data(); void testMatch(); void testToStringWithNoFilters(); void testToStringWithOneFilter(); void testToStringWithManyFilters(); void testConvertToFilterAndBack(); void testIsValid_data(); void testIsValid(); void testProperties(); void testFromProperties(); }; void IntersectionFilterTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void IntersectionFilterTest::testSetFilters() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo); QCOMPARE(intersectionFilter.filters().count(), 2); QCOMPARE(intersectionFilter.filters()[0], filterOne); QCOMPARE(intersectionFilter.filters()[1], filterTwo); } void IntersectionFilterTest::testAppendFilter() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo); intersectionFilter.append(filterThree); QCOMPARE(intersectionFilter.filters().count(), 3); QCOMPARE(intersectionFilter.filters()[2], filterThree); } void IntersectionFilterTest::testPrependFilter() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo); intersectionFilter.prepend(filterThree); QCOMPARE(intersectionFilter.filters().count(), 3); QCOMPARE(intersectionFilter.filters()[0], filterThree); } void IntersectionFilterTest::testClear() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo); intersectionFilter.clear(); QVERIFY(intersectionFilter.filters().isEmpty()); } void IntersectionFilterTest::testMatch_data() { QTest::addColumn("filterProperties"); QTest::addColumn("itemProperties"); QTest::addColumn("result"); // FIXME: take into account the match flags QVariantMap filterProperties; QVariantMap itemProperties; filterProperties["stringProperty"] = QString("stringValue"); filterProperties["intProperty"] = 10; itemProperties = filterProperties; QTest::newRow("all matching values") << filterProperties << itemProperties << true; itemProperties["intProperty"] = 11; QTest::newRow("one of the values is different") << filterProperties << itemProperties << false; itemProperties["stringProperty"] = QString("noMatch"); QTest::newRow("no match at all") << filterProperties << itemProperties << false; QTest::newRow("empty match") << QVariantMap() << itemProperties << true; } void IntersectionFilterTest::testMatch() { QFETCH(QVariantMap, filterProperties); QFETCH(QVariantMap, itemProperties); QFETCH(bool, result); QList filters; Q_FOREACH(const QString &key, filterProperties.keys()) { filters << History::Filter(key, filterProperties[key]); } History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(filters); QCOMPARE(intersectionFilter.match(itemProperties), result); } void IntersectionFilterTest::testToStringWithNoFilters() { History::IntersectionFilter filter; QVERIFY(filter.toString().isNull()); } void IntersectionFilterTest::testToStringWithOneFilter() { // test that with a single filter the result of toString() is equal to the output // of calling toString() on the filter directly History::Filter filter("aProperty", "aValue"); History::IntersectionFilter intersectionFilter; intersectionFilter.append(filter); QCOMPARE(intersectionFilter.toString(), filter.toString()); } void IntersectionFilterTest::testToStringWithManyFilters() { // check if each of the individual filters are present in the toString output History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo << filterThree); QString stringResult = intersectionFilter.toString(); QVERIFY(stringResult.contains(filterOne.toString())); QVERIFY(stringResult.contains(filterTwo.toString())); QVERIFY(stringResult.contains(filterThree.toString())); } void IntersectionFilterTest::testConvertToFilterAndBack() { History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo << filterThree); History::Filter castFilter = intersectionFilter; QCOMPARE(castFilter.toString(), intersectionFilter.toString()); QCOMPARE(castFilter.type(), History::FilterTypeIntersection); History::IntersectionFilter andBack = castFilter; QCOMPARE(andBack, intersectionFilter); QCOMPARE(andBack.toString(), intersectionFilter.toString()); } void IntersectionFilterTest::testIsValid_data() { QTest::addColumn("filter"); QTest::addColumn("isValid"); History::IntersectionFilter filter; QTest::newRow("invalid filter") << filter << false; filter.append(History::Filter()); QTest::newRow("valid filter") << filter << true; } void IntersectionFilterTest::testIsValid() { QFETCH(History::IntersectionFilter, filter); QFETCH(bool, isValid); QCOMPARE(filter.isValid(), isValid); } void IntersectionFilterTest::testProperties() { History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::IntersectionFilter intersectionFilter; intersectionFilter.setFilters(QList() << filterOne << filterTwo << filterThree); QVariantMap properties = intersectionFilter.properties(); QVERIFY(!properties.isEmpty()); QVERIFY(properties.contains(History::FieldFilters)); QCOMPARE(properties[History::FieldFilterType].toInt(), (int) History::FilterTypeIntersection); QVariantList filters = properties[History::FieldFilters].toList(); QCOMPARE(filters.count(), intersectionFilter.filters().count()); QVariantMap propsOne = filters[0].toMap(); QCOMPARE(propsOne, filterOne.properties()); QVariantMap propsTwo = filters[1].toMap(); QCOMPARE(propsTwo, filterTwo.properties()); QVariantMap propsThree = filters[2].toMap(); QCOMPARE(propsThree, filterThree.properties()); // check that a null filter returns an empty QVariantMap History::IntersectionFilter nullFilter; QVERIFY(nullFilter.properties().isEmpty()); } void IntersectionFilterTest::testFromProperties() { QVariantMap properties; // check that a null filter is returned History::Filter nullFilter = History::IntersectionFilter::fromProperties(properties); QVERIFY(nullFilter.isNull()); properties[History::FieldFilterType] = (int)History::FilterTypeIntersection; QVariantList filters; for (int i = 0; i < 3; ++i) { History::Filter filter(QString("filter%1").arg(QString::number(i)), QString("value%1").arg(QString::number(i)), History::MatchCaseInsensitive); filters.append(filter.properties()); } properties[History::FieldFilters] = filters; History::Filter filter = History::IntersectionFilter::fromProperties(properties); QCOMPARE(filter.type(), History::FilterTypeIntersection); History::IntersectionFilter intersectionFilter = filter; QCOMPARE(intersectionFilter.filters().count(), filters.count()); for (int i = 0; i < filters.count(); ++i) { QCOMPARE(intersectionFilter.filters()[i].properties(), filters[i].toMap()); } } QTEST_MAIN(IntersectionFilterTest) #include "IntersectionFilterTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/UnionFilterTest.cpp0000644000015600001650000002265112642552153027577 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "unionfilter.h" Q_DECLARE_METATYPE(History::MatchFlags) Q_DECLARE_METATYPE(History::UnionFilter) class UnionFilterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testSetFilters(); void testAppendFilter(); void testPrependFilter(); void testClear(); void testMatch_data(); void testMatch(); void testToStringWithNoFilters(); void testToStringWithOneFilter(); void testToStringWithManyFilters(); void testConvertToFilterAndBack(); void testIsValid_data(); void testIsValid(); void testProperties(); void testFromProperties(); }; void UnionFilterTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void UnionFilterTest::testSetFilters() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo); QCOMPARE(unionFilter.filters().count(), 2); QCOMPARE(unionFilter.filters()[0], filterOne); QCOMPARE(unionFilter.filters()[1], filterTwo); } void UnionFilterTest::testAppendFilter() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo); unionFilter.append(filterThree); QCOMPARE(unionFilter.filters().count(), 3); QCOMPARE(unionFilter.filters()[2], filterThree); } void UnionFilterTest::testPrependFilter() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo); unionFilter.prepend(filterThree); QCOMPARE(unionFilter.filters().count(), 3); QCOMPARE(unionFilter.filters()[0], filterThree); } void UnionFilterTest::testClear() { // create two filters and check that they are properly set History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo); unionFilter.clear(); QVERIFY(unionFilter.filters().isEmpty()); } void UnionFilterTest::testMatch_data() { QTest::addColumn("filterProperties"); QTest::addColumn("itemProperties"); QTest::addColumn("result"); // FIXME: take into account the match flags QVariantMap filterProperties; QVariantMap itemProperties; filterProperties["stringProperty"] = QString("stringValue"); filterProperties["intProperty"] = 10; itemProperties = filterProperties; QTest::newRow("all matching values") << filterProperties << itemProperties << true; itemProperties["intProperty"] = 11; QTest::newRow("one of the values match") << filterProperties << itemProperties << true; itemProperties["stringProperty"] = QString("noMatch"); QTest::newRow("no match") << filterProperties << itemProperties << false; QTest::newRow("empty match") << QVariantMap() << itemProperties << true; } void UnionFilterTest::testMatch() { QFETCH(QVariantMap, filterProperties); QFETCH(QVariantMap, itemProperties); QFETCH(bool, result); QList filters; Q_FOREACH(const QString &key, filterProperties.keys()) { filters << History::Filter(key, filterProperties[key]); } History::UnionFilter unionFilter; unionFilter.setFilters(filters); QCOMPARE(unionFilter.match(itemProperties), result); } void UnionFilterTest::testToStringWithNoFilters() { History::UnionFilter filter; QVERIFY(filter.toString().isNull()); } void UnionFilterTest::testToStringWithOneFilter() { // test that with a single filter the result of toString() is equal to the output // of calling toString() on the filter directly History::Filter filter("aProperty", "aValue"); History::UnionFilter unionFilter; unionFilter.append(filter); QCOMPARE(unionFilter.toString(), filter.toString()); } void UnionFilterTest::testToStringWithManyFilters() { // check if all the individual filters are present in the toString() call History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo << filterThree); QString stringResult = unionFilter.toString(); QVERIFY(stringResult.contains(filterOne.toString())); QVERIFY(stringResult.contains(filterTwo.toString())); QVERIFY(stringResult.contains(filterThree.toString())); // check that the filter starts with two parenthesis (one for the block and one for the first filter // and that it also ends with two parentesis QVERIFY(stringResult.startsWith("((")); QVERIFY(stringResult.endsWith("))")); } void UnionFilterTest::testConvertToFilterAndBack() { History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo << filterThree); History::Filter castFilter = unionFilter; QCOMPARE(castFilter.toString(), unionFilter.toString()); QCOMPARE(castFilter.type(), History::FilterTypeUnion); History::UnionFilter andBack = castFilter; QCOMPARE(andBack, unionFilter); QCOMPARE(andBack.toString(), unionFilter.toString()); } void UnionFilterTest::testIsValid_data() { QTest::addColumn("filter"); QTest::addColumn("isValid"); History::UnionFilter filter; QTest::newRow("invalid filter") << filter << false; filter.append(History::Filter()); QTest::newRow("valid filter") << filter << true; } void UnionFilterTest::testIsValid() { QFETCH(History::UnionFilter, filter); QFETCH(bool, isValid); QCOMPARE(filter.isValid(), isValid); } void UnionFilterTest::testProperties() { History::Filter filterOne("propertyOne", "valueOne"); History::Filter filterTwo("propertyTwo", "valueTwo"); History::Filter filterThree("propertyThree", "valueThree"); History::UnionFilter unionFilter; unionFilter.setFilters(QList() << filterOne << filterTwo << filterThree); QVariantMap properties = unionFilter.properties(); QVERIFY(!properties.isEmpty()); QVERIFY(properties.contains(History::FieldFilters)); QCOMPARE(properties[History::FieldFilterType].toInt(), (int) History::FilterTypeUnion); QVariantList filters = properties[History::FieldFilters].toList(); QCOMPARE(filters.count(), unionFilter.filters().count()); QVariantMap propsOne = filters[0].toMap(); QCOMPARE(propsOne, filterOne.properties()); QVariantMap propsTwo = filters[1].toMap(); QCOMPARE(propsTwo, filterTwo.properties()); QVariantMap propsThree = filters[2].toMap(); QCOMPARE(propsThree, filterThree.properties()); // check that a null filter returns an empty QVariantMap History::UnionFilter nullFilter; QVERIFY(nullFilter.properties().isEmpty()); } void UnionFilterTest::testFromProperties() { QVariantMap properties; // check that a null filter is returned History::Filter nullFilter = History::UnionFilter::fromProperties(properties); QVERIFY(nullFilter.isNull()); properties[History::FieldFilterType] = (int)History::FilterTypeUnion; QVariantList filters; for (int i = 0; i < 3; ++i) { History::Filter filter(QString("filter%1").arg(QString::number(i)), QString("value%1").arg(QString::number(i)), History::MatchCaseInsensitive); filters.append(filter.properties()); } properties[History::FieldFilters] = filters; History::Filter filter = History::UnionFilter::fromProperties(properties); QCOMPARE(filter.type(), History::FilterTypeUnion); History::UnionFilter unionFilter = filter; QCOMPARE(unionFilter.filters().count(), filters.count()); for (int i = 0; i < filters.count(); ++i) { QCOMPARE(unionFilter.filters()[i].properties(), filters[i].toMap()); } } QTEST_MAIN(UnionFilterTest) #include "UnionFilterTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/FilterTest.cpp0000644000015600001650000003142312642552153026563 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "filter.h" #include "intersectionfilter.h" #include "unionfilter.h" Q_DECLARE_METATYPE(History::MatchFlags) Q_DECLARE_METATYPE(History::MatchFlag) Q_DECLARE_METATYPE(History::Filter) class FilterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCreateNewFilter_data(); void testCreateNewFilter(); void testSetProperties_data(); void testSetProperties(); void testToString_data(); void testToString(); void testToStringPrefix(); void testNullToString(); void testMatch_data(); void testMatch(); void testMatchFlags_data(); void testMatchFlags(); void testEqualsOperator(); void testAssignmentOperator(); void testIsValid_data(); void testIsValid(); void testType(); void testProperties(); void testFromProperties(); }; void FilterTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } void FilterTest::testCreateNewFilter_data() { QTest::addColumn("filterProperty"); QTest::addColumn("filterValue"); QTest::addColumn("matchFlags"); QTest::newRow("string property and value") << "oneProperty" << QVariant("oneValue") << History::MatchFlags(); QStringList list; list << "oneValue" << "anotherValue" << "yetAnotherValue"; QTest::newRow("a stringlist property") << "stringListProperty" << QVariant(list) << History::MatchFlags(History::MatchCaseSensitive); QTest::newRow("combining two flags and an int property") << "intProperty" << QVariant(11) << History::MatchFlags(History::MatchPhoneNumber | History::MatchContains); } void FilterTest::testCreateNewFilter() { QFETCH(QString, filterProperty); QFETCH(QVariant, filterValue); QFETCH(History::MatchFlags, matchFlags); History::Filter filter(filterProperty, filterValue, matchFlags); QCOMPARE(filter.filterProperty(), filterProperty); QCOMPARE(filter.filterValue(), filterValue); QCOMPARE(filter.matchFlags(), matchFlags); } void FilterTest::testSetProperties_data() { QTest::addColumn("filterProperty"); QTest::addColumn("filterValue"); QTest::addColumn("matchFlags"); QTest::newRow("string property and value") << "oneProperty" << QVariant("oneValue") << History::MatchFlags(); QStringList list; list << "oneValue" << "anotherValue" << "yetAnotherValue"; QTest::newRow("a stringlist property") << "stringListProperty" << QVariant(list) << History::MatchFlags(History::MatchCaseSensitive); QTest::newRow("combining two flags and an int property") << "intProperty" << QVariant(11) << History::MatchFlags(History::MatchPhoneNumber | History::MatchContains); } void FilterTest::testSetProperties() { QFETCH(QString, filterProperty); QFETCH(QVariant, filterValue); QFETCH(History::MatchFlags, matchFlags); History::Filter filter; filter.setFilterProperty(filterProperty); QCOMPARE(filter.filterProperty(), filterProperty); filter.setFilterValue(filterValue); QCOMPARE(filter.filterValue(), filterValue); filter.setMatchFlags(matchFlags); QCOMPARE(filter.matchFlags(), matchFlags); } void FilterTest::testToString_data() { QTest::addColumn("filterProperty"); QTest::addColumn("filterValue"); QTest::addColumn("result"); QTest::newRow("string value") << "stringProperty" << QVariant("stringValue") << "stringProperty=\"stringValue\""; QTest::newRow("bool property with false value") << "boolProperty" << QVariant(false) << "boolProperty=0"; QTest::newRow("bool property with true value") << "boolProperty" << QVariant(true) << "boolProperty=1"; QTest::newRow("int property") << "intProperty" << QVariant(15) << "intProperty=15"; QTest::newRow("double property") << "doubleProperty" << QVariant(1.5) << "doubleProperty=1.5"; } void FilterTest::testToString() { QFETCH(QString, filterProperty); QFETCH(QVariant, filterValue); QFETCH(QString, result); History::Filter filter(filterProperty, filterValue); QCOMPARE(filter.toString(), result); } void FilterTest::testToStringPrefix() { QString prefix("somePrefix"); QString filterProperty("someProperty"); QString filterValue("someValue"); History::Filter filter(filterProperty, filterValue); QVERIFY(filter.toString(prefix).startsWith(QString("%1.").arg(prefix))); QVERIFY(filter.toString().startsWith(filterProperty)); } void FilterTest::testNullToString() { History::Filter filter; QVERIFY(filter.toString().isNull()); } void FilterTest::testMatch_data() { QTest::addColumn("properties"); QTest::addColumn("filterProperty"); QTest::addColumn("filterValue"); QTest::addColumn("matchFlags"); QTest::addColumn("result"); QVariantMap map; map["stringProperty"] = QString("stringValue"); QTest::newRow("simple match of a string property") << map << "stringProperty" << QVariant("stringValue") << History::MatchFlags(History::MatchCaseSensitive) << true; map.clear(); map["stringProperty"] = QString("anotherValue"); QTest::newRow("string property that should not match") << map << "stringProperty" << QVariant("stringValue") << History::MatchFlags(History::MatchCaseSensitive) << false; map.clear(); map["intProperty"] = 42; QTest::newRow("integer property") << map << "intProperty" << QVariant(42) << History::MatchFlags() << true; map.clear(); map["intProperty"] = 41; QTest::newRow("integer property that should not match") << map << "intProperty" << QVariant(42) << History::MatchFlags() << false; map.clear(); map["intProperty"] = 42; QTest::newRow("empty property") << map << "" << QVariant(42) << History::MatchFlags() << true; QTest::newRow("empty value") << map << "intProperty" << QVariant() << History::MatchFlags() << true; // FIXME: add more test cases for the match flags once they are implemented } void FilterTest::testMatch() { QFETCH(QVariantMap, properties); QFETCH(QString, filterProperty); QFETCH(QVariant, filterValue); QFETCH(History::MatchFlags, matchFlags); QFETCH(bool, result); History::Filter filter(filterProperty, filterValue, matchFlags); QCOMPARE(filter.match(properties), result); } void FilterTest::testMatchFlags_data() { QTest::addColumn("flags"); QTest::addColumn("flagToMatch"); QTest::addColumn("match"); /* MatchCaseSensitive = 0x01, MatchCaseInsensitive = 0x02, MatchContains = 0x04, MatchPhoneNumber = 0x08 */ QTest::newRow("null flag") << History::MatchFlags() << History::MatchCaseSensitive << false; QTest::newRow("case sensitive alone") << History::MatchFlags(History::MatchCaseSensitive) << History::MatchCaseSensitive << true; QTest::newRow("case insensitive alone") << History::MatchFlags(History::MatchCaseInsensitive) << History::MatchCaseInsensitive << true; QTest::newRow("contains alone") << History::MatchFlags(History::MatchContains) << History::MatchContains << true; QTest::newRow("phone number alone") << History::MatchFlags(History::MatchPhoneNumber) << History::MatchPhoneNumber << true; QTest::newRow("no mismatch") << History::MatchFlags(History::MatchPhoneNumber) << History::MatchContains << false; QTest::newRow("all still match one") << History::MatchFlags(History::MatchCaseInsensitive | History::MatchCaseSensitive | History::MatchContains | History::MatchPhoneNumber) << History::MatchPhoneNumber << true; } void FilterTest::testMatchFlags() { QFETCH(History::MatchFlags, flags); QFETCH(History::MatchFlag, flagToMatch); QFETCH(bool, match); QCOMPARE(flags.testFlag(flagToMatch), match); QCOMPARE((bool)(flags & flagToMatch), match); } void FilterTest::testEqualsOperator() { History::Filter filterOne("oneProperty", "oneValue"); History::Filter equal("oneProperty", "oneValue"); History::Filter differentProperty("anotherProperty", "oneValue"); History::Filter differentValue("oneProperty", "anotherValue"); QVERIFY(filterOne == equal); QVERIFY(!(filterOne == differentProperty)); QVERIFY(!(filterOne == differentValue)); QVERIFY(filterOne != differentProperty); QVERIFY(filterOne != differentValue); } void FilterTest::testAssignmentOperator() { History::Filter filter(History::FieldAccountId, "OneAccountId", History::MatchFlags(History::MatchContains | History::MatchCaseSensitive)); History::Filter other; other = filter; QVERIFY(other == filter); } void FilterTest::testIsValid_data() { QTest::addColumn("filter"); QTest::addColumn("isValid"); QTest::newRow("null filter") << History::Filter() << false; QTest::newRow("null property") << History::Filter(QString::null, "Foobar") << false; QTest::newRow("null value") << History::Filter("oneProperty") << false; QTest::newRow("valid filter") << History::Filter("oneProperty", "oneValue") << true; } void FilterTest::testIsValid() { QFETCH(History::Filter, filter); QFETCH(bool, isValid); QCOMPARE(filter.isValid(), isValid); QCOMPARE(filter.isNull(), !isValid); } void FilterTest::testType() { History::Filter filter; QCOMPARE(filter.type(), History::FilterTypeStandard); } void FilterTest::testProperties() { // test an empty filter History::Filter emptyFilter; QVERIFY(emptyFilter.properties().isEmpty()); // and now a regular filter History::Filter filter("foobarProperty", "foobarValue", History::MatchCaseInsensitive); QVariantMap properties = filter.properties(); QCOMPARE(properties[History::FieldFilterType].toInt(), (int)filter.type()); QCOMPARE(properties[History::FieldFilterProperty].toString(), filter.filterProperty()); QCOMPARE(properties[History::FieldFilterValue], filter.filterValue()); QCOMPARE(properties[History::FieldMatchFlags].toInt(), (int)filter.matchFlags()); } void FilterTest::testFromProperties() { QVariantMap properties; // test an empty filter History::Filter filter = History::Filter::fromProperties(properties); QVERIFY(filter.isNull()); // and now a regular filter properties[History::FieldFilterType] = (int) History::FilterTypeStandard; properties[History::FieldFilterProperty] = "oneProperty"; properties[History::FieldFilterValue] = "oneValue"; properties[History::FieldMatchFlags] = (int) History::MatchContains; filter = History::Filter::fromProperties(properties); QCOMPARE(filter.type(), (History::FilterType)properties[History::FieldFilterType].toInt()); QCOMPARE(filter.filterProperty(), properties[History::FieldFilterProperty].toString()); QCOMPARE(filter.filterValue(), properties[History::FieldFilterValue]); QCOMPARE(filter.matchFlags(), History::MatchFlags(properties[History::FieldMatchFlags].toInt())); // test that calling fromProperties() on intersection filters works as expected History::IntersectionFilter intersectionFilter; intersectionFilter.append(History::Filter("oneProperty", "oneValue")); properties = intersectionFilter.properties(); filter = History::Filter::fromProperties(properties); QCOMPARE(filter.type(), History::FilterTypeIntersection); QCOMPARE(filter.properties(), properties); // and also on union filters History::UnionFilter unionFilter; unionFilter.append(History::Filter("oneProperty", "oneValue")); properties = unionFilter.properties(); filter = History::Filter::fromProperties(properties); QCOMPARE(filter.type(), History::FilterTypeUnion); QCOMPARE(filter.properties(), properties); } QTEST_MAIN(FilterTest) #include "FilterTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/CMakeLists.txt0000644000015600001650000000402312642552153026526 0ustar pbuserpbgroup00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests/common ${CMAKE_CURRENT_BINARY_DIR} ${TP_QT5_INCLUDE_DIRS} ) generate_test(FilterTest SOURCES FilterTest.cpp LIBRARIES historyservice) generate_test(IntersectionFilterTest SOURCES IntersectionFilterTest.cpp LIBRARIES historyservice) generate_test(ParticipantTest SOURCES ParticipantTest.cpp LIBRARIES historyservice) generate_test(PhoneUtilsTest SOURCES PhoneUtilsTest.cpp LIBRARIES historyservice) generate_test(SortTest SOURCES SortTest.cpp LIBRARIES historyservice) generate_test(ThreadTest SOURCES ThreadTest.cpp LIBRARIES historyservice) generate_test(TextEventTest SOURCES TextEventTest.cpp LIBRARIES historyservice) generate_test(TextEventAttachmentTest SOURCES TextEventAttachmentTest.cpp LIBRARIES historyservice) generate_test(UnionFilterTest SOURCES UnionFilterTest.cpp LIBRARIES historyservice) generate_test(VoiceEventTest SOURCES VoiceEventTest.cpp LIBRARIES historyservice) # DBus based tests generate_test(ManagerTest SOURCES ManagerTest.cpp LIBRARIES historyservice USE_DBUS TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon WAIT_FOR com.canonical.HistoryService) generate_test(ThreadViewTest SOURCES ThreadViewTest.cpp LIBRARIES historyservice USE_DBUS TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon WAIT_FOR com.canonical.HistoryService) generate_test(EventViewTest SOURCES EventViewTest.cpp LIBRARIES historyservice USE_DBUS TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon WAIT_FOR com.canonical.HistoryService) # Telepathy-based tests generate_telepathy_test(ContactMatcherTest SOURCES ContactMatcherTest.cpp QT5_MODULES Core DBus Test Qml Contacts) history-service-0.1+16.04.20160104/tests/libhistoryservice/TextEventTest.cpp0000644000015600001650000004252012642552153027264 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "textevent.h" class TextEventTest : public QObject { Q_OBJECT private Q_SLOTS: void testCreateNewEvent_data(); void testCreateNewEvent(); void testCastToEventAndBack(); void testFromProperties_data(); void testFromProperties(); void testFromNullProperties(); void testProperties_data(); void testProperties(); void testSetProperties(); private: History::Participants participantsFromIdentifiers(const QString &accountId, const QStringList &identifiers); }; void TextEventTest::testCreateNewEvent_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("senderId"); QTest::addColumn("timestamp"); QTest::addColumn("newEvent"); QTest::addColumn("message"); QTest::addColumn("messageType"); QTest::addColumn("messageStatus"); QTest::addColumn("readTimestamp"); QTest::addColumn("subject"); QTest::addColumn("participants"); QTest::newRow("unread message") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject" << (QStringList() << "testParticipant"); QTest::newRow("read message") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-10) << false << "One Test Message" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2" << (QStringList() << "testParticipant2"); QTest::newRow("message status") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeText << (int)History::MessageStatusAccepted << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3" << (QStringList() << "testParticipant"); QTest::newRow("multi party message") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeMultiPart << 0 << QDateTime::currentDateTime().addDays(-5) << QString() << (QStringList() << "testParticipant"); QTest::newRow("multiple participants") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-7) << true << "One Test Message 2" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-4) << QString() << (QStringList() << "one" << "two" << "three" << "four"); } void TextEventTest::testCreateNewEvent() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, senderId); QFETCH(QDateTime, timestamp); QFETCH(bool, newEvent); QFETCH(QString, message); QFETCH(int, messageType); QFETCH(int, messageStatus); QFETCH(QDateTime, readTimestamp); QFETCH(QString, subject); QFETCH(QStringList, participants); History::TextEvent event(accountId, threadId, eventId, senderId, timestamp, newEvent, message, (History::MessageType)messageType, (History::MessageStatus)messageStatus, readTimestamp, subject, History::TextEventAttachments(), participantsFromIdentifiers(accountId, participants)); // check that the values are properly set QCOMPARE(event.accountId(), accountId); QCOMPARE(event.threadId(), threadId); QCOMPARE(event.eventId(), eventId); QCOMPARE(event.senderId(), senderId); QCOMPARE(event.timestamp(), timestamp); QCOMPARE(event.newEvent(), newEvent); QCOMPARE(event.message(), message); QCOMPARE(event.messageType(), (History::MessageType)messageType); QCOMPARE(event.messageStatus(), (History::MessageStatus)messageStatus); QCOMPARE(event.readTimestamp(), readTimestamp); QCOMPARE(event.subject(), subject); QCOMPARE(event.participants().identifiers(), participants); } void TextEventTest::testCastToEventAndBack() { History::TextEvent textEvent("oneAccountId", "oneThreadId", "oneEventId", "oneSender", QDateTime::currentDateTime(), true, "Hello", History::MessageTypeText); // test the copy constructor History::Event historyEvent(textEvent); QVERIFY(historyEvent == textEvent); History::TextEvent castBack(historyEvent); QVERIFY(castBack == textEvent); // and now the assignment operator History::Event anotherEvent; anotherEvent = textEvent; QVERIFY(anotherEvent == textEvent); History::TextEvent backAgain; backAgain = anotherEvent; QVERIFY(backAgain == textEvent); } void TextEventTest::testFromProperties_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("senderId"); QTest::addColumn("timestamp"); QTest::addColumn("newEvent"); QTest::addColumn("message"); QTest::addColumn("messageType"); QTest::addColumn("messageStatus"); QTest::addColumn("readTimestamp"); QTest::addColumn("subject"); QTest::addColumn("participants"); QTest::newRow("unread message") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject" << (QStringList() << "testParticipant"); QTest::newRow("read message") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-10) << false << "One Test Message" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2" << (QStringList() << "testParticipant2"); QTest::newRow("message status") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeText << (int)History::MessageStatusAccepted << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3" << (QStringList() << "testParticipant"); QTest::newRow("multi party message") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeMultiPart << 0 << QDateTime::currentDateTime().addDays(-5) << QString() << (QStringList() << "testParticipant"); QTest::newRow("multiple participants") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-7) << true << "One Test Message 2" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-4) << QString() << (QStringList() << "one" << "two" << "three" << "four"); } void TextEventTest::testFromProperties() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, senderId); QFETCH(QDateTime, timestamp); QFETCH(bool, newEvent); QFETCH(QString, message); QFETCH(int, messageType); QFETCH(int, messageStatus); QFETCH(QDateTime, readTimestamp); QFETCH(QString, subject); QFETCH(QStringList, participants); QVariantMap properties; properties[History::FieldAccountId] = accountId; properties[History::FieldThreadId] = threadId; properties[History::FieldEventId] = eventId; properties[History::FieldSenderId] = senderId; properties[History::FieldTimestamp] = timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz"); properties[History::FieldNewEvent] = newEvent; properties[History::FieldMessage] = message; properties[History::FieldMessageType] = messageType; properties[History::FieldMessageStatus] = messageStatus; properties[History::FieldReadTimestamp] = readTimestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz"); properties[History::FieldSubject] = subject; properties[History::FieldParticipants] = participantsFromIdentifiers(accountId, participants).toVariantList(); History::TextEvent textEvent = History::TextEvent::fromProperties(properties); QCOMPARE(textEvent.accountId(), accountId); QCOMPARE(textEvent.threadId(), threadId); QCOMPARE(textEvent.eventId(), eventId); QCOMPARE(textEvent.senderId(), senderId); QCOMPARE(textEvent.timestamp().toString(Qt::ISODate), timestamp.toString(Qt::ISODate)); QCOMPARE(textEvent.newEvent(), newEvent); QCOMPARE(textEvent.message(), message); QCOMPARE(textEvent.messageType(), (History::MessageType) messageType); QCOMPARE(textEvent.messageStatus(), (History::MessageStatus) messageStatus); QCOMPARE(textEvent.readTimestamp().toString(Qt::ISODate), readTimestamp.toString(Qt::ISODate)); QCOMPARE(textEvent.subject(), subject); QCOMPARE(textEvent.participants().identifiers(), participants); } void TextEventTest::testFromNullProperties() { // just to make sure, test that calling ::fromProperties() on an empty map returns a null event History::Event nullEvent = History::TextEvent::fromProperties(QVariantMap()); QVERIFY(nullEvent.isNull()); QCOMPARE(nullEvent.type(), History::EventTypeNull); } void TextEventTest::testProperties_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("senderId"); QTest::addColumn("timestamp"); QTest::addColumn("newEvent"); QTest::addColumn("message"); QTest::addColumn("messageType"); QTest::addColumn("messageStatus"); QTest::addColumn("readTimestamp"); QTest::addColumn("subject"); QTest::addColumn("participants"); QTest::newRow("unread message") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject" << (QStringList() << "testParticipant"); QTest::newRow("read message") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-10) << false << "One Test Message" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2" << (QStringList() << "testParticipant2"); QTest::newRow("message status") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeText << (int)History::MessageStatusAccepted << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3" << (QStringList() << "testParticipant"); QTest::newRow("multi party message") << "testAccountId" << "testThreadId" << "testEventId" << "testSenderId" << QDateTime::currentDateTime().addDays(-10) << true << "One Test Message" << (int)History::MessageTypeMultiPart << 0 << QDateTime::currentDateTime().addDays(-5) << QString() << (QStringList() << "testParticipant"); QTest::newRow("multiple participants") << "testAccountId2" << "testThreadId2" << "testEventId2" << "testSenderId2" << QDateTime::currentDateTime().addDays(-7) << true << "One Test Message 2" << (int)History::MessageTypeText << 0 << QDateTime::currentDateTime().addDays(-4) << QString() << (QStringList() << "one" << "two" << "three" << "four"); } void TextEventTest::testProperties() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, senderId); QFETCH(QDateTime, timestamp); QFETCH(bool, newEvent); QFETCH(QString, message); QFETCH(int, messageType); QFETCH(int, messageStatus); QFETCH(QDateTime, readTimestamp); QFETCH(QString, subject); QFETCH(QStringList, participants); History::TextEvent event(accountId, threadId, eventId, senderId, timestamp, newEvent, message, (History::MessageType)messageType, (History::MessageStatus)messageStatus, readTimestamp, subject, History::TextEventAttachments(), participantsFromIdentifiers(accountId, participants)); QVariantMap properties = event.properties(); QCOMPARE(properties[History::FieldAccountId].toString(), accountId); QCOMPARE(properties[History::FieldThreadId].toString(), threadId); QCOMPARE(properties[History::FieldEventId].toString(), eventId); QCOMPARE(properties[History::FieldSenderId].toString(), senderId); QCOMPARE(properties[History::FieldTimestamp].toString(), timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz")); QCOMPARE(properties[History::FieldNewEvent].toBool(), newEvent); QCOMPARE(properties[History::FieldMessage].toString(), message); QCOMPARE(properties[History::FieldMessageType].toInt(), messageType); QCOMPARE(properties[History::FieldMessageStatus].toInt(), messageStatus); QCOMPARE(properties[History::FieldReadTimestamp].toString(), readTimestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz")); QCOMPARE(properties[History::FieldSubject].toString(), subject); QCOMPARE(History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers(), participants); } void TextEventTest::testSetProperties() { History::TextEvent textEvent("oneAccountId", "oneThreadId", "oneEventId", "oneSender", QDateTime::currentDateTime(), true, "Hello", History::MessageTypeText); QDateTime readTimestamp = QDateTime::currentDateTime(); History::MessageStatus status = History::MessageStatusDelivered; bool newEvent = false; textEvent.setReadTimestamp(readTimestamp); textEvent.setMessageStatus(status); textEvent.setNewEvent(newEvent); QCOMPARE(textEvent.readTimestamp(), readTimestamp); QCOMPARE(textEvent.messageStatus(), status); QCOMPARE(textEvent.newEvent(), newEvent); } History::Participants TextEventTest::participantsFromIdentifiers(const QString &accountId, const QStringList &identifiers) { History::Participants participants; Q_FOREACH(const QString &identifier, identifiers) { participants << History::Participant(accountId, identifier); } return participants; } QTEST_MAIN(TextEventTest) #include "TextEventTest.moc" history-service-0.1+16.04.20160104/tests/libhistoryservice/TextEventAttachmentTest.cpp0000644000015600001650000002236112642552153031276 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "thread.h" #include "textevent.h" #include "voiceevent.h" Q_DECLARE_METATYPE(History::AttachmentFlags) Q_DECLARE_METATYPE(History::AttachmentFlag) class TextEventAttachmentTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCreateNewTextEventAttachment_data(); void testCreateNewTextEventAttachment(); void testFromProperties(); void testCopyConstructor(); void testAssignment(); void testEquals_data(); void testEquals(); void testIsNull(); }; void TextEventAttachmentTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void TextEventAttachmentTest::testCreateNewTextEventAttachment_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("attachmentId"); QTest::addColumn("contentType"); QTest::addColumn("filePath"); QTest::addColumn("status"); QTest::newRow("regular attachment") << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << "image/x-jpeg" << "/some/file/path.jpg" << History::AttachmentFlags(History::AttachmentDownloaded); QTest::newRow("no content type and pending attachment") << "anotheraccountid" << "anotherthreadid" << "anothereventid" << "anotherattachmentid" << "" << "/another/file/path.jpg" << History::AttachmentFlags(History::AttachmentPending); QTest::newRow("attachment with error") << "yetanotheraccountid" << "yetanotherthreadid" << "yetanothereventid" << "yetanotherattachmentid" << "" << "/some/file/path" << History::AttachmentFlags(History::AttachmentError); } void TextEventAttachmentTest::testCreateNewTextEventAttachment() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, attachmentId); QFETCH(QString, contentType); QFETCH(QString, filePath); QFETCH(History::AttachmentFlags, status); History::TextEventAttachment attachment(accountId, threadId, eventId, attachmentId, contentType, filePath, status); QCOMPARE(attachment.accountId(), accountId); QCOMPARE(attachment.threadId(), threadId); QCOMPARE(attachment.eventId(), eventId); QCOMPARE(attachment.attachmentId(), attachmentId); QCOMPARE(attachment.contentType(), contentType); QCOMPARE(attachment.filePath(), filePath); QCOMPARE(attachment.status(), status); QVariantMap properties = attachment.properties(); QCOMPARE(properties[History::FieldAccountId].toString(), accountId); QCOMPARE(properties[History::FieldThreadId].toString(), threadId); QCOMPARE(properties[History::FieldEventId].toString(), eventId); QCOMPARE(properties[History::FieldAttachmentId].toString(), attachmentId); QCOMPARE(properties[History::FieldContentType].toString(), contentType); QCOMPARE(properties[History::FieldFilePath].toString(), filePath); QCOMPARE(properties[History::FieldStatus].toInt(), (int) status); } void TextEventAttachmentTest::testFromProperties() { QVariantMap properties; properties[History::FieldAccountId] = "someAccountId"; properties[History::FieldThreadId] = "someThreadId"; properties[History::FieldEventId] = "someEventId"; properties[History::FieldAttachmentId] = "someAttachmentId"; properties[History::FieldContentType] = "someContentType"; properties[History::FieldFilePath] = "/some/file/path"; properties[History::FieldStatus] = (int) History::AttachmentDownloaded; History::TextEventAttachment attachment = History::TextEventAttachment::fromProperties(properties); QCOMPARE(attachment.accountId(), properties[History::FieldAccountId].toString()); QCOMPARE(attachment.threadId(), properties[History::FieldThreadId].toString()); QCOMPARE(attachment.eventId(), properties[History::FieldEventId].toString()); QCOMPARE(attachment.attachmentId(), properties[History::FieldAttachmentId].toString()); QCOMPARE(attachment.contentType(), properties[History::FieldContentType].toString()); QCOMPARE(attachment.filePath(), properties[History::FieldFilePath].toString()); QCOMPARE(attachment.status(), (History::AttachmentFlags) properties[History::FieldStatus].toInt()); // now load from an empty map History::TextEventAttachment emptyAttachment = History::TextEventAttachment::fromProperties(QVariantMap()); QVERIFY(emptyAttachment.isNull()); } void TextEventAttachmentTest::testCopyConstructor() { History::TextEventAttachment attachment("oneAccountId", "oneThreadId", "oneEventId", "oneAttachmentId", "oneContentType", "/one/file/path", History::AttachmentPending); History::TextEventAttachment copy(attachment); QCOMPARE(copy.accountId(), attachment.accountId()); QCOMPARE(copy.threadId(), attachment.threadId()); QCOMPARE(copy.eventId(), attachment.eventId()); QCOMPARE(copy.attachmentId(), attachment.attachmentId()); QCOMPARE(copy.contentType(), attachment.contentType()); QCOMPARE(copy.filePath(), attachment.filePath()); QCOMPARE(copy.status(), attachment.status()); } void TextEventAttachmentTest::testAssignment() { History::TextEventAttachment attachment("oneAccountId", "oneThreadId", "oneEventId", "oneAttachmentId", "oneContentType", "/one/file/path", History::AttachmentPending); History::TextEventAttachment copy; copy = attachment; QCOMPARE(copy.accountId(), attachment.accountId()); QCOMPARE(copy.threadId(), attachment.threadId()); QCOMPARE(copy.eventId(), attachment.eventId()); QCOMPARE(copy.attachmentId(), attachment.attachmentId()); QCOMPARE(copy.contentType(), attachment.contentType()); QCOMPARE(copy.filePath(), attachment.filePath()); QCOMPARE(copy.status(), attachment.status()); } void TextEventAttachmentTest::testEquals_data() { QTest::addColumn("accountId"); QTest::addColumn("threadId"); QTest::addColumn("eventId"); QTest::addColumn("attachmentId"); QTest::addColumn("secondAccountId"); QTest::addColumn("secondThreadId"); QTest::addColumn("secondEventId"); QTest::addColumn("secondAttachmentId"); QTest::addColumn("result"); QTest::newRow("equal") << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << true; QTest::newRow("different accountId") << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << "otheraccountid" << "somethreadid" << "someeventid" << "someattachmentid" << false; QTest::newRow("different threadId") << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << "someaccountid" << "otherthreadid" << "someeventid" << "someattachmentid" << false; QTest::newRow("different eventId") << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << "someaccountid" << "somethreadid" << "othereventid" << "someattachmentid" << false; QTest::newRow("different attachmentId") << "someaccountid" << "somethreadid" << "someeventid" << "someattachmentid" << "someaccountid" << "somethreadid" << "someeventid" << "otherattachmentid" << false; } void TextEventAttachmentTest::testEquals() { QFETCH(QString, accountId); QFETCH(QString, threadId); QFETCH(QString, eventId); QFETCH(QString, attachmentId); QFETCH(QString, secondAccountId); QFETCH(QString, secondThreadId); QFETCH(QString, secondEventId); QFETCH(QString, secondAttachmentId); QFETCH(bool, result); History::TextEventAttachment attachment(accountId, threadId, eventId, attachmentId, "oneContentType", "/one/file/path", History::AttachmentPending); History::TextEventAttachment anotherAttachment(secondAccountId, secondThreadId, secondEventId, secondAttachmentId, "anotherContentType", "/different/file/path", History::AttachmentDownloaded); QCOMPARE(attachment == anotherAttachment, result); } void TextEventAttachmentTest::testIsNull() { History::TextEventAttachment attachment; QVERIFY(attachment.isNull()); } QTEST_MAIN(TextEventAttachmentTest) #include "TextEventAttachmentTest.moc" history-service-0.1+16.04.20160104/tests/daemon/0000755000015600001650000000000012642552414021461 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/daemon/DaemonTest.cpp0000644000015600001650000004325412642552153024240 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: Tiago Salem Herrmann */ #include #include #include #include #include #include #include #include #include #include #include #include #include "telepathyhelper_p.h" #include "telepathytest.h" #include "mockcontroller.h" #include "handler.h" #include "approver.h" #include "manager.h" #include "thread.h" #include "textevent.h" #include "voiceevent.h" Q_DECLARE_METATYPE(Tp::CallChannelPtr) Q_DECLARE_METATYPE(Tp::TextChannelPtr) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(History::Threads) Q_DECLARE_METATYPE(History::Events) Q_DECLARE_METATYPE(History::MessageStatus) class DaemonTest : public TelepathyTest { Q_OBJECT Q_SIGNALS: void contactsReceived(QList contacts); private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMessageReceived(); void testMessageSentNoEventId(); void testMessageSent(); void testMissedCall(); void testOutgoingCall(); void testDeliveryReport_data(); void testDeliveryReport(); // helper slots void onPendingContactsFinished(Tp::PendingOperation*); private: Approver *mApprover; Handler *mHandler; MockController *mMockController; Tp::AccountPtr mAccount; }; void DaemonTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); initialize(); // register the handler mHandler = new Handler(this); History::TelepathyHelper::instance()->registerClient(mHandler, "HistoryTestHandler"); QTRY_VERIFY(mHandler->isRegistered()); // register the approver mApprover = new Approver(this); History::TelepathyHelper::instance()->registerClient(mApprover, "HistoryTestApprover"); // Tp-qt does not set registered status to approvers QTRY_VERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(TELEPHONY_SERVICE_APPROVER)); // we need to wait in order to give telepathy time to notify about the approver and handler QTest::qWait(3000); } void DaemonTest::init() { mAccount = addAccount("mock", "mock", "the account"); QVERIFY(!mAccount.isNull()); QTRY_VERIFY(mAccount->isReady(Tp::Account::FeatureCore)); QTRY_VERIFY(!mAccount->connection().isNull()); QTRY_VERIFY(History::TelepathyHelper::instance()->connected()); mMockController = new MockController("mock", this); } void DaemonTest::cleanup() { doCleanup(); mMockController->deleteLater(); } void DaemonTest::testMessageReceived() { QSignalSpy threadsAddedSpy(History::Manager::instance(), SIGNAL(threadsAdded(History::Threads))); QSignalSpy threadsModifiedSpy(History::Manager::instance(), SIGNAL(threadsModified(History::Threads))); QSignalSpy eventsAddedSpy(History::Manager::instance(), SIGNAL(eventsAdded(History::Events))); QVariantMap properties; QString sender = "123456789"; QString message = "Test message"; QDateTime sentTime = QDateTime::currentDateTime(); properties["Sender"] = sender; properties["SentTime"] = sentTime.toString(Qt::ISODate); properties["Recipients"] = QStringList() << sender; QSignalSpy handlerSpy(mHandler, SIGNAL(textChannelAvailable(Tp::TextChannelPtr))); mMockController->placeIncomingMessage(message, properties); QTRY_COMPARE(threadsAddedSpy.count(), 1); History::Threads threads = threadsAddedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread thread = threads.first(); QCOMPARE(thread.participants().count(), 1); QCOMPARE(thread.participants().first().identifier(), sender); QTRY_COMPARE(threadsModifiedSpy.count(), 1); threads = threadsModifiedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread modifiedThread = threads.first(); QVERIFY(modifiedThread == thread); QTRY_COMPARE(eventsAddedSpy.count(), 1); History::Events events = eventsAddedSpy.first().first().value(); QCOMPARE(events.count(), 1); History::TextEvent event = events.first(); QCOMPARE(event.senderId(), sender); QCOMPARE(event.threadId(), modifiedThread.threadId()); QVERIFY(modifiedThread.lastEvent() == event); QCOMPARE(event.timestamp().toString(Qt::ISODate), sentTime.toString(Qt::ISODate)); QCOMPARE(event.message(), message); QTRY_COMPARE(handlerSpy.count(), 1); Tp::TextChannelPtr channel = handlerSpy.first().first().value(); QVERIFY(channel); channel->requestClose(); } void DaemonTest::testMessageSentNoEventId() { // Request the contact to start chatting to QSignalSpy spy(this, SIGNAL(contactsReceived(QList))); QString recipient = "11111111"; connect(mAccount->connection()->contactManager()->contactsForIdentifiers(QStringList() << recipient), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onPendingContactsFinished(Tp::PendingOperation*))); QTRY_COMPARE(spy.count(), 1); QList contacts = spy.first().first().value >(); QCOMPARE(contacts.count(), 1); QCOMPARE(contacts.first()->id(), recipient); QSignalSpy spyTextChannel(mHandler, SIGNAL(textChannelAvailable(Tp::TextChannelPtr))); Q_FOREACH(Tp::ContactPtr contact, contacts) { mAccount->ensureTextChat(contact, QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".HistoryTestHandler"); } QTRY_COMPARE(spyTextChannel.count(), 1); Tp::TextChannelPtr channel = spyTextChannel.first().first().value(); QVERIFY(channel); QSignalSpy eventsAddedSpy(History::Manager::instance(), SIGNAL(eventsAdded(History::Events))); QString messageText = "Hello, big world!"; Tp::Message m(Tp::ChannelTextMessageTypeNormal, messageText); Tp::MessagePartList parts = m.parts(); Tp::MessagePart header = parts[0]; Tp::MessagePart body = parts[1]; header["no-event-id"] = QDBusVariant(true); Tp::MessagePartList newPart; newPart << header << body; Tp::PendingSendMessage *message = channel->send(newPart); QTRY_COMPARE(eventsAddedSpy.count(), 1); History::Events events = eventsAddedSpy.first().first().value(); QCOMPARE(events.count(), 1); History::TextEvent event = events.first(); QVERIFY(!event.eventId().isEmpty()); channel->requestClose(); } void DaemonTest::testMessageSent() { // Request the contact to start chatting to QSignalSpy spy(this, SIGNAL(contactsReceived(QList))); QString recipient = "987654321"; connect(mAccount->connection()->contactManager()->contactsForIdentifiers(QStringList() << recipient), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onPendingContactsFinished(Tp::PendingOperation*))); QTRY_COMPARE(spy.count(), 1); QList contacts = spy.first().first().value >(); QCOMPARE(contacts.count(), 1); QCOMPARE(contacts.first()->id(), recipient); QSignalSpy spyTextChannel(mHandler, SIGNAL(textChannelAvailable(Tp::TextChannelPtr))); Q_FOREACH(Tp::ContactPtr contact, contacts) { mAccount->ensureTextChat(contact, QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".HistoryTestHandler"); } QTRY_COMPARE(spyTextChannel.count(), 1); Tp::TextChannelPtr channel = spyTextChannel.first().first().value(); QVERIFY(channel); QSignalSpy threadsAddedSpy(History::Manager::instance(), SIGNAL(threadsAdded(History::Threads))); QSignalSpy threadsModifiedSpy(History::Manager::instance(), SIGNAL(threadsModified(History::Threads))); QSignalSpy eventsAddedSpy(History::Manager::instance(), SIGNAL(eventsAdded(History::Events))); QString messageText = "Hello, big world!"; Tp::PendingSendMessage *message = channel->send(messageText); QTRY_COMPARE(threadsAddedSpy.count(), 1); History::Threads threads = threadsAddedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread thread = threads.first(); QCOMPARE(thread.participants().count(), 1); QCOMPARE(thread.participants().first().identifier(), recipient); QTRY_COMPARE(threadsModifiedSpy.count(), 1); threads = threadsModifiedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread modifiedThread = threads.first(); QVERIFY(modifiedThread == thread); QTRY_COMPARE(eventsAddedSpy.count(), 1); History::Events events = eventsAddedSpy.first().first().value(); QCOMPARE(events.count(), 1); History::TextEvent event = events.first(); QCOMPARE(event.senderId(), QString("self")); QCOMPARE(event.threadId(), modifiedThread.threadId()); QVERIFY(modifiedThread.lastEvent() == event); QCOMPARE(event.message(), messageText); channel->requestClose(); } void DaemonTest::testMissedCall() { QSignalSpy newCallSpy(mApprover, SIGNAL(newCall())); // create an incoming call QString callerId = "33333333"; QVariantMap properties; properties["Caller"] = callerId; properties["State"] = "incoming"; mMockController->placeCall(properties); QTRY_COMPARE(newCallSpy.count(), 1); QSignalSpy threadsAddedSpy(History::Manager::instance(), SIGNAL(threadsAdded(History::Threads))); QSignalSpy threadsModifiedSpy(History::Manager::instance(), SIGNAL(threadsModified(History::Threads))); QSignalSpy eventsAddedSpy(History::Manager::instance(), SIGNAL(eventsAdded(History::Events))); // now hangup the call and check that the event was added to the database mMockController->hangupCall(callerId); QTRY_COMPARE(threadsAddedSpy.count(), 1); History::Threads threads = threadsAddedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread thread = threads.first(); QCOMPARE(thread.participants().count(), 1); QCOMPARE(thread.participants().first().identifier(), callerId); QTRY_COMPARE(threadsModifiedSpy.count(), 1); threads = threadsModifiedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread modifiedThread = threads.first(); QVERIFY(modifiedThread == thread); QTRY_COMPARE(eventsAddedSpy.count(), 1); History::Events events = eventsAddedSpy.first().first().value(); QCOMPARE(events.count(), 1); History::VoiceEvent event = events.first(); QCOMPARE(event.senderId(), callerId); QCOMPARE(event.threadId(), modifiedThread.threadId()); QVERIFY(modifiedThread.lastEvent() == event); QCOMPARE(event.missed(), true); } void DaemonTest::testOutgoingCall() { // Request the contact to start chatting to QString phoneNumber = "44444444"; QSignalSpy spy(this, SIGNAL(contactsReceived(QList))); connect(mAccount->connection()->contactManager()->contactsForIdentifiers(QStringList() << phoneNumber), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onPendingContactsFinished(Tp::PendingOperation*))); QTRY_COMPARE(spy.count(), 1); QList contacts = spy.first().first().value >(); QCOMPARE(contacts.count(), 1); QCOMPARE(contacts.first()->id(), phoneNumber); QSignalSpy spyCallChannel(mHandler, SIGNAL(callChannelAvailable(Tp::CallChannelPtr))); Q_FOREACH(Tp::ContactPtr contact, contacts) { mAccount->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".HistoryTestHandler"); } QTRY_COMPARE(spyCallChannel.count(), 1); Tp::CallChannelPtr channel = spyCallChannel.first().first().value(); QVERIFY(channel); mMockController->setCallState(phoneNumber, "alerting"); QTRY_COMPARE(channel->callState(), Tp::CallStateInitialised); mMockController->setCallState(phoneNumber, "active"); QTRY_COMPARE(channel->callState(), Tp::CallStateActive); QSignalSpy threadsAddedSpy(History::Manager::instance(), SIGNAL(threadsAdded(History::Threads))); QSignalSpy threadsModifiedSpy(History::Manager::instance(), SIGNAL(threadsModified(History::Threads))); QSignalSpy eventsAddedSpy(History::Manager::instance(), SIGNAL(eventsAdded(History::Events))); mMockController->setCallState(phoneNumber, "disconnected"); QTRY_COMPARE(channel->callState(), Tp::CallStateEnded); QTRY_COMPARE(threadsAddedSpy.count(), 1); History::Threads threads = threadsAddedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread thread = threads.first(); QCOMPARE(thread.participants().count(), 1); QCOMPARE(thread.participants().first().identifier(), phoneNumber); QTRY_COMPARE(threadsModifiedSpy.count(), 1); threads = threadsModifiedSpy.first().first().value(); QCOMPARE(threads.count(), 1); History::Thread modifiedThread = threads.first(); QVERIFY(modifiedThread == thread); QTRY_COMPARE(eventsAddedSpy.count(), 1); History::Events events = eventsAddedSpy.first().first().value(); QCOMPARE(events.count(), 1); History::VoiceEvent event = events.first(); QCOMPARE(event.senderId(), QString("self")); QCOMPARE(event.threadId(), modifiedThread.threadId()); QVERIFY(modifiedThread.lastEvent() == event); QCOMPARE(event.missed(), false); QVERIFY(event.duration().isValid()); } void DaemonTest::testDeliveryReport_data() { QTest::addColumn("phoneNumber"); QTest::addColumn("deliveryStatus"); QTest::addColumn("messageStatus"); QTest::newRow("delivered status") << "11112222" << "delivered" << History::MessageStatusDelivered; QTest::newRow("temporarily failed") << "11113333" << "temporarily_failed" << History::MessageStatusTemporarilyFailed; QTest::newRow("permanently failed") << "11114444" << "permanently_failed" << History::MessageStatusPermanentlyFailed; QTest::newRow("accepted status") << "11115555" << "accepted" << History::MessageStatusAccepted; QTest::newRow("read status") << "11116666" << "read" << History::MessageStatusRead; QTest::newRow("deleted") << "11117777" << "deleted" << History::MessageStatusDeleted; QTest::newRow("unknown") << "11118888" << "unknown" << History::MessageStatusUnknown; } void DaemonTest::testDeliveryReport() { QFETCH(QString, phoneNumber); QFETCH(QString, deliveryStatus); QFETCH(History::MessageStatus, messageStatus); // Request the contact to start chatting to QSignalSpy spy(this, SIGNAL(contactsReceived(QList))); connect(mAccount->connection()->contactManager()->contactsForIdentifiers(QStringList() << phoneNumber), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onPendingContactsFinished(Tp::PendingOperation*))); QTRY_COMPARE(spy.count(), 1); QList contacts = spy.first().first().value >(); QCOMPARE(contacts.count(), 1); QCOMPARE(contacts.first()->id(), phoneNumber); QSignalSpy spyTextChannel(mHandler, SIGNAL(textChannelAvailable(Tp::TextChannelPtr))); Q_FOREACH(Tp::ContactPtr contact, contacts) { mAccount->ensureTextChat(contact, QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".HistoryTestHandler"); } QTRY_COMPARE(spyTextChannel.count(), 1); Tp::TextChannelPtr channel = spyTextChannel.first().first().value(); QVERIFY(channel); QSignalSpy eventsAddedSpy(History::Manager::instance(), SIGNAL(eventsAdded(History::Events))); QString messageText = "Hello, big world!"; Tp::PendingSendMessage *message = channel->send(messageText); QTRY_COMPARE(eventsAddedSpy.count(), 1); History::Events events = eventsAddedSpy.first().first().value(); QCOMPARE(events.count(), 1); History::TextEvent event = events.first(); // now send a delivery report for this text and make sure the event gets updated QSignalSpy eventsModifiedSpy(History::Manager::instance(), SIGNAL(eventsModified(History::Events))); mMockController->placeDeliveryReport(QStringList() << phoneNumber, event.eventId(), deliveryStatus); QTRY_COMPARE(eventsModifiedSpy.count(), 1); events = eventsModifiedSpy.first().first().value(); QCOMPARE(events.count(), 1); event = events.first(); QCOMPARE(event.messageStatus(), messageStatus); channel->requestClose(); } void DaemonTest::onPendingContactsFinished(Tp::PendingOperation *op) { Tp::PendingContacts *pc = qobject_cast(op); if (!pc) { return; } Q_EMIT contactsReceived(pc->contacts()); } QTEST_MAIN(DaemonTest) #include "DaemonTest.moc" history-service-0.1+16.04.20160104/tests/daemon/approver.cpp0000644000015600001650000001425612642552153024033 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: Tiago Salem Herrmann */ #include "approver.h" #include #include #include #include #include #include Approver::Approver(QObject* parent) : QObject(parent), Tp::AbstractClientApprover(channelFilters()) { } Approver::~Approver() { } Tp::ChannelClassSpecList Approver::channelFilters() const { Tp::ChannelClassSpecList specList; specList << Tp::ChannelClassSpec::textChat() << Tp::ChannelClassSpec::audioCall(); return specList; } void Approver::addDispatchOperation(const Tp::MethodInvocationContextPtr<> &context, const Tp::ChannelDispatchOperationPtr &dispatchOperation) { bool willHandle = false; QList channels = dispatchOperation->channels(); Q_FOREACH (Tp::ChannelPtr channel, channels) { // Text Channel Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel); if (!textChannel.isNull()) { // right now we are not using any of the text channel's features in the approver // so no need to call becomeReady() on it. willHandle = true; continue; } // Call Channel Tp::CallChannelPtr callChannel = Tp::CallChannelPtr::dynamicCast(channel); if (!callChannel.isNull()) { Tp::PendingReady *pr = callChannel->becomeReady(Tp::Features() << Tp::CallChannel::FeatureCore << Tp::CallChannel::FeatureCallState << Tp::CallChannel::FeatureLocalHoldState); mChannels[pr] = callChannel; connect(pr, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onChannelReady(Tp::PendingOperation*))); callChannel->setProperty("accountId", QVariant(dispatchOperation->account()->uniqueIdentifier())); willHandle = true; continue; } } if (willHandle) { mDispatchOps.append(dispatchOperation); } context->setFinished(); // check if we need to approve channels already or if we should wait. processChannels(); } void Approver::processChannels() { Q_FOREACH (Tp::ChannelDispatchOperationPtr dispatchOperation, mDispatchOps) { QList channels = dispatchOperation->channels(); Q_FOREACH (Tp::ChannelPtr channel, channels) { // approve only text channels Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel); if (textChannel.isNull()) { continue; } if (dispatchOperation->possibleHandlers().contains(TELEPHONY_SERVICE_HANDLER)) { dispatchOperation->handleWith(TELEPHONY_SERVICE_HANDLER); mDispatchOps.removeAll(dispatchOperation); } // FIXME: this shouldn't happen, but in any case, we need to check what to do when // the phone app client is not available } } } void Approver::acceptCall() { Q_FOREACH (Tp::ChannelDispatchOperationPtr dispatchOperation, mDispatchOps) { QList channels = dispatchOperation->channels(); Q_FOREACH (Tp::ChannelPtr channel, channels) { if (dispatchOperation->possibleHandlers().contains(TELEPHONY_SERVICE_HANDLER)) { dispatchOperation->handleWith(TELEPHONY_SERVICE_HANDLER); mDispatchOps.removeAll(dispatchOperation); } } } } void Approver::rejectCall() { Q_FOREACH (Tp::ChannelDispatchOperationPtr dispatchOperation, mDispatchOps) { QList channels = dispatchOperation->channels(); Q_FOREACH (Tp::ChannelPtr channel, channels) { if (dispatchOperation->possibleHandlers().contains(TELEPHONY_SERVICE_HANDLER)) { Tp::PendingOperation *claimop = dispatchOperation->claim(); mChannels[claimop] = dispatchOperation->channels().first(); connect(claimop, SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onClaimFinished(Tp::PendingOperation*))); } } } } void Approver::onClaimFinished(Tp::PendingOperation* op) { if(!op || op->isError()) { return; } Tp::CallChannelPtr callChannel = Tp::CallChannelPtr::dynamicCast(mChannels[op]); if (callChannel) { Tp::PendingOperation *hangupop = callChannel->hangup(Tp::CallStateChangeReasonUserRequested, TP_QT_ERROR_REJECTED, QString()); mChannels[hangupop] = callChannel; connect(hangupop, SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onHangupFinished(Tp::PendingOperation*))); } } void Approver::onHangupFinished(Tp::PendingOperation* op) { if(!op || op->isError()) { return; } mDispatchOps.removeAll(dispatchOperation(op)); mChannels.remove(op); } Tp::ChannelDispatchOperationPtr Approver::dispatchOperation(Tp::PendingOperation *op) { Tp::ChannelPtr channel = mChannels[op]; QString accountId = channel->property("accountId").toString(); Q_FOREACH (Tp::ChannelDispatchOperationPtr dispatchOperation, mDispatchOps) { if (dispatchOperation->account()->uniqueIdentifier() == accountId) { return dispatchOperation; } } return Tp::ChannelDispatchOperationPtr(); } void Approver::onChannelReady(Tp::PendingOperation *op) { Q_EMIT newCall(); } history-service-0.1+16.04.20160104/tests/daemon/handler.h0000644000015600001650000000415312642552153023252 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #ifndef HANDLER_H #define HANDLER_H #include #include #include #include #include class Handler : public QObject, public Tp::AbstractClientHandler { Q_OBJECT public: Handler(QObject *parent = 0); ~Handler() { } bool bypassApproval() const; void handleChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList &channels, const QList &requestsSatisfied, const QDateTime &userActionTime, const Tp::AbstractClientHandler::HandlerInfo &handlerInfo); Tp::ChannelClassSpecList channelFilters(); void setBypassApproval(bool bypass); Q_SIGNALS: void textChannelAvailable(Tp::TextChannelPtr textChannel); void callChannelAvailable(Tp::CallChannelPtr callChannel); private Q_SLOTS: void onTextChannelReady(Tp::PendingOperation *op); void onCallChannelReady(Tp::PendingOperation *op); private: QMap mReadyRequests; bool mBypassApproval; }; #endif // HANDLER_H history-service-0.1+16.04.20160104/tests/daemon/handler.cpp0000644000015600001650000001164012642552153023604 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #include "handler.h" #include "telepathyhelper_p.h" #include #include #include #include #include Handler::Handler(QObject *parent) : QObject(parent), Tp::AbstractClientHandler(channelFilters()), mBypassApproval(false) { } void Handler::setBypassApproval(bool bypass) { mBypassApproval = bypass; } bool Handler::bypassApproval() const { return mBypassApproval; } void Handler::handleChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList &channels, const QList &requestsSatisfied, const QDateTime &userActionTime, const Tp::AbstractClientHandler::HandlerInfo &handlerInfo) { Q_UNUSED(account) Q_UNUSED(connection) Q_UNUSED(requestsSatisfied) Q_UNUSED(userActionTime) Q_UNUSED(handlerInfo) Q_FOREACH(const Tp::ChannelPtr channel, channels) { Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel); if (textChannel) { Tp::PendingReady *pr = textChannel->becomeReady(Tp::Features() << Tp::TextChannel::FeatureCore << Tp::TextChannel::FeatureChatState << Tp::TextChannel::FeatureMessageCapabilities << Tp::TextChannel::FeatureMessageQueue << Tp::TextChannel::FeatureMessageSentSignal); connect(pr, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onTextChannelReady(Tp::PendingOperation*))); mReadyRequests[pr] = textChannel; continue; } Tp::CallChannelPtr callChannel = Tp::CallChannelPtr::dynamicCast(channel); if (callChannel) { Tp::PendingReady *pr = callChannel->becomeReady(Tp::Features() << Tp::CallChannel::FeatureCore << Tp::CallChannel::FeatureCallState << Tp::CallChannel::FeatureContents << Tp::CallChannel::FeatureLocalHoldState); connect(pr, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onCallChannelReady(Tp::PendingOperation*))); mReadyRequests[pr] = callChannel; continue; } } context->setFinished(); } Tp::ChannelClassSpecList Handler::channelFilters() { Tp::ChannelClassSpecList specList; specList << Tp::ChannelClassSpec::audioCall(); specList << Tp::ChannelClassSpec::textChat(); return specList; } void Handler::onTextChannelReady(Tp::PendingOperation *op) { Tp::PendingReady *pr = qobject_cast(op); if (!pr) { qCritical() << "The pending object is not a Tp::PendingReady"; return; } Tp::ChannelPtr channel = mReadyRequests[pr]; Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel); if(!textChannel) { qCritical() << "The saved channel is not a Tp::TextChannel"; return; } mReadyRequests.remove(pr); Q_EMIT textChannelAvailable(textChannel); } void Handler::onCallChannelReady(Tp::PendingOperation *op) { Tp::PendingReady *pr = qobject_cast(op); if (!pr) { qCritical() << "The pending object is not a Tp::PendingReady"; return; } Tp::ChannelPtr channel = mReadyRequests[pr]; Tp::CallChannelPtr callChannel = Tp::CallChannelPtr::dynamicCast(channel); if(!callChannel) { qCritical() << "The saved channel is not a Tp::CallChannel"; return; } mReadyRequests.remove(pr); Q_EMIT callChannelAvailable(callChannel); } history-service-0.1+16.04.20160104/tests/daemon/approver.h0000644000015600001650000000371212642552153023473 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: Tiago Salem Herrmann */ #ifndef APPROVER_H #define APPROVER_H #include #include #include #include #define TELEPHONY_SERVICE_HANDLER TP_QT_IFACE_CLIENT + ".HistoryTestHandler" #define TELEPHONY_SERVICE_APPROVER TP_QT_IFACE_CLIENT + ".HistoryTestApprover" class Approver : public QObject, public Tp::AbstractClientApprover { Q_OBJECT public: Approver(QObject *parent = 0); ~Approver(); Tp::ChannelClassSpecList channelFilters() const; void addDispatchOperation(const Tp::MethodInvocationContextPtr<> &context, const Tp::ChannelDispatchOperationPtr &dispatchOperation); Q_SIGNALS: void newCall(); public Q_SLOTS: void acceptCall(); void rejectCall(); private Q_SLOTS: void processChannels(); void onClaimFinished(Tp::PendingOperation* op); void onHangupFinished(Tp::PendingOperation* op); void onChannelReady(Tp::PendingOperation *op); protected: Tp::ChannelDispatchOperationPtr dispatchOperation(Tp::PendingOperation *op); private: QList mDispatchOps; QMap mChannels; }; #endif // APPROVER_H history-service-0.1+16.04.20160104/tests/daemon/CMakeLists.txt0000644000015600001650000000067412642552153024230 0ustar pbuserpbgroup00000000000000include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests/common ${TP_QT5_INCLUDE_DIRS} ) generate_telepathy_test(DaemonTest SOURCES DaemonTest.cpp handler.cpp approver.cpp TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon WAIT_FOR com.canonical.HistoryService) history-service-0.1+16.04.20160104/tests/common/0000755000015600001650000000000012642552414021506 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/common/mockcontroller.h0000644000015600001650000000465212642552153024723 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #ifndef MOCKCONTROLLER_H #define MOCKCONTROLLER_H #include #include class MockController : public QObject { Q_OBJECT public: explicit MockController(const QString &protocol, QObject *parent = 0); Q_SIGNALS: void messageRead(const QString &messageId); void messageSent(const QString &message, const QVariantMap &properties); void callReceived(const QString &callerId); void callEnded(const QString &callerId); void callStateChanged(const QString &callerId, const QString &objectPath, const QString &state); void conferenceCreated(const QString &objectPath); void channelMerged(const QString &objectPath); void channelSplitted(const QString &objectPath); void disconnected(); void connectionDestroyed(); public Q_SLOTS: void placeIncomingMessage(const QString &message, const QVariantMap &properties); void placeDeliveryReport(const QStringList &recipients, const QString &messageId, const QString &status); QString placeCall(const QVariantMap &properties); void hangupCall(const QString &callerId); void setCallState(const QString &phoneNumber, const QString &state); void setOnline(bool online); void setPresence(const QString &status, const QString &statusMessage); // voicemail stuff void setVoicemailNumber(const QString &number); void setVoicemailIndicator(bool active); void setVoicemailCount(int count); // emergency numbers stuff void setEmergencyNumbers(const QStringList &numbers); // USSD stuff QString serial(); private: QDBusInterface mMockInterface; QString mProtocol; QString mMockObject; }; #endif // MOCKCONTROLLER_H history-service-0.1+16.04.20160104/tests/common/mock/0000755000015600001650000000000012642552414022437 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/common/mock/connection.h0000644000015600001650000001401012642552153024743 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #ifndef MOCKCONNECTION_H #define MOCKCONNECTION_H // qt #include // telepathy-qt #include #include #include #include #include // mock-cm #include "textchannel.h" #include "callchannel.h" #include "dbustypes.h" #include "emergencymodeiface.h" #include "ussdiface.h" #include "voicemailiface.h" class MockTextChannel; class MockCallChannel; class MockConnectionDBus; class MockConferenceCallChannel; class MockConnection : public Tp::BaseConnection { Q_OBJECT Q_DISABLE_COPY(MockConnection) public: MockConnection(const QDBusConnection &dbusConnection, const QString &cmName, const QString &protocolName, const QVariantMap ¶meters); QStringList inspectHandles(uint handleType, const Tp::UIntList& handles, Tp::DBusError *error); Tp::UIntList requestHandles(uint handleType, const QStringList& identifiers, Tp::DBusError* error); Tp::BaseChannelPtr createChannel(const QVariantMap& request, Tp::DBusError *error); Tp::ContactAttributesMap getContactAttributes(const Tp::UIntList &handles, const QStringList &ifaces, Tp::DBusError *error); uint setPresence(const QString& status, const QString& statusMessage, Tp::DBusError *error); void connect(Tp::DBusError *error); void setOnline(bool online); Tp::BaseConnectionRequestsInterfacePtr requestsIface; Tp::BaseConnectionSimplePresenceInterfacePtr simplePresenceIface; Tp::BaseConnectionContactsInterfacePtr contactsIface; uint newHandle(const QString &identifier); QMap callChannels(); // phone custom interfaces BaseConnectionEmergencyModeInterfacePtr emergencyModeIface; BaseConnectionVoicemailInterfacePtr voicemailIface; BaseConnectionUSSDInterfacePtr supplementaryServicesIface; uint ensureHandle(const QString &id); Tp::BaseChannelPtr createTextChannel(uint targetHandleType, uint targetHandle, const QVariantMap &hints, Tp::DBusError *error); Tp::BaseChannelPtr createCallChannel(uint targetHandleType, uint targetHandle, const QVariantMap &hints, Tp::DBusError *error); ~MockConnection(); QString placeCall(const QVariantMap &properties); QStringList emergencyNumbers(Tp::DBusError *error); void setEmergencyNumbers(const QStringList &emergencyNumbers); bool voicemailIndicator(Tp::DBusError *error); void setVoicemailIndicator(bool visible); QString voicemailNumber(Tp::DBusError *error); void setVoicemailNumber(const QString &number); uint voicemailCount(Tp::DBusError *error); void setVoicemailCount(int count); void USSDInitiate(const QString &command, Tp::DBusError *error); void USSDRespond(const QString &reply, Tp::DBusError *error); void USSDCancel(Tp::DBusError *error); QString serial(); // FIXME: there is a problem in telepathy-qt that connection object paths and services are not properly unregistered // and thus if we gain the same memory address (and thus the same pointer) every time we reconnect, there might be some problems QString uniqueName() const; Q_SIGNALS: void messageRead(const QString &messageId); void messageSent(const QString &message, const QVariantMap &info); void callReceived(const QString &callerId); void callEnded(const QString &callerId); void callStateChanged(const QString &callerId, const QString &objectPath, const QString &state); void conferenceCreated(const QString &objectPath); void channelMerged(const QString &objectPath); void channelSplitted(const QString &objectPath); void channelSplitted(const QDBusObjectPath &objectPath); public Q_SLOTS: void placeIncomingMessage(const QString &message, const QVariantMap &info); void placeDeliveryReport(const QStringList &recipients, const QString &messageId, const QString &status); void hangupCall(const QString &callerId); void setCallState(const QString &phoneNumber, const QString &state); void onTextChannelClosed(); void onCallChannelClosed(); void onCallChannelDestroyed(); void onCallStateChanged(MockCallChannel *channel, const QString &state); void onMessageRead(const QString &id); void onConferenceCallChannelClosed(); void onCallChannelSplitted(); private: void addMMSToService(const QString &path, const QVariantMap &properties, const QString &servicePath); MockTextChannel *textChannelForRecipients(const QStringList &recipients); QMap mHandles; Tp::SimpleStatusSpecMap mStatuses; QList mTextChannels; QMap mCallChannels; QMap mInitialCallStatus; QStringList mModems; Tp::SimplePresence mSelfPresence; MockConnectionDBus *mDBus; QStringList mIncomingCalls; MockConferenceCallChannel *mConferenceCall; QStringList mEmergencyNumbers; int mVoicemailCount; bool mVoicemailIndicator; QString mVoicemailNumber; }; #endif history-service-0.1+16.04.20160104/tests/common/mock/ussdiface.cpp0000644000015600001650000002477412642552153025127 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #include #include #include #include "ussdiface.h" // Conn.I.USSD BaseConnectionUSSDInterface::Adaptee::Adaptee(BaseConnectionUSSDInterface *interface) : QObject(interface), mInterface(interface) { } struct TP_QT_NO_EXPORT BaseConnectionUSSDInterface::Private { Private(BaseConnectionUSSDInterface *parent) : adaptee(new BaseConnectionUSSDInterface::Adaptee(parent)) { } QString state; QString serial; InitiateCallback initiateCB; RespondCallback respondCB; CancelCallback cancelCB; BaseConnectionUSSDInterface::Adaptee *adaptee; }; BaseConnectionUSSDInterface::Adaptee::~Adaptee() { } void BaseConnectionUSSDInterface::Adaptee::initiate(const QString &command, const ConnectionInterfaceUSSDAdaptor::InitiateContextPtr &context) { if (!mInterface->mPriv->initiateCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; mInterface->mPriv->initiateCB(command, &error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(); } void BaseConnectionUSSDInterface::Adaptee::respond(const QString &reply, const ConnectionInterfaceUSSDAdaptor::RespondContextPtr &context) { if (!mInterface->mPriv->respondCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; mInterface->mPriv->respondCB(reply, &error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(); } void BaseConnectionUSSDInterface::Adaptee::cancel(const ConnectionInterfaceUSSDAdaptor::CancelContextPtr &context) { if (!mInterface->mPriv->cancelCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; mInterface->mPriv->cancelCB(&error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(); } BaseConnectionUSSDInterface::BaseConnectionUSSDInterface() : AbstractConnectionInterface(TP_QT_IFACE_CONNECTION_USSD), mPriv(new Private(this)) { } BaseConnectionUSSDInterface::~BaseConnectionUSSDInterface() { delete mPriv; } void BaseConnectionUSSDInterface::setInitiateCallback(const InitiateCallback &cb) { mPriv->initiateCB = cb; } void BaseConnectionUSSDInterface::setRespondCallback(const RespondCallback &cb) { mPriv->respondCB = cb; } void BaseConnectionUSSDInterface::setCancelCallback(const CancelCallback &cb) { mPriv->cancelCB = cb; } QString BaseConnectionUSSDInterface::state() const { return mPriv->state; } void BaseConnectionUSSDInterface::setSerial(const QString &serial) const { mPriv->serial = serial; } QString BaseConnectionUSSDInterface::serial() const { return mPriv->serial; } void BaseConnectionUSSDInterface::StateChanged(const QString &state) { mPriv->state = state; Q_EMIT mPriv->adaptee->stateChanged(state); } void BaseConnectionUSSDInterface::InitiateUSSDComplete(const QString &ussdResp) { Q_EMIT mPriv->adaptee->initiateUSSDComplete(ussdResp); } void BaseConnectionUSSDInterface::RespondComplete(bool success, const QString &ussdResp) { Q_EMIT mPriv->adaptee->respondComplete(success, ussdResp); } void BaseConnectionUSSDInterface::BarringComplete(const QString &ssOp, const QString &cbService, const QVariantMap &cbMap) { Q_EMIT mPriv->adaptee->barringComplete(ssOp, cbService, cbMap); } void BaseConnectionUSSDInterface::ForwardingComplete(const QString &ssOp, const QString &cfService, const QVariantMap &cfMap) { Q_EMIT mPriv->adaptee->forwardingComplete(ssOp, cfService, cfMap); } void BaseConnectionUSSDInterface::WaitingComplete(const QString &ssOp, const QVariantMap &cwMap) { Q_EMIT mPriv->adaptee->waitingComplete(ssOp, cwMap); } void BaseConnectionUSSDInterface::CallingLinePresentationComplete(const QString &ssOp, const QString &status) { Q_EMIT mPriv->adaptee->callingLinePresentationComplete(ssOp, status); } void BaseConnectionUSSDInterface::ConnectedLinePresentationComplete(const QString &ssOp, const QString &status) { Q_EMIT mPriv->adaptee->connectedLinePresentationComplete(ssOp, status); } void BaseConnectionUSSDInterface::CallingLineRestrictionComplete(const QString &ssOp, const QString &status) { Q_EMIT mPriv->adaptee->callingLineRestrictionComplete(ssOp, status); } void BaseConnectionUSSDInterface::ConnectedLineRestrictionComplete(const QString &ssOp, const QString &status) { Q_EMIT mPriv->adaptee->connectedLineRestrictionComplete(ssOp, status); } void BaseConnectionUSSDInterface::InitiateFailed() { Q_EMIT mPriv->adaptee->initiateFailed(); } void BaseConnectionUSSDInterface::NotificationReceived(const QString &message) { Q_EMIT mPriv->adaptee->notificationReceived(message); } void BaseConnectionUSSDInterface::RequestReceived(const QString &message) { Q_EMIT mPriv->adaptee->requestReceived(message); } QVariantMap BaseConnectionUSSDInterface::immutableProperties() const { QVariantMap map; return map; } void BaseConnectionUSSDInterface::createAdaptor() { (void) new ConnectionInterfaceUSSDAdaptor(dbusObject()->dbusConnection(), mPriv->adaptee, dbusObject()); } ConnectionInterfaceUSSDAdaptor::ConnectionInterfaceUSSDAdaptor(const QDBusConnection& bus, QObject* adaptee, QObject* parent) : Tp::AbstractAdaptor(bus, adaptee, parent) { connect(adaptee, SIGNAL(notificationReceived(const QString &)), SIGNAL(NotificationReceived(const QString &))); connect(adaptee, SIGNAL(requestReceived(const QString &)), SIGNAL(RequestReceived(const QString &))); connect(adaptee, SIGNAL(initiateUSSDComplete(const QString &)), SIGNAL(InitiateUSSDComplete(const QString &))); connect(adaptee, SIGNAL(barringComplete(const QString &, const QString &, const QVariantMap &)), SIGNAL(BarringComplete(const QString &, const QString &, const QVariantMap &))); connect(adaptee, SIGNAL(forwardingComplete(const QString &, const QString &, const QVariantMap &)), SIGNAL(ForwardingComplete(const QString &, const QString &, const QVariantMap &))); connect(adaptee, SIGNAL(waitingComplete(const QString &, const QVariantMap &)), SIGNAL(WaitingComplete(const QString &, const QVariantMap &))); connect(adaptee, SIGNAL(callingLinePresentationComplete(const QString &, const QString &)), SIGNAL(CallingLinePresentationComplete(const QString &, const QString &))); connect(adaptee, SIGNAL(connectedLinePresentationComplete(const QString &, const QString &)), SIGNAL(ConnectedLinePresentationComplete(const QString &, const QString &))); connect(adaptee, SIGNAL(callingLineRestrictionComplete(const QString &, const QString &)), SIGNAL(CallingLineRestrictionComplete(const QString &, const QString &))); connect(adaptee, SIGNAL(connectedLineRestrictionComplete(const QString &, const QString &)), SIGNAL(ConnectedLineRestrictionComplete(const QString &, const QString &))); connect(adaptee, SIGNAL(initiateFailed()), SIGNAL(InitiateFailed())); connect(adaptee, SIGNAL(stateChanged(const QString&)), SIGNAL(StateChanged(const QString&))); connect(adaptee, SIGNAL(respondComplete(bool, const QString &)), SIGNAL(RespondComplete(bool, const QString &))); } ConnectionInterfaceUSSDAdaptor::~ConnectionInterfaceUSSDAdaptor() { } void ConnectionInterfaceUSSDAdaptor::Initiate(const QString &command, const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("initiate(const QString &,ConnectionInterfaceUSSDAdaptor::InitiateContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return; } InitiateContextPtr ctx = InitiateContextPtr( new Tp::MethodInvocationContext< >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "initiate", Q_ARG(QString, command), Q_ARG(ConnectionInterfaceUSSDAdaptor::InitiateContextPtr, ctx)); return; } void ConnectionInterfaceUSSDAdaptor::Respond(const QString &reply, const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("respond(QConnectionInterfaceUSSDAdaptor::RespondContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return; } RespondContextPtr ctx = RespondContextPtr( new Tp::MethodInvocationContext< >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "respond", Q_ARG(QString, reply), Q_ARG(ConnectionInterfaceUSSDAdaptor::RespondContextPtr, ctx)); return; } void ConnectionInterfaceUSSDAdaptor::Cancel(const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("cancel(ConnectionInterfaceUSSDAdaptor::CancelContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return; } CancelContextPtr ctx = CancelContextPtr( new Tp::MethodInvocationContext< >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "cancel", Q_ARG(ConnectionInterfaceUSSDAdaptor::CancelContextPtr, ctx)); return; } QString ConnectionInterfaceUSSDAdaptor::Serial() const { return qvariant_cast< QString >(adaptee()->property("serial")); } QString ConnectionInterfaceUSSDAdaptor::State() const { return qvariant_cast< QString >(adaptee()->property("state")); } history-service-0.1+16.04.20160104/tests/common/mock/main.cpp0000644000015600001650000000310412642552153024065 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann */ #include #include #include #include "protocol.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Tp::registerTypes(); Tp::enableDebug(true); Tp::enableWarnings(true); // create a standard protocol Tp::BaseProtocolPtr proto = Tp::BaseProtocol::create( QDBusConnection::sessionBus(), QLatin1String("mock")); // create a phone protocol Tp::BaseProtocolPtr phoneProto = Tp::BaseProtocol::create( QDBusConnection::sessionBus(), QLatin1String("ofono")); Tp::BaseConnectionManagerPtr cm = Tp::BaseConnectionManager::create( QDBusConnection::sessionBus(), QLatin1String("mock")); cm->addProtocol(phoneProto); cm->addProtocol(proto); cm->registerObject(); return a.exec(); } history-service-0.1+16.04.20160104/tests/common/mock/callchannel.h0000644000015600001650000000510312642552153025053 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #ifndef MOCKCALLCHANNEL_H #define MOCKCALLCHANNEL_H #include #include #include #include #include #include "connection.h" class MockConnection; class MockCallChannel : public QObject { Q_OBJECT public: MockCallChannel(MockConnection *conn, QString phoneNumber, QString state, uint targetHandle, QObject *parent = 0); ~MockCallChannel(); Tp::BaseChannelPtr baseChannel(); void onHangup(uint reason, const QString &detailedReason, const QString &message, Tp::DBusError* error); void onAccept(Tp::DBusError*); void onMuteStateChanged(const Tp::LocalMuteState &state, Tp::DBusError *error); void onHoldStateChanged(const Tp::LocalHoldState &state, const Tp::LocalHoldStateReason &reason, Tp::DBusError *error); void onDTMFStartTone(uchar event, Tp::DBusError *error); void onDTMFStopTone(Tp::DBusError *error); void onSplit(Tp::DBusError *error); QString objectPath() const; Tp::CallState callState() const; public Q_SLOTS: void setCallState(const QString &state); void init(); void onOfonoMuteChanged(bool mute); Q_SIGNALS: void callStateChanged(MockCallChannel *channel, const QString &state); void splitted(); private: QString mObjPath; QString mState; bool mIncoming; bool mRequestedHangup; Tp::BaseChannelPtr mBaseChannel; QString mPhoneNumber; MockConnection *mConnection; uint mTargetHandle; Tp::BaseChannelHoldInterfacePtr mHoldIface; Tp::BaseCallMuteInterfacePtr mMuteIface; Tp::BaseChannelSplittableInterfacePtr mSplittableIface; Tp::BaseChannelCallTypePtr mCallChannel; Tp::BaseCallContentDTMFInterfacePtr mDTMFIface; Tp::BaseCallContentPtr mCallContent; }; #endif // MOCKCALLCHANNEL_H history-service-0.1+16.04.20160104/tests/common/mock/emergencymodeiface.h0000644000015600001650000000766512642552153026441 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #ifndef OFONOEMERGENCYMODEIFACE_H #define OFONOEMERGENCYMODEIFACE_H // telepathy-qt #include #include #include #include #include class BaseConnectionEmergencyModeInterface; typedef Tp::SharedPtr BaseConnectionEmergencyModeInterfacePtr; #define TP_QT_IFACE_CONNECTION_EMERGENCYMODE "com.canonical.Telephony.EmergencyMode" class TP_QT_EXPORT BaseConnectionEmergencyModeInterface : public Tp::AbstractConnectionInterface { Q_OBJECT Q_DISABLE_COPY(BaseConnectionEmergencyModeInterface) public: static BaseConnectionEmergencyModeInterfacePtr create() { return BaseConnectionEmergencyModeInterfacePtr(new BaseConnectionEmergencyModeInterface()); } template static Tp::SharedPtr create() { return Tp::SharedPtr( new BaseConnectionEmergencyModeInterfaceSubclass()); } QVariantMap immutableProperties() const; virtual ~BaseConnectionEmergencyModeInterface(); typedef Tp::Callback1 EmergencyNumbersCallback; void setEmergencyNumbersCallback(const EmergencyNumbersCallback &cb); void setFakeEmergencyNumber(const QString &fakeEmergencyNumber); public Q_SLOTS: void setEmergencyNumbers(const QStringList &numbers); protected: BaseConnectionEmergencyModeInterface(); private: void createAdaptor(); class Adaptee; friend class Adaptee; struct Private; friend struct Private; Private *mPriv; }; class TP_QT_EXPORT ConnectionInterfaceEmergencyModeAdaptor : public Tp::AbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", TP_QT_IFACE_CONNECTION_EMERGENCYMODE) Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") public: ConnectionInterfaceEmergencyModeAdaptor(const QDBusConnection& dbusConnection, QObject* adaptee, QObject* parent); virtual ~ConnectionInterfaceEmergencyModeAdaptor(); typedef Tp::MethodInvocationContextPtr< QStringList > EmergencyNumbersContextPtr; public Q_SLOTS: // METHODS QStringList EmergencyNumbers(const QDBusMessage& dbusMessage); Q_SIGNALS: // SIGNALS void EmergencyNumbersChanged(const QStringList &numbers); }; class TP_QT_NO_EXPORT BaseConnectionEmergencyModeInterface::Adaptee : public QObject { Q_OBJECT public: Adaptee(BaseConnectionEmergencyModeInterface *interface); ~Adaptee(); private Q_SLOTS: void emergencyNumbers(const ConnectionInterfaceEmergencyModeAdaptor::EmergencyNumbersContextPtr &context); Q_SIGNALS: void emergencyNumbersChanged(const QStringList &numbers); public: BaseConnectionEmergencyModeInterface *mInterface; }; #endif history-service-0.1+16.04.20160104/tests/common/mock/conferencecallchannel.h0000644000015600001650000000557012642552153027113 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #ifndef MOCKCONFERENCECALLCHANNEL_H #define MOCKCONFERENCECALLCHANNEL_H #include #include #include #include #include #include "connection.h" #include "speakeriface.h" class MockConnection; class MockConferenceCallChannel : public QObject { Q_OBJECT public: MockConferenceCallChannel(MockConnection *conn, QList callChannels, QObject *parent = 0); ~MockConferenceCallChannel(); void onHangup(uint reason, const QString &detailedReason, const QString &message, Tp::DBusError* error); void onMuteStateChanged(const Tp::LocalMuteState &state, Tp::DBusError *error); void onHoldStateChanged(const Tp::LocalHoldState &state, const Tp::LocalHoldStateReason &reason, Tp::DBusError *error); void onDTMFStartTone(uchar event, Tp::DBusError *error); void onDTMFStopTone(Tp::DBusError *error); void onTurnOnSpeaker(bool active, Tp::DBusError *error); void onMerge(const QDBusObjectPath &channel, Tp::DBusError *error); Tp::BaseChannelPtr baseChannel(); void setConferenceActive(bool active); Q_SIGNALS: void channelMerged(const QString &objectPath); void initialized(); private Q_SLOTS: void onDtmfComplete(bool success); void sendNextDtmf(); void init(); void onOfonoMuteChanged(bool mute); void onChannelSplitted(const QDBusObjectPath &path); private: QString mObjPath; QString mPreviousState; bool mIncoming; bool mRequestedHangup; MockConnection *mConnection; QList mCallChannels; Tp::BaseChannelPtr mBaseChannel; Tp::BaseChannelHoldInterfacePtr mHoldIface; Tp::BaseChannelConferenceInterfacePtr mConferenceIface; Tp::BaseChannelMergeableConferenceInterfacePtr mMergeableIface; Tp::BaseCallMuteInterfacePtr mMuteIface; BaseChannelSpeakerInterfacePtr mSpeakerIface; Tp::BaseChannelCallTypePtr mCallChannel; Tp::BaseCallContentDTMFInterfacePtr mDTMFIface; bool mDtmfLock; QStringList mDtmfPendingStrings; }; #endif // MOCKCONFERENCECALLCHANNEL_H history-service-0.1+16.04.20160104/tests/common/mock/callchannel.cpp0000644000015600001650000002002412642552153025405 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #include "callchannel.h" MockCallChannel::MockCallChannel(MockConnection *conn, QString phoneNumber, QString state, uint targetHandle, QObject *parent): QObject(parent), mIncoming(false), mRequestedHangup(false), mConnection(conn), mPhoneNumber(phoneNumber), mTargetHandle(targetHandle), mState(state) { Tp::BaseChannelPtr baseChannel = Tp::BaseChannel::create(mConnection, TP_QT_IFACE_CHANNEL_TYPE_CALL, Tp::HandleTypeContact, targetHandle); Tp::BaseChannelCallTypePtr callType = Tp::BaseChannelCallType::create(baseChannel.data(), true, Tp::StreamTransportTypeUnknown, true, false, "",""); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(callType)); mHoldIface = Tp::BaseChannelHoldInterface::create(); mHoldIface->setSetHoldStateCallback(Tp::memFun(this,&MockCallChannel::onHoldStateChanged)); mMuteIface = Tp::BaseCallMuteInterface::create(); mMuteIface->setSetMuteStateCallback(Tp::memFun(this,&MockCallChannel::onMuteStateChanged)); mSplittableIface = Tp::BaseChannelSplittableInterface::create(); mSplittableIface->setSplitCallback(Tp::memFun(this,&MockCallChannel::onSplit)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mHoldIface)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mMuteIface)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mSplittableIface)); mBaseChannel = baseChannel; mCallChannel = Tp::BaseChannelCallTypePtr::dynamicCast(mBaseChannel->interface(TP_QT_IFACE_CHANNEL_TYPE_CALL)); mCallChannel->setHangupCallback(Tp::memFun(this,&MockCallChannel::onHangup)); mCallChannel->setAcceptCallback(Tp::memFun(this,&MockCallChannel::onAccept)); // init must be called after initialization, otherwise we will have no object path registered. QTimer::singleShot(0, this, SLOT(init())); } void MockCallChannel::onHangup(uint reason, const QString &detailedReason, const QString &message, Tp::DBusError *error) { // TODO: use the parameters sent by telepathy mRequestedHangup = true; setCallState("disconnected"); } void MockCallChannel::onAccept(Tp::DBusError*) { setCallState("active"); } void MockCallChannel::init() { Tp::CallMemberMap memberFlags; Tp::HandleIdentifierMap identifiers; QVariantMap stateDetails; Tp::CallStateReason reason; mIncoming = mState == "incoming" || mState == "waiting"; identifiers[mTargetHandle] = mPhoneNumber; reason.actor = 0; reason.reason = Tp::CallStateChangeReasonProgressMade; reason.message = ""; reason.DBusReason = ""; if (mIncoming) { memberFlags[mTargetHandle] = 0; } else { memberFlags[mTargetHandle] = Tp::CallMemberFlagRinging; } mCallChannel->setCallState(Tp::CallStateInitialising, 0, reason, stateDetails); mCallContent = Tp::BaseCallContent::create(baseChannel()->dbusConnection(), baseChannel().data(), "audio", Tp::MediaStreamTypeAudio, Tp::MediaStreamDirectionNone); mDTMFIface = Tp::BaseCallContentDTMFInterface::create(); mCallContent->plugInterface(Tp::AbstractCallContentInterfacePtr::dynamicCast(mDTMFIface)); mCallChannel->addContent(mCallContent); mDTMFIface->setStartToneCallback(Tp::memFun(this,&MockCallChannel::onDTMFStartTone)); mDTMFIface->setStopToneCallback(Tp::memFun(this,&MockCallChannel::onDTMFStopTone)); mCallChannel->setMembersFlags(memberFlags, identifiers, Tp::UIntList(), reason); mCallChannel->setCallState(Tp::CallStateInitialised, 0, reason, stateDetails); QObject::connect(mBaseChannel.data(), SIGNAL(closed()), this, SLOT(deleteLater())); } void MockCallChannel::onOfonoMuteChanged(bool mute) { Tp::LocalMuteState state = mute ? Tp::LocalMuteStateMuted : Tp::LocalMuteStateUnmuted; mMuteIface->setMuteState(state); } void MockCallChannel::onHoldStateChanged(const Tp::LocalHoldState &state, const Tp::LocalHoldStateReason &reason, Tp::DBusError *error) { if (state == Tp::LocalHoldStateHeld && mState == "active") { setCallState("held"); } else if (state == Tp::LocalHoldStateUnheld && mState == "held") { setCallState("active"); } } void MockCallChannel::onMuteStateChanged(const Tp::LocalMuteState &state, Tp::DBusError *error) { #if 0 FIXME: reimplement if (state == Tp::LocalMuteStateMuted) { mConnection->callVolume()->setMuted(true); } else if (state == Tp::LocalMuteStateUnmuted) { mConnection->callVolume()->setMuted(false); } #endif } void MockCallChannel::onDTMFStartTone(uchar event, Tp::DBusError *error) { QString finalString; if (event == 10) { finalString = "*"; } else if (event == 11) { finalString = "#"; } else { finalString = QString::number(event); } qDebug() << "start tone" << finalString; } void MockCallChannel::onDTMFStopTone(Tp::DBusError *error) { } void MockCallChannel::onSplit(Tp::DBusError *error) { Q_EMIT splitted(); } QString MockCallChannel::objectPath() const { return mBaseChannel->objectPath(); } Tp::CallState MockCallChannel::callState() const { return (Tp::CallState) mCallChannel->callState(); } MockCallChannel::~MockCallChannel() { qDebug() << "call channel closed"; // TODO - for some reason the object is not being removed mConnection->dbusConnection().unregisterObject(mBaseChannel->objectPath(), QDBusConnection::UnregisterTree); } Tp::BaseChannelPtr MockCallChannel::baseChannel() { return mBaseChannel; } void MockCallChannel::setCallState(const QString &state) { Tp::CallStateReason reason; QVariantMap stateDetails; reason.actor = 0; reason.reason = Tp::CallStateChangeReasonUserRequested; reason.message = ""; reason.DBusReason = ""; if (state == "disconnected") { qDebug() << "disconnected"; if (mIncoming && mState == "incoming" && !mRequestedHangup) { reason.reason = Tp::CallStateChangeReasonNoAnswer; } mCallChannel->setCallState(Tp::CallStateEnded, 0, reason, stateDetails); mBaseChannel->close(); } else if (state == "active") { qDebug() << "active"; mHoldIface->setHoldState(Tp::LocalHoldStateUnheld, Tp::LocalHoldStateReasonNone); if (mState == "dialing" || mState == "alerting" || mState == "incoming") { mCallChannel->setCallState(Tp::CallStateAccepted, 0, reason, stateDetails); } mCallChannel->setCallState(Tp::CallStateActive, 0, reason, stateDetails); } else if (state == "held") { mHoldIface->setHoldState(Tp::LocalHoldStateHeld, Tp::LocalHoldStateReasonNone); qDebug() << "held"; } else if (state == "dialing") { qDebug() << "dialing"; } else if (state == "alerting") { qDebug() << "alerting"; } else if (state == "incoming") { mIncoming = true; qDebug() << "incoming"; } else if (state == "waiting") { mIncoming = true; qDebug() << "waiting"; } mState = state; Q_EMIT callStateChanged(this, state); } history-service-0.1+16.04.20160104/tests/common/mock/emergencymodeiface.cpp0000644000015600001650000001106712642552153026763 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #include #include #include #include "emergencymodeiface.h" BaseConnectionEmergencyModeInterface::Adaptee::Adaptee(BaseConnectionEmergencyModeInterface *interface) : QObject(interface), mInterface(interface) { } struct TP_QT_NO_EXPORT BaseConnectionEmergencyModeInterface::Private { Private(BaseConnectionEmergencyModeInterface *parent) : adaptee(new BaseConnectionEmergencyModeInterface::Adaptee(parent)) { } EmergencyNumbersCallback emergencyNumbersCB; BaseConnectionEmergencyModeInterface::Adaptee *adaptee; QString fakeEmergencyNumber; }; BaseConnectionEmergencyModeInterface::Adaptee::~Adaptee() { } void BaseConnectionEmergencyModeInterface::Adaptee::emergencyNumbers(const ConnectionInterfaceEmergencyModeAdaptor::EmergencyNumbersContextPtr &context) { if (!mInterface->mPriv->emergencyNumbersCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; QStringList numbers = mInterface->mPriv->emergencyNumbersCB(&error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } if (mInterface->mPriv->fakeEmergencyNumber.isEmpty()) { context->setFinished(numbers); } else { context->setFinished(QStringList() << numbers << mInterface->mPriv->fakeEmergencyNumber); } } BaseConnectionEmergencyModeInterface::BaseConnectionEmergencyModeInterface() : AbstractConnectionInterface(TP_QT_IFACE_CONNECTION_EMERGENCYMODE), mPriv(new Private(this)) { } BaseConnectionEmergencyModeInterface::~BaseConnectionEmergencyModeInterface() { delete mPriv; } void BaseConnectionEmergencyModeInterface::setEmergencyNumbersCallback(const EmergencyNumbersCallback &cb) { mPriv->emergencyNumbersCB = cb; } void BaseConnectionEmergencyModeInterface::setEmergencyNumbers(const QStringList &numbers) { QStringList finalEmergencyList(numbers); if (!mPriv->fakeEmergencyNumber.isEmpty()) { finalEmergencyList << mPriv->fakeEmergencyNumber; } Q_EMIT mPriv->adaptee->emergencyNumbersChanged(finalEmergencyList); } void BaseConnectionEmergencyModeInterface::setFakeEmergencyNumber(const QString &fakeEmergencyNumber) { mPriv->fakeEmergencyNumber = fakeEmergencyNumber; } QVariantMap BaseConnectionEmergencyModeInterface::immutableProperties() const { QVariantMap map; return map; } void BaseConnectionEmergencyModeInterface::createAdaptor() { (void) new ConnectionInterfaceEmergencyModeAdaptor(dbusObject()->dbusConnection(), mPriv->adaptee, dbusObject()); } ConnectionInterfaceEmergencyModeAdaptor::ConnectionInterfaceEmergencyModeAdaptor(const QDBusConnection& bus, QObject* adaptee, QObject* parent) : Tp::AbstractAdaptor(bus, adaptee, parent) { connect(adaptee, SIGNAL(emergencyNumbersChanged(QStringList)), SIGNAL(EmergencyNumbersChanged(QStringList))); } ConnectionInterfaceEmergencyModeAdaptor::~ConnectionInterfaceEmergencyModeAdaptor() { } QStringList ConnectionInterfaceEmergencyModeAdaptor::EmergencyNumbers(const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("emergencyNumbers(ConnectionInterfaceEmergencyModeAdaptor::EmergencyNumbersContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return QStringList(); } EmergencyNumbersContextPtr ctx = EmergencyNumbersContextPtr( new Tp::MethodInvocationContext< QStringList >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "emergencyNumbers", Q_ARG(ConnectionInterfaceEmergencyModeAdaptor::EmergencyNumbersContextPtr, ctx)); return QStringList(); } history-service-0.1+16.04.20160104/tests/common/mock/conferencecallchannel.cpp0000644000015600001650000002365212642552153027447 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #include "conferencecallchannel.h" #include "callchannel.h" MockConferenceCallChannel::MockConferenceCallChannel(MockConnection *conn, QList callChannels, QObject *parent): mRequestedHangup(false), mConnection(conn), mDtmfLock(false), mCallChannels(callChannels) { Q_FOREACH(MockCallChannel *channel, mConnection->callChannels().values()) { if (channel->callState() == Tp::CallStateActive) { QDBusObjectPath path(channel->baseChannel()->objectPath()); mCallChannels << path; } } Tp::BaseChannelPtr baseChannel = Tp::BaseChannel::create(mConnection, TP_QT_IFACE_CHANNEL_TYPE_CALL, Tp::HandleTypeNone, 0); Tp::BaseChannelCallTypePtr callType = Tp::BaseChannelCallType::create(baseChannel.data(), true, Tp::StreamTransportTypeUnknown, true, false, "",""); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(callType)); mHoldIface = Tp::BaseChannelHoldInterface::create(); mHoldIface->setSetHoldStateCallback(Tp::memFun(this,&MockConferenceCallChannel::onHoldStateChanged)); mMuteIface = Tp::BaseCallMuteInterface::create(); mMuteIface->setSetMuteStateCallback(Tp::memFun(this,&MockConferenceCallChannel::onMuteStateChanged)); mSpeakerIface = BaseChannelSpeakerInterface::create(); mSpeakerIface->setTurnOnSpeakerCallback(Tp::memFun(this,&MockConferenceCallChannel::onTurnOnSpeaker)); mConferenceIface = Tp::BaseChannelConferenceInterface::create(mCallChannels); mMergeableIface = Tp::BaseChannelMergeableConferenceInterface::create(); mMergeableIface->setMergeCallback(Tp::memFun(this,&MockConferenceCallChannel::onMerge)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mHoldIface)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mMuteIface)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mSpeakerIface)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mConferenceIface)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mMergeableIface)); mBaseChannel = baseChannel; mCallChannel = Tp::BaseChannelCallTypePtr::dynamicCast(mBaseChannel->interface(TP_QT_IFACE_CHANNEL_TYPE_CALL)); mCallChannel->setHangupCallback(Tp::memFun(this,&MockConferenceCallChannel::onHangup)); Tp::CallStateReason reason; QVariantMap stateDetails; reason.actor = 0; reason.reason = Tp::CallStateChangeReasonUserRequested; reason.message = ""; reason.DBusReason = ""; mCallChannel->setCallState(Tp::CallStateActive, 0, reason, stateDetails); QObject::connect(mConnection, SIGNAL(channelSplitted(QDBusObjectPath)), SLOT(onChannelSplitted(QDBusObjectPath))); // init must be called after initialization, otherwise we will have no object path registered. QTimer::singleShot(0, this, SLOT(init())); } Tp::BaseChannelPtr MockConferenceCallChannel::baseChannel() { return mBaseChannel; } void MockConferenceCallChannel::onMerge(const QDBusObjectPath &channel, Tp::DBusError *error) { if (!mCallChannels.contains(channel)) { mCallChannels << channel; mConferenceIface->mergeChannel(channel, 0, QVariantMap()); Q_EMIT channelMerged(channel.path()); } } void MockConferenceCallChannel::onChannelSplitted(const QDBusObjectPath &path) { if (mCallChannels.contains(path)) { mCallChannels.removeAll(path); mConferenceIface->removeChannel(path, QVariantMap()); } if (mCallChannels.size() == 1) { // remove the call channel from the conference before closing it. mConferenceIface->removeChannel(mCallChannels.takeFirst(), QVariantMap()); Tp::CallStateReason reason; QVariantMap stateDetails; reason.actor = 0; reason.reason = Tp::CallStateChangeReasonUserRequested; reason.message = ""; reason.DBusReason = ""; mCallChannel->setCallState(Tp::CallStateEnded, 0, reason, stateDetails); mBaseChannel->close(); } } void MockConferenceCallChannel::onTurnOnSpeaker(bool active, Tp::DBusError *error) { //mConnection->setSpeakerMode(active); // FIXME: reimplement } void MockConferenceCallChannel::onHangup(uint reason, const QString &detailedReason, const QString &message, Tp::DBusError *error) { //FIXME: reimplement Tp::CallStateReason theReason; QVariantMap stateDetails; theReason.actor = 0; theReason.reason = reason; theReason.message = message; theReason.DBusReason = ""; mCallChannel->setCallState(Tp::CallStateEnded, 0, theReason, stateDetails); mBaseChannel->close(); } void MockConferenceCallChannel::init() { QVariantMap stateDetails; Tp::CallStateReason reason; mObjPath = mBaseChannel->objectPath(); reason.actor = 0; reason.reason = Tp::CallStateChangeReasonProgressMade; reason.message = ""; reason.DBusReason = ""; mCallChannel->setCallState(Tp::CallStateActive, 0, reason, stateDetails); mDTMFIface = Tp::BaseCallContentDTMFInterface::create(); mDTMFIface->setStartToneCallback(Tp::memFun(this,&MockConferenceCallChannel::onDTMFStartTone)); mDTMFIface->setStopToneCallback(Tp::memFun(this,&MockConferenceCallChannel::onDTMFStopTone)); QObject::connect(mBaseChannel.data(), SIGNAL(closed()), this, SLOT(deleteLater())); //QObject::connect(mConnection->callVolume(), SIGNAL(mutedChanged(bool)), SLOT(onOfonoMuteChanged(bool))); QObject::connect(mConnection, SIGNAL(speakerModeChanged(bool)), mSpeakerIface.data(), SLOT(setSpeakerMode(bool))); //QObject::connect(mConnection->voiceCallManager(), SIGNAL(sendTonesComplete(bool)), SLOT(onDtmfComplete(bool))); //mSpeakerIface->setSpeakerMode(mConnection->speakerMode()); QObject::connect(mConnection, SIGNAL(channelSplitted(const QDBusObjectPath&)), this, SLOT(onChannelSplitted(const QDBusObjectPath&))); QObject::connect(mConnection, SIGNAL(channelHangup(const QDBusObjectPath&)), this, SLOT(onChannelSplitted(const QDBusObjectPath&))); Q_EMIT initialized(); } void MockConferenceCallChannel::onOfonoMuteChanged(bool mute) { Tp::LocalMuteState state = mute ? Tp::LocalMuteStateMuted : Tp::LocalMuteStateUnmuted; mMuteIface->setMuteState(state); } void MockConferenceCallChannel::setConferenceActive(bool active) { if (active) { mHoldIface->setHoldState(Tp::LocalHoldStateUnheld, Tp::LocalHoldStateReasonNone); } else { mHoldIface->setHoldState(Tp::LocalHoldStateHeld, Tp::LocalHoldStateReasonNone); } } void MockConferenceCallChannel::onHoldStateChanged(const Tp::LocalHoldState &state, const Tp::LocalHoldStateReason &reason, Tp::DBusError *error) { /*if (state == Tp::LocalHoldStateHeld && mHoldIface->getHoldState() == Tp::LocalHoldStateUnheld) { mConnection->voiceCallManager()->swapCalls(); } else if (state == Tp::LocalHoldStateUnheld && mHoldIface->getHoldState() == Tp::LocalHoldStateHeld) { mConnection->voiceCallManager()->swapCalls(); }*/ // FIXME: reimplement } void MockConferenceCallChannel::onMuteStateChanged(const Tp::LocalMuteState &state, Tp::DBusError *error) { /*if (state == Tp::LocalMuteStateMuted) { mConnection->callVolume()->setMuted(true); } else if (state == Tp::LocalMuteStateUnmuted) { mConnection->callVolume()->setMuted(false); }*/ // FIXME: reimplement } void MockConferenceCallChannel::sendNextDtmf() { /*if (mDtmfLock) { return; } if (!mDtmfPendingStrings.isEmpty()) { mDtmfLock = true; mConnection->voiceCallManager()->sendTones(mDtmfPendingStrings.front()); }*/ // FIXME: reimplement } void MockConferenceCallChannel::onDtmfComplete(bool success) { mDtmfLock = false; if (success) { mDtmfPendingStrings.removeFirst(); if (mDtmfPendingStrings.isEmpty()) { return; } sendNextDtmf(); } else { QTimer::singleShot(1000, this, SLOT(sendNextDtmf())); } } void MockConferenceCallChannel::onDTMFStartTone(uchar event, Tp::DBusError *error) { QString finalString; if (event == 10) { finalString = "*"; } else if (event == 11) { finalString = "#"; } else { finalString = QString::number(event); } qDebug() << "start tone" << finalString; // we can't append to the first item in the queue as it is being sent and // we dont know yet if it will succeed or not. if (mDtmfPendingStrings.count() > 1) { mDtmfPendingStrings[1] += finalString; } else { mDtmfPendingStrings << finalString; } sendNextDtmf(); } void MockConferenceCallChannel::onDTMFStopTone(Tp::DBusError *error) { } MockConferenceCallChannel::~MockConferenceCallChannel() { qDebug() << "conference call channel closed"; // TODO - for some reason the object is not being removed mConnection->dbusConnection().unregisterObject(mObjPath, QDBusConnection::UnregisterTree); } history-service-0.1+16.04.20160104/tests/common/mock/dbustypes.h0000644000015600001650000000227512642552153024640 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann */ #ifndef DBUSTYPES #define DBUSTYPES struct MessageStruct { QDBusObjectPath path; QVariantMap properties; }; struct AttachmentStruct { QString id; QString contentType; QString filePath; quint64 offset; quint64 length; }; typedef QList AttachmentList; Q_DECLARE_METATYPE(AttachmentStruct) Q_DECLARE_METATYPE(AttachmentList) typedef QList MessageList; Q_DECLARE_METATYPE(MessageStruct) Q_DECLARE_METATYPE(MessageList) #endif history-service-0.1+16.04.20160104/tests/common/mock/protocol.cpp0000644000015600001650000000375112642552153025012 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann */ #include "protocol.h" #include "connection.h" #include #include Protocol::Protocol(const QDBusConnection &dbusConnection, const QString &name) : Tp::BaseProtocol(dbusConnection, name) { setRequestableChannelClasses(Tp::RequestableChannelClassSpecList() << Tp::RequestableChannelClassSpec::textChat() << Tp::RequestableChannelClassSpec::audioCall()); setCreateConnectionCallback(memFun(this, &Protocol::createConnection)); Tp::ProtocolParameterList parameters; Tp::ProtocolParameter parameter("modem-objpath", "s", 0); parameters << parameter; setParameters(parameters); addressingIface = Tp::BaseProtocolAddressingInterface::create(); if (name == "ofono") { addressingIface->setAddressableVCardFields(QStringList() << "tel"); } else { addressingIface->setAddressableVCardFields(QStringList() << "x-mock-im" << "x-sip"); } plugInterface(addressingIface); } Tp::BaseConnectionPtr Protocol::createConnection(const QVariantMap ¶meters, Tp::DBusError *error) { Q_UNUSED(error); return Tp::BaseConnection::create("mock", name().toLatin1(), parameters); } history-service-0.1+16.04.20160104/tests/common/mock/speakeriface.cpp0000644000015600001650000000770512642552153025576 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: Tiago Salem Herrmann */ #include #include #include #include "speakeriface.h" // Chan.I.Speaker BaseChannelSpeakerInterface::Adaptee::Adaptee(BaseChannelSpeakerInterface *interface) : QObject(interface), mInterface(interface) { } struct TP_QT_NO_EXPORT BaseChannelSpeakerInterface::Private { Private(BaseChannelSpeakerInterface *parent) : speakerMode(false), adaptee(new BaseChannelSpeakerInterface::Adaptee(parent)) { } bool speakerMode; turnOnSpeakerCallback turnOnSpeakerCB; BaseChannelSpeakerInterface::Adaptee *adaptee; }; BaseChannelSpeakerInterface::Adaptee::~Adaptee() { } void BaseChannelSpeakerInterface::Adaptee::turnOnSpeaker(bool active, const ChannelInterfaceSpeakerAdaptor::turnOnSpeakerContextPtr &context) { if (!mInterface->mPriv->turnOnSpeakerCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; mInterface->mPriv->turnOnSpeakerCB(active, &error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(); } BaseChannelSpeakerInterface::BaseChannelSpeakerInterface() : AbstractChannelInterface(TP_QT_IFACE_CHANNEL_SPEAKER), mPriv(new Private(this)) { } BaseChannelSpeakerInterface::~BaseChannelSpeakerInterface() { delete mPriv; } bool BaseChannelSpeakerInterface::speakerMode() const { return mPriv->speakerMode; } void BaseChannelSpeakerInterface::setTurnOnSpeakerCallback(const turnOnSpeakerCallback &cb) { mPriv->turnOnSpeakerCB = cb; } void BaseChannelSpeakerInterface::setSpeakerMode(bool active) { mPriv->speakerMode = active; Q_EMIT mPriv->adaptee->speakerChanged(active); } QVariantMap BaseChannelSpeakerInterface::immutableProperties() const { QVariantMap map; return map; } void BaseChannelSpeakerInterface::createAdaptor() { (void) new ChannelInterfaceSpeakerAdaptor(dbusObject()->dbusConnection(), mPriv->adaptee, dbusObject()); } ChannelInterfaceSpeakerAdaptor::ChannelInterfaceSpeakerAdaptor(const QDBusConnection& bus, QObject* adaptee, QObject* parent) : Tp::AbstractAdaptor(bus, adaptee, parent) { connect(adaptee, SIGNAL(speakerChanged(bool)), SIGNAL(SpeakerChanged(bool))); } ChannelInterfaceSpeakerAdaptor::~ChannelInterfaceSpeakerAdaptor() { } void ChannelInterfaceSpeakerAdaptor::turnOnSpeaker(bool active, const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("turnOnSpeaker(bool,ChannelInterfaceSpeakerAdaptor::turnOnSpeakerContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return; } turnOnSpeakerContextPtr ctx = turnOnSpeakerContextPtr( new Tp::MethodInvocationContext< bool >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "turnOnSpeaker", Q_ARG(bool, active), Q_ARG(ChannelInterfaceSpeakerAdaptor::turnOnSpeakerContextPtr, ctx)); return; } bool ChannelInterfaceSpeakerAdaptor::SpeakerMode() const { return qvariant_cast< bool >(adaptee()->property("speakerMode")); } history-service-0.1+16.04.20160104/tests/common/mock/mockconnectiondbus.cpp0000644000015600001650000001036512642552153027037 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "connection.h" #include "mockconnectiondbus.h" #include "mockconnectionadaptor.h" Q_DECLARE_METATYPE(QList< QVariantMap >) MockConnectionDBus::MockConnectionDBus(MockConnection *parent) : QObject(parent), mAdaptor(0), mConnection(parent) { connect(mConnection, SIGNAL(messageRead(QString)), SIGNAL(MessageRead(QString))); connect(mConnection, SIGNAL(messageSent(QString,QVariantMap)), SIGNAL(MessageSent(QString,QVariantMap))); connect(mConnection, SIGNAL(callReceived(QString)), SIGNAL(CallReceived(QString))); connect(mConnection, SIGNAL(callEnded(QString)), SIGNAL(CallEnded(QString))); connect(mConnection, SIGNAL(callStateChanged(QString,QString,QString)), SIGNAL(CallStateChanged(QString,QString,QString))); connect(mConnection, SIGNAL(conferenceCreated(QString)), SIGNAL(ConferenceCreated(QString))); connect(mConnection, SIGNAL(channelMerged(QString)), SIGNAL(ChannelMerged(QString))); connect(mConnection, SIGNAL(channelSplitted(QString)), SIGNAL(ChannelSplitted(QString))); connect(mConnection, SIGNAL(disconnected()), SIGNAL(Disconnected())); connect(mConnection, SIGNAL(destroyed()), SIGNAL(Destroyed())); qDBusRegisterMetaType >(); mObjectPath = "/com/canonical/MockConnection/" + mConnection->protocolName(); connectToBus(); } MockConnectionDBus::~MockConnectionDBus() { QDBusConnection::sessionBus().unregisterObject(mObjectPath, QDBusConnection::UnregisterTree); } bool MockConnectionDBus::connectToBus() { bool ok = QDBusConnection::sessionBus().registerService("com.canonical.MockConnection"); if (!ok) { return false; } if (!mAdaptor) { mAdaptor = new MockConnectionAdaptor(this); } return QDBusConnection::sessionBus().registerObject(mObjectPath, this); } void MockConnectionDBus::PlaceIncomingMessage(const QString &message, const QVariantMap &properties) { mConnection->placeIncomingMessage(message, properties); } void MockConnectionDBus::PlaceDeliveryReport(const QStringList &recipients, const QString &messageId, const QString &status) { mConnection->placeDeliveryReport(recipients, messageId, status); } QString MockConnectionDBus::PlaceCall(const QVariantMap &properties) { return mConnection->placeCall(properties); } void MockConnectionDBus::HangupCall(const QString &callerId) { mConnection->hangupCall(callerId); } void MockConnectionDBus::SetCallState(const QString &phoneNumber, const QString &state) { mConnection->setCallState(phoneNumber, state); } void MockConnectionDBus::SetOnline(bool online) { mConnection->setOnline(online); } void MockConnectionDBus::SetPresence(const QString &status, const QString &statusMessage) { Tp::DBusError error; mConnection->setPresence(status, statusMessage, &error); } void MockConnectionDBus::SetVoicemailIndicator(bool active) { mConnection->setVoicemailIndicator(active); } void MockConnectionDBus::SetVoicemailNumber(const QString &number) { mConnection->setVoicemailNumber(number); } void MockConnectionDBus::SetVoicemailCount(int count) { mConnection->setVoicemailCount(count); } void MockConnectionDBus::SetEmergencyNumbers(const QStringList &numbers) { mConnection->setEmergencyNumbers(numbers); } QString MockConnectionDBus::Serial() { return mConnection->serial(); } history-service-0.1+16.04.20160104/tests/common/mock/ussdiface.h0000644000015600001650000002226712642552153024567 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #ifndef OFONOUSSDIFACE_H #define OFONOUSSDIFACE_H // telepathy-qt #include #include #include #include #include class BaseConnectionUSSDInterface; typedef Tp::SharedPtr BaseConnectionUSSDInterfacePtr; #define TP_QT_IFACE_CONNECTION_USSD "com.canonical.Telephony.USSD" class TP_QT_EXPORT BaseConnectionUSSDInterface : public Tp::AbstractConnectionInterface { Q_OBJECT Q_DISABLE_COPY(BaseConnectionUSSDInterface) public: static BaseConnectionUSSDInterfacePtr create() { return BaseConnectionUSSDInterfacePtr(new BaseConnectionUSSDInterface()); } template static Tp::SharedPtr create() { return Tp::SharedPtr( new BaseConnectionUSSDInterfaceSubclass()); } QVariantMap immutableProperties() const; virtual ~BaseConnectionUSSDInterface(); typedef Tp::Callback2 InitiateCallback; void setInitiateCallback(const InitiateCallback &cb); typedef Tp::Callback2 RespondCallback; void setRespondCallback(const RespondCallback &cb); typedef Tp::Callback1 CancelCallback; void setCancelCallback(const CancelCallback &cb); QString state() const; QString serial() const; public Q_SLOTS: void NotificationReceived(const QString &message); void RequestReceived(const QString &message); void InitiateUSSDComplete(const QString &ussdResp); void RespondComplete(bool success, const QString &ussdResp); void BarringComplete(const QString &ssOp, const QString &cbService, const QVariantMap &cbMap); void ForwardingComplete(const QString &ssOp, const QString &cfService, const QVariantMap &cfMap); void WaitingComplete(const QString &ssOp, const QVariantMap &cwMap); void CallingLinePresentationComplete(const QString &ssOp, const QString &status); void ConnectedLinePresentationComplete(const QString &ssOp, const QString &status); void CallingLineRestrictionComplete(const QString &ssOp, const QString &status); void ConnectedLineRestrictionComplete(const QString &ssOp, const QString &status); void InitiateFailed(); void StateChanged(const QString &state); void setSerial(const QString& serial) const; protected: BaseConnectionUSSDInterface(); private: void createAdaptor(); class Adaptee; friend class Adaptee; struct Private; friend struct Private; Private *mPriv; }; class TP_QT_EXPORT ConnectionInterfaceUSSDAdaptor : public Tp::AbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", TP_QT_IFACE_CONNECTION_USSD) Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") Q_PROPERTY(QString State READ State) Q_PROPERTY(QString Serial READ Serial) public: ConnectionInterfaceUSSDAdaptor(const QDBusConnection& dbusConnection, QObject* adaptee, QObject* parent); virtual ~ConnectionInterfaceUSSDAdaptor(); typedef Tp::MethodInvocationContextPtr< > InitiateContextPtr; typedef Tp::MethodInvocationContextPtr< > RespondContextPtr; typedef Tp::MethodInvocationContextPtr< > CancelContextPtr; public Q_SLOTS: // METHODS void Initiate(const QString &command, const QDBusMessage& dbusMessage); void Respond(const QString &reply, const QDBusMessage& dbusMessage); void Cancel(const QDBusMessage& dbusMessage); QString State() const; QString Serial() const; Q_SIGNALS: // SIGNALS void NotificationReceived(const QString &message); void RequestReceived(const QString &message); void InitiateUSSDComplete(const QString &ussdResp); void RespondComplete(bool success, const QString &ussdResp); void BarringComplete(const QString &ssOp, const QString &cbService, const QVariantMap &cbMap); void ForwardingComplete(const QString &ssOp, const QString &cfService, const QVariantMap &cfMap); void WaitingComplete(const QString &ssOp, const QVariantMap &cwMap); void CallingLinePresentationComplete(const QString &ssOp, const QString &status); void ConnectedLinePresentationComplete(const QString &ssOp, const QString &status); void CallingLineRestrictionComplete(const QString &ssOp, const QString &status); void ConnectedLineRestrictionComplete(const QString &ssOp, const QString &status); void InitiateFailed(); void StateChanged(const QString &state); }; class TP_QT_NO_EXPORT BaseConnectionUSSDInterface::Adaptee : public QObject { Q_OBJECT Q_PROPERTY(QString state READ state) Q_PROPERTY(QString serial READ serial) public: Adaptee(BaseConnectionUSSDInterface *interface); ~Adaptee(); QString state() const { return mInterface->state(); } QString serial() const { return mInterface->serial(); } private Q_SLOTS: void initiate(const QString &command, const ConnectionInterfaceUSSDAdaptor::InitiateContextPtr &context); void respond(const QString &reply, const ConnectionInterfaceUSSDAdaptor::RespondContextPtr &context); void cancel(const ConnectionInterfaceUSSDAdaptor::CancelContextPtr &context); Q_SIGNALS: void notificationReceived(const QString &message); void requestReceived(const QString &message); void initiateUSSDComplete(const QString &ussdResp); void barringComplete(const QString &ssOp, const QString &cbService, const QVariantMap &cbMap); void forwardingComplete(const QString &ssOp, const QString &cfService, const QVariantMap &cfMap); void waitingComplete(const QString &ssOp, const QVariantMap &cwMap); void callingLinePresentationComplete(const QString &ssOp, const QString &status); void connectedLinePresentationComplete(const QString &ssOp, const QString &status); void callingLineRestrictionComplete(const QString &ssOp, const QString &status); void connectedLineRestrictionComplete(const QString &ssOp, const QString &status); void initiateFailed(); void respondComplete(bool success, const QString &response); void stateChanged(const QString &state); public: BaseConnectionUSSDInterface *mInterface; }; #endif history-service-0.1+16.04.20160104/tests/common/mock/mockconnectiondbus.h0000644000015600001650000000501312642552153026476 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Gustavo Pichorim Boiko */ #ifndef MOCKCONNECTIONDBUS_H #define MOCKCONNECTIONDBUS_H #include #include class MockConnectionAdaptor; class MockConnection; class MockConnectionDBus : public QObject, public QDBusContext { Q_OBJECT public: explicit MockConnectionDBus(MockConnection *parent); ~MockConnectionDBus(); bool connectToBus(); void PlaceIncomingMessage(const QString &message, const QVariantMap &properties); void PlaceDeliveryReport(const QStringList &recipients, const QString &messageId, const QString &status); QString PlaceCall(const QVariantMap &properties); void HangupCall(const QString &callerId); void SetCallState(const QString &phoneNumber, const QString &state); void SetOnline(bool online); void SetPresence(const QString &status, const QString &statusMessage); // voicemail stuff void SetVoicemailIndicator(bool active); void SetVoicemailNumber(const QString &number); void SetVoicemailCount(int count); // emergency numbers stuff void SetEmergencyNumbers(const QStringList &numbers); // USSD stuff QString Serial(); Q_SIGNALS: // signals that will be relayed into the bus void MessageRead(const QString &messageId); void MessageSent(const QString &mesasge, const QVariantMap &properties); void CallReceived(const QString &callerId); void CallEnded(const QString &callerId); void CallStateChanged(const QString &callerId, const QString &objectPath, const QString &state); void ConferenceCreated(const QString &objectPath); void ChannelMerged(const QString &objectPath); void ChannelSplitted(const QString &objectPath); void Disconnected(); void Destroyed(); private: MockConnectionAdaptor *mAdaptor; MockConnection *mConnection; QString mObjectPath; }; #endif // MOCKCONNECTIONDBUS_H history-service-0.1+16.04.20160104/tests/common/mock/MockConnection.xml0000644000015600001650000001334412642552153026077 0ustar pbuserpbgroup00000000000000 An interface to the fake connection manager history-service-0.1+16.04.20160104/tests/common/mock/connection.cpp0000644000015600001650000006303512642552153025311 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #include #include #include #include // telepathy-mock #include "connection.h" #include "phoneutils_p.h" #include "protocol.h" #include "conferencecallchannel.h" #include "mockconnectiondbus.h" MockConnection::MockConnection(const QDBusConnection &dbusConnection, const QString &cmName, const QString &protocolName, const QVariantMap ¶meters) : Tp::BaseConnection(dbusConnection, cmName, protocolName, parameters), mConferenceCall(0), mVoicemailIndicator(false), mVoicemailCount(0) { setSelfHandle(newHandle("")); setConnectCallback(Tp::memFun(this,&MockConnection::connect)); setInspectHandlesCallback(Tp::memFun(this,&MockConnection::inspectHandles)); setRequestHandlesCallback(Tp::memFun(this,&MockConnection::requestHandles)); setCreateChannelCallback(Tp::memFun(this,&MockConnection::createChannel)); // initialise requests interface (Connection.Interface.Requests) requestsIface = Tp::BaseConnectionRequestsInterface::create(this); // set requestable text channel properties Tp::RequestableChannelClass text; text.fixedProperties[TP_QT_IFACE_CHANNEL+".ChannelType"] = TP_QT_IFACE_CHANNEL_TYPE_TEXT; text.fixedProperties[TP_QT_IFACE_CHANNEL+".TargetHandleType"] = Tp::HandleTypeContact; text.allowedProperties.append(TP_QT_IFACE_CHANNEL+".TargetHandle"); text.allowedProperties.append(TP_QT_IFACE_CHANNEL+".TargetID"); text.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")); // set requestable call channel properties Tp::RequestableChannelClass call; call.fixedProperties[TP_QT_IFACE_CHANNEL+".ChannelType"] = TP_QT_IFACE_CHANNEL_TYPE_CALL; call.fixedProperties[TP_QT_IFACE_CHANNEL+".TargetHandleType"] = Tp::HandleTypeContact; call.fixedProperties[TP_QT_IFACE_CHANNEL_TYPE_CALL+".InitialAudio"] = true; call.allowedProperties.append(TP_QT_IFACE_CHANNEL+".TargetHandle"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL+".TargetID"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".InitialAudio"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".InitialVideo"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".InitialAudioName"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".InitialVideoName"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".InitialTransport"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".HardwareStreaming"); call.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); requestsIface->requestableChannelClasses << text << call; plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(requestsIface)); // init presence interface simplePresenceIface = Tp::BaseConnectionSimplePresenceInterface::create(); simplePresenceIface->setSetPresenceCallback(Tp::memFun(this,&MockConnection::setPresence)); simplePresenceIface->setMaximumStatusMessageLength(255); plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(simplePresenceIface)); // Set Presence Tp::SimpleStatusSpec presenceOnline; presenceOnline.type = Tp::ConnectionPresenceTypeAvailable; presenceOnline.maySetOnSelf = true; presenceOnline.canHaveMessage = true; Tp::SimpleStatusSpec presenceOffline; presenceOffline.type = Tp::ConnectionPresenceTypeOffline; presenceOffline.maySetOnSelf = true; presenceOffline.canHaveMessage = true; Tp::SimpleStatusSpec presenceAway; presenceAway.type = Tp::ConnectionPresenceTypeAway; presenceAway.maySetOnSelf = true; presenceAway.canHaveMessage = true; mStatuses.insert(QLatin1String("available"), presenceOnline); mStatuses.insert(QLatin1String("offline"), presenceOffline); mStatuses.insert(QLatin1String("away"), presenceAway); mStatuses.insert(QLatin1String("simlocked"), presenceAway); mStatuses.insert(QLatin1String("flightmode"), presenceOffline); mStatuses.insert(QLatin1String("nosim"), presenceOffline); mStatuses.insert(QLatin1String("nomodem"), presenceOffline); mStatuses.insert(QLatin1String("registered"), presenceOnline); mStatuses.insert(QLatin1String("roaming"), presenceOnline); mStatuses.insert(QLatin1String("unregistered"), presenceAway); mStatuses.insert(QLatin1String("denied"), presenceAway); mStatuses.insert(QLatin1String("unknown"), presenceAway); mStatuses.insert(QLatin1String("searching"), presenceAway); simplePresenceIface->setStatuses(mStatuses); mSelfPresence.type = Tp::ConnectionPresenceTypeOffline; contactsIface = Tp::BaseConnectionContactsInterface::create(); contactsIface->setGetContactAttributesCallback(Tp::memFun(this,&MockConnection::getContactAttributes)); contactsIface->setContactAttributeInterfaces(QStringList() << TP_QT_IFACE_CONNECTION << TP_QT_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE); plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(contactsIface)); // init custom emergency mode interface (not provided by telepathy emergencyModeIface = BaseConnectionEmergencyModeInterface::create(); emergencyModeIface->setEmergencyNumbersCallback(Tp::memFun(this,&MockConnection::emergencyNumbers)); plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(emergencyModeIface)); mEmergencyNumbers << "123" << "456" << "789"; emergencyModeIface->setEmergencyNumbers(mEmergencyNumbers); // init custom voicemail interface (not provided by telepathy) voicemailIface = BaseConnectionVoicemailInterface::create(); voicemailIface->setVoicemailCountCallback(Tp::memFun(this,&MockConnection::voicemailCount)); voicemailIface->setVoicemailIndicatorCallback(Tp::memFun(this,&MockConnection::voicemailIndicator)); voicemailIface->setVoicemailNumberCallback(Tp::memFun(this,&MockConnection::voicemailNumber)); voicemailIface->setVoicemailNumber(mVoicemailNumber); plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(voicemailIface)); voicemailIface->setVoicemailCount(mVoicemailCount); voicemailIface->setVoicemailIndicator(mVoicemailIndicator); mVoicemailNumber = "555"; supplementaryServicesIface = BaseConnectionUSSDInterface::create(); supplementaryServicesIface->setInitiateCallback(Tp::memFun(this,&MockConnection::USSDInitiate)); supplementaryServicesIface->setRespondCallback(Tp::memFun(this,&MockConnection::USSDRespond)); supplementaryServicesIface->setCancelCallback(Tp::memFun(this,&MockConnection::USSDCancel)); static int serial = 0; serial++; supplementaryServicesIface->setSerial(QString("accountserial%1").arg(QString::number(serial))); plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(supplementaryServicesIface)); mDBus = new MockConnectionDBus(this); } MockConnection::~MockConnection() { } void MockConnection::addMMSToService(const QString &path, const QVariantMap &properties, const QString &servicePath) { qDebug() << "addMMSToService " << path << properties << servicePath; #if 0 FIXME: re-implement MMSDMessage *msg = new MMSDMessage(path, properties); QObject::connect(msg, SIGNAL(propertyChanged(QString,QVariant)), SLOT(onMMSPropertyChanged(QString,QVariant))); mServiceMMSList[servicePath].append(msg); if (properties["Status"] == "received") { const QString normalizedNumber = PhoneUtils::normalizePhoneNumber(properties["Sender"].toString()); // check if there is an open channel for this number and use it Q_FOREACH(const QString &phoneNumber, mTextChannels.keys()) { if (PhoneUtils::comparePhoneNumbers(normalizedNumber, phoneNumber)) { qDebug() << "existing channel" << mTextChannels[phoneNumber]; mTextChannels[phoneNumber]->mmsReceived(path, properties); return; } } Tp::DBusError error; bool yours; qDebug() << "new handle" << normalizedNumber; uint handle = newHandle(normalizedNumber); ensureChannel(TP_QT_IFACE_CHANNEL_TYPE_TEXT,Tp::HandleTypeContact, handle, yours, handle, false, &error); if(error.isValid()) { qCritical() << "Error creating channel for incoming message " << error.name() << error.message(); return; } mTextChannels[normalizedNumber]->mmsReceived(path, properties); } #endif } MockTextChannel *MockConnection::textChannelForRecipients(const QStringList &recipients) { Q_FOREACH(MockTextChannel *channel, mTextChannels) { QStringList channelRecipients = channel->recipients(); if (channelRecipients.length() != recipients.length()) { continue; } bool ok = true; Q_FOREACH(const QString &recipient, recipients) { if (!channelRecipients.contains(recipient)) { ok = false; break; } } if (ok) { return channel; } } return 0; } uint MockConnection::setPresence(const QString& status, const QString& statusMessage, Tp::DBusError *error) { qDebug() << "setPresence" << status << statusMessage; Tp::SimpleContactPresences presences; if (!mStatuses.contains(status) || !mStatuses[status].maySetOnSelf) { error->set(TP_QT_ERROR_INVALID_ARGUMENT, "Status not supported or cannot be set"); return selfHandle(); } Tp::SimpleStatusSpec spec = mStatuses[status]; mSelfPresence.status = status; mSelfPresence.type = spec.type; mSelfPresence.statusMessage = spec.canHaveMessage ? statusMessage : ""; presences[selfHandle()] = mSelfPresence; simplePresenceIface->setPresences(presences); return selfHandle(); } Tp::ContactAttributesMap MockConnection::getContactAttributes(const Tp::UIntList &handles, const QStringList &ifaces, Tp::DBusError *error) { qDebug() << "getContactAttributes" << handles << ifaces; Tp::ContactAttributesMap attributesMap; QVariantMap attributes; Q_FOREACH(uint handle, handles) { attributes[TP_QT_IFACE_CONNECTION+"/contact-id"] = inspectHandles(Tp::HandleTypeContact, Tp::UIntList() << handle, error).at(0); if (ifaces.contains(TP_QT_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE)) { attributes[TP_QT_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE+"/presence"] = QVariant::fromValue(mSelfPresence); } attributesMap[handle] = attributes; } return attributesMap; } void MockConnection::setOnline(bool online) { qDebug() << "setOnline" << online; Tp::SimpleContactPresences presences; if (online) { mSelfPresence.status = "available"; mSelfPresence.statusMessage = ""; mSelfPresence.type = Tp::ConnectionPresenceTypeAvailable; } else { mSelfPresence.status = "offline"; mSelfPresence.statusMessage = ""; mSelfPresence.type = Tp::ConnectionPresenceTypeOffline; } presences[selfHandle()] = mSelfPresence; simplePresenceIface->setPresences(presences); } uint MockConnection::newHandle(const QString &identifier) { static int handleCount = 0; mHandles[++handleCount] = identifier; return handleCount; } QMap MockConnection::callChannels() { return mCallChannels; } QStringList MockConnection::inspectHandles(uint handleType, const Tp::UIntList& handles, Tp::DBusError *error) { QStringList identifiers; if( handleType != Tp::HandleTypeContact ) { error->set(TP_QT_ERROR_INVALID_ARGUMENT,"Not supported"); return QStringList(); } qDebug() << "MockConnection::inspectHandles " << handles; Q_FOREACH( uint handle, handles) { if (mHandles.keys().contains(handle)) { identifiers.append(mHandles.value(handle)); } else { error->set(TP_QT_ERROR_INVALID_HANDLE, "Handle not found"); return QStringList(); } } qDebug() << "MockConnection::inspectHandles " << identifiers; return identifiers; } void MockConnection::connect(Tp::DBusError *error) { qDebug() << "MockConnection::connect"; setStatus(Tp::ConnectionStatusConnected, Tp::ConnectionStatusReasonRequested); } Tp::UIntList MockConnection::requestHandles(uint handleType, const QStringList& identifiers, Tp::DBusError* error) { qDebug() << "requestHandles"; Tp::UIntList handles; if( handleType != Tp::HandleTypeContact ) { error->set(TP_QT_ERROR_INVALID_ARGUMENT, "Not supported"); return Tp::UIntList(); } Q_FOREACH( const QString& identifier, identifiers) { if (mHandles.values().contains(identifier)) { handles.append(mHandles.key(identifier)); } else { handles.append(newHandle(identifier)); } } qDebug() << "requestHandles" << handles; return handles; } Tp::BaseChannelPtr MockConnection::createTextChannel(uint targetHandleType, uint targetHandle, const QVariantMap &hints, Tp::DBusError *error) { Q_UNUSED(targetHandleType); Q_UNUSED(error); if (hints.contains(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")) && targetHandleType == Tp::HandleTypeNone && targetHandle == 0) { } QStringList recipients; bool flash; if (hints.contains(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"))) { recipients << inspectHandles(Tp::HandleTypeContact, qdbus_cast(hints[TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")]), error); } else { recipients << mHandles.value(targetHandle); } if (hints.contains(TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash"))) { flash = hints[TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash")].toBool(); } // FIXME: test flash messages MockTextChannel *channel = new MockTextChannel(this, recipients, targetHandle); QObject::connect(channel, SIGNAL(messageRead(QString)), SLOT(onMessageRead(QString))); QObject::connect(channel, SIGNAL(destroyed()), SLOT(onTextChannelClosed())); QObject::connect(channel, SIGNAL(messageSent(QString,QVariantMap)), SIGNAL(messageSent(QString,QVariantMap))); qDebug() << channel; mTextChannels << channel; return channel->baseChannel(); } void MockConnection::onMessageRead(const QString &id) { // FIXME: check what else to do Q_EMIT messageRead(id); } void MockConnection::onConferenceCallChannelClosed() { if (mConferenceCall) { mConferenceCall = NULL; } } void MockConnection::onCallChannelSplitted() { MockCallChannel *channel = qobject_cast(sender()); Q_EMIT channelSplitted(channel->baseChannel()->objectPath()); Q_EMIT channelSplitted(QDBusObjectPath(channel->baseChannel()->objectPath())); } Tp::BaseChannelPtr MockConnection::createCallChannel(uint targetHandleType, uint targetHandle, const QVariantMap &hints, Tp::DBusError *error) { Q_UNUSED(targetHandleType); QString requestedId = mHandles.value(targetHandle); if (hints.contains(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")) && targetHandleType == Tp::HandleTypeNone && targetHandle == 0) { // conference call request if (mConferenceCall) { error->set(TP_QT_ERROR_NOT_AVAILABLE, "Conference call already exists"); return Tp::BaseChannelPtr(); } QDBusArgument arg = hints[TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")].value(); QList channels; arg >> channels; if (!channels.isEmpty()) { mConferenceCall = new MockConferenceCallChannel(this, channels); QObject::connect(mConferenceCall, SIGNAL(destroyed()), SLOT(onConferenceCallChannelClosed())); QObject::connect(mConferenceCall, SIGNAL(channelMerged(QString)), SIGNAL(channelMerged(QString))); // the object path is only availabe after we return to the event loop, so emit the conferenceCreated signal // only after that. QObject::connect(mConferenceCall, &MockConferenceCallChannel::initialized, [this]() { Q_EMIT conferenceCreated(mConferenceCall->baseChannel()->objectPath()); }); return mConferenceCall->baseChannel(); } error->set(TP_QT_ERROR_NOT_AVAILABLE, "Impossible to merge calls"); return Tp::BaseChannelPtr(); } Q_FOREACH(const QString &id, mCallChannels.keys()) { if (id == requestedId) { return mCallChannels[id]->baseChannel(); } } QString state = "dialing"; if (mInitialCallStatus.contains(requestedId)) { state = mInitialCallStatus.take(requestedId); } mCallChannels[requestedId] = new MockCallChannel(this, requestedId, state, targetHandle); QObject::connect(mCallChannels[requestedId], SIGNAL(destroyed()), SLOT(onCallChannelClosed())); QObject::connect(mCallChannels[requestedId], SIGNAL(callStateChanged(MockCallChannel*,QString)), SLOT(onCallStateChanged(MockCallChannel*,QString))); QObject::connect(mCallChannels[requestedId], SIGNAL(splitted()), SLOT(onCallChannelSplitted())); qDebug() << mCallChannels[requestedId]; if (!mIncomingCalls.contains(requestedId)) { Q_EMIT callReceived(requestedId); } return mCallChannels[requestedId]->baseChannel(); } Tp::BaseChannelPtr MockConnection::createChannel(const QVariantMap &request, Tp::DBusError *error) { const QString channelType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString(); uint targetHandleType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt(); uint targetHandle = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt(); qDebug() << "MockConnection::createChannel" << targetHandle; if (mSelfPresence.type != Tp::ConnectionPresenceTypeAvailable) { error->set(TP_QT_ERROR_NETWORK_ERROR, "No network available"); return Tp::BaseChannelPtr(); } if (channelType == TP_QT_IFACE_CHANNEL_TYPE_TEXT) { return createTextChannel(targetHandleType, targetHandle, request, error); } else if (channelType == TP_QT_IFACE_CHANNEL_TYPE_CALL) { return createCallChannel(targetHandleType, targetHandle, request, error); } else { error->set(TP_QT_ERROR_NOT_IMPLEMENTED, "Channel type not available"); } return Tp::BaseChannelPtr(); } void MockConnection::placeIncomingMessage(const QString &message, const QVariantMap &info) { QString sender = info["Sender"].toString(); QStringList recipients = info["Recipients"].toStringList(); MockTextChannel *channel = textChannelForRecipients(recipients); if (!channel) { // request the channel Tp::DBusError error; QVariantMap request; bool yours; uint handle = newHandle(sender); request[TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")] = TP_QT_IFACE_CHANNEL_TYPE_TEXT; request[TP_QT_IFACE_CHANNEL + QLatin1String(".InitiatorHandle")] = handle; request[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")] = Tp::HandleTypeContact; request[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")] = handle; ensureChannel(request, yours, false, &error); if(error.isValid()) { qWarning() << "Error creating channel for incoming message" << error.name() << error.message(); return; } channel = textChannelForRecipients(recipients); if (!channel) { return; } } channel->messageReceived(message, info); } void MockConnection::placeDeliveryReport(const QStringList &recipients, const QString &messageId, const QString &status) { MockTextChannel *channel = textChannelForRecipients(recipients); if (!channel) { qWarning() << "Text channel for recipients" << recipients << "not found while placing a delivery report"; return; } channel->placeDeliveryReport(messageId, status); } void MockConnection::onTextChannelClosed() { MockTextChannel *channel = static_cast(sender()); if (channel) { qDebug() << "text channel closed for recipients " << channel->recipients(); mTextChannels.removeAll(channel); } } void MockConnection::onCallChannelClosed() { qDebug() << "onCallChannelClosed()"; MockCallChannel *channel = static_cast(sender()); if (channel) { QString key = mCallChannels.key(channel); qDebug() << "call channel closed for number " << key; mCallChannels.remove(key); mIncomingCalls.removeAll(key); Q_EMIT callEnded(key); } } void MockConnection::onCallChannelDestroyed() { // FIXME: implement } void MockConnection::onCallStateChanged(MockCallChannel *channel, const QString &state) { const QString key = mCallChannels.key(channel); if (key.isEmpty()) { return; } Q_EMIT callStateChanged(key, channel->objectPath(), state); } uint MockConnection::ensureHandle(const QString &id) { if (mHandles.values().contains(id)) { return mHandles.key(id); } return newHandle(id); } QString MockConnection::placeCall(const QVariantMap &properties) { qDebug() << "new call" << properties; bool yours; Tp::DBusError error; QString callerId = properties["Caller"].toString(); QString state = properties["State"].toString(); if (mCallChannels.contains(callerId)) { return mCallChannels[callerId]->objectPath(); } uint handle = ensureHandle(callerId); uint initiatorHandle = 0; if (state == "incoming" || state == "waiting") { initiatorHandle = handle; } else { initiatorHandle = selfHandle(); } qDebug() << "initiatorHandle " <objectPath(); } QStringList MockConnection::emergencyNumbers(Tp::DBusError *error) { return mEmergencyNumbers; } void MockConnection::setEmergencyNumbers(const QStringList &emergencyNumbers) { mEmergencyNumbers = emergencyNumbers; emergencyModeIface->setEmergencyNumbers(emergencyNumbers); } bool MockConnection::voicemailIndicator(Tp::DBusError *error) { return mVoicemailIndicator; } void MockConnection::setVoicemailIndicator(bool visible) { mVoicemailIndicator = visible; voicemailIface->setVoicemailIndicator(visible); } QString MockConnection::voicemailNumber(Tp::DBusError *error) { return mVoicemailNumber; } void MockConnection::setVoicemailNumber(const QString &number) { mVoicemailNumber = number; voicemailIface->setVoicemailNumber(mVoicemailNumber); } uint MockConnection::voicemailCount(Tp::DBusError *error) { return mVoicemailCount; } void MockConnection::setVoicemailCount(int count) { mVoicemailCount = count; voicemailIface->setVoicemailCount(mVoicemailCount); } void MockConnection::USSDInitiate(const QString &command, Tp::DBusError *error) { // FIXME: implement } void MockConnection::USSDRespond(const QString &reply, Tp::DBusError *error) { // FIXME: implement } void MockConnection::USSDCancel(Tp::DBusError *error) { // FIXME: implement } QString MockConnection::serial() { return supplementaryServicesIface->serial(); } QString MockConnection::uniqueName() const { static int count = 0; return QString("connection%1%2").arg((quintptr) this, 0, 16).arg(count++, 0, 16); } void MockConnection::hangupCall(const QString &callerId) { if (!mCallChannels.contains(callerId)) { return; } mCallChannels[callerId]->setCallState("disconnected"); mIncomingCalls.removeAll(callerId); } void MockConnection::setCallState(const QString &phoneNumber, const QString &state) { if (!mCallChannels.contains(phoneNumber)) { return; } mCallChannels[phoneNumber]->setCallState(state); } history-service-0.1+16.04.20160104/tests/common/mock/speakeriface.h0000644000015600001650000000754712642552153025247 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #ifndef OFONOSPEAKERIFACE_H #define OFONOSPEAKERIFACE_H // telepathy-qt #include #include #include #include #include #define TP_QT_IFACE_CHANNEL_SPEAKER "com.canonical.Telephony.Speaker" class BaseChannelSpeakerInterface; typedef Tp::SharedPtr BaseChannelSpeakerInterfacePtr; class TP_QT_EXPORT BaseChannelSpeakerInterface : public Tp::AbstractChannelInterface { Q_OBJECT Q_DISABLE_COPY(BaseChannelSpeakerInterface) public: static BaseChannelSpeakerInterfacePtr create() { return BaseChannelSpeakerInterfacePtr(new BaseChannelSpeakerInterface()); } template static Tp::SharedPtr create() { return Tp::SharedPtr( new BaseChannelSpeakerInterfaceSubclass()); } QVariantMap immutableProperties() const; virtual ~BaseChannelSpeakerInterface(); bool speakerMode() const; typedef Tp::Callback2 turnOnSpeakerCallback; void setTurnOnSpeakerCallback(const turnOnSpeakerCallback &cb); public Q_SLOTS: void setSpeakerMode(bool active); protected: BaseChannelSpeakerInterface(); private: void createAdaptor(); class Adaptee; friend class Adaptee; struct Private; friend struct Private; Private *mPriv; }; class TP_QT_EXPORT ChannelInterfaceSpeakerAdaptor : public Tp::AbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", TP_QT_IFACE_CHANNEL_SPEAKER) Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") Q_PROPERTY(bool SpeakerMode READ SpeakerMode) public: ChannelInterfaceSpeakerAdaptor(const QDBusConnection& dbusConnection, QObject* adaptee, QObject* parent); virtual ~ChannelInterfaceSpeakerAdaptor(); typedef Tp::MethodInvocationContextPtr< bool > turnOnSpeakerContextPtr; public: // PROPERTIES bool SpeakerMode() const; public Q_SLOTS: // METHODS void turnOnSpeaker(bool active, const QDBusMessage& dbusMessage); Q_SIGNALS: // SIGNALS void SpeakerChanged(bool active); }; class TP_QT_NO_EXPORT BaseChannelSpeakerInterface::Adaptee : public QObject { Q_OBJECT Q_PROPERTY(bool speakerMode READ speakerMode) public: Adaptee(BaseChannelSpeakerInterface *interface); ~Adaptee(); bool speakerMode() const { return mInterface->speakerMode(); } private Q_SLOTS: void turnOnSpeaker(bool active, const ChannelInterfaceSpeakerAdaptor::turnOnSpeakerContextPtr &context); Q_SIGNALS: void speakerChanged(bool active); public: BaseChannelSpeakerInterface *mInterface; }; #endif history-service-0.1+16.04.20160104/tests/common/mock/textchannel.h0000644000015600001650000000463612642552153025136 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #ifndef MOCKTEXTCHANNEL_H #define MOCKTEXTCHANNEL_H #include #include #include #include #include #include "connection.h" class MockConnection; class MockTextChannel : public QObject { Q_OBJECT public: MockTextChannel(MockConnection *conn, QStringList recipients, uint targetHandle, QObject *parent = 0); QString sendMessage(const Tp::MessagePartList& message, uint flags, Tp::DBusError* error); void messageReceived(const QString & message, const QVariantMap &info); Tp::BaseChannelPtr baseChannel(); void messageAcknowledged(const QString &id); void mmsReceived(const QString &id, const QVariantMap &properties); void addMembers(QStringList recipients); QStringList recipients() const; Tp::UIntList members(); void onAddMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error); void onRemoveMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error); public Q_SLOTS: void placeDeliveryReport(const QString &messageId, const QString &status); Q_SIGNALS: void messageRead(const QString &id); void messageSent(const QString &message, const QVariantMap &info); private: ~MockTextChannel(); Tp::BaseChannelPtr mBaseChannel; QStringList mRecipients; MockConnection *mConnection; uint mTargetHandle; Tp::BaseChannelMessagesInterfacePtr mMessagesIface; Tp::BaseChannelGroupInterfacePtr mGroupIface; Tp::BaseChannelTextTypePtr mTextChannel; uint mMessageCounter; Tp::UIntList mMembers; }; #endif // MOCKTEXTCHANNEL_H history-service-0.1+16.04.20160104/tests/common/mock/protocol.h0000644000015600001650000000225512642552153024455 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Tiago Salem Herrmann * * This file is part of telepathy-ofono. * * telepathy-ofono is free software; you can redistribute it and/or modify * it under the terms of the GNU LESSER General Public License as published by * the Free Software Foundation; version 3. * * telepathy-ofono 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 LESSER General Public License * along with this program. If not, see . */ #ifndef MOCKPROTOCOL_H #define MOCKPROTOCOL_H #include class Protocol : public Tp::BaseProtocol { Q_OBJECT Q_DISABLE_COPY(Protocol) public: Protocol(const QDBusConnection &dbusConnection, const QString &name); Tp::BaseProtocolAddressingInterfacePtr addressingIface; private: Tp::BaseConnectionPtr createConnection(const QVariantMap ¶meters, Tp::DBusError *error); }; #endif history-service-0.1+16.04.20160104/tests/common/mock/CMakeLists.txt0000644000015600001650000000146712642552153025207 0ustar pbuserpbgroup00000000000000include_directories( ${TP_QT5_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS} ${Qt5DBus_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ) find_library(TELEPATHY_QT5_SERVICE_LIBRARIES telepathy-qt5-service) set(mock_SRCS main.cpp protocol.cpp connection.cpp textchannel.cpp callchannel.cpp conferencecallchannel.cpp mockconnectiondbus.cpp speakeriface.cpp emergencymodeiface.cpp ussdiface.cpp voicemailiface.cpp) qt5_add_dbus_adaptor(mock_SRCS MockConnection.xml mockconnectiondbus.h MockConnectionDBus) add_executable(telepathy-mock ${mock_SRCS}) qt5_use_modules(telepathy-mock Core DBus) target_link_libraries(telepathy-mock ${TP_QT5_LIBRARIES} ${TELEPATHY_QT5_SERVICE_LIBRARIES} ${OFONO_QT_LIBRARIES} ${PULSEAUDIO_LIBRARIES}) history-service-0.1+16.04.20160104/tests/common/mock/voicemailiface.h0000644000015600001650000001241212642552153025550 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #ifndef OFONOVOICEMAILIFACE_H #define OFONOVOICEMAILIFACE_H // telepathy-qt #include #include #include #include #include class BaseConnectionVoicemailInterface; typedef Tp::SharedPtr BaseConnectionVoicemailInterfacePtr; #define TP_QT_IFACE_CONNECTION_VOICEMAIL "com.canonical.Telephony.Voicemail" class TP_QT_EXPORT BaseConnectionVoicemailInterface : public Tp::AbstractConnectionInterface { Q_OBJECT Q_DISABLE_COPY(BaseConnectionVoicemailInterface) public: static BaseConnectionVoicemailInterfacePtr create() { return BaseConnectionVoicemailInterfacePtr(new BaseConnectionVoicemailInterface()); } template static Tp::SharedPtr create() { return Tp::SharedPtr( new BaseConnectionVoicemailInterfaceSubclass()); } QVariantMap immutableProperties() const; virtual ~BaseConnectionVoicemailInterface(); typedef Tp::Callback1 VoicemailCountCallback; void setVoicemailCountCallback(const VoicemailCountCallback &cb); typedef Tp::Callback1 VoicemailIndicatorCallback; void setVoicemailIndicatorCallback(const VoicemailIndicatorCallback &cb); typedef Tp::Callback1 VoicemailNumberCallback; void setVoicemailNumberCallback(const VoicemailNumberCallback &cb); public Q_SLOTS: void setVoicemailCount(int count); void setVoicemailIndicator(bool active); void setVoicemailNumber(const QString &voicemailNumber); protected: BaseConnectionVoicemailInterface(); private: void createAdaptor(); class Adaptee; friend class Adaptee; struct Private; friend struct Private; Private *mPriv; }; class TP_QT_EXPORT ConnectionInterfaceVoicemailAdaptor : public Tp::AbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", TP_QT_IFACE_CONNECTION_VOICEMAIL) Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") public: ConnectionInterfaceVoicemailAdaptor(const QDBusConnection& dbusConnection, QObject* adaptee, QObject* parent); virtual ~ConnectionInterfaceVoicemailAdaptor(); typedef Tp::MethodInvocationContextPtr< bool > VoicemailIndicatorContextPtr; typedef Tp::MethodInvocationContextPtr< QString > VoicemailNumberContextPtr; typedef Tp::MethodInvocationContextPtr< uint > VoicemailCountContextPtr; public Q_SLOTS: // METHODS bool VoicemailIndicator(const QDBusMessage& dbusMessage); QString VoicemailNumber(const QDBusMessage& dbusMessage); uint VoicemailCount(const QDBusMessage& dbusMessage); Q_SIGNALS: // SIGNALS void VoicemailCountChanged(uint count); void VoicemailIndicatorChanged(bool active); void VoicemailNumberChanged(const QString &voicemailNumber); }; class TP_QT_NO_EXPORT BaseConnectionVoicemailInterface::Adaptee : public QObject { Q_OBJECT public: Adaptee(BaseConnectionVoicemailInterface *interface); ~Adaptee(); private Q_SLOTS: void voicemailIndicator(const ConnectionInterfaceVoicemailAdaptor::VoicemailIndicatorContextPtr &context); void voicemailNumber(const ConnectionInterfaceVoicemailAdaptor::VoicemailNumberContextPtr &context); void voicemailCount(const ConnectionInterfaceVoicemailAdaptor::VoicemailCountContextPtr &context); Q_SIGNALS: void voicemailCountChanged(uint count); void voicemailIndicatorChanged(bool active); void voicemailNumberChanged(const QString &voicemailNumber); public: BaseConnectionVoicemailInterface *mInterface; }; #endif history-service-0.1+16.04.20160104/tests/common/mock/voicemailiface.cpp0000644000015600001650000001640212642552153026106 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann Gustavo Pichorim Boiko */ #include #include #include #include "voicemailiface.h" // Conn.I.Voicemail BaseConnectionVoicemailInterface::Adaptee::Adaptee(BaseConnectionVoicemailInterface *interface) : QObject(interface), mInterface(interface) { } struct TP_QT_NO_EXPORT BaseConnectionVoicemailInterface::Private { Private(BaseConnectionVoicemailInterface *parent) : adaptee(new BaseConnectionVoicemailInterface::Adaptee(parent)) { } VoicemailCountCallback voicemailCountCB; VoicemailNumberCallback voicemailNumberCB; VoicemailIndicatorCallback voicemailIndicatorCB; BaseConnectionVoicemailInterface::Adaptee *adaptee; }; BaseConnectionVoicemailInterface::Adaptee::~Adaptee() { } void BaseConnectionVoicemailInterface::Adaptee::voicemailIndicator(const ConnectionInterfaceVoicemailAdaptor::VoicemailIndicatorContextPtr &context) { if (!mInterface->mPriv->voicemailIndicatorCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; bool active = mInterface->mPriv->voicemailIndicatorCB(&error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(active); } void BaseConnectionVoicemailInterface::Adaptee::voicemailNumber(const ConnectionInterfaceVoicemailAdaptor::VoicemailNumberContextPtr &context) { if (!mInterface->mPriv->voicemailNumberCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; QString number = mInterface->mPriv->voicemailNumberCB(&error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(number); } void BaseConnectionVoicemailInterface::Adaptee::voicemailCount(const ConnectionInterfaceVoicemailAdaptor::VoicemailCountContextPtr &context) { if (!mInterface->mPriv->voicemailCountCB.isValid()) { context->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented")); return; } Tp::DBusError error; uint count = mInterface->mPriv->voicemailCountCB(&error); if (error.isValid()) { context->setFinishedWithError(error.name(), error.message()); return; } context->setFinished(count); } BaseConnectionVoicemailInterface::BaseConnectionVoicemailInterface() : AbstractConnectionInterface(TP_QT_IFACE_CONNECTION_VOICEMAIL), mPriv(new Private(this)) { } BaseConnectionVoicemailInterface::~BaseConnectionVoicemailInterface() { delete mPriv; } void BaseConnectionVoicemailInterface::setVoicemailIndicatorCallback(const VoicemailIndicatorCallback &cb) { mPriv->voicemailIndicatorCB = cb; } void BaseConnectionVoicemailInterface::setVoicemailNumberCallback(const VoicemailNumberCallback &cb) { mPriv->voicemailNumberCB = cb; } void BaseConnectionVoicemailInterface::setVoicemailCountCallback(const VoicemailCountCallback &cb) { mPriv->voicemailCountCB = cb; } void BaseConnectionVoicemailInterface::setVoicemailCount(int count) { Q_EMIT mPriv->adaptee->voicemailCountChanged(uint(count)); } void BaseConnectionVoicemailInterface::setVoicemailNumber(const QString &voicemailNumber) { Q_EMIT mPriv->adaptee->voicemailNumberChanged(voicemailNumber); } void BaseConnectionVoicemailInterface::setVoicemailIndicator(bool active) { Q_EMIT mPriv->adaptee->voicemailIndicatorChanged(active); } QVariantMap BaseConnectionVoicemailInterface::immutableProperties() const { QVariantMap map; return map; } void BaseConnectionVoicemailInterface::createAdaptor() { (void) new ConnectionInterfaceVoicemailAdaptor(dbusObject()->dbusConnection(), mPriv->adaptee, dbusObject()); } ConnectionInterfaceVoicemailAdaptor::ConnectionInterfaceVoicemailAdaptor(const QDBusConnection& bus, QObject* adaptee, QObject* parent) : Tp::AbstractAdaptor(bus, adaptee, parent) { connect(adaptee, SIGNAL(voicemailCountChanged(uint)), SIGNAL(VoicemailCountChanged(uint))); connect(adaptee, SIGNAL(voicemailIndicatorChanged(bool)), SIGNAL(VoicemailIndicatorChanged(bool))); connect(adaptee, SIGNAL(voicemailNumberChanged(QString)), SIGNAL(VoicemailNumberChanged(QString))); } ConnectionInterfaceVoicemailAdaptor::~ConnectionInterfaceVoicemailAdaptor() { } bool ConnectionInterfaceVoicemailAdaptor::VoicemailIndicator(const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("voicemailIndicator(ConnectionInterfaceVoicemailAdaptor::VoicemailIndicatorContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return bool(); } VoicemailIndicatorContextPtr ctx = VoicemailIndicatorContextPtr( new Tp::MethodInvocationContext< bool >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "voicemailIndicator", Q_ARG(ConnectionInterfaceVoicemailAdaptor::VoicemailIndicatorContextPtr, ctx)); return bool(); } QString ConnectionInterfaceVoicemailAdaptor::VoicemailNumber(const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("voicemailNumber(ConnectionInterfaceVoicemailAdaptor::VoicemailNumberContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return QString(); } VoicemailNumberContextPtr ctx = VoicemailNumberContextPtr( new Tp::MethodInvocationContext< QString >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "voicemailNumber", Q_ARG(ConnectionInterfaceVoicemailAdaptor::VoicemailNumberContextPtr, ctx)); return QString(); } uint ConnectionInterfaceVoicemailAdaptor::VoicemailCount(const QDBusMessage& dbusMessage) { if (!adaptee()->metaObject()->indexOfMethod("voicemailCount(ConnectionInterfaceVoicemailAdaptor::VoicemailCountContextPtr)") == -1) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); return uint(); } VoicemailCountContextPtr ctx = VoicemailCountContextPtr( new Tp::MethodInvocationContext< uint >(dbusConnection(), dbusMessage)); QMetaObject::invokeMethod(adaptee(), "voicemailCount", Q_ARG(ConnectionInterfaceVoicemailAdaptor::VoicemailCountContextPtr, ctx)); return uint(); } history-service-0.1+16.04.20160104/tests/common/mock/textchannel.cpp0000644000015600001650000002420712642552153025465 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: Tiago Salem Herrmann * Gustavo Pichorim Boiko */ // telepathy-ofono #include "textchannel.h" QDBusArgument &operator<<(QDBusArgument &argument, const AttachmentStruct &attachment) { argument.beginStructure(); argument << attachment.id << attachment.contentType << attachment.filePath << attachment.offset << attachment.length; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, AttachmentStruct &attachment) { argument.beginStructure(); argument >> attachment.id >> attachment.contentType >> attachment.filePath >> attachment.offset >> attachment.length; argument.endStructure(); return argument; } MockTextChannel::MockTextChannel(MockConnection *conn, QStringList recipients, uint targetHandle, QObject *parent): QObject(parent), mConnection(conn), mRecipients(recipients), mTargetHandle(targetHandle), mMessageCounter(1) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); Tp::HandleType type = recipients.count() > 1 ? Tp::HandleTypeNone : Tp::HandleTypeContact; Tp::BaseChannelPtr baseChannel = Tp::BaseChannel::create(mConnection, TP_QT_IFACE_CHANNEL_TYPE_TEXT, type, targetHandle); mBaseChannel = baseChannel; Tp::BaseChannelTextTypePtr textType = Tp::BaseChannelTextType::create(baseChannel.data()); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(textType)); QStringList supportedContentTypes = QStringList() << "text/plain"; Tp::UIntList messageTypes = Tp::UIntList() << Tp::ChannelTextMessageTypeNormal << Tp::ChannelTextMessageTypeDeliveryReport; uint messagePartSupportFlags = 0; uint deliveryReportingSupport = Tp::DeliveryReportingSupportFlagReceiveSuccesses; mMessagesIface = Tp::BaseChannelMessagesInterface::create(textType.data(), supportedContentTypes, messageTypes, messagePartSupportFlags, deliveryReportingSupport); mMessagesIface->setSendMessageCallback(Tp::memFun(this,&MockTextChannel::sendMessage)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mMessagesIface)); // group stuff mGroupIface = Tp::BaseChannelGroupInterface::create(Tp::ChannelGroupFlagCanAdd, conn->selfHandle()); mGroupIface->setAddMembersCallback(Tp::memFun(this,&MockTextChannel::onAddMembers)); mGroupIface->setRemoveMembersCallback(Tp::memFun(this,&MockTextChannel::onRemoveMembers)); baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mGroupIface)); addMembers(recipients); mTextChannel = Tp::BaseChannelTextTypePtr::dynamicCast(mBaseChannel->interface(TP_QT_IFACE_CHANNEL_TYPE_TEXT)); mTextChannel->setMessageAcknowledgedCallback(Tp::memFun(this,&MockTextChannel::messageAcknowledged)); QObject::connect(mBaseChannel.data(), SIGNAL(closed()), this, SLOT(deleteLater())); } MockTextChannel::~MockTextChannel() { } Tp::BaseChannelPtr MockTextChannel::baseChannel() { return mBaseChannel; } void MockTextChannel::messageAcknowledged(const QString &id) { Q_EMIT messageRead(id); } QString MockTextChannel::sendMessage(const Tp::MessagePartList& message, uint flags, Tp::DBusError* error) { Tp::MessagePart header = message.at(0); Tp::MessagePart body = message.at(1); static int serial = 0; // FIXME: check what other data we need to emit in the signal QString id; if (!header["no-event-id"].variant().toBool()) { id = QString("sentmessage%1").arg(serial++); } QString messageText = body["content"].variant().toString(); QVariantMap properties; properties["SentTime"] = QDateTime::currentDateTime().toString(Qt::ISODate); properties["Recipients"] = mRecipients; properties["Id"] = id; Q_EMIT messageSent(messageText, properties); return id; } void MockTextChannel::placeDeliveryReport(const QString &messageId, const QString &status) { Tp::DeliveryStatus delivery_status; if (status == "sent" || status == "delivered") { delivery_status = Tp::DeliveryStatusDelivered; } else if(status == "failed" || status == "permanently_failed") { delivery_status = Tp::DeliveryStatusPermanentlyFailed; } else if (status == "temporarily_failed") { delivery_status = Tp::DeliveryStatusTemporarilyFailed; } else if(status == "pending" || status == "accepted") { delivery_status = Tp::DeliveryStatusAccepted; } else if (status == "read") { delivery_status = Tp::DeliveryStatusRead; } else if (status == "deleted") { delivery_status = Tp::DeliveryStatusDeleted; } else { delivery_status = Tp::DeliveryStatusUnknown; } Tp::MessagePartList partList; Tp::MessagePart header; header["message-sender"] = QDBusVariant(mTargetHandle); // FIXME: fix it header["message-sender-id"] = QDBusVariant(mRecipients.first()); header["message-type"] = QDBusVariant(Tp::ChannelTextMessageTypeDeliveryReport); header["delivery-status"] = QDBusVariant(delivery_status); header["delivery-token"] = QDBusVariant(messageId); partList << header; mTextChannel->addReceivedMessage(partList); } void MockTextChannel::messageReceived(const QString &message, const QVariantMap &info) { Tp::MessagePartList partList; Tp::MessagePart body; body["content-type"] = QDBusVariant("text/plain"); body["content"] = QDBusVariant(message); Tp::MessagePart header; header["message-token"] = QDBusVariant(info["SentTime"].toString() +"-" + QString::number(mMessageCounter++)); header["message-received"] = QDBusVariant(QDateTime::fromString(info["SentTime"].toString(), Qt::ISODate).toTime_t()); header["message-sender"] = QDBusVariant(mTargetHandle); header["message-sender-id"] = QDBusVariant(mRecipients.first()); header["message-type"] = QDBusVariant(Tp::ChannelTextMessageTypeNormal); partList << header << body; mTextChannel->addReceivedMessage(partList); } void MockTextChannel::mmsReceived(const QString &id, const QVariantMap &properties) { Tp::MessagePartList message; QString subject = properties["Subject"].toString(); QString smil = properties["Smil"].toString(); Tp::MessagePart header; header["message-token"] = QDBusVariant(id); header["message-sender"] = QDBusVariant(mTargetHandle); header["message-received"] = QDBusVariant(QDateTime::fromString(properties["Date"].toString(), Qt::ISODate).toTime_t()); header["message-type"] = QDBusVariant(Tp::DeliveryStatusDelivered); if (!subject.isEmpty()) { header["subject"] = QDBusVariant(subject); } message << header; AttachmentList mmsdAttachments = qdbus_cast(properties["Attachments"]); Q_FOREACH(const AttachmentStruct &attachment, mmsdAttachments) { QFile attachmentFile(attachment.filePath); if (!attachmentFile.open(QIODevice::ReadOnly)) { qWarning() << "fail to load attachment" << attachmentFile.errorString() << attachment.filePath; continue; } // FIXME check if we managed to read the total attachment file attachmentFile.seek(attachment.offset); QByteArray fileData = attachmentFile.read(attachment.length); Tp::MessagePart part; part["content-type"] = QDBusVariant(attachment.contentType); part["identifier"] = QDBusVariant(attachment.id); part["content"] = QDBusVariant(fileData); part["size"] = QDBusVariant(attachment.length); message << part; } if (!smil.isEmpty()) { Tp::MessagePart part; part["content-type"] = QDBusVariant(QString("application/smil")); part["identifier"] = QDBusVariant(QString("smil")); part["content"] = QDBusVariant(smil); part["size"] = QDBusVariant(smil.size()); message << part; } mTextChannel->addReceivedMessage(message); } void MockTextChannel::addMembers(QStringList recipients) { Tp::UIntList handles; Q_FOREACH(const QString &recipient, recipients) { uint handle = mConnection->ensureHandle(recipient); handles << handle; if (!mRecipients.contains(recipient)) { mRecipients << recipient; } if (!mMembers.contains(handle)) { mMembers << handle; } } mGroupIface->addMembers(handles, recipients); } QStringList MockTextChannel::recipients() const { return mRecipients; } Tp::UIntList MockTextChannel::members() { return mMembers; } void MockTextChannel::onAddMembers(const Tp::UIntList &handles, const QString &message, Tp::DBusError *error) { addMembers(mConnection->inspectHandles(Tp::HandleTypeContact, handles, error)); } void MockTextChannel::onRemoveMembers(const Tp::UIntList &handles, const QString &message, Tp::DBusError *error) { Q_FOREACH(uint handle, handles) { Q_FOREACH(const QString &recipient, mConnection->inspectHandles(Tp::HandleTypeContact, Tp::UIntList() << handle, error)) { mRecipients.removeAll(recipient); } mMembers.removeAll(handle); } mGroupIface->removeMembers(handles); } history-service-0.1+16.04.20160104/tests/common/mockcontroller.cpp0000644000015600001650000001007012642552153025245 0ustar pbuserpbgroup00000000000000/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko */ #include "mockcontroller.h" #include static const QString mockService("com.canonical.MockConnection"); static const QString mockObject("/com/canonical/MockConnection/%1"); static const QString mockInterface("com.canonical.MockConnection"); MockController::MockController(const QString &protocol, QObject *parent) : QObject(parent), mProtocol(protocol), mMockObject(mockObject.arg(protocol)), mMockInterface(mockService, mockObject.arg(protocol), mockInterface) { connect(&mMockInterface, SIGNAL(MessageRead(QString)), this, SIGNAL(messageRead(QString))); connect(&mMockInterface, SIGNAL(MessageSent(QString, QVariantMap)), this, SIGNAL(messageSent(QString, QVariantMap))); connect(&mMockInterface, SIGNAL(CallReceived(QString)), this, SIGNAL(callReceived(QString))); connect(&mMockInterface, SIGNAL(CallEnded(QString)), this, SIGNAL(callEnded(QString))); connect(&mMockInterface, SIGNAL(CallStateChanged(QString, QString, QString)), this, SIGNAL(callStateChanged(QString,QString,QString))); connect(&mMockInterface, SIGNAL(ConferenceCreated(QString)), this, SIGNAL(conferenceCreated(QString))); connect(&mMockInterface, SIGNAL(ChannelMerged(QString)), this, SIGNAL(channelMerged(QString))); connect(&mMockInterface, SIGNAL(ChannelSplitted(QString)), this, SIGNAL(channelSplitted(QString))); connect(&mMockInterface, SIGNAL(Disconnected()), this, SIGNAL(disconnected())); connect(&mMockInterface, SIGNAL(Destroyed()), this, SIGNAL(connectionDestroyed())); } void MockController::placeIncomingMessage(const QString &message, const QVariantMap &properties) { mMockInterface.call("PlaceIncomingMessage", message, properties); } void MockController::placeDeliveryReport(const QStringList &recipients, const QString &messageId, const QString &status) { mMockInterface.call("PlaceDeliveryReport", recipients, messageId, status); } QString MockController::placeCall(const QVariantMap &properties) { QDBusReply reply = mMockInterface.call("PlaceCall", properties); return reply; } void MockController::hangupCall(const QString &callerId) { mMockInterface.call("HangupCall", callerId); } void MockController::setCallState(const QString &phoneNumber, const QString &state) { mMockInterface.call("SetCallState", phoneNumber, state); } void MockController::setOnline(bool online) { mMockInterface.call("SetOnline", online); } void MockController::setPresence(const QString &status, const QString &statusMessage) { mMockInterface.call("SetPresence", status, statusMessage); } void MockController::setVoicemailNumber(const QString &number) { mMockInterface.call("SetVoicemailNumber", number); } void MockController::setVoicemailIndicator(bool active) { mMockInterface.call("SetVoicemailIndicator", active); } void MockController::setVoicemailCount(int count) { mMockInterface.call("SetVoicemailCount", count); } void MockController::setEmergencyNumbers(const QStringList &numbers) { mMockInterface.call("SetEmergencyNumbers", numbers); } QString MockController::serial() { QDBusReply reply = mMockInterface.call("Serial"); if (!reply.isValid()) { return QString::null; } return reply.value(); } history-service-0.1+16.04.20160104/tests/common/telepathytest.cpp0000644000015600001650000001120512642552153025110 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include #include #include #include "telepathytest.h" #include "telepathyhelper_p.h" void TelepathyTest::initialize() { Tp::registerTypes(); QSignalSpy spy(History::TelepathyHelper::instance(), SIGNAL(setupReady())); QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, DEFAULT_TIMEOUT); // just in case, remove any existing account that might be a leftover from // previous test runs Q_FOREACH(const Tp::AccountPtr &account, History::TelepathyHelper::instance()->accounts()) { QVERIFY(removeAccount(account)); } // create an account manager instance to help testing Tp::Features accountFeatures; accountFeatures << Tp::Account::FeatureCore << Tp::Account::FeatureProtocolInfo; Tp::Features contactFeatures; contactFeatures << Tp::Contact::FeatureAlias << Tp::Contact::FeatureAvatarData << Tp::Contact::FeatureAvatarToken << Tp::Contact::FeatureCapabilities << Tp::Contact::FeatureSimplePresence; Tp::Features connectionFeatures; connectionFeatures << Tp::Connection::FeatureCore << Tp::Connection::FeatureSelfContact << Tp::Connection::FeatureSimplePresence; Tp::ChannelFactoryPtr channelFactory = Tp::ChannelFactory::create(QDBusConnection::sessionBus()); channelFactory->addCommonFeatures(Tp::Channel::FeatureCore); mAccountManager = Tp::AccountManager::create( Tp::AccountFactory::create(QDBusConnection::sessionBus(), accountFeatures), Tp::ConnectionFactory::create(QDBusConnection::sessionBus(), connectionFeatures), channelFactory, Tp::ContactFactory::create(contactFeatures)); mReady = false; connect(mAccountManager->becomeReady(Tp::AccountManager::FeatureCore), &Tp::PendingOperation::finished, [=]{ mReady = true; }); QTRY_VERIFY(mReady); // give some time for telepathy stuff to settle QTest::qWait(1000); } void TelepathyTest::doCleanup() { // remove all accounts on every test to prevent garbage to go from one test to another Q_FOREACH(const Tp::AccountPtr &account, mAccounts) { QVERIFY(removeAccount(account)); } QVERIFY(mAccounts.isEmpty()); } Tp::AccountPtr TelepathyTest::addAccount(const QString &manager, const QString &protocol, const QString &displayName, const QVariantMap ¶meters) { bool finished = false; Tp::AccountPtr account; connect(mAccountManager->createAccount(manager, protocol, displayName, parameters), &Tp::PendingOperation::finished, [&](Tp::PendingOperation *op) { Tp::PendingAccount *pa = qobject_cast(op); if (op->isError() || !pa) { qCritical() << "Failed to create account:" << op->errorName() << op->errorMessage(); finished = true; return; } account = pa->account(); connect(account->setEnabled(true), &Tp::PendingOperation::finished, [&] { Tp::Presence presence(Tp::ConnectionPresenceTypeAvailable, "online", "online"); account->setRequestedPresence(presence); finished = true; }); }); while (!finished) { QTest::qWait(100); } mAccounts << account; return account; } bool TelepathyTest::removeAccount(const Tp::AccountPtr &account) { bool success = false; bool finished = false; connect(account->remove(), &Tp::PendingOperation::finished, [&](Tp::PendingOperation *op) { success = !op->isError(); finished = true; }); while (!finished) { QTest::qWait(100); } if (success) { mAccounts.removeAll(account); } return success; } QList TelepathyTest::accounts() const { return mAccounts; } void TelepathyTest::cleanup() { doCleanup(); } history-service-0.1+16.04.20160104/tests/common/dbus-session.conf.in0000644000015600001650000000321612642552153025402 0ustar pbuserpbgroup00000000000000 session unix:tmpdir=/tmp @CMAKE_CURRENT_BINARY_DIR@/dbus-services 60000 1000000000 1000000000 1000000000 120000 240000 100000 10000 100000 10000 50000 50000 50000 300000 history-service-0.1+16.04.20160104/tests/common/telepathytest.h0000644000015600001650000000303312642552153024555 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 TELEPATHYTEST_H #define TELEPATHYTEST_H #include #include #include #define DEFAULT_TIMEOUT 15000 class TelepathyTest : public QObject { Q_OBJECT protected: void initialize(); void doCleanup(); // helper slots void onAccountManagerReady(Tp::PendingOperation *op); Tp::AccountPtr addAccount(const QString &manager, const QString &protocol, const QString &displayName, const QVariantMap ¶meters = QVariantMap()); bool removeAccount(const Tp::AccountPtr &account); QList accounts() const; private Q_SLOTS: void cleanup(); private: Tp::AccountManagerPtr mAccountManager; bool mReady; QList mAccounts; }; #endif //TELEPATHYTEST_H history-service-0.1+16.04.20160104/tests/common/CMakeLists.txt0000644000015600001650000000077512642552153024257 0ustar pbuserpbgroup00000000000000include_directories(${TP_QT5_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src) configure_file(dbus-session.conf.in ${CMAKE_CURRENT_BINARY_DIR}/dbus-session.conf) add_library(mockcontroller STATIC mockcontroller.cpp mockcontroller.h) qt5_use_modules(mockcontroller Core DBus) add_library(telepathytest STATIC telepathytest.cpp telepathytest.h) qt5_use_modules(telepathytest Core DBus) target_link_libraries(telepathytest ${TP_QT5_LIBRARIES}) add_subdirectory(mock) add_subdirectory(dbus-services) history-service-0.1+16.04.20160104/tests/common/dbus-services/0000755000015600001650000000000012642552414024264 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tests/common/dbus-services/CMakeLists.txt0000644000015600001650000000074112642552153027026 0ustar pbuserpbgroup00000000000000# copy the services we want to use set(DBUS_SERVICES_DIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services) file(COPY ${DBUS_SERVICES_DIR}/org.freedesktop.Telepathy.MissionControl5.service DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${DBUS_SERVICES_DIR}/org.freedesktop.Telepathy.AccountManager.service DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${DBUS_SERVICES_DIR}/ca.desrt.dconf.service DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) history-service-0.1+16.04.20160104/tests/CMakeLists.txt0000644000015600001650000000025112642552153022754 0ustar pbuserpbgroup00000000000000include (GenerateTest) add_subdirectory(common) add_subdirectory(daemon) add_subdirectory(libhistoryservice) add_subdirectory(plugins) add_subdirectory(Ubuntu.History) history-service-0.1+16.04.20160104/src/0000755000015600001650000000000012642552414017643 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/src/participant.cpp0000644000015600001650000001500512642552156022671 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "participant.h" #include "participant_p.h" #include "types.h" namespace History { ParticipantPrivate::ParticipantPrivate() { } ParticipantPrivate::ParticipantPrivate(const QString &theAccountId, const QString &theIdentifier, const QString &theContactId, const QString &theAlias, const QString &theAvatar, const QVariantMap &theDetailProperties) : accountId(theAccountId), identifier(theIdentifier), contactId(theContactId), alias(theAlias), avatar(theAvatar), detailProperties(theDetailProperties) { } ParticipantPrivate::~ParticipantPrivate() { } Participant::Participant() : d_ptr(new ParticipantPrivate()) { } Participant::Participant(const QString &accountId, const QString &identifier, const QString &contactId, const QString &alias, const QString &avatar, const QVariantMap &detailProperties) : d_ptr(new ParticipantPrivate(accountId, identifier, contactId, alias, avatar, detailProperties)) { } Participant::Participant(const Participant &other) : d_ptr(new ParticipantPrivate(*other.d_ptr)) { } Participant &Participant::operator=(const Participant &other) { if (&other == this) { return *this; } d_ptr = QSharedPointer(new ParticipantPrivate(*other.d_ptr)); return *this; } Participant::~Participant() { } QString Participant::accountId() const { Q_D(const Participant); return d->accountId; } QString Participant::identifier() const { Q_D(const Participant); return d->identifier; } QString Participant::contactId() const { Q_D(const Participant); return d->contactId; } QString Participant::alias() const { Q_D(const Participant); return d->alias; } QString Participant::avatar() const { Q_D(const Participant); return d->avatar; } QVariantMap Participant::detailProperties() const { Q_D(const Participant); return d->detailProperties; } bool Participant::isNull() const { Q_D(const Participant); return d->accountId.isNull() || d->identifier.isNull(); } bool Participant::operator==(const Participant &other) const { Q_D(const Participant); return d->accountId == other.d_ptr->accountId && d->identifier == other.d_ptr->identifier; } bool Participant::operator<(const Participant &other) const { Q_D(const Participant); QString selfData = d->accountId + d->identifier; QString otherData = other.d_ptr->accountId + other.d_ptr->identifier; return selfData < otherData; } QVariantMap Participant::properties() const { Q_D(const Participant); QVariantMap map; map[FieldAccountId] = d->accountId; map[FieldIdentifier] = d->identifier; map[FieldContactId] = d->contactId; map[FieldAlias] = d->alias; map[FieldAvatar] = d->avatar; map[FieldDetailProperties] = d->detailProperties; return map; } Participant Participant::fromProperties(const QVariantMap &properties) { Participant participant; if (properties.isEmpty()) { return participant; } QString accountId = properties[FieldAccountId].toString(); QString identifier = properties[FieldIdentifier].toString(); QString contactId = properties[FieldContactId].toString(); QString alias = properties[FieldAlias].toString(); QString avatar = properties[FieldAvatar].toString(); QVariantMap detailProperties; QVariant detailPropertiesVariant = properties[FieldDetailProperties]; if (detailPropertiesVariant.canConvert()) { detailProperties = detailPropertiesVariant.toMap(); } else if (detailPropertiesVariant.canConvert()) { detailProperties = qdbus_cast(detailPropertiesVariant); Q_FOREACH(const QString &key, detailProperties.keys()) { QList list = qdbus_cast >(detailProperties[key]); detailProperties[key] = QVariant::fromValue(list); } } return Participant(accountId, identifier, contactId, alias, avatar, detailProperties); } QStringList Participants::identifiers() const { QStringList result; Q_FOREACH(const Participant &participant, *this) { result << participant.identifier(); } return result; } Participants Participants::fromVariant(const QVariant &variant) { Participants participants; if (variant.canConvert()) { participants = Participants::fromVariantList(variant.toList()); } else if (variant.canConvert()) { QDBusArgument argument = variant.value(); argument >> participants; } return participants; } Participants Participants::fromVariantList(const QVariantList &list) { Participants participants; Q_FOREACH(const QVariant& entry, list) { participants << Participant::fromProperties(entry.toMap()); } return participants; } QVariantList Participants::toVariantList() const { QVariantList list; Q_FOREACH(const Participant &participant, *this) { list << participant.properties(); } return list; } const QDBusArgument &operator>>(const QDBusArgument &argument, Participants &participants) { argument.beginArray(); while (!argument.atEnd()) { QVariantMap props; // we are casting from a QVariantList, so the inner argument is a QVariant and not the map directly // that's why this intermediate QVariant cast is needed QVariant variant; argument >> variant; QDBusArgument innerArgument = variant.value(); if (!innerArgument.atEnd()) { innerArgument >> props; } participants << Participant::fromProperties(props); } argument.endArray(); return argument; } } history-service-0.1+16.04.20160104/src/texteventattachment.h0000644000015600001650000000434212642552153024116 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_TEXT_EVENT_ATTACHMENT_H #define HISTORY_TEXT_EVENT_ATTACHMENT_H #include #include #include "types.h" namespace History { class TextEventAttachmentPrivate; class ItemFactory; class TextEventAttachment { Q_DECLARE_PRIVATE(TextEventAttachment) friend class ItemFactory; public: explicit TextEventAttachment(); TextEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, const QString &attachmentId, const QString &contentType, const QString &filePath, const History::AttachmentFlags &status = History::AttachmentDownloaded); TextEventAttachment(const TextEventAttachment &other); virtual ~TextEventAttachment(); TextEventAttachment& operator=(const TextEventAttachment &other); QString accountId() const; QString threadId() const; QString eventId() const; QString attachmentId() const; QString contentType() const; QString filePath() const; History::AttachmentFlags status() const; virtual QVariantMap properties() const; static TextEventAttachment fromProperties(const QVariantMap &properties); bool isNull() const; bool operator==(const TextEventAttachment &other); protected: QSharedPointer d_ptr; }; typedef QList TextEventAttachments; } #endif // HISTORY_TEXT_EVENT_ATTACHMENT_H history-service-0.1+16.04.20160104/src/unionfilter.cpp0000644000015600001650000001023612642552153022707 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "unionfilter.h" #include "unionfilter_p.h" #include #include #include namespace History { UnionFilterPrivate::UnionFilterPrivate() { } UnionFilterPrivate::~UnionFilterPrivate() { } bool UnionFilterPrivate::match(const QVariantMap properties) const { // if the filter list is empty, assume it matches if (filters.isEmpty()) { return true; } // return true if any of the filters match Q_FOREACH(const History::Filter &filter, filters) { if (filter.match(properties)) { return true; } } // if we reach this point it means none of the filters matched the properties return false; } bool UnionFilterPrivate::isValid() const { // FIXME: maybe we should check if at least one of the inner filters are valid? return !filters.isEmpty(); } QVariantMap UnionFilterPrivate::properties() const { QVariantMap map; if (!isValid()) { return map; } QVariantList filterList; Q_FOREACH(const Filter &filter, filters) { filterList << filter.properties(); } map[FieldFilters] = filterList; map[FieldFilterType] = (int) History::FilterTypeUnion; return map; } QString UnionFilterPrivate::toString(const QString &propertyPrefix) const { if (filters.isEmpty()) { return QString::null; } else if (filters.count() == 1) { return filters.first().toString(); } QStringList output; // wrap each filter string around parenthesis Q_FOREACH(const Filter &filter, filters) { QString value = filter.toString(propertyPrefix); if (!value.isEmpty()) { output << QString("(%1)").arg(value); } } return QString("(%1)").arg(output.join(" OR ")); } HISTORY_FILTER_DEFINE_COPY(UnionFilter, FilterTypeUnion) UnionFilter::UnionFilter() : Filter(*new UnionFilterPrivate()) { } UnionFilter::~UnionFilter() { } void UnionFilter::setFilters(const Filters &filters) { Q_D(UnionFilter); d->filters = filters; } void UnionFilter::prepend(const Filter &filter) { Q_D(UnionFilter); d->filters.prepend(filter); } void UnionFilter::append(const Filter &filter) { Q_D(UnionFilter); d->filters.append(filter); } void UnionFilter::clear() { Q_D(UnionFilter); d->filters.clear(); } Filters UnionFilter::filters() const { Q_D(const UnionFilter); return d->filters; } Filter UnionFilter::fromProperties(const QVariantMap &properties) { UnionFilter filter; if (properties.isEmpty()) { return filter; } QVariant filters = properties[FieldFilters]; QVariantList filterList; // when the filter travels through DBus, it arrives marshalled into QDBusArguments. // cover that case too. if (filters.canConvert()) { QDBusArgument argument = filters.value(); QVariantList list; argument >> list; // and cast also the inner filters Q_FOREACH(const QVariant &var, list) { QDBusArgument arg = var.value(); QVariantMap map; arg >> map; filterList.append(map); } } else { filterList = filters.toList(); } Q_FOREACH(const QVariant &props, filterList) { Filter innerFilter = History::Filter::fromProperties(props.toMap()); if (innerFilter.isValid()) { filter.append(innerFilter); } } return filter; } } history-service-0.1+16.04.20160104/src/thread.cpp0000644000015600001650000001563712642552153021632 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "thread.h" #include "thread_p.h" #include "textevent.h" #include "voiceevent.h" #include #include Q_DECLARE_METATYPE(QList< QVariantMap >) namespace History { // ------------- ThreadPrivate ------------------------------------------------ ThreadPrivate::ThreadPrivate() { } ThreadPrivate::ThreadPrivate(const QString &theAccountId, const QString &theThreadId, EventType theType, const Participants &theParticipants, const Event &theLastEvent, int theCount, int theUnreadCount, const Threads &theGroupedThreads) : accountId(theAccountId), threadId(theThreadId), type(theType), participants(theParticipants), lastEvent(theLastEvent), count(theCount), unreadCount(theUnreadCount), groupedThreads(theGroupedThreads) { } ThreadPrivate::~ThreadPrivate() { } // ------------- Thread ------------------------------------------------------ Thread::Thread() : d_ptr(new ThreadPrivate()) { } Thread::Thread(const QString &accountId, const QString &threadId, EventType type, const Participants &participants, const Event &lastEvent, int count, int unreadCount, const Threads &groupedThreads) : d_ptr(new ThreadPrivate(accountId, threadId, type, participants, lastEvent, count, unreadCount, groupedThreads)) { qDBusRegisterMetaType >(); qRegisterMetaType >(); } Thread::Thread(const Thread &other) : d_ptr(new ThreadPrivate(*other.d_ptr)) { } Thread::~Thread() { } Thread &Thread::operator=(const Thread &other) { if (&other == this) { return *this; } d_ptr = QSharedPointer(new ThreadPrivate(*other.d_ptr)); return *this; } QString Thread::accountId() const { Q_D(const Thread); return d->accountId; } QString Thread::threadId() const { Q_D(const Thread); return d->threadId; } EventType Thread::type() const { Q_D(const Thread); return d->type; } Participants Thread::participants() const { Q_D(const Thread); return d->participants; } Event Thread::lastEvent() const { Q_D(const Thread); return d->lastEvent; } int Thread::count() const { Q_D(const Thread); return d->count; } int Thread::unreadCount() const { Q_D(const Thread); return d->unreadCount; } History::Threads Thread::groupedThreads() const { Q_D(const Thread); return d->groupedThreads; } bool Thread::isNull() const { Q_D(const Thread); return d->accountId.isNull() && d->threadId.isNull() && d->participants.isEmpty(); } bool Thread::operator ==(const Thread &other) const { Q_D(const Thread); if (d->type != other.d_ptr->type) { return false; } if (d->accountId != other.d_ptr->accountId) { return false; } if (d->threadId != other.d_ptr->threadId) { return false; } return true; } bool Thread::operator<(const Thread &other) const { QString selfData = QString::number(type()) + accountId() + threadId(); QString otherData = QString::number(other.type()) + other.accountId() + other.threadId(); return selfData < otherData; } QVariantMap Thread::properties() const { Q_D(const Thread); if (d->accountId.isEmpty() || d->threadId.isEmpty()) { return QVariantMap(); } // include the properties from the last event QVariantMap map = lastEvent().properties(); // and add the thread ones too map[FieldAccountId] = d->accountId; map[FieldThreadId] = d->threadId; map[FieldType] = d->type; map[FieldParticipants] = d->participants.toVariantList(); map[FieldCount] = d->count; map[FieldUnreadCount] = d->unreadCount; map[FieldLastEventId] = lastEvent().eventId(); map[FieldLastEventTimestamp] = lastEvent().timestamp(); QList groupedThreads; Q_FOREACH(const Thread &thread, d->groupedThreads) { groupedThreads << thread.properties(); } if (!groupedThreads.isEmpty()) { map[FieldGroupedThreads] = QVariant::fromValue(groupedThreads); } return map; } Thread Thread::fromProperties(const QVariantMap &properties) { Thread thread; if (properties.isEmpty()) { return thread; } // FIXME: save the rest of the data QString accountId = properties[FieldAccountId].toString(); QString threadId = properties[FieldThreadId].toString(); EventType type = (EventType) properties[FieldType].toInt(); Participants participants = Participants::fromVariant(properties[FieldParticipants]); int count = properties[FieldCount].toInt(); int unreadCount = properties[FieldUnreadCount].toInt(); Threads groupedThreads; if (properties.contains(FieldGroupedThreads)) { QVariant variant = properties[FieldGroupedThreads]; if (variant.canConvert()) { Q_FOREACH(const QVariant& entry, variant.toList()) { groupedThreads << Thread::fromProperties(entry.toMap()); } } else if (variant.canConvert()) { QDBusArgument argument = variant.value(); argument >> groupedThreads; } } Event event; switch (type) { case EventTypeText: event = TextEvent::fromProperties(properties); break; case EventTypeVoice: event = VoiceEvent::fromProperties(properties); break; } return Thread(accountId, threadId, type, participants, event, count, unreadCount, groupedThreads); } const QDBusArgument &operator>>(const QDBusArgument &argument, Threads &threads) { argument.beginArray(); while (!argument.atEnd()) { QVariantMap props; QVariant variant; argument >> variant; QDBusArgument innerArgument = variant.value(); if (!innerArgument.atEnd()) { innerArgument >> props; } threads << Thread::fromProperties(props); } argument.endArray(); return argument; } } history-service-0.1+16.04.20160104/src/TextEventAttachment0000644000015600001650000000004112642552153023520 0ustar pbuserpbgroup00000000000000#include "texteventattachment.h" history-service-0.1+16.04.20160104/src/eventview.cpp0000644000015600001650000001146312642552153022370 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "eventview.h" #include "eventview_p.h" #include "event.h" #include "filter.h" #include "manager.h" #include "sort.h" #include "textevent.h" #include "voiceevent.h" #include #include namespace History { // ------------- EventViewPrivate ------------------------------------------------ EventViewPrivate::EventViewPrivate(History::EventType theType, const History::Sort &theSort, const History::Filter &theFilter) : type(theType), sort(theSort), filter(theFilter), valid(true), dbus(0) { } Events EventViewPrivate::filteredEvents(const Events &events) { bool filterNull = filter.isNull(); Events filtered; Q_FOREACH(const Event &event, events) { if (event.type() != type) { continue; } if (filterNull || filter.match(event.properties())) { filtered << events; } } return filtered; } void EventViewPrivate::_d_eventsAdded(const Events &events) { Q_Q(EventView); Events filtered = filteredEvents(events); if (!filtered.isEmpty()) { Q_EMIT q->eventsAdded(filtered); } } void EventViewPrivate::_d_eventsModified(const Events &events) { Q_Q(EventView); Events filtered = filteredEvents(events); if (!filtered.isEmpty()) { Q_EMIT q->eventsModified(filtered); } } void EventViewPrivate::_d_eventsRemoved(const Events &events) { Q_Q(EventView); Events filtered = filteredEvents(events); if (!filtered.isEmpty()) { Q_EMIT q->eventsRemoved(filtered); } } // ------------- EventView ------------------------------------------------------- EventView::EventView(EventType type, const History::Sort &sort, const History::Filter &filter) : d_ptr(new EventViewPrivate(type, sort, filter)) { d_ptr->q_ptr = this; if (!Manager::instance()->isServiceRunning()) { Q_EMIT invalidated(); d_ptr->valid = false; return; } QDBusInterface interface(History::DBusService, History::DBusObjectPath, History::DBusInterface); QDBusReply reply = interface.call("QueryEvents", (int) type, sort.properties(), filter.properties()); if (!reply.isValid()) { Q_EMIT invalidated(); d_ptr->valid = false; return; } d_ptr->objectPath = reply.value(); d_ptr->dbus = new QDBusInterface(History::DBusService, d_ptr->objectPath, History::EventViewInterface, QDBusConnection::sessionBus(), this); connect(Manager::instance(), SIGNAL(eventsAdded(History::Events)), SLOT(_d_eventsAdded(History::Events))); connect(Manager::instance(), SIGNAL(eventsModified(History::Events)), SLOT(_d_eventsModified(History::Events))); connect(Manager::instance(), SIGNAL(eventsRemoved(History::Events)), SLOT(_d_eventsRemoved(History::Events))); } EventView::~EventView() { Q_D(EventView); if (d->valid) { d->dbus->call("Destroy"); } } QList EventView::nextPage() { Q_D(EventView); QList events; if (!d->valid) { return events; } QDBusReply > reply = d->dbus->call("NextPage"); if (!reply.isValid()) { d->valid = false; Q_EMIT invalidated(); return events; } QList eventsProperties = reply.value(); Q_FOREACH(const QVariantMap &properties, eventsProperties) { Event event; switch (d->type) { case EventTypeText: event = TextEvent::fromProperties(properties); break; case EventTypeVoice: event = VoiceEvent::fromProperties(properties); break; } if (!event.isNull()) { events << event; } } return events; } bool EventView::isValid() const { Q_D(const EventView); return d->valid; } } #include "moc_eventview.cpp" history-service-0.1+16.04.20160104/src/utils.cpp0000644000015600001650000001132012642552153021504 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "utils_p.h" #include "phoneutils_p.h" #include #include #include namespace History { Utils::Utils() { } // FIXME: find a better way to determine when accounts should be grouped bool Utils::shouldGroupAccount(const QString &accountId) { return (matchFlagsForAccount(accountId) & MatchPhoneNumber); } MatchFlags Utils::matchFlagsForAccount(const QString &accountId) { static QMap protocolFlags; if (protocolFlags.isEmpty()) { protocolFlags["ofono"] = MatchPhoneNumber; protocolFlags["multimedia"] = MatchPhoneNumber; } QString protocol = protocolFromAccountId(accountId); if (protocolFlags.contains(protocol)) { return protocolFlags[protocol]; } // default to this value return History::MatchCaseSensitive; } QString Utils::protocolFromAccountId(const QString &accountId) { QStringList parsedId = accountId.split("/"); if (parsedId.count() < 3) { return QString::null; } return parsedId[1]; } bool Utils::compareIds(const QString &accountId, const QString &id1, const QString &id2) { MatchFlags matchFlags = matchFlagsForAccount(accountId); if (matchFlags & MatchPhoneNumber) { return PhoneUtils::comparePhoneNumbers(id1, id2); } if (matchFlags & MatchCaseInsensitive) { return id1.toLower() == id2.toLower(); } return id1 == id2; } bool Utils::compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags) { // if list size is different, just return if (participants1.count() != participants2.count()) { return false; } if (flags & MatchPhoneNumber) { QStringList normalizedParticipants1; QStringList normalizedParticipants2; Q_FOREACH(const QString &participant, participants1) { normalizedParticipants1 << PhoneUtils::normalizePhoneNumber(participant); } Q_FOREACH(const QString &participant, participants2) { normalizedParticipants2 << PhoneUtils::normalizePhoneNumber(participant); } return compareNormalizedParticipants(normalizedParticipants1, normalizedParticipants2, flags); } return compareNormalizedParticipants(participants1, participants2, flags); } bool Utils::compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags) { QStringList mutableParticipants2 = participants2; // if list size is different, just return if (participants1.count() != participants2.count()) { return false; } // and now compare the lists bool found = true; Q_FOREACH(const QString &participant, participants1) { if (flags & MatchPhoneNumber) { // we need to iterate the list and call the phone number comparing function for // each participant from the given thread bool inList = false; QStringList::iterator it = mutableParticipants2.begin(); while (it != mutableParticipants2.end()) { if (PhoneUtils::compareNormalizedPhoneNumbers(*it, participant)) { inList = true; mutableParticipants2.erase(it); break; } ++it; } if (!inList) { found = false; break; } } else if (!mutableParticipants2.contains(participant)) { found = false; break; } } return found; } QString Utils::normalizeId(const QString &accountId, const QString &id) { QString normalizedId = id; // for now we only normalize phone number IDs if (matchFlagsForAccount(accountId) & History::MatchPhoneNumber) { normalizedId = PhoneUtils::normalizePhoneNumber(id); } if (normalizedId.isEmpty()) { qWarning() << "Normalized phone number is empty:" << accountId << id; normalizedId = id; } return normalizedId; } } history-service-0.1+16.04.20160104/src/voiceevent_p.h0000644000015600001650000000320312642552153022500 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_VOICEEVENT_P_H #define HISTORY_VOICEEVENT_P_H #include "event_p.h" namespace History { class VoiceEventPrivate : public EventPrivate { public: VoiceEventPrivate(); VoiceEventPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theSender, const QDateTime &theTimestamp, bool theNewEvent, bool theMissed, const QTime &theDuration, const QString &theRemoteParticipant, const Participants &theParticipants); ~VoiceEventPrivate(); bool missed; QTime duration; QString remoteParticipant; EventType type() const; QVariantMap properties() const; HISTORY_EVENT_DECLARE_CLONE(VoiceEvent) }; } #endif // HISTORY_VOICEEVENT_P_H history-service-0.1+16.04.20160104/src/pluginthreadview.cpp0000644000015600001650000000373512642552153023740 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "pluginthreadview.h" #include "pluginthreadview_p.h" #include "pluginthreadviewadaptor.h" #include "types.h" #include #include Q_DECLARE_METATYPE(QList< QVariantMap >) namespace History { PluginThreadViewPrivate::PluginThreadViewPrivate() : adaptor(0) { } PluginThreadView::PluginThreadView(QObject *parent) : QObject(parent), d_ptr(new PluginThreadViewPrivate()) { Q_D(PluginThreadView); qDBusRegisterMetaType >(); d->adaptor = new ThreadViewAdaptor(this); QString id = QString("threadview%1%2").arg(QString::number((qulonglong)this), QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmsszzz")); d->objectPath = QString("%1/%2").arg(History::DBusObjectPath, id); QDBusConnection::sessionBus().registerObject(d->objectPath, this); } PluginThreadView::~PluginThreadView() { Q_D(PluginThreadView); QDBusConnection::sessionBus().unregisterObject(d->objectPath); } void PluginThreadView::Destroy() { qDebug() << __PRETTY_FUNCTION__; Q_D(PluginThreadView); deleteLater(); } bool PluginThreadView::IsValid() const { return true; } QString PluginThreadView::objectPath() const { Q_D(const PluginThreadView); return d->objectPath; } } history-service-0.1+16.04.20160104/src/ThreadView0000644000015600001650000000003012642552153021621 0ustar pbuserpbgroup00000000000000#include "threadview.h" history-service-0.1+16.04.20160104/src/EventView0000644000015600001650000000002712642552153021501 0ustar pbuserpbgroup00000000000000#include "eventview.h" history-service-0.1+16.04.20160104/src/textevent.h0000644000015600001650000000430512642552153022044 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_TEXTEVENT_H #define HISTORY_TEXTEVENT_H #include "event.h" #include "texteventattachment.h" namespace History { class TextEventPrivate; class ItemFactory; class TextEvent : public Event { Q_DECLARE_PRIVATE(TextEvent) friend class ItemFactory; public: explicit TextEvent(); TextEvent(const QString &accountId, const QString &threadId, const QString &eventId, const QString &sender, const QDateTime ×tamp, bool newEvent, const QString &message, MessageType messageType, MessageStatus messageStatus = MessageStatusUnknown, const QDateTime &readTimestamp = QDateTime(), const QString &subject = QString(), const TextEventAttachments &attachments = TextEventAttachments(), const Participants &participants = Participants()); ~TextEvent(); // copy related members TextEvent(const Event &other); TextEvent& operator=(const Event &other); QString message() const; MessageType messageType() const; MessageStatus messageStatus() const; void setMessageStatus(const MessageStatus &value); QDateTime readTimestamp() const; void setReadTimestamp(const QDateTime &value); QString subject() const; TextEventAttachments attachments() const; static Event fromProperties(const QVariantMap &properties); }; } #endif // HISTORY_TEXTEVENT_H history-service-0.1+16.04.20160104/src/plugin.h0000644000015600001650000000670412642552153021321 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_PLUGIN_H #define HISTORY_PLUGIN_H #include #include "filter.h" #include "types.h" #include "sort.h" #include namespace History { class PluginThreadView; class PluginEventView; class Plugin { public: virtual ~Plugin() {} virtual bool initialised() { return true; } // Reader part of the plugin virtual PluginThreadView* queryThreads(EventType type, const Sort &sort = Sort(), const Filter &filter = Filter(), const QVariantMap &properties = QVariantMap()) = 0; virtual PluginEventView* queryEvents(EventType type, const Sort &sort = Sort(), const Filter &filter = Filter()) = 0; virtual QVariantMap getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap()) = 0; virtual QVariantMap getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId) = 0; virtual QVariantMap threadForParticipants(const QString &accountId, EventType type, const QStringList &participants, History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0; virtual QList eventsForThread(const QVariantMap &thread) = 0; // Writer part of the plugin virtual QVariantMap createThreadForParticipants(const QString &accountId, EventType type, const QStringList &participants) { return QVariantMap(); } virtual bool removeThread(const QVariantMap &thread) { return false; } virtual EventWriteResult writeTextEvent(const QVariantMap &event) { return EventWriteError; } virtual bool removeTextEvent(const QVariantMap &event) { return false; } virtual EventWriteResult writeVoiceEvent(const QVariantMap &event) { return EventWriteError; } virtual bool removeVoiceEvent(const QVariantMap &event) { return false; } virtual bool beginBatchOperation() {} virtual bool endBatchOperation() {} virtual bool rollbackBatchOperation() {} // FIXME: this is hackish, but changing it required a broad refactory of HistoryDaemon virtual void generateContactCache() {} }; } Q_DECLARE_INTERFACE(History::Plugin, "com.canonical.historyservice.Plugin") #endif // HISTORY_PLUGIN_H history-service-0.1+16.04.20160104/src/VoiceEvent0000644000015600001650000000003012642552153021626 0ustar pbuserpbgroup00000000000000#include "voiceevent.h" history-service-0.1+16.04.20160104/src/unionfilter_p.h0000644000015600001650000000252512642552153022675 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_UNIONFILTER_P_H #define HISTORY_UNIONFILTER_P_H #include "filter_p.h" namespace History { class UnionFilter; class UnionFilterPrivate : public FilterPrivate { public: UnionFilterPrivate(); ~UnionFilterPrivate(); Filters filters; virtual FilterType type() const { return FilterTypeUnion; } QString toString(const QString &propertyPrefix = QString::null) const; bool match(const QVariantMap properties) const; bool isValid() const; virtual QVariantMap properties() const; HISTORY_FILTER_DECLARE_CLONE(UnionFilter) }; } #endif // HISTORY_UNIONFILTER_P_H history-service-0.1+16.04.20160104/src/thread_p.h0000644000015600001650000000307612642552153021610 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_THREAD_P_H #define HISTORY_THREAD_P_H #include #include "types.h" namespace History { class Thread; class ThreadPrivate { public: explicit ThreadPrivate(); ThreadPrivate(const QString &theAccountId, const QString &theThreadId, EventType theType, const Participants &theParticipants, const Event &theLastEvent, int theCount, int theUnreadCount, const Threads &theGroupedThreads); virtual ~ThreadPrivate(); QString accountId; QString threadId; Participants participants; EventType type; Event lastEvent; int count; int unreadCount; Threads groupedThreads; }; } #endif // HISTORY_THREAD_P_H history-service-0.1+16.04.20160104/src/intersectionfilter.cpp0000644000015600001650000001022212642552153024260 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "intersectionfilter.h" #include "intersectionfilter_p.h" #include #include #include namespace History { IntersectionFilterPrivate::IntersectionFilterPrivate() { } IntersectionFilterPrivate::~IntersectionFilterPrivate() { } bool IntersectionFilterPrivate::match(const QVariantMap properties) const { // return true only if all filters match Q_FOREACH(const Filter &filter, filters) { if (!filter.match(properties)) { return false; } } return true; } bool IntersectionFilterPrivate::isValid() const { // FIXME: maybe we should check if at least one of the inner filters are valid? return !filters.isEmpty(); } QVariantMap IntersectionFilterPrivate::properties() const { QVariantMap map; if (!isValid()) { return map; } QVariantList filterList; Q_FOREACH(const Filter &filter, filters) { filterList << filter.properties(); } map[FieldFilters] = filterList; map[FieldFilterType] = (int) History::FilterTypeIntersection; return map; } QString IntersectionFilterPrivate::toString(const QString &propertyPrefix) const { if (filters.isEmpty()) { return QString::null; } else if (filters.count() == 1) { return filters.first().toString(); } QStringList output; // wrap each filter string around parenthesis Q_FOREACH(const Filter &filter, filters) { QString value = filter.toString(propertyPrefix); if (!value.isEmpty()) { output << QString("(%1)").arg(value); } } return output.join(" AND "); } HISTORY_FILTER_DEFINE_COPY(IntersectionFilter, FilterTypeIntersection) IntersectionFilter::IntersectionFilter() : Filter(*new IntersectionFilterPrivate()) { } IntersectionFilter::~IntersectionFilter() { } void IntersectionFilter::setFilters(const Filters &filters) { Q_D(IntersectionFilter); d->filters = filters; } void IntersectionFilter::prepend(const Filter &filter) { Q_D(IntersectionFilter); d->filters.prepend(filter); } void IntersectionFilter::append(const Filter &filter) { Q_D(IntersectionFilter); d->filters.append(filter); } void IntersectionFilter::clear() { Q_D(IntersectionFilter); d->filters.clear(); } Filters IntersectionFilter::filters() const { Q_D(const IntersectionFilter); return d->filters; } Filter IntersectionFilter::fromProperties(const QVariantMap &properties) { IntersectionFilter filter; if (properties.isEmpty()) { return filter; } QVariant filters = properties[FieldFilters]; QVariantList filterList; // when the filter travels through DBus, it arrives marshalled into QDBusArguments. // cover that case too. if (filters.canConvert()) { QDBusArgument argument = filters.value(); QVariantList list; argument >> list; // and cast also the inner filters Q_FOREACH(const QVariant &var, list) { QDBusArgument arg = var.value(); QVariantMap map; arg >> map; filterList.append(map); } } else { filterList = filters.toList(); } Q_FOREACH(const QVariant &props, filterList) { Filter innerFilter = History::Filter::fromProperties(props.toMap()); if (innerFilter.isValid()) { filter.append(innerFilter); } } return filter; } } history-service-0.1+16.04.20160104/src/filter.cpp0000644000015600001650000001273512642552153021644 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "filter.h" #include "filter_p.h" #include "intersectionfilter.h" #include "unionfilter.h" #include #include namespace History { // ------------- FilterPrivate ------------------------------------------------ FilterPrivate::FilterPrivate() { } FilterPrivate::FilterPrivate(const QString &theFilterProperty, const QVariant &theFilterValue, MatchFlags theMatchFlags) : filterProperty(theFilterProperty), filterValue(theFilterValue), matchFlags(theMatchFlags) { } FilterPrivate::~FilterPrivate() { } QString FilterPrivate::toString(const QString &propertyPrefix) const { // FIXME: remove the toString() functionality or replace it by a better implementation if (filterProperty.isEmpty() || filterValue.isNull()) { return QString::null; } QString value; switch (filterValue.type()) { case QVariant::String: // FIXME: need to escape strings // wrap strings value = QString("\"%1\"").arg(filterValue.toString()); break; case QVariant::Bool: value = filterValue.toBool() ? "1" : "0"; break; case QVariant::Int: value = QString::number(filterValue.toInt()); break; case QVariant::Double: value = QString::number(filterValue.toDouble()); break; default: value = filterValue.toString(); } QString propertyName = propertyPrefix.isNull() ? filterProperty : QString("%1.%2").arg(propertyPrefix, filterProperty); // FIXME2: need to check for the match flags return QString("%1=%2").arg(propertyName, value); } bool FilterPrivate::match(const QVariantMap properties) const { // assume empty filters match anything if (filterProperty.isEmpty() || !filterValue.isValid() || !properties.contains(filterProperty)) { return true; } // FIXME: use the MatchFlags return properties[filterProperty] == filterValue; } QVariantMap FilterPrivate::properties() const { QVariantMap map; if (!isValid()) { return map; } map[FieldFilterType] = (int)FilterTypeStandard; map[FieldFilterProperty] = filterProperty; map[FieldFilterValue] = filterValue; map[FieldMatchFlags] = (int)matchFlags; return map; } // ------------- Filter ------------------------------------------------------- Filter::Filter(FilterPrivate &p) : d_ptr(&p) { } Filter::Filter(const QString &filterProperty, const QVariant &filterValue, MatchFlags matchFlags) : d_ptr(new FilterPrivate(filterProperty, filterValue, matchFlags)) { } Filter::Filter(const Filter &other) : d_ptr(other.d_ptr->clone()) { } Filter::~Filter() { } Filter &Filter::operator=(const Filter &other) { if (&other == this) { return *this; } d_ptr = QSharedPointer(other.d_ptr->clone()); } QString Filter::filterProperty() const { Q_D(const Filter); return d->filterProperty; } void Filter::setFilterProperty(const QString &value) { Q_D(Filter); d->filterProperty = value; } QVariant Filter::filterValue() const { Q_D(const Filter); return d->filterValue; } void Filter::setFilterValue(const QVariant &value) { Q_D(Filter); d->filterValue = value; } MatchFlags Filter::matchFlags() const { Q_D(const Filter); return d->matchFlags; } void Filter::setMatchFlags(const MatchFlags &flags) { Q_D(Filter); d->matchFlags = flags; } QString Filter::toString(const QString &propertyPrefix) const { Q_D(const Filter); return d->toString(propertyPrefix); } bool Filter::match(const QVariantMap properties) const { Q_D(const Filter); return d->match(properties); } FilterType Filter::type() const { Q_D(const Filter); return d->type(); } bool Filter::operator==(const Filter &other) const { // FIXME: implement in a more performant way return toString() == other.toString(); } bool Filter::isValid() const { Q_D(const Filter); return d->isValid(); } QVariantMap Filter::properties() const { Q_D(const Filter); return d->properties(); } Filter Filter::fromProperties(const QVariantMap &properties) { Filter filter; if (properties.isEmpty()) { return filter; } switch ((FilterType)properties[FieldFilterType].toInt()) { case FilterTypeStandard: filter = Filter(properties[FieldFilterProperty].toString(), properties[FieldFilterValue], (MatchFlags)properties[FieldMatchFlags].toInt()); break; case FilterTypeIntersection: filter = IntersectionFilter::fromProperties(properties); break; case FilterTypeUnion: filter = UnionFilter::fromProperties(properties); break; } return filter; } } history-service-0.1+16.04.20160104/src/TextEvent0000644000015600001650000000002712642552153021513 0ustar pbuserpbgroup00000000000000#include "textevent.h" history-service-0.1+16.04.20160104/src/Thread0000644000015600001650000000002412642552153020771 0ustar pbuserpbgroup00000000000000#include "thread.h" history-service-0.1+16.04.20160104/src/utils_p.h0000644000015600001650000000277312642552153021504 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 UTILS_P_H #define UTILS_P_H #include "types.h" namespace History { class Utils { public: static MatchFlags matchFlagsForAccount(const QString &accountId); static QString protocolFromAccountId(const QString &accountId); static bool compareIds(const QString &accountId, const QString &id1, const QString & id2); static bool compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags); static bool compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags); static bool shouldGroupAccount(const QString &accountId); static QString normalizeId(const QString &accountId, const QString &id); private: Utils(); }; } #endif // UTILS_P_H history-service-0.1+16.04.20160104/src/contactmatcher.cpp0000644000015600001650000004044712642552153023357 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "contactmatcher_p.h" #include "phoneutils_p.h" #include "telepathyhelper_p.h" #include "types.h" #include "utils_p.h" #include #include #include #include #include #include #include #include #include using namespace QtContacts; namespace History { ContactMatcher::ContactMatcher(QContactManager *manager, QObject *parent) : QObject(parent), mManager(manager) { if (!mManager) { mManager = new QContactManager("galera"); } // just trigger the creation of TelepathyHelper connect(History::TelepathyHelper::instance(), SIGNAL(setupReady()), SLOT(onSetupReady())); connect(mManager, SIGNAL(contactsAdded(QList)), SLOT(onContactsAdded(QList))); connect(mManager, SIGNAL(contactsChanged(QList)), SLOT(onContactsChanged(QList))); connect(mManager, SIGNAL(contactsRemoved(QList)), SLOT(onContactsRemoved(QList))); connect(mManager, SIGNAL(dataChanged()), SLOT(onDataChanged())); } void ContactMatcher::onSetupReady() { Q_FOREACH(const RequestInfo &request, mPendingRequests) { requestContactInfo(request.accountId, request.identifier); } mPendingRequests.clear(); } ContactMatcher::~ContactMatcher() { Q_FOREACH(QContactFetchRequest *request, mRequests.keys()) { request->deleteLater(); } mRequests.clear(); mContactMap.clear(); mManager->deleteLater(); } ContactMatcher *ContactMatcher::instance(QContactManager *manager) { static ContactMatcher self(manager); return &self; } /** * \brief Returns the contact information for the given \param identifier, taking into account * the addressable fields of the given \param accountId. * If \param synchronous is specified, a blocking synchronous request will be made to the contact * manager to return the specified data. * * Note that synchronous requests should only be placed after \ref TelepathyHelper is ready. */ QVariantMap ContactMatcher::contactInfo(const QString &accountId, const QString &identifier, bool synchronous) { InternalContactMap &internalMap = mContactMap[accountId]; // first do a simple string match on the map if (internalMap.contains(identifier)) { return internalMap[identifier]; } QVariantMap map; // and if there was no match, asynchronously request the info, and return an empty map for now if (History::TelepathyHelper::instance()->ready()) { map = requestContactInfo(accountId, identifier, synchronous); } else if (!synchronous) { RequestInfo info{accountId, identifier}; mPendingRequests.append(info); } map[History::FieldIdentifier] = identifier; map[History::FieldAccountId] = accountId; mContactMap[accountId][identifier] = map; return map; } QVariantList ContactMatcher::contactInfo(const QString &accountId, const QStringList &identifiers, bool synchronous) { QVariantList contacts; Q_FOREACH(const QString &identifier, identifiers) { contacts << contactInfo(accountId, identifier, synchronous); } return contacts; } void ContactMatcher::watchIdentifier(const QString &accountId, const QString &identifier, const QVariantMap ¤tInfo) { // only add the identifier to the map of watched identifiers QVariantMap map = currentInfo; map[History::FieldIdentifier] = identifier; mContactMap[accountId][identifier] = map; } void ContactMatcher::onContactsAdded(QList ids) { QList contacts = mManager->contacts(ids); // walk through the list of requested phone numbers ContactMap::iterator it = mContactMap.begin(); ContactMap::iterator end = mContactMap.end(); for (; it != end; ++it) { QString accountId = it.key(); InternalContactMap &internalMap = it.value(); InternalContactMap::iterator it2 = internalMap.begin(); InternalContactMap::iterator end2 = internalMap.end(); for (; it2 != end2; ++it2) { QString identifier = it2.key(); // skip entries that already have a match if (hasMatch(it2.value())) { continue; } // now for each entry not populated, check if it matches one of the newly added contacts Q_FOREACH(const QContact &contact, contacts) { QVariantMap map = matchAndUpdate(accountId, identifier, contact); if (hasMatch(map)){ break; } } } } } void ContactMatcher::onContactsChanged(QList ids) { QList contacts = mManager->contacts(ids); // walk through the list of requested phone numbers ContactMap::iterator it = mContactMap.begin(); ContactMap::iterator end = mContactMap.end(); for (; it != end; ++it) { QString accountId = it.key(); InternalContactMap &internalMap = it.value(); InternalContactMap::iterator it2 = internalMap.begin(); InternalContactMap::iterator end2 = internalMap.end(); QStringList identifiersToMatch; for (; it2 != end2; ++it2) { QVariantMap &contactInfo = it2.value(); QString identifier = it2.key(); Q_FOREACH(const QContact &contact, contacts) { bool previousMatch = (contactInfo.contains(History::FieldContactId) && contactInfo[History::FieldContactId].toString() == contact.id().toString()); QVariantMap map = matchAndUpdate(accountId, identifier, contact); if (hasMatch(map)){ break; } else if (previousMatch) { // if there was a previous match but it does not match anymore, try to match the phone number // to a different contact identifiersToMatch << identifier; break; } } } Q_FOREACH(const QString &identifier, identifiersToMatch) { internalMap.remove(identifier); requestContactInfo(accountId, identifier); } } } void ContactMatcher::onContactsRemoved(QList ids) { // search for entries that were matching this contact ContactMap::iterator it = mContactMap.begin(); ContactMap::iterator end = mContactMap.end(); for (; it != end; ++it) { QString accountId = it.key(); InternalContactMap &internalMap = it.value(); InternalContactMap::iterator it2 = internalMap.begin(); InternalContactMap::iterator end2 = internalMap.end(); QStringList identifiersToMatch; for (; it2 != end2; ++it2) { QVariantMap &info = it2.value(); // skip entries that didn't have a match if (!hasMatch(info)) { continue; } Q_FOREACH(const QContactId &id, ids) { if (id.toString() == info[History::FieldContactId].toString()) { identifiersToMatch << it2.key(); break; } } } // now make sure to try a new match on the phone numbers whose contact was removed Q_FOREACH(const QString &identifier, identifiersToMatch) { internalMap.remove(identifier); Q_EMIT contactInfoChanged(accountId, identifier, contactInfo(accountId, identifier)); } } } void ContactMatcher::onDataChanged() { ContactMap::iterator it = mContactMap.begin(); ContactMap::iterator end = mContactMap.end(); for (; it != end; ++it) { QString accountId = it.key(); InternalContactMap &internalMap = it.value(); // invalidate the cache QStringList identifiers = internalMap.keys(); internalMap.clear(); Q_FOREACH(const QString &identifier, identifiers) { QVariantMap info; info[History::FieldIdentifier] = identifier; Q_EMIT contactInfoChanged(accountId, identifier, info); requestContactInfo(accountId, identifier); } } } void ContactMatcher::onRequestStateChanged(QContactAbstractRequest::State state) { QContactFetchRequest *request = qobject_cast(sender()); if (!request) { return; } if (!mRequests.contains(request)) { request->deleteLater(); return; } if (state == QContactAbstractRequest::FinishedState) { request->deleteLater(); RequestInfo info = mRequests.take(request); QContact contact; if (!request->contacts().isEmpty()) { contact = request->contacts().first(); } matchAndUpdate(info.accountId, info.identifier, contact); } else if (state == QContactAbstractRequest::CanceledState) { request->deleteLater(); mRequests.remove(request); } } /** * \brief Requests contact info, and if the preference is for a synchronous request returns the contact information in a * QVariantMap. For asynchronous requests, an empty QVariantMap is returned. */ QVariantMap ContactMatcher::requestContactInfo(const QString &accountId, const QString &identifier, bool synchronous) { QStringList addressableVCardFields = addressableFields(accountId); if (addressableVCardFields.isEmpty()) { // FIXME: add support for generic accounts return QVariantMap(); } bool phoneCompare = addressableVCardFields.contains("tel"); QContactFetchHint hint; hint.setMaxCountHint(1); // FIXME: maybe we need to fetch the full contact? hint.setDetailTypesHint(QList() << QContactDetail::TypeDisplayLabel << QContactDetail::TypePhoneNumber << QContactDetail::TypeAvatar << QContactDetail::TypeExtendedDetail); QContactUnionFilter topLevelFilter; Q_FOREACH(const QString &field, addressableVCardFields) { if (field == "tel") { topLevelFilter.append(QContactPhoneNumber::match(identifier)); } else { // FIXME: handle more fields // rely on a generic field filter QContactDetailFilter nameFilter = QContactDetailFilter(); nameFilter.setDetailType(QContactExtendedDetail::Type, QContactExtendedDetail::FieldName); nameFilter.setMatchFlags(QContactFilter::MatchExactly); nameFilter.setValue(field); QContactDetailFilter valueFilter = QContactDetailFilter(); valueFilter.setDetailType(QContactExtendedDetail::Type, QContactExtendedDetail::FieldData); valueFilter.setMatchFlags(QContactFilter::MatchExactly); valueFilter.setValue(identifier); QContactIntersectionFilter intersectionFilter; intersectionFilter.append(nameFilter); intersectionFilter.append(valueFilter); topLevelFilter.append(intersectionFilter); } } if (synchronous) { QList contacts = mManager->contacts(topLevelFilter, QList(), hint); if (contacts.isEmpty()) { return QVariantMap(); } // for synchronous requests, return the results right away. return matchAndUpdate(accountId, identifier, contacts.first()); } else { // check if there is a request already going on for the given contact Q_FOREACH(const RequestInfo &info, mRequests.values()) { if (info.accountId != accountId) { // skip to the next item continue; } if (info.identifier == identifier) { // if so, just wait for it to finish return QVariantMap(); } } QContactFetchRequest *request = new QContactFetchRequest(this); request->setFetchHint(hint); request->setFilter(topLevelFilter); request->setManager(mManager); connect(request, SIGNAL(stateChanged(QContactAbstractRequest::State)), SLOT(onRequestStateChanged(QContactAbstractRequest::State))); RequestInfo info; info.accountId = accountId; info.identifier = identifier; mRequests[request] = info; request->start(); } return QVariantMap(); } QVariantList ContactMatcher::toVariantList(const QList &list) { QVariantList variantList; Q_FOREACH(int value, list) { variantList << value; } return variantList; } /** * \brief Matches contact data against the given identifier. If the match succeeds, return the updated data in a * QVariantMap, returns an empty map otherwise. */ QVariantMap ContactMatcher::matchAndUpdate(const QString &accountId, const QString &identifier, const QContact &contact) { QVariantMap contactInfo; contactInfo[History::FieldIdentifier] = identifier; contactInfo[History::FieldAccountId] = accountId; if (contact.isEmpty()) { return contactInfo; } QStringList fields = addressableFields(accountId); bool match = false; int fieldsCount = fields.count(); Q_FOREACH(const QString &field, fields) { if (field == "tel") { QList details = contact.details(QContactDetail::TypePhoneNumber); Q_FOREACH(const QContactPhoneNumber number, details) { if (History::PhoneUtils::comparePhoneNumbers(number.number(), identifier)) { QVariantMap detailProperties; detailProperties["phoneSubTypes"] = toVariantList(number.subTypes()); detailProperties["phoneContexts"] = toVariantList(number.contexts()); contactInfo[History::FieldDetailProperties] = detailProperties; match = true; break; } } } else { // FIXME: support more types of field // generic code for extra fields Q_FOREACH(const QContactExtendedDetail detail, contact.details(QContactDetail::TypeExtendedDetail)) { if (detail.name() == field && detail.data() == identifier) { match = true; break; } } } if (match) { break; } } if (match) { contactInfo[History::FieldContactId] = contact.id().toString(); contactInfo[History::FieldAlias] = QContactDisplayLabel(contact.detail(QContactDetail::TypeDisplayLabel)).label(); contactInfo[History::FieldAvatar] = QContactAvatar(contact.detail(QContactDetail::TypeAvatar)).imageUrl().toString(); mContactMap[accountId][identifier] = contactInfo; Q_EMIT contactInfoChanged(accountId, identifier, contactInfo); } return contactInfo; } QStringList ContactMatcher::addressableFields(const QString &accountId) { if (mAddressableFields.contains(accountId)) { return mAddressableFields[accountId]; } Tp::AccountPtr account = History::TelepathyHelper::instance()->accountForId(accountId); QStringList fields; if (!account.isNull()) { fields = account->protocolInfo().addressableVCardFields(); mAddressableFields[accountId] = fields; } return fields; } bool ContactMatcher::hasMatch(const QVariantMap &map) const { return (map.contains(History::FieldContactId) && !map[History::FieldContactId].toString().isEmpty()); } } history-service-0.1+16.04.20160104/src/PluginThreadView0000644000015600001650000000016212642552153023006 0ustar pbuserpbgroup00000000000000#ifndef PLUGINTHREADVIEW_H #define PLUGINTHREADVIEW_H #include "pluginthreadview.h" #endif // PLUGINTHREADVIEW_H history-service-0.1+16.04.20160104/src/pluginthreadview_p.h0000644000015600001650000000203412642552153023713 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 PLUGINTHREADVIEW_P_H #define PLUGINTHREADVIEW_P_H #include class ThreadViewAdaptor; namespace History { class PluginThreadViewPrivate { public: PluginThreadViewPrivate(); ThreadViewAdaptor *adaptor; QString objectPath; }; } #endif // PLUGINTHREADVIEW_P_H history-service-0.1+16.04.20160104/src/Manager0000644000015600001650000000002512642552153021135 0ustar pbuserpbgroup00000000000000#include "manager.h" history-service-0.1+16.04.20160104/src/Plugin0000644000015600001650000000002412642552153021020 0ustar pbuserpbgroup00000000000000#include "plugin.h" history-service-0.1+16.04.20160104/src/Event0000644000015600001650000000002312642552153020642 0ustar pbuserpbgroup00000000000000#include "event.h" history-service-0.1+16.04.20160104/src/voiceevent.h0000644000015600001650000000357112642552153022171 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_VOICEEVENT_H #define HISTORY_VOICEEVENT_H #include "event.h" namespace History { class VoiceEventPrivate; class ItemFactory; class VoiceEvent : public Event { Q_DECLARE_PRIVATE(VoiceEvent) friend class ItemFactory; public: explicit VoiceEvent(); VoiceEvent(const QString &accountId, const QString &threadId, const QString &eventId, const QString &sender, const QDateTime ×tamp, bool newEvent, bool missed, const QTime &duration = QTime(), const QString &remoteParticipant = QString::null, const Participants &participants = Participants()); ~VoiceEvent(); // copy related members VoiceEvent(const Event &other); VoiceEvent& operator=(const Event &other); bool missed() const; QTime duration() const; /*** * Returns the ID of the remote participant. * On incoming calls that's the same as the sender() property */ QString remoteParticipant() const; static Event fromProperties(const QVariantMap &properties); }; } #endif history-service-0.1+16.04.20160104/src/PluginEventView.xml0000644000015600001650000000265212642552153023465 0ustar pbuserpbgroup00000000000000 An interface to the history service EventView object. history-service-0.1+16.04.20160104/src/phoneutils.cpp0000644000015600001650000000771212642552153022550 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Renato Araujo Oliveira Filho * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "phoneutils_p.h" #include #include #include #include #include namespace History { PhoneUtils::PhoneUtils(QObject *parent) : QObject(parent) { } QString PhoneUtils::region() { QString countryCode = QLocale::system().name().split("_").last(); if (countryCode.size() < 2) { // fallback to US if no valid country code was provided, otherwise libphonenumber // will fail to parse any numbers return QString("US"); } return countryCode; } QString PhoneUtils::normalizePhoneNumber(const QString &phoneNumber) { static i18n::phonenumbers::PhoneNumberUtil *phonenumberUtil = i18n::phonenumbers::PhoneNumberUtil::GetInstance(); if (!isPhoneNumber(phoneNumber)) { return phoneNumber; } std::string number = phoneNumber.toStdString(); phonenumberUtil->NormalizeDiallableCharsOnly(&number); return QString::fromStdString(number); } bool PhoneUtils::comparePhoneNumbers(const QString &phoneNumberA, const QString &phoneNumberB) { // just do a simple string comparison first if (phoneNumberA == phoneNumberB) { return true; } QString normalizedPhoneNumberA = normalizePhoneNumber(phoneNumberA); QString normalizedPhoneNumberB = normalizePhoneNumber(phoneNumberB); return compareNormalizedPhoneNumbers(normalizedPhoneNumberA, normalizedPhoneNumberB); } bool PhoneUtils::compareNormalizedPhoneNumbers(const QString &numberA, const QString &numberB) { if (numberA == numberB) { return true; } static i18n::phonenumbers::PhoneNumberUtil *phonenumberUtil = i18n::phonenumbers::PhoneNumberUtil::GetInstance(); if (numberA.size() < 7 || numberB.size() < 7) { return false; } i18n::phonenumbers::PhoneNumberUtil::MatchType match = phonenumberUtil-> IsNumberMatchWithTwoStrings(numberA.toStdString(), numberB.toStdString()); return (match > i18n::phonenumbers::PhoneNumberUtil::NO_MATCH); } bool PhoneUtils::isPhoneNumber(const QString &phoneNumber) { static i18n::phonenumbers::PhoneNumberUtil *phonenumberUtil = i18n::phonenumbers::PhoneNumberUtil::GetInstance(); i18n::phonenumbers::PhoneNumber number; i18n::phonenumbers::PhoneNumberUtil::ErrorType error; error = phonenumberUtil->Parse(phoneNumber.toStdString(), region().toStdString(), &number); switch(error) { case i18n::phonenumbers::PhoneNumberUtil::INVALID_COUNTRY_CODE_ERROR: qWarning() << "Invalid country code for:" << phoneNumber; return false; case i18n::phonenumbers::PhoneNumberUtil::NOT_A_NUMBER: qWarning() << "The phone number is not a valid number:" << phoneNumber; return false; case i18n::phonenumbers::PhoneNumberUtil::TOO_SHORT_AFTER_IDD: case i18n::phonenumbers::PhoneNumberUtil::TOO_SHORT_NSN: case i18n::phonenumbers::PhoneNumberUtil::TOO_LONG_NSN: qWarning() << "Invalid phone number" << phoneNumber; return false; default: break; } return true; } } history-service-0.1+16.04.20160104/src/IntersectionFilter0000644000015600001650000000004012642552153023374 0ustar pbuserpbgroup00000000000000#include "intersectionfilter.h" history-service-0.1+16.04.20160104/src/intersectionfilter_p.h0000644000015600001650000000262512642552153024254 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_INTERSECTIONFILTER_P_H #define HISTORY_INTERSECTIONFILTER_P_H #include "filter_p.h" namespace History { class IntersectionFilter; class IntersectionFilterPrivate : public FilterPrivate { public: IntersectionFilterPrivate(); ~IntersectionFilterPrivate(); virtual FilterType type() const { return FilterTypeIntersection; } QString toString(const QString &propertyPrefix = QString::null) const; bool match(const QVariantMap properties) const; bool isValid() const; Filters filters; virtual QVariantMap properties() const; HISTORY_FILTER_DECLARE_CLONE(IntersectionFilter) }; } #endif // HISTORY_INTERSECTIONFILTER_P_H history-service-0.1+16.04.20160104/src/event.h0000644000015600001650000000336112642552153021140 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_EVENT_H #define HISTORY_EVENT_H #include #include #include #include #include #include "types.h" #include "participant.h" namespace History { class EventPrivate; class Event { Q_DECLARE_PRIVATE(Event) public: explicit Event(); Event(const Event &other); virtual ~Event(); Event& operator=(const Event &other); QString accountId() const; QString threadId() const; QString eventId() const; QString senderId() const; QDateTime timestamp() const; bool newEvent() const; void setNewEvent(bool value); EventType type() const; Participants participants() const; QVariantMap properties() const; bool isNull() const; bool operator==(const Event &other) const; bool operator!=(const Event &other) const; bool operator<(const Event &other) const; protected: Event(EventPrivate &p); QSharedPointer d_ptr; }; typedef QList Events; } #endif history-service-0.1+16.04.20160104/src/threadview_p.h0000644000015600001650000000313512642552153022477 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 THREADVIEW_P_H #define THREADVIEW_P_H #include "types.h" #include namespace History { class ThreadView; class ThreadViewPrivate { Q_DECLARE_PUBLIC(ThreadView) public: ThreadViewPrivate(History::EventType theType, const History::Sort &theSort, const History::Filter &theFilter); EventType type; Sort sort; Filter filter; QString objectPath; bool valid; QDBusInterface *dbus; Threads filteredThreads(const Threads &threads); // private slots void _d_threadsAdded(const History::Threads &threads); void _d_threadsModified(const History::Threads &threads); void _d_threadsRemoved(const History::Threads &threads); ThreadView *q_ptr; }; } #endif // THREADVIEW_P_H history-service-0.1+16.04.20160104/src/contactmatcher_p.h0000644000015600001650000000554112642552153023337 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 CONTACTMATCHER_P_H #define CONTACTMATCHER_P_H #include #include #include #include using namespace QtContacts; namespace History { typedef QMap InternalContactMap; typedef QMap ContactMap; typedef struct { QString accountId; QString identifier; } RequestInfo; class ContactMatcher : public QObject { Q_OBJECT public: static ContactMatcher *instance(QContactManager *manager = 0); QVariantMap contactInfo(const QString &accountId, const QString &identifier, bool synchronous = false); QVariantList contactInfo(const QString &accountId, const QStringList &identifiers, bool synchronous = false); // this will only watch for contact changes affecting the identifier, but won't fetch contact info void watchIdentifier(const QString &accountId, const QString &identifier, const QVariantMap ¤tInfo = QVariantMap()); Q_SIGNALS: void contactInfoChanged(const QString &acountId, const QString &identifier, const QVariantMap &contactInfo); protected Q_SLOTS: void onContactsAdded(QList ids); void onContactsChanged(QList ids); void onContactsRemoved(QList ids); void onDataChanged(); void onRequestStateChanged(QContactAbstractRequest::State state); void onSetupReady(); protected: QVariantMap requestContactInfo(const QString &accountId, const QString &identifier, bool synchronous = false); QVariantList toVariantList(const QList &list); QVariantMap matchAndUpdate(const QString &accountId, const QString &identifier, const QContact &contact); QStringList addressableFields(const QString &accountId); bool hasMatch(const QVariantMap &map) const; private: explicit ContactMatcher(QContactManager *manager = 0, QObject *parent = 0); ~ContactMatcher(); ContactMap mContactMap; QMap mRequests; QMap mAddressableFields; QList mPendingRequests; QContactManager *mManager; }; } #endif // CONTACTMATCHER_P_H history-service-0.1+16.04.20160104/src/manager_p.h0000644000015600001650000000223312642552153021745 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_MANAGER_P_H #define HISTORY_MANAGER_P_H #include #include #include #include "types.h" namespace History { class Manager; class ManagerDBus; class ManagerPrivate { public: ManagerPrivate(); ~ManagerPrivate(); QScopedPointer dbus; bool serviceRunning; QDBusServiceWatcher serviceWatcher; }; } #endif // HISTORY_MANAGER_P_H history-service-0.1+16.04.20160104/src/filter_p.h0000644000015600001650000000464312642552153021627 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_FILTER_P_H #define HISTORY_FILTER_P_H #include #include #include #include "types.h" #define HISTORY_FILTER_DECLARE_CLONE(Class) \ virtual FilterPrivate *clone() { return new Class##Private(*this); } #define HISTORY_FILTER_DEFINE_COPY(Class, Type) \ Class::Class(const Filter &other) { \ if (other.type() == Type) { d_ptr = QSharedPointer(reinterpret_cast(FilterPrivate::getD(other)->clone())); } \ else { d_ptr = QSharedPointer(new Class##Private()); } \ } \ Class& Class::operator=(const Filter &other) { \ if (other.type() == Type) { d_ptr = QSharedPointer(reinterpret_cast(FilterPrivate::getD(other)->clone())); } \ return *this; \ } namespace History { class FilterPrivate { public: FilterPrivate(); FilterPrivate(const QString &theFilterProperty, const QVariant &theFilterValue, MatchFlags theMatchFlags); virtual ~FilterPrivate(); QString filterProperty; QVariant filterValue; MatchFlags matchFlags; static const QSharedPointer& getD(const Filter& other) { return other.d_ptr; } virtual QString toString(const QString &propertyPrefix = QString::null) const; virtual bool match(const QVariantMap properties) const; virtual FilterType type() const { return History::FilterTypeStandard; } virtual bool isValid() const { return (!filterProperty.isNull()) && (!filterValue.isNull()); } virtual QVariantMap properties() const; HISTORY_FILTER_DECLARE_CLONE(Filter) }; } #endif // HISTORY_FILTER_P_H history-service-0.1+16.04.20160104/src/history-service.pc.in0000644000015600001650000000070012642552153023730 0ustar pbuserpbgroup00000000000000prefix=${CMAKE_INSTALL_PREFIX} exec_prefix=${CMAKE_INSTALL_PREFIX} libdir=${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} includedir=${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR} Name: history-service Description: History service to store messages and calls Version: ${PACKAGE_VERSION} Requires.private: Qt5Core, Qt5DBus Libs: -L${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -lhistoryservice Cflags: -I${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR} history-service-0.1+16.04.20160104/src/sort_p.h0000644000015600001650000000230512642552153021322 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_SORT_P_H #define HISTORY_SORT_P_H #include #include "types.h" namespace History { class Sort; class SortPrivate { public: SortPrivate(const QString &theSortField, Qt::SortOrder theSortOrder, Qt::CaseSensitivity theCaseSensitivity); virtual ~SortPrivate(); QString sortField; Qt::SortOrder sortOrder; Qt::CaseSensitivity caseSensitivity; }; } #endif // HISTORY_SORT_P_H history-service-0.1+16.04.20160104/src/voiceevent.cpp0000644000015600001650000001037112642552153022520 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "voiceevent.h" #include "voiceevent_p.h" namespace History { // ------------- VoiceEventPrivate ------------------------------------------------ VoiceEventPrivate::VoiceEventPrivate() { } VoiceEventPrivate::VoiceEventPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theSender, const QDateTime &theTimestamp, bool theNewEvent, bool theMissed, const QTime &theDuration, const QString &theRemoteParticipant, const Participants &theParticipants) : EventPrivate(theAccountId, theThreadId, theEventId, theSender, theTimestamp, theNewEvent, theParticipants), missed(theMissed), duration(theDuration), remoteParticipant(theRemoteParticipant) { } VoiceEventPrivate::~VoiceEventPrivate() { } EventType VoiceEventPrivate::type() const { return EventTypeVoice; } QVariantMap VoiceEventPrivate::properties() const { QVariantMap map = EventPrivate::properties(); map[FieldMissed] = missed; map[FieldDuration] = QTime(0,0,0,0).secsTo(duration); map[FieldRemoteParticipant] = remoteParticipant; return map; } // ------------- VoiceEvent ------------------------------------------------------- HISTORY_EVENT_DEFINE_COPY(VoiceEvent, EventTypeVoice) VoiceEvent::VoiceEvent() : Event(*new VoiceEventPrivate()) { } VoiceEvent::VoiceEvent(const QString &accountId, const QString &threadId, const QString &eventId, const QString &sender, const QDateTime ×tamp, bool newEvent, bool missed, const QTime &duration, const QString &remoteParticipant, const Participants &participants) : Event(*new VoiceEventPrivate(accountId, threadId, eventId, sender, timestamp, newEvent, missed, duration, remoteParticipant, participants)) { } VoiceEvent::~VoiceEvent() { } bool VoiceEvent::missed() const { Q_D(const VoiceEvent); return d->missed; } QTime VoiceEvent::duration() const { Q_D(const VoiceEvent); return d->duration; } QString VoiceEvent::remoteParticipant() const { Q_D(const VoiceEvent); return d->remoteParticipant; } Event VoiceEvent::fromProperties(const QVariantMap &properties) { Event event; if (properties.isEmpty()) { return event; } QString accountId = properties[FieldAccountId].toString(); QString threadId = properties[FieldThreadId].toString(); QString eventId = properties[FieldEventId].toString(); QString senderId = properties[FieldSenderId].toString(); QDateTime timestamp = QDateTime::fromString(properties[FieldTimestamp].toString(), Qt::ISODate); bool newEvent = properties[FieldNewEvent].toBool(); Participants participants = Participants::fromVariant(properties[FieldParticipants]); bool missed = properties[FieldMissed].toBool(); QTime duration = QTime(0,0,0).addSecs(properties[FieldDuration].toInt()); QString remoteParticipant = properties[FieldRemoteParticipant].toString(); event = VoiceEvent(accountId, threadId, eventId, senderId, timestamp, newEvent, missed, duration, remoteParticipant, participants); return event; } } history-service-0.1+16.04.20160104/src/telepathyhelper_p.h0000644000015600001650000000446112642552153023537 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2015 Canonical, Ltd. * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 TELEPATHYHELPER_H #define TELEPATHYHELPER_H #include #include #include #include #include #include #include "channelobserver_p.h" namespace History { class TelepathyHelper : public QObject { Q_OBJECT public: ~TelepathyHelper(); static TelepathyHelper *instance(); ChannelObserver *channelObserver() const; void registerClient(Tp::AbstractClient *client, QString name); Tp::AccountPtr accountForId(const QString &accountId); QList accounts() const; bool connected() const; bool ready() const; Q_SIGNALS: void channelObserverCreated(ChannelObserver *observer); void accountAdded(const Tp::AccountPtr &account); void accountRemoved(const Tp::AccountPtr &account); void setupReady(); public Q_SLOTS: void registerChannelObserver(); private Q_SLOTS: void onAccountManagerReady(Tp::PendingOperation *op); void onNewAccount(const Tp::AccountPtr &account); void onAccountRemoved(); private: explicit TelepathyHelper(QObject *parent = 0); Tp::AccountManagerPtr mAccountManager; Tp::Features mAccountManagerFeatures; Tp::Features mAccountFeatures; Tp::Features mContactFeatures; Tp::Features mConnectionFeatures; Tp::ClientRegistrarPtr mClientRegistrar; ChannelObserver *mChannelObserver; QList mAccounts; bool mReady; }; } #endif // TELEPATHYHELPER_H history-service-0.1+16.04.20160104/src/threadview.cpp0000644000015600001650000001154112642552153022513 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "threadview.h" #include "threadview_p.h" #include "filter.h" #include "manager.h" #include "sort.h" #include "thread.h" #include #include #include namespace History { // ------------- ThreadViewPrivate ------------------------------------------------ ThreadViewPrivate::ThreadViewPrivate(History::EventType theType, const History::Sort &theSort, const History::Filter &theFilter) : type(theType), sort(theSort), filter(theFilter), valid(true), dbus(0) { } Threads ThreadViewPrivate::filteredThreads(const Threads &threads) { bool filterNull = filter.isNull(); Threads filtered; Q_FOREACH(const Thread &thread, threads) { if (thread.type() != type) { continue; } if (filterNull || filter.match(thread.properties())) { filtered << thread; } } return filtered; } void ThreadViewPrivate::_d_threadsAdded(const History::Threads &threads) { Q_Q(ThreadView); Threads filtered = filteredThreads(threads); if (!filtered.isEmpty()) { Q_EMIT q->threadsAdded(filtered); } } void ThreadViewPrivate::_d_threadsModified(const Threads &threads) { Q_Q(ThreadView); Threads filtered = filteredThreads(threads); if (!filtered.isEmpty()) { Q_EMIT q->threadsModified(filtered); } } void ThreadViewPrivate::_d_threadsRemoved(const Threads &threads) { Q_Q(ThreadView); Threads filtered = filteredThreads(threads); if (!filtered.isEmpty()) { Q_EMIT q->threadsRemoved(filtered); } } // ------------- ThreadView ------------------------------------------------------- ThreadView::ThreadView(History::EventType type, const History::Sort &sort, const Filter &filter, const QVariantMap &properties) : d_ptr(new ThreadViewPrivate(type, sort, filter)) { d_ptr->q_ptr = this; if (!Manager::instance()->isServiceRunning()) { Q_EMIT invalidated(); d_ptr->valid = false; return; } QDBusInterface interface(History::DBusService, History::DBusObjectPath, History::DBusInterface); QDBusReply reply = interface.call("QueryThreads", (int) type, sort.properties(), filter.properties(), properties); if (!reply.isValid()) { Q_EMIT invalidated(); d_ptr->valid = false; return; } d_ptr->objectPath = reply.value(); d_ptr->dbus = new QDBusInterface(History::DBusService, d_ptr->objectPath, History::ThreadViewInterface, QDBusConnection::sessionBus(), this); connect(Manager::instance(), SIGNAL(threadsAdded(History::Threads)), SLOT(_d_threadsAdded(History::Threads))); connect(Manager::instance(), SIGNAL(threadsModified(History::Threads)), SLOT(_d_threadsModified(History::Threads))); connect(Manager::instance(), SIGNAL(threadsRemoved(History::Threads)), SLOT(_d_threadsRemoved(History::Threads))); } ThreadView::~ThreadView() { Q_D(ThreadView); if (d->valid) { d->dbus->call("Destroy"); } } Threads ThreadView::nextPage() { Threads threads; Q_D(ThreadView); if (!d->valid) { return threads; } QDBusReply > reply = d->dbus->call("NextPage"); if (!reply.isValid()) { qDebug() << "Error:" << reply.error(); d->valid = false; Q_EMIT invalidated(); return threads; } QList threadsProperties = reply.value(); Q_FOREACH(const QVariantMap &properties, threadsProperties) { Thread thread = Thread::fromProperties(properties); if (!thread.isNull()) { threads << thread; } } return threads; } bool ThreadView::isValid() const { Q_D(const ThreadView); return d->valid; } } #include "moc_threadview.cpp" history-service-0.1+16.04.20160104/src/textevent.cpp0000644000015600001650000001540712642552153022404 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 #include "textevent.h" #include "textevent_p.h" #include "texteventattachment.h" Q_DECLARE_METATYPE(QList< QVariantMap >) namespace History { // ------------- TextEventPrivate ------------------------------------------------ TextEventPrivate::TextEventPrivate() { } TextEventPrivate::TextEventPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theSender, const QDateTime &theTimestamp, bool theNewEvent, const QString &theMessage, MessageType theMessageType, MessageStatus theMessageStatus, const QDateTime &theReadTimestamp, const QString &theSubject, const TextEventAttachments &theAttachments, const Participants &theParticipants) : EventPrivate(theAccountId, theThreadId, theEventId, theSender, theTimestamp, theNewEvent, theParticipants), message(theMessage), messageType(theMessageType), messageStatus(theMessageStatus), readTimestamp(theReadTimestamp), subject(theSubject), attachments(theAttachments) { } TextEventPrivate::~TextEventPrivate() { } EventType TextEventPrivate::type() const { return EventTypeText; } QVariantMap TextEventPrivate::properties() const { QVariantMap map = EventPrivate::properties(); map[FieldMessage] = message; map[FieldMessageType] = (int)messageType; map[FieldMessageStatus] = (int)messageStatus; map[FieldReadTimestamp] = readTimestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz"); map[FieldSubject] = subject; QList attachmentsMap; Q_FOREACH(const TextEventAttachment &attachment, attachments) { attachmentsMap << attachment.properties(); } map[FieldAttachments] = QVariant::fromValue(attachmentsMap); return map; } // ------------- TextEvent ------------------------------------------------------- HISTORY_EVENT_DEFINE_COPY(TextEvent, EventTypeText) TextEvent::TextEvent() : Event(*new TextEventPrivate()) { } TextEvent::TextEvent(const QString &accountId, const QString &threadId, const QString &eventId, const QString &sender, const QDateTime ×tamp, bool newEvent, const QString &message, MessageType messageType, MessageStatus messageStatus, const QDateTime &readTimestamp, const QString &subject, const TextEventAttachments &attachments, const Participants &participants) : Event(*new TextEventPrivate(accountId, threadId, eventId, sender, timestamp, newEvent, message, messageType, messageStatus, readTimestamp, subject, attachments, participants)) { qDBusRegisterMetaType >(); qRegisterMetaType >(); } TextEvent::~TextEvent() { } QString TextEvent::message() const { Q_D(const TextEvent); return d->message; } MessageType TextEvent::messageType() const { Q_D(const TextEvent); return d->messageType; } MessageStatus TextEvent::messageStatus() const { Q_D(const TextEvent); return d->messageStatus; } void TextEvent::setMessageStatus(const MessageStatus &value) { Q_D(TextEvent); d->messageStatus = value; } QDateTime TextEvent::readTimestamp() const { Q_D(const TextEvent); return d->readTimestamp; } void TextEvent::setReadTimestamp(const QDateTime &value) { Q_D(TextEvent); d->readTimestamp = value; } QString TextEvent::subject() const { Q_D(const TextEvent); return d->subject; } TextEventAttachments TextEvent::attachments() const { Q_D(const TextEvent); return d->attachments; } Event TextEvent::fromProperties(const QVariantMap &properties) { Event event; if (properties.isEmpty()) { return event; } QString accountId = properties[FieldAccountId].toString(); QString threadId = properties[FieldThreadId].toString(); QString eventId = properties[FieldEventId].toString(); QString senderId = properties[FieldSenderId].toString(); QDateTime timestamp = QDateTime::fromString(properties[FieldTimestamp].toString(), Qt::ISODate); bool newEvent = properties[FieldNewEvent].toBool(); Participants participants = Participants::fromVariant(properties[FieldParticipants]); QString message = properties[FieldMessage].toString(); QString subject = properties[FieldSubject].toString(); MessageType messageType = (MessageType) properties[FieldMessageType].toInt(); MessageStatus messageStatus = (MessageStatus) properties[FieldMessageStatus].toInt(); QDateTime readTimestamp = QDateTime::fromString(properties[FieldReadTimestamp].toString(), Qt::ISODate); // read the attachments QList attachmentProperties = qdbus_cast >(properties[FieldAttachments]); // dbus_cast fails if the map was generated by a qml app, so we demarshal it by hand if (attachmentProperties.isEmpty()) { QVariantList attachmentList = properties[FieldAttachments].toList(); Q_FOREACH(const QVariant &attachmentMap, attachmentList) { attachmentProperties << attachmentMap.toMap(); } } TextEventAttachments attachments; Q_FOREACH(const QVariantMap &map, attachmentProperties) { TextEventAttachment attachment = TextEventAttachment::fromProperties(map); if (!attachment.isNull()) { attachments << attachment; } } // and finally create the event event = TextEvent(accountId, threadId, eventId, senderId, timestamp, newEvent, message, messageType, messageStatus, readTimestamp, subject, attachments, participants); return event; } } history-service-0.1+16.04.20160104/src/eventview.h0000644000015600001650000000337412642552153022037 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_EVENTVIEW_H #define HISTORY_EVENTVIEW_H #include "types.h" #include "event.h" #include "filter.h" #include "sort.h" #include namespace History { class EventViewPrivate; class EventView : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(EventView) public: EventView(History::EventType type, const History::Sort &sort, const History::Filter &filter); virtual ~EventView(); QList nextPage(); bool isValid() const; Q_SIGNALS: void eventsAdded(const History::Events &events); void eventsModified(const History::Events &events); void eventsRemoved(const History::Events &events); void invalidated(); private: Q_PRIVATE_SLOT(d_func(), void _d_eventsAdded(const History::Events &events)) Q_PRIVATE_SLOT(d_func(), void _d_eventsModified(const History::Events &events)) Q_PRIVATE_SLOT(d_func(), void _d_eventsRemoved(const History::Events &events)) QScopedPointer d_ptr; }; } #endif // HISTORY_EVENTVIEW_H history-service-0.1+16.04.20160104/src/telepathyhelper.cpp0000644000015600001650000001243012642552153023546 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2015 Canonical, Ltd. * * Authors: * Tiago Salem Herrmann * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "telepathyhelper_p.h" #include namespace History { TelepathyHelper::TelepathyHelper(QObject *parent) : QObject(parent), mChannelObserver(0), mReady(false) { mAccountFeatures << Tp::Account::FeatureCore << Tp::Account::FeatureProtocolInfo; mContactFeatures << Tp::Contact::FeatureAlias << Tp::Contact::FeatureAvatarData << Tp::Contact::FeatureAvatarToken << Tp::Contact::FeatureCapabilities << Tp::Contact::FeatureSimplePresence; mConnectionFeatures << Tp::Connection::FeatureCore << Tp::Connection::FeatureSelfContact << Tp::Connection::FeatureSimplePresence; Tp::ChannelFactoryPtr channelFactory = Tp::ChannelFactory::create(QDBusConnection::sessionBus()); channelFactory->addCommonFeatures(Tp::Channel::FeatureCore); mAccountManager = Tp::AccountManager::create( Tp::AccountFactory::create(QDBusConnection::sessionBus(), mAccountFeatures), Tp::ConnectionFactory::create(QDBusConnection::sessionBus(), mConnectionFeatures), channelFactory, Tp::ContactFactory::create(mContactFeatures)); connect(mAccountManager->becomeReady(Tp::AccountManager::FeatureCore), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onAccountManagerReady(Tp::PendingOperation*))); mClientRegistrar = Tp::ClientRegistrar::create(mAccountManager); } TelepathyHelper::~TelepathyHelper() { } TelepathyHelper *TelepathyHelper::instance() { static TelepathyHelper* helper = new TelepathyHelper(); return helper; } bool TelepathyHelper::ready() const { return mReady; } ChannelObserver *TelepathyHelper::channelObserver() const { return mChannelObserver; } void TelepathyHelper::registerChannelObserver() { // check if this instance is running on the main phone application // or if it is just the plugin imported somewhere else QString observerName = "HistoryDaemonObserver"; mChannelObserver = new ChannelObserver(this); registerClient(mChannelObserver, observerName); Q_EMIT channelObserverCreated(mChannelObserver); } void TelepathyHelper::registerClient(Tp::AbstractClient *client, QString name) { Tp::AbstractClientPtr clientPtr(client); bool succeeded = mClientRegistrar->registerClient(clientPtr, name); if (!succeeded) { name.append("%1"); int count = 0; // limit the number of registered clients to 20, that should be a safe margin while (!succeeded && count < 20) { succeeded = mClientRegistrar->registerClient(clientPtr, name.arg(++count)); if (succeeded) { name = name.arg(count); } } } if (succeeded) { QObject *object = dynamic_cast(client); if (object) { object->setProperty("clientName", TP_QT_IFACE_CLIENT + "." + name ); } } } Tp::AccountPtr TelepathyHelper::accountForId(const QString &accountId) { Q_FOREACH(const Tp::AccountPtr &account, mAccounts) { if (account->uniqueIdentifier() == accountId) { return account; } } return Tp::AccountPtr(); } QList TelepathyHelper::accounts() const { return mAccounts; } void TelepathyHelper::onAccountManagerReady(Tp::PendingOperation *op) { Q_UNUSED(op) Q_FOREACH(const Tp::AccountPtr &account, mAccountManager->allAccounts()) { onNewAccount(account); } connect(mAccountManager.data(), SIGNAL(newAccount(Tp::AccountPtr)), SLOT(onNewAccount(Tp::AccountPtr))); mReady = true; Q_EMIT setupReady(); } void TelepathyHelper::onNewAccount(const Tp::AccountPtr &account) { connect(account.data(), SIGNAL(removed()), SLOT(onAccountRemoved())); mAccounts.append(account); Q_EMIT accountAdded(account); } void TelepathyHelper::onAccountRemoved() { Tp::AccountPtr account(qobject_cast(sender())); if (account.isNull() || !mAccounts.contains(account)) { qWarning() << "The removed account was not found."; return; } account.data()->disconnect(this); mAccounts.removeAll(account); Q_EMIT accountRemoved(account); } bool TelepathyHelper::connected() const { Q_FOREACH(const Tp::AccountPtr &account, mAccounts) { if (!account->connection().isNull()) { return true; } } return false; } } history-service-0.1+16.04.20160104/src/threadview.h0000644000015600001650000000347512642552153022167 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_THREADVIEW_H #define HISTORY_THREADVIEW_H #include "types.h" #include "filter.h" #include "sort.h" #include "thread.h" #include namespace History { class ThreadViewPrivate; class ThreadView : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(ThreadView) public: ThreadView(History::EventType type, const History::Sort &sort, const History::Filter &filter, const QVariantMap &properties); ~ThreadView(); Threads nextPage(); bool isValid() const; Q_SIGNALS: void threadsAdded(const History::Threads &threads); void threadsModified(const History::Threads &threads); void threadsRemoved(const History::Threads &threads); void invalidated(); private: Q_PRIVATE_SLOT(d_func(), void _d_threadsAdded(const History::Threads &threads)) Q_PRIVATE_SLOT(d_func(), void _d_threadsModified(const History::Threads &threads)) Q_PRIVATE_SLOT(d_func(), void _d_threadsRemoved(const History::Threads &threads)) QScopedPointer d_ptr; }; } #endif // HISTORY_THREADVIEW_H history-service-0.1+16.04.20160104/src/phoneutils_p.h0000644000015600001650000000271512642552153022532 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Renato Araujo Oliveira Filho * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 PHONEUTILS_H #define PHONEUTILS_H #include namespace History { class PhoneUtils : public QObject { Q_OBJECT public: explicit PhoneUtils(QObject *parent = 0); Q_INVOKABLE static bool comparePhoneNumbers(const QString &numberA, const QString &numberB); Q_INVOKABLE static bool compareNormalizedPhoneNumbers(const QString &numberA, const QString &numberB); Q_INVOKABLE static bool isPhoneNumber(const QString &identifier); Q_INVOKABLE static QString normalizePhoneNumber(const QString &identifier); private: static QString region(); }; } #endif // PHONEUTILS_H history-service-0.1+16.04.20160104/src/UnionFilter0000644000015600001650000000003112642552153022016 0ustar pbuserpbgroup00000000000000#include "unionfilter.h" history-service-0.1+16.04.20160104/src/plugineventview.cpp0000644000015600001650000000371212642552153023605 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "plugineventview.h" #include "plugineventview_p.h" #include "plugineventviewadaptor.h" #include "types.h" #include #include Q_DECLARE_METATYPE(QList< QVariantMap >) namespace History { PluginEventViewPrivate::PluginEventViewPrivate() : adaptor(0) { } PluginEventView::PluginEventView(QObject *parent) : QObject(parent), d_ptr(new PluginEventViewPrivate()) { Q_D(PluginEventView); qDBusRegisterMetaType >(); d->adaptor = new EventViewAdaptor(this); QString id = QString("eventview%1%2").arg(QString::number((qulonglong)this), QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmsszzz")); d->objectPath = QString("%1/%2").arg(History::DBusObjectPath, id); QDBusConnection::sessionBus().registerObject(d->objectPath, this); } PluginEventView::~PluginEventView() { Q_D(PluginEventView); QDBusConnection::sessionBus().unregisterObject(d->objectPath); } void PluginEventView::Destroy() { qDebug() << __PRETTY_FUNCTION__; Q_D(PluginEventView); deleteLater(); } bool PluginEventView::IsValid() const { return true; } QString PluginEventView::objectPath() const { Q_D(const PluginEventView); return d->objectPath; } } history-service-0.1+16.04.20160104/src/Participant0000644000015600001650000000003112642552153022036 0ustar pbuserpbgroup00000000000000#include "participant.h" history-service-0.1+16.04.20160104/src/channelobserver.cpp0000644000015600001650000001570112642552153023533 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "channelobserver_p.h" #include "types.h" #include #include #include #include #include namespace History { ChannelObserver::ChannelObserver(QObject *parent) : QObject(parent), Tp::AbstractClientObserver(channelFilters(), true) { } Tp::ChannelClassSpecList ChannelObserver::channelFilters() const { Tp::ChannelClassSpecList specList; specList << Tp::ChannelClassSpec::audioCall(); specList << Tp::ChannelClassSpec::textChat(); return specList; } void ChannelObserver::observeChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList &channels, const Tp::ChannelDispatchOperationPtr &dispatchOperation, const QList &requestsSatisfied, const Tp::AbstractClientObserver::ObserverInfo &observerInfo) { Q_UNUSED(account) Q_UNUSED(connection) Q_UNUSED(dispatchOperation) Q_UNUSED(requestsSatisfied) Q_UNUSED(observerInfo) qDebug() << __PRETTY_FUNCTION__; Q_FOREACH (Tp::ChannelPtr channel, channels) { // tp-qt has not support for the SMS interface if (channel->immutableProperties().contains(TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash"))) { if (channel->immutableProperties()[TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash")].toBool()) { continue; } } mContexts[channel.data()] = context; mChannels.append(channel); connect(channel.data(), SIGNAL(invalidated(Tp::DBusProxy*,const QString&, const QString&)), SLOT(onChannelInvalidated())); channel->setProperty(History::FieldAccountId, account->uniqueIdentifier()); qDebug() << "Saving account id:" << account->uniqueIdentifier(); Tp::CallChannelPtr callChannel = Tp::CallChannelPtr::dynamicCast(channel); if (callChannel) { Tp::PendingReady *ready = callChannel->becomeReady(Tp::Features() << Tp::CallChannel::FeatureCore << Tp::CallChannel::FeatureCallMembers << Tp::CallChannel::FeatureCallState << Tp::CallChannel::FeatureContents << Tp::CallChannel::FeatureLocalHoldState); connect(ready, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onCallChannelReady(Tp::PendingOperation*))); mReadyMap[ready] = callChannel; } Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel); if (textChannel) { Tp::PendingReady *ready = textChannel->becomeReady(Tp::Features() << Tp::TextChannel::FeatureCore << Tp::TextChannel::FeatureChatState << Tp::TextChannel::FeatureMessageCapabilities << Tp::TextChannel::FeatureMessageQueue << Tp::TextChannel::FeatureMessageSentSignal); connect(ready, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onTextChannelReady(Tp::PendingOperation*))); mReadyMap[ready] = textChannel; } } } void ChannelObserver::onCallChannelReady(Tp::PendingOperation *op) { Tp::PendingReady *ready = qobject_cast(op); if (!ready) { qCritical() << "Pending operation is not a pending ready:" << op; return; } if (!mReadyMap.contains(ready)) { qWarning() << "Pending ready finished but not on the map:" << ready; return; } Tp::CallChannelPtr callChannel = Tp::CallChannelPtr::dynamicCast(mReadyMap[ready]); mReadyMap.remove(ready); if (!callChannel) { qWarning() << "Ready channel is not a call channel:" << callChannel; return; } Q_EMIT callChannelAvailable(callChannel); checkContextFinished(callChannel.data()); } void ChannelObserver::onChannelInvalidated() { Tp::ChannelPtr channel(qobject_cast(sender())); mChannels.removeAll(channel); } void ChannelObserver::onTextChannelReady(Tp::PendingOperation *op) { Tp::PendingReady *ready = qobject_cast(op); if (!ready) { qCritical() << "Pending operation is not a pending ready:" << op; return; } if (!mReadyMap.contains(ready)) { qWarning() << "Pending ready finished but not on the map:" << ready; return; } Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(mReadyMap[ready]); mReadyMap.remove(ready); if (!textChannel) { qWarning() << "Ready channel is not a call channel:" << textChannel; return; } Q_EMIT textChannelAvailable(textChannel); checkContextFinished(textChannel.data()); } void ChannelObserver::checkContextFinished(Tp::Channel *channel) { if (!mContexts.contains(channel)) { qWarning() << "Context for channel not available:" << channel; return; } Tp::MethodInvocationContextPtr<> context = mContexts[channel]; mContexts.remove(channel); // check if this is the last channel from the context Q_FOREACH(Tp::MethodInvocationContextPtr<> otherContext, mContexts.values()) { // if we find the context, just return from the function. We need to wait // for the other channels to become ready before setting the context finished if (otherContext == context) { return; } } context->setFinished(); } } history-service-0.1+16.04.20160104/src/event_p.h0000644000015600001650000000472212642552153021461 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_EVENT_P_H #define HISTORY_EVENT_P_H #include #include #include #include "types.h" #include "participant.h" #define HISTORY_EVENT_DECLARE_CLONE(Class) \ virtual EventPrivate *clone() { return new Class##Private(*this); } #define HISTORY_EVENT_DEFINE_COPY(Class, Type) \ Class::Class(const Event &other) { \ if (other.type() == Type) { d_ptr = QSharedPointer(reinterpret_cast(EventPrivate::getD(other)->clone())); } \ else { d_ptr = QSharedPointer(new Class##Private()); } \ } \ Class& Class::operator=(const Event &other) { \ if (other.type() == Type) { d_ptr = QSharedPointer(reinterpret_cast(EventPrivate::getD(other)->clone())); } \ return *this; \ } namespace History { class EventPrivate { public: EventPrivate(); EventPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theSenderId, const QDateTime &theTimestamp, bool theNewEvent, const Participants &theParticipants); virtual ~EventPrivate(); virtual EventType type() const { return EventTypeNull; } virtual QVariantMap properties() const; QString accountId; QString threadId; QString eventId; QString senderId; QString receiver; QDateTime timestamp; bool newEvent; Participants participants; static const QSharedPointer& getD(const Event& other) { return other.d_ptr; } HISTORY_EVENT_DECLARE_CLONE(Event) }; } #endif // HISTORY_EVENT_P_H history-service-0.1+16.04.20160104/src/Types0000644000015600001650000000002312642552153020665 0ustar pbuserpbgroup00000000000000#include "types.h" history-service-0.1+16.04.20160104/src/participant_p.h0000644000015600001650000000304612642552153022654 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_PARTICIPANT_P_H #define HISTORY_PARTICIPANT_P_H #include #include namespace History { class Participant; class ParticipantPrivate { public: explicit ParticipantPrivate(); ParticipantPrivate(const QString &theAccountId, const QString &theIdentifier, const QString &theContactId = QString::null, const QString &theAlias = QString::null, const QString &theAvatar = QString::null, const QVariantMap &theDetailProperties = QVariantMap()); virtual ~ParticipantPrivate(); QString accountId; QString identifier; QString contactId; QString alias; QString avatar; QVariantMap detailProperties; }; } #endif // HISTORY_PARTICIPANT_P_H history-service-0.1+16.04.20160104/src/filter.h0000644000015600001650000000411612642552153021303 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_FILTER_H #define HISTORY_FILTER_H #include #include #include #include "types.h" namespace History { class FilterPrivate; // simple filter class Filter { Q_DECLARE_PRIVATE(Filter) public: Filter(const QString &filterProperty = QString::null, const QVariant &filterValue = QVariant(), MatchFlags matchFlags = MatchCaseSensitive); Filter(const Filter &other); virtual ~Filter(); Filter& operator=(const Filter& other); QString filterProperty() const; void setFilterProperty(const QString &value); QVariant filterValue() const; void setFilterValue(const QVariant &value); MatchFlags matchFlags() const; void setMatchFlags(const MatchFlags &flags); QString toString(const QString &propertyPrefix = QString::null) const; bool match(const QVariantMap properties) const; FilterType type() const; bool operator==(const Filter &other) const; bool operator!=(const Filter &other) const { return !operator==(other); } bool isValid() const; bool isNull() const { return !isValid(); } QVariantMap properties() const; static Filter fromProperties(const QVariantMap &properties); protected: Filter(FilterPrivate &p); QSharedPointer d_ptr; }; typedef QList Filters; } #endif history-service-0.1+16.04.20160104/src/intersectionfilter.h0000644000015600001650000000267512642552153023742 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_INTERSECTIONFILTER_H #define HISTORY_INTERSECTIONFILTER_H #include "filter.h" #include "types.h" namespace History { class IntersectionFilterPrivate; // AND filter class IntersectionFilter : public Filter { Q_DECLARE_PRIVATE(IntersectionFilter) public: IntersectionFilter(); ~IntersectionFilter(); // copy related members IntersectionFilter(const Filter &other); IntersectionFilter& operator=(const Filter &other); void setFilters(const Filters &filters); void prepend(const Filter &filter); void append(const Filter &filter); void clear(); Filters filters() const; static Filter fromProperties(const QVariantMap &properties); }; } #endif history-service-0.1+16.04.20160104/src/thread.h0000644000015600001650000000432112642552153021263 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_THREAD_H #define HISTORY_THREAD_H #include #include #include #include #include #include "types.h" #include "event.h" #include "participant.h" namespace History { class ThreadPrivate; class ItemFactory; class Thread; typedef QList Threads; class Thread { Q_DECLARE_PRIVATE(Thread) friend class ItemFactory; public: explicit Thread(); Thread(const QString &accountId, const QString &threadId, EventType type, const Participants &participants, const Event &lastEvent = Event(), int count = 0, int unreadCount = 0, const Threads &groupedThreads = Threads()); Thread(const Thread &other); virtual ~Thread(); Thread& operator=(const Thread &other); QString accountId() const; QString threadId() const; EventType type() const; Participants participants() const; Event lastEvent() const; int count() const; int unreadCount() const; Threads groupedThreads() const; bool isNull() const; bool operator==(const Thread &other) const; bool operator<(const Thread &other) const; virtual QVariantMap properties() const; static Thread fromProperties(const QVariantMap &properties); protected: QSharedPointer d_ptr; }; const QDBusArgument &operator>>(const QDBusArgument &argument, Threads &threads); } #endif // HISTORY_THREAD_H history-service-0.1+16.04.20160104/src/PluginEventView0000644000015600001650000000015712642552153022664 0ustar pbuserpbgroup00000000000000#ifndef PLUGINEVENTVIEW_H #define PLUGINEVENTVIEW_H #include "plugineventview.h" #endif // PLUGINEVENTVIEW_H history-service-0.1+16.04.20160104/src/texteventattachment_p.h0000644000015600001650000000331312642552153024432 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_TEXT_EVENT_ATTACHMENT_P_H #define HISTORY_TEXT_EVENT_ATTACHMENT_P_H #include #include "types.h" namespace History { class TextEventAttachment; class TextEventAttachmentPrivate { public: explicit TextEventAttachmentPrivate(); TextEventAttachmentPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theAttachmentId, const QString &theContentType, const QString &theFilePath, const History::AttachmentFlags &theStatus); virtual ~TextEventAttachmentPrivate(); QString accountId; QString threadId; QString eventId; QString attachmentId; QString contentType; QString filePath; History::AttachmentFlags status; }; } #endif // HISTORY_TEXT_EVENT_ATTACHMENT_P_H history-service-0.1+16.04.20160104/src/event.cpp0000644000015600001650000001322512642552153021473 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "event.h" #include "event_p.h" namespace History { // ------------- EventPrivate ------------------------------------------------ EventPrivate::EventPrivate() { } EventPrivate::EventPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theSenderId, const QDateTime &theTimestamp, bool theNewEvent, const Participants &theParticipants) : accountId(theAccountId), threadId(theThreadId), eventId(theEventId), senderId(theSenderId), timestamp(theTimestamp), newEvent(theNewEvent), participants(theParticipants) { } EventPrivate::~EventPrivate() { } QVariantMap EventPrivate::properties() const { QVariantMap map; map[FieldAccountId] = accountId; map[FieldThreadId] = threadId; map[FieldEventId] = eventId; map[FieldSenderId] = senderId; map[FieldTimestamp] = timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz"); map[FieldDate] = timestamp.date().toString(Qt::ISODate); map[FieldNewEvent] = newEvent; map[FieldType] = type(); map[FieldParticipants] = participants.toVariantList(); return map; } // ------------- Event ------------------------------------------------------- /*! * \class Event * * \brief The Event class provides the base class for all events stored * and loaded from the history backends. * * This class should not be used directly and instead * the derived classes should be used. * * \sa TextEvent, VoiceEvent */ /*! * \brief Constructs an empty Event */ Event::Event() : d_ptr(new EventPrivate()) { } /*! * \brief Constructs an Event by copying the data from another one. * \param other The item to be copied; */ Event::Event(const Event &other) : d_ptr(other.d_ptr->clone()) { } /*! \internal * \brief Constructor to be used by derived classes to pass a EventPrivate instance * \param p The instance of the private class; */ Event::Event(EventPrivate &p) : d_ptr(&p) { } Event::~Event() { } /*! * \brief Assign operator for the Event class * \param other The event to be copied; */ Event& Event::operator=(const Event &other) { if (&other == this) { return *this; } d_ptr = QSharedPointer(other.d_ptr->clone()); } /*! * \brief Returns the account ID this event belongs to. */ QString Event::accountId() const { Q_D(const Event); return d->accountId; } /*! * \brief Returns the ID of the communication thread this event belongs to. * \sa HistoryThread */ QString Event::threadId() const { Q_D(const Event); return d->threadId; } /*! * \brief Returns the ID that uniquely identifies this event. */ QString Event::eventId() const { Q_D(const Event); return d->eventId; } /*! * \brief Returns the ID of the sender of this event. */ QString Event::senderId() const { Q_D(const Event); return d->senderId; } /*! * \brief Returns the timestamp of when the event happened. */ QDateTime Event::timestamp() const { Q_D(const Event); return d->timestamp; } /*! * \brief Returns whether the event is new (not yet seen by the user). * \sa Event::setNewEvent() */ bool Event::newEvent() const { Q_D(const Event); return d->newEvent; } /*! * \brief Set whether this event is new (not yet seen by the user). * \param value True if the event is new. False otherwise. * \sa Event::newEvent() */ void Event::setNewEvent(bool value) { Q_D(Event); d->newEvent = value; } /*! * \brief Returns the type of this event. */ EventType Event::type() const { Q_D(const Event); return d->type(); } Participants Event::participants() const { Q_D(const Event); return d->participants; } /*! * \brief Returns the event properties */ QVariantMap Event::properties() const { Q_D(const Event); return d->properties(); } /*! * \brief Return whether this event is a null event. */ bool Event::isNull() const { Q_D(const Event); return d->accountId.isNull() && d->threadId.isNull() && d->eventId.isNull(); } /*! * \brief Compare this event with another one. * \param other The other event; */ bool Event::operator==(const Event &other) const { Q_D(const Event); if (type() != other.type()) { return false; } if (d->accountId != other.d_ptr->accountId) { return false; } if (d->threadId != other.d_ptr->threadId) { return false; } if (d->eventId != other.d_ptr->eventId) { return false; } return true; } bool Event::operator!=(const Event &other) const { return !(*this == other); } bool Event::operator<(const Event &other) const { QString selfData = accountId() + threadId() + eventId(); QString otherData = other.accountId() + other.threadId() + other.eventId(); return selfData < otherData; } } history-service-0.1+16.04.20160104/src/PluginThreadView.xml0000644000015600001650000000265512642552153023616 0ustar pbuserpbgroup00000000000000 An interface to the history service ThreadView object. history-service-0.1+16.04.20160104/src/managerdbus_p.h0000644000015600001650000000573112642552153022631 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 MANAGERDBUS_P_H #define MANAGERDBUS_P_H #include #include #include "types.h" #include "event.h" #include "thread.h" class HistoryServiceAdaptor; namespace History { class ManagerDBus : public QObject { Q_OBJECT public: explicit ManagerDBus(QObject *parent = 0); Thread threadForParticipants(const QString &accountId, EventType type, const QStringList &participants, History::MatchFlags matchFlags, bool create); bool writeEvents(const History::Events &events); bool removeThreads(const Threads &threads); bool removeEvents(const Events &events); Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap()); Event getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId); Q_SIGNALS: // signals that will be triggered after processing bus signals void threadsAdded(const History::Threads &threads); void threadsModified(const History::Threads &threads); void threadsRemoved(const History::Threads &threads); void eventsAdded(const History::Events &events); void eventsModified(const History::Events &events); void eventsRemoved(const History::Events &events); protected Q_SLOTS: void onThreadsAdded(const QList &threads); void onThreadsModified(const QList &threads); void onThreadsRemoved(const QList &threads); void onEventsAdded(const QList &events); void onEventsModified(const QList &events); void onEventsRemoved(const QList &events); protected: Threads threadsFromProperties(const QList &threadsProperties); QList threadsToProperties(const Threads &threads); Event eventFromProperties(const QVariantMap &properties); Events eventsFromProperties(const QList &eventsProperties); QList eventsToProperties(const Events &events); private: HistoryServiceAdaptor *mAdaptor; QDBusInterface mInterface; }; } #endif // MANAGERDBUS_P_H history-service-0.1+16.04.20160104/src/unionfilter.h0000644000015600001650000000257112642552153022357 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_UNIONFILTER_H #define HISTORY_UNIONFILTER_H #include "filter.h" #include "types.h" namespace History { class UnionFilterPrivate; // OR filter class UnionFilter : public Filter { Q_DECLARE_PRIVATE(UnionFilter) public: UnionFilter(); ~UnionFilter(); // copy related members UnionFilter(const Filter &other); UnionFilter& operator=(const Filter &other); void setFilters(const Filters &filters); void prepend(const Filter &filter); void append(const Filter &filter); void clear(); Filters filters() const; static Filter fromProperties(const QVariantMap &properties); }; } #endif history-service-0.1+16.04.20160104/src/textevent_p.h0000644000015600001650000000366512642552153022373 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_TEXTEVENT_P_H #define HISTORY_TEXTEVENT_P_H #include "event_p.h" #include "types.h" namespace History { class TextEvent; class TextEventPrivate : public EventPrivate { public: TextEventPrivate(); TextEventPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theSender, const QDateTime &theTimestamp, bool theNewEvent, const QString &theMessage, MessageType theMessageType, MessageStatus theMessageStatus, const QDateTime &theReadTimestamp, const QString &theSubject, const TextEventAttachments &theAttachments, const Participants &theParticipants); ~TextEventPrivate(); QString message; MessageType messageType; MessageStatus messageStatus; QDateTime readTimestamp; QString subject; TextEventAttachments attachments; EventType type() const; QVariantMap properties() const; HISTORY_EVENT_DECLARE_CLONE(TextEvent) }; } #endif // HISTORY_TEXTEVENT_P_H history-service-0.1+16.04.20160104/src/types.h0000644000015600001650000001152012642552153021157 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 TYPES_H #define TYPES_H #include #include #define DefineSharedPointer(type) class type; typedef QSharedPointer type##Ptr; typedef QWeakPointer type##WeakPtr; typedef QList type##s; namespace History { DefineSharedPointer(EventView) DefineSharedPointer(Plugin) DefineSharedPointer(ThreadView) // enums enum EventType { EventTypeText, EventTypeVoice, EventTypeNull }; enum FilterType { FilterTypeStandard, FilterTypeIntersection, FilterTypeUnion }; enum MatchFlag { MatchCaseSensitive = 0x01, MatchCaseInsensitive = 0x02, MatchContains = 0x04, MatchPhoneNumber = 0x08 }; Q_DECLARE_FLAGS(MatchFlags, MatchFlag) enum MessageStatus { MessageStatusUnknown, MessageStatusDelivered, MessageStatusTemporarilyFailed, MessageStatusPermanentlyFailed, MessageStatusAccepted, MessageStatusRead, MessageStatusDeleted, MessageStatusPending // pending attachment download }; enum MessageType { MessageTypeText = 0, MessageTypeMultiPart = 1, MessageTypeInformation = 2 }; // FIXME (boiko): I think this needs to be changed to a simple enum and not flags, // as the statuses are mutually exclusive enum AttachmentFlag { AttachmentDownloaded = 0x01, AttachmentPending = 0x02, AttachmentError = 0x04 }; Q_DECLARE_FLAGS(AttachmentFlags, AttachmentFlag) // Event writing results enum EventWriteResult { EventWriteCreated, EventWriteModified, EventWriteError }; // dbus service, object path and interface static const char* DBusService = "com.canonical.HistoryService"; static const char* DBusObjectPath = "/com/canonical/HistoryService"; static const char* DBusInterface = "com.canonical.HistoryService"; static const char* ThreadViewInterface = "com.canonical.HistoryService.ThreadView"; static const char* EventViewInterface = "com.canonical.HistoryService.EventView"; // fields static const char* FieldAccountId = "accountId"; static const char* FieldThreadId = "threadId"; static const char* FieldEventId = "eventId"; static const char* FieldType = "type"; static const char* FieldParticipants = "participants"; static const char* FieldCount = "count"; static const char* FieldUnreadCount = "unreadCount"; static const char* FieldSenderId = "senderId"; static const char* FieldTimestamp = "timestamp"; static const char* FieldDate = "date"; static const char* FieldNewEvent = "newEvent"; // thread fields static const char* FieldLastEventId = "lastEventId"; static const char* FieldLastEventTimestamp = "lastEventTimestamp"; static const char* FieldGroupedThreads = "groupedThreads"; // text event fields static const char* FieldMessage = "message"; static const char* FieldMessageType = "messageType"; static const char* FieldMessageStatus = "messageStatus"; static const char* FieldReadTimestamp = "readTimestamp"; static const char* FieldSubject = "subject"; static const char* FieldAttachments = "attachments"; // text attachment fields static const char* FieldAttachmentId = "attachmentId"; static const char* FieldContentType = "contentType"; static const char* FieldFilePath = "filePath"; static const char* FieldStatus = "status"; // voice event fields static const char* FieldMissed = "missed"; static const char* FieldDuration = "duration"; static const char* FieldRemoteParticipant = "remoteParticipant"; // sort stuff static const char* FieldSortField = "sortField"; static const char* FieldSortOrder = "sortOrder"; static const char* FieldCaseSensitivity = "caseSensitivity"; // filter stuff static const char* FieldFilterType = "filterType"; static const char* FieldFilterProperty = "filterProperty"; static const char* FieldFilterValue = "filterValue"; static const char* FieldMatchFlags = "matchFlags"; static const char* FieldFilters = "filters"; static const char* FieldGroupingProperty = "groupingProperty"; // contact matching stuff static const char* FieldContactId = "contactId"; static const char* FieldAlias = "alias"; static const char* FieldAvatar = "avatar"; static const char* FieldIdentifier = "identifier"; static const char* FieldDetailProperties = "detailProperties"; } #endif // TYPES_H history-service-0.1+16.04.20160104/src/managerdbus.cpp0000644000015600001650000001567712642552153022657 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "managerdbus_p.h" #include "event.h" #include "manager.h" #include "thread.h" #include "textevent.h" #include "voiceevent.h" #include #include Q_DECLARE_METATYPE(QList< QVariantMap >) namespace History { ManagerDBus::ManagerDBus(QObject *parent) : QObject(parent), mAdaptor(0), mInterface(DBusService, DBusObjectPath, DBusInterface) { qDBusRegisterMetaType >(); qRegisterMetaType >(); // listen for signals coming from the bus QDBusConnection connection = QDBusConnection::sessionBus(); connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsAdded", this, SLOT(onThreadsAdded(QList))); connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsModified", this, SLOT(onThreadsModified(QList))); connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsRemoved", this, SLOT(onThreadsRemoved(QList))); connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsAdded", this, SLOT(onEventsAdded(QList))); connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsModified", this, SLOT(onEventsModified(QList))); connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsRemoved", this, SLOT(onEventsRemoved(QList))); } Thread ManagerDBus::threadForParticipants(const QString &accountId, EventType type, const QStringList &participants, MatchFlags matchFlags, bool create) { Thread thread; // FIXME: move to async call if possible QDBusReply reply = mInterface.call("ThreadForParticipants", accountId, (int) type, participants, (int)matchFlags, create); if (reply.isValid()) { QVariantMap properties = reply.value(); thread = Thread::fromProperties(properties); } return thread; } bool ManagerDBus::writeEvents(const Events &events) { QList eventMap = eventsToProperties(events); if (eventMap.isEmpty()) { return false; } QDBusReply reply = mInterface.call("WriteEvents", QVariant::fromValue(eventMap)); if (!reply.isValid()) { return false; } return reply.value(); } bool ManagerDBus::removeThreads(const Threads &threads) { QList threadMap = threadsToProperties(threads); if (threadMap.isEmpty()) { return false; } QDBusReply reply = mInterface.call("RemoveThreads", QVariant::fromValue(threadMap)); if (!reply.isValid()) { return false; } return reply.value(); } bool ManagerDBus::removeEvents(const Events &events) { QList eventMap = eventsToProperties(events); if (eventMap.isEmpty()) { return false; } QDBusReply reply = mInterface.call("RemoveEvents", QVariant::fromValue(eventMap)); if (!reply.isValid()) { return false; } return reply.value(); } Thread ManagerDBus::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties) { Thread thread; QDBusReply reply = mInterface.call("GetSingleThread", (int)type, accountId, threadId, properties); if (!reply.isValid()) { return thread; } thread = Thread::fromProperties(reply.value()); return thread; } Event ManagerDBus::getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId) { Event event; QDBusReply reply = mInterface.call("GetSingleEvent", (int)type, accountId, threadId, eventId); if (!reply.isValid()) { return event; } event = eventFromProperties(reply.value()); return event; } void ManagerDBus::onThreadsAdded(const QList &threads) { Q_EMIT threadsAdded(threadsFromProperties(threads)); } void ManagerDBus::onThreadsModified(const QList &threads) { Q_EMIT threadsModified(threadsFromProperties(threads)); } void ManagerDBus::onThreadsRemoved(const QList &threads) { Q_EMIT threadsRemoved(threadsFromProperties(threads)); } void ManagerDBus::onEventsAdded(const QList &events) { Q_EMIT eventsAdded(eventsFromProperties(events)); } void ManagerDBus::onEventsModified(const QList &events) { Q_EMIT eventsModified(eventsFromProperties(events)); } void ManagerDBus::onEventsRemoved(const QList &events) { Q_EMIT eventsRemoved(eventsFromProperties(events)); } Threads ManagerDBus::threadsFromProperties(const QList &threadsProperties) { Threads threads; Q_FOREACH(const QVariantMap &map, threadsProperties) { Thread thread = Thread::fromProperties(map); if (!thread.isNull()) { threads << thread; } } return threads; } QList ManagerDBus::threadsToProperties(const Threads &threads) { QList threadsPropertyMap; Q_FOREACH(const Thread &thread, threads) { threadsPropertyMap << thread.properties(); } return threadsPropertyMap; } Event ManagerDBus::eventFromProperties(const QVariantMap &properties) { EventType type = (EventType)properties[FieldType].toInt(); switch (type) { case EventTypeText: return TextEvent::fromProperties(properties); case EventTypeVoice: return VoiceEvent::fromProperties(properties); } } Events ManagerDBus::eventsFromProperties(const QList &eventsProperties) { Events events; Q_FOREACH(const QVariantMap &map, eventsProperties) { events << eventFromProperties(map); } return events; } QList ManagerDBus::eventsToProperties(const Events &events) { QList eventsPropertyMap; Q_FOREACH(const Event &event, events) { eventsPropertyMap << event.properties(); } return eventsPropertyMap; } } history-service-0.1+16.04.20160104/src/eventview_p.h0000644000015600001650000000315612642552153022354 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 EVENTVIEW_P_H #define EVENTVIEW_P_H #include "types.h" #include "filter.h" #include "sort.h" #include namespace History { class EventView; class EventViewPrivate { Q_DECLARE_PUBLIC(EventView) public: EventViewPrivate(History::EventType theType, const History::Sort &theSort, const History::Filter &theFilter); EventType type; Sort sort; Filter filter; QString objectPath; bool valid; QDBusInterface *dbus; Events filteredEvents(const Events &events); // private slots void _d_eventsAdded(const History::Events &events); void _d_eventsModified(const History::Events &events); void _d_eventsRemoved(const History::Events &events); EventView *q_ptr; }; } #endif // EVENTVIEW_P_H history-service-0.1+16.04.20160104/src/channelobserver_p.h0000644000015600001650000000431612642552153023517 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 CHANNELOBSERVER_H #define CHANNELOBSERVER_H #include #include #include #include namespace History { class ChannelObserver : public QObject, public Tp::AbstractClientObserver { Q_OBJECT public: explicit ChannelObserver(QObject *parent = 0); Tp::ChannelClassSpecList channelFilters() const; void observeChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList &channels, const Tp::ChannelDispatchOperationPtr &dispatchOperation, const QList &requestsSatisfied, const Tp::AbstractClientObserver::ObserverInfo &observerInfo); Q_SIGNALS: void textChannelAvailable(const Tp::TextChannelPtr &channel); void callChannelAvailable(const Tp::CallChannelPtr &channel); protected Q_SLOTS: void onChannelInvalidated(); void onCallChannelReady(Tp::PendingOperation *op); void onTextChannelReady(Tp::PendingOperation *op); protected: void checkContextFinished(Tp::Channel *channel); private: QMap > mContexts; QMap mReadyMap; QList mChannels; }; } #endif // CHANNELOBSERVER_H history-service-0.1+16.04.20160104/src/Sort0000644000015600001650000000002212642552153020507 0ustar pbuserpbgroup00000000000000#include "sort.h" history-service-0.1+16.04.20160104/src/CMakeLists.txt0000644000015600001650000000526612642552153022414 0ustar pbuserpbgroup00000000000000set(library_SRCS channelobserver.cpp contactmatcher.cpp event.cpp eventview.cpp filter.cpp intersectionfilter.cpp manager.cpp managerdbus.cpp participant.cpp phoneutils.cpp pluginthreadview.cpp plugineventview.cpp sort.cpp telepathyhelper.cpp textevent.cpp texteventattachment.cpp thread.cpp threadview.cpp unionfilter.cpp utils.cpp voiceevent.cpp ) set(library_HDRS Event event.h EventView eventview.h Filter filter.h IntersectionFilter intersectionfilter.h Manager manager.h Participant participant.h Plugin plugin.h PluginThreadView pluginthreadview.h PluginEventView plugineventview.h Sort sort.h TextEvent textevent.h TextEventAttachment texteventattachment.h Thread thread.h ThreadView threadview.h Types types.h UnionFilter unionfilter.h VoiceEvent voiceevent.h ) set(library_PRIV_HDRS channelobserver_p.h contactmatcher_p.h event_p.h eventview_p.h filter_p.h intersectionfilter_p.h manager_p.h managerdbus_p.h participant_p.h phoneutils_p.h pluginthreadview_p.h plugineventview_p.h sort_p.h telepathyhelper_p.h textevent_p.h texteventattachment_p.h thread_p.h threadview_p.h unionfilter_p.h utils_p.h voiceevent_p.h ) qt5_add_dbus_adaptor(library_SRCS PluginThreadView.xml pluginthreadview.h History::PluginThreadView) qt5_add_dbus_adaptor(library_SRCS PluginEventView.xml plugineventview.h History::PluginEventView) include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR} ${LibPhoneNumber_INCLUDE_DIRS} ${TP_QT5_INCLUDE_DIRS}) add_library(historyservice SHARED ${library_SRCS} ${library_HDRS} ${library_PRIV_HDRS}) # Set the library version and the SOVERSION set_target_properties(historyservice PROPERTIES SOVERSION ${HISTORY_VERSION_MAJOR} VERSION ${HISTORY_VERSION_MAJOR}.${HISTORY_VERSION_MINOR}.${HISTORY_VERSION_PATCH}) target_link_libraries(historyservice ${LibPhoneNumber_LIBRARIES} ${TP_QT5_LIBRARIES}) qt5_use_modules(historyservice Contacts Core DBus) install(TARGETS historyservice DESTINATION ${CMAKE_INSTALL_LIBDIR}) set(INCLUDE_INSTALL_DIR include/history-service) install(FILES ${library_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/History) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/history-service.pc.in ${CMAKE_CURRENT_BINARY_DIR}/history-service.pc) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/history-service.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT pkgconfig) history-service-0.1+16.04.20160104/src/manager.cpp0000644000015600001650000001220212642552153021756 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "manager.h" #include "manager_p.h" #include "managerdbus_p.h" #include "eventview.h" #include "intersectionfilter.h" #include "textevent.h" #include "thread.h" #include "threadview.h" #include "voiceevent.h" #include #include #include #include namespace History { // ------------- ManagerPrivate ------------------------------------------------ ManagerPrivate::ManagerPrivate() : dbus(new ManagerDBus()), serviceWatcher(DBusService, QDBusConnection::sessionBus()) { } ManagerPrivate::~ManagerPrivate() { } // ------------- Manager ------------------------------------------------------- Manager::Manager() : d_ptr(new ManagerPrivate()) { Q_D(Manager); // Propagate the signals from the event watcher connect(d->dbus.data(), SIGNAL(threadsAdded(History::Threads)), SIGNAL(threadsAdded(History::Threads))); connect(d->dbus.data(), SIGNAL(threadsModified(History::Threads)), SIGNAL(threadsModified(History::Threads))); connect(d->dbus.data(), SIGNAL(threadsRemoved(History::Threads)), SIGNAL(threadsRemoved(History::Threads))); connect(d->dbus.data(), SIGNAL(eventsAdded(History::Events)), SIGNAL(eventsAdded(History::Events))); connect(d->dbus.data(), SIGNAL(eventsModified(History::Events)), SIGNAL(eventsModified(History::Events))); connect(d->dbus.data(), SIGNAL(eventsRemoved(History::Events)), SIGNAL(eventsRemoved(History::Events))); // watch for the service going up and down connect(&d->serviceWatcher, &QDBusServiceWatcher::serviceRegistered, [&](const QString &serviceName) { qDebug() << "HistoryService: service registered:" << serviceName; this->d_ptr->serviceRunning = true; Q_EMIT this->serviceRunningChanged(); }); connect(&d->serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, [&](const QString &serviceName) { qDebug() << "HistoryService: service unregistered:" << serviceName; this->d_ptr->serviceRunning = false; Q_EMIT this->serviceRunningChanged(); }); // and fetch the current status d->serviceRunning = false; QDBusReply reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(DBusService); if (reply.isValid()) { d->serviceRunning = reply.value(); } } Manager::~Manager() { } Manager *Manager::instance() { static Manager *self = new Manager(); return self; } ThreadViewPtr Manager::queryThreads(EventType type, const Sort &sort, const Filter &filter, const QVariantMap &properties) { return ThreadViewPtr(new ThreadView(type, sort, filter, properties)); } EventViewPtr Manager::queryEvents(EventType type, const Sort &sort, const Filter &filter) { return EventViewPtr(new EventView(type, sort, filter)); } Event Manager::getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId) { Q_D(Manager); Event event = d->dbus->getSingleEvent(type, accountId, threadId, eventId); return event; } Thread Manager::threadForParticipants(const QString &accountId, EventType type, const QStringList &participants, MatchFlags matchFlags, bool create) { Q_D(Manager); return d->dbus->threadForParticipants(accountId, type, participants, matchFlags, create); } Thread Manager::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties) { Q_D(Manager); Thread thread = d->dbus->getSingleThread(type, accountId, threadId, properties); return thread; } bool Manager::writeEvents(const Events &events) { Q_D(Manager); return d->dbus->writeEvents(events); } bool Manager::removeThreads(const Threads &threads) { Q_D(Manager); return d->dbus->removeThreads(threads); } bool Manager::removeEvents(const Events &events) { Q_D(Manager); return d->dbus->removeEvents(events); } bool Manager::isServiceRunning() const { Q_D(const Manager); return d->serviceRunning; } } history-service-0.1+16.04.20160104/src/pluginthreadview.h0000644000015600001650000000272212642552153023400 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 PLUGINTHREADVIEW_H #define PLUGINTHREADVIEW_H #include #include #include #include namespace History { class PluginThreadViewPrivate; class PluginThreadView : public QObject, public QDBusContext { Q_OBJECT Q_DECLARE_PRIVATE(PluginThreadView) public: explicit PluginThreadView(QObject *parent = 0); virtual ~PluginThreadView(); // DBus exposed methods Q_NOREPLY void Destroy(); virtual QList NextPage() = 0; virtual bool IsValid() const; // other methods QString objectPath() const; Q_SIGNALS: void Invalidated(); private: QScopedPointer d_ptr; }; } #endif // PLUGINTHREADVIEW_H history-service-0.1+16.04.20160104/src/plugineventview_p.h0000644000015600001650000000202512642552153023565 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 PLUGINEVENTVIEW_P_H #define PLUGINEVENTVIEW_P_H #include class EventViewAdaptor; namespace History { class PluginEventViewPrivate { public: PluginEventViewPrivate(); EventViewAdaptor *adaptor; QString objectPath; }; } #endif // PLUGINEVENTVIEW_P_H history-service-0.1+16.04.20160104/src/manager.h0000644000015600001650000000532012642552153021426 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_MANAGER_H #define HISTORY_MANAGER_H #include #include #include "types.h" #include "event.h" #include "filter.h" #include "sort.h" #include "thread.h" namespace History { class ManagerPrivate; class Manager : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(Manager) public: ~Manager(); static Manager *instance(); ThreadViewPtr queryThreads(EventType type, const Sort &sort = Sort(), const Filter &filter = Filter(), const QVariantMap &properties = QVariantMap()); EventViewPtr queryEvents(EventType type, const Sort &sort = Sort(), const Filter &filter = Filter()); Event getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId); Thread threadForParticipants(const QString &accountId, EventType type, const QStringList &participants, History::MatchFlags matchFlags = History::MatchCaseSensitive, bool create = false); Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap()); bool writeEvents(const History::Events &events); bool removeThreads(const Threads &threads); bool removeEvents(const Events &events); bool isServiceRunning() const; Q_SIGNALS: void threadsAdded(const History::Threads &threads); void threadsModified(const History::Threads &threads); void threadsRemoved(const History::Threads &threads); void eventsAdded(const History::Events &events); void eventsModified(const History::Events &events); void eventsRemoved(const History::Events &events); void serviceRunningChanged(); private: Manager(); QScopedPointer d_ptr; }; } #endif history-service-0.1+16.04.20160104/src/participant.h0000644000015600001650000000472312642552153022340 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_PARTICIPANT_H #define HISTORY_PARTICIPANT_H #include #include #include #include #include #include namespace History { class ParticipantPrivate; class Participant { Q_DECLARE_PRIVATE(Participant) public: explicit Participant(); Participant(const QString &accountId, const QString &identifier, const QString &contactId = QString::null, const QString &alias = QString::null, const QString &avatar = QString::null, const QVariantMap &detailProperties = QVariantMap()); Participant(const Participant &other); Participant& operator=(const Participant &other); virtual ~Participant(); QString accountId() const; QString identifier() const; QString contactId() const; QString alias() const; QString avatar() const; QVariantMap detailProperties() const; bool isNull() const; bool operator==(const Participant &other) const; bool operator<(const Participant &other) const; virtual QVariantMap properties() const; static Participant fromProperties(const QVariantMap &properties); protected: QSharedPointer d_ptr; }; // define the participants list with toVariantList() and fromVariantList() helpers class Participants : public QList { public: QStringList identifiers() const; static Participants fromVariant(const QVariant &variant); static Participants fromVariantList(const QVariantList &list); QVariantList toVariantList() const; }; const QDBusArgument &operator>>(const QDBusArgument &argument, Participants &participants); } #endif // HISTORY_PARTICIPANT_H history-service-0.1+16.04.20160104/src/sort.h0000644000015600001650000000321012642552153020777 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_SORT_H #define HISTORY_SORT_H #include #include #include #include #include "types.h" namespace History { class SortPrivate; class Sort { Q_DECLARE_PRIVATE(Sort) public: Sort(const QString &sortField = FieldTimestamp, Qt::SortOrder sortOrder = Qt::AscendingOrder, Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive); Sort(const Sort &other); ~Sort(); QString sortField() const; void setSortField(const QString &value); Qt::SortOrder sortOrder() const; void setSortOrder(Qt::SortOrder value); Qt::CaseSensitivity caseSensitivity() const; void setCaseSensitivity(Qt::CaseSensitivity value); QVariantMap properties() const; static Sort fromProperties(const QVariantMap &properties); protected: QSharedPointer d_ptr; }; } #endif // HISTORY_SORT_H history-service-0.1+16.04.20160104/src/plugineventview.h0000644000015600001650000000271112642552153023250 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 PLUGINEVENTVIEW_H #define PLUGINEVENTVIEW_H #include #include #include #include namespace History { class PluginEventViewPrivate; class PluginEventView : public QObject, public QDBusContext { Q_OBJECT Q_DECLARE_PRIVATE(PluginEventView) public: explicit PluginEventView(QObject *parent = 0); virtual ~PluginEventView(); // DBus exposed methods Q_NOREPLY void Destroy(); virtual QList NextPage() = 0; virtual bool IsValid() const; // other methods QString objectPath() const; Q_SIGNALS: void Invalidated(); private: QScopedPointer d_ptr; }; } #endif // PLUGINEVENTVIEW_H history-service-0.1+16.04.20160104/src/texteventattachment.cpp0000644000015600001650000001356512642552153024460 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "texteventattachment.h" #include "texteventattachment_p.h" namespace History { // ------------- TextEventAttachmentPrivate ------------------------------------------------ TextEventAttachmentPrivate::TextEventAttachmentPrivate() { } TextEventAttachmentPrivate::TextEventAttachmentPrivate(const QString &theAccountId, const QString &theThreadId, const QString &theEventId, const QString &theAttachmentId, const QString &theContentType, const QString &theFilePath, const AttachmentFlags &theStatus) : accountId(theAccountId), threadId(theThreadId), eventId(theEventId), attachmentId(theAttachmentId), contentType(theContentType), filePath(theFilePath), status(theStatus) { } TextEventAttachmentPrivate::~TextEventAttachmentPrivate() { } // ------------- TextEventAttachment ------------------------------------------------------ /*! * \class TextEventAttachment * * \brief The TextEventAttachment class provides a way to store a single attachment * belonging to a text event. */ TextEventAttachment::TextEventAttachment() : d_ptr(new TextEventAttachmentPrivate()) { } TextEventAttachment::TextEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, const QString &attachmentId, const QString &contentType, const QString &filePath, const AttachmentFlags &status) : d_ptr(new TextEventAttachmentPrivate(accountId, threadId, eventId, attachmentId, contentType, filePath, status)) { } TextEventAttachment::TextEventAttachment(const TextEventAttachment &other) : d_ptr(new TextEventAttachmentPrivate(*other.d_ptr)) { } TextEventAttachment::~TextEventAttachment() { } TextEventAttachment& TextEventAttachment::operator=(const TextEventAttachment &other) { d_ptr = QSharedPointer(new TextEventAttachmentPrivate(*other.d_ptr)); } /*! * \brief Returns the account ID this attachment belongs to. */ QString TextEventAttachment::accountId() const { Q_D(const TextEventAttachment); return d->accountId; } /*! * \brief Returns the thread ID this attachment belongs to. */ QString TextEventAttachment::threadId() const { Q_D(const TextEventAttachment); return d->threadId; } /*! * \brief Returns the event ID this attachment belongs to. */ QString TextEventAttachment::eventId() const { Q_D(const TextEventAttachment); return d->eventId; } /*! * \brief Returns the attachment ID */ QString TextEventAttachment::attachmentId() const { Q_D(const TextEventAttachment); return d->attachmentId; } /*! * \brief Returns the content type of this attachment */ QString TextEventAttachment::contentType() const { Q_D(const TextEventAttachment); return d->contentType; } /*! * \brief Returns the file path of this attachment */ QString TextEventAttachment::filePath() const { Q_D(const TextEventAttachment); return d->filePath; } /*! * \brief Returns the status of this attachment */ AttachmentFlags TextEventAttachment::status() const { Q_D(const TextEventAttachment); return d->status; } QVariantMap TextEventAttachment::properties() const { Q_D(const TextEventAttachment); QVariantMap map; map[FieldAccountId] = d->accountId; map[FieldThreadId] = d->threadId; map[FieldEventId] = d->eventId; map[FieldAttachmentId] = d->attachmentId; map[FieldContentType] = d->contentType; map[FieldFilePath] = d->filePath; map[FieldStatus] = (int)d->status; return map; } TextEventAttachment TextEventAttachment::fromProperties(const QVariantMap &properties) { TextEventAttachment attachment; if (properties.isEmpty()) { return attachment; } attachment = TextEventAttachment(properties[FieldAccountId].toString(), properties[FieldThreadId].toString(), properties[FieldEventId].toString(), properties[FieldAttachmentId].toString(), properties[FieldContentType].toString(), properties[FieldFilePath].toString(), (History::AttachmentFlags)properties[FieldStatus].toInt()); return attachment; } bool TextEventAttachment::isNull() const { Q_D(const TextEventAttachment); return d->accountId.isNull() && d->threadId.isNull() && d->eventId.isNull() && d->attachmentId.isNull(); } bool TextEventAttachment::operator==(const TextEventAttachment &other) { Q_D(TextEventAttachment); if (d->accountId != other.d_ptr->accountId) { return false; } if (d->threadId != other.d_ptr->threadId) { return false; } if (d->eventId != other.d_ptr->eventId) { return false; } if (d->attachmentId != other.d_ptr->attachmentId) { return false; } return true; } } history-service-0.1+16.04.20160104/src/Filter0000644000015600001650000000002412642552153021007 0ustar pbuserpbgroup00000000000000#include "filter.h" history-service-0.1+16.04.20160104/src/sort.cpp0000644000015600001650000000535412642552153021345 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "sort.h" #include "sort_p.h" namespace History { // ------------- SortPrivate ------------------------------------------------ SortPrivate::SortPrivate(const QString &theSortField, Qt::SortOrder theSortOrder, Qt::CaseSensitivity theCaseSensitivity) : sortField(theSortField), sortOrder(theSortOrder), caseSensitivity(theCaseSensitivity) { } SortPrivate::~SortPrivate() { } // ------------- Sort ------------------------------------------------------- Sort::Sort(const QString &sortField, Qt::SortOrder sortOrder, Qt::CaseSensitivity caseSensitivity) : d_ptr(new SortPrivate(sortField, sortOrder, caseSensitivity)) { } Sort::Sort(const Sort &other) : d_ptr(new SortPrivate(*other.d_ptr)) { } Sort::~Sort() { } QString Sort::sortField() const { Q_D(const Sort); return d->sortField; } void Sort::setSortField(const QString &value) { Q_D(Sort); d->sortField = value; } Qt::SortOrder Sort::sortOrder() const { Q_D(const Sort); return d->sortOrder; } void Sort::setSortOrder(Qt::SortOrder value) { Q_D(Sort); d->sortOrder = value; } Qt::CaseSensitivity Sort::caseSensitivity() const { Q_D(const Sort); return d->caseSensitivity; } void Sort::setCaseSensitivity(Qt::CaseSensitivity value) { Q_D(Sort); d->caseSensitivity = value; } QVariantMap Sort::properties() const { Q_D(const Sort); QVariantMap map; map[FieldSortField] = d->sortField; map[FieldSortOrder] = (int)d->sortOrder; map[FieldCaseSensitivity] = (int)d->caseSensitivity; return map; } Sort Sort::fromProperties(const QVariantMap &properties) { Sort sort; if (properties.isEmpty()) { return sort; } sort = Sort(properties[FieldSortField].toString(), (Qt::SortOrder) properties[FieldSortOrder].toInt(), (Qt::CaseSensitivity) properties[FieldCaseSensitivity].toInt()); return sort; } } history-service-0.1+16.04.20160104/tools/0000755000015600001650000000000012642552414020214 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tools/reader/0000755000015600001650000000000012642552414021456 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tools/reader/main.cpp0000644000015600001650000001051112642552153023104 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "manager.h" #include "eventview.h" #include "filter.h" #include "intersectionfilter.h" #include "thread.h" #include "threadview.h" #include "textevent.h" #include "voiceevent.h" #include #include void printEvent(const History::Event &event) { QString extraInfo; History::TextEvent textEvent; History::VoiceEvent voiceEvent; switch (event.type()) { case History::EventTypeText: textEvent = event; extraInfo = QString("message: %1").arg(textEvent.message()); break; case History::EventTypeVoice: voiceEvent = event; extraInfo = QString("missed: %1\n duration: %2").arg(voiceEvent.missed() ? "yes" : "no", voiceEvent.duration().toString()); break; } qDebug() << qPrintable(QString(" * Event: accountId: %1\n threadId: %2\n eventId: %3\n senderId: %4\n timestamp: %5\n newEvent: %6") .arg(event.accountId(), event.threadId(), event.eventId(), event.senderId(), event.timestamp().toString(), event.newEvent() ? "yes" : "no")); qDebug() << qPrintable(QString(" %1").arg(extraInfo)); } void printThread(const History::Thread &thread) { QString type = "Unknown"; switch (thread.type()) { case History::EventTypeText: type = "Text"; break; case History::EventTypeVoice: type = "Voice"; break; } qDebug() << qPrintable(QString("- %1 thread - accountId: %2 threadId: %3 count: %4 unreadCount: %5").arg(type, thread.accountId(), thread.threadId(), QString::number(thread.count()), QString::number(thread.unreadCount()))); qDebug() << qPrintable(QString(" Participants: %1").arg(thread.participants().identifiers().join(", "))); if (!thread.lastEvent().isNull()) { qDebug() << " Last event:"; printEvent(thread.lastEvent()); } qDebug() << " All events:"; } int main(int argc, char **argv) { QCoreApplication app(argc, argv); History::Manager *manager = History::Manager::instance(); QList eventTypes; eventTypes << History::EventTypeText << History::EventTypeVoice; Q_FOREACH(History::EventType type, eventTypes) { History::ThreadViewPtr view = manager->queryThreads(type); History::Threads threads = view->nextPage(); while (!threads.isEmpty()) { Q_FOREACH(const History::Thread &thread, threads) { printThread(thread); // now print the events for this thread History::IntersectionFilter filter; filter.append(History::Filter(History::FieldThreadId, thread.threadId())); filter.append(History::Filter(History::FieldAccountId, thread.accountId())); History::EventViewPtr eventView = manager->queryEvents(type, History::Sort(), filter); History::Events events = eventView->nextPage(); while (!events.isEmpty()) { Q_FOREACH(const History::Event &event, events) { printEvent(event); } events = eventView->nextPage(); } } threads = view->nextPage(); } } } history-service-0.1+16.04.20160104/tools/reader/CMakeLists.txt0000644000015600001650000000033412642552153024216 0ustar pbuserpbgroup00000000000000set(reader_SRCS main.cpp) include_directories( ${CMAKE_SOURCE_DIR}/src ) add_executable(history-reader ${reader_SRCS}) qt5_use_modules(history-reader Core) target_link_libraries(history-reader historyservice) history-service-0.1+16.04.20160104/tools/makevoiceevents/0000755000015600001650000000000012642552414023404 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tools/makevoiceevents/main.cpp0000644000015600001650000000571612642552153025045 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * 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 . * * Authored by Kyle Nitzsche * */ #include "manager.h" #include "types.h" #include "voiceevent.h" #include #include #include #include using namespace History; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QStringList args = QCoreApplication::arguments(); if (args.size() == 1) { qDebug() << "Usage: makevoiceevents accountId"; return 1; } //TODO let the user pass numbers as args so they can corrspond to //the user's contacts QString number0 = "1234567890"; QString number1 = "0987654321"; QString number2 = "3216540987"; QString number; int idx = 0; int control = 0; const QString accountId = args.at(1); QString sender; QDateTime datetime = QDateTime::currentDateTime(); while (idx < 50) { QList participants_; idx ++; bool missed; if (control == 0) { qDebug() << "=== control is 0"; number = number0; control++; missed = false; sender = "self"; } else if (control == 1) { qDebug() << "=== control is 1"; number = number1; control++; missed = true; sender = number; } else { qDebug() << "=== control is else"; control = 0; number = number2; datetime = datetime.addDays(-1); missed = false; sender = number; } Thread thread = Manager::instance()->threadForParticipants(accountId, EventTypeVoice, QStringList() << number, MatchCaseSensitive, true); const QString eventId = QString::number(QDateTime::currentMSecsSinceEpoch()); bool newEvent = true; const QTime duration = QTime(0,10,20); VoiceEvent ev(accountId, thread.threadId(), eventId, sender, datetime, newEvent, missed, duration, number, thread.participants()); QList events; events.append(ev); Manager::instance()->writeEvents(events); } } history-service-0.1+16.04.20160104/tools/makevoiceevents/CMakeLists.txt0000644000015600001650000000053612642552153026150 0ustar pbuserpbgroup00000000000000set(makevoiceevents_SRCS main.cpp) include_directories( ${CMAKE_SOURCE_DIR}/src ) add_executable(history-makevoiceevents ${makevoiceevents_SRCS}) qt5_use_modules(history-makevoiceevents Core) target_link_libraries(history-makevoiceevents historyservice) install(TARGETS history-makevoiceevents RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) history-service-0.1+16.04.20160104/tools/CMakeLists.txt0000644000015600001650000000013412642552153022752 0ustar pbuserpbgroup00000000000000add_subdirectory(reader) add_subdirectory(maketextevents) add_subdirectory(makevoiceevents) history-service-0.1+16.04.20160104/tools/maketextevents/0000755000015600001650000000000012642552414023263 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/tools/maketextevents/main.cpp0000644000015600001650000000603212642552153024714 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * 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 . * * Authored by Kyle Nitzsche * */ #include "manager.h" #include "types.h" #include "textevent.h" #include "texteventattachment.h" #include #include #include #include using namespace History; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QStringList args = QCoreApplication::arguments(); if (args.size() == 1) { qDebug() << "Usage: maketextevents accountId"; return 1; } //TODO let the user pass numbers as args so they can corrspond to //the user's contacts QString number0 = "1234567890"; QString number1 = "01234456789"; QString number2 = "0987654321"; QString number; int idx = 0; QDateTime datetime = QDateTime::currentDateTime(); int control = 0; while (idx < 50) { idx ++; if (control == 0) { qDebug() << "=== control is 0"; number = number0; control++; } else if (control == 1) { qDebug() << "=== control is 1"; number = number1; control++; } else { qDebug() << "=== control is else"; control = 0; number = number2; datetime = datetime.addDays(-1); } const QString accountId = args.at(1); Thread thread = Manager::instance()->threadForParticipants(accountId, EventTypeText, QStringList() << number, MatchCaseSensitive, true); const QString threadId = thread.threadId(); const QString eventId = QString::number(QDateTime::currentMSecsSinceEpoch()); const QString sender = number; TextEvent ev(accountId, threadId, eventId, sender, datetime, true, QString::fromStdString("the text message"), MessageTypeText, MessageStatusUnknown, datetime, QString::fromStdString("the subject")); QList events; events.append(ev); Manager::instance()->writeEvents(events); } } history-service-0.1+16.04.20160104/tools/maketextevents/CMakeLists.txt0000644000015600001650000000053012642552153026021 0ustar pbuserpbgroup00000000000000set(maketextevents_SRCS main.cpp) include_directories( ${CMAKE_SOURCE_DIR}/src ) add_executable(history-maketextevents ${maketextevents_SRCS}) qt5_use_modules(history-maketextevents Core) target_link_libraries(history-maketextevents historyservice) install(TARGETS history-maketextevents RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) history-service-0.1+16.04.20160104/config.h.in0000644000015600001650000000141512642552153021100 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This file is part of phone-app. * * phone-app 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; version 3. * * phone-app 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 CONFIG_H #define CONFIG_H #define HISTORY_PLUGIN_PATH "@CMAKE_INSTALL_PREFIX@/@HISTORY_PLUGIN_PATH@" #endif history-service-0.1+16.04.20160104/COPYING0000644000015600001650000010451312642552153020113 0ustar pbuserpbgroup00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . history-service-0.1+16.04.20160104/Ubuntu/0000755000015600001650000000000012642552414020336 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/Ubuntu/History/0000755000015600001650000000000012642552414021777 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlintersectionfilter.h0000644000015600001650000000220012642552153030212 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYQMLINTERSECTIONFILTER_H #define HISTORYQMLINTERSECTIONFILTER_H #include "historyqmlfilter.h" #include "types.h" #include "intersectionfilter.h" class HistoryQmlIntersectionFilter : public HistoryQmlCompoundFilter { Q_OBJECT public: explicit HistoryQmlIntersectionFilter(QObject *parent = 0); History::Filter filter() const; }; #endif // HISTORYQMLINTERSECTIONFILTER_H history-service-0.1+16.04.20160104/Ubuntu/History/historygroupedthreadsmodel.h0000644000015600001650000000545512642552153027644 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYGROUPEDTHREADSMODEL_H #define HISTORYGROUPEDTHREADSMODEL_H #include "historythreadmodel.h" #include class HistoryThreadGroup { public: History::Thread displayedThread; History::Threads threads; bool operator==(const HistoryThreadGroup &other) const; }; typedef QList HistoryThreadGroupList; class HistoryGroupedThreadsModel : public HistoryThreadModel { Q_OBJECT Q_PROPERTY(QString groupingProperty READ groupingProperty WRITE setGroupingProperty NOTIFY groupingPropertyChanged) Q_ENUMS(CustomRoles) public: enum CustomRoles { ThreadsRole = LastThreadRole }; explicit HistoryGroupedThreadsModel(QObject *parent = 0); QString groupingProperty() const; void setGroupingProperty(const QString &value); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); virtual QHash roleNames() const; Q_INVOKABLE QVariant get(int row) const; Q_SIGNALS: void groupingPropertyChanged(); protected: int existingPositionForEntry(const History::Thread &thread) const; void removeGroup(const HistoryThreadGroup &group); void updateDisplayedThread(HistoryThreadGroup &group); protected Q_SLOTS: virtual void updateQuery(); virtual void onThreadsAdded(const History::Threads &threads); virtual void onThreadsModified(const History::Threads &threads); virtual void onThreadsRemoved(const History::Threads &threads); private Q_SLOTS: void processThreadGrouping(const History::Thread &thread); void removeThreadFromGroup(const History::Thread &thread); void markGroupAsChanged(const HistoryThreadGroup &group); void notifyDataChanged(); private: QString mGroupingProperty; HistoryThreadGroupList mGroups; QList mChangedGroups; QHash mRoles; }; #endif // HISTORYGROUPEDTHREADSMODEL_H history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlunionfilter.cpp0000644000015600001650000000217112642552153027176 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historyqmlunionfilter.h" #include "unionfilter.h" HistoryQmlUnionFilter::HistoryQmlUnionFilter(QObject *parent) : HistoryQmlCompoundFilter(parent) { } History::Filter HistoryQmlUnionFilter::filter() const { History::UnionFilter unionFilter; Q_FOREACH(HistoryQmlFilter *filter, mFilters) { unionFilter.append(filter->filter()); } return unionFilter; } history-service-0.1+16.04.20160104/Ubuntu/History/historythreadmodel.h0000644000015600001650000000521712642552153026067 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYTHREADMODEL_H #define HISTORYTHREADMODEL_H #include "historymodel.h" #include "types.h" #include "textevent.h" #include "thread.h" class HistoryQmlFilter; class HistoryQmlSort; class HistoryThreadModel : public HistoryModel { Q_OBJECT Q_ENUMS(ThreadRole) public: enum ThreadRole { CountRole = HistoryModel::LastRole, UnreadCountRole, LastEventIdRole, LastEventSenderIdRole, LastEventTimestampRole, LastEventDateRole, LastEventNewRole, LastEventTextMessageRole, LastEventTextMessageTypeRole, LastEventTextMessageStatusRole, LastEventTextReadTimestampRole, LastEventTextSubjectRole, LastEventTextAttachmentsRole, LastEventCallMissedRole, LastEventCallDurationRole, LastThreadRole }; explicit HistoryThreadModel(QObject *parent = 0); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role) const; QVariant threadData(const History::Thread &thread, int role) const; bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; void fetchMore(const QModelIndex &parent); virtual QHash roleNames() const; Q_INVOKABLE bool removeThreads(const QVariantList &threadsProperties); protected Q_SLOTS: virtual void updateQuery(); virtual void onThreadsAdded(const History::Threads &threads); virtual void onThreadsModified(const History::Threads &threads); virtual void onThreadsRemoved(const History::Threads &threads); protected: History::Threads fetchNextPage(); bool mCanFetchMore; bool mGroupThreads; private: History::ThreadViewPtr mThreadView; History::Threads mThreads; QHash mRoles; mutable QMap > mAttachmentCache; }; #endif // HISTORYTHREADMODEL_H history-service-0.1+16.04.20160104/Ubuntu/History/historyeventmodel.cpp0000644000015600001650000003264212642552153026276 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historyeventmodel.h" #include "eventview.h" #include "historyqmltexteventattachment.h" #include "manager.h" #include "contactmatcher_p.h" #include #include #include HistoryEventModel::HistoryEventModel(QObject *parent) : HistoryModel(parent), mCanFetchMore(true), mEventWritingTimer(0) { // configure the roles mRoles = HistoryModel::roleNames(); mRoles[EventIdRole] = "eventId"; mRoles[SenderIdRole] = "senderId"; mRoles[SenderRole] = "sender"; mRoles[TimestampRole] = "timestamp"; mRoles[DateRole] = "date"; mRoles[NewEventRole] = "newEvent"; mRoles[TextMessageRole] = "textMessage"; mRoles[TextMessageTypeRole] = "textMessageType"; mRoles[TextMessageStatusRole] = "textMessageStatus"; mRoles[TextMessageAttachmentsRole] = "textMessageAttachments"; mRoles[TextReadTimestampRole] = "textReadTimestamp"; mRoles[TextReadSubjectRole] = "textSubject"; mRoles[CallMissedRole] = "callMissed"; mRoles[CallDurationRole] = "callDuration"; mRoles[RemoteParticipantRole] = "remoteParticipant"; } int HistoryEventModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return mEvents.count(); } QVariant HistoryEventModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= mEvents.count()) { return QVariant(); } QVariant result = eventData(mEvents[index.row()], role); if (result.isNull()) { result = HistoryModel::data(index, role); } return result; } QVariant HistoryEventModel::eventData(const History::Event &event, int role) const { History::TextEvent textEvent; History::VoiceEvent voiceEvent; switch (event.type()) { case History::EventTypeText: textEvent = event; break; case History::EventTypeVoice: voiceEvent = event; break; } QVariant result; switch (role) { case EventIdRole: result = event.eventId(); break; case SenderIdRole: result = event.senderId(); break; case SenderRole: result = History::ContactMatcher::instance()->contactInfo(event.accountId(), event.senderId()); break; case TimestampRole: result = event.timestamp(); break; case DateRole: result = event.timestamp().date(); break; case NewEventRole: result = event.newEvent(); break; case PropertiesRole: result = event.properties(); break; case TextMessageRole: if (!textEvent.isNull()) { result = textEvent.message(); } break; case TextMessageTypeRole: if (!textEvent.isNull()) { result = (int)textEvent.messageType(); } break; case TextMessageStatusRole: if (!textEvent.isNull()) { result = (int)textEvent.messageStatus(); } break; case TextReadTimestampRole: if (!textEvent.isNull()) { result = textEvent.readTimestamp(); } break; case TextReadSubjectRole: if (!textEvent.isNull()) { result = textEvent.subject(); } break; case TextMessageAttachmentsRole: if (!textEvent.isNull()) { if (mAttachmentCache.contains(textEvent)) { result = mAttachmentCache.value(textEvent); } else { QList attachments; Q_FOREACH(const History::TextEventAttachment &attachment, textEvent.attachments()) { attachments << QVariant::fromValue(new HistoryQmlTextEventAttachment(attachment, const_cast(this))); } mAttachmentCache[textEvent] = attachments; result = attachments; } } break; case CallMissedRole: if (!voiceEvent.isNull()) { result = voiceEvent.missed(); } break; case CallDurationRole: if (!voiceEvent.isNull()) { result = voiceEvent.duration(); } break; case RemoteParticipantRole: if (!voiceEvent.isNull()) { result = voiceEvent.remoteParticipant(); } break; } return result; } bool HistoryEventModel::canFetchMore(const QModelIndex &parent) const { if (parent.isValid() || !mFilter || mView.isNull()) { return false; } return mCanFetchMore; } void HistoryEventModel::fetchMore(const QModelIndex &parent) { if (parent.isValid() || !mFilter || mView.isNull()) { return; } History::Events events = fetchNextPage(); if (events.isEmpty()) { mCanFetchMore = false; Q_EMIT canFetchMoreChanged(); } else { Q_FOREACH(const History::Event &event, events) { // watch for contact changes for the given identifiers Q_FOREACH(const History::Participant &participant, event.participants()) { watchContactInfo(event.accountId(), participant.identifier(), participant.properties()); } } beginInsertRows(QModelIndex(), mEvents.count(), mEvents.count() + events.count() - 1); mEvents << events; endInsertRows(); } } QHash HistoryEventModel::roleNames() const { return mRoles; } bool HistoryEventModel::removeEvents(const QVariantList &eventsProperties) { History::Events events; Q_FOREACH(const QVariant &entry, eventsProperties) { QVariantMap eventProperties = entry.toMap(); History::Event event; switch (eventProperties[History::FieldType].toInt()) { case History::EventTypeText: event = History::TextEvent::fromProperties(eventProperties); break; case History::EventTypeVoice: event = History::VoiceEvent::fromProperties(eventProperties); break; } if (!event.isNull()) { events << event; } } if (events.isEmpty()) { return false; } return History::Manager::instance()->removeEvents(events); } bool HistoryEventModel::writeEvents(const QVariantList &eventsProperties) { History::Events events; Q_FOREACH(const QVariant &entry, eventsProperties) { QVariantMap eventProperties = entry.toMap(); History::Event event; switch (eventProperties[History::FieldType].toInt()) { case History::EventTypeText: event = History::TextEvent::fromProperties(eventProperties); break; case History::EventTypeVoice: event = History::VoiceEvent::fromProperties(eventProperties); break; } if (!event.isNull()) { events << event; } } if (events.isEmpty()) { return false; } return History::Manager::instance()->writeEvents(events); } bool HistoryEventModel::removeEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, int eventType, const QString &attachmentId) { History::TextEvent textEvent; History::Event event = History::Manager::instance()->getSingleEvent((History::EventType)eventType, accountId, threadId, eventId); if (event.type() != History::EventTypeText) { qWarning() << "Trying to remove an attachment from a non text event"; return false; } QVariantMap properties = event.properties(); QList attachmentProperties = qdbus_cast >(properties[History::FieldAttachments]); QList newAttachmentProperties; int count = 0; Q_FOREACH(const QVariantMap &map, attachmentProperties) { if (map[History::FieldAttachmentId] != attachmentId) { count++; newAttachmentProperties << map; } } if (count == attachmentProperties.size()) { qWarning() << "No attachment found for id " << attachmentId; return false; } properties[History::FieldAttachments] = QVariant::fromValue(newAttachmentProperties); textEvent = History::TextEvent::fromProperties(properties); return History::Manager::instance()->writeEvents(History::Events() << textEvent); } bool HistoryEventModel::markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType) { History::Event event = History::Manager::instance()->getSingleEvent((History::EventType)eventType, accountId, threadId, eventId); event.setNewEvent(false); if (event.type() == History::EventTypeText) { History::TextEvent textEvent = event; textEvent.setReadTimestamp(QDateTime::currentDateTime()); event = textEvent; } mEventWritingQueue << event; if (mEventWritingTimer != 0) { killTimer(mEventWritingTimer); } mEventWritingTimer = startTimer(500); return true; } void HistoryEventModel::updateQuery() { // remove all events from the model if (!mEvents.isEmpty()) { beginRemoveRows(QModelIndex(), 0, mEvents.count() - 1); mEvents.clear(); endRemoveRows(); } // and create the view again History::Filter queryFilter; History::Sort querySort; if (!mView.isNull()) { mView->disconnect(this); } if (mFilter) { queryFilter = mFilter->filter(); } else { // we should not return anything if there is no filter return; } if (mSort) { querySort = mSort->sort(); } mView = History::Manager::instance()->queryEvents((History::EventType)mType, querySort, queryFilter); connect(mView.data(), SIGNAL(eventsAdded(History::Events)), SLOT(onEventsAdded(History::Events))); connect(mView.data(), SIGNAL(eventsModified(History::Events)), SLOT(onEventsModified(History::Events))); connect(mView.data(), SIGNAL(eventsRemoved(History::Events)), SLOT(onEventsRemoved(History::Events))); connect(mView.data(), SIGNAL(invalidated()), SLOT(triggerQueryUpdate())); mCanFetchMore = true; Q_EMIT canFetchMoreChanged(); Q_FOREACH(const QVariant &attachment, mAttachmentCache) { HistoryQmlTextEventAttachment *qmlAttachment = attachment.value(); if(qmlAttachment) { qmlAttachment->deleteLater(); } } mAttachmentCache.clear(); fetchMore(QModelIndex()); } void HistoryEventModel::onEventsAdded(const History::Events &events) { if (!events.count()) { return; } Q_FOREACH(const History::Event &event, events) { // if the event is already on the model, skip it if (mEvents.contains(event)) { continue; } int pos = positionForItem(event.properties()); beginInsertRows(QModelIndex(), pos, pos); mEvents.insert(pos, event); endInsertRows(); } } void HistoryEventModel::onEventsModified(const History::Events &events) { History::Events newEvents; Q_FOREACH(const History::Event &event, events) { int pos = mEvents.indexOf(event); if (pos >= 0) { mEvents[pos] = event; QModelIndex idx = index(pos); if (event.type() == History::EventTypeText) { History::TextEvent textEvent = event; mAttachmentCache.remove(textEvent); } Q_EMIT dataChanged(idx, idx); } else { newEvents << event; } } // append the events that were not yet on the model if (!newEvents.isEmpty()) { onEventsAdded(newEvents); } } void HistoryEventModel::onEventsRemoved(const History::Events &events) { Q_FOREACH(const History::Event &event, events) { int pos = mEvents.indexOf(event); if (pos >= 0) { beginRemoveRows(QModelIndex(), pos, pos); mEvents.removeAt(pos); endRemoveRows(); } } // FIXME: there is a corner case here: if an event was not loaded yet, but was already // removed by another client, it will still show up when a new page is requested. Maybe it // should be handle internally in History::EventView? } void HistoryEventModel::timerEvent(QTimerEvent *event) { HistoryModel::timerEvent(event); if (event->timerId() == mEventWritingTimer) { killTimer(mEventWritingTimer); mEventWritingTimer = 0; if (mEventWritingQueue.isEmpty()) { return; } qDebug() << "Goint to update" << mEventWritingQueue.count() << "events."; if (History::Manager::instance()->writeEvents(mEventWritingQueue)) { qDebug() << "... succeeded!"; mEventWritingQueue.clear(); } } } History::Events HistoryEventModel::fetchNextPage() { return mView->nextPage(); } history-service-0.1+16.04.20160104/Ubuntu/History/historyqmltexteventattachment.cpp0000644000015600001650000000331612642552153030741 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "texteventattachment.h" #include "historyqmltexteventattachment.h" HistoryQmlTextEventAttachment::HistoryQmlTextEventAttachment(const History::TextEventAttachment &attachment, QObject *parent) : QObject(parent), mAttachment(attachment) { } QString HistoryQmlTextEventAttachment::accountId() const { return mAttachment.accountId(); } QString HistoryQmlTextEventAttachment::threadId() const { return mAttachment.threadId(); } QString HistoryQmlTextEventAttachment::eventId() const { return mAttachment.eventId(); } QString HistoryQmlTextEventAttachment::attachmentId() const { return mAttachment.attachmentId(); } QString HistoryQmlTextEventAttachment::contentType() const { return mAttachment.contentType(); } QString HistoryQmlTextEventAttachment::filePath() const { return mAttachment.filePath(); } int HistoryQmlTextEventAttachment::status() const { return mAttachment.status(); } history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlsort.h0000644000015600001650000000401012642552153025266 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYQMLSORT_H #define HISTORYQMLSORT_H #include #include "types.h" #include "sort.h" class HistoryQmlSort : public QObject { Q_OBJECT Q_ENUMS(SortOrder) Q_ENUMS(CaseSensitivity) Q_PROPERTY(QString sortField READ sortField WRITE setSortField NOTIFY sortFieldChanged) Q_PROPERTY(SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) Q_PROPERTY(CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged) public: enum SortOrder { AscendingOrder = Qt::AscendingOrder, DescendingOrder = Qt::DescendingOrder }; enum CaseSensitivity { CaseInsensitive = Qt::CaseInsensitive, CaseSensitive = Qt::CaseSensitive }; explicit HistoryQmlSort(QObject *parent = 0); QString sortField() const; void setSortField(const QString &value); SortOrder sortOrder() const; void setSortOrder(SortOrder order); CaseSensitivity caseSensitivity() const; void setCaseSensitivity(CaseSensitivity value); History::Sort sort() const; Q_SIGNALS: void sortChanged(); void sortFieldChanged(); void sortOrderChanged(); void caseSensitivityChanged(); private: History::Sort mSort; }; #endif // HISTORYQMLSORT_H history-service-0.1+16.04.20160104/Ubuntu/History/historyeventmodel.h0000644000015600001650000000574512642552153025747 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYEVENTMODEL_H #define HISTORYEVENTMODEL_H #include "historymodel.h" #include "textevent.h" #include "voiceevent.h" #include class HistoryEventModel : public HistoryModel { Q_OBJECT Q_ENUMS(EventRole) public: enum EventRole { EventIdRole = HistoryModel::LastRole, SenderIdRole, SenderRole, TimestampRole, DateRole, NewEventRole, TextMessageRole, TextMessageTypeRole, TextMessageStatusRole, TextReadTimestampRole, TextReadSubjectRole, TextMessageAttachmentsRole, CallMissedRole, CallDurationRole, RemoteParticipantRole, LastEventRole }; explicit HistoryEventModel(QObject *parent = 0); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role) const; QVariant eventData(const History::Event &event, int role) const; Q_INVOKABLE virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE virtual void fetchMore(const QModelIndex &parent = QModelIndex()); virtual QHash roleNames() const; Q_INVOKABLE bool removeEvents(const QVariantList &eventsProperties); Q_INVOKABLE bool writeEvents(const QVariantList &eventsProperties); Q_INVOKABLE bool markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType); Q_INVOKABLE bool removeEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, int eventType, const QString &attachmentId); protected Q_SLOTS: virtual void updateQuery(); virtual void onEventsAdded(const History::Events &events); virtual void onEventsModified(const History::Events &events); virtual void onEventsRemoved(const History::Events &events); protected: void timerEvent(QTimerEvent *event); History::Events fetchNextPage(); private: History::EventViewPtr mView; History::Events mEvents; bool mCanFetchMore; QHash mRoles; mutable QMap > mAttachmentCache; History::Events mEventWritingQueue; int mEventWritingTimer; }; #endif // HISTORYEVENTMODEL_H history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlplugin.h0000644000015600001650000000214412642552153025603 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYQMLPLUGIN_H #define HISTORYQMLPLUGIN_H #include class HistoryQmlPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: void initializeEngine(QQmlEngine *engine, const char *uri); void registerTypes(const char *uri); }; #endif // HISTORYQMLPLUGIN_H history-service-0.1+16.04.20160104/Ubuntu/History/historygroupedthreadsmodel.cpp0000644000015600001650000002354612642552153030200 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historygroupedthreadsmodel.h" #include "utils_p.h" #include "manager.h" #include "phoneutils_p.h" #include #include #include HistoryGroupedThreadsModel::HistoryGroupedThreadsModel(QObject *parent) : HistoryThreadModel(parent) { qDBusRegisterMetaType >(); qRegisterMetaType >(); mGroupThreads = true; mRoles = HistoryThreadModel::roleNames(); mRoles[ThreadsRole] = "threads"; } QVariant HistoryGroupedThreadsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const HistoryThreadGroup &group = mGroups[index.row()]; // get the data from the latest thread, and overwrite if necessary QVariant result = threadData(group.displayedThread, role); switch (role) { case HistoryThreadModel::CountRole: { int count = 0; Q_FOREACH(const History::Thread &thread, group.threads) { count += thread.count(); } result = count; break; } case HistoryThreadModel::UnreadCountRole: { int count = 0; Q_FOREACH(const History::Thread &thread, group.threads) { count += thread.unreadCount(); } result = count; break; } case ThreadsRole: { QVariantList threads; Q_FOREACH(const History::Thread &thread, group.threads) { threads << thread.properties(); } result = threads; break; } } if (result.isNull()) { // get the shared roles result = HistoryModel::data(index, role); } return result; } void HistoryGroupedThreadsModel::fetchMore(const QModelIndex &parent) { if (!canFetchMore(parent)) { return; } const History::Threads &threads = fetchNextPage(); Q_FOREACH(const History::Thread &thread, threads) { processThreadGrouping(thread); // insert the identifiers in the contact map Q_FOREACH(const History::Participant &participant, thread.participants()) { watchContactInfo(thread.accountId(), participant.identifier(), participant.properties()); } } notifyDataChanged(); if (threads.isEmpty()) { mCanFetchMore = false; Q_EMIT canFetchMoreChanged(); } } QHash HistoryGroupedThreadsModel::roleNames() const { return mRoles; } QVariant HistoryGroupedThreadsModel::get(int row) const { if (row >= rowCount() || row < 0) { return QVariant(); } return data(index(row), ThreadsRole); } int HistoryGroupedThreadsModel::existingPositionForEntry(const History::Thread &thread) const { int pos = -1; if (mGroupingProperty == History::FieldParticipants) { for (int i = 0; i < mGroups.count(); ++i) { const HistoryThreadGroup &group = mGroups[i]; Q_FOREACH(const History::Thread &groupedThread, group.threads) { History::Threads threads; // when removing threads, we cant get the grouped threads from history if (thread.groupedThreads().size() == 0) { threads << thread; } else { threads = thread.groupedThreads(); } Q_FOREACH(const History::Thread &groupedThread2, threads) { if (groupedThread == groupedThread2) { return i; } } } } return pos; } for (int i = 0; i < mGroups.count(); ++i) { const HistoryThreadGroup &group = mGroups[i]; if (thread.properties()[mGroupingProperty] == group.displayedThread.properties()[mGroupingProperty]) { pos = i; break; } } return pos; } void HistoryGroupedThreadsModel::removeGroup(const HistoryThreadGroup &group) { int pos = mGroups.indexOf(group); if (pos >= 0){ beginRemoveRows(QModelIndex(), pos, pos); mGroups.removeAt(pos); endRemoveRows(); } } void HistoryGroupedThreadsModel::updateDisplayedThread(HistoryThreadGroup &group) { int pos = mGroups.indexOf(group); if (pos < 0) { qWarning() << "Group not found!!"; return; } History::Thread displayedThread = group.threads.first(); QVariantMap displayedProperties = displayedThread.properties(); Q_FOREACH(const History::Thread &other, group.threads) { if (isAscending() ? lessThan(other.properties(), displayedProperties) : lessThan(displayedProperties, other.properties())) { displayedThread = other; displayedProperties = displayedThread.properties(); } } // check if we need to update the order int newPos = positionForItem(displayedProperties); // NOTE: only set the new displayedThread AFTER calling positionForItem group.displayedThread = displayedThread; // the positionForItem function might return the pos+1 value for the current item as it considers // this to be the position for a new insertion if (newPos != pos && newPos != pos+1) { beginMoveRows(QModelIndex(), pos, pos, QModelIndex(), newPos); // QList::move() behaves in a different way than the QAbstractItemModel moving functions // that's why the delta was added mGroups.move(pos, newPos > pos ? newPos-1 : newPos); endMoveRows(); } } void HistoryGroupedThreadsModel::updateQuery() { // remove all entries and call the query update if (!mGroups.isEmpty()) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); mGroups.clear(); endRemoveRows(); } HistoryThreadModel::updateQuery(); } void HistoryGroupedThreadsModel::onThreadsAdded(const History::Threads &threads) { Q_FOREACH(const History::Thread &thread, threads) { processThreadGrouping(thread); } notifyDataChanged(); } void HistoryGroupedThreadsModel::onThreadsModified(const History::Threads &threads) { Q_FOREACH(const History::Thread &thread, threads) { processThreadGrouping(thread); } notifyDataChanged(); } void HistoryGroupedThreadsModel::onThreadsRemoved(const History::Threads &threads) { Q_FOREACH(const History::Thread &thread, threads) { removeThreadFromGroup(thread); } notifyDataChanged(); } void HistoryGroupedThreadsModel::processThreadGrouping(const History::Thread &thread) { QVariantMap queryProperties; queryProperties[History::FieldGroupingProperty] = mGroupingProperty; History::Thread groupedThread = History::Manager::instance()->getSingleThread((History::EventType)mType, thread.accountId(), thread.threadId(), queryProperties); if (groupedThread.properties().isEmpty()) { removeThreadFromGroup(thread); return; } int pos = existingPositionForEntry(groupedThread); // if the group is empty, we need to insert it into the map if (pos < 0) { HistoryThreadGroup group; int newPos = positionForItem(groupedThread.properties()); group.threads = groupedThread.groupedThreads(); group.displayedThread = groupedThread; beginInsertRows(QModelIndex(), newPos, newPos); mGroups.insert(newPos, group); endInsertRows(); return; } HistoryThreadGroup &group = mGroups[pos]; group.threads = groupedThread.groupedThreads(); updateDisplayedThread(group); markGroupAsChanged(group); } void HistoryGroupedThreadsModel::removeThreadFromGroup(const History::Thread &thread) { QVariantMap properties = thread.properties(); int pos = existingPositionForEntry(thread); if (pos < 0) { qWarning() << "Could not find group for property " << properties[mGroupingProperty]; return; } HistoryThreadGroup &group = mGroups[pos]; group.threads.removeAll(thread); if (group.threads.isEmpty()) { removeGroup(group); } else { updateDisplayedThread(group); markGroupAsChanged(group); } } void HistoryGroupedThreadsModel::markGroupAsChanged(const HistoryThreadGroup &group) { if (!mChangedGroups.contains(group)) { mChangedGroups.append(group); } } void HistoryGroupedThreadsModel::notifyDataChanged() { Q_FOREACH(const HistoryThreadGroup &group, mChangedGroups) { int pos = mGroups.indexOf(group); if (pos >= 0) { QModelIndex idx = index(pos); Q_EMIT dataChanged(idx, idx); } else { qWarning() << "Group not found!"; } } mChangedGroups.clear(); } QString HistoryGroupedThreadsModel::groupingProperty() const { return mGroupingProperty; } void HistoryGroupedThreadsModel::setGroupingProperty(const QString &value) { mGroupingProperty = value; Q_EMIT groupingPropertyChanged(); triggerQueryUpdate(); } int HistoryGroupedThreadsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return mGroups.count(); } bool HistoryThreadGroup::operator==(const HistoryThreadGroup &other) const { return displayedThread == other.displayedThread; } history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlunionfilter.h0000644000015600001650000000212212642552153026637 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYQMLUNIONFILTER_H #define HISTORYQMLUNIONFILTER_H #include "historyqmlfilter.h" #include #include class HistoryQmlUnionFilter : public HistoryQmlCompoundFilter { Q_OBJECT public: explicit HistoryQmlUnionFilter(QObject *parent = 0); History::Filter filter() const; }; #endif // HISTORYQMLUNIONFILTER_H history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlsort.cpp0000644000015600001650000000371412642552153025633 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historyqmlsort.h" #include "sort.h" HistoryQmlSort::HistoryQmlSort(QObject *parent) : QObject(parent) { connect(this, SIGNAL(sortFieldChanged()), SIGNAL(sortChanged())); connect(this, SIGNAL(sortOrderChanged()), SIGNAL(sortChanged())); connect(this, SIGNAL(caseSensitivityChanged()), SIGNAL(sortChanged())); } QString HistoryQmlSort::sortField() const { return mSort.sortField(); } void HistoryQmlSort::setSortField(const QString &value) { mSort.setSortField(value); Q_EMIT sortFieldChanged(); } HistoryQmlSort::SortOrder HistoryQmlSort::sortOrder() const { return (SortOrder) mSort.sortOrder(); } void HistoryQmlSort::setSortOrder(HistoryQmlSort::SortOrder order) { mSort.setSortOrder((Qt::SortOrder) order); Q_EMIT sortOrderChanged(); } HistoryQmlSort::CaseSensitivity HistoryQmlSort::caseSensitivity() const { return (CaseSensitivity) mSort.caseSensitivity(); } void HistoryQmlSort::setCaseSensitivity(HistoryQmlSort::CaseSensitivity value) { mSort.setCaseSensitivity((Qt::CaseSensitivity) value); Q_EMIT caseSensitivityChanged(); } History::Sort HistoryQmlSort::sort() const { return mSort; } history-service-0.1+16.04.20160104/Ubuntu/History/historythreadmodel.cpp0000644000015600001650000002520712642552153026423 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historythreadmodel.h" #include "historyqmltexteventattachment.h" #include "manager.h" #include "threadview.h" #include "voiceevent.h" #include Q_DECLARE_METATYPE(History::TextEventAttachments) Q_DECLARE_METATYPE(QList) HistoryThreadModel::HistoryThreadModel(QObject *parent) : HistoryModel(parent), mCanFetchMore(true), mGroupThreads(false) { qRegisterMetaType >(); qDBusRegisterMetaType >(); // configure the roles mRoles = HistoryModel::roleNames(); mRoles[CountRole] = "count"; mRoles[UnreadCountRole] = "unreadCount"; // roles related to the thread´s last event mRoles[LastEventIdRole] = "eventId"; mRoles[LastEventSenderIdRole] = "eventSenderId"; mRoles[LastEventTimestampRole] = "eventTimestamp"; mRoles[LastEventDateRole] = "eventDate"; mRoles[LastEventNewRole] = "eventNew"; mRoles[LastEventTextMessageRole] = "eventTextMessage"; mRoles[LastEventTextMessageTypeRole] = "eventTextMessageType"; mRoles[LastEventTextMessageStatusRole] = "eventTextMessageStatus"; mRoles[LastEventTextReadTimestampRole] = "eventTextReadTimestamp"; mRoles[LastEventTextAttachmentsRole] = "eventTextAttachments"; mRoles[LastEventTextSubjectRole] = "eventTextSubject"; mRoles[LastEventCallMissedRole] = "eventCallMissed"; mRoles[LastEventCallDurationRole] = "eventCallDuration"; } int HistoryThreadModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return mThreads.count(); } QVariant HistoryThreadModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= mThreads.count()) { return QVariant(); } History::Thread thread = mThreads[index.row()]; QVariant result = threadData(thread, role); if (result.isNull()) { result = HistoryModel::data(index, role); } return result; } QVariant HistoryThreadModel::threadData(const History::Thread &thread, int role) const { History::Event event = thread.lastEvent(); History::TextEvent textEvent; History::VoiceEvent voiceEvent; if (!event.isNull()) { switch (event.type()) { case History::EventTypeText: textEvent = event; break; case History::EventTypeVoice: voiceEvent = event; break; } } QVariant result; switch (role) { case CountRole: result = thread.count(); break; case UnreadCountRole: result = thread.unreadCount(); break; case PropertiesRole: result = thread.properties(); break; case LastEventIdRole: if (!event.isNull()) { result = event.eventId(); } break; case LastEventSenderIdRole: if (!event.isNull()) { result = event.senderId(); } break; case LastEventTimestampRole: if (!event.isNull()) { result = event.timestamp(); } break; case LastEventDateRole: if (!event.isNull()) { result = event.timestamp().date(); } break; case LastEventNewRole: if (!event.isNull()) { result = event.newEvent(); } break; case LastEventTextMessageRole: if (!textEvent.isNull()) { result = textEvent.message(); } break; case LastEventTextMessageTypeRole: if (!textEvent.isNull()) { result = (int) textEvent.messageType(); } break; case LastEventTextMessageStatusRole: if (!textEvent.isNull()) { result = (int) textEvent.messageStatus(); } break; case LastEventTextReadTimestampRole: if (!textEvent.isNull()) { result = textEvent.readTimestamp(); } break; case LastEventTextSubjectRole: if (!textEvent.isNull()) { result = textEvent.subject(); } break; case LastEventTextAttachmentsRole: if (!textEvent.isNull()) { if (mAttachmentCache.contains(textEvent)) { result = mAttachmentCache.value(textEvent); } else { QList attachments; Q_FOREACH(const History::TextEventAttachment &attachment, textEvent.attachments()) { attachments << QVariant::fromValue(new HistoryQmlTextEventAttachment(attachment, const_cast(this))); } mAttachmentCache[textEvent] = attachments; result = attachments; } } break; case LastEventCallMissedRole: if (!voiceEvent.isNull()) { result = voiceEvent.missed(); } break; case LastEventCallDurationRole: if (!voiceEvent.isNull()) { result = voiceEvent.duration(); } break; } return result; } bool HistoryThreadModel::canFetchMore(const QModelIndex &parent) const { if (parent.isValid() || !mFilter || mThreadView.isNull()) { return false; } return mCanFetchMore; } void HistoryThreadModel::fetchMore(const QModelIndex &parent) { if (parent.isValid() || mThreadView.isNull()) { return; } History::Threads threads = fetchNextPage(); if (threads.isEmpty()) { mCanFetchMore = false; Q_EMIT canFetchMoreChanged(); } else { Q_FOREACH(const History::Thread &thread, threads) { // insert the identifiers in the contact map Q_FOREACH(const History::Participant &participant, thread.participants()) { watchContactInfo(thread.accountId(), participant.identifier(), participant.properties()); } } beginInsertRows(QModelIndex(), mThreads.count(), mThreads.count() + threads.count() - 1); mThreads << threads; endInsertRows(); } } QHash HistoryThreadModel::roleNames() const { return mRoles; } bool HistoryThreadModel::removeThreads(const QVariantList &threadsProperties) { History::Threads threads; Q_FOREACH(const QVariant &entry, threadsProperties) { QVariantMap threadProperties = entry.toMap(); History::Thread thread = History::Thread::fromProperties(threadProperties); if (!thread.isNull()) { threads << thread; } } if (threads.isEmpty()) { return false; } return History::Manager::instance()->removeThreads(threads); } void HistoryThreadModel::updateQuery() { // remove all events from the model if (!mThreads.isEmpty()) { beginRemoveRows(QModelIndex(), 0, mThreads.count() - 1); mThreads.clear(); endRemoveRows(); } History::Filter queryFilter; History::Sort querySort; if (!mThreadView.isNull()) { mThreadView->disconnect(this); } if (mFilter) { queryFilter = mFilter->filter(); } else { // we should not return anything if there is no filter return; } if (mSort) { querySort = mSort->sort(); } QVariantMap properties; if (mGroupThreads) { properties[History::FieldGroupingProperty] = History::FieldParticipants; } mThreadView = History::Manager::instance()->queryThreads((History::EventType)mType, querySort, queryFilter, properties); connect(mThreadView.data(), SIGNAL(threadsAdded(History::Threads)), SLOT(onThreadsAdded(History::Threads))); connect(mThreadView.data(), SIGNAL(threadsModified(History::Threads)), SLOT(onThreadsModified(History::Threads))); connect(mThreadView.data(), SIGNAL(threadsRemoved(History::Threads)), SLOT(onThreadsRemoved(History::Threads))); connect(mThreadView.data(), SIGNAL(invalidated()), SLOT(triggerQueryUpdate())); Q_FOREACH(const QVariant &attachment, mAttachmentCache) { HistoryQmlTextEventAttachment *qmlAttachment = attachment.value(); if(qmlAttachment) { qmlAttachment->deleteLater(); } } mAttachmentCache.clear(); // and fetch again mCanFetchMore = true; Q_EMIT canFetchMoreChanged(); fetchMore(QModelIndex()); } void HistoryThreadModel::onThreadsAdded(const History::Threads &threads) { if (threads.isEmpty()) { return; } Q_FOREACH(const History::Thread &thread, threads) { // if the thread is already inserted, skip it if (mThreads.contains(thread)) { continue; } int pos = positionForItem(thread.properties()); beginInsertRows(QModelIndex(), pos, pos); mThreads.insert(pos, thread); endInsertRows(); } } void HistoryThreadModel::onThreadsModified(const History::Threads &threads) { History::Threads newThreads; Q_FOREACH(const History::Thread &thread, threads) { int pos = mThreads.indexOf(thread); if (pos >= 0) { mThreads[pos] = thread; QModelIndex idx = index(pos); Q_EMIT dataChanged(idx, idx); } else { newThreads << thread; } } // add threads that were not yet on the model if (!newThreads.isEmpty()) { onThreadsAdded(newThreads); } } void HistoryThreadModel::onThreadsRemoved(const History::Threads &threads) { Q_FOREACH(const History::Thread &thread, threads) { int pos = mThreads.indexOf(thread); if (pos >= 0) { beginRemoveRows(QModelIndex(), pos, pos); mThreads.removeAt(pos); endRemoveRows(); } } // FIXME: there is a corner case here: if a thread was not loaded yet, but was already // removed by another client, it will still show up when a new page is requested. Maybe it // should be handle internally in History::ThreadView? } History::Threads HistoryThreadModel::fetchNextPage() { return mThreadView->nextPage(); } history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlfilter.h0000644000015600001650000000551212642552153025574 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYQMLFILTER_H #define HISTORYQMLFILTER_H #include #include #include "types.h" #include "filter.h" class HistoryQmlFilter : public QObject { Q_OBJECT Q_ENUMS(MatchFlag) Q_PROPERTY(QString filterProperty READ filterProperty WRITE setFilterProperty NOTIFY filterPropertyChanged) Q_PROPERTY(QVariant filterValue READ filterValue WRITE setFilterValue NOTIFY filterValueChanged) Q_PROPERTY(int matchFlags READ matchFlags WRITE setMatchFlags NOTIFY matchFlagsChanged) public: enum MatchFlag { MatchCaseSensitive = History::MatchCaseSensitive, MatchCaseInsensitive = History::MatchCaseInsensitive, MatchContains = History::MatchContains, MatchPhoneNumber = History::MatchPhoneNumber }; explicit HistoryQmlFilter(QObject *parent = 0); QString filterProperty() const; void setFilterProperty(const QString &value); QVariant filterValue() const; void setFilterValue(const QVariant &value); int matchFlags() const; void setMatchFlags(int flags); virtual History::Filter filter() const; Q_SIGNALS: void filterPropertyChanged(); void filterValueChanged(); void matchFlagsChanged(); void filterChanged(); protected: History::Filter mFilter; }; // compound filter class HistoryQmlCompoundFilter : public HistoryQmlFilter { Q_OBJECT Q_PROPERTY(QQmlListProperty filters READ filters NOTIFY filtersChanged) Q_CLASSINFO("DefaultProperty", "filters") public: explicit HistoryQmlCompoundFilter(QObject* parent = 0); virtual ~HistoryQmlCompoundFilter(); QQmlListProperty filters(); static void filtersAppend(QQmlListProperty* prop, HistoryQmlFilter* filter); static int filtersCount(QQmlListProperty* prop); static HistoryQmlFilter* filtersAt(QQmlListProperty* prop, int index); static void filtersClear(QQmlListProperty* prop); Q_SIGNALS: void filtersChanged(); protected: QList mFilters; }; #endif // HISTORYQMLFILTER_H history-service-0.1+16.04.20160104/Ubuntu/History/historymodel.h0000644000015600001650000001365612642552153024705 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYMODEL_H #define HISTORYMODEL_H #include "types.h" #include "historyqmlfilter.h" #include "historyqmlsort.h" #include #include #include class HistoryModel : public QAbstractListModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(HistoryQmlFilter *filter READ filter WRITE setFilter NOTIFY filterChanged) Q_PROPERTY(HistoryQmlSort *sort READ sort WRITE setSort NOTIFY sortChanged) Q_PROPERTY(EventType type READ type WRITE setType NOTIFY typeChanged) Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged) Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged) Q_ENUMS(EventType) Q_ENUMS(MessageType) Q_ENUMS(MatchFlag) Q_ENUMS(MessageStatus) Q_ENUMS(AttachmentFlag) Q_ENUMS(Role) public: enum EventType { EventTypeText = History::EventTypeText, EventTypeVoice = History::EventTypeVoice }; enum MessageType { MessageTypeText = History::MessageTypeText, MessageTypeMultiPart = History::MessageTypeMultiPart, MessageTypeInformation = History::MessageTypeInformation }; enum MatchFlag { MatchCaseSensitive = History::MatchCaseSensitive, MatchCaseInsensitive = History::MatchCaseInsensitive, MatchContains = History::MatchContains, MatchPhoneNumber = History::MatchPhoneNumber }; enum MessageStatus { MessageStatusUnknown = History::MessageStatusUnknown, MessageStatusDelivered = History::MessageStatusDelivered, MessageStatusTemporarilyFailed = History::MessageStatusTemporarilyFailed, MessageStatusPermanentlyFailed = History::MessageStatusPermanentlyFailed, MessageStatusAccepted = History::MessageStatusAccepted, MessageStatusRead = History::MessageStatusRead, MessageStatusDeleted = History::MessageStatusDeleted, MessageStatusPending = History::MessageStatusPending // pending attachment download }; enum AttachmentFlag { AttachmentDownloaded = History::AttachmentDownloaded, AttachmentPending = History::AttachmentPending, AttachmentError = History::AttachmentError }; enum Role { AccountIdRole = Qt::UserRole, ThreadIdRole, ParticipantsRole, TypeRole, PropertiesRole, LastRole }; explicit HistoryModel(QObject *parent = 0); Q_INVOKABLE virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE virtual void fetchMore(const QModelIndex &parent = QModelIndex()); virtual QHash roleNames() const; virtual QVariant data(const QModelIndex &index, int role) const; HistoryQmlFilter *filter() const; void setFilter(HistoryQmlFilter *value); HistoryQmlSort *sort() const; void setSort(HistoryQmlSort *value); EventType type() const; void setType(EventType value); bool matchContacts() const; void setMatchContacts(bool value); Q_INVOKABLE QVariantMap threadForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags = (int)History::MatchCaseSensitive, bool create = false); Q_INVOKABLE QString threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags = (int)History::MatchCaseSensitive, bool create = false); Q_INVOKABLE bool writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message); Q_INVOKABLE virtual QVariant get(int row) const; // QML parser status things void classBegin(); void componentComplete(); Q_SIGNALS: void countChanged(); void filterChanged(); void sortChanged(); void typeChanged(); void matchContactsChanged(); void canFetchMoreChanged(); protected Q_SLOTS: void triggerQueryUpdate(); virtual void updateQuery() = 0; void onContactInfoChanged(const QString &accountId, const QString &identifier, const QVariantMap &contactInfo); void watchContactInfo(const QString &accountId, const QString &identifier, const QVariantMap ¤tInfo); protected: virtual void timerEvent(QTimerEvent *event); bool lessThan(const QVariantMap &left, const QVariantMap &right) const; int positionForItem(const QVariantMap &item) const; bool isAscending() const; HistoryQmlFilter *mFilter; HistoryQmlSort *mSort; EventType mType; bool mMatchContacts; private: QHash mRoles; int mUpdateTimer; bool mWaitingForQml; }; #endif // HISTORYMODEL_H history-service-0.1+16.04.20160104/Ubuntu/History/historygroupedeventsmodel.h0000644000015600001650000000475312642552153027516 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYGROUPEDEVENTSMODEL_H #define HISTORYGROUPEDEVENTSMODEL_H #include "historyeventmodel.h" typedef struct { History::Events events; History::Event displayedEvent; } HistoryEventGroup; class HistoryGroupedEventsModel : public HistoryEventModel { Q_OBJECT Q_PROPERTY(QStringList groupingProperties READ groupingProperties WRITE setGroupingProperties NOTIFY groupingPropertiesChanged) Q_ENUMS(GroupedRole) public: enum GroupedRole { EventsRole = HistoryEventModel::LastEventRole, EventCountRole }; explicit HistoryGroupedEventsModel(QObject *parent = 0); // reimplemented from HistoryEventModel int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex()); QHash roleNames() const; Q_INVOKABLE QVariant get(int row) const; QStringList groupingProperties() const; void setGroupingProperties(const QStringList &properties); Q_SIGNALS: void groupingPropertiesChanged(); protected Q_SLOTS: void updateQuery(); void onEventsAdded(const History::Events &events); void onEventsModified(const History::Events &events); void onEventsRemoved(const History::Events &events); protected: bool areOfSameGroup(const History::Event &event1, const History::Event &event2); void addEventToGroup(const History::Event &event, HistoryEventGroup &group, int row); void removeEventFromGroup(const History::Event &event, HistoryEventGroup &group, int row); private: QStringList mGroupingProperties; QList mEventGroups; }; #endif // HISTORYGROUPEDEVENTSMODEL_H history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlfilter.cpp0000644000015600001650000000742112642552153026130 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historyqmlfilter.h" #include "filter.h" HistoryQmlFilter::HistoryQmlFilter(QObject *parent) : QObject(parent) { connect(this, SIGNAL(filterPropertyChanged()), SIGNAL(filterChanged())); connect(this, SIGNAL(filterValueChanged()), SIGNAL(filterChanged())); connect(this, SIGNAL(matchFlagsChanged()), SIGNAL(filterChanged())); } QString HistoryQmlFilter::filterProperty() const { return mFilter.filterProperty(); } void HistoryQmlFilter::setFilterProperty(const QString &value) { mFilter.setFilterProperty(value); Q_EMIT filterPropertyChanged(); } QVariant HistoryQmlFilter::filterValue() const { return mFilter.filterValue(); } void HistoryQmlFilter::setFilterValue(const QVariant &value) { mFilter.setFilterValue(value); Q_EMIT filterValueChanged(); } int HistoryQmlFilter::matchFlags() const { return mFilter.matchFlags(); } void HistoryQmlFilter::setMatchFlags(int flags) { mFilter.setMatchFlags((History::MatchFlags)flags); Q_EMIT matchFlagsChanged(); } History::Filter HistoryQmlFilter::filter() const { return mFilter; } HistoryQmlCompoundFilter::HistoryQmlCompoundFilter(QObject *parent) : HistoryQmlFilter(parent) { } HistoryQmlCompoundFilter::~HistoryQmlCompoundFilter() { } QQmlListProperty HistoryQmlCompoundFilter::filters() { return QQmlListProperty(this, 0, // opaque data filtersAppend, filtersCount, filtersAt, filtersClear); } void HistoryQmlCompoundFilter::filtersAppend(QQmlListProperty *prop, HistoryQmlFilter *filter) { HistoryQmlCompoundFilter* compoundFilter = static_cast(prop->object); compoundFilter->mFilters.append(filter); QObject::connect(filter, SIGNAL(filterChanged()), compoundFilter, SIGNAL(filterChanged()), Qt::UniqueConnection); Q_EMIT compoundFilter->filterChanged(); } int HistoryQmlCompoundFilter::filtersCount(QQmlListProperty *prop) { HistoryQmlCompoundFilter *compoundFilter = static_cast(prop->object); return compoundFilter->mFilters.count(); } HistoryQmlFilter *HistoryQmlCompoundFilter::filtersAt(QQmlListProperty *prop, int index) { HistoryQmlCompoundFilter* compoundFilter = static_cast(prop->object); return compoundFilter->mFilters[index]; } void HistoryQmlCompoundFilter::filtersClear(QQmlListProperty *prop) { HistoryQmlCompoundFilter* compoundFilter = static_cast(prop->object); if (!compoundFilter->mFilters.isEmpty()) { Q_FOREACH(HistoryQmlFilter *filter, compoundFilter->mFilters) { filter->disconnect(compoundFilter); } compoundFilter->mFilters.clear(); } } history-service-0.1+16.04.20160104/Ubuntu/History/historygroupedeventsmodel.cpp0000644000015600001650000002307012642552153030042 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historygroupedeventsmodel.h" #include "utils_p.h" #include "phoneutils_p.h" #include "sort.h" #include "historyqmlsort.h" #include "participant.h" HistoryGroupedEventsModel::HistoryGroupedEventsModel(QObject *parent) : HistoryEventModel(parent) { } int HistoryGroupedEventsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return mEventGroups.count(); } QVariant HistoryGroupedEventsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= mEventGroups.count()) { return QVariant(); } HistoryEventGroup group = mEventGroups[index.row()]; QVariant result; QVariantList events; switch (role) { case EventsRole: Q_FOREACH(const History::Event &event, group.events) { events << event.properties(); } result = events; break; case EventCountRole: result = group.events.count(); break; default: result = eventData(group.displayedEvent, role); break; } if (result.isNull()) { // for the shared roles result = HistoryModel::data(index, role); } return result; } void HistoryGroupedEventsModel::fetchMore(const QModelIndex &parent) { if (!canFetchMore(parent)) { return; } History::Events events = fetchNextPage(); // History already deliver us the events in the right order // but we might have added new entries in the added, removed, modified events. // still, it is less expensive to do a sequential search starting from the bottom // than to do a binary search for each event, as it is very likely that the entries // belong to the bottom part of the model. Q_FOREACH(const History::Event event, events) { // watch for contact changes for the given identifiers Q_FOREACH(const History::Participant &participant, event.participants()) { watchContactInfo(event.accountId(), participant.identifier(), participant.properties()); } bool found = false; int pos = mEventGroups.count() -1; for (; pos >= 0; pos--) { HistoryEventGroup &group = mEventGroups[pos]; if (areOfSameGroup(event, group.displayedEvent)) { found = true; addEventToGroup(event, group, pos); break; } else if (isAscending() ? lessThan(group.displayedEvent.properties(), event.properties()) : lessThan(event.properties(), group.displayedEvent.properties())) { break; } } if (!found) { // the item goes into a new group right after the position found above pos++; HistoryEventGroup group; group.displayedEvent = event; group.events << event; beginInsertRows(QModelIndex(), pos, pos); mEventGroups.insert(pos, group); endInsertRows(); } } } QHash HistoryGroupedEventsModel::roleNames() const { QHash roles = HistoryEventModel::roleNames(); roles[EventsRole] = "events"; roles[EventCountRole] = "eventCount"; return roles; } void HistoryGroupedEventsModel::updateQuery() { // remove all event groups from the model if (!mEventGroups.isEmpty()) { beginRemoveRows(QModelIndex(), 0, mEventGroups.count() - 1); mEventGroups.clear(); endRemoveRows(); } // and ask HistoryEventModel to update the query and fetch items again HistoryEventModel::updateQuery(); } void HistoryGroupedEventsModel::onEventsAdded(const History::Events &events) { if (!events.count()) { return; } Q_FOREACH(const History::Event &event, events) { int pos = positionForItem(event.properties()); // check if the event belongs to the group at the position if (pos >= 0 && pos < mEventGroups.count()) { HistoryEventGroup &group = mEventGroups[pos]; if (areOfSameGroup(event, group.displayedEvent)) { addEventToGroup(event, group, pos); continue; } } // else, we just create a new group beginInsertRows(QModelIndex(), pos, pos); HistoryEventGroup group; group.displayedEvent = event; group.events << event; mEventGroups.insert(pos, group); endInsertRows(); } } void HistoryGroupedEventsModel::onEventsModified(const History::Events &events) { // FIXME: we are not yet handling events changing the property used for sorting // so for now the behavior is to find the item and check if it needs inserting or // updating in the group, which is exactly what onEventsAdded() does, so: onEventsAdded(events); } void HistoryGroupedEventsModel::onEventsRemoved(const History::Events &events) { Q_FOREACH(const History::Event &event, events) { int pos = positionForItem(event.properties()); if (pos < 0 || pos >= rowCount()) { continue; } HistoryEventGroup &group = mEventGroups[pos]; if (!group.events.contains(event)) { continue; } removeEventFromGroup(event, group, pos); } } bool HistoryGroupedEventsModel::areOfSameGroup(const History::Event &event1, const History::Event &event2) { QVariantMap props1 = event1.properties(); QVariantMap props2 = event2.properties(); Q_FOREACH(const QString &property, mGroupingProperties) { // first check if the property exists in the maps if (!props1.contains(property) || !props2.contains(property)) { return false; } // get one of the account ids to use for comparing QString accountId = props1[History::FieldAccountId].toString(); // now check if the values are the same if (property == History::FieldParticipants) { if (!History::Utils::compareParticipants(event1.participants().identifiers(), event2.participants().identifiers(), History::Utils::matchFlagsForAccount(accountId))) { return false; } } else if (props1[property] != props2[property]) { return false; } } // if it didn't fail before, the events are indeed of the same group return true; } void HistoryGroupedEventsModel::addEventToGroup(const History::Event &event, HistoryEventGroup &group, int row) { if (!group.events.contains(event)) { // insert the event in the correct position according to the sort criteria bool append = true; for (int i = 0; i < group.events.count(); ++i) { History::Event &otherEvent = group.events[i]; if (isAscending() ? lessThan(event.properties(), otherEvent.properties()) : lessThan(otherEvent.properties(), event.properties())) { group.events.insert(i, event); append = false; break; } } // if it is not above any item, just append it if (append) { group.events.append(event); } } // now check if the displayed event should be updated History::Event &firstEvent = group.events.first(); if (group.displayedEvent != firstEvent) { group.displayedEvent = firstEvent; QModelIndex idx(index(row)); Q_EMIT dataChanged(idx, idx); } } void HistoryGroupedEventsModel::removeEventFromGroup(const History::Event &event, HistoryEventGroup &group, int row) { if (group.events.contains(event)) { group.events.removeOne(event); } if (group.events.isEmpty()) { beginRemoveRows(QModelIndex(), row, row); mEventGroups.removeAt(row); endRemoveRows(); return; } if (group.displayedEvent == event) { // check what is the event that should be displayed group.displayedEvent = group.events.first(); Q_FOREACH(const History::Event &other, group.events) { if (isAscending() ? lessThan(other.properties(), group.displayedEvent.properties()) : lessThan(group.displayedEvent.properties(), other.properties())) { group.displayedEvent = other; } } } QModelIndex idx = index(row); Q_EMIT dataChanged(idx, idx); } QVariant HistoryGroupedEventsModel::get(int row) const { if (row >= rowCount() || row < 0) { return QVariant(); } return data(index(row), EventsRole); } QStringList HistoryGroupedEventsModel::groupingProperties() const { return mGroupingProperties; } void HistoryGroupedEventsModel::setGroupingProperties(const QStringList &properties) { mGroupingProperties = properties; Q_EMIT groupingPropertiesChanged(); triggerQueryUpdate(); } history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlintersectionfilter.cpp0000644000015600001650000000227012642552153030554 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historyqmlintersectionfilter.h" #include "intersectionfilter.h" HistoryQmlIntersectionFilter::HistoryQmlIntersectionFilter(QObject *parent) : HistoryQmlCompoundFilter(parent) { } History::Filter HistoryQmlIntersectionFilter::filter() const { History::IntersectionFilter intersectionFilter; Q_FOREACH(HistoryQmlFilter *filter, mFilters) { intersectionFilter.append(filter->filter()); } return intersectionFilter; } history-service-0.1+16.04.20160104/Ubuntu/History/qmldir0000644000015600001650000000005112642552153023206 0ustar pbuserpbgroup00000000000000module Ubuntu.History plugin history-qml history-service-0.1+16.04.20160104/Ubuntu/History/CMakeLists.txt0000644000015600001650000000204712642552153024542 0ustar pbuserpbgroup00000000000000# QML plugin set(plugin_SRCS historyeventmodel.cpp historygroupedeventsmodel.cpp historygroupedthreadsmodel.cpp historymodel.cpp historyqmlfilter.cpp historyqmlintersectionfilter.cpp historyqmlplugin.cpp historyqmlsort.cpp historyqmltexteventattachment.cpp historyqmlunionfilter.cpp historythreadmodel.cpp ) set(plugin_HDRS historyeventmodel.h historygroupedeventsmodel.h historygroupedthreadsmodel.h historymodel.cpp historyqmlfilter.h historyqmlintersectionfilter.h historyqmlplugin.h historyqmlsort.h historyqmltexteventattachment.h historyqmlunionfilter.h historythreadmodel.h ) include_directories( ${CMAKE_SOURCE_DIR}/src ) add_library(history-qml SHARED ${plugin_SRCS} ${plugin_HDRS}) qt5_use_modules(history-qml Contacts Core Qml Quick) target_link_libraries(history-qml historyservice ) set(PLUGIN_DIR ${QT_INSTALL_QML}/Ubuntu/History) install(TARGETS history-qml DESTINATION ${PLUGIN_DIR}) install(FILES qmldir DESTINATION ${PLUGIN_DIR}) history-service-0.1+16.04.20160104/Ubuntu/History/historyqmlplugin.cpp0000644000015600001650000000422612642552153026141 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historyqmlplugin.h" #include "historyqmlfilter.h" #include "historyqmlintersectionfilter.h" #include "historyqmlsort.h" #include "historyqmlunionfilter.h" #include "historythreadmodel.h" #include "historygroupedthreadsmodel.h" #include "historyeventmodel.h" #include "historygroupedeventsmodel.h" #include "historyqmltexteventattachment.h" #include #include void HistoryQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { // FIXME: check what to do here Q_UNUSED(engine) Q_UNUSED(uri) } void HistoryQmlPlugin::registerTypes(const char *uri) { // @uri History qmlRegisterType(uri, 0, 1, "HistoryEventModel"); qmlRegisterType(uri, 0, 1, "HistoryGroupedEventsModel"); qmlRegisterType(uri, 0, 1, "HistoryThreadModel"); qmlRegisterType(uri, 0, 1, "HistoryGroupedThreadsModel"); qmlRegisterType(uri, 0, 1, "HistoryFilter"); qmlRegisterType(uri, 0, 1, "HistoryIntersectionFilter"); qmlRegisterType(uri, 0, 1, "HistorySort"); qmlRegisterType(uri, 0, 1, "HistoryUnionFilter"); qmlRegisterUncreatableType(uri, 0, 1, "HistoryTextEventAttachment", ""); qmlRegisterUncreatableType(uri, 0, 1, "QAbstractItemModel", ""); } history-service-0.1+16.04.20160104/Ubuntu/History/historyqmltexteventattachment.h0000644000015600001650000000416012642552153030404 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * Tiago Salem Herrmann * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYQMLTEXTEVENTATTACHMENT_H #define HISTORYQMLTEXTEVENTATTACHMENT_H #include #include #include "historyqmltexteventattachment.h" #include "types.h" #include "texteventattachment.h" class HistoryQmlTextEventAttachment : public QObject { Q_OBJECT Q_ENUMS(AttachmentFlag) Q_PROPERTY(QString accountId READ accountId CONSTANT) Q_PROPERTY(QString threadId READ threadId CONSTANT) Q_PROPERTY(QString eventId READ eventId CONSTANT) Q_PROPERTY(QString attachmentId READ attachmentId CONSTANT) Q_PROPERTY(QString contentType READ contentType CONSTANT) Q_PROPERTY(QString filePath READ filePath CONSTANT) Q_PROPERTY(int status READ status CONSTANT) public: enum AttachmentFlag { AttachmentDownloaded = History::AttachmentDownloaded, AttachmentPending = History::AttachmentPending, AttachmentError = History::AttachmentError }; explicit HistoryQmlTextEventAttachment(const History::TextEventAttachment &attachment, QObject *parent = 0); QString accountId() const; QString threadId() const; QString eventId() const; QString attachmentId() const; QString contentType() const; QString filePath() const; int status() const; protected: History::TextEventAttachment mAttachment; }; #endif // HISTORYQMLTEXTEVENTATTACHMENT_H history-service-0.1+16.04.20160104/Ubuntu/History/historymodel.cpp0000644000015600001650000002744112642552153025235 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historymodel.h" #include "historyqmlfilter.h" #include "historyqmlsort.h" #include "contactmatcher_p.h" #include "phoneutils_p.h" #include "thread.h" #include "textevent.h" #include "manager.h" #include "utils_p.h" #include #include #include HistoryModel::HistoryModel(QObject *parent) : QAbstractListModel(parent), mFilter(0), mSort(new HistoryQmlSort(this)), mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0), mWaitingForQml(false) { // configure the roles mRoles[AccountIdRole] = "accountId"; mRoles[ThreadIdRole] = "threadId"; mRoles[ParticipantsRole] = "participants"; mRoles[TypeRole] = "type"; mRoles[PropertiesRole] = "properties"; connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged())); connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged())); connect(this, SIGNAL(modelReset()), this, SIGNAL(countChanged())); // reset the view when the service is stopped or started connect(History::Manager::instance(), SIGNAL(serviceRunningChanged()), this, SLOT(triggerQueryUpdate())); // create the view and get some objects triggerQueryUpdate(); } bool HistoryModel::canFetchMore(const QModelIndex &parent) const { return false; } void HistoryModel::fetchMore(const QModelIndex &parent) { Q_UNUSED(parent) // do nothing, just make the method invokable } QHash HistoryModel::roleNames() const { return mRoles; } QVariant HistoryModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return QVariant(); } QVariantMap properties = index.data(PropertiesRole).toMap(); QVariant result; switch (role) { case AccountIdRole: result = properties[History::FieldAccountId]; break; case ThreadIdRole: result = properties[History::FieldThreadId]; break; case TypeRole: result = properties[History::FieldType]; break; case ParticipantsRole: if (mMatchContacts) { result = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(), History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers()); } else { //FIXME: handle contact changes result = properties[History::FieldParticipants]; } break; } return result; } HistoryQmlFilter *HistoryModel::filter() const { return mFilter; } void HistoryModel::setFilter(HistoryQmlFilter *value) { if (mFilter) { mFilter->disconnect(this); } mFilter = value; if (mFilter) { connect(mFilter, SIGNAL(filterChanged()), SLOT(triggerQueryUpdate())); } Q_EMIT filterChanged(); triggerQueryUpdate(); } HistoryQmlSort *HistoryModel::sort() const { return mSort; } void HistoryModel::setSort(HistoryQmlSort *value) { // disconnect the previous sort if (mSort) { mSort->disconnect(this); } mSort = value; if (mSort) { connect(mSort, SIGNAL(sortChanged()), SLOT(triggerQueryUpdate())); } Q_EMIT sortChanged(); triggerQueryUpdate(); } HistoryModel::EventType HistoryModel::type() const { return mType; } void HistoryModel::setType(EventType value) { mType = value; Q_EMIT typeChanged(); triggerQueryUpdate(); } bool HistoryModel::matchContacts() const { return mMatchContacts; } void HistoryModel::setMatchContacts(bool value) { if (mMatchContacts == value) { return; } mMatchContacts = value; Q_EMIT matchContactsChanged(); if (mMatchContacts) { connect(History::ContactMatcher::instance(), SIGNAL(contactInfoChanged(QString,QString,QVariantMap)), SLOT(onContactInfoChanged(QString,QString,QVariantMap))); } else { History::ContactMatcher::instance()->disconnect(this); } // mark all indexes as changed if (rowCount() > 0) { Q_EMIT dataChanged(index(0), index(rowCount()-1)); } } QVariantMap HistoryModel::threadForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create) { if (participants.isEmpty()) { return QVariantMap(); } History::Thread thread = History::Manager::instance()->threadForParticipants(accountId, (History::EventType)eventType, participants, (History::MatchFlags)matchFlags, create); if (!thread.isNull()) { return thread.properties(); } return QVariantMap(); } QString HistoryModel::threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create) { if (participants.isEmpty()) { return QString::null; } History::Thread thread = History::Manager::instance()->threadForParticipants(accountId, (History::EventType)eventType, participants, (History::MatchFlags)matchFlags, create); if (!thread.isNull()) { return thread.threadId(); } return QString::null; } bool HistoryModel::writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message) { if (participants.isEmpty() || threadId.isEmpty() || accountId.isEmpty()) { return false; } History::TextEvent historyEvent = History::TextEvent(accountId, threadId, QString(QCryptographicHash::hash(QByteArray( QDateTime::currentDateTime().toString().toLatin1()), QCryptographicHash::Md5).toHex()), "self", QDateTime::currentDateTime(), false, message, History::MessageTypeInformation, History::MessageStatusUnknown, QDateTime::currentDateTime()); History::Events events; events << historyEvent; return History::Manager::instance()->writeEvents(events); } void HistoryModel::onContactInfoChanged(const QString &accountId, const QString &identifier, const QVariantMap &contactInfo) { Q_UNUSED(contactInfo) if (!mMatchContacts) { return; } QList changedIndexes; int count = rowCount(); for (int i = 0; i < count; ++i) { // WARNING: do not use mEvents directly to verify which indexes to change as there is the // HistoryGroupedEventsModel which is based on this model and handles the items in a different way QModelIndex idx = index(i); QVariantMap properties = idx.data(PropertiesRole).toMap(); History::Participants participants = History::Participants::fromVariantList(properties[History::FieldParticipants].toList()); Q_FOREACH(const History::Participant &participant, participants) { // FIXME: right now we might be grouping threads from different accounts, so we are not enforcing // the accountId to be the same as the one from the contact info, but maybe we need to do that // in the future? if (History::Utils::compareIds(accountId, participant.identifier(), identifier)) { changedIndexes << idx; } } } // now emit the dataChanged signal to all changed indexes Q_FOREACH(const QModelIndex &idx, changedIndexes) { Q_EMIT dataChanged(idx, idx); } } void HistoryModel::watchContactInfo(const QString &accountId, const QString &identifier, const QVariantMap ¤tInfo) { if (mMatchContacts) { History::ContactMatcher::instance()->watchIdentifier(accountId, identifier, currentInfo); } } void HistoryModel::timerEvent(QTimerEvent *event) { if (event->timerId() == mUpdateTimer && !mWaitingForQml) { killTimer(mUpdateTimer); mUpdateTimer = 0; updateQuery(); } } bool HistoryModel::lessThan(const QVariantMap &left, const QVariantMap &right) const { QVariant leftValue = left[sort()->sortField()]; QVariant rightValue = right[sort()->sortField()]; return leftValue < rightValue; } int HistoryModel::positionForItem(const QVariantMap &item) const { // do a binary search for the item position on the list int lowerBound = 0; int upperBound = rowCount() - 1; if (upperBound < 0) { return 0; } while (true) { int pos = (upperBound + lowerBound) / 2; const QVariantMap posItem = index(pos).data(PropertiesRole).toMap(); if (lowerBound == pos) { if (isAscending() ? lessThan(item, posItem) : lessThan(posItem, item)) { return pos; } } if (isAscending() ? lessThan(posItem, item) : lessThan(item, posItem)) { lowerBound = pos + 1; // its in the upper if (lowerBound > upperBound) { return pos += 1; } } else if (lowerBound > upperBound) { return pos; } else { upperBound = pos - 1; // its in the lower } } } bool HistoryModel::isAscending() const { return mSort && mSort->sort().sortOrder() == Qt::AscendingOrder; } QVariant HistoryModel::get(int row) const { QVariantMap data; QModelIndex idx = index(row, 0); if (idx.isValid()) { QHash roles = roleNames(); Q_FOREACH(int role, roles.keys()) { data.insert(roles[role], idx.data(role)); } } return data; } void HistoryModel::classBegin() { mWaitingForQml = true; } void HistoryModel::componentComplete() { mWaitingForQml = false; if (mUpdateTimer) { killTimer(mUpdateTimer); mUpdateTimer = 0; } updateQuery(); } void HistoryModel::triggerQueryUpdate() { if (mUpdateTimer) { killTimer(mUpdateTimer); } // delay the loading of the model data until the settings settle down mUpdateTimer = startTimer(100); } history-service-0.1+16.04.20160104/Ubuntu/CMakeLists.txt0000644000015600001650000000003212642552153023071 0ustar pbuserpbgroup00000000000000add_subdirectory(History) history-service-0.1+16.04.20160104/daemon/0000755000015600001650000000000012642552414020317 5ustar pbuserpbgroup00000000000000history-service-0.1+16.04.20160104/daemon/pluginmanager.cpp0000644000015600001650000000326412642552153023661 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "pluginmanager.h" #include "config.h" #include "plugin.h" #include #include #include namespace History { PluginManager::PluginManager(QObject *parent) : QObject(parent) { loadPlugins(); } PluginManager::~PluginManager() { } PluginManager *PluginManager::instance() { static PluginManager *self = new PluginManager(); return self; } Plugins PluginManager::plugins() { return mPlugins; } void PluginManager::loadPlugins() { QString pluginPath = qgetenv("HISTORY_PLUGIN_PATH"); if (pluginPath.isEmpty()) { pluginPath = HISTORY_PLUGIN_PATH; } QDir dir(pluginPath); Q_FOREACH (QString fileName, dir.entryList(QStringList() << "*.so", QDir::Files)) { QPluginLoader loader(dir.absoluteFilePath(fileName)); Plugin *plugin = qobject_cast(loader.instance()); if (plugin) { mPlugins.append(PluginPtr(plugin)); } } } } history-service-0.1+16.04.20160104/daemon/history-daemon.conf0000644000015600001650000000067312642552153024136 0ustar pbuserpbgroup00000000000000description "history-daemon" author "Gustavo Pichorim Boiko " start on started address-book-service stop on runlevel [06] # history-daemon is a dbus-activated service, so in order to avoid risking having # two or more instances running, just trigger its dbus activation in this job exec dbus-send --session --dest=com.canonical.HistoryService /com/canonical/HistoryService org.freedesktop.DBus.Peer.GetMachineId history-service-0.1+16.04.20160104/daemon/pluginmanager.h0000644000015600001650000000225212642552153023322 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORY_PLUGINMANAGER_H #define HISTORY_PLUGINMANAGER_H #include #include "types.h" namespace History { class Plugin; class PluginManager : public QObject { Q_OBJECT public: ~PluginManager(); static PluginManager *instance(); Plugins plugins(); protected: void loadPlugins(); private: explicit PluginManager(QObject *parent = 0); Plugins mPlugins; }; } #endif // HISTORY_PLUGINMANAGER_H ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000history-service-0.1+16.04.20160104/daemon/org.freedesktop.Telepathy.Client.HistoryDaemonObserver.service.inhistory-service-0.1+16.04.20160104/daemon/org.freedesktop.Telepathy.Client.HistoryDaemonObserver.ser0000644000015600001650000000021612642552153033601 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=org.freedesktop.Telepathy.Client.HistoryDaemonObserver Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/history-daemon history-service-0.1+16.04.20160104/daemon/main.cpp0000644000015600001650000000250212642552153021746 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historydaemon.h" #include #include bool checkApplicationRunning() { bool result = false; QDBusReply reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(History::DBusService); if (reply.isValid()) { result = reply.value(); } return result; } int main(int argc, char **argv) { Tp::registerTypes(); Tp::enableWarnings(true); QCoreApplication app(argc, argv); if (checkApplicationRunning()) { return 1; } HistoryDaemon::instance(); return app.exec(); } history-service-0.1+16.04.20160104/daemon/textchannelobserver.h0000644000015600001650000000367512642552153024570 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 TEXTCHANNELOBSERVER_H #define TEXTCHANNELOBSERVER_H #include #include #include class TextChannelObserver : public QObject { Q_OBJECT public: explicit TextChannelObserver(QObject *parent = 0); public Q_SLOTS: void onTextChannelAvailable(Tp::TextChannelPtr textChannel); Q_SIGNALS: void messageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message); void messageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message); void messageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken); protected: void showNotificationForMessage(const Tp::ReceivedMessage &message); Tp::TextChannelPtr channelFromPath(const QString &path); protected Q_SLOTS: void onTextChannelInvalidated(); void onMessageReceived(const Tp::ReceivedMessage &message); void onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, const QString &sentMessageToken); void onPendingMessageRemoved(const Tp::ReceivedMessage &message); private: QList mChannels; }; #endif // TEXTCHANNELOBSERVER_H history-service-0.1+16.04.20160104/daemon/com.canonical.HistoryService.service.in0000644000015600001650000000016412642552153027774 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.HistoryService Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/history-daemon history-service-0.1+16.04.20160104/daemon/HistoryDaemonObserver.client0000644000015600001650000000137612642552153026023 0ustar pbuserpbgroup00000000000000[org.freedesktop.Telepathy.Client] Interfaces=org.freedesktop.Telepathy.Client.Observer; [org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 0] org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call1 org.freedesktop.Telepathy.Channel.TargetHandleType u=1 org.freedesktop.Telepathy.Channel.Type.Call1.InitialAudio b=true [org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 1] org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text [org.freedesktop.Telepathy.Client.Observer.Capabilities] org.freedesktop.Telepathy.Channel.Type.Call1/audio=true org.freedesktop.Telepathy.Channel.Type.Call1/audio/speex=true [org.freedesktop.Telepathy.Client.Observer] Recover=true history-service-0.1+16.04.20160104/daemon/callchannelobserver.cpp0000644000015600001650000000350512642552153025042 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "callchannelobserver.h" CallChannelObserver::CallChannelObserver(QObject *parent) : QObject(parent) { } void CallChannelObserver::onCallChannelAvailable(Tp::CallChannelPtr callChannel) { // save the timestamp as a property in the call channel callChannel->setProperty("timestamp", QDateTime::currentDateTime()); if (callChannel->callState() == Tp::CallStateActive) { callChannel->setProperty("activeTimestamp", QDateTime::currentDateTime()); } connect(callChannel.data(), SIGNAL(callStateChanged(Tp::CallState)), SLOT(onCallStateChanged(Tp::CallState))); mChannels.append(callChannel); } void CallChannelObserver::onCallStateChanged(Tp::CallState state) { Tp::CallChannel *channel = qobject_cast(sender()); if (!channel) { return; } switch (state) { case Tp::CallStateEnded: Q_EMIT callEnded(Tp::CallChannelPtr(channel)); break; case Tp::CallStateActive: channel->setProperty("activeTimestamp", QDateTime::currentDateTime()); break; } } history-service-0.1+16.04.20160104/daemon/historydaemon.h0000644000015600001650000000607512642552153023365 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYDAEMON_H #define HISTORYDAEMON_H #include #include #include #include "types.h" #include "textchannelobserver.h" #include "callchannelobserver.h" #include "historyservicedbus.h" #include "plugin.h" class HistoryDaemon : public QObject { Q_OBJECT public: ~HistoryDaemon(); static HistoryDaemon *instance(); static QStringList participantsFromChannel(const Tp::TextChannelPtr &textChannel); QVariantMap threadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants, History::MatchFlags matchFlags = History::MatchCaseSensitive, bool create = true); QString queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties); QString queryEvents(int type, const QVariantMap &sort, const QVariantMap &filter); QVariantMap getSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties); QVariantMap getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId); QVariantMap getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId); bool writeEvents(const QList &events); bool removeEvents(const QList &events); bool removeThreads(const QList &threads); private Q_SLOTS: void onObserverCreated(); void onCallEnded(const Tp::CallChannelPtr &channel); void onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message); void onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message); void onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken); protected: History::MatchFlags matchFlagsForChannel(const Tp::ChannelPtr &channel); QString hashThread(const QVariantMap &thread); private: HistoryDaemon(QObject *parent = 0); CallChannelObserver mCallObserver; TextChannelObserver mTextObserver; QMap mProtocolFlags; History::PluginPtr mBackend; HistoryServiceDBus mDBus; }; #endif history-service-0.1+16.04.20160104/daemon/history-daemon.desktop.in0000644000015600001650000000035612642552153025265 0ustar pbuserpbgroup00000000000000[Desktop Entry] Name=tr("History Service Daemon") Comment=tr("Listens for calls and messages and stores them.") Icon=history-daemon Exec=history-daemon Terminal=false Type=Application NoDisplay=true X-Ubuntu-Gettext-Domain=history-daemon history-service-0.1+16.04.20160104/daemon/HistoryService.xml0000644000015600001650000001675112642552153024035 0ustar pbuserpbgroup00000000000000 An interface to the history service history-service-0.1+16.04.20160104/daemon/historyservicedbus.cpp0000644000015600001650000001050212642552153024761 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historydaemon.h" #include "historyservicedbus.h" #include "historyserviceadaptor.h" #include "types.h" Q_DECLARE_METATYPE(QList< QVariantMap >) HistoryServiceDBus::HistoryServiceDBus(QObject *parent) : QObject(parent), mAdaptor(0) { qDBusRegisterMetaType >(); } bool HistoryServiceDBus::connectToBus() { bool ok = QDBusConnection::sessionBus().registerService(History::DBusService); if (!ok) { return false; } if (!mAdaptor) { mAdaptor = new HistoryServiceAdaptor(this); } return QDBusConnection::sessionBus().registerObject(History::DBusObjectPath, this); } void HistoryServiceDBus::notifyThreadsAdded(const QList &threads) { Q_EMIT ThreadsAdded(threads); } void HistoryServiceDBus::notifyThreadsModified(const QList &threads) { Q_EMIT ThreadsModified(threads); } void HistoryServiceDBus::notifyThreadsRemoved(const QList &threads) { Q_EMIT ThreadsRemoved(threads); } void HistoryServiceDBus::notifyEventsAdded(const QList &events) { Q_EMIT EventsAdded(events); } void HistoryServiceDBus::notifyEventsModified(const QList &events) { Q_EMIT EventsModified(events); } void HistoryServiceDBus::notifyEventsRemoved(const QList &events) { Q_EMIT EventsRemoved(events); } QVariantMap HistoryServiceDBus::ThreadForParticipants(const QString &accountId, int type, const QStringList &participants, int matchFlags, bool create) { return HistoryDaemon::instance()->threadForParticipants(accountId, (History::EventType) type, participants, (History::MatchFlags) matchFlags, create); } bool HistoryServiceDBus::WriteEvents(const QList &events) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->writeEvents(events); } bool HistoryServiceDBus::RemoveThreads(const QList &threads) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->removeThreads(threads); } bool HistoryServiceDBus::RemoveEvents(const QList &events) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->removeEvents(events); } QString HistoryServiceDBus::QueryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->queryThreads(type, sort, filter, properties); } QString HistoryServiceDBus::QueryEvents(int type, const QVariantMap &sort, const QVariantMap &filter) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->queryEvents(type, sort, filter); } QVariantMap HistoryServiceDBus::GetSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->getSingleThread(type, accountId, threadId, properties); } QVariantMap HistoryServiceDBus::GetSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId) { qDebug() << __PRETTY_FUNCTION__; return HistoryDaemon::instance()->getSingleEvent(type, accountId, threadId, eventId); } history-service-0.1+16.04.20160104/daemon/callchannelobserver.h0000644000015600001650000000242112642552153024503 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 CALLCHANNELOBSERVER_H #define CALLCHANNELOBSERVER_H #include #include class CallChannelObserver : public QObject { Q_OBJECT public: explicit CallChannelObserver(QObject *parent = 0); public Q_SLOTS: void onCallChannelAvailable(Tp::CallChannelPtr callChannel); Q_SIGNALS: void callEnded(Tp::CallChannelPtr callChannel); protected Q_SLOTS: void onCallStateChanged(Tp::CallState state); private: QList mChannels; }; #endif // CALLCHANNELOBSERVER_H history-service-0.1+16.04.20160104/daemon/textchannelobserver.cpp0000644000015600001650000000603712642552153025116 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "textchannelobserver.h" #include #include TextChannelObserver::TextChannelObserver(QObject *parent) : QObject(parent) { } void TextChannelObserver::onTextChannelAvailable(Tp::TextChannelPtr textChannel) { connect(textChannel.data(), SIGNAL(invalidated(Tp::DBusProxy*,const QString&, const QString&)), SLOT(onTextChannelInvalidated())); connect(textChannel.data(), SIGNAL(messageReceived(const Tp::ReceivedMessage&)), SLOT(onMessageReceived(const Tp::ReceivedMessage&))); connect(textChannel.data(), SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)), SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString))); connect(textChannel.data(), SIGNAL(pendingMessageRemoved(const Tp::ReceivedMessage&)), SLOT(onPendingMessageRemoved(const Tp::ReceivedMessage&))); // process the messages that are already pending in the channel Q_FOREACH(const Tp::ReceivedMessage &message, textChannel->messageQueue()) { Q_EMIT messageReceived(textChannel, message); } mChannels.append(textChannel); } void TextChannelObserver::onTextChannelInvalidated() { Tp::TextChannelPtr textChannel(qobject_cast(sender())); mChannels.removeAll(textChannel); } void TextChannelObserver::onMessageReceived(const Tp::ReceivedMessage &message) { Tp::TextChannelPtr textChannel(qobject_cast(sender())); if (textChannel.isNull()) { return; } Q_EMIT messageReceived(textChannel, message); } void TextChannelObserver::onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, const QString &sentMessageToken) { Tp::TextChannelPtr textChannel(qobject_cast(sender())); if (textChannel.isNull()) { return; } if (message.parts()[0]["skip-storage"].variant().toBool()) { return; } Q_EMIT messageSent(textChannel, message, sentMessageToken); } void TextChannelObserver::onPendingMessageRemoved(const Tp::ReceivedMessage &message) { Tp::TextChannelPtr textChannel(qobject_cast(sender())); if (textChannel.isNull()) { return; } Q_EMIT messageRead(textChannel, message); } history-service-0.1+16.04.20160104/daemon/historyservicedbus.h0000644000015600001650000000554512642552153024441 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 HISTORYSERVICEDBUS_H #define HISTORYSERVICEDBUS_H #include #include #include "types.h" class HistoryServiceAdaptor; class HistoryServiceDBus : public QObject, public QDBusContext { Q_OBJECT public: explicit HistoryServiceDBus(QObject *parent = 0); bool connectToBus(); void notifyThreadsAdded(const QList &threads); void notifyThreadsModified(const QList &threads); void notifyThreadsRemoved(const QList &threads); void notifyEventsAdded(const QList &events); void notifyEventsModified(const QList &events); void notifyEventsRemoved(const QList &events); // functions exposed on DBUS QVariantMap ThreadForParticipants(const QString &accountId, int type, const QStringList &participants, int matchFlags, bool create); bool WriteEvents(const QList &events); bool RemoveThreads(const QList &threads); bool RemoveEvents(const QList &events); // views QString QueryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties); QString QueryEvents(int type, const QVariantMap &sort, const QVariantMap &filter); QVariantMap GetSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties); QVariantMap GetSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId); Q_SIGNALS: // signals that will be relayed into the bus void ThreadsAdded(const QList &threads); void ThreadsModified(const QList &threads); void ThreadsRemoved(const QList &threads); void EventsAdded(const QList &events); void EventsModified(const QList &events); void EventsRemoved(const QList &events); private: HistoryServiceAdaptor *mAdaptor; }; #endif // HISTORYSERVICEDBUS_H history-service-0.1+16.04.20160104/daemon/CMakeLists.txt0000644000015600001650000000366112642552153023065 0ustar pbuserpbgroup00000000000000 set(qt_SRCS callchannelobserver.cpp historydaemon.cpp historyservicedbus.cpp pluginmanager.cpp textchannelobserver.cpp ) set(daemon_SRCS main.cpp ${qt_SRCS}) include_directories( ${TP_QT5_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) qt5_add_dbus_adaptor(daemon_SRCS HistoryService.xml historyservicedbus.h HistoryServiceDBus) add_executable(history-daemon ${daemon_SRCS} ${daemon_HDRS}) qt5_use_modules(history-daemon Core DBus) target_link_libraries(history-daemon ${TP_QT5_LIBRARIES} historyservice ) # Handle i18n in desktop files set(desktop_FILES history-daemon.desktop) foreach(DESKTOP_FILE ${desktop_FILES}) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE}) file(STRINGS ${DESKTOP_FILE}.in DESKTOP_FILE_CONTENTS) foreach(LINE ${DESKTOP_FILE_CONTENTS}) string(REGEX REPLACE "tr\\\(\"(.*)\"\\\)" "\\1" LINE "${LINE}") file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE} "${LINE}\n") endforeach(LINE) endforeach(DESKTOP_FILE) configure_file(org.freedesktop.Telepathy.Client.HistoryDaemonObserver.service.in org.freedesktop.Telepathy.Client.HistoryDaemonObserver.service) configure_file(com.canonical.HistoryService.service.in com.canonical.HistoryService.service) install(TARGETS history-daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Telepathy.Client.HistoryDaemonObserver.service ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.HistoryService.service DESTINATION share/dbus-1/services ) install(FILES HistoryDaemonObserver.client DESTINATION share/telepathy/clients) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/history-daemon.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/history-daemon.conf DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/upstart/sessions/) history-service-0.1+16.04.20160104/daemon/historydaemon.cpp0000644000015600001650000006740212642552153023721 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2015 Canonical, Ltd. * * Authors: * Gustavo Pichorim Boiko * * This file is part of history-service. * * history-service 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; version 3. * * history-service 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 "historydaemon.h" #include "telepathyhelper_p.h" #include "filter.h" #include "sort.h" #include "pluginmanager.h" #include "plugin.h" #include "pluginthreadview.h" #include "plugineventview.h" #include #include #include #include HistoryDaemon::HistoryDaemon(QObject *parent) : QObject(parent), mCallObserver(this), mTextObserver(this) { // get the first plugin if (!History::PluginManager::instance()->plugins().isEmpty()) { mBackend = History::PluginManager::instance()->plugins().first(); } // FIXME: maybe we should only set the plugin as ready after the contact cache was generated connect(History::TelepathyHelper::instance(), &History::TelepathyHelper::setupReady, [&]() { mBackend->generateContactCache(); mDBus.connectToBus(); }); connect(History::TelepathyHelper::instance(), SIGNAL(channelObserverCreated(ChannelObserver*)), SLOT(onObserverCreated())); History::TelepathyHelper::instance()->registerChannelObserver(); connect(&mCallObserver, SIGNAL(callEnded(Tp::CallChannelPtr)), SLOT(onCallEnded(Tp::CallChannelPtr))); connect(&mTextObserver, SIGNAL(messageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)), SLOT(onMessageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage))); connect(&mTextObserver, SIGNAL(messageSent(Tp::TextChannelPtr,Tp::Message,QString)), SLOT(onMessageSent(Tp::TextChannelPtr,Tp::Message,QString))); connect(&mTextObserver, SIGNAL(messageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)), SLOT(onMessageRead(Tp::TextChannelPtr,Tp::ReceivedMessage))); // FIXME: we need to do this in a better way, but for now this should do mProtocolFlags["ofono"] = History::MatchPhoneNumber; mProtocolFlags["multimedia"] = History::MatchPhoneNumber; } HistoryDaemon::~HistoryDaemon() { } HistoryDaemon *HistoryDaemon::instance() { static HistoryDaemon *self = new HistoryDaemon(); return self; } QStringList HistoryDaemon::participantsFromChannel(const Tp::TextChannelPtr &textChannel) { QStringList participants; Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) { participants << contact->id(); } if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact && textChannel->targetContact() == textChannel->connection()->selfContact()) { participants << textChannel->targetContact()->id(); } return participants; } QVariantMap HistoryDaemon::threadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants, History::MatchFlags matchFlags, bool create) { if (!mBackend) { return QVariantMap(); } QVariantMap thread = mBackend->threadForParticipants(accountId, type, participants, matchFlags); if (thread.isEmpty() && create) { thread = mBackend->createThreadForParticipants(accountId, type, participants); if (!thread.isEmpty()) { mDBus.notifyThreadsAdded(QList() << thread); } } return thread; } QString HistoryDaemon::queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties) { if (!mBackend) { return QString::null; } History::Sort theSort = History::Sort::fromProperties(sort); History::Filter theFilter = History::Filter::fromProperties(filter); History::PluginThreadView *view = mBackend->queryThreads((History::EventType)type, theSort, theFilter, properties); if (!view) { return QString::null; } // FIXME: maybe we should keep a list of views to manually remove them at some point? view->setParent(this); return view->objectPath(); } QString HistoryDaemon::queryEvents(int type, const QVariantMap &sort, const QVariantMap &filter) { if (!mBackend) { return QString::null; } History::Sort theSort = History::Sort::fromProperties(sort); History::Filter theFilter = History::Filter::fromProperties(filter); History::PluginEventView *view = mBackend->queryEvents((History::EventType)type, theSort, theFilter); if (!view) { return QString::null; } // FIXME: maybe we should keep a list of views to manually remove them at some point? view->setParent(this); return view->objectPath(); } QVariantMap HistoryDaemon::getSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties) { if (!mBackend) { return QVariantMap(); } return mBackend->getSingleThread((History::EventType)type, accountId, threadId, properties); } QVariantMap HistoryDaemon::getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId) { if (!mBackend) { return QVariantMap(); } return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId); } bool HistoryDaemon::writeEvents(const QList &events) { if (!mBackend) { return false; } QList newEvents; QList modifiedEvents; QMap threads; mBackend->beginBatchOperation(); Q_FOREACH(const QVariantMap &event, events) { History::EventType type = (History::EventType) event[History::FieldType].toInt(); History::EventWriteResult result; // get the threads for the events to notify their modifications QString accountId = event[History::FieldAccountId].toString(); QString threadId = event[History::FieldThreadId].toString(); QVariantMap savedEvent = event; // and finally write the event switch (type) { case History::EventTypeText: result = mBackend->writeTextEvent(savedEvent); break; case History::EventTypeVoice: result = mBackend->writeVoiceEvent(savedEvent); break; } // only get the thread AFTER the event is written to make sure it is up-to-date QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap()); QString hash = hashThread(thread); threads[hash] = thread; // set the participants field in the event savedEvent[History::FieldParticipants] = thread[History::FieldParticipants]; // check if the event was a new one or a modification to an existing one switch (result) { case History::EventWriteCreated: newEvents << savedEvent; break; case History::EventWriteModified: modifiedEvents << savedEvent; break; case History::EventWriteError: mBackend->rollbackBatchOperation(); return false; } } mBackend->endBatchOperation(); // and last but not least, notify the results if (!newEvents.isEmpty()) { mDBus.notifyEventsAdded(newEvents); } if (!modifiedEvents.isEmpty()) { mDBus.notifyEventsModified(modifiedEvents); } if (!threads.isEmpty()) { mDBus.notifyThreadsModified(threads.values()); } return true; } bool HistoryDaemon::removeEvents(const QList &events) { qDebug() << __PRETTY_FUNCTION__; if (!mBackend) { return false; } mBackend->beginBatchOperation(); Q_FOREACH(const QVariantMap &event, events) { History::EventType type = (History::EventType) event[History::FieldType].toInt(); bool success = true; switch (type) { case History::EventTypeText: success = mBackend->removeTextEvent(event); break; case History::EventTypeVoice: success = mBackend->removeVoiceEvent(event); break; } if (!success) { mBackend->rollbackBatchOperation(); return false; } } // now we need to get all the threads that were affected by the removal of events // this loop needs to be separate from the item removal loop because we rely on the // count property of threads to decide if they were just modified or if they need to // be removed. QMap removedThreads; QMap modifiedThreads; Q_FOREACH(const QVariantMap &event, events) { History::EventType type = (History::EventType) event[History::FieldType].toInt(); QString accountId = event[History::FieldAccountId].toString(); QString threadId = event[History::FieldThreadId].toString(); QVariantMap thread = mBackend->getSingleThread(type, accountId, threadId, QVariantMap()); if (thread.isEmpty()) { continue; } QString hash = hashThread(thread); if (thread[History::FieldCount].toInt() > 0) { // the thread still has items and we should notify it was modified modifiedThreads[hash] = thread; } else { removedThreads[hash] = thread; } } // finally remove the threads that are now empty Q_FOREACH(const QVariantMap &thread, removedThreads.values()) { // the thread is now empty and needs to be removed if (!mBackend->removeThread(thread)) { mBackend->rollbackBatchOperation(); return false; } } mBackend->endBatchOperation(); mDBus.notifyEventsRemoved(events); if (!removedThreads.isEmpty()) { mDBus.notifyThreadsRemoved(removedThreads.values()); } if (!modifiedThreads.isEmpty()) { mDBus.notifyThreadsModified(modifiedThreads.values()); } return true; } bool HistoryDaemon::removeThreads(const QList &threads) { qDebug() << __PRETTY_FUNCTION__; if (!mBackend) { return false; } // In order to remove a thread all we have to do is to remove all its items // then it is going to be removed by removeEvents() once it detects the thread is // empty. QList events; QMap removedEmptyThreads; Q_FOREACH(const QVariantMap &thread, threads) { QList thisEvents = mBackend->eventsForThread(thread); if (thisEvents.isEmpty()) { mBackend->beginBatchOperation(); if (!mBackend->removeThread(thread)) { mBackend->rollbackBatchOperation(); return false; } mBackend->endBatchOperation(); QString hash = hashThread(thread); removedEmptyThreads[hash] = thread; continue; } events += thisEvents; } if (!removedEmptyThreads.isEmpty()) { mDBus.notifyThreadsRemoved(removedEmptyThreads.values()); } if (events.size() > 0) { if(removeEvents(events)) { return true; } } return false; } void HistoryDaemon::onObserverCreated() { qDebug() << __PRETTY_FUNCTION__; History::ChannelObserver *observer = History::TelepathyHelper::instance()->channelObserver(); connect(observer, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)), &mCallObserver, SLOT(onCallChannelAvailable(Tp::CallChannelPtr))); connect(observer, SIGNAL(textChannelAvailable(Tp::TextChannelPtr)), &mTextObserver, SLOT(onTextChannelAvailable(Tp::TextChannelPtr))); } void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel) { qDebug() << __PRETTY_FUNCTION__; QStringList participants; Q_FOREACH(const Tp::ContactPtr contact, channel->remoteMembers()) { participants << contact->id(); } // it shouldn't happen, but in case it does, we won't crash if (participants.isEmpty()) { qWarning() << "Participants list was empty for call channel" << channel; return; } QString accountId = channel->property(History::FieldAccountId).toString(); QVariantMap thread = threadForParticipants(accountId, History::EventTypeVoice, participants, matchFlagsForChannel(channel), true); // fill the call info QDateTime timestamp = channel->property(History::FieldTimestamp).toDateTime(); // FIXME: check if checking for isRequested() is enough bool incoming = !channel->isRequested(); int duration; bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer; if (!missed) { QDateTime activeTime = channel->property("activeTimestamp").toDateTime(); duration = activeTime.secsTo(QDateTime::currentDateTime()); } QString eventId = QString("%1:%2").arg(thread[History::FieldThreadId].toString()).arg(timestamp.toString()); QVariantMap event; event[History::FieldType] = History::EventTypeVoice; event[History::FieldAccountId] = thread[History::FieldAccountId]; event[History::FieldThreadId] = thread[History::FieldThreadId]; event[History::FieldEventId] = eventId; event[History::FieldSenderId] = incoming ? channel->initiatorContact()->id() : "self"; event[History::FieldTimestamp] = timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz"); event[History::FieldNewEvent] = missed; // only mark as a new (unseen) event if it is a missed call event[History::FieldMissed] = missed; event[History::FieldDuration] = duration; // FIXME: check what to do when there are more than just one remote participant event[History::FieldRemoteParticipant] = participants[0]; writeEvents(QList() << event); } void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message) { qDebug() << __PRETTY_FUNCTION__; QString eventId; Tp::MessagePart header = message.header(); QString senderId; History::MessageStatus status = History::MessageStatusUnknown; if (message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) { senderId = "self"; status = History::MessageStatusDelivered; } else { senderId = message.sender()->id(); } if (message.messageToken().isEmpty()) { eventId = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); } else { eventId = message.messageToken(); } // ignore delivery reports for now. // FIXME: maybe we should set the readTimestamp when a delivery report is received if (message.isRescued()) { return; } if (message.isDeliveryReport() && message.deliveryDetails().hasOriginalToken()) { // at this point we assume the delivery report is for a message that was already // sent and properly saved at our database, so we can safely get it here to update QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.deliveryDetails().originalToken()); if (textEvent.isEmpty()) { qWarning() << "Cound not find the original event to update with delivery details."; return; } History::MessageStatus status; switch (message.deliveryDetails().status()) { case Tp::DeliveryStatusAccepted: status = History::MessageStatusAccepted; break; case Tp::DeliveryStatusDeleted: status = History::MessageStatusDeleted; break; case Tp::DeliveryStatusDelivered: status = History::MessageStatusDelivered; break; case Tp::DeliveryStatusPermanentlyFailed: status = History::MessageStatusPermanentlyFailed; break; case Tp::DeliveryStatusRead: status = History::MessageStatusRead; break; case Tp::DeliveryStatusTemporarilyFailed: status = History::MessageStatusTemporarilyFailed; break; case Tp::DeliveryStatusUnknown: status = History::MessageStatusUnknown; break; } textEvent[History::FieldMessageStatus] = (int) status; if (!writeEvents(QList() << textEvent)) { qWarning() << "Failed to save the new message status!"; } return; } QStringList participants = participantsFromChannel(textChannel); QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(), History::EventTypeText, participants, matchFlagsForChannel(textChannel), true); int count = 1; QList attachments; History::MessageType type = History::MessageTypeText; QString subject; if (message.hasNonTextContent()) { QString normalizedAccountId = QString(QCryptographicHash::hash(thread[History::FieldAccountId].toString().toLatin1(), QCryptographicHash::Md5).toHex()); QString normalizedThreadId = QString(QCryptographicHash::hash(thread[History::FieldThreadId].toString().toLatin1(), QCryptographicHash::Md5).toHex()); QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex()); QString mmsStoragePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); type = History::MessageTypeMultiPart; subject = message.header()["subject"].variant().toString(); QDir dir(mmsStoragePath); if (!dir.exists("history-service") && !dir.mkpath("history-service")) { qDebug() << "Failed to create dir"; return; } dir.cd("history-service"); Q_FOREACH(const Tp::MessagePart &part, message.parts()) { // ignore the header part if (part["content-type"].variant().toString().isEmpty()) { continue; } mmsStoragePath = dir.absoluteFilePath(QString("attachments/%1/%2/%3/"). arg(normalizedAccountId, normalizedThreadId, normalizedEventId)); QFile file(mmsStoragePath+QString::number(count++)); if (!dir.mkpath(mmsStoragePath) || !file.open(QIODevice::WriteOnly)) { qWarning() << "Failed to save attachment"; continue; } file.write(part["content"].variant().toByteArray()); file.close(); QVariantMap attachment; attachment[History::FieldAccountId] = thread[History::FieldAccountId]; attachment[History::FieldThreadId] = thread[History::FieldThreadId]; attachment[History::FieldEventId] = eventId; attachment[History::FieldAttachmentId] = part["identifier"].variant(); attachment[History::FieldContentType] = part["content-type"].variant(); attachment[History::FieldFilePath] = file.fileName(); attachment[History::FieldStatus] = (int) History::AttachmentDownloaded; attachments << attachment; } } QVariantMap event; event[History::FieldType] = History::EventTypeText; event[History::FieldAccountId] = thread[History::FieldAccountId]; event[History::FieldThreadId] = thread[History::FieldThreadId]; event[History::FieldEventId] = eventId; event[History::FieldSenderId] = senderId; event[History::FieldTimestamp] = message.received().toString("yyyy-MM-ddTHH:mm:ss.zzz"); event[History::FieldNewEvent] = true; // message is always unread until it reaches HistoryDaemon::onMessageRead event[History::FieldMessage] = message.text(); event[History::FieldMessageType] = (int)type; event[History::FieldMessageStatus] = (int)status; event[History::FieldReadTimestamp] = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); event[History::FieldSubject] = subject; event[History::FieldAttachments] = QVariant::fromValue(attachments); writeEvents(QList() << event); } QVariantMap HistoryDaemon::getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId) { QStringList participants = participantsFromChannel(textChannel); QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(), History::EventTypeText, participants, matchFlagsForChannel(textChannel), false); if (thread.isEmpty()) { qWarning() << "Cound not find the thread related to this eventId."; return QVariantMap(); } QVariantMap textEvent = getSingleEvent((int)History::EventTypeText, textChannel->property(History::FieldAccountId).toString(), thread[History::FieldThreadId].toString(), messageId); return textEvent; } void HistoryDaemon::onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message) { QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.messageToken()); if (textEvent.isEmpty()) { qWarning() << "Cound not find the original event to update with newEvent = false."; return; } textEvent[History::FieldNewEvent] = false; if (!writeEvents(QList() << textEvent)) { qWarning() << "Failed to save the new message status!"; } } void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken) { qDebug() << __PRETTY_FUNCTION__; QStringList participants = participantsFromChannel(textChannel); QList attachments; History::MessageType type = History::MessageTypeText; int count = 1; QString subject; QString eventId; if (messageToken.isEmpty()) { eventId = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); } else { eventId = messageToken; } QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(), History::EventTypeText, participants, matchFlagsForChannel(textChannel), true); if (message.hasNonTextContent()) { QString normalizedAccountId = QString(QCryptographicHash::hash(thread[History::FieldAccountId].toString().toLatin1(), QCryptographicHash::Md5).toHex()); QString normalizedThreadId = QString(QCryptographicHash::hash(thread[History::FieldThreadId].toString().toLatin1(), QCryptographicHash::Md5).toHex()); QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex()); QString mmsStoragePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); type = History::MessageTypeMultiPart; subject = message.header()["subject"].variant().toString(); QDir dir(mmsStoragePath); if (!dir.exists("history-service") && !dir.mkpath("history-service")) { qDebug() << "Failed to create dir"; return; } dir.cd("history-service"); Q_FOREACH(const Tp::MessagePart &part, message.parts()) { // ignore the header part if (part["content-type"].variant().toString().isEmpty()) { continue; } mmsStoragePath = dir.absoluteFilePath(QString("attachments/%1/%2/%3/"). arg(normalizedAccountId, normalizedThreadId, normalizedEventId)); QFile file(mmsStoragePath+QString::number(count++)); if (!dir.mkpath(mmsStoragePath) || !file.open(QIODevice::WriteOnly)) { qWarning() << "Failed to save attachment"; continue; } file.write(part["content"].variant().toByteArray()); file.close(); QVariantMap attachment; attachment[History::FieldAccountId] = thread[History::FieldAccountId]; attachment[History::FieldThreadId] = thread[History::FieldThreadId]; attachment[History::FieldEventId] = eventId; attachment[History::FieldAttachmentId] = part["identifier"].variant(); attachment[History::FieldContentType] = part["content-type"].variant(); attachment[History::FieldFilePath] = file.fileName(); attachment[History::FieldStatus] = (int) History::AttachmentDownloaded; attachments << attachment; } } QVariantMap event; event[History::FieldType] = History::EventTypeText; event[History::FieldAccountId] = thread[History::FieldAccountId]; event[History::FieldThreadId] = thread[History::FieldThreadId]; event[History::FieldEventId] = eventId; event[History::FieldSenderId] = "self"; event[History::FieldTimestamp] = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); // FIXME: check why message.sent() is empty event[History::FieldNewEvent] = false; // outgoing messages are never new (unseen) event[History::FieldMessage] = message.text(); event[History::FieldMessageType] = type; if (textChannel->deliveryReportingSupport() & Tp::DeliveryReportingSupportFlagReceiveSuccesses) { event[History::FieldMessageStatus] = (int)History::MessageStatusUnknown; } else { event[History::FieldMessageStatus] = (int)History::MessageStatusAccepted; } event[History::FieldReadTimestamp] = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); event[History::FieldSubject] = ""; event[History::FieldAttachments] = QVariant::fromValue(attachments); writeEvents(QList() << event); } History::MatchFlags HistoryDaemon::matchFlagsForChannel(const Tp::ChannelPtr &channel) { QString protocol = channel->connection()->protocolName(); if (mProtocolFlags.contains(protocol)) { return mProtocolFlags[protocol]; } // default to this value return History::MatchCaseSensitive; } QString HistoryDaemon::hashThread(const QVariantMap &thread) { QString hash = QString::number(thread[History::FieldType].toInt()); hash += "#-#" + thread[History::FieldAccountId].toString(); hash += "#-#" + thread[History::FieldThreadId].toString(); return hash; } history-service-0.1+16.04.20160104/TODO0000644000015600001650000000005712642552153017546 0ustar pbuserpbgroup00000000000000- Use implicit sharing for Threads and Events. history-service-0.1+16.04.20160104/CMakeLists.txt0000644000015600001650000000706412642552153021623 0ustar pbuserpbgroup00000000000000project(history-service) cmake_minimum_required(VERSION 2.8) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) # Standard install paths include(GNUInstallDirs) # Check for include files include(CheckIncludeFileCXX) include(CheckIncludeFile) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) find_package(Qt5Contacts) find_package(Qt5DBus) find_package(Qt5Qml) find_package(Qt5Quick) find_package(Qt5Test) find_package(LibPhoneNumber REQUIRED) include(qt5) if(NOT CMAKE_CROSSCOMPILING) find_program(QMAKE_EXECUTABLE qmake) if(QMAKE_EXECUTABLE STREQUAL "QMAKE_EXECUTABLE-NOTFOUND") message(FATAL_ERROR "qmake not found") endif() execute_process( COMMAND ${QMAKE_EXECUTABLE} -query QT_INSTALL_QML RESULT_VARIABLE RESULT OUTPUT_VARIABLE QT_INSTALL_QML OUTPUT_STRIP_TRAILING_WHITESPACE ) if(NOT RESULT EQUAL 0) message(FATAL_ERROR "Failed to determine QT_INSTALL_QML from qmake") endif() else() # qmake isn't multi-arch aware as it installs arch-specific mkspec files # in to /usr/share, so we can't use it here (we'd need a qmake binary # for the host arch using data for the target arch) set(QT_INSTALL_QML "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt5/qml") endif() find_package(PkgConfig REQUIRED) pkg_check_modules(TP_QT5 REQUIRED TelepathyQt5) pkg_check_modules(SQLITE3 REQUIRED sqlite3) pkg_check_modules(QTGLIB QtGLib-2.0) find_program(DBUS_RUNNER dbus-test-runner) add_definitions(-DQT_NO_KEYWORDS) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) set(HISTORY_PLUGIN_PATH ${CMAKE_INSTALL_LIBDIR}/history-service/plugins) configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY) # generate a macro to make it easier to enable coverage support on targets function(ENABLE_COVERAGE) get_directory_property(COVERAGE_TARGETS DIRECTORY ${CMAKE_SOURCE_DIR} COVERAGE_TARGETS) list(APPEND COVERAGE_TARGETS ${ARGN}) MESSAGE(STATUS "Enabling coverage report for target(s): ${ARGN}") set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY COVERAGE_TARGETS ${COVERAGE_TARGETS}) endfunction() enable_testing() add_definitions(-std=c++11) # Define the version to be used in the library set(HISTORY_VERSION_MAJOR 0) set(HISTORY_VERSION_MINOR 0) set(HISTORY_VERSION_PATCH 0) set(PACKAGE_VERSION ${HISTORY_VERSION_MAJOR}.${HISTORY_VERSION_MINOR}.${HISTORY_VERSION_PATCH}) add_subdirectory(src) add_subdirectory(daemon) add_subdirectory(plugins) add_subdirectory(tools) add_subdirectory(Ubuntu) add_subdirectory(tests) include(EnableCoverageReport) ##################################################################### # Enable code coverage calculation with gcov/gcovr/lcov # Usage: # * Switch build type to coverage (use ccmake or cmake-gui) # * Invoke make, make test, make coverage # * Find html report in subdir coveragereport # * Find xml report feasible for jenkins in coverage.xml ##################################################################### IF(CMAKE_BUILD_TYPE MATCHES [cC][oO][vV][eE][rR][aA][gG][eE]) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs" ) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage -fprofile-arcs" ) SET(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -coverage" ) SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -coverage" ) GET_DIRECTORY_PROPERTY(COVERAGE_TARGETS DIRECTORY ${CMAKE_SOURCE_DIR} COVERAGE_TARGETS) ENABLE_COVERAGE_REPORT(TARGETS ${COVERAGE_TARGETS}) ENDIF(CMAKE_BUILD_TYPE MATCHES [cC][oO][vV][eE][rR][aA][gG][eE])