diff --git a/CMakeLists.txt b/CMakeLists.txt index 63557c624..aedcc6e80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,181 +1,181 @@ project(kdepimlibs) # where to look first for cmake modules. This line must be the first one or cmake will use the system's FindFoo.cmake set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") ############### Build Options ############### option(KDEPIM_ONLY_KLEO "Only build the libraries needed by Kleopatra." FALSE) ############### The kdepimlibs version (used e.g. in KdepimLibsConfig.cmake) ############### set(KDEPIMLIBS_VERSION_MAJOR 4) set(KDEPIMLIBS_VERSION_MINOR 3) set(KDEPIMLIBS_VERSION_PATCH 62) set(KDEPIMLIBS_VERSION ${KDEPIMLIBS_VERSION_MAJOR}.${KDEPIMLIBS_VERSION_MINOR}.${KDEPIMLIBS_VERSION_PATCH}) ############### search packages used by KDE ############### find_package(KDE4 4.3.62 REQUIRED) include(KDE4Defaults) include(MacroLibrary) ############### Needed commands before building anything ############### add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) ############### Find the stuff we need ############### set(Boost_MINIMUM_VERSION "1.33.1") -find_package(Boost) -macro_log_feature(Boost_FOUND "Boost" "Boost C++ Libraries" "http://www.boost.org" TRUE "" "Required by several critical KDEPIM apps.") +find_package(Boost ${Boost_MINIMUM_VERSION} COMPONENTS graph) +macro_log_feature(Boost_FOUND "Boost" "Boost C++ Libraries" "http://www.boost.org" TRUE ${Boost_MINIMUM_VERSION} "The Boost libraries boost and boost-graph are required by several critical KDEPIM apps.") #FindGpgme.cmake already handles the log message but we must ensure it is required. find_package(Gpgme REQUIRED) # configure macros if (GPGME_FOUND) include (gpgme++/ConfigureChecks.cmake) endif (GPGME_FOUND) if (NOT KDEPIM_ONLY_KLEO) #FindAkonadi.cmake is only there for compatibility reasons, but we don't want to use that. find_package(Akonadi 1.2.60 QUIET NO_MODULE) macro_log_feature(Akonadi_FOUND "Akonadi" "Akonadi server libraries (from kdesupport)" "http://pim.kde.org/akonadi" TRUE "1.2.60" "Akonadi is required to build KdepimLibs.") find_package(Sasl2) macro_log_feature(SASL2_FOUND "cyrus-sasl" "Cyrus SASL API" "http://asg.web.cmu.edu/sasl/sasl-library.html" TRUE "" "Required to support authentication of logins in the IMAP and Sieve kioslaves.") include (ConfigureChecks.cmake) set(SHARED_MIME_INFO_MINIMUM_VERSION "0.30") find_package(SharedMimeInfo) macro_log_feature(SHARED_MIME_INFO_FOUND "SMI" "SharedMimeInfo" "http://freedesktop.org/wiki/Software/shared-mime-info" TRUE "0.30" "SharedMimeInfo is required.") endif (NOT KDEPIM_ONLY_KLEO) ############### Now, we add the KDEPIMLibs components ############### # These targets will always be built add_subdirectory(cmake) add_subdirectory(gpgme++) add_subdirectory(qgpgme) if (NOT KDEPIM_ONLY_KLEO) add_subdirectory(akonadi) add_subdirectory(kabc) add_subdirectory(kblog) add_subdirectory(kcal) add_subdirectory(kholidays) add_subdirectory(kimap) add_subdirectory(kioslave) add_subdirectory(kldap) add_subdirectory(kmime) add_subdirectory(kpimidentities) add_subdirectory(kpimutils) add_subdirectory(kpimtextedit) add_subdirectory(kresources) add_subdirectory(ktnef) add_subdirectory(kxmlrpcclient) add_subdirectory(mailtransport) add_subdirectory(microblog) add_subdirectory(syndication) # Build the CamelCase headers add_subdirectory(includes) endif (NOT KDEPIM_ONLY_KLEO) # doc must be a subdir of kdepimlibs macro_optional_add_subdirectory(doc) # All done, let's display what we found... macro_display_feature_log() ############### Here we install some extra stuff ############### if (NOT KDEPIM_ONLY_KLEO) install(FILES kdepimlibs-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) endif (NOT KDEPIM_ONLY_KLEO) # now create the KdepimLibsConfig.cmake file, which will be loaded by # kdelibs/cmake/modules/FindKdepimLibs.cmake and which has to contain all information # about the installed kdepimlibs anybody would like to have. Alex # we need the absolute directories where stuff will be installed too # but since the variables which contain the destinations can be relative # or absolute paths, we need this macro to make them all absoulte, Alex macro(MAKE_INSTALL_PATH_ABSOLUTE out in) if (IS_ABSOLUTE "${in}") # IS_ABSOLUTE is new since cmake 2.4.8 set(${out} "${in}") else (IS_ABSOLUTE "${in}") set(${out} "\${KDEPIMLIBS_INSTALL_DIR}/${in}") endif (IS_ABSOLUTE "${in}") endmacro(MAKE_INSTALL_PATH_ABSOLUTE out in) # all the following variables are put into KdepimLibsConfig.cmake, so # they are usable by projects using kdepimlibs. Alex make_install_path_absolute(KDEPIMLIBS_DATA_DIR ${DATA_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_DBUS_INTERFACES_DIR ${DBUS_INTERFACES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_DBUS_SERVICES_DIR ${DBUS_SERVICES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_INCLUDE_DIR ${INCLUDE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LIB_DIR ${LIB_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_BIN_DIR ${BIN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LIBEXEC_DIR ${LIBEXEC_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SBIN_DIR ${SBIN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_HTML_DIR ${HTML_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_CONFIG_DIR ${CONFIG_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_ICON_DIR ${ICON_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_KCFG_DIR ${KCFG_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LOCALE_DIR ${LOCALE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_MIME_DIR ${MIME_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SOUND_DIR ${SOUND_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_TEMPLATES_DIR ${TEMPLATES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_WALLPAPER_DIR ${WALLPAPER_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_KCONF_UPDATE_DIR ${KCONF_UPDATE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_AUTOSTART_DIR ${AUTOSTART_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_XDG_APPS_DIR ${XDG_APPS_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_XDG_DIRECTORY_DIR ${XDG_DIRECTORY_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SYSCONF_DIR ${SYSCONF_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_MAN_DIR ${MAN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_INFO_DIR ${INFO_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SERVICES_DIR ${SERVICES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SERVICETYPES_DIR ${SERVICETYPES_INSTALL_DIR}) # Used in configure_file() and install(EXPORT) set(KDEPIMLIBS_TARGET_PREFIX KDEPIMLibs__) # this file is installed and contains all necessary information about the installed kdepimlibs, it also loads the file with the exported targets configure_file(KdepimLibsConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfig.cmake" @ONLY) # this file will be installed too and will be used by cmake when searching for the Config.cmake file to check the version of kdepimlibs, Alex macro_write_basic_cmake_version_file(${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfigVersion.cmake ${KDEPIMLIBS_VERSION_MAJOR} ${KDEPIMLIBS_VERSION_MINOR} ${KDEPIMLIBS_VERSION_PATCH}) set(_KdepimLibsConfig_INSTALL_DIR ${LIB_INSTALL_DIR}/KdepimLibs/cmake) # places where find_package() looks for FooConfig.cmake files: # CMake >= 2.6.0 looks in lib/Foo*/cmake/, CMake >= 2.6.3 also looks in # lib/cmake/Foo*/, which packagers prefer. So they can set the KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR # option to have kdepimlibs install its Config file there. Alex if(KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR) set(_KdepimLibsConfig_INSTALL_DIR ${LIB_INSTALL_DIR}/cmake/KdepimLibs) endif(KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfig.cmake DESTINATION ${_KdepimLibsConfig_INSTALL_DIR} ) # Install the file with the exported targets, use ${KDEPIMLIBS_TARGET_PREFIX} as prefix for the names of these targets, Alex install(EXPORT kdepimlibsLibraryTargets NAMESPACE ${KDEPIMLIBS_TARGET_PREFIX} DESTINATION ${_KdepimLibsConfig_INSTALL_DIR} FILE KDEPimLibsLibraryTargetsWithPrefix.cmake ) # Install a KDEPimLibsDependencies.cmake so people using kdepimlibs 4.2 with kdelibs < 4.2 get a useful error message, Alex file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/KDEPimLibsDependencies.cmake "\n message(FATAL_ERROR \"For using this version of kdepimlibs (${KDEPIMLIBS_VERSION}) you need a newer version of kdelibs, please update.\")\n") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KDEPimLibsDependencies.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules) diff --git a/akonadi/CMakeLists.txt b/akonadi/CMakeLists.txt index f9c48e59e..f907be9f4 100644 --- a/akonadi/CMakeLists.txt +++ b/akonadi/CMakeLists.txt @@ -1,270 +1,269 @@ project(akonadi-kde) add_definitions( -DKDE_DEFAULT_DEBUG_AREA=5250 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) if(CMAKE_COMPILE_GCOV) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") endif(CMAKE_COMPILE_GCOV) if (KDE4_BUILD_TESTS) # only with this macro the AKONADI_TESTS_EXPORT macro will do something add_definitions(-DCOMPILING_TESTS) add_subdirectory( tests ) endif (KDE4_BUILD_TESTS) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_subdirectory( kabc ) add_subdirectory( kmime ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_QTDBUS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR}/akonadi/private ) # libakonadi-kde set( akonadikde_LIB_SRC entity.cpp # keep it at top to not break enable-final agentbase.cpp agentfilterproxymodel.cpp agentinstance.cpp agentinstancecreatejob.cpp agentinstancemodel.cpp agentinstancewidget.cpp agentmanager.cpp agenttype.cpp agenttypemodel.cpp agenttypewidget.cpp agenttypedialog.cpp attribute.cpp attributefactory.cpp cachepolicy.cpp cachepolicypage.cpp changerecorder.cpp collection.cpp collectioncopyjob.cpp collectioncreatejob.cpp collectiondeletejob.cpp collectiondialog.cpp collectionfilterproxymodel.cpp collectiongeneralpropertiespage.cpp collectionfetchjob.cpp collectionfetchscope.cpp collectionmodel.cpp collectionmodel_p.cpp collectionmodifyjob.cpp collectionmovejob.cpp collectionpathresolver.cpp collectionpropertiesdialog.cpp collectionpropertiespage.cpp collectionrequester.cpp collectionrightsattribute.cpp collectionselectjob.cpp collectionstatistics.cpp collectionstatisticsdelegate.cpp collectionstatisticsjob.cpp collectionstatisticsmodel.cpp collectionsync.cpp collectionview.cpp control.cpp descendantsproxymodel.cpp entitycache.cpp entitydisplayattribute.cpp entityhiddenattribute.cpp entitytreemodel.cpp entitytreemodel_p.cpp entityfilterproxymodel.cpp entitytreeviewstatesaver.cpp erroroverlay.cpp exception.cpp favoritecollectionsmodel.cpp firstrun.cpp flatcollectionproxymodel.cpp item.cpp itemcreatejob.cpp itemcopyjob.cpp itemdeletejob.cpp itemfetchjob.cpp itemfetchscope.cpp itemmodel.cpp itemmonitor.cpp itemmovejob.cpp itemsearchjob.cpp itemserializer.cpp itemserializerplugin.cpp itemmodifyjob.cpp itemsync.cpp itemview.cpp job.cpp linkjob.cpp filteractionjob.cpp mimetypechecker.cpp monitor.cpp monitor_p.cpp pastehelper.cpp preprocessorbase.cpp protocolhelper.cpp resourcebase.cpp resourcescheduler.cpp resourceselectjob.cpp + resourcesynchronizationjob.cpp searchcreatejob.cpp selectionproxymodel.cpp selftestdialog.cpp session.cpp servermanager.cpp standardactionmanager.cpp statisticsproxymodel.cpp statisticstooltipproxymodel.cpp subscriptionjob.cpp subscriptionchangeproxymodel.cpp subscriptiondialog.cpp subscriptionmodel.cpp transactionjobs.cpp transactionsequence.cpp transportresourcebase.cpp unlinkjob.cpp # Temporary until ported to Qt-plugin framework pluginloader.cpp ) # DBus interfaces and adaptors set(akonadi_xml ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.NotificationManager.xml) set_source_files_properties(${akonadi_xml} PROPERTIES INCLUDE "notificationmessage_p.h") qt4_add_dbus_interface( akonadikde_LIB_SRC ${akonadi_xml} notificationmanagerinterface ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.AgentManager.xml ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Tracer.xml ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Resource.xml resourcebase.h Akonadi::ResourceBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Preprocessor.xml preprocessorbase.h Akonadi::PreprocessorBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Status.xml agentbase.h Akonadi::AgentBase ) -# TODO move this to kdesupport/akonadi/interfaces +qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC interfaces/org.freedesktop.Akonadi.Resource.Transport.xml transportresourcebase_p.h Akonadi::TransportResourceBasePrivate ) -# TODO merge with Akonadi.Control in kdesupport/akonadi/interfaces -qt4_add_dbus_adaptor( akonadikde_LIB_SRC interfaces/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) -#qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) kde4_add_ui_files( akonadikde_LIB_SRC cachepolicypage.ui collectiongeneralpropertiespage.ui subscriptiondialog.ui controlprogressindicator.ui selftestdialog.ui ) kde4_add_library( akonadi-kde SHARED ${akonadikde_LIB_SRC} ) macro_ensure_version( "4.2.0" ${KDE_VERSION} KDE_IS_AT_LEAST_42 ) target_link_libraries( akonadi-kde ${KDE4_SOLID_LIBS} ${QT_QTNETWORK_LIBRARY} ${QT_QTDBUS_LIBRARY} ${QT_QTSQL_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${AKONADI_COMMON_LIBRARIES} ) set( AKONADI_KDE_DEPS ${KDE4_KDEUI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTCORE_LIBRARY} ) if(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde LINK_INTERFACE_LIBRARIES ${AKONADI_KDE_DEPS}) else(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde ${AKONADI_KDE_DEPS}) endif(${KDE_IS_AT_LEAST_42}) set_target_properties( akonadi-kde PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS akonadi-kde EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( FILES akonadi_export.h agentbase.h agentfilterproxymodel.h agentinstance.h agentinstancecreatejob.h agentinstancemodel.h agentinstancewidget.h agentmanager.h agenttype.h agenttypemodel.h agenttypewidget.h agenttypedialog.h attribute.h attributefactory.h cachepolicy.h changerecorder.h collection.h collectioncopyjob.h collectioncreatejob.h collectiondeletejob.h collectiondialog.h collectionfilterproxymodel.h collectionfetchjob.h collectionfetchscope.h collectionmodel.h collectionmodifyjob.h collectionpropertiesdialog.h collectionpropertiespage.h collectionrequester.h collectionstatisticsdelegate.h collectionstatisticsmodel.h collectionstatistics.h collectionstatisticsjob.h collectionview.h control.h descendantsproxymodel.h entity.h entitydisplayattribute.h entityhiddenattribute.h entitytreemodel.h entityfilterproxymodel.h entitytreeviewstatesaver.h exception.h favoritecollectionsmodel.h item.h itemcreatejob.h itemcopyjob.h itemdeletejob.h itemfetchjob.h itemfetchscope.h itemmodel.h itemmodifyjob.h itemmonitor.h itemmovejob.h itempayloadinternals_p.h itemsearchjob.h itemserializerplugin.h itemsync.h itemview.h job.h linkjob.h filteractionjob.h mimetypechecker.h monitor.h qtest_akonadi.h preprocessorbase.h resourcebase.h + resourcesynchronizationjob.h searchcreatejob.h selectionproxymodel.h session.h servermanager.h standardactionmanager.h statisticsproxymodel.h statisticstooltipproxymodel.h transactionjobs.h transactionsequence.h transportresourcebase.h unlinkjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi COMPONENT Devel ) install( FILES collectionpathresolver_p.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/private COMPONENT Devel ) install( FILES kcfg2dbus.xsl DESTINATION ${DATA_INSTALL_DIR}/akonadi-kde ) diff --git a/akonadi/entitytreemodel_p.cpp b/akonadi/entitytreemodel_p.cpp index 82f395d19..42b1029b7 100644 --- a/akonadi/entitytreemodel_p.cpp +++ b/akonadi/entitytreemodel_p.cpp @@ -1,784 +1,788 @@ /* Copyright (c) 2008 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "entitytreemodel_p.h" #include "entitytreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; EntityTreeModelPrivate::EntityTreeModelPrivate( EntityTreeModel *parent ) : q_ptr( parent ), m_collectionFetchStrategy( EntityTreeModel::FetchCollectionsRecursive ), m_itemPopulation( EntityTreeModel::ImmediatePopulation ), m_includeUnsubscribed( true ), m_includeStatistics( false ), m_showRootCollection( false ) { } int EntityTreeModelPrivate::indexOf( const QList &nodes, Entity::Id id ) const { int i = 0; foreach ( const Node *node, nodes ) { if ( node->id == id ) return i; i++; } return -1; } ItemFetchJob* EntityTreeModelPrivate::getItemFetchJob( const Collection &parent, ItemFetchScope scope ) const { ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( parent, m_session ); itemJob->setFetchScope( scope ); return itemJob; } ItemFetchJob* EntityTreeModelPrivate::getItemFetchJob( const Item &item, ItemFetchScope scope ) const { ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( item, m_session ); itemJob->setFetchScope( scope ); return itemJob; } void EntityTreeModelPrivate::runItemFetchJob(ItemFetchJob *itemFetchJob, const Collection &parent) const { Q_Q( const EntityTreeModel ); // TODO: This hack is probably not needed anymore. Remove it. // ### HACK: itemsReceivedFromJob needs to know which collection items were added to. // That is not provided by akonadi, so we attach it in a property. itemFetchJob->setProperty( ItemFetchCollectionId(), QVariant( parent.id() ) ); q->connect( itemFetchJob, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ), q, SLOT( itemsFetched( const Akonadi::Item::List& ) ) ); q->connect( itemFetchJob, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::fetchItems( const Collection &parent ) { Q_Q( EntityTreeModel ); // TODO: Use a more specific fetch scope to get only the envelope for mails etc. ItemFetchJob *itemJob = getItemFetchJob(parent, m_monitor->itemFetchScope() ); runItemFetchJob(itemJob, parent); } void EntityTreeModelPrivate::fetchCollections( const Collection &collection, CollectionFetchJob::Type type ) { Q_Q( EntityTreeModel ); CollectionFetchJob *job = new CollectionFetchJob( collection, type, m_session ); job->fetchScope().setIncludeUnsubscribed( m_includeUnsubscribed ); job->fetchScope().setIncludeStatistics( m_includeStatistics ); job->fetchScope().setContentMimeTypes( m_monitor->mimeTypesMonitored() ); q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ), q, SLOT( collectionsFetched( const Akonadi::Collection::List& ) ) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::collectionsFetched( const Akonadi::Collection::List& collections ) { // TODO: refactor this stuff into separate methods for listing resources in Collection::root, and listing collections within resources. Q_Q( EntityTreeModel ); Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self(); Collection::List _collections = collections; forever { int collectionsSize = _collections.size(); QMutableListIterator it(_collections); while (it.hasNext()) { const Collection col = it.next(); const Collection::Id parentId = col.parentCollection().id(); const Collection::Id colId = col.id(); if ( m_collections.contains( parentId ) ) { insertCollection( col, m_collections.value( parentId ) ); + if ( m_itemPopulation == EntityTreeModel::ImmediatePopulation ) + fetchItems( col ); + if ( m_pendingChildCollections.contains( colId ) ) { QList pendingParentIds = m_pendingChildCollections.value( colId ); foreach(const Collection::Id &id, pendingParentIds) { Collection pendingCollection = m_pendingCollections.value(id); Q_ASSERT( pendingCollection.isValid() ); insertPendingCollection( pendingCollection, col, it ); m_pendingCollections.remove(id); } if ( !it.findNext(col) && !it.findPrevious(col) ) { Q_ASSERT("Something went very wrong" == "false"); } m_pendingChildCollections.remove( colId ); } it.remove(); } else { m_pendingCollections.insert( colId, col ); if ( !m_pendingChildCollections.value( parentId ).contains( colId ) ) m_pendingChildCollections[ parentId ].append( colId ); } } if ( _collections.isEmpty() ) break; // forever if( _collections.size() == collectionsSize ) { // Didn't process any collections this iteration. // Persist them until the next time collectionsFetched receives collections. kWarning() << "Some collections could not be inserted into the model yet."; break; // forever } } } void EntityTreeModelPrivate::itemsFetched( const Akonadi::Item::List& items ) { Q_Q( EntityTreeModel ); QObject *job = q->sender(); Q_ASSERT( job ); const Collection::Id collectionId = job->property( ItemFetchCollectionId() ).value(); Item::List itemsToInsert; Item::List itemsToUpdate; const Collection collection = m_collections.value( collectionId ); Q_ASSERT( collection.isValid() ); const QList collectionEntities = m_childEntities.value( collectionId ); foreach ( const Item &item, items ) { if ( indexOf( collectionEntities, item.id() ) != -1 ) { itemsToUpdate << item; } else { if ( m_mimeChecker.wantedMimeTypes().isEmpty() || m_mimeChecker.isWantedItem( item ) ) { itemsToInsert << item; } } } if ( itemsToInsert.size() > 0 ) { const int startRow = m_childEntities.value( collectionId ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collectionId ) ); q->beginInsertRows( parentIndex, startRow, startRow + items.size() - 1 ); foreach ( const Item &item, items ) { Item::Id itemId = item.id(); m_items.insert( itemId, item ); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; m_childEntities[ collectionId ].append( node ); } q->endInsertRows(); } } void EntityTreeModelPrivate::monitoredMimeTypeChanged( const QString & mimeType, bool monitored ) { if ( monitored ) m_mimeChecker.addWantedMimeType( mimeType ); else m_mimeChecker.removeWantedMimeType( mimeType ); } void EntityTreeModelPrivate::retrieveAncestors(const Akonadi::Collection& collection) { Q_Q( EntityTreeModel ); // Unlike fetchCollections, this method fetches collections by traversing up, not down. CollectionFetchJob *job = new CollectionFetchJob( collection.parentCollection(), CollectionFetchJob::Base, m_session ); job->fetchScope().setIncludeUnsubscribed( m_includeUnsubscribed ); job->fetchScope().setIncludeStatistics( m_includeStatistics ); q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ), q, SLOT( ancestorsFetched( const Akonadi::Collection::List& ) ) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::ancestorsFetched(const Akonadi::Collection::List& collectionList) { // List is a size of one. foreach(const Collection &collection, collectionList) { // We should find a collection already in the tree before we reach the collection root. // We're looking to bridge a gap here. Q_ASSERT(collection != Collection::root()); // We already checked this either on the previous recursion or in monitoredCollectionAdded. Q_ASSERT(!m_collections.contains(collection.id())); m_ancestors.prepend(collection); if (m_collections.contains(collection.parentCollection().id())) { m_ancestors.prepend( m_collections.value(collection.parentCollection().id()) ); insertAncestors(m_ancestors); } else { retrieveAncestors(collection); } } } void EntityTreeModelPrivate::insertAncestors(const Akonadi::Collection::List& collectionList) { Collection::List::const_iterator it; const Collection::List::const_iterator begin = collectionList.constBegin() + 1; const Collection::List::const_iterator end = collectionList.constEnd(); for (it = begin; it != end; ++it) { insertCollection(*it, *(it-1)); } m_ancestors.clear(); } void EntityTreeModelPrivate::insertCollection( const Akonadi::Collection& collection, const Akonadi::Collection& parent ) { Q_ASSERT(collection.isValid()); Q_ASSERT(parent.isValid()); Q_Q( EntityTreeModel ); // TODO: Use order attribute of parent if available // Otherwise prepend collections and append items. Currently this prepends all collections. // Or I can prepend and append for single signals, then 'change' the parent. // QList childCols = m_childEntities.value( parent.id() ); // int row = childCols.size(); // int numChildCols = childCollections.value(parent.id()).size(); const int row = 0; const QModelIndex parentIndex = q->indexForCollection( parent ); q->beginInsertRows( parentIndex, row, row ); m_collections.insert( collection.id(), collection ); Node *node = new Node; node->id = collection.id(); node->parent = parent.id(); node->type = Node::Collection; m_childEntities[ parent.id() ].prepend( node ); q->endInsertRows(); - - if ( m_itemPopulation == EntityTreeModel::ImmediatePopulation ) - fetchItems( collection ); } void EntityTreeModelPrivate::insertPendingCollection( const Akonadi::Collection& collection, const Akonadi::Collection& parent, QMutableListIterator &colIt ) { insertCollection(collection, parent); + m_pendingCollections.remove( collection.id() ); + if ( m_itemPopulation == EntityTreeModel::ImmediatePopulation ) + fetchItems( collection ); + if (colIt.findPrevious(collection) || colIt.findNext(collection)) { colIt.remove(); } Q_ASSERT(m_collections.contains(parent.id())); QList pendingChildCollectionsToInsert = m_pendingChildCollections.value(collection.id()); QList::const_iterator it; const QList::const_iterator begin = pendingChildCollectionsToInsert.constBegin(); const QList::const_iterator end = pendingChildCollectionsToInsert.constEnd(); for ( it = begin; it != end; ++it ) { insertPendingCollection(m_pendingCollections.value( *it ), collection, colIt); } m_pendingChildCollections.remove( parent.id() ); } void EntityTreeModelPrivate::monitoredCollectionAdded( const Akonadi::Collection& collection, const Akonadi::Collection& parent ) { // If the resource is removed while populating the model with it, we might still // get some monitor signals. These stale/out-of-order signals can't be completely eliminated // in the akonadi server due to implementation details, so we also handle such signals in the model silently - // in all the monior slots. + // in all the monitored slots. // Stephen Kelly, 28, July 2009 // This is currently temporarily blocked by a uninitialized value bug in the server. // if ( !m_collections.contains( parent.id() ) ) // { // kWarning() << "Got a stale notification for a collection whose parent was already removed." << collection.id() << collection.remoteId(); // return; // } // Some collection trees contain multiple mimetypes. Even though server side filtering ensures we // only get the ones we're interested in from the job, we have to filter on collections received through signals too. if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedCollection( collection ) ) return; if (!m_collections.contains(parent.id())) { // The collection we're interested in is contained in a collection we're not interested in. // We download the ancestors of the collection we're interested in to complete the tree. m_ancestors.prepend(collection); retrieveAncestors(collection); return; } insertCollection(collection, parent); } void EntityTreeModelPrivate::monitoredCollectionRemoved( const Akonadi::Collection& collection ) { if ( !m_collections.contains( collection.parent() ) ) return; Q_Q( EntityTreeModel ); // This may be a signal for a collection we've already removed by removing its ancestor. if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } const int row = indexOf( m_childEntities.value( collection.parentCollection().id() ), collection.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.parentCollection().id() ) ); q->beginRemoveRows( parentIndex, row, row ); // Delete all descendant collections and items. removeChildEntities(collection.id()); // Remove deleted collection from its parent. m_childEntities[ collection.parentCollection().id() ].removeAt( row ); q->endRemoveRows(); } void EntityTreeModelPrivate::removeChildEntities(Collection::Id colId) { QList::const_iterator it; QList childList = m_childEntities.value(colId); const QList::const_iterator begin = childList.constBegin(); const QList::const_iterator end = childList.constEnd(); for (it = begin; it != end; ++it) { if (Node::Item == (*it)->type) { m_items.remove((*it)->id); } else { removeChildEntities((*it)->id); m_collections.remove((*it)->id); } } m_childEntities.remove(colId); } void EntityTreeModelPrivate::monitoredCollectionMoved( const Akonadi::Collection& collection, const Akonadi::Collection& sourceCollection, const Akonadi::Collection& destCollection ) { if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } Q_Q( EntityTreeModel ); const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), collection.id() ); const QModelIndex srcParentIndex = q->indexForCollection( sourceCollection ); const QModelIndex destParentIndex = q->indexForCollection( destCollection ); const int destRow = 0; // Prepend collections // TODO: Uncomment for Qt4.6 // q->beginMoveRows( srcParentIndex, srcRow, srcRow, destParentIndex, destRow ); // Node *node = m_childEntities[ sourceCollection.id() ].takeAt( srcRow ); // m_childEntities[ destCollection.id() ].prepend( node ); // q->endMoveRows(); } void EntityTreeModelPrivate::monitoredCollectionChanged( const Akonadi::Collection &collection ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } m_collections[ collection.id() ] = collection; const QModelIndex index = q->indexForCollection( collection ); Q_ASSERT( index.isValid() ); q->dataChanged( index, index ); } void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged( Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( id ) ) { kWarning() << "Got statistics response for non-existing collection:" << id; } else { m_collections[ id ].setStatistics( statistics ); const QModelIndex index = q->indexForCollection( m_collections[ id ] ); q->dataChanged( index, index ); } } void EntityTreeModelPrivate::monitoredItemAdded( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem( item ) ) return; const int row = m_childEntities.value( collection.id() ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); m_items.insert( item.id(), item ); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; m_childEntities[ collection.id() ].append( node ); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemRemoved( const Akonadi::Item &item ) { Q_Q( EntityTreeModel ); const Collection::List parents = getParentCollections( item ); if ( parents.isEmpty() ) return; if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } // TODO: Iterate over all (virtual) collections. const Collection collection = parents.first(); Q_ASSERT( m_collections.contains( collection.id() ) ); const int row = indexOf( m_childEntities.value( collection.id() ), item.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginRemoveRows( parentIndex, row, row ); m_items.remove( item.id() ); m_childEntities[ collection.id() ].removeAt( row ); q->endRemoveRows(); } void EntityTreeModelPrivate::monitoredItemChanged( const Akonadi::Item &item, const QSet& ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } m_items[ item.id() ] = item; const QModelIndexList indexes = q->indexesForItem( item ); foreach ( const QModelIndex &index, indexes ) { if( !index.isValid() ) { kWarning() << "item has invalid index:" << item.id() << item.remoteId(); } else { q->dataChanged( index, index ); } } } void EntityTreeModelPrivate::monitoredItemMoved( const Akonadi::Item& item, const Akonadi::Collection& sourceCollection, const Akonadi::Collection& destCollection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( sourceCollection.id() ) ); Q_ASSERT( m_collections.contains( destCollection.id() ) ); const Item::Id itemId = item.id(); const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), itemId ); const QModelIndex srcIndex = q->indexForCollection( sourceCollection ); const QModelIndex destIndex = q->indexForCollection( destCollection ); // Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes? const int destRow = q->rowCount( destIndex ); // TODO: Uncomment for Qt4.6 // q->beginMoveRows( srcIndex, srcRow, srcRow, destIndex, destRow ); // Node *node = m_childEntities[ sourceItem.id() ].takeAt( srcRow ); // m_childEntities[ destItem.id() ].append( node ); // q->endMoveRows(); } void EntityTreeModelPrivate::monitoredItemLinked( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem( item ) ) return; const int row = m_childEntities.value( collection.id() ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; m_childEntities[ collection.id()].append( node ); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemUnlinked( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); const int row = indexOf( m_childEntities.value( collection.id() ), item.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); m_childEntities[ collection.id() ].removeAt( row ); q->endInsertRows(); } void EntityTreeModelPrivate::fetchJobDone( KJob *job ) { Q_ASSERT(m_pendingCollections.isEmpty()); Q_ASSERT(m_pendingChildCollections.isEmpty()); if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::copyJobDone( KJob *job ) { if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::moveJobDone( KJob *job ) { if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::updateJobDone( KJob *job ) { if ( job->error() ) { // TODO: handle job errors kWarning() << "Job error:" << job->errorString(); } else { // TODO: Is this trying to do the job of collectionstatisticschanged? // CollectionStatisticsJob *csjob = static_cast( job ); // Collection result = csjob->collection(); // collectionStatisticsChanged( result.id(), csjob->statistics() ); } } void EntityTreeModelPrivate::startFirstListJob() { Q_Q(EntityTreeModel); if (m_collections.size() > 0) return; Collection rootCollection; // Even if the root collection is the invalid collection, we still need to start // the first list job with Collection::root. if ( m_showRootCollection ) { rootCollection = Collection::root(); // Notify the outside that we're putting collection::root into the model. q->beginInsertRows( QModelIndex(), 0, 0 ); m_collections.insert( rootCollection.id(), rootCollection ); m_rootNode = new Node; m_rootNode->id = rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_childEntities[ -1 ].append( m_rootNode ); q->endInsertRows(); } else { // Otherwise store it silently because it's not part of the usable model. rootCollection = m_rootCollection; m_rootNode = new Node; m_rootNode->id = rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_collections.insert( rootCollection.id(), rootCollection ); } // Includes recursive trees. Lower levels are fetched in the onRowsInserted slot if // necessary. // HACK: fix this for recursive listing if we filter on mimetypes that only exit deeper // in the hierarchy if ( ( m_collectionFetchStrategy == EntityTreeModel::FetchFirstLevelChildCollections) /*|| ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive )*/ ) { fetchCollections( rootCollection, CollectionFetchJob::FirstLevel ); } if ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive ) fetchCollections( rootCollection, CollectionFetchJob::Recursive ); // If the root collection is not collection::root, then it could have items, and they will need to be // retrieved now. if ( m_itemPopulation != EntityTreeModel::NoItemPopulation ) { if (rootCollection != Collection::root()) fetchItems( rootCollection ); } } Collection EntityTreeModelPrivate::getParentCollection( Entity::Id id ) const { QHashIterator > iter( m_childEntities ); while ( iter.hasNext() ) { iter.next(); if ( indexOf( iter.value(), id ) != -1 ) { return m_collections.value( iter.key() ); } } return Collection(); } Collection::List EntityTreeModelPrivate::getParentCollections( const Item &item ) const { Collection::List list; QHashIterator > iter( m_childEntities ); while ( iter.hasNext() ) { iter.next(); if ( indexOf( iter.value(), item.id() ) != -1 ) { list << m_collections.value( iter.key() ); } } return list; } Collection EntityTreeModelPrivate::getParentCollection( const Collection &collection ) const { return m_collections.value( collection.parentCollection().id() ); } Entity::Id EntityTreeModelPrivate::childAt( Collection::Id id, int position, bool *ok ) const { const QList list = m_childEntities.value( id ); if ( list.size() <= position ) { *ok = false; return 0; } *ok = true; return list.at( position )->id; } int EntityTreeModelPrivate::indexOf( Collection::Id parent, Collection::Id collectionId ) const { return indexOf( m_childEntities.value( parent ), collectionId ); } Item EntityTreeModelPrivate::getItem( Item::Id id) const { if ( id > 0 ) id *= -1; return m_items.value( id ); } diff --git a/akonadi/filteractionjob.h b/akonadi/filteractionjob.h index 4a07ad7ac..e6075100a 100644 --- a/akonadi/filteractionjob.h +++ b/akonadi/filteractionjob.h @@ -1,173 +1,176 @@ /* Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_FILTERACTIONJOB_H #define AKONADI_FILTERACTIONJOB_H #include "item.h" #include "transactionsequence.h" namespace Akonadi { class Collection; class ItemFetchScope; class Job; /** - @short Base class for a filter/action for FilterActionJob. - - Abstract class defining an interface for a filter and an action for - FilterActionJob. The virtual methods must be implemented in subclasses. - - @code - class ClearErrorAction : public Akonadi::FilterAction - { - public: - // reimpl - virtual Akonadi::ItemFetchScope fetchScope() const - { - ItemFetchScope scope; - scope.fetchFullPayload( false ); - scope.fetchAttribute(); - return scope; - } - - virtual bool itemAccepted( const Akonadi::Item &item ) const - { - return item.hasAttribute(); - } - - virtual Akonadi::Job *itemAction( const Akonadi::Item &item ) const - { - Item cp = item; - cp.removeAttribute(); - return new ItemModifyJob( cp ); - } - }; - @endcode - - @see FilterActionJob - - @author Constantin Berzan - @since 4.4 -*/ + * @short Base class for a filter/action for FilterActionJob. + * + * Abstract class defining an interface for a filter and an action for + * FilterActionJob. The virtual methods must be implemented in subclasses. + * + * @code + * class ClearErrorAction : public Akonadi::FilterAction + * { + * public: + * // reimpl + * virtual Akonadi::ItemFetchScope fetchScope() const + * { + * ItemFetchScope scope; + * scope.fetchFullPayload( false ); + * scope.fetchAttribute(); + * return scope; + * } + * + * virtual bool itemAccepted( const Akonadi::Item &item ) const + * { + * return item.hasAttribute(); + * } + * + * virtual Akonadi::Job *itemAction( const Akonadi::Item &item ) const + * { + * Item cp = item; + * cp.removeAttribute(); + * return new ItemModifyJob( cp ); + * } + * }; + * @endcode + * + * @see FilterActionJob + * + * @author Constantin Berzan + * @since 4.4 + */ class AKONADI_EXPORT FilterAction { public: /** - Destroys this FilterAction. - A FilterActionJob will delete its FilterAction automatically. - */ + * Destroys this filter action. + * + * A FilterActionJob will delete its FilterAction automatically. + */ virtual ~FilterAction(); /** - Returns an ItemFetchScope to use if the FilterActionJob needs to fetch - the items from a collection. - Note that the items are not fetched unless FilterActionJob is - constructed with a Collection parameter. - */ + * Returns an ItemFetchScope to use if the FilterActionJob needs + * to fetch the items from a collection. + * + * @note The items are not fetched unless FilterActionJob is + * constructed with a Collection parameter. + */ virtual Akonadi::ItemFetchScope fetchScope() const = 0; /** - Returns true if the @p item is accepted by the filter and should be - acted upon by the FilterActionJob. - */ + * Returns @c true if the @p item is accepted by the filter and should be + * acted upon by the FilterActionJob. + */ virtual bool itemAccepted( const Akonadi::Item &item ) const = 0; /** - Returns a job to act on the @p item. - The FilterActionJob will finish when all such jobs are finished. - */ + * Returns a job to act on the @p item. + * The FilterActionJob will finish when all such jobs are finished. + */ virtual Akonadi::Job *itemAction( const Akonadi::Item &item ) const = 0; }; - - /** - @short Job to filter and apply an action on a set of items. - - This jobs filters through a set of items, and applies an action to the - items which are accepted by the filter. The filter and action - are provided by a functor class derived from FilterAction. - - For example, a MarkAsRead action/filter may be used to mark all messages - in a folder as read. - - @code - FilterActionJob *mjob = new FilterActionJob( LocalFolders::self()->outbox(), - new ClearErrorAction, this ); - connect( mjob, SIGNAL(result(KJob*)), this, SLOT(massModifyResult(KJob*)) ); - @endcode - - @see FilterAction - - @author Constantin Berzan - @since 4.4 -*/ + * @short Job to filter and apply an action on a set of items. + * + * This jobs filters through a set of items, and applies an action to the + * items which are accepted by the filter. The filter and action + * are provided by a functor class derived from FilterAction. + * + * For example, a MarkAsRead action/filter may be used to mark all messages + * in a folder as read. + * + * @code + * FilterActionJob *mjob = new FilterActionJob( LocalFolders::self()->outbox(), + * new ClearErrorAction, this ); + * connect( mjob, SIGNAL( result( KJob* ) ), this, SLOT( massModifyResult( KJob* ) ) ); + * @endcode + * + * @see FilterAction + * + * @author Constantin Berzan + * @since 4.4 + */ class AKONADI_EXPORT FilterActionJob : public TransactionSequence { Q_OBJECT public: /** - Creates a FilterActionJob to act on a single item. - - @param item The item to act on. The item is not re-fetched. - @param functor The FilterAction to use. - */ + * Creates a filter action job to act on a single item. + * + * @param item The item to act on. The item is not re-fetched. + * @param functor The FilterAction to use. + * @param parent The parent object. + */ FilterActionJob( const Item &item, FilterAction *functor, QObject *parent = 0 ); /** - Creates a FilterActionJob to act on a set of items. - - @param items The items to act on. The items are not re-fetched. - @param functor The FilterAction to use. - */ + * Creates a filter action job to act on a set of items. + * + * @param items The items to act on. The items are not re-fetched. + * @param functor The FilterAction to use. + * @param parent The parent object. + */ FilterActionJob( const Item::List &items, FilterAction *functor, QObject *parent = 0 ); /** - Creates a FilterActionJob to act on items in a collection. - - @param items The items to act on. The items are fetched using functor->fetchScope(). - @param functor The FilterAction to use. - */ + * Creates a filter action job to act on items in a collection. + * + * @param collection The collection to act on. + * The items of the collection are fetched using functor->fetchScope(). + * @param functor The FilterAction to use. + * @param parent The parent object. + */ FilterActionJob( const Collection &collection, FilterAction *functor, QObject *parent = 0 ); /** - Destroys this FilterActionJob. - This job autodeletes itself. - */ + * Destroys the filter action job. + */ ~FilterActionJob(); protected: /* reimpl */ virtual void doStart(); private: //@cond PRIVATE class Private; Private *const d; Q_PRIVATE_SLOT( d, void fetchResult( KJob* ) ) //@endcond }; } // namespace Akonadi #endif // AKONADI_FILTERACTIONJOB_H diff --git a/akonadi/interfaces/org.freedesktop.Akonadi.Agent.Control.xml b/akonadi/interfaces/org.freedesktop.Akonadi.Agent.Control.xml deleted file mode 100644 index d91fdf6a4..000000000 --- a/akonadi/interfaces/org.freedesktop.Akonadi.Agent.Control.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/akonadi/kmime/CMakeLists.txt b/akonadi/kmime/CMakeLists.txt index 8be70a7e1..d6a815120 100644 --- a/akonadi/kmime/CMakeLists.txt +++ b/akonadi/kmime/CMakeLists.txt @@ -1,46 +1,45 @@ include_directories( ${CMAKE_SOURCE_DIR}/ ${QT_QTDBUS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ) add_subdirectory( tests ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII ${KDE4_ENABLE_EXCEPTIONS}" ) ########### next target ############### set( kmimeakonadi_LIB_SRC addressattribute.cpp localfolders.cpp localfoldersbuildjob.cpp messagefolderattribute.cpp messagemodel.cpp messageparts.cpp messagethreadingattribute.cpp messagethreaderproxymodel.cpp - resourcesynchronizationjob.cpp # copied from playground/pim/akonaditest/resourcetester ) kde4_add_kcfg_files( kmimeakonadi_LIB_SRC localfolderssettings.kcfgc ) install( FILES localfolders.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) kde4_add_library( akonadi-kmime SHARED ${kmimeakonadi_LIB_SRC} ) target_link_libraries( akonadi-kmime akonadi-kde kmime ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS} ) set_target_properties( akonadi-kmime PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install(TARGETS akonadi-kmime EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES addressattribute.h localfolders.h akonadi-kmime_export.h messagefolderattribute.h messagemodel.h messageparts.h messagethreadingattribute.h messagethreaderproxymodel.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/kmime COMPONENT Devel ) diff --git a/akonadi/kmime/localfoldersbuildjob.cpp b/akonadi/kmime/localfoldersbuildjob.cpp index 78017cec6..4983d8255 100644 --- a/akonadi/kmime/localfoldersbuildjob.cpp +++ b/akonadi/kmime/localfoldersbuildjob.cpp @@ -1,405 +1,404 @@ /* Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "localfoldersbuildjob_p.h" #include "localfolders.h" #include "localfolderssettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include - -#include // copied from playground/pim/akonaditest +#include using namespace Akonadi; typedef LocalFoldersSettings Settings; /** @internal */ class Akonadi::LocalFoldersBuildJob::Private { public: Private( LocalFoldersBuildJob *qq ) : q( qq ) { for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { defaultFolders.append( Collection() ); } } void resourceCreateResult( KJob *job ); // slot void resourceSyncResult( KJob *job ); // slot void fetchCollections(); void collectionFetchResult( KJob *job ); // slot void createAndConfigureCollections(); void collectionCreateResult( KJob *job ); // slot void collectionModifyResult( KJob *job ); // slot // May be called for any default type, including Root. static QString nameForType( int type ); // May be called for any default type, including Root. static QString iconNameForType( int type ); // May be called for any default or custom type, other than Root. static LocalFolders::Type typeForName( const QString &name ); LocalFoldersBuildJob *q; bool canBuild; Collection::List defaultFolders; //QSet customFolders; QSet pendingJobs; }; LocalFoldersBuildJob::LocalFoldersBuildJob( bool canBuild, QObject *parent ) : TransactionSequence( parent ) , d( new Private( this ) ) { d->canBuild = canBuild; } LocalFoldersBuildJob::~LocalFoldersBuildJob() { delete d; } const Collection::List &LocalFoldersBuildJob::defaultFolders() const { return d->defaultFolders; } #if 0 const QSet &LocalFoldersBuildJob::customFolders() const { return d->customFolders; } #endif void LocalFoldersBuildJob::doStart() { // Update config and check if resource exists. Settings::self()->readConfig(); kDebug() << "resourceId from settings" << Settings::resourceId(); AgentInstance resource = AgentManager::self()->instance( Settings::resourceId() ); if( resource.isValid() ) { // Good, the resource exists. d->fetchCollections(); } else { // Create resource if allowed. if( !d->canBuild ) { setError( UserDefinedError ); setErrorText( QLatin1String( "Resource not found, and creating not allowed." ) ); emitResult(); } else { kDebug() << "Creating maildir resource."; AgentType type = AgentManager::self()->type( QString::fromLatin1( "akonadi_maildir_resource" ) ); AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type, this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(resourceCreateResult(KJob*)) ); job->start(); // non-Akonadi::Job } } } void LocalFoldersBuildJob::Private::resourceCreateResult( KJob *job ) { Q_ASSERT( canBuild ); if( job->error() ) { q->setError( UserDefinedError ); q->setErrorText( QLatin1String( "Resource creation failed." ) ); q->emitResult(); } else { AgentInstance agent; - + // Get the resource instance, and save its ID to config. { Q_ASSERT( dynamic_cast( job ) ); AgentInstanceCreateJob *cjob = static_cast( job ); agent = cjob->instance(); Settings::setResourceId( agent.identifier() ); kDebug() << "Created maildir resource with id" << Settings::resourceId(); } // Configure the resource. { agent.setName( i18n( "Local Mail Folders" ) ); QDBusInterface conf( QString::fromLatin1( "org.freedesktop.Akonadi.Resource." ) + Settings::resourceId(), QString::fromLatin1( "/Settings" ), QString::fromLatin1( "org.kde.Akonadi.Maildir.Settings" ) ); QDBusReply reply = conf.call( QString::fromLatin1( "setPath" ), KGlobal::dirs()->localxdgdatadir() + QString::fromLatin1( "mail" ) ); if( !reply.isValid() ) { q->setError( UserDefinedError ); q->setErrorText( QLatin1String( "Failed to set the root maildir via D-Bus." ) ); q->emitResult(); } else { agent.reconfigure(); } } // Sync the resource. if( !q->error() ) { ResourceSynchronizationJob *sjob = new ResourceSynchronizationJob( agent, q ); QObject::connect( sjob, SIGNAL(result(KJob*)), q, SLOT(resourceSyncResult(KJob*)) ); sjob->start(); // non-Akonadi } } } void LocalFoldersBuildJob::Private::resourceSyncResult( KJob *job ) { Q_ASSERT( canBuild ); if( job->error() ) { q->setError( UserDefinedError ); q->setErrorText( QLatin1String( "ResourceSynchronizationJob failed." ) ); q->emitResult(); } else { fetchCollections(); } } void LocalFoldersBuildJob::Private::fetchCollections() { kDebug() << "Fetching collections in the maildir resource."; CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, q ); job->setResource( Settings::resourceId() ); // limit search QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchResult(KJob*)) ); } void LocalFoldersBuildJob::Private::collectionFetchResult( KJob *job ) { QSet cols; // Get the root maildir collection, and put all the other ones in cols. { Q_ASSERT( dynamic_cast( job ) ); CollectionFetchJob *fjob = static_cast( job ); Q_FOREACH( const Collection &col, fjob->collections() ) { if( col.parent() == Collection::root().id() ) { // This is the root maildir collection. Q_ASSERT( !defaultFolders[ LocalFolders::Root ].isValid() ); defaultFolders[ LocalFolders::Root ] = col; } else { // This is some local folder. cols.insert( col ); } } } // Go no further if the root maildir was not found. if( !defaultFolders[ LocalFolders::Root ].isValid() ) { q->setError( UserDefinedError ); q->setErrorText( QLatin1String( "Failed to fetch root maildir collection." ) ); q->emitResult(); return; } // Get the default folders. They must be direct children of the root maildir, // and have appropriate names. Leave all the other folders in cols. Q_FOREACH( const Collection &col, cols ) { if( col.parent() == defaultFolders[ LocalFolders::Root ].id() ) { // This is a direct child; guess its type. LocalFolders::Type type = typeForName( col.name() ); if( type != LocalFolders::Custom ) { // This is one of the default folders. Q_ASSERT( !defaultFolders[ type ].isValid() ); defaultFolders[ type ] = col; cols.remove( col ); } } } // Everything left in cols is custom folders. //customFolders = cols; // Some collections may still need to be created (if the evil user deleted // them from disk, for example), and some may need to be configured (rights, // icon, i18n'ed name). if( canBuild ) { createAndConfigureCollections(); } else { for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { if( !defaultFolders[ type ].isValid() ) { q->setError( UserDefinedError ); q->setErrorText( QString::fromLatin1( "Failed to fetch %1 collection." ).arg( nameForType( type ) ) ); q->emitResult(); break; } } // If all collections are valid, we are done. if( !q->error() ) { q->commit(); } } } void LocalFoldersBuildJob::Private::createAndConfigureCollections() { Q_ASSERT( canBuild ); // We need the root maildir at least. Q_ASSERT( defaultFolders[ LocalFolders::Root ].isValid() ); for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { if( !defaultFolders[ type ].isValid() ) { // This default folder needs to be created. kDebug() << "Creating default folder" << nameForType( type ); Q_ASSERT( type != LocalFolders::Root ); Collection col; col.setParent( defaultFolders[ LocalFolders::Root ].id() ); col.setName( nameForType( type ) ); EntityDisplayAttribute *attr = new EntityDisplayAttribute; attr->setIconName( iconNameForType( type ) ); attr->setDisplayName( i18nc( "local mail folder", nameForType( type ).toLatin1() ) ); col.addAttribute( attr ); col.setRights( Collection::Rights( Collection::AllRights ^ Collection::CanDeleteCollection ) ); CollectionCreateJob *cjob = new CollectionCreateJob( col, q ); QObject::connect( cjob, SIGNAL(result(KJob*)), q, SLOT(collectionCreateResult(KJob*)) ); } else if( defaultFolders[ type ].rights() & Collection::CanDeleteCollection ) { // This default folder needs to be modified. kDebug() << "Modifying default folder" << nameForType( type ); Collection &col = defaultFolders[ type ]; EntityDisplayAttribute *attr = new EntityDisplayAttribute; attr->setIconName( iconNameForType( type ) ); attr->setDisplayName( i18nc( "local mail folder", nameForType( type ).toLatin1() ) ); col.addAttribute( attr ); col.setRights( Collection::Rights( Collection::AllRights ^ Collection::CanDeleteCollection ) ); CollectionModifyJob *mjob = new CollectionModifyJob( col, q ); QObject::connect( mjob, SIGNAL(result(KJob*)), q, SLOT(collectionModifyResult(KJob*)) ); } // NOTE: We do not set the mime type here because the maildir resource does that. // It sets inode/directory (for child collections) and message/rfc822 (for messages), // which is exactly what we want. } // When those subjobs are finished, we are done. Settings::self()->writeConfig(); q->commit(); } void LocalFoldersBuildJob::Private::collectionCreateResult( KJob *job ) { if( job->error() ) { kDebug() << "CollectionCreateJob failed."; // TransactionSequence will take care of the error. return; } Q_ASSERT( dynamic_cast( job ) ); CollectionCreateJob *cjob = static_cast( job ); const Collection col = cjob->collection(); int type = typeForName( col.name() ); Q_ASSERT( type < LocalFolders::LastDefaultType ); Q_ASSERT( !defaultFolders[ type ].isValid() ); defaultFolders[ type ] = col; } void LocalFoldersBuildJob::Private::collectionModifyResult( KJob *job ) { if( job->error() ) { kDebug() << "CollectionModifyJob failed."; // TransactionSequence will take care of the error. return; } Q_ASSERT( dynamic_cast( job ) ); // Our collection in defaultFolders[...] is already modified. // (And CollectionModifyJob does not provide collection().) } // static QString LocalFoldersBuildJob::Private::nameForType( int type ) { switch( type ) { case LocalFolders::Root: return QLatin1String( "Local Folders" ); case LocalFolders::Inbox: return QLatin1String( "inbox" ); case LocalFolders::Outbox: return QLatin1String( "outbox" ); case LocalFolders::SentMail: return QLatin1String( "sent-mail" ); case LocalFolders::Trash: return QLatin1String( "trash" ); case LocalFolders::Drafts: return QLatin1String( "drafts" ); case LocalFolders::Templates: return QLatin1String( "templates" ); default: Q_ASSERT( false ); return QString(); } } //static QString LocalFoldersBuildJob::Private::iconNameForType( int type ) { // Icons imitating KMail. switch( type ) { case LocalFolders::Root: return QString(); case LocalFolders::Inbox: return QLatin1String( "mail-folder-inbox" ); case LocalFolders::Outbox: return QLatin1String( "mail-folder-outbox" ); case LocalFolders::SentMail: return QLatin1String( "mail-folder-sent" ); case LocalFolders::Trash: return QLatin1String( "user-trash" ); case LocalFolders::Drafts: return QLatin1String( "document-properties" ); case LocalFolders::Templates: return QLatin1String( "document-new" ); default: Q_ASSERT( false ); return QString(); } } //static LocalFolders::Type LocalFoldersBuildJob::Private::typeForName( const QString &name ) { if( name == QLatin1String( "root" ) ) { Q_ASSERT( false ); return LocalFolders::Root; } else if( name == QLatin1String( "inbox" ) ) { return LocalFolders::Inbox; } else if( name == QLatin1String( "outbox" ) ) { return LocalFolders::Outbox; } else if( name == QLatin1String( "sent-mail" ) ) { return LocalFolders::SentMail; } else if( name == QLatin1String( "trash" ) ) { return LocalFolders::Trash; } else if( name == QLatin1String( "drafts" ) ) { return LocalFolders::Drafts; } else if( name == QLatin1String( "templates" ) ) { return LocalFolders::Templates; } else { return LocalFolders::Custom; } } #include "localfoldersbuildjob_p.moc" diff --git a/akonadi/kmime/resourcesynchronizationjob.h b/akonadi/kmime/resourcesynchronizationjob.h deleted file mode 100644 index 6ed04db2f..000000000 --- a/akonadi/kmime/resourcesynchronizationjob.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2009 Volker Krause - * - * This library 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; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - */ - -#ifndef AKONADI_RESOURCESYNCJOB_H -#define AKONADI_RESOURCESYNCJOB_H - -#include - -namespace Akonadi { - -class AgentInstance; -class ResourceSynchronizationJobPrivate; - -/** - Synchronizes a given resource. -*/ -class ResourceSynchronizationJob : public KJob -{ - Q_OBJECT - public: - /** - Create a new synchronization job for the given agent. - */ - ResourceSynchronizationJob( const AgentInstance &instance, QObject *parent = 0 ); - - /** - Destructor. - */ - ~ResourceSynchronizationJob(); - - /* reimpl */ - void start(); - - private slots: - void slotSynchronized(); - void slotTimeout(); - - private: - ResourceSynchronizationJobPrivate* const d; -}; - -} - -#endif diff --git a/akonadi/kmime/tests/CMakeLists.txt b/akonadi/kmime/tests/CMakeLists.txt index 8741b4c09..b2c1629c2 100644 --- a/akonadi/kmime/tests/CMakeLists.txt +++ b/akonadi/kmime/tests/CMakeLists.txt @@ -1,55 +1,55 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) # TODO apparently QTEST_AKONADIMAIN doesn't like NO_CASTs... remove_definitions( -DQT_NO_CAST_FROM_ASCII ) remove_definitions( -DQT_NO_CAST_TO_ASCII ) set( requester_srcs foldersrequester.cpp ) kde4_add_executable( requester TEST ${requester_srcs} ) -target_link_librarieS( requester akonadi-kmime ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY}) +target_link_libraries( requester akonadi-kmime akonadi-kde ${KDE4_KDEUI_LIBS}) # for racetest set( requester_exe "QLatin1String( \"${CMAKE_CURRENT_BINARY_DIR}/requester\" )" ) add_definitions( -DREQUESTER_EXE='${requester_exe}' ) macro(add_akonadi_isolated_test _source) get_filename_component(_targetName ${_source} NAME_WE) set(_srcList ${_source} ) kde4_add_executable(${_targetName} TEST ${_srcList}) target_link_libraries(${_targetName} ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} + ${QT_QTDBUS_LIBRARY} ${KDE4_AKONADI_LIBS} ${KDE4_KDECORE_LIBS} ${KDE4_MAILTRANSPORT_LIBS} ${KDE4_KMIME_LIBS} - ${QT_QTCORE_LIBRARY} - ${QT_QTDBUS_LIBRARY} + akonadi-kde akonadi-kmime ) # based on kde4_add_unit_test if (WIN32) get_target_property( _loc ${_targetName} LOCATION ) set(_executable ${_loc}.bat) else (WIN32) set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_targetName}) endif (WIN32) if (UNIX) set(_executable ${_executable}.shell) endif (UNIX) find_program(_testrunner akonaditest) add_test( akonadikmime-${_targetName} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config.xml ${_executable} ) endmacro(add_akonadi_isolated_test) add_akonadi_isolated_test( customfoldertest.cpp ) add_akonadi_isolated_test( racetest.cpp ) diff --git a/akonadi/monitor_p.cpp b/akonadi/monitor_p.cpp index 4ca3d8007..0e6e53afa 100644 --- a/akonadi/monitor_p.cpp +++ b/akonadi/monitor_p.cpp @@ -1,319 +1,333 @@ /* Copyright (c) 2007 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmessage_p.h" #include "session.h" #include using namespace Akonadi; static const int PipelineSize = 5; MonitorPrivate::MonitorPrivate(Monitor * parent) : q_ptr( parent ), nm( 0 ), monitorAll( false ), collectionCache( 3*PipelineSize ), // needs to be at least 3x pipeline size for the collection move case itemCache( PipelineSize ), // needs to be at least 1x pipeline size fetchCollection( false ), fetchCollectionStatistics( false ) { } void MonitorPrivate::init() { QObject::connect( &collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); QObject::connect( &itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); } bool MonitorPrivate::connectToNotificationManager() { NotificationMessage::registerDBusTypes(); if ( !nm ) nm = new org::freedesktop::Akonadi::NotificationManager( QLatin1String( "org.freedesktop.Akonadi" ), QLatin1String( "/notifications" ), QDBusConnection::sessionBus(), q_ptr ); else return true; if ( !nm ) { kWarning() << "Unable to connect to notification manager"; } else { QObject::connect( nm, SIGNAL(notify(Akonadi::NotificationMessage::List)), q_ptr, SLOT(slotNotify(Akonadi::NotificationMessage::List)) ); return true; } return false; } int MonitorPrivate::pipelineSize() const { return PipelineSize; } bool MonitorPrivate::acceptNotification(const NotificationMessage & msg) { if ( isSessionIgnored( msg.sessionId() ) ) return false; switch ( msg.type() ) { case NotificationMessage::InvalidType: kWarning() << "Received invalid change notification!"; return false; case NotificationMessage::Item: return isItemMonitored( msg.uid(), msg.parentCollection(), msg.parentDestCollection(), msg.mimeType(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); case NotificationMessage::Collection: return isCollectionMonitored( msg.uid(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); } Q_ASSERT( false ); return false; } void MonitorPrivate::dispatchNotifications() { while ( pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty() ) { const NotificationMessage msg = pendingNotifications.dequeue(); if ( ensureDataAvailable( msg ) && pipeline.isEmpty() ) emitNotification( msg ); else pipeline.enqueue( msg ); } } bool MonitorPrivate::ensureDataAvailable( const NotificationMessage &msg ) { bool allCached = true; if ( fetchCollection ) { if ( !collectionCache.ensureCached( msg.parentCollection(), mCollectionFetchScope ) ) allCached = false; if ( msg.operation() == NotificationMessage::Move && !collectionCache.ensureCached( msg.parentDestCollection(), mCollectionFetchScope ) ) allCached = false; } if ( msg.operation() == NotificationMessage::Remove ) return allCached; // the actual object is gone already, nothing to fetch there if ( msg.type() == NotificationMessage::Item && !mItemFetchScope.isEmpty() ) { if ( !itemCache.ensureCached( msg.uid(), mItemFetchScope ) ) allCached = false; } else if ( msg.type() == NotificationMessage::Collection && fetchCollection ) { if ( !collectionCache.ensureCached( msg.uid(), mCollectionFetchScope ) ) allCached = false; } return allCached; } void MonitorPrivate::emitNotification( const NotificationMessage &msg ) { const Collection parent = collectionCache.retrieve( msg.parentCollection() ); Collection destParent; if ( msg.operation() == NotificationMessage::Move ) destParent = collectionCache.retrieve( msg.parentDestCollection() ); if ( msg.type() == NotificationMessage::Collection ) { const Collection col = collectionCache.retrieve( msg.uid() ); emitCollectionNotification( msg, col, parent, destParent ); } else if ( msg.type() == NotificationMessage::Item ) { const Item item = itemCache.retrieve( msg.uid() ); emitItemNotification( msg, item, parent, destParent ); } } void MonitorPrivate::dataAvailable() { while ( !pipeline.isEmpty() ) { const NotificationMessage msg = pipeline.head(); if ( ensureDataAvailable( msg ) ) { emitNotification( msg ); pipeline.dequeue(); } else { break; } } dispatchNotifications(); } void MonitorPrivate::updatePendingStatistics( const NotificationMessage &msg ) { if ( msg.type() == NotificationMessage::Item ) { notifyCollectionStatisticsWatchers( msg.parentCollection(), msg.resource() ); } else if ( msg.type() == NotificationMessage::Collection && msg.operation() == NotificationMessage::Remove ) { // no need for statistics updates anymore recentlyChangedCollections.remove( msg.uid() ); } } void MonitorPrivate::slotSessionDestroyed( QObject * object ) { Session* session = qobject_cast( object ); if ( session ) sessions.removeAll( session->sessionId() ); } void MonitorPrivate::slotStatisticsChangedFinished( KJob* job ) { if ( job->error() ) { kWarning() << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast( job ); emit q_ptr->collectionStatisticsChanged( statisticsJob->collection().id(), statisticsJob->statistics() ); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { foreach( Collection::Id collection, recentlyChangedCollections ) { if ( fetchCollectionStatistics ) { fetchStatistics( collection ); } else { static const CollectionStatistics dummyStatistics; emit q_ptr->collectionStatisticsChanged( collection, dummyStatistics ); } } recentlyChangedCollections.clear(); } void MonitorPrivate::slotNotify( const NotificationMessage::List &msgs ) { foreach ( const NotificationMessage &msg, msgs ) { invalidateCaches( msg ); if ( acceptNotification( msg ) ) { updatePendingStatistics( msg ); NotificationMessage::appendAndCompress( pendingNotifications, msg ); } } dispatchNotifications(); } void MonitorPrivate::emitItemNotification( const NotificationMessage &msg, const Item &item, const Collection &collection, const Collection &collectionDest ) { Q_ASSERT( msg.type() == NotificationMessage::Item ); Collection col = collection; Collection colDest = collectionDest; if ( !col.isValid() ) { col = Collection( msg.parentCollection() ); col.setResource( QString::fromUtf8( msg.resource() ) ); } if ( !colDest.isValid() ) { colDest = Collection( msg.parentDestCollection() ); // FIXME setResource here required ? } Item it = item; if ( !it.isValid() ) { it = Item( msg.uid() ); it.setRemoteId( msg.remoteId() ); it.setMimeType( msg.mimeType() ); } + if ( !it.parentCollection().isValid() ) { + if ( msg.operation() == NotificationMessage::Move ) + it.setParentCollection( colDest ); + else + it.setParentCollection( col ); + } switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->itemAdded( it, col ); break; case NotificationMessage::Modify: emit q_ptr->itemChanged( it, msg.itemParts() ); break; case NotificationMessage::Move: emit q_ptr->itemMoved( it, col, colDest ); break; case NotificationMessage::Remove: emit q_ptr->itemRemoved( it ); emit q_ptr->itemRemoved( it, col ); break; case NotificationMessage::Link: emit q_ptr->itemLinked( it, col ); break; case NotificationMessage::Unlink: emit q_ptr->itemUnlinked( it, col ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in item change notification"; break; } } void MonitorPrivate::emitCollectionNotification( const NotificationMessage &msg, const Collection &col, const Collection &par, const Collection &dest ) { Q_ASSERT( msg.type() == NotificationMessage::Collection ); Collection collection = col; if ( !collection.isValid() ) { collection = Collection( msg.uid() ); - collection.setParentCollection( Collection( msg.parentCollection() ) ); collection.setResource( QString::fromUtf8( msg.resource() ) ); collection.setRemoteId( msg.remoteId() ); } + Collection parent = par; if ( !parent.isValid() ) parent = Collection( msg.parentCollection() ); Collection destination = dest; if ( !destination.isValid() ) destination = Collection( msg.parentDestCollection() ); + + if ( !collection.parentCollection().isValid() ) { + if ( msg.operation() == NotificationMessage::Move ) + collection.setParentCollection( destination ); + else + collection.setParentCollection( parent ); + } + switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->collectionAdded( collection, parent ); break; case NotificationMessage::Modify: emit q_ptr->collectionChanged( collection ); break; case NotificationMessage::Move: emit q_ptr->collectionMoved( collection, parent, destination ); break; case NotificationMessage::Remove: emit q_ptr->collectionRemoved( collection ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in collection change notification"; } } void MonitorPrivate::invalidateCaches( const NotificationMessage &msg ) { // remove invalidates if ( msg.operation() == NotificationMessage::Remove ) { if ( msg.type() == NotificationMessage::Collection ) { collectionCache.invalidate( msg.uid() ); } else if ( msg.type() == NotificationMessage::Item ) { itemCache.invalidate( msg.uid() ); } } // modify removes the cache entry, as we need to re-fetch if ( msg.operation() == NotificationMessage::Modify ) { if ( msg.type() == NotificationMessage::Collection ) { collectionCache.update( msg.uid(), mCollectionFetchScope ); } else if ( msg.type() == NotificationMessage::Item ) { itemCache.update( msg.uid(), mItemFetchScope ); } } } // @endcond diff --git a/akonadi/resourcebase.cpp b/akonadi/resourcebase.cpp index c2900f6ec..c04c6d06b 100644 --- a/akonadi/resourcebase.cpp +++ b/akonadi/resourcebase.cpp @@ -1,607 +1,640 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "resourcebase.h" #include "agentbase_p.h" #include "resourceadaptor.h" #include "collectiondeletejob.h" #include "collectionsync_p.h" #include "itemsync.h" #include "resourcescheduler_p.h" #include "tracerinterface.h" #include "xdgbasedirs_p.h" #include "changerecorder.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmodifyjob.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "session.h" #include "resourceselectjob_p.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; class Akonadi::ResourceBasePrivate : public AgentBasePrivate { public: ResourceBasePrivate( ResourceBase *parent ) : AgentBasePrivate( parent ), scheduler( 0 ), mItemSyncer( 0 ), mCollectionSyncer( 0 ) { mStatusMessage = defaultReadyMessage(); } Q_DECLARE_PUBLIC( ResourceBase ) void delayedInit() { if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + mId ) ) kFatal() << "Unable to register service at D-Bus: " << QDBusConnection::sessionBus().lastError().message(); AgentBasePrivate::delayedInit(); } virtual void changeProcessed() { mMonitor->changeProcessed(); if ( !mMonitor->isEmpty() ) scheduler->scheduleChangeReplay(); scheduler->taskDone(); } void slotDeliveryDone( KJob* job ); void slotCollectionSyncDone( KJob *job ); void slotLocalListDone( KJob *job ); void slotSynchronizeCollection( const Collection &col ); void slotCollectionListDone( KJob *job ); void slotItemSyncDone( KJob *job ); void slotPercent( KJob* job, unsigned long percent ); void slotDeleteResourceCollection(); void slotDeleteResourceCollectionDone( KJob *job ); void slotCollectionDeletionDone( KJob *job ); + void slotPrepareItemRetrieval( const Akonadi::Item &item ); + void slotPrepareItemRetrievalResult( KJob* job ); + // synchronize states Collection currentCollection; ResourceScheduler *scheduler; ItemSync *mItemSyncer; CollectionSync *mCollectionSyncer; }; ResourceBase::ResourceBase( const QString & id ) : AgentBase( new ResourceBasePrivate( this ), id ) { Q_D( ResourceBase ); new ResourceAdaptor( this ); d->scheduler = new ResourceScheduler( this ); d->mMonitor->setChangeRecordingEnabled( true ); connect( d->mMonitor, SIGNAL( changesAdded() ), d->scheduler, SLOT( scheduleChangeReplay() ) ); d->mMonitor->setResourceMonitored( d->mId.toLatin1() ); connect( d->scheduler, SIGNAL( executeFullSync() ), SLOT( retrieveCollections() ) ); connect( d->scheduler, SIGNAL( executeCollectionTreeSync() ), SLOT( retrieveCollections() ) ); connect( d->scheduler, SIGNAL( executeCollectionSync( const Akonadi::Collection& ) ), SLOT( slotSynchronizeCollection( const Akonadi::Collection& ) ) ); connect( d->scheduler, SIGNAL( executeItemFetch( const Akonadi::Item&, const QSet& ) ), - SLOT( retrieveItem( const Akonadi::Item&, const QSet& ) ) ); + SLOT( slotPrepareItemRetrieval(Akonadi::Item)) ); connect( d->scheduler, SIGNAL( executeResourceCollectionDeletion() ), SLOT( slotDeleteResourceCollection() ) ); connect( d->scheduler, SIGNAL( status( int, const QString& ) ), SIGNAL( status( int, const QString& ) ) ); connect( d->scheduler, SIGNAL( executeChangeReplay() ), d->mMonitor, SLOT( replayNext() ) ); connect( d->scheduler, SIGNAL( fullSyncComplete() ), SIGNAL( synchronized() ) ); connect( d->mMonitor, SIGNAL( nothingToReplay() ), d->scheduler, SLOT( taskDone() ) ); connect( this, SIGNAL( synchronized() ), d->scheduler, SLOT( taskDone() ) ); connect( this, SIGNAL( agentNameChanged( const QString& ) ), this, SIGNAL( nameChanged( const QString& ) ) ); d->scheduler->setOnline( d->mOnline ); if ( !d->mMonitor->isEmpty() ) d->scheduler->scheduleChangeReplay(); new ResourceSelectJob( identifier() ); } ResourceBase::~ResourceBase() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName( const QString &name ) { AgentBase::setAgentName( name ); } QString ResourceBase::name() const { return AgentBase::agentName(); } QString ResourceBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug() << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug() << "Identifier argument missing"; exit( 1 ); } QByteArray catalog; char *p = strrchr( argv[0], '/' ); if ( p ) catalog = QByteArray( p + 1 ); else catalog = QByteArray( argv[0] ); KCmdLineArgs::init( argc, argv, identifier.toLatin1(), catalog, ki18nc("@title, application name", "Akonadi Resource"), "0.1", ki18nc("@title, application description", "Akonadi Resource") ); KCmdLineOptions options; options.add( "identifier ", ki18nc("@label, commandline option", "Resource identifier") ); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } int ResourceBase::init( ResourceBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); int rv = kapp->exec(); delete r; return rv; } void ResourceBase::itemRetrieved( const Item &item ) { Q_D( ResourceBase ); Q_ASSERT( d->scheduler->currentTask().type == ResourceScheduler::FetchItem ); if ( !item.isValid() ) { QDBusMessage reply( d->scheduler->currentTask().dbusMsg ); reply << false; QDBusConnection::sessionBus().send( reply ); d->scheduler->taskDone(); return; } Item i( item ); QSet requestedParts = d->scheduler->currentTask().itemParts; foreach ( const QByteArray &part, requestedParts ) { if ( !item.loadedPayloadParts().contains( part ) ) { kWarning() << "Item does not provide part" << part; } } ItemModifyJob *job = new ItemModifyJob( i ); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->disableRevisionCheck(); connect( job, SIGNAL( result( KJob* ) ), SLOT( slotDeliveryDone( KJob* ) ) ); } void ResourceBasePrivate::slotDeliveryDone(KJob * job) { Q_Q( ResourceBase ); Q_ASSERT( scheduler->currentTask().type == ResourceScheduler::FetchItem ); QDBusMessage reply( scheduler->currentTask().dbusMsg ); if ( job->error() ) { emit q->error( QLatin1String( "Error while creating item: " ) + job->errorString() ); reply << false; } else { reply << true; } QDBusConnection::sessionBus().send( reply ); scheduler->taskDone(); } void ResourceBasePrivate::slotDeleteResourceCollection() { Q_Q( ResourceBase ); CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->fetchScope().setResource( q->identifier() ); connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotDeleteResourceCollectionDone( KJob* ) ) ); } void ResourceBasePrivate::slotDeleteResourceCollectionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); scheduler->taskDone(); } else { const CollectionFetchJob *fetchJob = static_cast( job ); if ( !fetchJob->collections().isEmpty() ) { CollectionDeleteJob *job = new CollectionDeleteJob( fetchJob->collections().first() ); connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotCollectionDeletionDone( KJob* ) ) ); } else { // there is no resource collection, so just ignore the request scheduler->taskDone(); } } } void ResourceBasePrivate::slotCollectionDeletionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBase::changeCommitted( const Item& item ) { Q_D( ResourceBase ); ItemModifyJob *job = new ItemModifyJob( item ); job->d_func()->setClean(); job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error? job->ignorePayload(); // we only want to reset the dirty flag and update the remote id d->changeProcessed(); } void ResourceBase::changeCommitted( const Collection &collection ) { Q_D( ResourceBase ); CollectionModifyJob *job = new CollectionModifyJob( collection ); Q_UNUSED( job ); //TODO: error checking d->changeProcessed(); } bool ResourceBase::requestItemDelivery( qint64 uid, const QString & remoteId, const QString &mimeType, const QStringList &_parts ) { Q_D( ResourceBase ); if ( !isOnline() ) { emit error( i18nc( "@info", "Cannot fetch item in offline mode." ) ); return false; } setDelayedReply( true ); // FIXME: we need at least the revision number too Item item( uid ); item.setMimeType( mimeType ); item.setRemoteId( remoteId ); QSet parts; Q_FOREACH( const QString &str, _parts ) parts.insert( str.toLatin1() ); d->scheduler->scheduleItemFetch( item, parts, message().createReply() ); return true; } void ResourceBase::collectionsRetrieved( const Collection::List & collections ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrieved()", "Calling collectionsRetrieved() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mCollectionSyncer, SIGNAL( result( KJob* ) ), SLOT( slotCollectionSyncDone( KJob* ) ) ); } d->mCollectionSyncer->setRemoteCollections( collections ); } void ResourceBase::collectionsRetrievedIncremental( const Collection::List & changedCollections, const Collection::List & removedCollections ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievedIncremental()", "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mCollectionSyncer, SIGNAL( result( KJob* ) ), SLOT( slotCollectionSyncDone( KJob* ) ) ); } d->mCollectionSyncer->setRemoteCollections( changedCollections, removedCollections ); } void ResourceBase::setCollectionStreamingEnabled( bool enable ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::setCollectionStreamingEnabled()", "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mCollectionSyncer, SIGNAL( result( KJob* ) ), SLOT( slotCollectionSyncDone( KJob* ) ) ); } d->mCollectionSyncer->setStreamingEnabled( enable ); } void ResourceBase::collectionsRetrievalDone() { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievalDone()", "Calling collectionsRetrievalDone() although no collection retrieval is in progress" ); // streaming enabled, so finalize the sync if ( d->mCollectionSyncer ) { d->mCollectionSyncer->retrievalDone(); } // user did the sync himself, we are done now else { d->scheduler->taskDone(); } } void ResourceBasePrivate::slotCollectionSyncDone( KJob * job ) { Q_Q( ResourceBase ); mCollectionSyncer = 0; if ( job->error() ) { emit q->error( job->errorString() ); } else { if ( scheduler->currentTask().type == ResourceScheduler::SyncAll ) { CollectionFetchJob *list = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); list->fetchScope().setResource( mId ); q->connect( list, SIGNAL( result( KJob* ) ), q, SLOT( slotLocalListDone( KJob* ) ) ); return; } } scheduler->taskDone(); } void ResourceBasePrivate::slotLocalListDone( KJob * job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } else { Collection::List cols = static_cast( job )->collections(); foreach ( const Collection &col, cols ) { scheduler->scheduleSync( col ); } scheduler->scheduleFullSyncCompletion(); } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection( const Collection &col ) { Q_Q( ResourceBase ); currentCollection = col; // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentMimeTypes(); contentTypes.removeAll( Collection::mimeType() ); if ( !contentTypes.isEmpty() ) { emit q->status( AgentBase::Running, i18nc( "@info:status", "Syncing collection '%1'", currentCollection.name() ) ); q->retrieveItems( currentCollection ); return; } scheduler->taskDone(); } +void ResourceBasePrivate::slotPrepareItemRetrieval( const Akonadi::Item &item ) +{ + Q_Q( ResourceBase ); + ItemFetchJob *fetch = new ItemFetchJob( item, this ); + fetch->fetchScope().setAncestorRetrieval( q->changeRecorder()->itemFetchScope().ancestorRetrieval() ); + q->connect( fetch, SIGNAL(result(KJob*)), SLOT(slotPrepareItemRetrievalResult(KJob*)) ); +} + +void ResourceBasePrivate::slotPrepareItemRetrievalResult( KJob* job ) +{ + Q_Q( ResourceBase ); + Q_ASSERT_X( scheduler->currentTask().type == ResourceScheduler::FetchItem, + "ResourceBasePrivate::slotPrepareItemRetrievalResult()", + "Preparing item retrieval although no item retrieval is in progress" ); + if ( job->error() ) { + q->cancelTask( job->errorText() ); + return; + } + ItemFetchJob *fetch = qobject_cast( job ); + if ( fetch->items().count() != 1 ) { + q->cancelTask( QLatin1String("The requested item does no longer exist") ); + return; + } + const Item item = fetch->items().first(); + const QSet parts = scheduler->currentTask().itemParts; + if ( !q->retrieveItem( item, parts ) ) + q->cancelTask(); +} + void ResourceBase::itemsRetrievalDone() { Q_D( ResourceBase ); // streaming enabled, so finalize the sync if ( d->mItemSyncer ) { d->mItemSyncer->deliveryDone(); } // user did the sync himself, we are done now else { d->scheduler->taskDone(); } } void ResourceBase::clearCache() { Q_D( ResourceBase ); d->scheduler->scheduleResourceCollectionDeletion(); } Collection ResourceBase::currentCollection() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection , "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress" ); return d->currentCollection; } Item ResourceBase::currentItem() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::FetchItem , "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress" ); return d->scheduler->currentTask().item; } void ResourceBase::synchronizeCollectionTree() { d_func()->scheduler->scheduleCollectionTreeSync(); } void ResourceBase::cancelTask() { Q_D( ResourceBase ); switch ( d->scheduler->currentTask().type ) { case ResourceScheduler::FetchItem: itemRetrieved( Item() ); // sends the error reply and break; case ResourceScheduler::ChangeReplay: d->changeProcessed(); break; default: d->scheduler->taskDone(); } } void ResourceBase::cancelTask( const QString &msg ) { cancelTask(); emit error( msg ); } void ResourceBase::deferTask() { Q_D( ResourceBase ); d->scheduler->deferTask(); } void ResourceBase::doSetOnline( bool state ) { d_func()->scheduler->setOnline( state ); } void ResourceBase::synchronizeCollection( qint64 collectionId ) { CollectionFetchJob* job = new CollectionFetchJob( Collection( collectionId ), CollectionFetchJob::Base ); job->fetchScope().setResource( identifier() ); + job->fetchScope().setAncestorRetrieval( changeRecorder()->collectionFetchScope().ancestorRetrieval() ); connect( job, SIGNAL( result( KJob* ) ), SLOT( slotCollectionListDone( KJob* ) ) ); } void ResourceBasePrivate::slotCollectionListDone( KJob *job ) { if ( !job->error() ) { Collection::List list = static_cast( job )->collections(); if ( !list.isEmpty() ) { Collection col = list.first(); scheduler->scheduleSync( col ); } } // TODO: error handling } void ResourceBase::setTotalItems( int amount ) { kDebug() << amount; Q_D( ResourceBase ); setItemStreamingEnabled( true ); d->mItemSyncer->setTotalItems( amount ); } void ResourceBase::setItemStreamingEnabled( bool enable ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::setItemStreamingEnabled()", "Calling setItemStreamingEnabled() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mItemSyncer, SIGNAL( result( KJob* ) ), SLOT( slotItemSyncDone( KJob* ) ) ); } d->mItemSyncer->setStreamingEnabled( enable ); } void ResourceBase::itemsRetrieved( const Item::List &items ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrieved()", "Calling itemsRetrieved() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mItemSyncer, SIGNAL( result( KJob* ) ), SLOT( slotItemSyncDone( KJob* ) ) ); } d->mItemSyncer->setFullSyncItems( items ); } void ResourceBase::itemsRetrievedIncremental( const Item::List &changedItems, const Item::List &removedItems ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrievedIncremental()", "Calling itemsRetrievedIncremental() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mItemSyncer, SIGNAL( result( KJob* ) ), SLOT( slotItemSyncDone( KJob* ) ) ); } d->mItemSyncer->setIncrementalSyncItems( changedItems, removedItems ); } void ResourceBasePrivate::slotItemSyncDone( KJob *job ) { mItemSyncer = 0; Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBasePrivate::slotPercent( KJob *job, unsigned long percent ) { Q_Q( ResourceBase ); Q_UNUSED( job ); emit q->percent( percent ); } #include "resourcebase.moc" diff --git a/akonadi/resourcebase.h b/akonadi/resourcebase.h index 1c42efc5b..6290b030c 100644 --- a/akonadi/resourcebase.h +++ b/akonadi/resourcebase.h @@ -1,468 +1,470 @@ /* This file is part of akonadiresources. Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RESOURCEBASE_H #define AKONADI_RESOURCEBASE_H #include "akonadi_export.h" #include #include #include class KJob; class ResourceAdaptor; namespace Akonadi { class ResourceBasePrivate; /** * @short The base class for all Akonadi resources. * * This class should be used as a base class by all resource agents, * because it encapsulates large parts of the protocol between * resource agent, agent manager and the Akonadi storage. * * It provides many convenience methods to make implementing a * new Akonadi resource agent as simple as possible. * *

