1/*
2 This file is part of akonadiresources.
3
4 Copyright (c) 2006 Till Adam <adam@kde.org>
5 Copyright (c) 2007 Volker Krause <vkrause@kde.org>
6 Copyright (c) 2008 Kevin Krammer <kevin.krammer@gmx.at>
7
8 This library is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Library General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at your
11 option) any later version.
12
13 This library is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
16 License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22*/
23
24#ifndef AKONADI_AGENTBASE_H
25#define AKONADI_AGENTBASE_H
26
27#include "akonadi_export.h"
28#include <akonadi/item.h>
29
30#include <KDE/KApplication>
31
32#include <QtDBus/QDBusConnection>
33#include <QtDBus/QDBusContext>
34
35class Akonadi__ControlAdaptor;
36class Akonadi__StatusAdaptor;
37
38namespace Akonadi {
39
40class AgentBasePrivate;
41class ChangeRecorder;
42class Collection;
43class Item;
44class Session;
45
46/**
47 * @short The base class for all Akonadi agents and resources.
48 *
49 * This class is a base class for all Akonadi agents, which covers the real
50 * agent processes and all resources.
51 *
52 * It provides:
53 * - lifetime management
54 * - change monitoring and recording
55 * - configuration interface
56 * - problem reporting
57 *
58 * Akonadi Server supports several ways to launch agents and resources:
59 * - As a separate application (@see AKONADI_AGENT_MAIN)
60 * - As a thread in the AgentServer
61 * - As a separate process, using the akonadi_agent_launcher
62 *
63 * The idea is this, the agent or resource is written as a plugin instead of an
64 * executable (@see AgentFactory). In the AgentServer case, the AgentServer
65 * looks up the plugin and launches the agent in a separate thread. In the
66 * launcher case, a new akonadi_agent_launcher process is started for each
67 * agent or resource instance.
68 *
69 * When making an Agent or Resource suitable for running in the AgentServer some
70 * extra caution is needed. Because multiple instances of several kinds of agents
71 * run in the same process, one cannot blindly use global objects like KGlobal.
72 * For this reasons several methods where added to avoid problems in this context,
73 * most notably AgentBase::config() and AgentBase::componentData(). Additionally,
74 * one cannot use QDBusConnection::sessionBus() with dbus < 1.4, because of a
75 * multithreading bug in libdbus. Instead one should use
76 * DBusConnectionPool::threadConnection() which works around this problem.
77 *
78 * @author Till Adam <adam@kde.org>, Volker Krause <vkrause@kde.org>
79 */
80class AKONADI_EXPORT AgentBase : public QObject, protected QDBusContext
81{
82 Q_OBJECT
83
84public:
85 /**
86 * @short The interface for reacting on monitored or replayed changes.
87 *
88 * The Observer provides an interface to react on monitored or replayed changes.
89 *
90 * Since the this base class does only tell the change recorder that the change
91 * has been processed, an AgentBase subclass which wants to actually process
92 * the change needs to subclass Observer and reimplement the methods it is
93 * interested in.
94 *
95 * Such an agent specific Observer implementation can either be done
96 * stand-alone, i.e. as a separate object, or by inheriting both AgentBase
97 * and AgentBase::Observer.
98 *
99 * The observer implementation then has registered with the agent, so it
100 * can forward the incoming changes to the observer.
101 *
102 * @note In the multiple inheritance approach the init() method automatically
103 * registers itself as the observer.
104 *
105 * @note Do not call the base implementation of reimplemented virtual methods!
106 * The default implementation disconnected themselves from the Akonadi::ChangeRecorder
107 * to enable internal optimizations for unused notifications.
108 *
109 * Example for stand-alone observer:
110 * @code
111 * class ExampleAgent : public AgentBase
112 * {
113 * public:
114 * ExampleAgent( const QString &id );
115 *
116 * ~ExampleAgent();
117 *
118 * private:
119 * AgentBase::Observer *mObserver;
120 * };
121 *
122 * class ExampleObserver : public AgentBase::Observer
123 * {
124 * protected:
125 * void itemChanged( const Item &item );
126 * };
127 *
128 * ExampleAgent::ExampleAgent( const QString &id )
129 : AgentBase( id )
130 , mObserver( 0 )
131 * {
132 * mObserver = new ExampleObserver();
133 * registerObserver( mObserver );
134 * }
135 *
136 * ExampleAgent::~ExampleAgent()
137 * {
138 * delete mObserver;
139 * }
140 *
141 * void ExampleObserver::itemChanged( const Item &item )
142 * {
143 * // do something with item
144 * kDebug() << "Item id=" << item.id();
145 *
146 * // let base implementation tell the change recorder that we
147 * // have processed the change
148 * AgentBase::Observer::itemChanged( item );
149 * }
150 * @endcode
151 *
152 * Example for observer through multiple inheritance:
153 * @code
154 * class ExampleAgent : public AgentBase, public AgentBase::Observer
155 * {
156 * public:
157 * ExampleAgent( const QString &id );
158 *
159 * protected:
160 * void itemChanged( const Item &item );
161 * };
162 *
163 * ExampleAgent::ExampleAgent( const QString &id )
164 : AgentBase( id )
165 * {
166 * // no need to create or register observer since
167 * // we are the observer and registration happens automatically
168 * // in init()
169 * }
170 *
171 * void ExampleAgent::itemChanged( const Item &item )
172 * {
173 * // do something with item
174 * kDebug() << "Item id=" << item.id();
175 *
176 * // let base implementation tell the change recorder that we
177 * // have processed the change
178 * AgentBase::Observer::itemChanged( item );
179 * }
180 * @endcode
181 *
182 * @author Kevin Krammer <kevin.krammer@gmx.at>
183 *
184 * @deprecated Use ObserverV2 instead
185 */
186 class AKONADI_EXPORT Observer // krazy:exclude=dpointer
187 {
188 public:
189 /**
190 * Creates an observer instance.
191 */
192 Observer();
193
194 /**
195 * Destroys the observer instance.
196 */
197 virtual ~Observer();
198
199 /**
200 * Reimplement to handle adding of new items.
201 * @param item The newly added item.
202 * @param collection The collection @p item got added to.
203 */
204 virtual void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection);
205
206 /**
207 * Reimplement to handle changes to existing items.
208 * @param item The changed item.
209 * @param partIdentifiers The identifiers of the item parts that has been changed.
210 */
211 virtual void itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &partIdentifiers);
212
213 /**
214 * Reimplement to handle deletion of items.
215 * @param item The deleted item.
216 */
217 virtual void itemRemoved(const Akonadi::Item &item);
218
219 /**
220 * Reimplement to handle adding of new collections.
221 * @param collection The newly added collection.
222 * @param parent The parent collection.
223 */
224 virtual void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent);
225
226 /**
227 * Reimplement to handle changes to existing collections.
228 * @param collection The changed collection.
229 */
230 virtual void collectionChanged(const Akonadi::Collection &collection);
231
232 /**
233 * Reimplement to handle deletion of collections.
234 * @param collection The deleted collection.
235 */
236 virtual void collectionRemoved(const Akonadi::Collection &collection);
237 };
238
239 /**
240 * BC extension of Observer with support for monitoring item and collection moves.
241 * Use this one instead of Observer.
242 *
243 * @since 4.4
244 */
245 class AKONADI_EXPORT ObserverV2 : public Observer // krazy:exclude=dpointer
246 {
247 public:
248 using Observer::collectionChanged;
249
250 /**
251 * Reimplement to handle item moves.
252 * When using this class in combination with Akonadi::ResourceBase, inter-resource
253 * moves are handled internally already and the corresponding add or delete method
254 * is called instead.
255 *
256 * @param item The moved item.
257 * @param collectionSource The collection the item has been moved from.
258 * @param collectionDestination The collection the item has been moved to.
259 */
260 virtual void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource,
261 const Akonadi::Collection &collectionDestination);
262
263 /**
264 * Reimplement to handle item linking.
265 * This is only relevant for virtual resources.
266 * @param item The linked item.
267 * @param collection The collection the item is linked to.
268 */
269 virtual void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection);
270
271 /**
272 * Reimplement to handle item unlinking.
273 * This is only relevant for virtual resources.
274 * @param item The unlinked item.
275 * @param collection The collection the item is unlinked from.
276 */
277 virtual void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection);
278
279 /**
280 * Reimplement to handle collection moves.
281 * When using this class in combination with Akonadi::ResourceBase, inter-resource
282 * moves are handled internally already and the corresponding add or delete method
283 * is called instead.
284 *
285 * @param collection The moved collection.
286 * @param collectionSource The previous parent collection.
287 * @param collectionDestination The new parent collection.
288 */
289 virtual void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource,
290 const Akonadi::Collection &collectionDestination);
291
292 /**
293 * Reimplement to handle changes to existing collections.
294 * @param collection The changed collection.
295 * @param changedAttributes The identifiers of the collection parts/attributes that has been changed.
296 */
297 virtual void collectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &changedAttributes);
298 };
299
300 /**
301 * BC extension of ObserverV2 with support for batch operations
302 *
303 * @warning When using ObserverV3, you will never get single-item notifications
304 * from AgentBase::Observer, even when you don't reimplement corresponding batch
305 * method from ObserverV3. For instance, when you don't reimplement itemsRemoved()
306 * here, you will not get any notifications about item removal whatsoever!
307 *
308 * @since 4.11
309 */
310 class AKONADI_EXPORT ObserverV3 : public ObserverV2 // krazy:exclude=dpointer
311 {
312 public:
313 /**
314 * Reimplement to handle changes in flags of existing items
315 *
316 * @warning When using ObserverV3, you will never get notifications about
317 * flag changes via Observer::itemChanged(), even when you don't reimplement
318 * itemsFlagsChanged()!
319 *
320 * @param item The changed item.
321 * @param addedFlags Flags that have been added to the item
322 * @param removedFlags Flags that have been removed from the item
323 */
324 virtual void itemsFlagsChanged(const Akonadi::Item::List &items, const QSet<QByteArray> &addedFlags, const QSet<QByteArray> &removedFlags);
325
326 /**
327 * Reimplement to handle batch notification about items deletion.
328 *
329 * @param items List of deleted items
330 */
331 virtual void itemsRemoved(const Akonadi::Item::List &items);
332
333 /**
334 * Reimplement to handle batch notification about items move
335 *
336 * @param items List of moved items
337 * @param sourceCollection Collection from where the items were moved
338 * @param destinationCollection Collection to which the items were moved
339 */
340 virtual void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &sourceCollection,
341 const Akonadi::Collection &destinationCollection);
342
343 /**
344 * Reimplement to handle batch notifications about items linking.
345 *
346 * @param items Linked items
347 * @param collection Collection to which the items have been linked
348 */
349 virtual void itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection);
350
351 /**
352 * Reimplement to handle batch notifications about items unlinking.
353 *
354 * @param items Unlinked items
355 * @param collection Collection from which the items have been unlinked
356 */
357 virtual void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection);
358 };
359
360 /**
361 * Observer that adds support for item tagging
362 *
363 * @warning ObserverV4 subclasses ObserverV3 which changes behavior of some of the
364 * virtual methods from Observer and ObserverV2. Please make sure you read
365 * documentation of ObserverV3 and adapt your agent accordingly.
366 *
367 * @since 4.13
368 */
369 class AKONADI_EXPORT ObserverV4 : public ObserverV3 // krazy:exclude=dpointer
370 {
371 public:
372 /**
373 * Reimplement to handle tags additions
374 *
375 * @param tag Newly added tag
376 */
377 virtual void tagAdded(const Akonadi::Tag &tag);
378
379 /**
380 * Reimplement to handle tags changes
381 *
382 * @param tag Tag that has been changed
383 */
384 virtual void tagChanged(const Akonadi::Tag &tag);
385
386 /**
387 * Reimplement to handle tags removal.
388 *
389 * @note All items that were tagged by @p tag will get a separate notification
390 * about untagging via itemsTagsChanged(). It is guaranteed that the itemsTagsChanged()
391 * notification will be delivered before this one.
392 *
393 * @param tag Tag that has been removed.
394 */
395 virtual void tagRemoved(const Akonadi::Tag &tag);
396
397 /**
398 * Reimplement to handle items tagging
399 *
400 * @param items Items that were tagged or untagged
401 * @param addedTags Set of tags that were added to all @p items
402 * @param removedTags Set of tags that were removed from all @p items
403 */
404 virtual void itemsTagsChanged(const Akonadi::Item::List &items, const QSet<Akonadi::Tag> &addedTags, const QSet<Akonadi::Tag> &removedTags);
405 };
406
407 /**
408 * This enum describes the different states the
409 * agent can be in.
410 */
411 enum Status {
412 Idle = 0, ///< The agent does currently nothing.
413 Running, ///< The agent is working on something.
414 Broken, ///< The agent encountered an error state.
415 NotConfigured ///< The agent is lacking required configuration
416 };
417
418 /**
419 * Use this method in the main function of your agent
420 * application to initialize your agent subclass.
421 * This method also takes care of creating a KApplication
422 * object and parsing command line arguments.
423 *
424 * @note In case the given class is also derived from AgentBase::Observer
425 * it gets registered as its own observer (see AgentBase::Observer), e.g.
426 * <tt>agentInstance->registerObserver( agentInstance );</tt>
427 *
428 * @code
429 *
430 * class MyAgent : public AgentBase
431 * {
432 * ...
433 * };
434 *
435 * AKONADI_AGENT_MAIN( MyAgent )
436 *
437 * @endcode
438 *
439 * @param argc number of arguments
440 * @param argv arguments for the function
441 */
442 template <typename T>
443 static int init(int argc, char **argv)
444 {
445 const QString id = parseArguments(argc, argv);
446 KApplication app;
447 T *r = new T(id);
448
449 // check if T also inherits AgentBase::Observer and
450 // if it does, automatically register it on itself
451 Observer *observer = dynamic_cast<Observer *>(r);
452 if (observer != 0) {
453 r->registerObserver(observer);
454 }
455 return init(r);
456 }
457
458 /**
459 * This method returns the current status code of the agent.
460 *
461 * The following return values are possible:
462 *
463 * - 0 - Idle
464 * - 1 - Running
465 * - 2 - Broken
466 * - 3 - NotConfigured
467 */
468 virtual int status() const;
469
470 /**
471 * This method returns an i18n'ed description of the current status code.
472 */
473 virtual QString statusMessage() const;
474
475 /**
476 * This method returns the current progress of the agent in percentage.
477 */
478 virtual int progress() const;
479
480 /**
481 * This method returns an i18n'ed description of the current progress.
482 */
483 virtual QString progressMessage() const;
484
485public Q_SLOTS:
486 /**
487 * This method is called whenever the agent shall show its configuration dialog
488 * to the user. It will be automatically called when the agent is started for
489 * the first time.
490 *
491 * @param windowId The parent window id.
492 *
493 * @note If the method is reimplemented it has to emit the configurationDialogAccepted()
494 * or configurationDialogRejected() signals depending on the users choice.
495 */
496 virtual void configure(WId windowId);
497
498public:
499 /**
500 * This method returns the windows id, which should be used for dialogs.
501 */
502 WId winIdForDialogs() const;
503
504#ifdef Q_OS_WIN
505 /**
506 * Overload of @ref configure needed because WId cannot be automatically casted
507 * to qlonglong on Windows.
508 */
509 void configure(qlonglong windowId);
510#endif
511
512 /**
513 * Returns the instance identifier of this agent.
514 */
515 QString identifier() const;
516
517 /**
518 * This method is called when the agent is removed from
519 * the system, so it can do some cleanup stuff.
520 *
521 * @note If you reimplement this in a subclass make sure
522 * to call this base implementation at the end.
523 */
524 virtual void cleanup();
525
526 /**
527 * Registers the given observer for reacting on monitored or recorded changes.
528 *
529 * @param observer The change handler to register. No ownership transfer, i.e.
530 * the caller stays owner of the pointer and can reset
531 * the registration by calling this method with @c 0
532 */
533 void registerObserver(Observer *observer);
534
535 /**
536 * This method is used to set the name of the agent.
537 *
538 * @since 4.3
539 * @param name name of the agent
540 */
541 //FIXME_API: make sure location is renamed to this by agentbase
542 void setAgentName(const QString &name);
543
544 /**
545 * Returns the name of the agent.
546 *
547 * @since 4.3
548 */
549 QString agentName() const;
550
551 /**
552 * Returns the component data object for this agent instance.
553 * In case of stand-alone agents this is identical to KGlobal::mainComponent().
554 * In case of in-process agents there is one component data object
555 * per agent instance thread.
556 * This method provides valid results even when called in the agent ctor.
557 * @since 4.6
558 */
559 static KComponentData componentData();
560
561Q_SIGNALS:
562 /**
563 * This signal is emitted whenever the name of the agent has changed.
564 *
565 * @param name The new name of the agent.
566 *
567 * @since 4.3
568 */
569 void agentNameChanged(const QString &name);
570
571 /**
572 * This signal should be emitted whenever the status of the agent has been changed.
573 * @param status The new Status code.
574 * @param message A i18n'ed description of the new status.
575 */
576 void status(int status, const QString &message = QString());
577
578 /**
579 * This signal should be emitted whenever the progress of an action in the agent
580 * (e.g. data transfer, connection establishment to remote server etc.) has changed.
581 *
582 * @param progress The progress of the action in percent.
583 */
584 void percent(int progress);
585
586 /**
587 * This signal shall be used to report warnings.
588 *
589 * @param message The i18n'ed warning message.
590 */
591 void warning(const QString &message);
592
593 /**
594 * This signal shall be used to report errors.
595 *
596 * @param message The i18n'ed error message.
597 */
598 void error(const QString &message);
599
600 /**
601 * This signal should be emitted whenever the status of the agent has been changed.
602 * @param status The object that describes the status change.
603 *
604 * @since 4.6
605 */
606 void advancedStatus(const QVariantMap &status);
607
608 /**
609 * Emitted when another application has remotely asked the agent to abort
610 * its current operation.
611 * Connect to this signal if your agent supports abortion. After aborting
612 * and cleaning up, agents should return to Idle status.
613 *
614 * @since 4.4
615 */
616 void abortRequested();
617
618 /**
619 * Emitted if another application has changed the agent's configuration remotely
620 * and called AgentInstance::reconfigure().
621 *
622 * @since 4.2
623 */
624 void reloadConfiguration();
625
626 /**
627 * Emitted when the online state changed.
628 * @param online The online state.
629 * @since 4.2
630 */
631 void onlineChanged(bool online);
632
633 /**
634 * This signal is emitted whenever the user has accepted the configuration dialog.
635 *
636 * @note Implementors of agents/resources are responsible to emit this signal if
637 * the agent/resource reimplements configure().
638 *
639 * @since 4.4
640 */
641 void configurationDialogAccepted();
642
643 /**
644 * This signal is emitted whenever the user has rejected the configuration dialog.
645 *
646 * @note Implementors of agents/resources are responsible to emit this signal if
647 * the agent/resource reimplements configure().
648 *
649 * @since 4.4
650 */
651 void configurationDialogRejected();
652
653protected:
654 /**
655 * Creates an agent base.
656 *
657 * @param id The instance id of the agent.
658 */
659 AgentBase(const QString &id);
660
661 /**
662 * Destroys the agent base.
663 */
664 ~AgentBase();
665
666 /**
667 * This method is called whenever the agent application is about to
668 * quit.
669 *
670 * Reimplement this method to do session cleanup (e.g. disconnecting
671 * from groupware server).
672 */
673 virtual void aboutToQuit();
674
675 /**
676 * Returns the Akonadi::ChangeRecorder object used for monitoring.
677 * Use this to configure which parts you want to monitor.
678 */
679 ChangeRecorder *changeRecorder() const;
680
681 /**
682 * Returns the config object for this Agent.
683 */
684 KSharedConfigPtr config();
685
686 /**
687 * Marks the current change as processes and replays the next change if change
688 * recording is enabled (noop otherwise). This method is called
689 * from the default implementation of the change notification slots. While not
690 * required when not using change recording, it is nevertheless recommended
691 * to call this method when done with processing a change notification.
692 */
693 void changeProcessed();
694
695 /**
696 * Returns whether the agent is currently online.
697 */
698 bool isOnline() const;
699
700 /**
701 * Sets whether the agent needs network or not.
702 *
703 * @since 4.2
704 * @todo use this in combination with Solid::Networking::Notifier to change
705 * the onLine status of the agent.
706 * @param needsNetwork @c true if the agents needs network. Defaults to @c false
707 */
708 void setNeedsNetwork(bool needsNetwork);
709
710 /**
711 * Sets whether the agent shall be online or not.
712 */
713 void setOnline(bool state);
714
715protected:
716 /**
717 * Sets the agent offline but will make it online again after a given time
718 *
719 * Use this method when the agent detects some problem with its backend but it wants
720 * to retry all pending operations after some time - e.g. a server can not be reached currently
721 *
722 * Example usage:
723 * @code
724 * void ExampleResource::onItemRemovedFinished(KJob *job)
725 * {
726 * if (job->error()) {
727 * emit status(Broken, job->errorString());
728 * deferTask();
729 * setTemporaryOffline(300);
730 * return;
731 * }
732 * ...
733 * }
734 * @endcode
735 *
736 * @since 4.13
737 * @param makeOnlineInSeconds timeout in seconds after which the agent changes to online
738 */
739 void setTemporaryOffline(int makeOnlineInSeconds = 300);
740
741 //@cond PRIVATE
742 AgentBasePrivate *d_ptr;
743 explicit AgentBase(AgentBasePrivate *d, const QString &id);
744 friend class ObserverV2;
745 //@endcond
746
747 /**
748 * This method is called whenever the @p online status has changed.
749 * Reimplement this method to react on online status changes.
750 * @param online online status
751 */
752 virtual void doSetOnline(bool online);
753
754private:
755 //@cond PRIVATE
756 static QString parseArguments(int, char **);
757 static int init(AgentBase *r);
758 void setOnlineInternal(bool state);
759
760 // D-Bus interface stuff
761 void abort();
762 void reconfigure();
763 void quit();
764
765 // dbus agent interface
766 friend class ::Akonadi__StatusAdaptor;
767 friend class ::Akonadi__ControlAdaptor;
768
769 Q_DECLARE_PRIVATE(AgentBase)
770 Q_PRIVATE_SLOT(d_func(), void delayedInit())
771 Q_PRIVATE_SLOT(d_func(), void slotStatus(int, const QString &))
772 Q_PRIVATE_SLOT(d_func(), void slotPercent(int))
773 Q_PRIVATE_SLOT(d_func(), void slotWarning(const QString &))
774 Q_PRIVATE_SLOT(d_func(), void slotError(const QString &))
775 Q_PRIVATE_SLOT(d_func(), void slotNetworkStatusChange(Solid::Networking::Status))
776 Q_PRIVATE_SLOT(d_func(), void slotResumedFromSuspend())
777 Q_PRIVATE_SLOT(d_func(), void slotTemporaryOfflineTimeout())
778
779 //@endcond
780};
781
782}
783
784#ifndef AKONADI_AGENT_MAIN
785/**
786 * Convenience Macro for the most common main() function for Akonadi agents.
787 */
788#define AKONADI_AGENT_MAIN( agentClass ) \
789 int main( int argc, char **argv ) \
790 { \
791 return Akonadi::AgentBase::init<agentClass>( argc, argv ); \
792 }
793#endif
794
795#endif
796