How to write a resource

* * The following provides an overview of what you need to do to implement * your own Akonadi resource. In the following, the term 'backend' refers * to the entity the resource connects with Akonadi, be it a single file * or a remote server. * * @todo Complete this (online/offline state management) * *
Basic %Resource Framework
* * The following is needed to create a new resource: * - A new class deriving from Akonadi::ResourceBase, implementing at least all * pure-virtual methods, see below for further details. * - call init() in your main() function. * - a .desktop file similar to the following example * \code * [Desktop Entry] * Encoding=UTF-8 * Name=My Akonadi Resource * Type=AkonadiResource * Exec=akonadi_my_resource * Icon=my-icon * * X-Akonadi-MimeTypes= * X-Akonadi-Capabilities=Resource * X-Akonadi-Identifier=akonadi_my_resource * \endcode * *
Handling PIM Items
* * To follow item changes in the backend, the following steps are necessary: * - Implement retrieveItems() to synchronize all items in the given * collection. If the backend supports incremental retrieval, * implementing support for that is recommended to improve performance. * - Convert the items provided by the backend to Akonadi items. * This typically happens either in retrieveItems() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Item objects for every retrieved * item. It's very important that every object has its remote identifier set. * - Call itemsRetrieved() or itemsRetrievedIncremental() respectively * with the item objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any item in the Akonadi storage manually. * * To fetch item data on demand, the method retrieveItem() needs to be * reimplemented. Fetch the requested data there and call itemRetrieved() * with the result item. * * To write local changes back to the backend, you need to re-implement * the following three methods: * - itemAdded() * - itemChanged() * - itemRemoved() * Note that these three functions don't get the full payload of the items by default, * you need to change the item fetch scope of the change recorder to fetch the full * payload. This can be expensive with big payloads, though.
* Once you have handled changes in these methods call changeCommitted(). * These methods are called whenever a local item related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * *
Handling Collections
* * To follow collection changes in the backend, the following steps are necessary: * - Implement retrieveCollections() to retrieve collections from the backend. * If the backend supports incremental collections updates, implementing * support for that is recommended to improve performance. * - Convert the collections of the backend to Akonadi collections. * This typically happens either in retrieveCollections() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Collection objects for every retrieved * collection. It's very important that every object has its remote identifier * and its parent remote identifier set. * - Call collectionsRetrieved() or collectionsRetrievedIncremental() respectively * with the collection objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any collection in the Akonadi storage manually. * * * To write local collection changes back to the backend, you need to re-implement * the following three methods: * - collectionAdded() * - collectionChanged() * - collectionRemoved() * Once you have handled changes in these methods call changeCommitted(). * These methods are called whenever a local collection related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * * @todo Convenience base class for collection-less resources */ // FIXME_API: API dox need to be updated for Observer approach (kevin) class AKONADI_EXPORT ResourceBase : public AgentBase { Q_OBJECT public: /** * Use this method in the main function of your resource * application to initialize your resource subclass. * This method also takes care of creating a KApplication * object and parsing command line arguments. * * @note In case the given class is also derived from AgentBase::Observer * it gets registered as its own observer (see AgentBase::Observer), e.g. * resourceInstance->registerObserver( resourceInstance ); * * @code * * class MyResource : public ResourceBase * { * ... * }; * * int main( int argc, char **argv ) * { * return ResourceBase::init( argc, argv ); * } * * @endcode */ template static int init( int argc, char **argv ) { const QString id = parseArguments( argc, argv ); KApplication app; T* r = new T( id ); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Observer *observer = dynamic_cast( r ); if ( observer != 0 ) r->registerObserver( observer ); return init( r ); } /** * This method is used to set the name of the resource. */ //FIXME_API: make sure location is renamed to this by resourcebase void setName( const QString &name ); /** * Returns the name of the resource. */ QString name() const; Q_SIGNALS: /** * This signal is emitted whenever the name of the resource has changed. * * @param name The new name of the resource. */ void nameChanged( const QString &name ); /** * Emitted when a full synchronization has been completed. */ void synchronized(); protected Q_SLOTS: /** * Retrieve the collection tree from the remote server and supply it via * collectionsRetrieved() or collectionsRetrievedIncremental(). * @see collectionsRetrieved(), collectionsRetrievedIncremental() */ virtual void retrieveCollections() = 0; /** * Retrieve all (new/changed) items in collection @p collection. * It is recommended to use incremental retrieval if the backend supports that * and provide the result by calling itemsRetrievedIncremental(). * If incremental retrieval is not possible, provide the full listing by calling * itemsRetrieved( const Item::List& ). * In any case, ensure that all items have a correctly set remote identifier * to allow synchronizing with items already existing locally. * In case you don't want to use the built-in item syncing code, store the retrieved * items manually and call itemsRetrieved() once you are done. * @param collection The collection whose items to retrieve. * @see itemsRetrieved( const Item::List& ), itemsRetrievedIncremental(), itemsRetrieved(), currentCollection() */ virtual void retrieveItems( const Akonadi::Collection &collection ) = 0; /** * Retrieve a single item from the backend. The item to retrieve is provided as @p item. * Add the requested payload parts and call itemRetrieved() when done. * @param item The empty item whose payload should be retrieved. Use this object when delivering * the result instead of creating a new item to ensure conflict detection will work. * @param parts The item parts that should be retrieved. * @return false if there is an immediate error when retrieving the item. * @see itemRetrieved() */ virtual bool retrieveItem( const Akonadi::Item &item, const QSet &parts ) = 0; protected: /** * Creates a base resource. * * @param id The instance id of the resource. */ ResourceBase( const QString & id ); /** * Destroys the base resource. */ ~ResourceBase(); /** * Call this method from retrieveItem() once the result is available. * * @param item The retrieved item. */ void itemRetrieved( const Item &item ); /** * Resets the dirty flag of the given item and updates the remote id. * * Call whenever you have successfully written changes back to the server. * This implicitly calls changeProcessed(). * @param item The changed item. */ void changeCommitted( const Item &item ); /** * Call whenever you have successfully handled or ignored a collection * change notification. * * This will update the remote identifier of @p collection if necessary, * as well as any other collection attributes. * This implicitly calls changeProcessed(). * @param collection The collection which changes have been handled. */ void changeCommitted( const Collection &collection ); /** * Call this to supply the full folder tree retrieved from the remote server. * * @param collections A list of collections. * @see collectionsRetrievedIncremental() */ void collectionsRetrieved( const Collection::List &collections ); /** * Call this to supply incrementally retrieved collections from the remote server. * * @param changedCollections Collections that have been added or changed. * @param removedCollections Collections that have been deleted. * @see collectionsRetrieved() */ void collectionsRetrievedIncremental( const Collection::List &changedCollections, const Collection::List &removedCollections ); /** * Enable collection streaming, that is collections don't have to be delivered at once * as result of a retrieveCollections() call but can be delivered by multiple calls * to collectionsRetrieved() or collectionsRetrievedIncremental(). When all collections * have been retrieved, call collectionsRetrievalDone(). * @param enable @c true if collection streaming should be enabled, @c false by default */ void setCollectionStreamingEnabled( bool enable ); /** * Call this method to indicate you finished synchronizing the collection tree. * * This is not needed if you use the built in syncing without collection streaming * and call collectionsRetrieved() or collectionRetrievedIncremental() instead. * If collection streaming is enabled, call this method once all collections have been delivered * using collectionsRetrieved() or collectionsRetrievedIncremental(). */ void collectionsRetrievalDone(); /** * Call this method to supply the full collection listing from the remote server. * * If the remote server supports incremental listing, it's strongly * recommended to use itemsRetrievedIncremental() instead. * @param items A list of items. * @see itemsRetrievedIncremental(). */ void itemsRetrieved( const Item::List &items ); /** * Call this method when you want to use the itemsRetrieved() method * in streaming mode and indicate the amount of items that will arrive * that way. * @deprecated Use setItemStreamingEnabled( true ) + itemsRetrieved[Incremental]() * + itemsRetrieved() instead. */ void setTotalItems( int amount ); /** * Enable item streaming. * Item streaming is disabled by default. * @param enable @c true if items are delivered in chunks rather in one big block. */ void setItemStreamingEnabled( bool enable ); /** * Call this method to supply incrementally retrieved items from the remote server. * * @param changedItems Items changed in the backend. * @param removedItems Items removed from the backend. */ void itemsRetrievedIncremental( const Item::List &changedItems, const Item::List &removedItems ); /** * Call this method to indicate you finished synchronizing the current collection. * * This is not needed if you use the built in syncing without item streaming * and call itemsRetrieved() or itemsRetrievedIncremental() instead. * If item streaming is enabled, call this method once all items have been delivered * using itemsRetrieved() or itemsRetrievedIncremental(). * @see retrieveItems() */ void itemsRetrievalDone(); /** * Call this method to remove all items and collections of the resource from the * server cache. * * The method should be used whenever the configuration of the resource has changed * and therefor the cached items might not be valid any longer. * * @since 4.3 */ void clearCache(); /** * Returns the collection that is currently synchronized. */ Collection currentCollection() const; /** * Returns the item that is currently retrieved. */ Item currentItem() const; /** * This method is called whenever the resource should start synchronize all data. */ void synchronize(); /** * This method is called whenever the collection with the given @p id * shall be synchronized. */ void synchronizeCollection( qint64 id ); /** * Refetches the Collections. */ void synchronizeCollectionTree(); /** * Stops the execution of the current task and continues with the next one. */ void cancelTask(); /** * Stops the execution of the current task and continues with the next one. * Additionally an error message is emitted. */ void cancelTask( const QString &error ); /** * Stops the execution of the current task and continues with the next one. * The current task will be tried again later. * * @since 4.3 */ void deferTask(); /** * Inherited from AgentBase. */ void doSetOnline( bool online ); private: static QString parseArguments( int, char** ); static int init( ResourceBase *r ); // dbus resource interface friend class ::ResourceAdaptor; bool requestItemDelivery( qint64 uid, const QString &remoteId, const QString &mimeType, const QStringList &parts ); private: Q_DECLARE_PRIVATE( ResourceBase ) Q_PRIVATE_SLOT( d_func(), void slotDeliveryDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionSyncDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotDeleteResourceCollection() ) Q_PRIVATE_SLOT( d_func(), void slotDeleteResourceCollectionDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionDeletionDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotLocalListDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotSynchronizeCollection( const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionListDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotItemSyncDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotPercent( KJob*, unsigned long ) ) + Q_PRIVATE_SLOT( d_func(), void slotPrepareItemRetrieval( const Akonadi::Item& item ) ) + Q_PRIVATE_SLOT( d_func(), void slotPrepareItemRetrievalResult( KJob* ) ) }; } #ifndef AKONADI_RESOURCE_MAIN /** * Convenience Macro for the most common main() function for Akonadi resources. */ #define AKONADI_RESOURCE_MAIN( resourceClass ) \ int main( int argc, char **argv ) \ { \ return Akonadi::ResourceBase::init( argc, argv ); \ } #endif #endif diff --git a/akonadi/kmime/resourcesynchronizationjob.cpp b/akonadi/resourcesynchronizationjob.cpp similarity index 75% rename from akonadi/kmime/resourcesynchronizationjob.cpp rename to akonadi/resourcesynchronizationjob.cpp index e6a8946f2..7f5600306 100644 --- a/akonadi/kmime/resourcesynchronizationjob.cpp +++ b/akonadi/resourcesynchronizationjob.cpp @@ -1,121 +1,126 @@ /* * Copyright (c) 2009 Volker Krause * * This library 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; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "resourcesynchronizationjob.h" #include #include #include #include #include #include #include namespace Akonadi { class ResourceSynchronizationJobPrivate { public: - ResourceSynchronizationJobPrivate() : + ResourceSynchronizationJobPrivate( ResourceSynchronizationJob* parent ) : + q( parent ), interface( 0 ), safetyTimer( 0 ), timeoutCount( 0 ) {} + ResourceSynchronizationJob *q; AgentInstance instance; QDBusInterface* interface; QTimer* safetyTimer; int timeoutCount; static int timeoutCountLimit; + + void slotSynchronized(); + void slotTimeout(); }; int ResourceSynchronizationJobPrivate::timeoutCountLimit = 60; ResourceSynchronizationJob::ResourceSynchronizationJob(const AgentInstance& instance, QObject* parent) : KJob( parent ), - d( new ResourceSynchronizationJobPrivate ) + d( new ResourceSynchronizationJobPrivate( this ) ) { d->instance = instance; d->safetyTimer = new QTimer( this ); connect( d->safetyTimer, SIGNAL(timeout()), SLOT(slotTimeout()) ); d->safetyTimer->setInterval( 10 * 1000 ); d->safetyTimer->setSingleShot( false ); } ResourceSynchronizationJob::~ResourceSynchronizationJob() { delete d; } void ResourceSynchronizationJob::start() { if ( !d->instance.isValid() ) { setError( UserDefinedError ); setErrorText( i18n( "Invalid resource instance." ) ); emitResult(); return; } d->interface = new QDBusInterface( QString::fromLatin1( "org.freedesktop.Akonadi.Resource.%1" ).arg( d->instance.identifier() ), QString::fromLatin1( "/" ), QString::fromLatin1( "org.freedesktop.Akonadi.Resource" ), QDBusConnection::sessionBus(), this ); connect( d->interface, SIGNAL(synchronized()), SLOT(slotSynchronized()) ); if ( d->interface->isValid() ) { d->instance.synchronize(); d->safetyTimer->start(); } else { setError( UserDefinedError ); setErrorText( i18n( "Unable to obtain D-Bus interface for resource '%1'", d->instance.identifier() ) ); emitResult(); return; } } -void ResourceSynchronizationJob::slotSynchronized() +void ResourceSynchronizationJobPrivate::slotSynchronized() { - disconnect( d->interface, SIGNAL(synchronized()), this, SLOT(slotSynchronized()) ); - d->safetyTimer->stop(); - emitResult(); + q->disconnect( interface, SIGNAL(synchronized()), q, SLOT(slotSynchronized()) ); + safetyTimer->stop(); + q->emitResult(); } -void ResourceSynchronizationJob::slotTimeout() +void ResourceSynchronizationJobPrivate::slotTimeout() { - d->instance = AgentManager::self()->instance( d->instance.identifier() ); - d->timeoutCount++; - - if ( d->timeoutCount > d->timeoutCountLimit ) { - d->safetyTimer->stop(); - setError( UserDefinedError ); - setErrorText( i18n( "Resource synchronization timed out." ) ); - emitResult(); + instance = AgentManager::self()->instance( instance.identifier() ); + timeoutCount++; + + if ( timeoutCount > timeoutCountLimit ) { + safetyTimer->stop(); + q->setError( KJob::UserDefinedError ); + q->setErrorText( i18n( "Resource synchronization timed out." ) ); + q->emitResult(); return; } - if ( d->instance.status() == AgentInstance::Idle ) { + if ( instance.status() == AgentInstance::Idle ) { // try again, we might have lost the synchronized() signal - kDebug() << "trying again to sync resource" << d->instance.identifier(); - d->instance.synchronize(); + kDebug() << "trying again to sync resource" << instance.identifier(); + instance.synchronize(); } } } #include "resourcesynchronizationjob.moc" diff --git a/akonadi/resourcesynchronizationjob.h b/akonadi/resourcesynchronizationjob.h new file mode 100644 index 000000000..cf1d23d58 --- /dev/null +++ b/akonadi/resourcesynchronizationjob.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009 Volker Krause + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef AKONADI_RESOURCESYNCHRONIZATIONJOB_H +#define AKONADI_RESOURCESYNCHRONIZATIONJOB_H + +#include "akonadi_export.h" + +#include + +namespace Akonadi { + +class AgentInstance; +class ResourceSynchronizationJobPrivate; + +/** + * @short Job that synchronizes a resource. + * + * This job will trigger a resource to synchronize the backend it is + * responsible for (e.g. a local file or a groupware server) with the + * Akonadi storage. + * + * If you only want to trigger the synchronization without being + * interested in the result, using Akonadi::AgentInstance::synchronize() is enough. + * If you want to wait until it's finished, use this class. + * + * Example: + * + * @code + * using namespace Akonadi; + * + * const AgentInstance resource = AgentManager::self()->instance( "myresourceidentifier" ); + * + * ResourceSynchronizationJob *job = new ResourceSynchronizationJob( resource ); + * connect( job, SIGNAL( result( KJob* ) ), SLOT( synchronizationFinished( KJob* ) ) ); + * + * @endcode + * + * @note This is a KJob not an Akonadi::Job, so it wont auto-start! + * + * @author Volker Krause + * @since 4.4 + */ +class AKONADI_EXPORT ResourceSynchronizationJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new synchronization job for the given resource. + * + * @param instance The resource instance to synchronize. + */ + ResourceSynchronizationJob( const AgentInstance &instance, QObject *parent = 0 ); + + /** + * Destroys the synchronization job. + */ + ~ResourceSynchronizationJob(); + + /* reimpl */ + void start(); + + private: + //@cond PRIVATE + ResourceSynchronizationJobPrivate* const d; + friend class ResourceSynchronizationJobPrivate; + + Q_PRIVATE_SLOT( d, void slotSynchronized() ) + Q_PRIVATE_SLOT( d, void slotTimeout() ) + //@endcond +}; + +} + +#endif diff --git a/akonadi/tests/monitortest.cpp b/akonadi/tests/monitortest.cpp index 182b2f9cf..68033e3d0 100644 --- a/akonadi/tests/monitortest.cpp +++ b/akonadi/tests/monitortest.cpp @@ -1,359 +1,362 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "monitortest.h" #include "test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; QTEST_AKONADIMAIN( MonitorTest, NoGUI ) static Collection res3; Q_DECLARE_METATYPE(Akonadi::Collection::Id) Q_DECLARE_METATYPE(QSet) void MonitorTest::initTestCase() { Control::start(); res3 = Collection( collectionIdFromPath( "res3" ) ); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void MonitorTest::testMonitor_data() { QTest::addColumn( "fetchCol" ); QTest::newRow( "with collection fetching" ) << true; QTest::newRow( "without collection fetching" ) << false; } void MonitorTest::testMonitor() { QFETCH( bool, fetchCol ); Monitor *monitor = new Monitor( this ); monitor->setCollectionMonitored( Collection::root() ); monitor->fetchCollection( fetchCol ); monitor->itemFetchScope().fetchFullPayload(); // monitor signals qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); QSignalSpy caddspy( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); QSignalSpy cmodspy( monitor, SIGNAL(collectionChanged(const Akonadi::Collection&)) ); QSignalSpy cmvspy( monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)) ); QSignalSpy crmspy( monitor, SIGNAL(collectionRemoved(const Akonadi::Collection&)) ); QSignalSpy cstatspy( monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)) ); QSignalSpy iaddspy( monitor, SIGNAL(itemAdded(const Akonadi::Item&, const Akonadi::Collection&)) ); QSignalSpy imodspy( monitor, SIGNAL(itemChanged(const Akonadi::Item&, const QSet&)) ); QSignalSpy imvspy( monitor, SIGNAL(itemMoved(const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection&)) ); QSignalSpy irmspy( monitor, SIGNAL(itemRemoved(const Akonadi::Item&)) ); QVERIFY( caddspy.isValid() ); QVERIFY( cmodspy.isValid() ); QVERIFY( cmvspy.isValid() ); QVERIFY( crmspy.isValid() ); QVERIFY( cstatspy.isValid() ); QVERIFY( iaddspy.isValid() ); QVERIFY( imodspy.isValid() ); QVERIFY( imvspy.isValid() ); QVERIFY( irmspy.isValid() ); // create a collection Collection monitorCol; monitorCol.setParentCollection( res3 ); monitorCol.setName( "monitor" ); CollectionCreateJob *create = new CollectionCreateJob( monitorCol, this ); - QVERIFY( create->exec() ); + AKVERIFYEXEC( create ); monitorCol = create->collection(); QVERIFY( monitorCol.isValid() ); QTest::qWait(1000); // make sure the DBus signal has been processed QCOMPARE( caddspy.count(), 1 ); QList arg = caddspy.takeFirst(); Collection col = arg.at(0).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), QString("monitor") ); Collection parent = arg.at(1).value(); QCOMPARE( parent, res3 ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // add an item Item newItem; newItem.setMimeType( "application/octet-stream" ); ItemCreateJob *append = new ItemCreateJob( newItem, monitorCol, this ); QVERIFY( append->exec() ); Item monitorRef = append->item(); QVERIFY( monitorRef.isValid() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); /* qRegisterMetaType() registers the type with a name of "qlonglong". Doing qRegisterMetaType( "Akonadi::Collection::Id" ) doesn't help. The problem here is that Akonadi::Collection::Id is a typedef to qlonglong, and qlonglong is already a registered meta type. So the signal spy will give us a QVariant of type Akonadi::Collection::Id, but calling .value() on that variant will in fact end up calling qvariant_cast. From the point of view of QMetaType, Akonadi::Collection::Id and qlonglong are different types, so QVariant can't convert, and returns a default-constructed qlonglong, zero. When connecting to a real slot (without QSignalSpy), this problem is avoided, because the casting is done differently (via a lot of void pointers). The docs say nothing about qRegisterMetaType -ing a typedef, so I'm not sure if this is a bug or not. (cberzan) */ QCOMPARE( iaddspy.count(), 1 ); arg = iaddspy.takeFirst(); Item item = arg.at( 0 ).value(); QCOMPARE( item, monitorRef ); QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); Collection collection = arg.at( 1 ).value(); QCOMPARE( collection.id(), monitorCol.id() ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // modify an item item.setPayload( "some new content" ); ItemModifyJob *store = new ItemModifyJob( item, this ); QVERIFY( store->exec() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); QCOMPARE( imodspy.count(), 1 ); arg = imodspy.takeFirst(); item = arg.at( 0 ).value(); QCOMPARE( monitorRef, item ); QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray( "some new content" ) ); QSet parts = arg.at( 1 ).value >(); QCOMPARE( parts, QSet() << "PLD:RFC822" ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // move an item ItemMoveJob *move = new ItemMoveJob( item, res3 ); QVERIFY( move->exec() ); QTest::qWait( 1000 ); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); QCOMPARE( imvspy.count(), 1 ); arg = imvspy.takeFirst(); item = arg.at( 0 ).value(); // the item QCOMPARE( monitorRef, item ); col = arg.at( 1 ).value(); // the source collection QCOMPARE( col.id(), monitorCol.id() ); col = arg.at( 2 ).value(); // the destination collection QCOMPARE( col.id(), res3.id() ); QCOMPARE( cmodspy.count(), 2 ); arg = cmodspy.takeFirst(); Collection col1 = arg.at( 0 ).value(); arg = cmodspy.takeFirst(); Collection col2 = arg.at( 0 ).value(); // source and dest collections, in any order QVERIFY( ( col1.id() == monitorCol.id() && col2.id() == res3.id() ) || ( col2.id() == monitorCol.id() && col1.id() == res3.id() ) ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // delete an item ItemDeleteJob *del = new ItemDeleteJob( monitorRef, this ); QVERIFY( del->exec() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); cmodspy.clear(); QCOMPARE( irmspy.count(), 1 ); arg = irmspy.takeFirst(); Item ref = qvariant_cast( arg.at(0) ); QCOMPARE( monitorRef, ref ); + QCOMPARE( ref.parentCollection(), res3 ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); imvspy.clear(); // modify a collection monitorCol.setName( "changed name" ); CollectionModifyJob *mod = new CollectionModifyJob( monitorCol, this ); AKVERIFYEXEC( mod ); QTest::qWait(1000); QCOMPARE( cmodspy.count(), 1 ); arg = cmodspy.takeFirst(); col = arg.at(0).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), QString("changed name") ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // move a collection Collection dest = Collection( collectionIdFromPath( "res1/foo" ) ); CollectionMoveJob *cmove = new CollectionMoveJob( monitorCol, dest, this ); AKVERIFYEXEC( cmove ); QTest::qWait( 1000 ); QCOMPARE( cmvspy.count(), 1 ); arg = cmvspy.takeFirst(); col = arg.at( 0 ).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), monitorCol.name() ); col = arg.at( 1 ).value(); QCOMPARE( col, res3 ); col = arg.at( 2 ).value(); QCOMPARE( col, dest ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // delete a collection CollectionDeleteJob *cdel = new CollectionDeleteJob( monitorCol, this ); QVERIFY( cdel->exec() ); QTest::qWait(1000); QCOMPARE( crmspy.count(), 1 ); arg = crmspy.takeFirst(); - QCOMPARE( arg.at(0).value().id(), monitorCol.id() ); + col = arg.at(0).value(); + QCOMPARE( col.id(), monitorCol.id() ); + QCOMPARE( col.parentCollection(), dest ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); } void MonitorTest::testVirtualCollectionsMonitoring() { Monitor *monitor = new Monitor( this ); monitor->setCollectionMonitored( Collection( 1 ) ); // top-level 'Search' collection QSignalSpy caddspy( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); QVERIFY( caddspy.isValid() ); SearchCreateJob *job = new SearchCreateJob( "Test search collection", "test-search-query", this ); AKVERIFYEXEC( job ); QTest::qWait( 1000 ); QCOMPARE( caddspy.count(), 1 ); } #include "monitortest.moc" diff --git a/kcal/attendee.h b/kcal/attendee.h index f436323ee..73bf508c1 100644 --- a/kcal/attendee.h +++ b/kcal/attendee.h @@ -1,282 +1,283 @@ /* This file is part of the kcal library. Copyright (c) 2001-2003 Cornelius Schumacher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Attendee class. @author Cornelius Schumacher \ */ #ifndef KCAL_ATTENDEE_H #define KCAL_ATTENDEE_H #include #include #include "listbase.h" #include "person.h" namespace KCal { /** @brief Represents information related to an attendee of an Calendar Incidence, typically a meeting or task (to-do). Attendees are people with a name and (optional) email address who are invited to participate in some way in a meeting or task. This class also tracks that status of the invitation: accepted; tentatively accepted; declined; delegated to another person; in-progress; completed. Attendees may optionally be asked to @acronym RSVP ("Respond Please") to the invitation. Note that each attendee be can optionally associated with a @acronym UID (unique identifier) derived from a Calendar Incidence, Email Message, or any other thing you want. */ class KCAL_EXPORT Attendee : private Person { public: using Person::setEmail; using Person::email; using Person::setName; using Person::name; using Person::fullName; /** The different types of participant status. The meaning is specific to the incidence type in context. */ enum PartStat { NeedsAction, /**< Event, to-do or journal needs action (default) */ Accepted, /**< Event, to-do or journal accepted */ Declined, /**< Event, to-do or journal declined */ Tentative, /**< Event or to-do tentatively accepted */ Delegated, /**< Event or to-do delegated */ Completed, /**< To-do completed */ InProcess, /**< To-do in process of being completed */ None }; /** The different types of participation roles. */ enum Role { ReqParticipant, /**< Participation is required (default) */ OptParticipant, /**< Participation is optional */ NonParticipant, /**< Non-Participant; copied for information purposes */ Chair /**< Chairperson */ }; /** List of attendees. */ typedef ListBase List; /** Constructs an attendee consisting of a Person name (@p name) and email address (@p email); invitation status and #Role; an optional @acronym RSVP flag and @acronym UID. @param name is person name of the attendee. @param email is person email address of the attendee. @param rsvp if true, the attendee is requested to reply to invitations. @param status is the #PartStat status of the attendee. @param role is the #Role of the attendee. @param uid is the @acronym UID of the attendee. */ Attendee( const QString &name, const QString &email, bool rsvp = false, PartStat status = None, Role role = ReqParticipant, const QString &uid = QString() ); /** Constructs an attendee by copying another attendee. @param attendee is the attendee to be copied. */ Attendee( const Attendee &attendee ); /** Destroys the attendee. */ ~Attendee(); /** Sets the Role of the attendee to @p role. @param role is the Role to use for the attendee. @see role() */ void setRole( Role role ); /** Returns the Role of the attendee. @see setRole() */ Role role() const; /** Returns the attendee Role as a text string. @see role(), roleName() */ QString roleStr() const; /** Returns the specified #Role @p role as a text string. @param role is a #Role value. @see role(), roleStr() */ static QString roleName( Role role ); /** Returns a list of strings representing each #Role. */ static QStringList roleList(); /** Sets the @acronym UID of the attendee to @p uid. @param uid is the @acronym UID to use for the attendee. @see uid() */ void setUid ( const QString &uid ); /** Returns the @acronym UID of the attendee. @see setUid() */ QString uid() const; /** Sets the #PartStat of the attendee to @p status. @param status is the #PartStat to use for the attendee. @see status() */ void setStatus( PartStat status ); /** Returns the #PartStat of the attendee. @see setStatus() */ PartStat status() const; /** Returns the attendee #PartStat as a text string. @see status(), statusName() */ QString statusStr() const; /** Returns the specified #PartStat @p status as a text string. @param status is a #PartStat value. @see status(), statusStr() */ static QString statusName( PartStat status ); /** Returns a list of strings representing each #PartStat. */ static QStringList statusList(); /** Sets the @acronym RSVP flag of the attendee to @p rsvp. @param rsvp if set (true), the attendee is requested to reply to invitations. @see RSVP() */ void setRSVP( bool rsvp ); /** Returns the attendee @acronym RSVP flag. @see setRSVP() */ bool RSVP() const; /** Compares this with @p attendee for equality. @param attendee the attendee to compare. */ - bool operator==( const Attendee &attendee ); + //KDE5: make const + bool operator==( const Attendee &attendee ); //krazy:exclude=operators /** Sets the delegate. @param delegate is a string containing a MAILTO URI of those delegated to attend the meeting. @see delegate(), setDelegator(). */ void setDelegate( const QString &delegate ); /** Returns the delegate. @see setDelegate(). */ QString delegate() const; /** Sets the delegator. @param delegator is a string containing a MAILTO URI of those who have delegated their meeting attendance. @see delegator(), setDelegate(). */ void setDelegator( const QString &delegator ); /** Returns the delegator. @see setDelegator(). */ QString delegator() const; /** Sets this attendee equal to @p attendee. @param attendee is the attendee to copy. */ Attendee &operator=( const Attendee &attendee ); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } #endif diff --git a/kcal/calfilter.h b/kcal/calfilter.h index 85e9488be..be203ec64 100644 --- a/kcal/calfilter.h +++ b/kcal/calfilter.h @@ -1,228 +1,229 @@ /* This file is part of the kcal library. Copyright (c) 2001,2003,2004 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the CalFilter class. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #ifndef KCAL_CALFILTER_H #define KCAL_CALFILTER_H #include #include "kcal_export.h" #include "event.h" #include "todo.h" #include "journal.h" namespace KCal { /** @brief Provides a filter for calendars. This class provides a means for filtering calendar incidences by a list of email addresses, a list of categories, or other #Criteria. The following #Criteria are available: - remove recurring Incidences - keep Incidences with a matching category (see setCategoryList()) - remove completed To-dos (see setCompletedTimeSpan()) - remove inactive To-dos - remove To-dos without a matching attendee (see setEmailList()) */ class KCAL_EXPORT CalFilter { public: /** Filtering Criteria. */ enum Criteria { HideRecurring = 1, /**< Remove incidences that recur */ HideCompletedTodos = 2,/**< Remove completed to-dos */ ShowCategories = 4, /**< Show incidences with at least one matching category */ HideInactiveTodos = 8, /**< Remove to-dos that haven't started yet */ HideNoMatchingAttendeeTodos = 16 /**< Remove to-dos without a matching attendee */ }; /** Constructs an empty filter -- a filter without a name or criteria. */ CalFilter(); /** Constructs a filter with @p name. @param name is the name of this filter. */ explicit CalFilter( const QString &name ); /** Destroys this filter. */ ~CalFilter(); /** Sets the filter name. @param name is the name of this filter. @see name(). */ void setName( const QString &name ); /** Returns the filter name. @see setName(). */ QString name() const; /** Sets the criteria which must be fulfilled for an Incidence to pass the filter. @param criteria is a combination of #Criteria. @see criteria(). */ void setCriteria( int criteria ); /** Returns the inclusive filter criteria. @see setCriteria(). */ int criteria() const; /** Applies the filter to a list of Events. All events not matching the filter criteria are removed from the list. @param eventList is a list of Events to filter. */ void apply( Event::List *eventList ) const; /** Applies the filter to a list of To-dos. All to-dos not matching the filter criterias are removed from the list. @param todoList is a list of To-dos to filter. */ void apply( Todo::List *todoList ) const; /** Applies the filter to a list of Journals. All journals not matching the filter criterias are removed from the list. @param journalList is a list of Journals to filter. */ void apply( Journal::List *journalList ) const; /** Applies the filter criteria to the specified Incidence. @param incidence is the Incidence to filter. @return true if the Incidence passes the criteria; false otherwise. */ bool filterIncidence( Incidence *incidence ) const; /** Enables or disables the filter. @param enabled is true if the filter is to be enabled; false otherwise. @see isEnabled(). */ void setEnabled( bool enabled ); /** Returns whether the filter is enabled or not. @see setEnabled(). */ bool isEnabled() const; /** Sets the list of categories to be considered when filtering incidences according to the #ShowCategories criteria. @param categoryList is a QStringList of categories. @see categoryList(). */ void setCategoryList( const QStringList &categoryList ); /** Returns the category list for this filter. @see setCategoryList(). */ QStringList categoryList() const; /** Sets the list of email addresses to be considered when filtering incidences according ot the #HideNoMatchingAttendeeTodos criteria. @param emailList is a QStringList of email addresses. @see emailList(). */ void setEmailList( const QStringList &emailList ); /** Returns the email list for this filter. @see setEmailList(). */ QStringList emailList() const; /** Sets the number of days for the #HideCompletedTodos criteria. If a to-do has been completed within the recent @p timespan days, then that to-do will be removed during filtering. If a time span is not specified in the filter, then all completed to-dos will be removed if the #HideCompletedTodos criteria is set. @param timespan is an integer representing a time span in days. @see completedTimeSpan(). */ void setCompletedTimeSpan( int timespan ); /** Returns the completed time span for this filter. @see setCompletedTimeSpan() */ int completedTimeSpan() const; /** Compares this with @p filter for equality. @param filter the CalFilter to compare. */ - bool operator==( const CalFilter &filter ); + //KDE5: make const + bool operator==( const CalFilter &filter ); //krazy:exclude=operators private: //@cond PRIVATE Q_DISABLE_COPY( CalFilter ) class Private; Private *const d; //@endcond }; } #endif diff --git a/kcal/person.cpp b/kcal/person.cpp index f05975c9f..b07eb4161 100644 --- a/kcal/person.cpp +++ b/kcal/person.cpp @@ -1,162 +1,171 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Person class. @brief Represents a person, by name and email address. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "person.h" #include "kpimutils/email.h" #include #include #include using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::Person::Private { public: QString mName; // person name QString mEmail; // person email address }; //@endcond Person::Person() : d( new KCal::Person::Private ) { } Person::Person( const QString &fullName ) : d( new Private ) { KPIMUtils::extractEmailAddressAndName( fullName, d->mEmail, d->mName ); } Person Person::fromFullName( const QString &fullName ) { QString email, name; KPIMUtils::extractEmailAddressAndName( fullName, email, name ); return Person( name, email ); } Person::Person( const QString &name, const QString &email ) : d( new KCal::Person::Private ) { d->mName = name; d->mEmail = email; } Person::Person( const Person &person ) : d( new KCal::Person::Private( *person.d ) ) { } Person::~Person() { delete d; } +#if defined(Q_CC_MSVC) bool KCal::Person::operator==( const Person &person ) const +#else +bool KCal::Person::operator==( const Person &person ) +#endif { return d->mName == person.d->mName && d->mEmail == person.d->mEmail; } Person &KCal::Person::operator=( const Person &person ) { // check for self assignment if ( &person == this ) { return *this; } *d = *person.d; return *this; } QString Person::fullName() const { if ( d->mName.isEmpty() ) { return d->mEmail; } else { if ( d->mEmail.isEmpty() ) { return d->mName; } else { // Taken from KABC::Addressee::fullEmail QString name = d->mName; QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); bool weNeedToQuote = name.indexOf( needQuotes ) != -1; if ( weNeedToQuote ) { if ( name[0] != '"' ) { name.prepend( '"' ); } if ( name[ name.length()-1 ] != '"' ) { name.append( '"' ); } } return name + " <" + d->mEmail + '>'; } } } QString Person::name() const { return d->mName; } QString Person::email() const { return d->mEmail; } bool Person::isEmpty() const { return d->mEmail.isEmpty() && d->mName.isEmpty(); } void Person::setName( const QString &name ) { d->mName = name; } void Person::setEmail( const QString &email ) { if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { d->mEmail = email.mid( 7 ); } else { d->mEmail = email; } } + +static inline uint qHash( const Person &key ) +{ + return qHash( key.fullName() ); +} diff --git a/kcal/person.h b/kcal/person.h index 5b69f988b..aaea8c4a6 100644 --- a/kcal/person.h +++ b/kcal/person.h @@ -1,160 +1,159 @@ /* This file is part of the kcal library. Copyright (c) 2001-2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Person class. @brief Represents a person, by name ane email address. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #ifndef KCAL_PERSON_H #define KCAL_PERSON_H #include #include #include "kcal_export.h" namespace KCal { /** This class represents a person, with a name and an email address. It supports the "FirstName LastName " format. */ class KCAL_EXPORT Person { public: /** Constructs a blank person. */ Person(); /** Constructs a person with name and email address taken from @p fullName. @param fullName is the name and email of the person in the form FirstName LastName \. */ static Person fromFullName( const QString &fullName ); /** \deprecated Use fromFullName() instead. */ KDE_CONSTRUCTOR_DEPRECATED explicit Person( const QString &fullName ); /** Constructs a person with the name @p name and email address @p email. @param name is the name of this person. @param email is the email address of this person. */ Person( const QString &name, const QString &email ); /** Constructs a person as a copy of another person object. @param person is the person to copy. */ Person( const Person &person ); /** Destroys a person. */ ~Person(); /** Returns true if the person name and email address are empty. */ bool isEmpty() const; /** Returns the full name of this person. */ QString fullName( ) const; /** Sets the name of the person to @p name. @param name is the name of this person. @see name() */ void setName( const QString &name ); /** Returns the person name string. @see setName() */ QString name() const; /** Sets the email address for this person to @p email. @param email is the email address for this person. @see email() */ void setEmail( const QString &email ); /** Returns the email address for this person. @see setEmail() */ QString email() const; /** Compares this with @p person for equality. @param person is the person to compare. */ + //KDE5: make const for all +#if defined(Q_CC_MSVC) bool operator==( const Person &person ) const; - +#else + bool operator==( const Person &person ); //krazy:exclude=operators +#endif /** Sets this person equal to @p person. @param person is the person to copy. */ Person &operator=( const Person &person ); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } -inline uint qHash(const KCal::Person &key) -{ - return qHash(key.fullName()); -} - #endif diff --git a/mailtransport/tests/CMakeLists.txt b/mailtransport/tests/CMakeLists.txt index 60b94bb3d..f6d8ba545 100644 --- a/mailtransport/tests/CMakeLists.txt +++ b/mailtransport/tests/CMakeLists.txt @@ -1,64 +1,63 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) macro(add_akonadi_isolated_test _source) get_filename_component(_targetName ${_source} NAME_WE) set(_srcList ${_source} ) kde4_add_executable(${_targetName} TEST ${_srcList}) target_link_libraries(${_targetName} ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} - ${KDE4_AKONADI_LIBS} - ${KDE4_AKONADI_KMIME_LIBS} - ${KDE4_KDECORE_LIBS} - mailtransport - ${KDE4_KMIME_LIBS} - ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} + akonadi-kde + akonadi-kmime + mailtransport + kmime + ${KDE4_KDECORE_LIBS} ) # based on kde4_add_unit_test if (WIN32) get_target_property( _loc ${_targetName} LOCATION ) set(_executable ${_loc}.bat) else (WIN32) set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_targetName}) endif (WIN32) if (UNIX) set(_executable ${_executable}.shell) endif (UNIX) find_program(_testrunner akonaditest) add_test( mailtransport-${_targetName} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config.xml ${_executable} ) endmacro(add_akonadi_isolated_test) # Independent executables: set(tm_srcs transportmgr.cpp) kde4_add_executable(transportmgr TEST ${tm_srcs}) target_link_libraries(transportmgr ${KDE4_KDEUI_LIBS} mailtransport) set(queuer_srcs queuer.cpp) kde4_add_executable(queuer TEST ${queuer_srcs}) -target_link_libraries(queuer ${KDE4_KDEUI_LIBS} ${KDE4_MAILTRANSPORT_LIBS} ${KDE4_KMIME_LIBS} mailtransport) +target_link_libraries(queuer ${KDE4_KDEUI_LIBS} mailtransport kmime akonadi-kde) set( sendqueued_srcs sendqueued.cpp ) kde4_add_executable( sendqueued TEST ${sendqueued_srcs} ) -target_link_libraries( sendqueued mailtransport ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY}) +target_link_libraries( sendqueued ${KDE4_KDEUI_LIBS} mailtransport akonadi-kde akonadi-kmime) set( clearerror_srcs clearerror.cpp ) kde4_add_executable( clearerror TEST ${clearerror_srcs} ) -target_link_libraries( clearerror mailtransport ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY}) +target_link_libraries( clearerror ${KDE4_KDEUI_LIBS} mailtransport akonadi-kde akonadi-kmime) set( abort_srcs abort.cpp ) kde4_add_executable( abort TEST ${abort_srcs} ) -target_link_libraries( abort mailtransport ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY}) +target_link_libraries( abort ${KDE4_KDEUI_LIBS} mailtransport akonadi-kde) # Akonadi testrunner-based tests: add_akonadi_isolated_test( attributetest.cpp ) add_akonadi_isolated_test( messagequeuejobtest.cpp )