From 14fa9251ef4820a6b0cea2137305a8f0395125eb Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Tue, 14 Apr 2015 22:08:21 +0000 Subject: [PATCH] import working copy of kdepim-runtime --- CMakeLists.txt | 1 + kdepim-runtime/CMakeLists.txt | 212 + kdepim-runtime/COPYING | 346 + kdepim-runtime/COPYING.LIB | 510 + kdepim-runtime/CTestConfig.cmake | 13 + kdepim-runtime/CTestCustom.cmake | 31 + kdepim-runtime/Mainpage.dox | 105 + kdepim-runtime/README | 3 + kdepim-runtime/accountwizard/CMakeLists.txt | 71 + kdepim-runtime/accountwizard/HOWTO | 2 + kdepim-runtime/accountwizard/Messages.sh | 3 + kdepim-runtime/accountwizard/TODO | 5 + .../accountwizard/accountwizard-mime.xml | 7 + .../accountwizard/accountwizard.desktop | 97 + .../accountwizard/accountwizard.knsrc | 5 + kdepim-runtime/accountwizard/configfile.cpp | 91 + kdepim-runtime/accountwizard/configfile.h | 53 + kdepim-runtime/accountwizard/dialog.cpp | 206 + kdepim-runtime/accountwizard/dialog.h | 69 + kdepim-runtime/accountwizard/dynamicpage.cpp | 65 + kdepim-runtime/accountwizard/dynamicpage.h | 39 + kdepim-runtime/accountwizard/global.cpp | 115 + kdepim-runtime/accountwizard/global.h | 39 + kdepim-runtime/accountwizard/identity.cpp | 152 + kdepim-runtime/accountwizard/identity.h | 68 + .../accountwizard/inprocess-main.cpp | 46 + .../accountwizard/ispdb/CMakeLists.txt | 15 + kdepim-runtime/accountwizard/ispdb/ispdb.cpp | 277 + kdepim-runtime/accountwizard/ispdb/ispdb.h | 133 + kdepim-runtime/accountwizard/ispdb/main.cpp | 61 + kdepim-runtime/accountwizard/ldap.cpp | 117 + kdepim-runtime/accountwizard/ldap.h | 48 + kdepim-runtime/accountwizard/loadpage.cpp | 88 + kdepim-runtime/accountwizard/loadpage.h | 51 + kdepim-runtime/accountwizard/main.cpp | 81 + kdepim-runtime/accountwizard/page.cpp | 60 + kdepim-runtime/accountwizard/page.h | 64 + .../accountwizard/personaldatapage.cpp | 278 + .../accountwizard/personaldatapage.h | 64 + kdepim-runtime/accountwizard/providerpage.cpp | 110 + kdepim-runtime/accountwizard/providerpage.h | 60 + kdepim-runtime/accountwizard/resource.cpp | 165 + kdepim-runtime/accountwizard/resource.h | 51 + kdepim-runtime/accountwizard/servertest.cpp | 64 + kdepim-runtime/accountwizard/servertest.h | 54 + kdepim-runtime/accountwizard/setupmanager.cpp | 245 + kdepim-runtime/accountwizard/setupmanager.h | 86 + kdepim-runtime/accountwizard/setupobject.cpp | 35 + kdepim-runtime/accountwizard/setupobject.h | 46 + kdepim-runtime/accountwizard/setuppage.cpp | 71 + kdepim-runtime/accountwizard/setuppage.h | 52 + kdepim-runtime/accountwizard/transport.cpp | 156 + kdepim-runtime/accountwizard/transport.h | 62 + kdepim-runtime/accountwizard/typepage.cpp | 117 + kdepim-runtime/accountwizard/typepage.h | 47 + kdepim-runtime/accountwizard/ui/loadpage.ui | 54 + .../accountwizard/ui/personaldatapage.ui | 283 + .../accountwizard/ui/providerpage.ui | 63 + kdepim-runtime/accountwizard/ui/setuppage.ui | 103 + kdepim-runtime/accountwizard/ui/typepage.ui | 84 + .../accountwizard/wizards/CMakeLists.txt | 1 + .../wizards/tine20/CMakeLists.txt | 2 + .../accountwizard/wizards/tine20/Messages.sh | 4 + .../wizards/tine20/tine20wizard.desktop | 93 + .../wizards/tine20/tine20wizard.es | 177 + .../wizards/tine20/tine20wizard.ui | 87 + kdepim-runtime/agents/.krazy | 1 + kdepim-runtime/agents/CMakeLists.txt | 20 + kdepim-runtime/agents/Info.plist.template | 36 + kdepim-runtime/agents/Mainpage.dox | 2 + .../agents/akonadinepomukfeederagent.desktop | 53 + .../agents/invitations/CMakeLists.txt | 35 + kdepim-runtime/agents/invitations/Messages.sh | 3 + .../agents/invitations/incidenceattribute.cpp | 92 + .../agents/invitations/incidenceattribute.h | 63 + .../agents/invitations/invitationsagent.cpp | 560 + .../invitations/invitationsagent.desktop | 97 + .../agents/invitations/invitationsagent.h | 101 + .../agents/maildispatcher/CMakeLists.txt | 41 + .../agents/maildispatcher/Messages.sh | 4 + kdepim-runtime/agents/maildispatcher/TODO | 16 + .../akonadi_maildispatcher_agent.notifyrc | 194 + .../agents/maildispatcher/configdialog.cpp | 80 + .../agents/maildispatcher/configdialog.h | 44 + .../maildispatcher/maildispatcheragent.cpp | 373 + .../maildispatcheragent.desktop | 93 + .../maildispatcher/maildispatcheragent.h | 75 + .../maildispatcher/maildispatcheragent.kcfg | 18 + ...reedesktop.Akonadi.MailDispatcherAgent.xml | 7 + .../agents/maildispatcher/outboxqueue.cpp | 459 + .../agents/maildispatcher/outboxqueue.h | 96 + .../agents/maildispatcher/sendjob.cpp | 484 + .../agents/maildispatcher/sendjob.h | 91 + .../maildispatcher/sentactionhandler.cpp | 70 + .../agents/maildispatcher/sentactionhandler.h | 44 + .../agents/maildispatcher/settings.kcfgc | 8 + .../agents/maildispatcher/settings.ui | 77 + .../agents/maildispatcher/storeresultjob.cpp | 140 + .../agents/maildispatcher/storeresultjob.h | 74 + .../maildispatcher/tests/CMakeLists.txt | 43 + .../agents/maildispatcher/tests/TODO | 6 + .../agents/maildispatcher/tests/aborttest.cpp | 237 + .../agents/maildispatcher/tests/aborttest.h | 54 + .../agents/maildispatcher/tests/dupetest.cpp | 221 + .../agents/maildispatcher/tests/dupetest.h | 51 + .../tests/unittestenv/config.xml | 7 + .../kdehome/share/config/akonadi-firstrunrc | 3 + .../share/config/akonadi_knut_resource_0rc | 4 + .../akonadi_mailtransport_dummy_resource_0rc | 2 + .../unittestenv/kdehome/share/config/kdebugrc | 110 + .../unittestenv/kdehome/share/config/kdedrc | 3 + .../kdehome/share/config/kwalletrc | 2 + .../kdehome/share/config/mailtransports | 23 + .../unittestenv/kdehome/share/config/qttestrc | 2 + .../tests/unittestenv/kdehome/testdata.xml | 4 + .../xdgconfig/akonadi/akonadiserverrc | 4 + .../tests/unittestenv/xdglocal/.keep | 1 + .../agents/migration/CMakeLists.txt | 40 + kdepim-runtime/agents/migration/Messages.sh | 2 + .../agents/migration/migrationagent.cpp | 63 + .../agents/migration/migrationagent.desktop | 44 + .../agents/migration/migrationagent.h | 42 + .../agents/migration/migrationexecutor.cpp | 108 + .../agents/migration/migrationexecutor.h | 61 + .../agents/migration/migrationscheduler.cpp | 299 + .../agents/migration/migrationscheduler.h | 133 + .../migration/migrationstatuswidget.cpp | 125 + .../agents/migration/migrationstatuswidget.h | 46 + .../agents/migration/tests/CMakeLists.txt | 16 + .../agents/migration/tests/dummymigrator.cpp | 68 + .../agents/migration/tests/dummymigrator.h | 49 + .../agents/migration/tests/schedulertest.cpp | 293 + .../agents/newmailnotifier/CMakeLists.txt | 59 + .../agents/newmailnotifier/Messages.sh | 4 + kdepim-runtime/agents/newmailnotifier/NEWS | 3 + kdepim-runtime/agents/newmailnotifier/TODO | 4 + .../akonadi_newmailnotifier_agent.notifyrc | 174 + .../kconf_update/CMakeLists.txt | 1 + .../kconf_update/newmailnotifier.upd | 6 + .../newmailnotifier/newmailnotifieragent.cpp | 563 + .../newmailnotifieragent.desktop | 95 + .../newmailnotifier/newmailnotifieragent.h | 113 + .../newmailnotifieragentsettings.kcfg | 42 + .../newmailnotifieragentsettings.kcfgc | 6 + .../newmailnotifierattribute.cpp | 80 + .../newmailnotifierattribute.h | 45 + .../newmailnotifierselectcollectionwidget.cpp | 222 + .../newmailnotifierselectcollectionwidget.h | 67 + .../newmailnotifiersettingsdialog.cpp | 225 + .../newmailnotifiersettingsdialog.h | 58 + .../newmailnotifiershowmessagejob.cpp | 57 + .../newmailnotifiershowmessagejob.h | 39 + ...rg.freedesktop.Akonadi.NewMailNotifier.xml | 77 + .../newmailnotifier/specialnotifierjob.cpp | 179 + .../newmailnotifier/specialnotifierjob.h | 50 + .../agents/newmailnotifier/util.cpp | 80 + kdepim-runtime/agents/newmailnotifier/util.h | 35 + kdepim-runtime/akonadi-prefix.h.cmake | 6 + kdepim-runtime/akonadi-version.h.cmake | 25 + .../cmake/modules/FindXsltproc.cmake | 31 + kdepim-runtime/config-enterprise.h.cmake | 2 + kdepim-runtime/defaultsetup/CMakeLists.txt | 8 + .../defaultsetup/defaultaddressbook.desktop | 55 + .../defaultsetup/defaultcalendar.desktop | 50 + .../defaultsetup/defaultnotebook.desktop | 79 + kdepim-runtime/doc/git-migration.txt | 7 + kdepim-runtime/doc/libakonadi.xmi | 9639 +++++++++++++++++ .../doc/pics/akonadi_agent_handling.eps | 1142 ++ .../doc/pics/akonadi_agent_handling.png | Bin 0 -> 9008 bytes .../doc/pics/akonadi_agent_handling_small.png | Bin 0 -> 3611 bytes .../doc/pics/akonadi_client_search.eps | 1282 +++ .../doc/pics/akonadi_client_search.png | Bin 0 -> 10244 bytes .../doc/pics/akonadi_client_search_small.png | Bin 0 -> 3726 bytes .../doc/pics/akonadi_communication.xmi | 185 + .../doc/pics/akonadi_concept_schema.sla | 721 ++ .../doc/pics/akonadi_overview_uml.png | Bin 0 -> 14070 bytes .../doc/pics/akonadi_overview_uml.ps | 2551 +++++ .../doc/pics/akonadi_overview_uml_small.png | Bin 0 -> 57683 bytes kdepim-runtime/doc/pics/concept.eps | 2351 ++++ kdepim-runtime/doc/pics/concept.png | Bin 0 -> 18310 bytes kdepim-runtime/doc/pics/concept.sla | 783 ++ kdepim-runtime/doc/pics/convert.sh | 5 + kdepim-runtime/doc/todo.dox | 81 + kdepim-runtime/kcm/CMakeLists.txt | 35 + kdepim-runtime/kcm/Mainpage.dox | 18 + kdepim-runtime/kcm/Messages.sh | 4 + kdepim-runtime/kcm/akonadiconfigmodule.cpp | 36 + kdepim-runtime/kcm/akonadiconfigmodule.h | 31 + kdepim-runtime/kcm/configmodule.cpp | 49 + kdepim-runtime/kcm/configmodule.h | 36 + kdepim-runtime/kcm/kcm_akonadi.desktop | 110 + .../kcm/kcm_akonadi_resources.desktop | 109 + kdepim-runtime/kcm/kcm_akonadi_server.desktop | 63 + .../kcm/resourcesmanagementwidget.cpp | 138 + .../kcm/resourcesmanagementwidget.h | 67 + .../kcm/resourcesmanagementwidget.ui | 102 + kdepim-runtime/kcm/serverconfigmodule.cpp | 241 + kdepim-runtime/kcm/serverconfigmodule.h | 64 + kdepim-runtime/kcm/serverconfigmodule.ui | 94 + kdepim-runtime/kcm/servermysqlstorage.ui | 211 + kdepim-runtime/kcm/serverpsqlstorage.ui | 182 + kdepim-runtime/kcm/serverstoragedriver.ui | 55 + kdepim-runtime/kdepim-mime.xml | 44 + kdepim-runtime/kdepim-runtime-version.h.cmake | 36 + kdepim-runtime/kioslave/CMakeLists.txt | 21 + kdepim-runtime/kioslave/Messages.sh | 2 + kdepim-runtime/kioslave/akonadi.protocol | 10 + kdepim-runtime/kioslave/akonadislave.cpp | 233 + kdepim-runtime/kioslave/akonadislave.h | 61 + kdepim-runtime/kresources/CMakeLists.txt | 36 + kdepim-runtime/kresources/kabc/CMakeLists.txt | 21 + kdepim-runtime/kresources/kabc/Messages.sh | 2 + .../kresources/kabc/akonadi.desktop | 99 + .../kresources/kabc/resourceakonadi.cpp | 257 + .../kresources/kabc/resourceakonadi.h | 92 + .../kresources/kabc/resourceakonadi_p.cpp | 533 + .../kresources/kabc/resourceakonadi_p.h | 107 + .../kresources/kabc/resourceakonadiconfig.cpp | 76 + .../kresources/kabc/resourceakonadiconfig.h | 38 + .../kresources/kabc/resourceakonadiplugin.cpp | 40 + .../kresources/kabc/subresource.cpp | 184 + kdepim-runtime/kresources/kabc/subresource.h | 91 + kdepim-runtime/kresources/kcal/CMakeLists.txt | 20 + kdepim-runtime/kresources/kcal/Messages.sh | 2 + .../kresources/kcal/akonadi.desktop | 105 + .../kresources/kcal/resourceakonadi.cpp | 452 + .../kresources/kcal/resourceakonadi.h | 172 + .../kresources/kcal/resourceakonadi_p.cpp | 429 + .../kresources/kcal/resourceakonadi_p.h | 115 + .../kresources/kcal/resourceakonadiconfig.cpp | 83 + .../kresources/kcal/resourceakonadiconfig.h | 39 + .../kresources/kcal/resourceakonadiplugin.cpp | 39 + .../kresources/kcal/subresource.cpp | 172 + kdepim-runtime/kresources/kcal/subresource.h | 73 + kdepim-runtime/kresources/shared/Messages.sh | 2 + .../shared/abstractsubresourcemodel.cpp | 316 + .../shared/abstractsubresourcemodel.h | 123 + .../kresources/shared/concurrentjobs.cpp | 166 + .../kresources/shared/concurrentjobs.h | 195 + .../kresources/shared/idarbiterbase.cpp | 88 + .../kresources/shared/idarbiterbase.h | 55 + .../kresources/shared/itemfetchadapter.cpp | 62 + .../kresources/shared/itemfetchadapter.h | 55 + .../kresources/shared/itemsavecontext.h | 47 + .../kresources/shared/itemsavejob.cpp | 60 + .../kresources/shared/itemsavejob.h | 36 + .../kresources/shared/resourceconfigbase.cpp | 238 + .../kresources/shared/resourceconfigbase.h | 97 + .../kresources/shared/resourceprivatebase.cpp | 508 + .../kresources/shared/resourceprivatebase.h | 193 + .../kresources/shared/sharedresourceiface.h | 36 + .../kresources/shared/sharedresourceprivate.h | 144 + .../shared/storecollectiondialog.cpp | 169 + .../kresources/shared/storecollectiondialog.h | 70 + .../storecollectionfilterproxymodel.cpp | 82 + .../shared/storecollectionfilterproxymodel.h | 52 + .../shared/storecollectionmodel.cpp | 91 + .../kresources/shared/storecollectionmodel.h | 63 + .../kresources/shared/storeconfigiface.h | 46 + .../kresources/shared/subresourcebase.cpp | 226 + .../kresources/shared/subresourcebase.h | 106 + .../kresources/shared/subresourcemodel.h | 242 + kdepim-runtime/libkdepim-copy/CMakeLists.txt | 27 + kdepim-runtime/libkdepim-copy/Messages.sh | 2 + .../libkdepim-copy/calendardiffalgo.cpp | 214 + .../libkdepim-copy/calendardiffalgo.h | 58 + kdepim-runtime/libkdepim-copy/diffalgo.cpp | 87 + kdepim-runtime/libkdepim-copy/diffalgo.h | 138 + .../libkdepim-copy/htmldiffalgodisplay.cpp | 97 + .../libkdepim-copy/htmldiffalgodisplay.h | 54 + .../libkdepim-copy/kincidencechooser.cpp | 468 + .../libkdepim-copy/kincidencechooser.h | 92 + .../libkdepim-copy/libkdepim-copy_export.h | 43 + .../libkdepim-copy/tests/CMakeLists.txt | 6 + .../tests/testkincidencechooser.cpp | 45 + kdepim-runtime/migration/CMakeLists.txt | 40 + .../migration/entitytreecreatejob.cpp | 113 + .../migration/entitytreecreatejob.h | 50 + kdepim-runtime/migration/gid/CMakeLists.txt | 25 + kdepim-runtime/migration/gid/Messages.sh | 2 + .../migration/gid/gidmigrationjob.cpp | 143 + .../migration/gid/gidmigrationjob.h | 88 + kdepim-runtime/migration/gid/gidmigrator.cpp | 71 + kdepim-runtime/migration/gid/gidmigrator.h | 48 + kdepim-runtime/migration/gid/main.cpp | 94 + kdepim-runtime/migration/infodialog.cpp | 186 + kdepim-runtime/migration/infodialog.h | 68 + .../migration/kaddressbook/CMakeLists.txt | 8 + .../migration/kaddressbook/Messages.sh | 2 + .../kaddressbook/kaddressbookmigrator.cpp | 117 + .../kaddressbook/kaddressbookmigrator.desktop | 94 + kdepim-runtime/migration/kjots/CMakeLists.txt | 36 + kdepim-runtime/migration/kjots/Messages.sh | 2 + .../migration/kjots/kjotsmigrator.cpp | 358 + .../migration/kjots/kjotsmigrator.h | 76 + kdepim-runtime/migration/kjots/main.cpp | 82 + kdepim-runtime/migration/kjots/metatype.h | 29 + kdepim-runtime/migration/kmail/CMakeLists.txt | 60 + kdepim-runtime/migration/kmail/Messages.sh | 2 + .../kmail/abstractcollectionmigrator.cpp | 841 ++ .../kmail/abstractcollectionmigrator.h | 111 + .../migration/kmail/emptyresourcecleaner.cpp | 229 + .../migration/kmail/emptyresourcecleaner.h | 75 + .../migration/kmail/imapcacheadapter.cpp | 168 + .../migration/kmail/imapcacheadapter.h | 54 + .../kmail/imapcachecollectionmigrator.cpp | 646 ++ .../kmail/imapcachecollectionmigrator.h | 80 + .../kmail/imapcachelocalimporter.cpp | 348 + .../migration/kmail/imapcachelocalimporter.h | 72 + .../migration/kmail/kmail-migratorrc | 11 + .../migration/kmail/kmailmigrator.cpp | 1354 +++ .../migration/kmail/kmailmigrator.h | 161 + .../kmail/localfolderscollectionmigrator.cpp | 123 + .../kmail/localfolderscollectionmigrator.h | 48 + kdepim-runtime/migration/kmail/main.cpp | 104 + .../migration/kmail/messagetag.trig | 102 + kdepim-runtime/migration/kmail/metatype.h | 29 + .../migration/kmail/mixedtreeconverter.cpp | 120 + .../migration/kmail/mixedtreeconverter.h | 47 + .../migration/kmail/subscriptionjob_p.h | 75 + kdepim-runtime/migration/kmigratorbase.cpp | 138 + kdepim-runtime/migration/kmigratorbase.h | 92 + .../migration/knotes/CMakeLists.txt | 37 + kdepim-runtime/migration/knotes/Messages.sh | 2 + .../migration/knotes/knoteconfig.kcfg | 89 + .../migration/knotes/knoteconfig.kcfgc | 3 + .../migration/knotes/knotesmigrator.cpp | 273 + .../migration/knotes/knotesmigrator.h | 73 + .../migration/knotes/knotesmigratorconfig.cpp | 53 + .../migration/knotes/knotesmigratorconfig.h | 38 + kdepim-runtime/migration/knotes/main.cpp | 78 + .../migration/knotes/notealarmattribute.cpp | 69 + .../migration/knotes/notealarmattribute.h | 46 + .../migration/knotes/notedisplayattribute.cpp | 255 + .../migration/knotes/notedisplayattribute.h | 94 + .../migration/knotes/notelockattribute.cpp | 58 + .../migration/knotes/notelockattribute.h | 42 + .../knotes/showfoldernotesattribute.cpp | 49 + .../knotes/showfoldernotesattribute.h | 36 + kdepim-runtime/migration/kres/CMakeLists.txt | 37 + kdepim-runtime/migration/kres/Messages.sh | 2 + .../migration/kres/kabcmigrator.cpp | 91 + kdepim-runtime/migration/kres/kabcmigrator.h | 49 + .../migration/kres/kcalmigrator.cpp | 169 + kdepim-runtime/migration/kres/kcalmigrator.h | 48 + kdepim-runtime/migration/kres/kres-migratorrc | 4 + kdepim-runtime/migration/kres/kresmigrator.h | 322 + .../migration/kres/kresmigratorbase.cpp | 177 + .../migration/kres/kresmigratorbase.h | 73 + kdepim-runtime/migration/kres/main.cpp | 113 + kdepim-runtime/migration/kres/metatype.h | 29 + kdepim-runtime/migration/migratorbase.cpp | 288 + kdepim-runtime/migration/migratorbase.h | 233 + kdepim-runtime/migration/tests/CMakeLists.txt | 43 + .../migration/tests/testgidmigration.cpp | 115 + .../migration/tests/testgidmigration.h | 68 + .../migration/tests/testmigratorbase.cpp | 143 + .../migration/tests/testnotesmigration.cpp | 148 + .../migration/tests/testnotesmigration.h | 70 + .../tests/unittestenv/config-sqlite-db.xml | 8 + .../kdehome/share/apps/kjots/N23552.book | 32 + .../kdehome/share/apps/kjots/f23552.book | 57 + .../kdehome/share/apps/kjots/k21863.book | 30 + .../kdehome/share/apps/kjots/kde3_TDHwQa.book | 28 + .../kdehome/share/apps/kjots/kde3_hu4mua.book | 13 + .../kdehome/share/apps/knotes/notes.ics | 31 + .../apps/knotes/notes/libkcal-2098450417.977 | 10 + .../apps/knotes/notes/libkcal-297296717.52 | 10 + .../kdehome/share/config/akonadi-firstrunrc | 4 + .../share/config/akonadi_knut_resource_0rc | 4 + .../unittestenv/kdehome/share/config/kdebugrc | 80 + .../unittestenv/kdehome/share/config/kdedrc | 3 + .../kdehome/share/config/kres-migratorrc | 2 + .../share/config/kresources/notes/stdrc | 15 + .../kdehome/share/config/kwalletrc | 2 + .../unittestenv/kdehome/share/config/qttestrc | 2 + .../unittestenv/kdehome/testdata-res1.xml | 76 + .../xdgconfig/akonadi/akonadiserverrc | 5 + kdepim-runtime/opensync/CMakeLists.txt | 29 + kdepim-runtime/opensync/akonadi-sync | 14 + kdepim-runtime/opensync/akonadi_opensync.cpp | 201 + kdepim-runtime/opensync/akonadisink.cpp | 58 + kdepim-runtime/opensync/akonadisink.h | 41 + kdepim-runtime/opensync/datasink.cpp | 433 + kdepim-runtime/opensync/datasink.h | 103 + kdepim-runtime/opensync/sinkbase.cpp | 158 + kdepim-runtime/opensync/sinkbase.h | 80 + kdepim-runtime/plugins/CMakeLists.txt | 74 + kdepim-runtime/plugins/Messages.sh | 2 + .../plugins/akonadi_serializer_addressee.cpp | 267 + .../akonadi_serializer_addressee.desktop | 101 + .../plugins/akonadi_serializer_addressee.h | 58 + .../plugins/akonadi_serializer_bookmark.cpp | 76 + .../akonadi_serializer_bookmark.desktop | 99 + .../plugins/akonadi_serializer_bookmark.h | 46 + .../akonadi_serializer_contactgroup.cpp | 134 + .../akonadi_serializer_contactgroup.desktop | 99 + .../plugins/akonadi_serializer_contactgroup.h | 58 + .../plugins/akonadi_serializer_kalarm.cpp | 275 + .../plugins/akonadi_serializer_kalarm.desktop | 94 + .../plugins/akonadi_serializer_kalarm.h | 66 + .../plugins/akonadi_serializer_kcal.cpp | 302 + .../plugins/akonadi_serializer_kcal.desktop | 100 + .../plugins/akonadi_serializer_kcal.h | 53 + .../plugins/akonadi_serializer_kcalcore.cpp | 361 + .../akonadi_serializer_kcalcore.desktop | 100 + .../plugins/akonadi_serializer_kcalcore.h | 59 + .../plugins/akonadi_serializer_mail.cpp | 236 + .../plugins/akonadi_serializer_mail.desktop | 101 + .../plugins/akonadi_serializer_mail.h | 69 + .../plugins/akonadi_serializer_microblog.cpp | 64 + .../akonadi_serializer_microblog.desktop | 99 + .../plugins/akonadi_serializer_microblog.h | 42 + kdepim-runtime/plugins/kaeventformatter.cpp | 344 + kdepim-runtime/plugins/kaeventformatter.h | 112 + kdepim-runtime/plugins/tests/CMakeLists.txt | 31 + .../plugins/tests/addresseeserializertest.cpp | 47 + .../plugins/tests/kcalcoreserializertest.cpp | 133 + .../plugins/tests/kcalserializertest.cpp | 135 + .../tests/mailserializerplugintest.cpp | 95 + .../plugins/tests/mailserializerplugintest.h | 34 + .../plugins/tests/mailserializertest.cpp | 264 + .../plugins/tests/mailserializertest.h | 38 + kdepim-runtime/qml/CMakeLists.txt | 5 + kdepim-runtime/qml/Messages.sh | 8 + .../AkonadiBreadcrumbNavigationView.qml | 146 + kdepim-runtime/qml/akonadi/CMakeLists.txt | 16 + .../qml/akonadi/CollectionDelegate.qml | 217 + kdepim-runtime/qml/akonadi/border_dot.png | Bin 0 -> 251 bytes kdepim-runtime/qml/akonadi/check.png | Bin 0 -> 3922 bytes kdepim-runtime/qml/akonadi/collectionview.qml | 100 + kdepim-runtime/qml/akonadi/qmldir | 3 + .../qml/akonadi/sliderbackground.png | Bin 0 -> 26391 bytes .../qml/akonadi/tests/CMakeLists.txt | 11 + .../qml/akonadi/tests/collectionviewtest.qml | 39 + kdepim-runtime/qml/akonadi/tests/qmltest.cpp | 111 + .../qml/akonadi/transparentplus.png | Bin 0 -> 852 bytes .../qml/kde/BreadcrumbNavigationView.qml | 548 + kdepim-runtime/qml/kde/CMakeLists.txt | 45 + kdepim-runtime/qml/kde/Dialog.qml | 74 + kdepim-runtime/qml/kde/Flap.qml | 161 + kdepim-runtime/qml/kde/Flap2.qml | 110 + kdepim-runtime/qml/kde/SlideoutPanel.qml | 137 + .../qml/kde/SlideoutPanelContainer.qml | 70 + .../qml/kde/dividing-line-horizontal.png | Bin 0 -> 139 bytes kdepim-runtime/qml/kde/dividing-line.png | Bin 0 -> 181 bytes .../qml/kde/flap-collapsed-bottom.png | Bin 0 -> 1626 bytes kdepim-runtime/qml/kde/flap-collapsed-mid.png | Bin 0 -> 1012 bytes kdepim-runtime/qml/kde/flap-collapsed-top.png | Bin 0 -> 6071 bytes .../qml/kde/flap-expanded-bottom.png | Bin 0 -> 2170 bytes kdepim-runtime/qml/kde/flap-expanded-mid.png | Bin 0 -> 1071 bytes kdepim-runtime/qml/kde/flap-expanded-top.png | Bin 0 -> 37640 bytes kdepim-runtime/qml/kde/kdeintegration.cpp | 306 + kdepim-runtime/qml/kde/kdeintegration.h | 58 + .../qml/kde/kdeintegrationplugin.cpp | 47 + kdepim-runtime/qml/kde/kdeintegrationplugin.h | 35 + kdepim-runtime/qml/kde/list-line-top.png | Bin 0 -> 297 bytes kdepim-runtime/qml/kde/qmldir | 8 + .../qml/kde/qmldir_without_kdeqmlplugin | 7 + kdepim-runtime/qml/kde/scrollable-bottom.png | Bin 0 -> 308 bytes kdepim-runtime/qml/kde/scrollable-top.png | Bin 0 -> 317 bytes kdepim-runtime/qml/kde/tests/MyItem.qml | 41 + .../qml/kde/tests/SliderComponent.qml | 111 + .../qml/kde/tests/SomeComponent.qml | 47 + kdepim-runtime/qml/kde/tests/actionstest.qml | 103 + kdepim-runtime/qml/kde/tests/behaviouronx.qml | 36 + .../kde/tests/bindinggroupedproperties.qml | 30 + .../qml/kde/tests/collapsibilesections.qml | 113 + kdepim-runtime/qml/kde/tests/disconnect.qml | 51 + kdepim-runtime/qml/kde/tests/dragitems-0.qml | 77 + kdepim-runtime/qml/kde/tests/elementmodel.qml | 92 + kdepim-runtime/qml/kde/tests/i18n.qml | 51 + kdepim-runtime/qml/kde/tests/icons.qml | 40 + .../qml/kde/tests/kstandarddirs.qml | 33 + kdepim-runtime/qml/kde/tests/mm2px.qml | 35 + .../qml/kde/tests/propagateevent.qml | 125 + .../qml/kde/tests/qml_moves/CMakeLists.txt | 42 + .../kde/tests/qml_moves/dynamictreemodel.cpp | 1009 ++ .../kde/tests/qml_moves/dynamictreemodel.h | 358 + .../qml/kde/tests/qml_moves/indexfinder.h | 84 + .../qml/kde/tests/qml_moves/main.cpp | 18 + .../qml/kde/tests/qml_moves/mainview.qml | 45 + .../qml/kde/tests/qml_moves/mainwindow.cpp | 82 + .../qml/kde/tests/qml_moves/mainwindow.h | 34 + .../qml/kde/tests/qml_moves/qml_moves.pro | 14 + .../001-change-opacity | 13 + .../002-change-opacity-with-braces | 13 + .../003-change-opacity-properly | 13 + .../004-add-second-breadcrumbitem | 26 + ...005-try-to-make-bottom-item-always-visible | 42 + .../006-using-apply-range-helps-somewhat | 48 + .../BreadcrumbNavigationView.qml | 553 + .../qmlbreadcrumbnavigation/CMakeLists.txt | 61 + .../qmlbreadcrumbnavigation/ListDelegate.qml | 125 + .../backgroundtile.png | Bin 0 -> 2139 bytes .../qmlbreadcrumbnavigation/border_dot.png | Bin 0 -> 251 bytes .../breadcrumbnavigation.cpp | 185 + .../breadcrumbnavigation.h | 100 + .../breadcrumbnavigationcontext.cpp | 397 + .../breadcrumbnavigationcontext.h | 89 + .../checkableitemproxymodel.cpp | 122 + .../checkableitemproxymodel.h | 60 + .../cmake/FindQt4.cmake | 1256 +++ .../cmake/MacroPushRequiredVars.cmake | 47 + .../cmake/Qt4ConfigDependentSettings.cmake | 384 + .../cmake/Qt4Macros.cmake | 414 + .../currentindicator.png | Bin 0 -> 902 bytes .../dividing-line-horizontal.png | Bin 0 -> 127 bytes .../qmlbreadcrumbnavigation/dividing-line.png | Bin 0 -> 168 bytes .../dynamictreemodel.cpp | 966 ++ .../dynamictreemodel.h | 334 + .../dynamictreewidget.cpp | 398 + .../dynamictreewidget.h | 81 + .../tests/qmlbreadcrumbnavigation/fuzz.png | Bin 0 -> 62501 bytes .../qmlbreadcrumbnavigation/indexfinder.h | 69 + .../tests/qmlbreadcrumbnavigation/kbihash_p.h | 401 + .../kbreadcrumbselectionmodel.cpp | 210 + .../kbreadcrumbselectionmodel.h | 162 + .../kmodelindexproxymapper.cpp | 247 + .../kmodelindexproxymapper.h | 113 + .../kproxyitemselectionmodel.cpp | 256 + .../kproxyitemselectionmodel.h | 77 + .../kresettingproxymodel.cpp | 54 + .../kresettingproxymodel.h | 45 + .../kselectionproxymodel.cpp | 2314 ++++ .../kselectionproxymodel.h | 501 + .../qmlbreadcrumbnavigation/list-line-top.png | Bin 0 -> 283 bytes .../tests/qmlbreadcrumbnavigation/main.cpp | 18 + .../qmlbreadcrumbnavigation/mainview.qml | 98 + .../qmlbreadcrumbnavigation/mainwindow.cpp | 170 + .../qmlbreadcrumbnavigation/mainwindow.h | 36 + .../qmllistselectionmodel.cpp | 71 + .../qmllistselectionmodel.h | 66 + .../scrollable-bottom.png | Bin 0 -> 288 bytes .../scrollable-top.png | Bin 0 -> 294 bytes .../selected_bottom.png | Bin 0 -> 2529 bytes .../qmlbreadcrumbnavigation/selected_top.png | Bin 0 -> 3237 bytes .../simple/001-assert.patch | 29 + .../simple/CMakeLists.txt | 42 + .../simple/cmake/FindQt4.cmake | 1256 +++ .../simple/cmake/MacroPushRequiredVars.cmake | 47 + .../cmake/Qt4ConfigDependentSettings.cmake | 384 + .../simple/cmake/Qt4Macros.cmake | 414 + .../qmlbreadcrumbnavigation/simple/main.cpp | 18 + .../simple/mainview.qml | 51 + .../simple/mainwindow.cpp | 90 + .../simple/mainwindow.h | 53 + .../slideout-panel-background.png | Bin 0 -> 27307 bytes .../slideout-panel-handle.png | Bin 0 -> 1442 bytes .../transparentplus.png | Bin 0 -> 852 bytes .../transparentplus.svg | 70 + .../qmlbreadcrumbnavigation/tree_example | 20 + .../qml/kde/tests/qobject_sender.qml | 50 + .../qml/kde/tests/slideoutpaneltest.qml | 89 + .../qml/kde/tests/synced_sliders.qml | 58 + .../qml/kde/tests/transformorigin.qml | 148 + kdepim-runtime/qml/kde/transparentplus.svg | 70 + kdepim-runtime/resources/.krazy | 1 + kdepim-runtime/resources/CMakeLists.txt | 80 + kdepim-runtime/resources/Info.plist.template | 36 + kdepim-runtime/resources/Mainpage.dox | 2 + .../resources/akonotes/CMakeLists.txt | 42 + kdepim-runtime/resources/akonotes/Messages.sh | 2 + .../resources/akonotes/akonotesresource.cpp | 50 + .../akonotes/akonotesresource.desktop | 97 + .../resources/akonotes/akonotesresource.h | 41 + kdepim-runtime/resources/akonotes/main.cpp | 24 + .../resources/birthdays/CMakeLists.txt | 35 + .../resources/birthdays/Messages.sh | 3 + .../resources/birthdays/birthdaysresource.cpp | 343 + .../birthdays/birthdaysresource.desktop | 99 + .../resources/birthdays/birthdaysresource.h | 69 + .../birthdays/birthdaysresource.kcfg | 25 + .../resources/birthdays/configdialog.cpp | 43 + .../resources/birthdays/configdialog.h | 45 + .../resources/birthdays/configdialog.ui | 162 + .../resources/birthdays/settings.kcfgc | 7 + .../resources/contacts/CMakeLists.txt | 36 + kdepim-runtime/resources/contacts/Messages.sh | 3 + .../resources/contacts/contactsresource.cpp | 534 + .../contacts/contactsresource.desktop | 100 + .../resources/contacts/contactsresource.h | 82 + .../resources/contacts/contactsresource.kcfg | 21 + .../resources/contacts/settings.kcfgc | 8 + .../resources/contacts/settingsdialog.cpp | 100 + .../resources/contacts/settingsdialog.h | 55 + .../resources/contacts/settingsdialog.ui | 87 + .../resources/contacts/wizard/CMakeLists.txt | 5 + .../resources/contacts/wizard/Messages.sh | 6 + .../contacts/wizard/contactswizard.desktop | 100 + .../contacts/wizard/contactswizard.es.cmake | 44 + .../contacts/wizard/contactswizard.ui | 52 + kdepim-runtime/resources/dav/CMakeLists.txt | 1 + kdepim-runtime/resources/dav/COPYING | 340 + kdepim-runtime/resources/dav/README | 50 + kdepim-runtime/resources/dav/TODO | 2 + .../resources/dav/common/davcollection.cpp | 89 + .../resources/dav/common/davcollection.h | 142 + .../dav/common/davcollectiondeletejob.cpp | 61 + .../dav/common/davcollectiondeletejob.h | 59 + .../dav/common/davcollectionmodifyjob.cpp | 158 + .../dav/common/davcollectionmodifyjob.h | 80 + .../dav/common/davcollectionsfetchjob.cpp | 307 + .../dav/common/davcollectionsfetchjob.h | 82 + .../common/davcollectionsmultifetchjob.cpp | 62 + .../dav/common/davcollectionsmultifetchjob.h | 77 + .../resources/dav/common/davitem.cpp | 93 + kdepim-runtime/resources/dav/common/davitem.h | 109 + .../resources/dav/common/davitemcreatejob.cpp | 133 + .../resources/dav/common/davitemcreatejob.h | 64 + .../resources/dav/common/davitemdeletejob.cpp | 67 + .../resources/dav/common/davitemdeletejob.h | 56 + .../resources/dav/common/davitemfetchjob.cpp | 93 + .../resources/dav/common/davitemfetchjob.h | 62 + .../resources/dav/common/davitemmodifyjob.cpp | 113 + .../resources/dav/common/davitemmodifyjob.h | 62 + .../resources/dav/common/davitemsfetchjob.cpp | 153 + .../resources/dav/common/davitemsfetchjob.h | 72 + .../resources/dav/common/davitemslistjob.cpp | 203 + .../resources/dav/common/davitemslistjob.h | 76 + .../resources/dav/common/davjobbase.cpp | 84 + .../resources/dav/common/davjobbase.h | 78 + .../resources/dav/common/davmanager.cpp | 120 + .../resources/dav/common/davmanager.h | 104 + .../dav/common/davmultigetprotocol.cpp | 23 + .../dav/common/davmultigetprotocol.h | 52 + .../common/davprincipalhomesetsfetchjob.cpp | 219 + .../dav/common/davprincipalhomesetsfetchjob.h | 76 + .../dav/common/davprincipalsearchjob.cpp | 369 + .../dav/common/davprincipalsearchjob.h | 112 + .../resources/dav/common/davprotocolbase.cpp | 33 + .../resources/dav/common/davprotocolbase.h | 121 + .../resources/dav/common/davutils.cpp | 365 + .../resources/dav/common/davutils.h | 179 + .../resources/dav/common/etagcache.cpp | 97 + .../resources/dav/common/etagcache.h | 108 + .../dav/protocols/caldavprotocol.cpp | 373 + .../resources/dav/protocols/caldavprotocol.h | 49 + .../dav/protocols/carddavprotocol.cpp | 148 + .../resources/dav/protocols/carddavprotocol.h | 49 + .../dav/protocols/groupdavprotocol.cpp | 128 + .../dav/protocols/groupdavprotocol.h | 43 + .../resources/dav/resource/CMakeLists.txt | 107 + .../resources/dav/resource/Messages.sh | 3 + .../dav/resource/akonadi-resources.png | Bin 0 -> 69059 bytes .../resources/dav/resource/configdialog.cpp | 278 + .../resources/dav/resource/configdialog.h | 66 + .../resources/dav/resource/configdialog.ui | 238 + .../dav/resource/davfreebusyhandler.cpp | 219 + .../dav/resource/davfreebusyhandler.h | 99 + .../dav/resource/davgroupwareprovider.desktop | 68 + .../dav/resource/davgroupwareresource.cpp | 1023 ++ .../dav/resource/davgroupwareresource.desktop | 89 + .../dav/resource/davgroupwareresource.h | 124 + .../dav/resource/davgroupwareresource.kcfg | 45 + .../dav/resource/davprotocolattribute.cpp | 54 + .../dav/resource/davprotocolattribute.h | 43 + .../resources/dav/resource/searchdialog.cpp | 193 + .../resources/dav/resource/searchdialog.h | 59 + .../resources/dav/resource/searchdialog.ui | 221 + .../resources/dav/resource/settings.cpp | 624 ++ .../resources/dav/resource/settings.h | 150 + .../resources/dav/resource/settingsbase.kcfgc | 8 + .../resources/dav/resource/setupwizard.cpp | 535 + .../resources/dav/resource/setupwizard.h | 154 + .../dav/resource/urlconfigurationdialog.cpp | 256 + .../dav/resource/urlconfigurationdialog.h | 77 + .../dav/resource/urlconfigurationdialog.ui | 237 + .../resources/dav/services/citadel.desktop | 50 + .../resources/dav/services/davical.desktop | 50 + .../resources/dav/services/egroupware.desktop | 52 + .../dav/services/opengroupware.desktop | 51 + .../dav/services/owncloud-pre5.desktop | 45 + .../resources/dav/services/owncloud.desktop | 50 + .../resources/dav/services/scalix.desktop | 51 + .../resources/dav/services/sogo.desktop | 52 + .../resources/dav/services/yahoo.desktop | 56 + .../resources/dav/services/zarafa.desktop | 51 + .../resources/dav/services/zimbra.desktop | 52 + .../resources/facebook/CMakeLists.txt | 83 + .../resources/facebook/Info.plist.template | 36 + kdepim-runtime/resources/facebook/Messages.sh | 4 + .../akonadi_facebook_resource.notifyrc | 236 + .../resources/facebook/facebookresource.cpp | 426 + .../facebook/facebookresource.desktop | 87 + .../resources/facebook/facebookresource.h | 145 + .../facebook/facebookresource_events.cpp | 108 + .../facebook/facebookresource_friends.cpp | 260 + .../facebook/facebookresource_notes.cpp | 117 + .../facebookresource_notifications.cpp | 397 + .../facebook/facebookresource_posts.cpp | 208 + .../resources/facebook/icons/CMakeLists.txt | 1 + .../icons/hi16-apps-facebookresource.png | Bin 0 -> 698 bytes .../icons/hi22-apps-facebookresource.png | Bin 0 -> 1011 bytes .../icons/hi32-apps-facebookresource.png | Bin 0 -> 1545 bytes .../icons/hi48-apps-facebookresource.png | Bin 0 -> 2539 bytes .../icons/hisc-apps-facebookresource.sgvz | Bin 0 -> 1478 bytes .../facebook/serializer/CMakeLists.txt | 36 + .../akonadi_serializer_socialnotification.cpp | 121 + ...nadi_serializer_socialnotification.desktop | 84 + .../akonadi_serializer_socialnotification.h | 41 + .../x-vnd.akonadi.socialnotification.xml | 8 + .../resources/facebook/settings.cpp | 82 + kdepim-runtime/resources/facebook/settings.h | 46 + .../resources/facebook/settingsbase.kcfg | 26 + .../resources/facebook/settingsbase.kcfgc | 7 + .../resources/facebook/settingsdialog.cpp | 226 + .../resources/facebook/settingsdialog.h | 55 + .../resources/facebook/settingsdialog.ui | 181 + .../resources/facebook/timestampattribute.cpp | 58 + .../resources/facebook/timestampattribute.h | 40 + .../folderarchivesettings/CMakeLists.txt | 31 + .../folderarchivesettings/Messages.sh | 2 + .../autotests/CMakeLists.txt | 10 + .../folderarchiveaccountinfotest.cpp | 74 + .../autotests/folderarchiveaccountinfotest.h | 40 + .../folderarchiveaccountinfo.cpp | 127 + .../folderarchiveaccountinfo.h | 67 + .../folderarchivesettingpage.cpp | 162 + .../folderarchivesettingpage.h | 67 + .../folderarchivesettings_export.h | 39 + .../folderarchiveutil.cpp | 30 + .../folderarchivesettings/folderarchiveutil.h | 31 + kdepim-runtime/resources/gmail/CMakeLists.txt | 59 + kdepim-runtime/resources/gmail/Messages.sh | 3 + .../gmail/gmailchangeitemslabelstask.cpp | 74 + .../gmail/gmailchangeitemslabelstask.h | 40 + .../resources/gmail/gmailconfigdialog.cpp | 260 + .../resources/gmail/gmailconfigdialog.h | 79 + .../resources/gmail/gmailconfigdialog.ui | 243 + .../resources/gmail/gmaillabelattribute.cpp | 106 + .../resources/gmail/gmaillabelattribute.h | 54 + .../resources/gmail/gmaillinkitemstask.cpp | 209 + .../resources/gmail/gmaillinkitemstask.h | 63 + .../resources/gmail/gmailmessagehelper.cpp | 107 + .../resources/gmail/gmailmessagehelper.h | 47 + .../gmail/gmailpasswordrequester.cpp | 95 + .../resources/gmail/gmailpasswordrequester.h | 49 + .../resources/gmail/gmailresource.cpp | 241 + .../resources/gmail/gmailresource.desktop | 64 + .../resources/gmail/gmailresource.h | 68 + .../resources/gmail/gmailresource.kcfg | 116 + .../resources/gmail/gmailresourcestate.cpp | 48 + .../resources/gmail/gmailresourcestate.h | 44 + .../gmail/gmailretrievecollectionstask.cpp | 247 + .../gmail/gmailretrievecollectionstask.h | 36 + .../gmail/gmailretrieveitemstask.cpp | 57 + .../resources/gmail/gmailretrieveitemstask.h | 51 + .../resources/gmail/gmailsettings.cpp | 305 + .../resources/gmail/gmailsettings.h | 87 + .../resources/gmail/saslplugin/CMakeLists.txt | 18 + .../resources/gmail/saslplugin/config.h | 584 + .../gmail/saslplugin/plugin_common.c | 925 ++ .../gmail/saslplugin/plugin_common.h | 222 + .../gmail/saslplugin/xoauth2plugin.c | 243 + .../gmail/saslplugin/xoauth2plugin_init.c | 58 + .../resources/gmail/settingsbase.kcfgc | 6 + .../resources/google/CMakeLists.txt | 15 + .../resources/google/calendar/CMakeLists.txt | 77 + .../resources/google/calendar/Messages.sh | 4 + .../google/calendar/calendarresource.cpp | 766 ++ .../google/calendar/calendarresource.h | 71 + .../calendar/defaultreminderattribute.cpp | 114 + .../calendar/defaultreminderattribute.h | 45 + .../calendar/googlecalendarresource.desktop | 88 + .../resources/google/calendar/settings.cpp | 64 + .../resources/google/calendar/settings.h | 33 + .../google/calendar/settingsbase.kcfg | 37 + .../google/calendar/settingsbase.kcfgc | 7 + .../google/calendar/settingsdialog.cpp | 238 + .../google/calendar/settingsdialog.h | 57 + .../google/common/googleaccountmanager.cpp | 248 + .../google/common/googleaccountmanager.h | 69 + .../google/common/googleresource.cpp | 444 + .../resources/google/common/googleresource.h | 130 + .../google/common/googlesettings.cpp | 55 + .../resources/google/common/googlesettings.h | 58 + .../google/common/googlesettingsdialog.cpp | 252 + .../google/common/googlesettingsdialog.h | 82 + .../resources/google/contacts/CMakeLists.txt | 63 + .../resources/google/contacts/Messages.sh | 4 + .../google/contacts/contactsresource.cpp | 577 + .../google/contacts/contactsresource.h | 81 + .../contacts/googlecontactsresource.desktop | 91 + .../resources/google/contacts/settings.cpp | 63 + .../resources/google/contacts/settings.h | 33 + .../google/contacts/settingsbase.kcfg | 26 + .../google/contacts/settingsbase.kcfgc | 7 + .../google/contacts/settingsdialog.cpp | 52 + .../google/contacts/settingsdialog.h | 37 + kdepim-runtime/resources/ical/CMakeLists.txt | 44 + kdepim-runtime/resources/ical/Messages.sh | 4 + .../resources/ical/icalresource.desktop | 105 + .../resources/ical/icalresource.kcfg | 26 + .../resources/ical/icalresourceplugin.cpp | 32 + .../resources/ical/notes/CMakeLists.txt | 35 + .../resources/ical/notes/notesresource.cpp | 60 + .../ical/notes/notesresource.desktop | 129 + .../resources/ical/notes/notesresource.h | 45 + .../resources/ical/notes/notesresource.kcfg | 30 + .../resources/ical/notes/settings.kcfgc | 9 + kdepim-runtime/resources/ical/settings.kcfgc | 10 + .../resources/ical/shared/icalresource.cpp | 154 + .../resources/ical/shared/icalresource.h | 61 + .../ical/shared/icalresourcebase.cpp | 175 + .../resources/ical/shared/icalresourcebase.h | 111 + .../resources/ical/tests/CMakeLists.txt | 4 + .../resources/ical/tests/event.ical | 26 + .../resources/ical/tests/ical-empty.xml | 6 + .../resources/ical/tests/ical-step1.xml | 52 + .../resources/ical/tests/icaltest.es | 31 + kdepim-runtime/resources/ical/tests/task.ical | 16 + .../resources/ical/wizard/CMakeLists.txt | 4 + .../resources/ical/wizard/Messages.sh | 4 + .../resources/ical/wizard/icalwizard.desktop | 105 + .../resources/ical/wizard/icalwizard.es.cmake | 43 + .../resources/ical/wizard/icalwizard.ui | 45 + .../resources/icaldir/CMakeLists.txt | 42 + kdepim-runtime/resources/icaldir/Messages.sh | 3 + .../resources/icaldir/icaldirresource.cpp | 306 + .../resources/icaldir/icaldirresource.desktop | 87 + .../resources/icaldir/icaldirresource.h | 62 + .../resources/icaldir/icaldirresource.kcfg | 22 + .../resources/icaldir/settings.kcfgc | 7 + .../resources/icaldir/settingsdialog.ui | 221 + kdepim-runtime/resources/imap/CMakeLists.txt | 123 + kdepim-runtime/resources/imap/Messages.sh | 3 + .../resources/imap/addcollectiontask.cpp | 156 + .../resources/imap/addcollectiontask.h | 53 + kdepim-runtime/resources/imap/additemtask.cpp | 214 + kdepim-runtime/resources/imap/additemtask.h | 50 + .../resources/imap/batchfetcher.cpp | 243 + kdepim-runtime/resources/imap/batchfetcher.h | 79 + .../resources/imap/changecollectiontask.cpp | 294 + .../resources/imap/changecollectiontask.h | 51 + .../resources/imap/changeitemsflagstask.cpp | 153 + .../resources/imap/changeitemsflagstask.h | 56 + .../resources/imap/changeitemtask.cpp | 303 + .../resources/imap/changeitemtask.h | 61 + .../resources/imap/expungecollectiontask.cpp | 101 + .../resources/imap/expungecollectiontask.h | 46 + .../resources/imap/highestmodseqattribute.cpp | 58 + .../resources/imap/highestmodseqattribute.h | 41 + kdepim-runtime/resources/imap/imapaccount.cpp | 109 + kdepim-runtime/resources/imap/imapaccount.h | 69 + kdepim-runtime/resources/imap/imapflags.cpp | 26 + kdepim-runtime/resources/imap/imapflags.h | 51 + .../resources/imap/imapidlemanager.cpp | 185 + .../resources/imap/imapidlemanager.h | 82 + .../resources/imap/imapresource.cpp | 77 + .../resources/imap/imapresource.desktop | 101 + kdepim-runtime/resources/imap/imapresource.h | 51 + .../resources/imap/imapresource.kcfg | 114 + .../resources/imap/imapresourcebase.cpp | 766 ++ .../resources/imap/imapresourcebase.h | 171 + kdepim-runtime/resources/imap/main.cpp | 22 + .../resources/imap/messagehelper.cpp | 82 + kdepim-runtime/resources/imap/messagehelper.h | 42 + .../resources/imap/movecollectiontask.cpp | 154 + .../resources/imap/movecollectiontask.h | 50 + .../resources/imap/moveitemstask.cpp | 326 + kdepim-runtime/resources/imap/moveitemstask.h | 58 + .../resources/imap/noinferiorsattribute.cpp | 59 + .../resources/imap/noinferiorsattribute.h | 40 + .../resources/imap/noselectattribute.cpp | 59 + .../resources/imap/noselectattribute.h | 40 + .../imap/passwordrequesterinterface.cpp | 33 + .../imap/passwordrequesterinterface.h | 57 + .../imap/removecollectionrecursivetask.cpp | 170 + .../imap/removecollectionrecursivetask.h | 56 + .../resources/imap/resourcestate.cpp | 364 + kdepim-runtime/resources/imap/resourcestate.h | 137 + .../resources/imap/resourcestateinterface.cpp | 51 + .../resources/imap/resourcestateinterface.h | 115 + .../resources/imap/resourcetask.cpp | 539 + kdepim-runtime/resources/imap/resourcetask.h | 164 + .../imap/retrievecollectionmetadatatask.cpp | 316 + .../imap/retrievecollectionmetadatatask.h | 52 + .../imap/retrievecollectionstask.cpp | 236 + .../resources/imap/retrievecollectionstask.h | 55 + .../resources/imap/retrieveitemstask.cpp | 590 + .../resources/imap/retrieveitemstask.h | 86 + .../resources/imap/retrieveitemtask.cpp | 148 + .../resources/imap/retrieveitemtask.h | 56 + kdepim-runtime/resources/imap/searchtask.cpp | 223 + kdepim-runtime/resources/imap/searchtask.h | 48 + kdepim-runtime/resources/imap/serverinfo.ui | 24 + .../resources/imap/serverinfodialog.cpp | 47 + .../resources/imap/serverinfodialog.h | 41 + kdepim-runtime/resources/imap/sessionpool.cpp | 533 + kdepim-runtime/resources/imap/sessionpool.h | 130 + .../resources/imap/sessionuiproxy.h | 40 + kdepim-runtime/resources/imap/settings.cpp | 329 + kdepim-runtime/resources/imap/settings.h | 74 + .../resources/imap/settingsbase.kcfgc | 6 + .../imap/settingspasswordrequester.cpp | 142 + .../imap/settingspasswordrequester.h | 55 + kdepim-runtime/resources/imap/setupserver.cpp | 660 ++ kdepim-runtime/resources/imap/setupserver.h | 113 + .../resources/imap/setupserverview_desktop.ui | 653 ++ .../resources/imap/setupserverview_mobile.ui | 514 + .../resources/imap/subscriptiondialog.cpp | 466 + .../resources/imap/subscriptiondialog.h | 124 + .../resources/imap/tests/CMakeLists.txt | 53 + .../imap/tests/dummypasswordrequester.cpp | 83 + .../imap/tests/dummypasswordrequester.h | 53 + .../imap/tests/dummyresourcestate.cpp | 352 + .../resources/imap/tests/dummyresourcestate.h | 157 + .../resources/imap/tests/imaptestbase.cpp | 137 + .../resources/imap/tests/imaptestbase.h | 95 + .../imap/tests/testaddcollectiontask.cpp | 181 + .../resources/imap/tests/testadditemtask.cpp | 184 + .../imap/tests/testchangecollectiontask.cpp | 244 + .../imap/tests/testchangeitemtask.cpp | 218 + .../imap/tests/testexpungecollectiontask.cpp | 128 + .../imap/tests/testmovecollectiontask.cpp | 197 + .../imap/tests/testmoveitemstask.cpp | 263 + .../testremovecollectionrecursivetask.cpp | 247 + .../resources/imap/tests/testresourcetask.cpp | 178 + .../testretrievecollectionmetadatatask.cpp | 342 + .../tests/testretrievecollectionstask.cpp | 491 + .../imap/tests/testretrieveitemstask.cpp | 613 ++ .../imap/tests/testretrieveitemtask.cpp | 126 + .../resources/imap/tests/testsessionpool.cpp | 794 ++ .../imap/tests/testsubscriptiondialog.cpp | 59 + .../resources/imap/timestampattribute.cpp | 61 + .../resources/imap/timestampattribute.h | 42 + .../resources/imap/uidnextattribute.cpp | 59 + .../resources/imap/uidnextattribute.h | 40 + .../resources/imap/uidvalidityattribute.cpp | 59 + .../resources/imap/uidvalidityattribute.h | 40 + .../resources/imap/wizard/CMakeLists.txt | 2 + .../resources/imap/wizard/Messages.sh | 4 + .../resources/imap/wizard/imapwizard.desktop | 101 + .../resources/imap/wizard/imapwizard.es | 128 + .../resources/imap/wizard/imapwizard.ui | 97 + kdepim-runtime/resources/kabc/CMakeLists.txt | 41 + kdepim-runtime/resources/kabc/Messages.sh | 3 + .../resources/kabc/kabcresource.cpp | 896 ++ .../resources/kabc/kabcresource.desktop | 100 + kdepim-runtime/resources/kabc/kabcresource.h | 117 + .../resources/kabc/kresourceassistant.cpp | 400 + .../resources/kabc/kresourceassistant.h | 56 + .../resources/kalarm/CMakeLists.txt | 2 + kdepim-runtime/resources/kalarm/Messages.sh | 4 + .../resources/kalarm/kalarm/CMakeLists.txt | 49 + .../kalarm/kalarm/kalarmresource.cpp | 545 + .../kalarm/kalarm/kalarmresource.desktop | 97 + .../resources/kalarm/kalarm/kalarmresource.h | 79 + .../kalarm/kalarm/kalarmresource.kcfg | 33 + .../resources/kalarm/kalarm/settings.kcfgc | 10 + .../resources/kalarm/kalarmdir/CMakeLists.txt | 45 + .../resources/kalarm/kalarmdir/autoqpointer.h | 46 + .../kalarm/kalarmdir/kalarmdirresource.cpp | 1229 +++ .../kalarmdir/kalarmdirresource.desktop | 92 + .../kalarm/kalarmdir/kalarmdirresource.h | 105 + .../kalarm/kalarmdir/kalarmdirresource.kcfg | 32 + .../resources/kalarm/kalarmdir/settings.kcfgc | 8 + .../kalarm/kalarmdir/settingsdialog.cpp | 135 + .../kalarm/kalarmdir/settingsdialog.h | 68 + .../kalarm/kalarmdir/settingsdialog.ui | 152 + .../kalarm/shared/alarmtyperadiowidget.cpp | 73 + .../kalarm/shared/alarmtyperadiowidget.h | 50 + .../kalarm/shared/alarmtyperadiowidget.ui | 50 + .../kalarm/shared/alarmtypewidget.cpp | 57 + .../resources/kalarm/shared/alarmtypewidget.h | 48 + .../kalarm/shared/alarmtypewidget.ui | 50 + .../kalarm/shared/kalarmresourcecommon.cpp | 207 + .../kalarm/shared/kalarmresourcecommon.h | 60 + kdepim-runtime/resources/kcal/CMakeLists.txt | 36 + kdepim-runtime/resources/kcal/Messages.sh | 2 + .../resources/kcal/kcalresource.cpp | 695 ++ .../resources/kcal/kcalresource.desktop | 101 + kdepim-runtime/resources/kcal/kcalresource.h | 109 + .../resources/kdeaccounts/CMakeLists.txt | 34 + .../resources/kdeaccounts/Messages.sh | 3 + .../kdeaccounts/kdeaccountsresource.cpp | 168 + .../kdeaccounts/kdeaccountsresource.desktop | 99 + .../kdeaccounts/kdeaccountsresource.h | 64 + .../kdeaccounts/kdeaccountsresource.kcfg | 26 + .../resources/kdeaccounts/settings.kcfgc | 7 + kdepim-runtime/resources/kolab/CMakeLists.txt | 61 + .../resources/kolab/kolabhelpers.cpp | 387 + kdepim-runtime/resources/kolab/kolabhelpers.h | 44 + .../resources/kolab/kolabmessagehelper.cpp | 58 + .../resources/kolab/kolabmessagehelper.h | 42 + .../resources/kolab/kolabresource.cpp | 180 + .../resources/kolab/kolabresource.desktop | 81 + .../resources/kolab/kolabresource.h | 59 + .../resources/kolab/kolabresourcestate.cpp | 82 + .../resources/kolab/kolabresourcestate.h | 37 + .../kolab/kolabretrievecollectionstask.cpp | 249 + .../kolab/kolabretrievecollectionstask.h | 60 + .../resources/kolabproxy/CMakeLists.txt | 108 + .../resources/kolabproxy/Messages.sh | 3 + .../kolabproxy/addressbookhandler.cpp | 167 + .../resources/kolabproxy/addressbookhandler.h | 45 + .../akonadi_kolabproxy_resource.notifyrc | 112 + .../resources/kolabproxy/calendarhandler.cpp | 49 + .../resources/kolabproxy/calendarhandler.h | 43 + .../resources/kolabproxy/changeformat.ui | 133 + .../kolabproxy/collectiontreebuilder.cpp | 103 + .../kolabproxy/collectiontreebuilder.h | 59 + .../kolabproxy/freebusyupdatehandler.cpp | 112 + .../kolabproxy/freebusyupdatehandler.h | 55 + .../resources/kolabproxy/handlermanager.cpp | 83 + .../resources/kolabproxy/handlermanager.h | 57 + .../resources/kolabproxy/hi64-apps-kolab.png | Bin 0 -> 5496 bytes .../resources/kolabproxy/imapitemaddedjob.cpp | 130 + .../resources/kolabproxy/imapitemaddedjob.h | 44 + .../kolabproxy/imapitemremovedjob.cpp | 72 + .../resources/kolabproxy/imapitemremovedjob.h | 39 + .../resources/kolabproxy/incidencehandler.cpp | 97 + .../resources/kolabproxy/incidencehandler.h | 49 + .../resources/kolabproxy/itemaddedjob.cpp | 77 + .../resources/kolabproxy/itemaddedjob.h | 42 + .../resources/kolabproxy/itemchangedjob.cpp | 136 + .../resources/kolabproxy/itemchangedjob.h | 44 + .../resources/kolabproxy/journalhandler.cpp | 50 + .../resources/kolabproxy/journalhandler.h | 43 + .../resources/kolabproxy/kolabdefs.cpp | 117 + .../resources/kolabproxy/kolabdefs.h | 55 + .../resources/kolabproxy/kolabhandler.cpp | 225 + .../resources/kolabproxy/kolabhandler.h | 160 + .../kolabproxy/kolabproxyresource.cpp | 832 ++ .../kolabproxy/kolabproxyresource.desktop | 62 + .../resources/kolabproxy/kolabproxyresource.h | 144 + .../kolabproxy/kolabproxyresource.kcfg | 18 + .../resources/kolabproxy/kolabsettings.ui | 195 + .../resources/kolabproxy/notehandler.cpp | 104 + .../resources/kolabproxy/notehandler.h | 44 + .../org.freedesktop.Akonadi.kolabproxy.xml | 9 + .../kolabproxy/revertitemchangesjob.cpp | 87 + .../kolabproxy/revertitemchangesjob.h | 44 + .../resources/kolabproxy/settings.kcfgc | 8 + .../kolabproxy/setupdefaultfoldersjob.cpp | 175 + .../kolabproxy/setupdefaultfoldersjob.h | 43 + .../resources/kolabproxy/setupkolab.cpp | 228 + .../resources/kolabproxy/setupkolab.h | 69 + .../resources/kolabproxy/taskshandler.cpp | 50 + .../resources/kolabproxy/taskshandler.h | 43 + .../resources/kolabproxy/tests/CMakeLists.txt | 42 + .../kolabproxy/tests/clientsidetest.cpp | 237 + .../resources/kolabproxy/tests/couriervm.conf | 10 + .../kolabproxy/tests/create_ldap_users.py | 276 + .../resources/kolabproxy/tests/dovecotvm.conf | 10 + .../resources/kolabproxy/tests/event.ical | 26 + .../kolabproxy/tests/imap-quicktest.es | 28 + .../kolabproxy/tests/imapsignaltest.cpp | 339 + .../tests/imaptest-dovecot-step1.xml | 93 + .../tests/imaptest-dovecot-step2.xml | 121 + .../kolabproxy/tests/imaptest-dovecot.es | 2 + .../kolabproxy/tests/imaptest-kolab-step1.xml | 99 + .../kolabproxy/tests/imaptest-kolab-step2.xml | 128 + .../kolabproxy/tests/imaptest-kolab.es | 2 + .../resources/kolabproxy/tests/imaptest.es | 112 + .../kolabproxy/tests/kolab-step1.xml | 69 + .../kolabproxy/tests/kolab-step2.xml | 270 + .../kolabproxy/tests/kolabconvertertest.cpp | 120 + .../kolabproxy/tests/kolabevent.mbox | 56 + .../resources/kolabproxy/tests/kolabtest.es | 129 + .../resources/kolabproxy/tests/kolabvm.conf | 11 + .../kolabproxy/tests/proxyintegrationtest.cpp | 157 + .../kolabproxy/tests/runimapcommand.py | 284 + .../resources/kolabproxy/tests/task.ical | 16 + .../resources/kolabproxy/tests/testmail.mbox | 19 + .../resources/kolabproxy/tests/testutils.cpp | 101 + .../resources/kolabproxy/tests/testutils.h | 39 + .../tests/unittestenv/config-mysql-db.xml | 8 + .../tests/unittestenv/config-mysql-fs.xml | 8 + .../unittestenv/config-postgresql-db.xml | 8 + .../unittestenv/config-postgresql-fs.xml | 8 + .../tests/unittestenv/config-sqlite-db.xml | 8 + .../kdehome/share/config/akonadi-firstrunrc | 4 + .../share/config/akonadi_knut_resource_0rc | 4 + .../unittestenv/kdehome/share/config/kdebugrc | 80 + .../unittestenv/kdehome/share/config/kdedrc | 3 + .../unittestenv/kdehome/testdata-res1.xml | 37 + .../akonadi/akonadiserverrc | 5 + .../akonadi/akonadiserverrc | 6 + .../akonadi/akonadiserverrc | 9 + .../akonadi/akonadiserverrc | 10 + .../akonadi/akonadiserverrc | 10 + .../resources/kolabproxy/upgradejob.cpp | 176 + .../resources/kolabproxy/upgradejob.h | 53 + .../kolabproxy/wizard/CMakeLists.txt | 5 + .../resources/kolabproxy/wizard/Messages.sh | 4 + .../kolabproxy/wizard/kolabwizard.desktop | 101 + .../kolabproxy/wizard/kolabwizard.es | 192 + .../kolabproxy/wizard/kolabwizard.ui | 111 + .../resources/localbookmarks/CMakeLists.txt | 36 + .../resources/localbookmarks/Messages.sh | 4 + .../localbookmarks/localbookmarksresource.cpp | 221 + .../localbookmarksresource.desktop | 102 + .../localbookmarks/localbookmarksresource.h | 53 + .../localbookmarksresource.kcfg | 18 + .../resources/localbookmarks/settings.kcfgc | 8 + .../resources/maildir/CMakeLists.txt | 44 + kdepim-runtime/resources/maildir/Messages.sh | 3 + .../resources/maildir/configdialog.cpp | 110 + .../resources/maildir/configdialog.h | 51 + .../maildir/libmaildir/CMakeLists.txt | 12 + .../resources/maildir/libmaildir/keycache.cpp | 88 + .../resources/maildir/libmaildir/keycache.h | 77 + .../resources/maildir/libmaildir/maildir.cpp | 846 ++ .../resources/maildir/libmaildir/maildir.h | 259 + .../maildir/libmaildir/maildir_export.h | 43 + .../maildir/libmaildir/tests/CMakeLists.txt | 13 + .../maildir/libmaildir/tests/testmaildir.cpp | 435 + .../maildir/libmaildir/tests/testmaildir.h | 59 + .../resources/maildir/maildirresource.cpp | 881 ++ .../resources/maildir/maildirresource.desktop | 108 + .../resources/maildir/maildirresource.h | 106 + .../resources/maildir/maildirresource.kcfg | 25 + kdepim-runtime/resources/maildir/main.cpp | 26 + .../resources/maildir/retrieveitemsjob.cpp | 185 + .../resources/maildir/retrieveitemsjob.h | 71 + .../resources/maildir/settings.kcfgc | 9 + kdepim-runtime/resources/maildir/settings.ui | 84 + .../resources/maildir/tests/CMakeLists.txt | 43 + .../resources/maildir/tests/maildir-empty.xml | 5 + .../resources/maildir/tests/maildir-step1.xml | 29 + .../resources/maildir/tests/maildir-step2.xml | 29 + .../resources/maildir/tests/maildir.js | 63 + .../resources/maildir/tests/maildir.xml | 732 ++ .../grandchild/cur/1237726881.6570.rfoxg!2,S | 282 + .../.child1.directory/grandchild/new/.keep | 1 + .../.child1.directory/grandchild/tmp/.keep | 1 + .../child1/cur/1237726858.6570.dtdn4!2,S | 107 + .../child1/cur/1237726875.6570.R4KOW!2,S | 150 + .../maildir/.root.directory/child1/new/.keep | 1 + .../maildir/.root.directory/child1/tmp/.keep | 1 + .../maildir/.root.directory/child2/.keep | 1 + .../maildir/.root.directory/child2/cur/.keep | 1 + .../maildir/.root.directory/child2/new/.keep | 1 + .../maildir/.root.directory/child2/tmp/.keep | 1 + .../root/cur/1237726845.6570.BejQg!2,S | 171 + .../maildir/tests/maildir/root/new/.keep | 1 + .../maildir/tests/maildir/root/tmp/.keep | 1 + .../resources/maildir/tests/synctest.cpp | 62 + .../resources/maildir/tests/synctest.h | 44 + .../resources/maildir/tests/testmail.mbox | 19 + .../maildir/tests/unittestenv/config.xml | 6 + .../kdehome/share/config/akonadi-firstrunrc | 3 + .../share/config/akonadi_maildir_resource_0rc | 2 + .../unittestenv/kdehome/share/config/kdebugrc | 110 + .../unittestenv/kdehome/share/config/kdedrc | 3 + .../kdehome/share/config/kwalletrc | 2 + .../unittestenv/kdehome/share/config/qttestrc | 2 + .../tests/unittestenv/kdehome/testdata.xml | 4 + .../xdgconfig/akonadi/akonadiserverrc | 4 + .../maildir/tests/unittestenv/xdglocal/.keep | 1 + .../resources/maildir/wizard/CMakeLists.txt | 2 + .../resources/maildir/wizard/Messages.sh | 4 + .../maildir/wizard/maildirwizard.desktop | 105 + .../resources/maildir/wizard/maildirwizard.es | 41 + .../resources/maildir/wizard/maildirwizard.ui | 56 + .../mailtransport_dummy/CMakeLists.txt | 22 + .../resources/mailtransport_dummy/Messages.sh | 3 + .../mailtransport_dummy/configdialog.cpp | 56 + .../mailtransport_dummy/configdialog.h | 43 + .../mailtransport_dummy/mtdummyresource.cpp | 108 + .../mtdummyresource.desktop | 90 + .../mailtransport_dummy/mtdummyresource.h | 58 + .../mailtransport_dummy/mtdummyresource.kcfg | 14 + .../mailtransport_dummy/settings.kcfgc | 8 + .../resources/mailtransport_dummy/settings.ui | 60 + kdepim-runtime/resources/mbox/CMakeLists.txt | 44 + kdepim-runtime/resources/mbox/Messages.sh | 3 + kdepim-runtime/resources/mbox/compactpage.cpp | 139 + kdepim-runtime/resources/mbox/compactpage.h | 50 + kdepim-runtime/resources/mbox/compactpage.ui | 140 + .../resources/mbox/deleteditemsattribute.cpp | 96 + .../resources/mbox/deleteditemsattribute.h | 64 + kdepim-runtime/resources/mbox/lockfilepage.ui | 156 + .../resources/mbox/lockmethodpage.cpp | 57 + .../resources/mbox/lockmethodpage.h | 40 + .../resources/mbox/mboxresource.cpp | 376 + .../resources/mbox/mboxresource.desktop | 103 + kdepim-runtime/resources/mbox/mboxresource.h | 68 + .../resources/mbox/mboxresource.kcfg | 54 + kdepim-runtime/resources/mbox/settings.kcfgc | 8 + .../resources/mbox/wizard/CMakeLists.txt | 2 + .../resources/mbox/wizard/Messages.sh | 4 + .../mbox/wizard/mailboxwizard.desktop | 103 + .../resources/mbox/wizard/mailboxwizard.es | 41 + .../resources/mbox/wizard/mailboxwizard.ui | 56 + .../resources/mixedmaildir/CMakeLists.txt | 70 + .../resources/mixedmaildir/Messages.sh | 3 + .../mixedmaildir/compactchangehelper.cpp | 238 + .../mixedmaildir/compactchangehelper.h | 61 + .../resources/mixedmaildir/configdialog.cpp | 93 + .../resources/mixedmaildir/configdialog.h | 45 + .../mixedmaildir/kmindexreader/CMakeLists.txt | 23 + .../kmindexreader/kmindexreader.cpp | 597 + .../kmindexreader/kmindexreader.h | 153 + .../kmindexreader/kmindexreader_export.h | 44 + .../kmindexreader/tests/CMakeLists.txt | 14 + .../kmindexreader/tests/TestIdxReader_data.h | 55 + .../kmindexreader/tests/data/.keep | 1 + .../kmindexreader/tests/testidxreader.cpp | 105 + .../kmindexreader/tests/testidxreader.h | 39 + .../mixedmaildir/mixedmaildirresource.cpp | 835 ++ .../mixedmaildir/mixedmaildirresource.desktop | 93 + .../mixedmaildir/mixedmaildirresource.h | 115 + .../mixedmaildir/mixedmaildirresource.kcfg | 22 + .../mixedmaildir/mixedmaildirstore.cpp | 2366 ++++ .../mixedmaildir/mixedmaildirstore.h | 54 + .../mixedmaildir/retrieveitemsjob.cpp | 359 + .../resources/mixedmaildir/retrieveitemsjob.h | 71 + .../resources/mixedmaildir/settings.kcfgc | 8 + .../resources/mixedmaildir/settings.ui | 69 + .../mixedmaildir/tests/CMakeLists.txt | 206 + .../tests/collectioncreatetest.cpp | 326 + .../tests/collectiondeletetest.cpp | 465 + .../tests/collectionfetchtest.cpp | 471 + .../tests/collectionmodifytest.cpp | 628 ++ .../mixedmaildir/tests/collectionmovetest.cpp | 1999 ++++ .../mixedmaildir/tests/data/.dimap.index | Bin 0 -> 1441 bytes .../tests/data/.maildir-tagged.index | Bin 0 -> 1525 bytes .../mixedmaildir/tests/data/.maildir.index | Bin 0 -> 1441 bytes .../tests/data/.mbox-tagged.index | Bin 0 -> 1317 bytes .../tests/data/.mbox-unpurged.index | Bin 0 -> 673 bytes .../mixedmaildir/tests/data/.mbox.index | Bin 0 -> 1257 bytes .../resources/mixedmaildir/tests/data/README | 38 + .../data/dimap/cur/1279980064.4595.LUBVK | 46 + .../data/dimap/cur/1279980064.4595.RTmAd_2,S | 45 + .../data/dimap/cur/1279980064.4595.g8PCJ | 46 + .../data/dimap/cur/1279980064.4595.qs6V9_2,S | 48 + .../mixedmaildir/tests/data/dimap/new/.keep | 1 + .../mixedmaildir/tests/data/dimap/tmp/.keep | 1 + .../cur/1279982188.18722.6qZsA_2,S | 45 + .../cur/1279982188.18722.Xdz3R_2,S | 46 + .../cur/1279982188.18722.f0l49_2,S | 45 + .../cur/1279982188.18722.kwx1b_2,S | 47 + .../tests/data/maildir-tagged/new/.keep | 1 + .../tests/data/maildir-tagged/tmp/.keep | 1 + .../data/maildir/cur/1279979617.4595.bwXSm | 45 + .../maildir/cur/1279979618.4595.CStza_2,S | 47 + .../maildir/cur/1279979618.4595.DUl0I_2,S | 44 + .../data/maildir/cur/1279979618.4595.pY5ny | 45 + .../mixedmaildir/tests/data/maildir/new/.keep | 1 + .../mixedmaildir/tests/data/maildir/tmp/.keep | 1 + .../resources/mixedmaildir/tests/data/mbox | 187 + .../mixedmaildir/tests/data/mbox-tagged | 187 + .../mixedmaildir/tests/data/mbox-unpurged | 187 + .../mixedmaildir/tests/itemcreatetest.cpp | 535 + .../mixedmaildir/tests/itemdeletetest.cpp | 603 ++ .../mixedmaildir/tests/itemfetchtest.cpp | 1157 ++ .../mixedmaildir/tests/itemmodifytest.cpp | 670 ++ .../mixedmaildir/tests/itemmovetest.cpp | 673 ++ .../mixedmaildir/tests/storecompacttest.cpp | 395 + .../tests/templatemethodstest.cpp | 232 + .../resources/mixedmaildir/tests/testdata.qrc | 31 + .../mixedmaildir/tests/testdatatest.cpp | 106 + .../mixedmaildir/tests/testdatautil.cpp | 202 + .../mixedmaildir/tests/testdatautil.h | 44 + kdepim-runtime/resources/nntp/CMakeLists.txt | 36 + kdepim-runtime/resources/nntp/Messages.sh | 3 + .../resources/nntp/configdialog.cpp | 43 + kdepim-runtime/resources/nntp/configdialog.h | 43 + kdepim-runtime/resources/nntp/configdialog.ui | 399 + .../nntp/nntpcollectionattribute.cpp | 61 + .../resources/nntp/nntpcollectionattribute.h | 53 + .../resources/nntp/nntpresource.cpp | 335 + .../resources/nntp/nntpresource.desktop | 99 + kdepim-runtime/resources/nntp/nntpresource.h | 73 + .../resources/nntp/nntpresource.kcfg | 83 + kdepim-runtime/resources/nntp/settings.cpp | 59 + kdepim-runtime/resources/nntp/settings.h | 39 + .../resources/nntp/settingsbase.kcfgc | 8 + .../resources/openxchange/CMakeLists.txt | 60 + .../resources/openxchange/Messages.sh | 3 + .../resources/openxchange/configdialog.cpp | 110 + .../resources/openxchange/configdialog.h | 51 + .../resources/openxchange/configdialog.ui | 143 + .../openxchange/icons/CMakeLists.txt | 1 + .../openxchange/icons/hi128-app-ox.png | Bin 0 -> 4840 bytes .../openxchange/icons/hi16-app-ox.png | Bin 0 -> 286 bytes .../openxchange/icons/hi32-app-ox.png | Bin 0 -> 534 bytes .../openxchange/icons/hi48-app-ox.png | Bin 0 -> 954 bytes .../openxchange/icons/hi64-app-ox.png | Bin 0 -> 1427 bytes .../openxchange/openxchangeresource.cpp | 1138 ++ .../openxchange/openxchangeresource.desktop | 88 + .../openxchange/openxchangeresource.h | 88 + .../openxchange/openxchangeresource.kcfg | 35 + .../openxchange/oxa/connectiontestjob.cpp | 77 + .../openxchange/oxa/connectiontestjob.h | 49 + .../openxchange/oxa/contactutils.cpp | 424 + .../resources/openxchange/oxa/contactutils.h | 57 + .../resources/openxchange/oxa/davmanager.cpp | 72 + .../resources/openxchange/oxa/davmanager.h | 95 + .../resources/openxchange/oxa/davutils.cpp | 72 + .../resources/openxchange/oxa/davutils.h | 69 + .../resources/openxchange/oxa/folder.cpp | 199 + .../resources/openxchange/oxa/folder.h | 204 + .../openxchange/oxa/foldercreatejob.cpp | 96 + .../openxchange/oxa/foldercreatejob.h | 68 + .../openxchange/oxa/folderdeletejob.cpp | 78 + .../openxchange/oxa/folderdeletejob.h | 65 + .../openxchange/oxa/foldermodifyjob.cpp | 95 + .../openxchange/oxa/foldermodifyjob.h | 70 + .../openxchange/oxa/foldermovejob.cpp | 95 + .../resources/openxchange/oxa/foldermovejob.h | 73 + .../openxchange/oxa/folderrequestjob.cpp | 85 + .../openxchange/oxa/folderrequestjob.h | 70 + .../oxa/foldersrequestdeltajob.cpp | 93 + .../openxchange/oxa/foldersrequestdeltajob.h | 78 + .../openxchange/oxa/foldersrequestjob.cpp | 94 + .../openxchange/oxa/foldersrequestjob.h | 81 + .../resources/openxchange/oxa/folderutils.cpp | 185 + .../resources/openxchange/oxa/folderutils.h | 52 + .../openxchange/oxa/incidenceutils.cpp | 566 + .../openxchange/oxa/incidenceutils.h | 60 + .../resources/openxchange/oxa/object.cpp | 123 + .../resources/openxchange/oxa/object.h | 96 + .../openxchange/oxa/objectcreatejob.cpp | 119 + .../openxchange/oxa/objectcreatejob.h | 52 + .../openxchange/oxa/objectdeletejob.cpp | 78 + .../openxchange/oxa/objectdeletejob.h | 49 + .../openxchange/oxa/objectmodifyjob.cpp | 117 + .../openxchange/oxa/objectmodifyjob.h | 52 + .../openxchange/oxa/objectmovejob.cpp | 96 + .../resources/openxchange/oxa/objectmovejob.h | 53 + .../openxchange/oxa/objectrequestjob.cpp | 85 + .../openxchange/oxa/objectrequestjob.h | 51 + .../oxa/objectsrequestdeltajob.cpp | 93 + .../openxchange/oxa/objectsrequestdeltajob.h | 80 + .../openxchange/oxa/objectsrequestjob.cpp | 95 + .../openxchange/oxa/objectsrequestjob.h | 73 + .../resources/openxchange/oxa/objectutils.cpp | 128 + .../resources/openxchange/oxa/objectutils.h | 69 + .../resources/openxchange/oxa/oxerrors.cpp | 48 + .../resources/openxchange/oxa/oxerrors.h | 57 + .../resources/openxchange/oxa/oxutils.cpp | 138 + .../resources/openxchange/oxa/oxutils.h | 51 + .../openxchange/oxa/updateusersjob.cpp | 93 + .../openxchange/oxa/updateusersjob.h | 55 + .../resources/openxchange/oxa/user.cpp | 64 + .../resources/openxchange/oxa/user.h | 56 + .../openxchange/oxa/useridrequestjob.cpp | 78 + .../openxchange/oxa/useridrequestjob.h | 49 + .../resources/openxchange/oxa/users.cpp | 154 + .../resources/openxchange/oxa/users.h | 70 + .../openxchange/oxa/usersrequestjob.cpp | 100 + .../openxchange/oxa/usersrequestjob.h | 51 + .../resources/openxchange/settings.kcfgc | 7 + kdepim-runtime/resources/pop3/CMakeLists.txt | 72 + kdepim-runtime/resources/pop3/Messages.sh | 3 + kdepim-runtime/resources/pop3/TODO | 49 + .../resources/pop3/accountdialog.cpp | 690 ++ kdepim-runtime/resources/pop3/accountdialog.h | 85 + kdepim-runtime/resources/pop3/jobs.cpp | 507 + kdepim-runtime/resources/pop3/jobs.h | 207 + kdepim-runtime/resources/pop3/metatype.h | 29 + .../resources/pop3/pop3resource.cpp | 1039 ++ .../resources/pop3/pop3resource.desktop | 102 + kdepim-runtime/resources/pop3/pop3resource.h | 188 + .../resources/pop3/pop3resourceattribute.cpp | 82 + .../resources/pop3/pop3resourceattribute.h | 45 + kdepim-runtime/resources/pop3/popsettings.ui | 646 ++ kdepim-runtime/resources/pop3/settings.cpp | 82 + kdepim-runtime/resources/pop3/settings.h | 47 + kdepim-runtime/resources/pop3/settings.kcfg | 93 + .../resources/pop3/settingsbase.kcfgc | 8 + .../resources/pop3/tests/CMakeLists.txt | 58 + .../pop3/tests/fakeserver/fakeserver.cpp | 295 + .../pop3/tests/fakeserver/fakeserver.h | 106 + .../resources/pop3/tests/pop3test.cpp | 914 ++ .../resources/pop3/tests/pop3test.h | 73 + .../pop3/tests/unittestenv/config.xml | 6 + .../unittestenv/kdehome/share/apps/.keep | 1 + .../kdehome/share/config/akonadi-firstrunrc | 3 + .../unittestenv/kdehome/share/config/kdebugrc | 103 + .../xdgconfig/akonadi/akonadiserverrc | 4 + .../pop3/tests/unittestenv/xdglocal/.keep | 1 + .../resources/pop3/wizard/CMakeLists.txt | 2 + .../resources/pop3/wizard/Messages.sh | 4 + .../resources/pop3/wizard/pop3wizard.desktop | 105 + .../resources/pop3/wizard/pop3wizard.es | 63 + .../resources/pop3/wizard/pop3wizard.ui | 90 + .../resources/shared/CMakeLists.txt | 2 + kdepim-runtime/resources/shared/Messages.sh | 4 + .../shared/collectionannotationsattribute.cpp | 94 + .../shared/collectionannotationsattribute.h | 47 + .../shared/collectionflagsattribute.cpp | 67 + .../shared/collectionflagsattribute.h | 44 + .../resources/shared/createandsettagsjob.cpp | 71 + .../resources/shared/createandsettagsjob.h | 45 + .../resources/shared/dirsettingsdialog.cpp | 77 + .../resources/shared/dirsettingsdialog.h | 48 + .../resources/shared/filestore/CMakeLists.txt | 52 + .../resources/shared/filestore/Messages.sh | 2 + .../shared/filestore/abstractlocalstore.cpp | 853 ++ .../shared/filestore/abstractlocalstore.h | 126 + .../filestore/akonadi-filestore_export.h | 41 + .../shared/filestore/collectioncreatejob.cpp | 80 + .../shared/filestore/collectioncreatejob.h | 66 + .../shared/filestore/collectiondeletejob.cpp | 73 + .../shared/filestore/collectiondeletejob.h | 64 + .../shared/filestore/collectionfetchjob.cpp | 103 + .../shared/filestore/collectionfetchjob.h | 83 + .../shared/filestore/collectionmodifyjob.cpp | 73 + .../shared/filestore/collectionmodifyjob.h | 64 + .../shared/filestore/collectionmovejob.cpp | 80 + .../shared/filestore/collectionmovejob.h | 66 + .../entitycompactchangeattribute.cpp | 112 + .../filestore/entitycompactchangeattribute.h | 71 + .../shared/filestore/itemcreatejob.cpp | 77 + .../shared/filestore/itemcreatejob.h | 66 + .../shared/filestore/itemdeletejob.cpp | 70 + .../shared/filestore/itemdeletejob.h | 63 + .../shared/filestore/itemfetchjob.cpp | 107 + .../resources/shared/filestore/itemfetchjob.h | 79 + .../shared/filestore/itemmodifyjob.cpp | 92 + .../shared/filestore/itemmodifyjob.h | 71 + .../shared/filestore/itemmovejob.cpp | 78 + .../resources/shared/filestore/itemmovejob.h | 67 + .../resources/shared/filestore/job.cpp | 59 + .../resources/shared/filestore/job.h | 110 + .../resources/shared/filestore/session.cpp | 145 + .../resources/shared/filestore/session_p.h | 89 + .../shared/filestore/sessionimpls.cpp | 203 + .../shared/filestore/sessionimpls_p.h | 61 + .../shared/filestore/storecompactjob.cpp | 80 + .../shared/filestore/storecompactjob.h | 71 + .../shared/filestore/storeinterface.h | 89 + .../shared/filestore/tests/CMakeLists.txt | 27 + .../tests/abstractlocalstoretest.cpp | 969 ++ .../resources/shared/getcredentialsjob.cpp | 104 + .../resources/shared/getcredentialsjob.h | 61 + .../resources/shared/imapaclattribute.cpp | 125 + .../resources/shared/imapaclattribute.h | 52 + .../resources/shared/imapquotaattribute.cpp | 193 + .../resources/shared/imapquotaattribute.h | 58 + .../resources/shared/singlefileresource.h | 365 + .../shared/singlefileresourcebase.cpp | 280 + .../resources/shared/singlefileresourcebase.h | 188 + .../shared/singlefileresourceconfigdialog.h | 59 + .../shared/singlefileresourceconfigdialog.ui | 22 + .../singlefileresourceconfigdialog_desktop.ui | 181 + .../singlefileresourceconfigdialog_mobile.ui | 108 + .../singlefileresourceconfigdialogbase.cpp | 229 + .../singlefileresourceconfigdialogbase.h | 144 + .../resources/shared/tests/CMakeLists.txt | 10 + .../collectionannotationattributetest.cpp | 85 + .../shared/tests/imapaclattributetest.cpp | 110 + kdepim-runtime/resources/vcard/CMakeLists.txt | 38 + kdepim-runtime/resources/vcard/Messages.sh | 3 + kdepim-runtime/resources/vcard/settings.kcfgc | 9 + .../resources/vcard/tests/CMakeLists.txt | 6 + .../vcard/tests/vcardtest-readonly.js | 12 + .../vcard/tests/vcardtest-readonly.xml | 24 + .../resources/vcard/tests/vcardtest.js | 11 + .../resources/vcard/tests/vcardtest.vcf | 15 + .../resources/vcard/tests/vcardtest.xml | 24 + .../resources/vcard/vcardresource.cpp | 189 + .../resources/vcard/vcardresource.desktop | 88 + .../resources/vcard/vcardresource.h | 60 + .../resources/vcard/vcardresource.kcfg | 26 + .../resources/vcard/wizard/CMakeLists.txt | 4 + .../resources/vcard/wizard/Messages.sh | 4 + .../vcard/wizard/vcardwizard.desktop | 73 + .../vcard/wizard/vcardwizard.es.cmake | 43 + .../resources/vcard/wizard/vcardwizard.ui | 45 + .../resources/vcarddir/CMakeLists.txt | 44 + kdepim-runtime/resources/vcarddir/Messages.sh | 3 + .../resources/vcarddir/settings.kcfgc | 7 + .../resources/vcarddir/settingsdialog.ui | 221 + .../resources/vcarddir/vcarddirresource.cpp | 278 + .../vcarddir/vcarddirresource.desktop | 86 + .../resources/vcarddir/vcarddirresource.h | 61 + .../resources/vcarddir/vcarddirresource.kcfg | 22 + .../resources/vcarddir/wizard/CMakeLists.txt | 4 + .../resources/vcarddir/wizard/Messages.sh | 4 + .../vcarddir/wizard/vcarddirwizard.desktop | 73 + .../vcarddir/wizard/vcarddirwizard.es.cmake | 43 + .../vcarddir/wizard/vcarddirwizard.ui | 45 + kdepim-runtime/resourcetester/CMakeLists.txt | 37 + .../resourcetester/collectiontest.cpp | 99 + .../resourcetester/collectiontest.h | 53 + kdepim-runtime/resourcetester/global.cpp | 76 + kdepim-runtime/resourcetester/global.h | 37 + kdepim-runtime/resourcetester/itemtest.cpp | 82 + kdepim-runtime/resourcetester/itemtest.h | 53 + kdepim-runtime/resourcetester/main.cpp | 98 + kdepim-runtime/resourcetester/qemu.cpp | 204 + kdepim-runtime/resourcetester/qemu.h | 55 + kdepim-runtime/resourcetester/resource.cpp | 156 + kdepim-runtime/resourcetester/resource.h | 60 + kdepim-runtime/resourcetester/script.cpp | 69 + kdepim-runtime/resourcetester/script.h | 44 + kdepim-runtime/resourcetester/system.cpp | 45 + kdepim-runtime/resourcetester/system.h | 36 + kdepim-runtime/resourcetester/test.cpp | 80 + kdepim-runtime/resourcetester/test.h | 44 + .../resourcetester/tests/CMakeLists.txt | 1 + .../resourcetester/tests/construct.es | 12 + .../tests/unittestenv/config-mysql-db.xml | 6 + .../tests/unittestenv/config.xml | 8 + .../kdehome/share/apps/kwallet/kdewallet.kwl | Bin 0 -> 288 bytes .../kdehome/share/config/akonadi-firstrunrc | 3 + .../unittestenv/kdehome/share/config/kdebugrc | 78 + .../unittestenv/kdehome/share/config/kdedrc | 3 + .../kdehome/share/config/kwalletrc | 8 + .../akonadi/akonadiserverrc | 5 + .../resourcetester/wrappedobject.cpp | 22 + kdepim-runtime/resourcetester/wrappedobject.h | 40 + .../resourcetester/xmloperations.cpp | 428 + kdepim-runtime/resourcetester/xmloperations.h | 179 + kdepim-runtime/tray/CMakeLists.txt | 22 + kdepim-runtime/tray/Messages.sh | 2 + kdepim-runtime/tray/akonaditray.desktop | 101 + kdepim-runtime/tray/backup.cpp | 163 + kdepim-runtime/tray/backup.h | 56 + kdepim-runtime/tray/backupassistant.cpp | 115 + kdepim-runtime/tray/backupassistant.h | 55 + kdepim-runtime/tray/dock.cpp | 211 + kdepim-runtime/tray/dock.h | 88 + kdepim-runtime/tray/global.cpp | 106 + kdepim-runtime/tray/global.h | 61 + kdepim-runtime/tray/icons/CMakeLists.txt | 1 + .../tray/icons/hi128-app-akonaditray.png | Bin 0 -> 22934 bytes .../tray/icons/hi22-app-akonaditray.png | Bin 0 -> 1487 bytes .../tray/icons/hi32-app-akonaditray.png | Bin 0 -> 2530 bytes .../tray/icons/hi64-app-akonaditray.png | Bin 0 -> 7355 bytes .../tray/icons/hisc-app-akonaditray.svgz | Bin 0 -> 10294 bytes kdepim-runtime/tray/main.cpp | 75 + .../tray/org.freedesktop.akonaditray.xml | 22 + kdepim-runtime/tray/restore.cpp | 164 + kdepim-runtime/tray/restore.h | 56 + kdepim-runtime/tray/restoreassistant.cpp | 104 + kdepim-runtime/tray/restoreassistant.h | 55 + 1533 files changed, 191508 insertions(+) create mode 100644 kdepim-runtime/CMakeLists.txt create mode 100644 kdepim-runtime/COPYING create mode 100644 kdepim-runtime/COPYING.LIB create mode 100644 kdepim-runtime/CTestConfig.cmake create mode 100644 kdepim-runtime/CTestCustom.cmake create mode 100644 kdepim-runtime/Mainpage.dox create mode 100644 kdepim-runtime/README create mode 100644 kdepim-runtime/accountwizard/CMakeLists.txt create mode 100644 kdepim-runtime/accountwizard/HOWTO create mode 100644 kdepim-runtime/accountwizard/Messages.sh create mode 100644 kdepim-runtime/accountwizard/TODO create mode 100644 kdepim-runtime/accountwizard/accountwizard-mime.xml create mode 100644 kdepim-runtime/accountwizard/accountwizard.desktop create mode 100644 kdepim-runtime/accountwizard/accountwizard.knsrc create mode 100644 kdepim-runtime/accountwizard/configfile.cpp create mode 100644 kdepim-runtime/accountwizard/configfile.h create mode 100644 kdepim-runtime/accountwizard/dialog.cpp create mode 100644 kdepim-runtime/accountwizard/dialog.h create mode 100644 kdepim-runtime/accountwizard/dynamicpage.cpp create mode 100644 kdepim-runtime/accountwizard/dynamicpage.h create mode 100644 kdepim-runtime/accountwizard/global.cpp create mode 100644 kdepim-runtime/accountwizard/global.h create mode 100644 kdepim-runtime/accountwizard/identity.cpp create mode 100644 kdepim-runtime/accountwizard/identity.h create mode 100644 kdepim-runtime/accountwizard/inprocess-main.cpp create mode 100644 kdepim-runtime/accountwizard/ispdb/CMakeLists.txt create mode 100644 kdepim-runtime/accountwizard/ispdb/ispdb.cpp create mode 100644 kdepim-runtime/accountwizard/ispdb/ispdb.h create mode 100644 kdepim-runtime/accountwizard/ispdb/main.cpp create mode 100644 kdepim-runtime/accountwizard/ldap.cpp create mode 100644 kdepim-runtime/accountwizard/ldap.h create mode 100644 kdepim-runtime/accountwizard/loadpage.cpp create mode 100644 kdepim-runtime/accountwizard/loadpage.h create mode 100644 kdepim-runtime/accountwizard/main.cpp create mode 100644 kdepim-runtime/accountwizard/page.cpp create mode 100644 kdepim-runtime/accountwizard/page.h create mode 100644 kdepim-runtime/accountwizard/personaldatapage.cpp create mode 100644 kdepim-runtime/accountwizard/personaldatapage.h create mode 100644 kdepim-runtime/accountwizard/providerpage.cpp create mode 100644 kdepim-runtime/accountwizard/providerpage.h create mode 100644 kdepim-runtime/accountwizard/resource.cpp create mode 100644 kdepim-runtime/accountwizard/resource.h create mode 100644 kdepim-runtime/accountwizard/servertest.cpp create mode 100644 kdepim-runtime/accountwizard/servertest.h create mode 100644 kdepim-runtime/accountwizard/setupmanager.cpp create mode 100644 kdepim-runtime/accountwizard/setupmanager.h create mode 100644 kdepim-runtime/accountwizard/setupobject.cpp create mode 100644 kdepim-runtime/accountwizard/setupobject.h create mode 100644 kdepim-runtime/accountwizard/setuppage.cpp create mode 100644 kdepim-runtime/accountwizard/setuppage.h create mode 100644 kdepim-runtime/accountwizard/transport.cpp create mode 100644 kdepim-runtime/accountwizard/transport.h create mode 100644 kdepim-runtime/accountwizard/typepage.cpp create mode 100644 kdepim-runtime/accountwizard/typepage.h create mode 100644 kdepim-runtime/accountwizard/ui/loadpage.ui create mode 100644 kdepim-runtime/accountwizard/ui/personaldatapage.ui create mode 100644 kdepim-runtime/accountwizard/ui/providerpage.ui create mode 100644 kdepim-runtime/accountwizard/ui/setuppage.ui create mode 100644 kdepim-runtime/accountwizard/ui/typepage.ui create mode 100644 kdepim-runtime/accountwizard/wizards/CMakeLists.txt create mode 100644 kdepim-runtime/accountwizard/wizards/tine20/CMakeLists.txt create mode 100644 kdepim-runtime/accountwizard/wizards/tine20/Messages.sh create mode 100644 kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.desktop create mode 100644 kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.es create mode 100644 kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.ui create mode 100644 kdepim-runtime/agents/.krazy create mode 100644 kdepim-runtime/agents/CMakeLists.txt create mode 100644 kdepim-runtime/agents/Info.plist.template create mode 100644 kdepim-runtime/agents/Mainpage.dox create mode 100644 kdepim-runtime/agents/akonadinepomukfeederagent.desktop create mode 100644 kdepim-runtime/agents/invitations/CMakeLists.txt create mode 100755 kdepim-runtime/agents/invitations/Messages.sh create mode 100644 kdepim-runtime/agents/invitations/incidenceattribute.cpp create mode 100644 kdepim-runtime/agents/invitations/incidenceattribute.h create mode 100644 kdepim-runtime/agents/invitations/invitationsagent.cpp create mode 100644 kdepim-runtime/agents/invitations/invitationsagent.desktop create mode 100644 kdepim-runtime/agents/invitations/invitationsagent.h create mode 100644 kdepim-runtime/agents/maildispatcher/CMakeLists.txt create mode 100755 kdepim-runtime/agents/maildispatcher/Messages.sh create mode 100644 kdepim-runtime/agents/maildispatcher/TODO create mode 100644 kdepim-runtime/agents/maildispatcher/akonadi_maildispatcher_agent.notifyrc create mode 100644 kdepim-runtime/agents/maildispatcher/configdialog.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/configdialog.h create mode 100644 kdepim-runtime/agents/maildispatcher/maildispatcheragent.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/maildispatcheragent.desktop create mode 100644 kdepim-runtime/agents/maildispatcher/maildispatcheragent.h create mode 100644 kdepim-runtime/agents/maildispatcher/maildispatcheragent.kcfg create mode 100644 kdepim-runtime/agents/maildispatcher/org.freedesktop.Akonadi.MailDispatcherAgent.xml create mode 100644 kdepim-runtime/agents/maildispatcher/outboxqueue.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/outboxqueue.h create mode 100644 kdepim-runtime/agents/maildispatcher/sendjob.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/sendjob.h create mode 100644 kdepim-runtime/agents/maildispatcher/sentactionhandler.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/sentactionhandler.h create mode 100644 kdepim-runtime/agents/maildispatcher/settings.kcfgc create mode 100644 kdepim-runtime/agents/maildispatcher/settings.ui create mode 100644 kdepim-runtime/agents/maildispatcher/storeresultjob.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/storeresultjob.h create mode 100644 kdepim-runtime/agents/maildispatcher/tests/CMakeLists.txt create mode 100644 kdepim-runtime/agents/maildispatcher/tests/TODO create mode 100644 kdepim-runtime/agents/maildispatcher/tests/aborttest.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/tests/aborttest.h create mode 100644 kdepim-runtime/agents/maildispatcher/tests/dupetest.cpp create mode 100644 kdepim-runtime/agents/maildispatcher/tests/dupetest.h create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/config.xml create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_mailtransport_dummy_resource_0rc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdebugrc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdedrc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kwalletrc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/mailtransports create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/qttestrc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/testdata.xml create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdglocal/.keep create mode 100644 kdepim-runtime/agents/migration/CMakeLists.txt create mode 100755 kdepim-runtime/agents/migration/Messages.sh create mode 100644 kdepim-runtime/agents/migration/migrationagent.cpp create mode 100644 kdepim-runtime/agents/migration/migrationagent.desktop create mode 100644 kdepim-runtime/agents/migration/migrationagent.h create mode 100644 kdepim-runtime/agents/migration/migrationexecutor.cpp create mode 100644 kdepim-runtime/agents/migration/migrationexecutor.h create mode 100644 kdepim-runtime/agents/migration/migrationscheduler.cpp create mode 100644 kdepim-runtime/agents/migration/migrationscheduler.h create mode 100644 kdepim-runtime/agents/migration/migrationstatuswidget.cpp create mode 100644 kdepim-runtime/agents/migration/migrationstatuswidget.h create mode 100644 kdepim-runtime/agents/migration/tests/CMakeLists.txt create mode 100644 kdepim-runtime/agents/migration/tests/dummymigrator.cpp create mode 100644 kdepim-runtime/agents/migration/tests/dummymigrator.h create mode 100644 kdepim-runtime/agents/migration/tests/schedulertest.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/CMakeLists.txt create mode 100755 kdepim-runtime/agents/newmailnotifier/Messages.sh create mode 100644 kdepim-runtime/agents/newmailnotifier/NEWS create mode 100644 kdepim-runtime/agents/newmailnotifier/TODO create mode 100644 kdepim-runtime/agents/newmailnotifier/akonadi_newmailnotifier_agent.notifyrc create mode 100644 kdepim-runtime/agents/newmailnotifier/kconf_update/CMakeLists.txt create mode 100644 kdepim-runtime/agents/newmailnotifier/kconf_update/newmailnotifier.upd create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifieragent.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifieragent.desktop create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifieragent.h create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfg create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfgc create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.h create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.h create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.h create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifiershowmessagejob.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/newmailnotifiershowmessagejob.h create mode 100644 kdepim-runtime/agents/newmailnotifier/org.freedesktop.Akonadi.NewMailNotifier.xml create mode 100644 kdepim-runtime/agents/newmailnotifier/specialnotifierjob.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/specialnotifierjob.h create mode 100644 kdepim-runtime/agents/newmailnotifier/util.cpp create mode 100644 kdepim-runtime/agents/newmailnotifier/util.h create mode 100644 kdepim-runtime/akonadi-prefix.h.cmake create mode 100644 kdepim-runtime/akonadi-version.h.cmake create mode 100644 kdepim-runtime/cmake/modules/FindXsltproc.cmake create mode 100644 kdepim-runtime/config-enterprise.h.cmake create mode 100644 kdepim-runtime/defaultsetup/CMakeLists.txt create mode 100644 kdepim-runtime/defaultsetup/defaultaddressbook.desktop create mode 100644 kdepim-runtime/defaultsetup/defaultcalendar.desktop create mode 100644 kdepim-runtime/defaultsetup/defaultnotebook.desktop create mode 100644 kdepim-runtime/doc/git-migration.txt create mode 100644 kdepim-runtime/doc/libakonadi.xmi create mode 100644 kdepim-runtime/doc/pics/akonadi_agent_handling.eps create mode 100644 kdepim-runtime/doc/pics/akonadi_agent_handling.png create mode 100644 kdepim-runtime/doc/pics/akonadi_agent_handling_small.png create mode 100644 kdepim-runtime/doc/pics/akonadi_client_search.eps create mode 100644 kdepim-runtime/doc/pics/akonadi_client_search.png create mode 100644 kdepim-runtime/doc/pics/akonadi_client_search_small.png create mode 100644 kdepim-runtime/doc/pics/akonadi_communication.xmi create mode 100644 kdepim-runtime/doc/pics/akonadi_concept_schema.sla create mode 100644 kdepim-runtime/doc/pics/akonadi_overview_uml.png create mode 100644 kdepim-runtime/doc/pics/akonadi_overview_uml.ps create mode 100644 kdepim-runtime/doc/pics/akonadi_overview_uml_small.png create mode 100644 kdepim-runtime/doc/pics/concept.eps create mode 100644 kdepim-runtime/doc/pics/concept.png create mode 100644 kdepim-runtime/doc/pics/concept.sla create mode 100755 kdepim-runtime/doc/pics/convert.sh create mode 100644 kdepim-runtime/doc/todo.dox create mode 100644 kdepim-runtime/kcm/CMakeLists.txt create mode 100644 kdepim-runtime/kcm/Mainpage.dox create mode 100644 kdepim-runtime/kcm/Messages.sh create mode 100644 kdepim-runtime/kcm/akonadiconfigmodule.cpp create mode 100644 kdepim-runtime/kcm/akonadiconfigmodule.h create mode 100644 kdepim-runtime/kcm/configmodule.cpp create mode 100644 kdepim-runtime/kcm/configmodule.h create mode 100644 kdepim-runtime/kcm/kcm_akonadi.desktop create mode 100644 kdepim-runtime/kcm/kcm_akonadi_resources.desktop create mode 100644 kdepim-runtime/kcm/kcm_akonadi_server.desktop create mode 100644 kdepim-runtime/kcm/resourcesmanagementwidget.cpp create mode 100644 kdepim-runtime/kcm/resourcesmanagementwidget.h create mode 100644 kdepim-runtime/kcm/resourcesmanagementwidget.ui create mode 100644 kdepim-runtime/kcm/serverconfigmodule.cpp create mode 100644 kdepim-runtime/kcm/serverconfigmodule.h create mode 100644 kdepim-runtime/kcm/serverconfigmodule.ui create mode 100644 kdepim-runtime/kcm/servermysqlstorage.ui create mode 100644 kdepim-runtime/kcm/serverpsqlstorage.ui create mode 100644 kdepim-runtime/kcm/serverstoragedriver.ui create mode 100644 kdepim-runtime/kdepim-mime.xml create mode 100644 kdepim-runtime/kdepim-runtime-version.h.cmake create mode 100644 kdepim-runtime/kioslave/CMakeLists.txt create mode 100644 kdepim-runtime/kioslave/Messages.sh create mode 100644 kdepim-runtime/kioslave/akonadi.protocol create mode 100644 kdepim-runtime/kioslave/akonadislave.cpp create mode 100644 kdepim-runtime/kioslave/akonadislave.h create mode 100644 kdepim-runtime/kresources/CMakeLists.txt create mode 100644 kdepim-runtime/kresources/kabc/CMakeLists.txt create mode 100644 kdepim-runtime/kresources/kabc/Messages.sh create mode 100644 kdepim-runtime/kresources/kabc/akonadi.desktop create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadi.cpp create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadi.h create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadi_p.cpp create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadi_p.h create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadiconfig.cpp create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadiconfig.h create mode 100644 kdepim-runtime/kresources/kabc/resourceakonadiplugin.cpp create mode 100644 kdepim-runtime/kresources/kabc/subresource.cpp create mode 100644 kdepim-runtime/kresources/kabc/subresource.h create mode 100644 kdepim-runtime/kresources/kcal/CMakeLists.txt create mode 100644 kdepim-runtime/kresources/kcal/Messages.sh create mode 100644 kdepim-runtime/kresources/kcal/akonadi.desktop create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadi.cpp create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadi.h create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadi_p.cpp create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadi_p.h create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadiconfig.cpp create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadiconfig.h create mode 100644 kdepim-runtime/kresources/kcal/resourceakonadiplugin.cpp create mode 100644 kdepim-runtime/kresources/kcal/subresource.cpp create mode 100644 kdepim-runtime/kresources/kcal/subresource.h create mode 100644 kdepim-runtime/kresources/shared/Messages.sh create mode 100644 kdepim-runtime/kresources/shared/abstractsubresourcemodel.cpp create mode 100644 kdepim-runtime/kresources/shared/abstractsubresourcemodel.h create mode 100644 kdepim-runtime/kresources/shared/concurrentjobs.cpp create mode 100644 kdepim-runtime/kresources/shared/concurrentjobs.h create mode 100644 kdepim-runtime/kresources/shared/idarbiterbase.cpp create mode 100644 kdepim-runtime/kresources/shared/idarbiterbase.h create mode 100644 kdepim-runtime/kresources/shared/itemfetchadapter.cpp create mode 100644 kdepim-runtime/kresources/shared/itemfetchadapter.h create mode 100644 kdepim-runtime/kresources/shared/itemsavecontext.h create mode 100644 kdepim-runtime/kresources/shared/itemsavejob.cpp create mode 100644 kdepim-runtime/kresources/shared/itemsavejob.h create mode 100644 kdepim-runtime/kresources/shared/resourceconfigbase.cpp create mode 100644 kdepim-runtime/kresources/shared/resourceconfigbase.h create mode 100644 kdepim-runtime/kresources/shared/resourceprivatebase.cpp create mode 100644 kdepim-runtime/kresources/shared/resourceprivatebase.h create mode 100644 kdepim-runtime/kresources/shared/sharedresourceiface.h create mode 100644 kdepim-runtime/kresources/shared/sharedresourceprivate.h create mode 100644 kdepim-runtime/kresources/shared/storecollectiondialog.cpp create mode 100644 kdepim-runtime/kresources/shared/storecollectiondialog.h create mode 100644 kdepim-runtime/kresources/shared/storecollectionfilterproxymodel.cpp create mode 100644 kdepim-runtime/kresources/shared/storecollectionfilterproxymodel.h create mode 100644 kdepim-runtime/kresources/shared/storecollectionmodel.cpp create mode 100644 kdepim-runtime/kresources/shared/storecollectionmodel.h create mode 100644 kdepim-runtime/kresources/shared/storeconfigiface.h create mode 100644 kdepim-runtime/kresources/shared/subresourcebase.cpp create mode 100644 kdepim-runtime/kresources/shared/subresourcebase.h create mode 100644 kdepim-runtime/kresources/shared/subresourcemodel.h create mode 100644 kdepim-runtime/libkdepim-copy/CMakeLists.txt create mode 100644 kdepim-runtime/libkdepim-copy/Messages.sh create mode 100644 kdepim-runtime/libkdepim-copy/calendardiffalgo.cpp create mode 100644 kdepim-runtime/libkdepim-copy/calendardiffalgo.h create mode 100644 kdepim-runtime/libkdepim-copy/diffalgo.cpp create mode 100644 kdepim-runtime/libkdepim-copy/diffalgo.h create mode 100644 kdepim-runtime/libkdepim-copy/htmldiffalgodisplay.cpp create mode 100644 kdepim-runtime/libkdepim-copy/htmldiffalgodisplay.h create mode 100644 kdepim-runtime/libkdepim-copy/kincidencechooser.cpp create mode 100644 kdepim-runtime/libkdepim-copy/kincidencechooser.h create mode 100644 kdepim-runtime/libkdepim-copy/libkdepim-copy_export.h create mode 100644 kdepim-runtime/libkdepim-copy/tests/CMakeLists.txt create mode 100644 kdepim-runtime/libkdepim-copy/tests/testkincidencechooser.cpp create mode 100644 kdepim-runtime/migration/CMakeLists.txt create mode 100644 kdepim-runtime/migration/entitytreecreatejob.cpp create mode 100644 kdepim-runtime/migration/entitytreecreatejob.h create mode 100644 kdepim-runtime/migration/gid/CMakeLists.txt create mode 100644 kdepim-runtime/migration/gid/Messages.sh create mode 100644 kdepim-runtime/migration/gid/gidmigrationjob.cpp create mode 100644 kdepim-runtime/migration/gid/gidmigrationjob.h create mode 100644 kdepim-runtime/migration/gid/gidmigrator.cpp create mode 100644 kdepim-runtime/migration/gid/gidmigrator.h create mode 100644 kdepim-runtime/migration/gid/main.cpp create mode 100644 kdepim-runtime/migration/infodialog.cpp create mode 100644 kdepim-runtime/migration/infodialog.h create mode 100644 kdepim-runtime/migration/kaddressbook/CMakeLists.txt create mode 100644 kdepim-runtime/migration/kaddressbook/Messages.sh create mode 100644 kdepim-runtime/migration/kaddressbook/kaddressbookmigrator.cpp create mode 100644 kdepim-runtime/migration/kaddressbook/kaddressbookmigrator.desktop create mode 100644 kdepim-runtime/migration/kjots/CMakeLists.txt create mode 100644 kdepim-runtime/migration/kjots/Messages.sh create mode 100644 kdepim-runtime/migration/kjots/kjotsmigrator.cpp create mode 100644 kdepim-runtime/migration/kjots/kjotsmigrator.h create mode 100644 kdepim-runtime/migration/kjots/main.cpp create mode 100644 kdepim-runtime/migration/kjots/metatype.h create mode 100644 kdepim-runtime/migration/kmail/CMakeLists.txt create mode 100644 kdepim-runtime/migration/kmail/Messages.sh create mode 100644 kdepim-runtime/migration/kmail/abstractcollectionmigrator.cpp create mode 100644 kdepim-runtime/migration/kmail/abstractcollectionmigrator.h create mode 100644 kdepim-runtime/migration/kmail/emptyresourcecleaner.cpp create mode 100644 kdepim-runtime/migration/kmail/emptyresourcecleaner.h create mode 100644 kdepim-runtime/migration/kmail/imapcacheadapter.cpp create mode 100644 kdepim-runtime/migration/kmail/imapcacheadapter.h create mode 100644 kdepim-runtime/migration/kmail/imapcachecollectionmigrator.cpp create mode 100644 kdepim-runtime/migration/kmail/imapcachecollectionmigrator.h create mode 100644 kdepim-runtime/migration/kmail/imapcachelocalimporter.cpp create mode 100644 kdepim-runtime/migration/kmail/imapcachelocalimporter.h create mode 100644 kdepim-runtime/migration/kmail/kmail-migratorrc create mode 100644 kdepim-runtime/migration/kmail/kmailmigrator.cpp create mode 100644 kdepim-runtime/migration/kmail/kmailmigrator.h create mode 100644 kdepim-runtime/migration/kmail/localfolderscollectionmigrator.cpp create mode 100644 kdepim-runtime/migration/kmail/localfolderscollectionmigrator.h create mode 100644 kdepim-runtime/migration/kmail/main.cpp create mode 100644 kdepim-runtime/migration/kmail/messagetag.trig create mode 100644 kdepim-runtime/migration/kmail/metatype.h create mode 100644 kdepim-runtime/migration/kmail/mixedtreeconverter.cpp create mode 100644 kdepim-runtime/migration/kmail/mixedtreeconverter.h create mode 100644 kdepim-runtime/migration/kmail/subscriptionjob_p.h create mode 100644 kdepim-runtime/migration/kmigratorbase.cpp create mode 100644 kdepim-runtime/migration/kmigratorbase.h create mode 100644 kdepim-runtime/migration/knotes/CMakeLists.txt create mode 100644 kdepim-runtime/migration/knotes/Messages.sh create mode 100644 kdepim-runtime/migration/knotes/knoteconfig.kcfg create mode 100644 kdepim-runtime/migration/knotes/knoteconfig.kcfgc create mode 100644 kdepim-runtime/migration/knotes/knotesmigrator.cpp create mode 100644 kdepim-runtime/migration/knotes/knotesmigrator.h create mode 100644 kdepim-runtime/migration/knotes/knotesmigratorconfig.cpp create mode 100644 kdepim-runtime/migration/knotes/knotesmigratorconfig.h create mode 100644 kdepim-runtime/migration/knotes/main.cpp create mode 100644 kdepim-runtime/migration/knotes/notealarmattribute.cpp create mode 100644 kdepim-runtime/migration/knotes/notealarmattribute.h create mode 100644 kdepim-runtime/migration/knotes/notedisplayattribute.cpp create mode 100644 kdepim-runtime/migration/knotes/notedisplayattribute.h create mode 100644 kdepim-runtime/migration/knotes/notelockattribute.cpp create mode 100644 kdepim-runtime/migration/knotes/notelockattribute.h create mode 100644 kdepim-runtime/migration/knotes/showfoldernotesattribute.cpp create mode 100644 kdepim-runtime/migration/knotes/showfoldernotesattribute.h create mode 100644 kdepim-runtime/migration/kres/CMakeLists.txt create mode 100644 kdepim-runtime/migration/kres/Messages.sh create mode 100644 kdepim-runtime/migration/kres/kabcmigrator.cpp create mode 100644 kdepim-runtime/migration/kres/kabcmigrator.h create mode 100644 kdepim-runtime/migration/kres/kcalmigrator.cpp create mode 100644 kdepim-runtime/migration/kres/kcalmigrator.h create mode 100644 kdepim-runtime/migration/kres/kres-migratorrc create mode 100644 kdepim-runtime/migration/kres/kresmigrator.h create mode 100644 kdepim-runtime/migration/kres/kresmigratorbase.cpp create mode 100644 kdepim-runtime/migration/kres/kresmigratorbase.h create mode 100644 kdepim-runtime/migration/kres/main.cpp create mode 100644 kdepim-runtime/migration/kres/metatype.h create mode 100644 kdepim-runtime/migration/migratorbase.cpp create mode 100644 kdepim-runtime/migration/migratorbase.h create mode 100644 kdepim-runtime/migration/tests/CMakeLists.txt create mode 100644 kdepim-runtime/migration/tests/testgidmigration.cpp create mode 100644 kdepim-runtime/migration/tests/testgidmigration.h create mode 100644 kdepim-runtime/migration/tests/testmigratorbase.cpp create mode 100644 kdepim-runtime/migration/tests/testnotesmigration.cpp create mode 100644 kdepim-runtime/migration/tests/testnotesmigration.h create mode 100644 kdepim-runtime/migration/tests/unittestenv/config-sqlite-db.xml create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/N23552.book create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/f23552.book create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/k21863.book create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_TDHwQa.book create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_hu4mua.book create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes.ics create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-2098450417.977 create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-297296717.52 create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdebugrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdedrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kres-migratorrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kresources/notes/stdrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kwalletrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/qttestrc create mode 100644 kdepim-runtime/migration/tests/unittestenv/kdehome/testdata-res1.xml create mode 100644 kdepim-runtime/migration/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/opensync/CMakeLists.txt create mode 100644 kdepim-runtime/opensync/akonadi-sync create mode 100644 kdepim-runtime/opensync/akonadi_opensync.cpp create mode 100644 kdepim-runtime/opensync/akonadisink.cpp create mode 100644 kdepim-runtime/opensync/akonadisink.h create mode 100644 kdepim-runtime/opensync/datasink.cpp create mode 100644 kdepim-runtime/opensync/datasink.h create mode 100644 kdepim-runtime/opensync/sinkbase.cpp create mode 100644 kdepim-runtime/opensync/sinkbase.h create mode 100644 kdepim-runtime/plugins/CMakeLists.txt create mode 100644 kdepim-runtime/plugins/Messages.sh create mode 100644 kdepim-runtime/plugins/akonadi_serializer_addressee.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_addressee.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_addressee.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_bookmark.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_bookmark.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_bookmark.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_contactgroup.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_contactgroup.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_contactgroup.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kalarm.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kalarm.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kalarm.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kcal.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kcal.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kcal.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kcalcore.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kcalcore.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_kcalcore.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_mail.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_mail.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_mail.h create mode 100644 kdepim-runtime/plugins/akonadi_serializer_microblog.cpp create mode 100644 kdepim-runtime/plugins/akonadi_serializer_microblog.desktop create mode 100644 kdepim-runtime/plugins/akonadi_serializer_microblog.h create mode 100644 kdepim-runtime/plugins/kaeventformatter.cpp create mode 100644 kdepim-runtime/plugins/kaeventformatter.h create mode 100644 kdepim-runtime/plugins/tests/CMakeLists.txt create mode 100644 kdepim-runtime/plugins/tests/addresseeserializertest.cpp create mode 100644 kdepim-runtime/plugins/tests/kcalcoreserializertest.cpp create mode 100644 kdepim-runtime/plugins/tests/kcalserializertest.cpp create mode 100644 kdepim-runtime/plugins/tests/mailserializerplugintest.cpp create mode 100644 kdepim-runtime/plugins/tests/mailserializerplugintest.h create mode 100644 kdepim-runtime/plugins/tests/mailserializertest.cpp create mode 100644 kdepim-runtime/plugins/tests/mailserializertest.h create mode 100644 kdepim-runtime/qml/CMakeLists.txt create mode 100644 kdepim-runtime/qml/Messages.sh create mode 100644 kdepim-runtime/qml/akonadi/AkonadiBreadcrumbNavigationView.qml create mode 100644 kdepim-runtime/qml/akonadi/CMakeLists.txt create mode 100644 kdepim-runtime/qml/akonadi/CollectionDelegate.qml create mode 100644 kdepim-runtime/qml/akonadi/border_dot.png create mode 100644 kdepim-runtime/qml/akonadi/check.png create mode 100644 kdepim-runtime/qml/akonadi/collectionview.qml create mode 100644 kdepim-runtime/qml/akonadi/qmldir create mode 100644 kdepim-runtime/qml/akonadi/sliderbackground.png create mode 100644 kdepim-runtime/qml/akonadi/tests/CMakeLists.txt create mode 100644 kdepim-runtime/qml/akonadi/tests/collectionviewtest.qml create mode 100644 kdepim-runtime/qml/akonadi/tests/qmltest.cpp create mode 100644 kdepim-runtime/qml/akonadi/transparentplus.png create mode 100644 kdepim-runtime/qml/kde/BreadcrumbNavigationView.qml create mode 100644 kdepim-runtime/qml/kde/CMakeLists.txt create mode 100644 kdepim-runtime/qml/kde/Dialog.qml create mode 100644 kdepim-runtime/qml/kde/Flap.qml create mode 100644 kdepim-runtime/qml/kde/Flap2.qml create mode 100644 kdepim-runtime/qml/kde/SlideoutPanel.qml create mode 100644 kdepim-runtime/qml/kde/SlideoutPanelContainer.qml create mode 100644 kdepim-runtime/qml/kde/dividing-line-horizontal.png create mode 100644 kdepim-runtime/qml/kde/dividing-line.png create mode 100644 kdepim-runtime/qml/kde/flap-collapsed-bottom.png create mode 100644 kdepim-runtime/qml/kde/flap-collapsed-mid.png create mode 100644 kdepim-runtime/qml/kde/flap-collapsed-top.png create mode 100644 kdepim-runtime/qml/kde/flap-expanded-bottom.png create mode 100644 kdepim-runtime/qml/kde/flap-expanded-mid.png create mode 100644 kdepim-runtime/qml/kde/flap-expanded-top.png create mode 100644 kdepim-runtime/qml/kde/kdeintegration.cpp create mode 100644 kdepim-runtime/qml/kde/kdeintegration.h create mode 100644 kdepim-runtime/qml/kde/kdeintegrationplugin.cpp create mode 100644 kdepim-runtime/qml/kde/kdeintegrationplugin.h create mode 100644 kdepim-runtime/qml/kde/list-line-top.png create mode 100644 kdepim-runtime/qml/kde/qmldir create mode 100644 kdepim-runtime/qml/kde/qmldir_without_kdeqmlplugin create mode 100644 kdepim-runtime/qml/kde/scrollable-bottom.png create mode 100644 kdepim-runtime/qml/kde/scrollable-top.png create mode 100644 kdepim-runtime/qml/kde/tests/MyItem.qml create mode 100644 kdepim-runtime/qml/kde/tests/SliderComponent.qml create mode 100644 kdepim-runtime/qml/kde/tests/SomeComponent.qml create mode 100644 kdepim-runtime/qml/kde/tests/actionstest.qml create mode 100644 kdepim-runtime/qml/kde/tests/behaviouronx.qml create mode 100644 kdepim-runtime/qml/kde/tests/bindinggroupedproperties.qml create mode 100644 kdepim-runtime/qml/kde/tests/collapsibilesections.qml create mode 100644 kdepim-runtime/qml/kde/tests/disconnect.qml create mode 100644 kdepim-runtime/qml/kde/tests/dragitems-0.qml create mode 100644 kdepim-runtime/qml/kde/tests/elementmodel.qml create mode 100644 kdepim-runtime/qml/kde/tests/i18n.qml create mode 100644 kdepim-runtime/qml/kde/tests/icons.qml create mode 100644 kdepim-runtime/qml/kde/tests/kstandarddirs.qml create mode 100644 kdepim-runtime/qml/kde/tests/mm2px.qml create mode 100644 kdepim-runtime/qml/kde/tests/propagateevent.qml create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/CMakeLists.txt create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/indexfinder.h create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/main.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/mainview.qml create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.h create mode 100644 kdepim-runtime/qml/kde/tests/qml_moves/qml_moves.pro create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/001-change-opacity create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/002-change-opacity-with-braces create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/003-change-opacity-properly create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/004-add-second-breadcrumbitem create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/005-try-to-make-bottom-item-always-visible create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/006-using-apply-range-helps-somewhat create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/CMakeLists.txt create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/ListDelegate.qml create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/backgroundtile.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/border_dot.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigation.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigation.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/FindQt4.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/MacroPushRequiredVars.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4ConfigDependentSettings.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4Macros.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/currentindicator.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dividing-line-horizontal.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dividing-line.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/fuzz.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/indexfinder.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbihash_p.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/list-line-top.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/main.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainview.qml create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/scrollable-bottom.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/scrollable-top.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/selected_bottom.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/selected_top.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/001-assert.patch create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/CMakeLists.txt create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/FindQt4.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/MacroPushRequiredVars.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4ConfigDependentSettings.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4Macros.cmake create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/main.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainview.qml create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.h create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/slideout-panel-background.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/slideout-panel-handle.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/transparentplus.png create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/transparentplus.svg create mode 100644 kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/tree_example create mode 100644 kdepim-runtime/qml/kde/tests/qobject_sender.qml create mode 100644 kdepim-runtime/qml/kde/tests/slideoutpaneltest.qml create mode 100644 kdepim-runtime/qml/kde/tests/synced_sliders.qml create mode 100644 kdepim-runtime/qml/kde/tests/transformorigin.qml create mode 100644 kdepim-runtime/qml/kde/transparentplus.svg create mode 100644 kdepim-runtime/resources/.krazy create mode 100644 kdepim-runtime/resources/CMakeLists.txt create mode 100644 kdepim-runtime/resources/Info.plist.template create mode 100644 kdepim-runtime/resources/Mainpage.dox create mode 100644 kdepim-runtime/resources/akonotes/CMakeLists.txt create mode 100644 kdepim-runtime/resources/akonotes/Messages.sh create mode 100644 kdepim-runtime/resources/akonotes/akonotesresource.cpp create mode 100644 kdepim-runtime/resources/akonotes/akonotesresource.desktop create mode 100644 kdepim-runtime/resources/akonotes/akonotesresource.h create mode 100644 kdepim-runtime/resources/akonotes/main.cpp create mode 100644 kdepim-runtime/resources/birthdays/CMakeLists.txt create mode 100644 kdepim-runtime/resources/birthdays/Messages.sh create mode 100644 kdepim-runtime/resources/birthdays/birthdaysresource.cpp create mode 100644 kdepim-runtime/resources/birthdays/birthdaysresource.desktop create mode 100644 kdepim-runtime/resources/birthdays/birthdaysresource.h create mode 100644 kdepim-runtime/resources/birthdays/birthdaysresource.kcfg create mode 100644 kdepim-runtime/resources/birthdays/configdialog.cpp create mode 100644 kdepim-runtime/resources/birthdays/configdialog.h create mode 100644 kdepim-runtime/resources/birthdays/configdialog.ui create mode 100644 kdepim-runtime/resources/birthdays/settings.kcfgc create mode 100644 kdepim-runtime/resources/contacts/CMakeLists.txt create mode 100644 kdepim-runtime/resources/contacts/Messages.sh create mode 100644 kdepim-runtime/resources/contacts/contactsresource.cpp create mode 100644 kdepim-runtime/resources/contacts/contactsresource.desktop create mode 100644 kdepim-runtime/resources/contacts/contactsresource.h create mode 100644 kdepim-runtime/resources/contacts/contactsresource.kcfg create mode 100644 kdepim-runtime/resources/contacts/settings.kcfgc create mode 100644 kdepim-runtime/resources/contacts/settingsdialog.cpp create mode 100644 kdepim-runtime/resources/contacts/settingsdialog.h create mode 100644 kdepim-runtime/resources/contacts/settingsdialog.ui create mode 100644 kdepim-runtime/resources/contacts/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/contacts/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/contacts/wizard/contactswizard.desktop create mode 100644 kdepim-runtime/resources/contacts/wizard/contactswizard.es.cmake create mode 100644 kdepim-runtime/resources/contacts/wizard/contactswizard.ui create mode 100644 kdepim-runtime/resources/dav/CMakeLists.txt create mode 100644 kdepim-runtime/resources/dav/COPYING create mode 100644 kdepim-runtime/resources/dav/README create mode 100644 kdepim-runtime/resources/dav/TODO create mode 100644 kdepim-runtime/resources/dav/common/davcollection.cpp create mode 100644 kdepim-runtime/resources/dav/common/davcollection.h create mode 100644 kdepim-runtime/resources/dav/common/davcollectiondeletejob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davcollectiondeletejob.h create mode 100644 kdepim-runtime/resources/dav/common/davcollectionmodifyjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davcollectionmodifyjob.h create mode 100644 kdepim-runtime/resources/dav/common/davcollectionsfetchjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davcollectionsfetchjob.h create mode 100644 kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.h create mode 100644 kdepim-runtime/resources/dav/common/davitem.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitem.h create mode 100644 kdepim-runtime/resources/dav/common/davitemcreatejob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitemcreatejob.h create mode 100644 kdepim-runtime/resources/dav/common/davitemdeletejob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitemdeletejob.h create mode 100644 kdepim-runtime/resources/dav/common/davitemfetchjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitemfetchjob.h create mode 100644 kdepim-runtime/resources/dav/common/davitemmodifyjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitemmodifyjob.h create mode 100644 kdepim-runtime/resources/dav/common/davitemsfetchjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitemsfetchjob.h create mode 100644 kdepim-runtime/resources/dav/common/davitemslistjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davitemslistjob.h create mode 100644 kdepim-runtime/resources/dav/common/davjobbase.cpp create mode 100644 kdepim-runtime/resources/dav/common/davjobbase.h create mode 100644 kdepim-runtime/resources/dav/common/davmanager.cpp create mode 100644 kdepim-runtime/resources/dav/common/davmanager.h create mode 100644 kdepim-runtime/resources/dav/common/davmultigetprotocol.cpp create mode 100644 kdepim-runtime/resources/dav/common/davmultigetprotocol.h create mode 100644 kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.h create mode 100644 kdepim-runtime/resources/dav/common/davprincipalsearchjob.cpp create mode 100644 kdepim-runtime/resources/dav/common/davprincipalsearchjob.h create mode 100644 kdepim-runtime/resources/dav/common/davprotocolbase.cpp create mode 100644 kdepim-runtime/resources/dav/common/davprotocolbase.h create mode 100644 kdepim-runtime/resources/dav/common/davutils.cpp create mode 100644 kdepim-runtime/resources/dav/common/davutils.h create mode 100644 kdepim-runtime/resources/dav/common/etagcache.cpp create mode 100644 kdepim-runtime/resources/dav/common/etagcache.h create mode 100644 kdepim-runtime/resources/dav/protocols/caldavprotocol.cpp create mode 100644 kdepim-runtime/resources/dav/protocols/caldavprotocol.h create mode 100644 kdepim-runtime/resources/dav/protocols/carddavprotocol.cpp create mode 100644 kdepim-runtime/resources/dav/protocols/carddavprotocol.h create mode 100644 kdepim-runtime/resources/dav/protocols/groupdavprotocol.cpp create mode 100644 kdepim-runtime/resources/dav/protocols/groupdavprotocol.h create mode 100644 kdepim-runtime/resources/dav/resource/CMakeLists.txt create mode 100644 kdepim-runtime/resources/dav/resource/Messages.sh create mode 100644 kdepim-runtime/resources/dav/resource/akonadi-resources.png create mode 100644 kdepim-runtime/resources/dav/resource/configdialog.cpp create mode 100644 kdepim-runtime/resources/dav/resource/configdialog.h create mode 100644 kdepim-runtime/resources/dav/resource/configdialog.ui create mode 100644 kdepim-runtime/resources/dav/resource/davfreebusyhandler.cpp create mode 100644 kdepim-runtime/resources/dav/resource/davfreebusyhandler.h create mode 100644 kdepim-runtime/resources/dav/resource/davgroupwareprovider.desktop create mode 100644 kdepim-runtime/resources/dav/resource/davgroupwareresource.cpp create mode 100644 kdepim-runtime/resources/dav/resource/davgroupwareresource.desktop create mode 100644 kdepim-runtime/resources/dav/resource/davgroupwareresource.h create mode 100644 kdepim-runtime/resources/dav/resource/davgroupwareresource.kcfg create mode 100644 kdepim-runtime/resources/dav/resource/davprotocolattribute.cpp create mode 100644 kdepim-runtime/resources/dav/resource/davprotocolattribute.h create mode 100644 kdepim-runtime/resources/dav/resource/searchdialog.cpp create mode 100644 kdepim-runtime/resources/dav/resource/searchdialog.h create mode 100644 kdepim-runtime/resources/dav/resource/searchdialog.ui create mode 100644 kdepim-runtime/resources/dav/resource/settings.cpp create mode 100644 kdepim-runtime/resources/dav/resource/settings.h create mode 100644 kdepim-runtime/resources/dav/resource/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/dav/resource/setupwizard.cpp create mode 100644 kdepim-runtime/resources/dav/resource/setupwizard.h create mode 100644 kdepim-runtime/resources/dav/resource/urlconfigurationdialog.cpp create mode 100644 kdepim-runtime/resources/dav/resource/urlconfigurationdialog.h create mode 100644 kdepim-runtime/resources/dav/resource/urlconfigurationdialog.ui create mode 100644 kdepim-runtime/resources/dav/services/citadel.desktop create mode 100644 kdepim-runtime/resources/dav/services/davical.desktop create mode 100644 kdepim-runtime/resources/dav/services/egroupware.desktop create mode 100644 kdepim-runtime/resources/dav/services/opengroupware.desktop create mode 100644 kdepim-runtime/resources/dav/services/owncloud-pre5.desktop create mode 100644 kdepim-runtime/resources/dav/services/owncloud.desktop create mode 100644 kdepim-runtime/resources/dav/services/scalix.desktop create mode 100644 kdepim-runtime/resources/dav/services/sogo.desktop create mode 100644 kdepim-runtime/resources/dav/services/yahoo.desktop create mode 100644 kdepim-runtime/resources/dav/services/zarafa.desktop create mode 100644 kdepim-runtime/resources/dav/services/zimbra.desktop create mode 100644 kdepim-runtime/resources/facebook/CMakeLists.txt create mode 100644 kdepim-runtime/resources/facebook/Info.plist.template create mode 100644 kdepim-runtime/resources/facebook/Messages.sh create mode 100644 kdepim-runtime/resources/facebook/akonadi_facebook_resource.notifyrc create mode 100644 kdepim-runtime/resources/facebook/facebookresource.cpp create mode 100644 kdepim-runtime/resources/facebook/facebookresource.desktop create mode 100644 kdepim-runtime/resources/facebook/facebookresource.h create mode 100644 kdepim-runtime/resources/facebook/facebookresource_events.cpp create mode 100644 kdepim-runtime/resources/facebook/facebookresource_friends.cpp create mode 100644 kdepim-runtime/resources/facebook/facebookresource_notes.cpp create mode 100644 kdepim-runtime/resources/facebook/facebookresource_notifications.cpp create mode 100644 kdepim-runtime/resources/facebook/facebookresource_posts.cpp create mode 100644 kdepim-runtime/resources/facebook/icons/CMakeLists.txt create mode 100644 kdepim-runtime/resources/facebook/icons/hi16-apps-facebookresource.png create mode 100644 kdepim-runtime/resources/facebook/icons/hi22-apps-facebookresource.png create mode 100644 kdepim-runtime/resources/facebook/icons/hi32-apps-facebookresource.png create mode 100644 kdepim-runtime/resources/facebook/icons/hi48-apps-facebookresource.png create mode 100644 kdepim-runtime/resources/facebook/icons/hisc-apps-facebookresource.sgvz create mode 100644 kdepim-runtime/resources/facebook/serializer/CMakeLists.txt create mode 100644 kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.cpp create mode 100644 kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.desktop create mode 100644 kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.h create mode 100644 kdepim-runtime/resources/facebook/serializer/x-vnd.akonadi.socialnotification.xml create mode 100644 kdepim-runtime/resources/facebook/settings.cpp create mode 100644 kdepim-runtime/resources/facebook/settings.h create mode 100644 kdepim-runtime/resources/facebook/settingsbase.kcfg create mode 100644 kdepim-runtime/resources/facebook/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/facebook/settingsdialog.cpp create mode 100644 kdepim-runtime/resources/facebook/settingsdialog.h create mode 100644 kdepim-runtime/resources/facebook/settingsdialog.ui create mode 100644 kdepim-runtime/resources/facebook/timestampattribute.cpp create mode 100644 kdepim-runtime/resources/facebook/timestampattribute.h create mode 100644 kdepim-runtime/resources/folderarchivesettings/CMakeLists.txt create mode 100755 kdepim-runtime/resources/folderarchivesettings/Messages.sh create mode 100644 kdepim-runtime/resources/folderarchivesettings/autotests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.cpp create mode 100644 kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.h create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.cpp create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.h create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.cpp create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.h create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchivesettings_export.h create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.cpp create mode 100644 kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.h create mode 100644 kdepim-runtime/resources/gmail/CMakeLists.txt create mode 100755 kdepim-runtime/resources/gmail/Messages.sh create mode 100644 kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.h create mode 100644 kdepim-runtime/resources/gmail/gmailconfigdialog.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailconfigdialog.h create mode 100644 kdepim-runtime/resources/gmail/gmailconfigdialog.ui create mode 100644 kdepim-runtime/resources/gmail/gmaillabelattribute.cpp create mode 100644 kdepim-runtime/resources/gmail/gmaillabelattribute.h create mode 100644 kdepim-runtime/resources/gmail/gmaillinkitemstask.cpp create mode 100644 kdepim-runtime/resources/gmail/gmaillinkitemstask.h create mode 100644 kdepim-runtime/resources/gmail/gmailmessagehelper.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailmessagehelper.h create mode 100644 kdepim-runtime/resources/gmail/gmailpasswordrequester.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailpasswordrequester.h create mode 100644 kdepim-runtime/resources/gmail/gmailresource.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailresource.desktop create mode 100644 kdepim-runtime/resources/gmail/gmailresource.h create mode 100644 kdepim-runtime/resources/gmail/gmailresource.kcfg create mode 100644 kdepim-runtime/resources/gmail/gmailresourcestate.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailresourcestate.h create mode 100644 kdepim-runtime/resources/gmail/gmailretrievecollectionstask.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailretrievecollectionstask.h create mode 100644 kdepim-runtime/resources/gmail/gmailretrieveitemstask.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailretrieveitemstask.h create mode 100644 kdepim-runtime/resources/gmail/gmailsettings.cpp create mode 100644 kdepim-runtime/resources/gmail/gmailsettings.h create mode 100644 kdepim-runtime/resources/gmail/saslplugin/CMakeLists.txt create mode 100644 kdepim-runtime/resources/gmail/saslplugin/config.h create mode 100644 kdepim-runtime/resources/gmail/saslplugin/plugin_common.c create mode 100644 kdepim-runtime/resources/gmail/saslplugin/plugin_common.h create mode 100644 kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin.c create mode 100644 kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin_init.c create mode 100644 kdepim-runtime/resources/gmail/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/google/CMakeLists.txt create mode 100644 kdepim-runtime/resources/google/calendar/CMakeLists.txt create mode 100644 kdepim-runtime/resources/google/calendar/Messages.sh create mode 100644 kdepim-runtime/resources/google/calendar/calendarresource.cpp create mode 100644 kdepim-runtime/resources/google/calendar/calendarresource.h create mode 100644 kdepim-runtime/resources/google/calendar/defaultreminderattribute.cpp create mode 100644 kdepim-runtime/resources/google/calendar/defaultreminderattribute.h create mode 100644 kdepim-runtime/resources/google/calendar/googlecalendarresource.desktop create mode 100644 kdepim-runtime/resources/google/calendar/settings.cpp create mode 100644 kdepim-runtime/resources/google/calendar/settings.h create mode 100644 kdepim-runtime/resources/google/calendar/settingsbase.kcfg create mode 100644 kdepim-runtime/resources/google/calendar/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/google/calendar/settingsdialog.cpp create mode 100644 kdepim-runtime/resources/google/calendar/settingsdialog.h create mode 100644 kdepim-runtime/resources/google/common/googleaccountmanager.cpp create mode 100644 kdepim-runtime/resources/google/common/googleaccountmanager.h create mode 100644 kdepim-runtime/resources/google/common/googleresource.cpp create mode 100644 kdepim-runtime/resources/google/common/googleresource.h create mode 100644 kdepim-runtime/resources/google/common/googlesettings.cpp create mode 100644 kdepim-runtime/resources/google/common/googlesettings.h create mode 100644 kdepim-runtime/resources/google/common/googlesettingsdialog.cpp create mode 100644 kdepim-runtime/resources/google/common/googlesettingsdialog.h create mode 100644 kdepim-runtime/resources/google/contacts/CMakeLists.txt create mode 100644 kdepim-runtime/resources/google/contacts/Messages.sh create mode 100644 kdepim-runtime/resources/google/contacts/contactsresource.cpp create mode 100644 kdepim-runtime/resources/google/contacts/contactsresource.h create mode 100644 kdepim-runtime/resources/google/contacts/googlecontactsresource.desktop create mode 100644 kdepim-runtime/resources/google/contacts/settings.cpp create mode 100644 kdepim-runtime/resources/google/contacts/settings.h create mode 100644 kdepim-runtime/resources/google/contacts/settingsbase.kcfg create mode 100644 kdepim-runtime/resources/google/contacts/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/google/contacts/settingsdialog.cpp create mode 100644 kdepim-runtime/resources/google/contacts/settingsdialog.h create mode 100644 kdepim-runtime/resources/ical/CMakeLists.txt create mode 100644 kdepim-runtime/resources/ical/Messages.sh create mode 100644 kdepim-runtime/resources/ical/icalresource.desktop create mode 100644 kdepim-runtime/resources/ical/icalresource.kcfg create mode 100644 kdepim-runtime/resources/ical/icalresourceplugin.cpp create mode 100644 kdepim-runtime/resources/ical/notes/CMakeLists.txt create mode 100644 kdepim-runtime/resources/ical/notes/notesresource.cpp create mode 100644 kdepim-runtime/resources/ical/notes/notesresource.desktop create mode 100644 kdepim-runtime/resources/ical/notes/notesresource.h create mode 100644 kdepim-runtime/resources/ical/notes/notesresource.kcfg create mode 100644 kdepim-runtime/resources/ical/notes/settings.kcfgc create mode 100644 kdepim-runtime/resources/ical/settings.kcfgc create mode 100644 kdepim-runtime/resources/ical/shared/icalresource.cpp create mode 100644 kdepim-runtime/resources/ical/shared/icalresource.h create mode 100644 kdepim-runtime/resources/ical/shared/icalresourcebase.cpp create mode 100644 kdepim-runtime/resources/ical/shared/icalresourcebase.h create mode 100644 kdepim-runtime/resources/ical/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/ical/tests/event.ical create mode 100644 kdepim-runtime/resources/ical/tests/ical-empty.xml create mode 100644 kdepim-runtime/resources/ical/tests/ical-step1.xml create mode 100644 kdepim-runtime/resources/ical/tests/icaltest.es create mode 100644 kdepim-runtime/resources/ical/tests/task.ical create mode 100644 kdepim-runtime/resources/ical/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/ical/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/ical/wizard/icalwizard.desktop create mode 100644 kdepim-runtime/resources/ical/wizard/icalwizard.es.cmake create mode 100644 kdepim-runtime/resources/ical/wizard/icalwizard.ui create mode 100644 kdepim-runtime/resources/icaldir/CMakeLists.txt create mode 100644 kdepim-runtime/resources/icaldir/Messages.sh create mode 100644 kdepim-runtime/resources/icaldir/icaldirresource.cpp create mode 100644 kdepim-runtime/resources/icaldir/icaldirresource.desktop create mode 100644 kdepim-runtime/resources/icaldir/icaldirresource.h create mode 100644 kdepim-runtime/resources/icaldir/icaldirresource.kcfg create mode 100644 kdepim-runtime/resources/icaldir/settings.kcfgc create mode 100644 kdepim-runtime/resources/icaldir/settingsdialog.ui create mode 100644 kdepim-runtime/resources/imap/CMakeLists.txt create mode 100755 kdepim-runtime/resources/imap/Messages.sh create mode 100644 kdepim-runtime/resources/imap/addcollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/addcollectiontask.h create mode 100644 kdepim-runtime/resources/imap/additemtask.cpp create mode 100644 kdepim-runtime/resources/imap/additemtask.h create mode 100644 kdepim-runtime/resources/imap/batchfetcher.cpp create mode 100644 kdepim-runtime/resources/imap/batchfetcher.h create mode 100644 kdepim-runtime/resources/imap/changecollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/changecollectiontask.h create mode 100644 kdepim-runtime/resources/imap/changeitemsflagstask.cpp create mode 100644 kdepim-runtime/resources/imap/changeitemsflagstask.h create mode 100644 kdepim-runtime/resources/imap/changeitemtask.cpp create mode 100644 kdepim-runtime/resources/imap/changeitemtask.h create mode 100644 kdepim-runtime/resources/imap/expungecollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/expungecollectiontask.h create mode 100644 kdepim-runtime/resources/imap/highestmodseqattribute.cpp create mode 100644 kdepim-runtime/resources/imap/highestmodseqattribute.h create mode 100644 kdepim-runtime/resources/imap/imapaccount.cpp create mode 100644 kdepim-runtime/resources/imap/imapaccount.h create mode 100644 kdepim-runtime/resources/imap/imapflags.cpp create mode 100644 kdepim-runtime/resources/imap/imapflags.h create mode 100644 kdepim-runtime/resources/imap/imapidlemanager.cpp create mode 100644 kdepim-runtime/resources/imap/imapidlemanager.h create mode 100644 kdepim-runtime/resources/imap/imapresource.cpp create mode 100644 kdepim-runtime/resources/imap/imapresource.desktop create mode 100644 kdepim-runtime/resources/imap/imapresource.h create mode 100644 kdepim-runtime/resources/imap/imapresource.kcfg create mode 100644 kdepim-runtime/resources/imap/imapresourcebase.cpp create mode 100644 kdepim-runtime/resources/imap/imapresourcebase.h create mode 100644 kdepim-runtime/resources/imap/main.cpp create mode 100644 kdepim-runtime/resources/imap/messagehelper.cpp create mode 100644 kdepim-runtime/resources/imap/messagehelper.h create mode 100644 kdepim-runtime/resources/imap/movecollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/movecollectiontask.h create mode 100644 kdepim-runtime/resources/imap/moveitemstask.cpp create mode 100644 kdepim-runtime/resources/imap/moveitemstask.h create mode 100644 kdepim-runtime/resources/imap/noinferiorsattribute.cpp create mode 100644 kdepim-runtime/resources/imap/noinferiorsattribute.h create mode 100644 kdepim-runtime/resources/imap/noselectattribute.cpp create mode 100644 kdepim-runtime/resources/imap/noselectattribute.h create mode 100644 kdepim-runtime/resources/imap/passwordrequesterinterface.cpp create mode 100644 kdepim-runtime/resources/imap/passwordrequesterinterface.h create mode 100644 kdepim-runtime/resources/imap/removecollectionrecursivetask.cpp create mode 100644 kdepim-runtime/resources/imap/removecollectionrecursivetask.h create mode 100644 kdepim-runtime/resources/imap/resourcestate.cpp create mode 100644 kdepim-runtime/resources/imap/resourcestate.h create mode 100644 kdepim-runtime/resources/imap/resourcestateinterface.cpp create mode 100644 kdepim-runtime/resources/imap/resourcestateinterface.h create mode 100644 kdepim-runtime/resources/imap/resourcetask.cpp create mode 100644 kdepim-runtime/resources/imap/resourcetask.h create mode 100644 kdepim-runtime/resources/imap/retrievecollectionmetadatatask.cpp create mode 100644 kdepim-runtime/resources/imap/retrievecollectionmetadatatask.h create mode 100644 kdepim-runtime/resources/imap/retrievecollectionstask.cpp create mode 100644 kdepim-runtime/resources/imap/retrievecollectionstask.h create mode 100644 kdepim-runtime/resources/imap/retrieveitemstask.cpp create mode 100644 kdepim-runtime/resources/imap/retrieveitemstask.h create mode 100644 kdepim-runtime/resources/imap/retrieveitemtask.cpp create mode 100644 kdepim-runtime/resources/imap/retrieveitemtask.h create mode 100644 kdepim-runtime/resources/imap/searchtask.cpp create mode 100644 kdepim-runtime/resources/imap/searchtask.h create mode 100644 kdepim-runtime/resources/imap/serverinfo.ui create mode 100644 kdepim-runtime/resources/imap/serverinfodialog.cpp create mode 100644 kdepim-runtime/resources/imap/serverinfodialog.h create mode 100644 kdepim-runtime/resources/imap/sessionpool.cpp create mode 100644 kdepim-runtime/resources/imap/sessionpool.h create mode 100644 kdepim-runtime/resources/imap/sessionuiproxy.h create mode 100644 kdepim-runtime/resources/imap/settings.cpp create mode 100644 kdepim-runtime/resources/imap/settings.h create mode 100644 kdepim-runtime/resources/imap/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/imap/settingspasswordrequester.cpp create mode 100644 kdepim-runtime/resources/imap/settingspasswordrequester.h create mode 100644 kdepim-runtime/resources/imap/setupserver.cpp create mode 100644 kdepim-runtime/resources/imap/setupserver.h create mode 100644 kdepim-runtime/resources/imap/setupserverview_desktop.ui create mode 100644 kdepim-runtime/resources/imap/setupserverview_mobile.ui create mode 100644 kdepim-runtime/resources/imap/subscriptiondialog.cpp create mode 100644 kdepim-runtime/resources/imap/subscriptiondialog.h create mode 100644 kdepim-runtime/resources/imap/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/imap/tests/dummypasswordrequester.cpp create mode 100644 kdepim-runtime/resources/imap/tests/dummypasswordrequester.h create mode 100644 kdepim-runtime/resources/imap/tests/dummyresourcestate.cpp create mode 100644 kdepim-runtime/resources/imap/tests/dummyresourcestate.h create mode 100644 kdepim-runtime/resources/imap/tests/imaptestbase.cpp create mode 100644 kdepim-runtime/resources/imap/tests/imaptestbase.h create mode 100644 kdepim-runtime/resources/imap/tests/testaddcollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testadditemtask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testchangecollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testchangeitemtask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testexpungecollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testmovecollectiontask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testmoveitemstask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testremovecollectionrecursivetask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testresourcetask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testretrievecollectionmetadatatask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testretrievecollectionstask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testretrieveitemstask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testretrieveitemtask.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testsessionpool.cpp create mode 100644 kdepim-runtime/resources/imap/tests/testsubscriptiondialog.cpp create mode 100644 kdepim-runtime/resources/imap/timestampattribute.cpp create mode 100644 kdepim-runtime/resources/imap/timestampattribute.h create mode 100644 kdepim-runtime/resources/imap/uidnextattribute.cpp create mode 100644 kdepim-runtime/resources/imap/uidnextattribute.h create mode 100644 kdepim-runtime/resources/imap/uidvalidityattribute.cpp create mode 100644 kdepim-runtime/resources/imap/uidvalidityattribute.h create mode 100644 kdepim-runtime/resources/imap/wizard/CMakeLists.txt create mode 100755 kdepim-runtime/resources/imap/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/imap/wizard/imapwizard.desktop create mode 100644 kdepim-runtime/resources/imap/wizard/imapwizard.es create mode 100644 kdepim-runtime/resources/imap/wizard/imapwizard.ui create mode 100644 kdepim-runtime/resources/kabc/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kabc/Messages.sh create mode 100644 kdepim-runtime/resources/kabc/kabcresource.cpp create mode 100644 kdepim-runtime/resources/kabc/kabcresource.desktop create mode 100644 kdepim-runtime/resources/kabc/kabcresource.h create mode 100644 kdepim-runtime/resources/kabc/kresourceassistant.cpp create mode 100644 kdepim-runtime/resources/kabc/kresourceassistant.h create mode 100644 kdepim-runtime/resources/kalarm/CMakeLists.txt create mode 100755 kdepim-runtime/resources/kalarm/Messages.sh create mode 100644 kdepim-runtime/resources/kalarm/kalarm/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kalarm/kalarm/kalarmresource.cpp create mode 100644 kdepim-runtime/resources/kalarm/kalarm/kalarmresource.desktop create mode 100644 kdepim-runtime/resources/kalarm/kalarm/kalarmresource.h create mode 100644 kdepim-runtime/resources/kalarm/kalarm/kalarmresource.kcfg create mode 100644 kdepim-runtime/resources/kalarm/kalarm/settings.kcfgc create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/autoqpointer.h create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.cpp create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.desktop create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.h create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.kcfg create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/settings.kcfgc create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.cpp create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.h create mode 100644 kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.ui create mode 100644 kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.cpp create mode 100644 kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.h create mode 100644 kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.ui create mode 100644 kdepim-runtime/resources/kalarm/shared/alarmtypewidget.cpp create mode 100644 kdepim-runtime/resources/kalarm/shared/alarmtypewidget.h create mode 100644 kdepim-runtime/resources/kalarm/shared/alarmtypewidget.ui create mode 100644 kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.cpp create mode 100644 kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.h create mode 100644 kdepim-runtime/resources/kcal/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kcal/Messages.sh create mode 100644 kdepim-runtime/resources/kcal/kcalresource.cpp create mode 100644 kdepim-runtime/resources/kcal/kcalresource.desktop create mode 100644 kdepim-runtime/resources/kcal/kcalresource.h create mode 100644 kdepim-runtime/resources/kdeaccounts/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kdeaccounts/Messages.sh create mode 100644 kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.cpp create mode 100644 kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.desktop create mode 100644 kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.h create mode 100644 kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.kcfg create mode 100644 kdepim-runtime/resources/kdeaccounts/settings.kcfgc create mode 100644 kdepim-runtime/resources/kolab/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kolab/kolabhelpers.cpp create mode 100644 kdepim-runtime/resources/kolab/kolabhelpers.h create mode 100644 kdepim-runtime/resources/kolab/kolabmessagehelper.cpp create mode 100644 kdepim-runtime/resources/kolab/kolabmessagehelper.h create mode 100644 kdepim-runtime/resources/kolab/kolabresource.cpp create mode 100644 kdepim-runtime/resources/kolab/kolabresource.desktop create mode 100644 kdepim-runtime/resources/kolab/kolabresource.h create mode 100644 kdepim-runtime/resources/kolab/kolabresourcestate.cpp create mode 100644 kdepim-runtime/resources/kolab/kolabresourcestate.h create mode 100644 kdepim-runtime/resources/kolab/kolabretrievecollectionstask.cpp create mode 100644 kdepim-runtime/resources/kolab/kolabretrievecollectionstask.h create mode 100644 kdepim-runtime/resources/kolabproxy/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kolabproxy/Messages.sh create mode 100644 kdepim-runtime/resources/kolabproxy/addressbookhandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/addressbookhandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/akonadi_kolabproxy_resource.notifyrc create mode 100644 kdepim-runtime/resources/kolabproxy/calendarhandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/calendarhandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/changeformat.ui create mode 100644 kdepim-runtime/resources/kolabproxy/collectiontreebuilder.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/collectiontreebuilder.h create mode 100644 kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/handlermanager.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/handlermanager.h create mode 100644 kdepim-runtime/resources/kolabproxy/hi64-apps-kolab.png create mode 100644 kdepim-runtime/resources/kolabproxy/imapitemaddedjob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/imapitemaddedjob.h create mode 100644 kdepim-runtime/resources/kolabproxy/imapitemremovedjob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/imapitemremovedjob.h create mode 100644 kdepim-runtime/resources/kolabproxy/incidencehandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/incidencehandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/itemaddedjob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/itemaddedjob.h create mode 100644 kdepim-runtime/resources/kolabproxy/itemchangedjob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/itemchangedjob.h create mode 100644 kdepim-runtime/resources/kolabproxy/journalhandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/journalhandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/kolabdefs.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/kolabdefs.h create mode 100644 kdepim-runtime/resources/kolabproxy/kolabhandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/kolabhandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/kolabproxyresource.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/kolabproxyresource.desktop create mode 100644 kdepim-runtime/resources/kolabproxy/kolabproxyresource.h create mode 100644 kdepim-runtime/resources/kolabproxy/kolabproxyresource.kcfg create mode 100644 kdepim-runtime/resources/kolabproxy/kolabsettings.ui create mode 100644 kdepim-runtime/resources/kolabproxy/notehandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/notehandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/org.freedesktop.Akonadi.kolabproxy.xml create mode 100644 kdepim-runtime/resources/kolabproxy/revertitemchangesjob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/revertitemchangesjob.h create mode 100644 kdepim-runtime/resources/kolabproxy/settings.kcfgc create mode 100644 kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.h create mode 100644 kdepim-runtime/resources/kolabproxy/setupkolab.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/setupkolab.h create mode 100644 kdepim-runtime/resources/kolabproxy/taskshandler.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/taskshandler.h create mode 100644 kdepim-runtime/resources/kolabproxy/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kolabproxy/tests/clientsidetest.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/tests/couriervm.conf create mode 100755 kdepim-runtime/resources/kolabproxy/tests/create_ldap_users.py create mode 100644 kdepim-runtime/resources/kolabproxy/tests/dovecotvm.conf create mode 100644 kdepim-runtime/resources/kolabproxy/tests/event.ical create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imap-quicktest.es create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imapsignaltest.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step1.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step2.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot.es create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step1.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step2.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab.es create mode 100644 kdepim-runtime/resources/kolabproxy/tests/imaptest.es create mode 100644 kdepim-runtime/resources/kolabproxy/tests/kolab-step1.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/kolab-step2.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/kolabconvertertest.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/tests/kolabevent.mbox create mode 100644 kdepim-runtime/resources/kolabproxy/tests/kolabtest.es create mode 100644 kdepim-runtime/resources/kolabproxy/tests/kolabvm.conf create mode 100644 kdepim-runtime/resources/kolabproxy/tests/proxyintegrationtest.cpp create mode 100755 kdepim-runtime/resources/kolabproxy/tests/runimapcommand.py create mode 100644 kdepim-runtime/resources/kolabproxy/tests/task.ical create mode 100644 kdepim-runtime/resources/kolabproxy/tests/testmail.mbox create mode 100644 kdepim-runtime/resources/kolabproxy/tests/testutils.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/tests/testutils.h create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-db.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-fs.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-db.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-fs.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-sqlite-db.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc create mode 100755 kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdebugrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdedrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/testdata-res1.xml create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.fs/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.db/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.fs/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-sqlite.db/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/kolabproxy/upgradejob.cpp create mode 100644 kdepim-runtime/resources/kolabproxy/upgradejob.h create mode 100644 kdepim-runtime/resources/kolabproxy/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/kolabproxy/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.desktop create mode 100644 kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.es create mode 100644 kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.ui create mode 100644 kdepim-runtime/resources/localbookmarks/CMakeLists.txt create mode 100644 kdepim-runtime/resources/localbookmarks/Messages.sh create mode 100644 kdepim-runtime/resources/localbookmarks/localbookmarksresource.cpp create mode 100644 kdepim-runtime/resources/localbookmarks/localbookmarksresource.desktop create mode 100644 kdepim-runtime/resources/localbookmarks/localbookmarksresource.h create mode 100644 kdepim-runtime/resources/localbookmarks/localbookmarksresource.kcfg create mode 100644 kdepim-runtime/resources/localbookmarks/settings.kcfgc create mode 100644 kdepim-runtime/resources/maildir/CMakeLists.txt create mode 100644 kdepim-runtime/resources/maildir/Messages.sh create mode 100644 kdepim-runtime/resources/maildir/configdialog.cpp create mode 100644 kdepim-runtime/resources/maildir/configdialog.h create mode 100644 kdepim-runtime/resources/maildir/libmaildir/CMakeLists.txt create mode 100644 kdepim-runtime/resources/maildir/libmaildir/keycache.cpp create mode 100644 kdepim-runtime/resources/maildir/libmaildir/keycache.h create mode 100644 kdepim-runtime/resources/maildir/libmaildir/maildir.cpp create mode 100644 kdepim-runtime/resources/maildir/libmaildir/maildir.h create mode 100644 kdepim-runtime/resources/maildir/libmaildir/maildir_export.h create mode 100644 kdepim-runtime/resources/maildir/libmaildir/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.cpp create mode 100644 kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.h create mode 100644 kdepim-runtime/resources/maildir/maildirresource.cpp create mode 100644 kdepim-runtime/resources/maildir/maildirresource.desktop create mode 100644 kdepim-runtime/resources/maildir/maildirresource.h create mode 100644 kdepim-runtime/resources/maildir/maildirresource.kcfg create mode 100644 kdepim-runtime/resources/maildir/main.cpp create mode 100644 kdepim-runtime/resources/maildir/retrieveitemsjob.cpp create mode 100644 kdepim-runtime/resources/maildir/retrieveitemsjob.h create mode 100644 kdepim-runtime/resources/maildir/settings.kcfgc create mode 100644 kdepim-runtime/resources/maildir/settings.ui create mode 100644 kdepim-runtime/resources/maildir/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/maildir/tests/maildir-empty.xml create mode 100644 kdepim-runtime/resources/maildir/tests/maildir-step1.xml create mode 100644 kdepim-runtime/resources/maildir/tests/maildir-step2.xml create mode 100644 kdepim-runtime/resources/maildir/tests/maildir.js create mode 100644 kdepim-runtime/resources/maildir/tests/maildir.xml create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/cur/1237726881.6570.rfoxg!2,S create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/new/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/tmp/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726858.6570.dtdn4!2,S create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726875.6570.R4KOW!2,S create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/new/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/tmp/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/cur/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/new/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/tmp/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/root/cur/1237726845.6570.BejQg!2,S create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/root/new/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/maildir/root/tmp/.keep create mode 100644 kdepim-runtime/resources/maildir/tests/synctest.cpp create mode 100644 kdepim-runtime/resources/maildir/tests/synctest.h create mode 100644 kdepim-runtime/resources/maildir/tests/testmail.mbox create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/config.xml create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi_maildir_resource_0rc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdebugrc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdedrc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kwalletrc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/qttestrc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/testdata.xml create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/maildir/tests/unittestenv/xdglocal/.keep create mode 100644 kdepim-runtime/resources/maildir/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/maildir/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/maildir/wizard/maildirwizard.desktop create mode 100644 kdepim-runtime/resources/maildir/wizard/maildirwizard.es create mode 100644 kdepim-runtime/resources/maildir/wizard/maildirwizard.ui create mode 100644 kdepim-runtime/resources/mailtransport_dummy/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mailtransport_dummy/Messages.sh create mode 100644 kdepim-runtime/resources/mailtransport_dummy/configdialog.cpp create mode 100644 kdepim-runtime/resources/mailtransport_dummy/configdialog.h create mode 100644 kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.cpp create mode 100644 kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.desktop create mode 100644 kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.h create mode 100644 kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.kcfg create mode 100644 kdepim-runtime/resources/mailtransport_dummy/settings.kcfgc create mode 100644 kdepim-runtime/resources/mailtransport_dummy/settings.ui create mode 100644 kdepim-runtime/resources/mbox/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mbox/Messages.sh create mode 100644 kdepim-runtime/resources/mbox/compactpage.cpp create mode 100644 kdepim-runtime/resources/mbox/compactpage.h create mode 100644 kdepim-runtime/resources/mbox/compactpage.ui create mode 100644 kdepim-runtime/resources/mbox/deleteditemsattribute.cpp create mode 100644 kdepim-runtime/resources/mbox/deleteditemsattribute.h create mode 100644 kdepim-runtime/resources/mbox/lockfilepage.ui create mode 100644 kdepim-runtime/resources/mbox/lockmethodpage.cpp create mode 100644 kdepim-runtime/resources/mbox/lockmethodpage.h create mode 100644 kdepim-runtime/resources/mbox/mboxresource.cpp create mode 100644 kdepim-runtime/resources/mbox/mboxresource.desktop create mode 100644 kdepim-runtime/resources/mbox/mboxresource.h create mode 100644 kdepim-runtime/resources/mbox/mboxresource.kcfg create mode 100644 kdepim-runtime/resources/mbox/settings.kcfgc create mode 100644 kdepim-runtime/resources/mbox/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mbox/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/mbox/wizard/mailboxwizard.desktop create mode 100644 kdepim-runtime/resources/mbox/wizard/mailboxwizard.es create mode 100644 kdepim-runtime/resources/mbox/wizard/mailboxwizard.ui create mode 100644 kdepim-runtime/resources/mixedmaildir/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mixedmaildir/Messages.sh create mode 100644 kdepim-runtime/resources/mixedmaildir/compactchangehelper.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/compactchangehelper.h create mode 100644 kdepim-runtime/resources/mixedmaildir/configdialog.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/configdialog.h create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.h create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader_export.h create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/TestIdxReader_data.h create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/data/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.h create mode 100644 kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.desktop create mode 100644 kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.h create mode 100644 kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.kcfg create mode 100644 kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.h create mode 100644 kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.h create mode 100644 kdepim-runtime/resources/mixedmaildir/settings.kcfgc create mode 100644 kdepim-runtime/resources/mixedmaildir/settings.ui create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/collectioncreatetest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/collectiondeletetest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/collectionfetchtest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/collectionmodifytest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/collectionmovetest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/.dimap.index create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/.maildir-tagged.index create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/.maildir.index create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/.mbox-tagged.index create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/.mbox-unpurged.index create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/.mbox.index create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/README create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.LUBVK create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.RTmAd_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.g8PCJ create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.qs6V9_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/dimap/new/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/dimap/tmp/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.6qZsA_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.Xdz3R_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.f0l49_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.kwx1b_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/new/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/tmp/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979617.4595.bwXSm create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.CStza_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.DUl0I_2,S create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.pY5ny create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir/new/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/maildir/tmp/.keep create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/mbox create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/mbox-tagged create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/data/mbox-unpurged create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/itemcreatetest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/itemdeletetest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/itemfetchtest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/itemmodifytest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/itemmovetest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/storecompacttest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/templatemethodstest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/testdata.qrc create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/testdatatest.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/testdatautil.cpp create mode 100644 kdepim-runtime/resources/mixedmaildir/tests/testdatautil.h create mode 100644 kdepim-runtime/resources/nntp/CMakeLists.txt create mode 100644 kdepim-runtime/resources/nntp/Messages.sh create mode 100644 kdepim-runtime/resources/nntp/configdialog.cpp create mode 100644 kdepim-runtime/resources/nntp/configdialog.h create mode 100644 kdepim-runtime/resources/nntp/configdialog.ui create mode 100644 kdepim-runtime/resources/nntp/nntpcollectionattribute.cpp create mode 100644 kdepim-runtime/resources/nntp/nntpcollectionattribute.h create mode 100644 kdepim-runtime/resources/nntp/nntpresource.cpp create mode 100644 kdepim-runtime/resources/nntp/nntpresource.desktop create mode 100644 kdepim-runtime/resources/nntp/nntpresource.h create mode 100644 kdepim-runtime/resources/nntp/nntpresource.kcfg create mode 100644 kdepim-runtime/resources/nntp/settings.cpp create mode 100644 kdepim-runtime/resources/nntp/settings.h create mode 100644 kdepim-runtime/resources/nntp/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/openxchange/CMakeLists.txt create mode 100644 kdepim-runtime/resources/openxchange/Messages.sh create mode 100644 kdepim-runtime/resources/openxchange/configdialog.cpp create mode 100644 kdepim-runtime/resources/openxchange/configdialog.h create mode 100644 kdepim-runtime/resources/openxchange/configdialog.ui create mode 100644 kdepim-runtime/resources/openxchange/icons/CMakeLists.txt create mode 100644 kdepim-runtime/resources/openxchange/icons/hi128-app-ox.png create mode 100644 kdepim-runtime/resources/openxchange/icons/hi16-app-ox.png create mode 100644 kdepim-runtime/resources/openxchange/icons/hi32-app-ox.png create mode 100644 kdepim-runtime/resources/openxchange/icons/hi48-app-ox.png create mode 100644 kdepim-runtime/resources/openxchange/icons/hi64-app-ox.png create mode 100644 kdepim-runtime/resources/openxchange/openxchangeresource.cpp create mode 100644 kdepim-runtime/resources/openxchange/openxchangeresource.desktop create mode 100644 kdepim-runtime/resources/openxchange/openxchangeresource.h create mode 100644 kdepim-runtime/resources/openxchange/openxchangeresource.kcfg create mode 100644 kdepim-runtime/resources/openxchange/oxa/connectiontestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/connectiontestjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/contactutils.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/contactutils.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/davmanager.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/davmanager.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/davutils.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/davutils.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/folder.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/folder.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldercreatejob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldercreatejob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/folderdeletejob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/folderdeletejob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldermovejob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldermovejob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/folderrequestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/folderrequestjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/folderutils.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/folderutils.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/incidenceutils.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/incidenceutils.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/object.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/object.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectcreatejob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectcreatejob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectdeletejob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectdeletejob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectmovejob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectmovejob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectrequestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectrequestjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectutils.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/objectutils.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/oxerrors.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/oxerrors.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/oxutils.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/oxutils.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/updateusersjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/updateusersjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/user.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/user.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/useridrequestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/useridrequestjob.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/users.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/users.h create mode 100644 kdepim-runtime/resources/openxchange/oxa/usersrequestjob.cpp create mode 100644 kdepim-runtime/resources/openxchange/oxa/usersrequestjob.h create mode 100644 kdepim-runtime/resources/openxchange/settings.kcfgc create mode 100644 kdepim-runtime/resources/pop3/CMakeLists.txt create mode 100644 kdepim-runtime/resources/pop3/Messages.sh create mode 100644 kdepim-runtime/resources/pop3/TODO create mode 100644 kdepim-runtime/resources/pop3/accountdialog.cpp create mode 100644 kdepim-runtime/resources/pop3/accountdialog.h create mode 100644 kdepim-runtime/resources/pop3/jobs.cpp create mode 100644 kdepim-runtime/resources/pop3/jobs.h create mode 100644 kdepim-runtime/resources/pop3/metatype.h create mode 100644 kdepim-runtime/resources/pop3/pop3resource.cpp create mode 100644 kdepim-runtime/resources/pop3/pop3resource.desktop create mode 100644 kdepim-runtime/resources/pop3/pop3resource.h create mode 100644 kdepim-runtime/resources/pop3/pop3resourceattribute.cpp create mode 100644 kdepim-runtime/resources/pop3/pop3resourceattribute.h create mode 100644 kdepim-runtime/resources/pop3/popsettings.ui create mode 100644 kdepim-runtime/resources/pop3/settings.cpp create mode 100644 kdepim-runtime/resources/pop3/settings.h create mode 100644 kdepim-runtime/resources/pop3/settings.kcfg create mode 100644 kdepim-runtime/resources/pop3/settingsbase.kcfgc create mode 100644 kdepim-runtime/resources/pop3/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.cpp create mode 100644 kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.h create mode 100644 kdepim-runtime/resources/pop3/tests/pop3test.cpp create mode 100644 kdepim-runtime/resources/pop3/tests/pop3test.h create mode 100644 kdepim-runtime/resources/pop3/tests/unittestenv/config.xml create mode 100644 kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/apps/.keep create mode 100644 kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc create mode 100755 kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/kdebugrc create mode 100644 kdepim-runtime/resources/pop3/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resources/pop3/tests/unittestenv/xdglocal/.keep create mode 100644 kdepim-runtime/resources/pop3/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/pop3/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/pop3/wizard/pop3wizard.desktop create mode 100644 kdepim-runtime/resources/pop3/wizard/pop3wizard.es create mode 100644 kdepim-runtime/resources/pop3/wizard/pop3wizard.ui create mode 100644 kdepim-runtime/resources/shared/CMakeLists.txt create mode 100644 kdepim-runtime/resources/shared/Messages.sh create mode 100644 kdepim-runtime/resources/shared/collectionannotationsattribute.cpp create mode 100644 kdepim-runtime/resources/shared/collectionannotationsattribute.h create mode 100644 kdepim-runtime/resources/shared/collectionflagsattribute.cpp create mode 100644 kdepim-runtime/resources/shared/collectionflagsattribute.h create mode 100644 kdepim-runtime/resources/shared/createandsettagsjob.cpp create mode 100644 kdepim-runtime/resources/shared/createandsettagsjob.h create mode 100644 kdepim-runtime/resources/shared/dirsettingsdialog.cpp create mode 100644 kdepim-runtime/resources/shared/dirsettingsdialog.h create mode 100644 kdepim-runtime/resources/shared/filestore/CMakeLists.txt create mode 100644 kdepim-runtime/resources/shared/filestore/Messages.sh create mode 100644 kdepim-runtime/resources/shared/filestore/abstractlocalstore.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/abstractlocalstore.h create mode 100644 kdepim-runtime/resources/shared/filestore/akonadi-filestore_export.h create mode 100644 kdepim-runtime/resources/shared/filestore/collectioncreatejob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/collectioncreatejob.h create mode 100644 kdepim-runtime/resources/shared/filestore/collectiondeletejob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/collectiondeletejob.h create mode 100644 kdepim-runtime/resources/shared/filestore/collectionfetchjob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/collectionfetchjob.h create mode 100644 kdepim-runtime/resources/shared/filestore/collectionmodifyjob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/collectionmodifyjob.h create mode 100644 kdepim-runtime/resources/shared/filestore/collectionmovejob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/collectionmovejob.h create mode 100644 kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.h create mode 100644 kdepim-runtime/resources/shared/filestore/itemcreatejob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/itemcreatejob.h create mode 100644 kdepim-runtime/resources/shared/filestore/itemdeletejob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/itemdeletejob.h create mode 100644 kdepim-runtime/resources/shared/filestore/itemfetchjob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/itemfetchjob.h create mode 100644 kdepim-runtime/resources/shared/filestore/itemmodifyjob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/itemmodifyjob.h create mode 100644 kdepim-runtime/resources/shared/filestore/itemmovejob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/itemmovejob.h create mode 100644 kdepim-runtime/resources/shared/filestore/job.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/job.h create mode 100644 kdepim-runtime/resources/shared/filestore/session.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/session_p.h create mode 100644 kdepim-runtime/resources/shared/filestore/sessionimpls.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/sessionimpls_p.h create mode 100644 kdepim-runtime/resources/shared/filestore/storecompactjob.cpp create mode 100644 kdepim-runtime/resources/shared/filestore/storecompactjob.h create mode 100644 kdepim-runtime/resources/shared/filestore/storeinterface.h create mode 100644 kdepim-runtime/resources/shared/filestore/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/shared/filestore/tests/abstractlocalstoretest.cpp create mode 100644 kdepim-runtime/resources/shared/getcredentialsjob.cpp create mode 100644 kdepim-runtime/resources/shared/getcredentialsjob.h create mode 100644 kdepim-runtime/resources/shared/imapaclattribute.cpp create mode 100644 kdepim-runtime/resources/shared/imapaclattribute.h create mode 100644 kdepim-runtime/resources/shared/imapquotaattribute.cpp create mode 100644 kdepim-runtime/resources/shared/imapquotaattribute.h create mode 100644 kdepim-runtime/resources/shared/singlefileresource.h create mode 100644 kdepim-runtime/resources/shared/singlefileresourcebase.cpp create mode 100644 kdepim-runtime/resources/shared/singlefileresourcebase.h create mode 100644 kdepim-runtime/resources/shared/singlefileresourceconfigdialog.h create mode 100644 kdepim-runtime/resources/shared/singlefileresourceconfigdialog.ui create mode 100644 kdepim-runtime/resources/shared/singlefileresourceconfigdialog_desktop.ui create mode 100644 kdepim-runtime/resources/shared/singlefileresourceconfigdialog_mobile.ui create mode 100644 kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.cpp create mode 100644 kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.h create mode 100644 kdepim-runtime/resources/shared/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/shared/tests/collectionannotationattributetest.cpp create mode 100644 kdepim-runtime/resources/shared/tests/imapaclattributetest.cpp create mode 100644 kdepim-runtime/resources/vcard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/vcard/Messages.sh create mode 100644 kdepim-runtime/resources/vcard/settings.kcfgc create mode 100644 kdepim-runtime/resources/vcard/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resources/vcard/tests/vcardtest-readonly.js create mode 100644 kdepim-runtime/resources/vcard/tests/vcardtest-readonly.xml create mode 100644 kdepim-runtime/resources/vcard/tests/vcardtest.js create mode 100644 kdepim-runtime/resources/vcard/tests/vcardtest.vcf create mode 100644 kdepim-runtime/resources/vcard/tests/vcardtest.xml create mode 100644 kdepim-runtime/resources/vcard/vcardresource.cpp create mode 100644 kdepim-runtime/resources/vcard/vcardresource.desktop create mode 100644 kdepim-runtime/resources/vcard/vcardresource.h create mode 100644 kdepim-runtime/resources/vcard/vcardresource.kcfg create mode 100644 kdepim-runtime/resources/vcard/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/vcard/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/vcard/wizard/vcardwizard.desktop create mode 100644 kdepim-runtime/resources/vcard/wizard/vcardwizard.es.cmake create mode 100644 kdepim-runtime/resources/vcard/wizard/vcardwizard.ui create mode 100644 kdepim-runtime/resources/vcarddir/CMakeLists.txt create mode 100644 kdepim-runtime/resources/vcarddir/Messages.sh create mode 100644 kdepim-runtime/resources/vcarddir/settings.kcfgc create mode 100644 kdepim-runtime/resources/vcarddir/settingsdialog.ui create mode 100644 kdepim-runtime/resources/vcarddir/vcarddirresource.cpp create mode 100644 kdepim-runtime/resources/vcarddir/vcarddirresource.desktop create mode 100644 kdepim-runtime/resources/vcarddir/vcarddirresource.h create mode 100644 kdepim-runtime/resources/vcarddir/vcarddirresource.kcfg create mode 100644 kdepim-runtime/resources/vcarddir/wizard/CMakeLists.txt create mode 100644 kdepim-runtime/resources/vcarddir/wizard/Messages.sh create mode 100644 kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.desktop create mode 100644 kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.es.cmake create mode 100644 kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.ui create mode 100644 kdepim-runtime/resourcetester/CMakeLists.txt create mode 100644 kdepim-runtime/resourcetester/collectiontest.cpp create mode 100644 kdepim-runtime/resourcetester/collectiontest.h create mode 100644 kdepim-runtime/resourcetester/global.cpp create mode 100644 kdepim-runtime/resourcetester/global.h create mode 100644 kdepim-runtime/resourcetester/itemtest.cpp create mode 100644 kdepim-runtime/resourcetester/itemtest.h create mode 100644 kdepim-runtime/resourcetester/main.cpp create mode 100644 kdepim-runtime/resourcetester/qemu.cpp create mode 100644 kdepim-runtime/resourcetester/qemu.h create mode 100644 kdepim-runtime/resourcetester/resource.cpp create mode 100644 kdepim-runtime/resourcetester/resource.h create mode 100644 kdepim-runtime/resourcetester/script.cpp create mode 100644 kdepim-runtime/resourcetester/script.h create mode 100644 kdepim-runtime/resourcetester/system.cpp create mode 100644 kdepim-runtime/resourcetester/system.h create mode 100644 kdepim-runtime/resourcetester/test.cpp create mode 100644 kdepim-runtime/resourcetester/test.h create mode 100644 kdepim-runtime/resourcetester/tests/CMakeLists.txt create mode 100644 kdepim-runtime/resourcetester/tests/construct.es create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/config-mysql-db.xml create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/config.xml create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/apps/kwallet/kdewallet.kwl create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc create mode 100755 kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdebugrc create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdedrc create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kwalletrc create mode 100644 kdepim-runtime/resourcetester/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc create mode 100644 kdepim-runtime/resourcetester/wrappedobject.cpp create mode 100644 kdepim-runtime/resourcetester/wrappedobject.h create mode 100644 kdepim-runtime/resourcetester/xmloperations.cpp create mode 100644 kdepim-runtime/resourcetester/xmloperations.h create mode 100644 kdepim-runtime/tray/CMakeLists.txt create mode 100644 kdepim-runtime/tray/Messages.sh create mode 100644 kdepim-runtime/tray/akonaditray.desktop create mode 100644 kdepim-runtime/tray/backup.cpp create mode 100644 kdepim-runtime/tray/backup.h create mode 100644 kdepim-runtime/tray/backupassistant.cpp create mode 100644 kdepim-runtime/tray/backupassistant.h create mode 100644 kdepim-runtime/tray/dock.cpp create mode 100644 kdepim-runtime/tray/dock.h create mode 100644 kdepim-runtime/tray/global.cpp create mode 100644 kdepim-runtime/tray/global.h create mode 100644 kdepim-runtime/tray/icons/CMakeLists.txt create mode 100644 kdepim-runtime/tray/icons/hi128-app-akonaditray.png create mode 100644 kdepim-runtime/tray/icons/hi22-app-akonaditray.png create mode 100644 kdepim-runtime/tray/icons/hi32-app-akonaditray.png create mode 100644 kdepim-runtime/tray/icons/hi64-app-akonaditray.png create mode 100644 kdepim-runtime/tray/icons/hisc-app-akonaditray.svgz create mode 100644 kdepim-runtime/tray/main.cpp create mode 100644 kdepim-runtime/tray/org.freedesktop.akonaditray.xml create mode 100644 kdepim-runtime/tray/restore.cpp create mode 100644 kdepim-runtime/tray/restore.h create mode 100644 kdepim-runtime/tray/restoreassistant.cpp create mode 100644 kdepim-runtime/tray/restoreassistant.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a4ef630..90b6222c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,4 +30,5 @@ macro_optional_add_subdirectory (kaffeine) macro_optional_add_subdirectory (kamoso) macro_optional_add_subdirectory (kcachegrind) macro_optional_add_subdirectory (kdepimlibs) +macro_optional_add_subdirectory (kdepim-runtime) macro_optional_add_subdirectory (kdepim) diff --git a/kdepim-runtime/CMakeLists.txt b/kdepim-runtime/CMakeLists.txt new file mode 100644 index 00000000..f3d48b4c --- /dev/null +++ b/kdepim-runtime/CMakeLists.txt @@ -0,0 +1,212 @@ +project(kdepim-runtime) + +# 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 ${kdepim-runtime_SOURCE_DIR}/cmake/modules) + +############### KDEPIM-Runtime version ################ +# KDEPIM_RUNTIME_VERSION +# Version scheme: "x.y.z build". +# +# x is the version number. +# y is the major release number. +# z is the minor release number. +# +# "x.y.z" follow the kdelibs version kdepim is released with. +# +# If "z" is 0, it the version is "x.y" +# +# KDEPIM_RUNTIME_DEV_VERSION +# is empty for final versions. For development versions "build" is +# something like "pre", "alpha1", "alpha2", "beta1", "beta2", "rc1", "rc2". +# +# Examples in chronological order: +# +# 3.0 +# 3.0.1 +# 3.1 alpha1 +# 3.1 beta1 +# 3.1 beta2 +# 3.1 rc1 +# 3.1 +# 3.1.1 +# 3.2 pre +# 3.2 alpha1 + +if(NOT DEFINED KDEPIM_RUNTIME_DEV_VERSION) + set(KDEPIM_RUNTIME_DEV_VERSION "") +endif() + +set(KDEPIM_RUNTIME_VERSION "4.14.3${KDEPIM_RUNTIME_DEV_VERSION}") + +configure_file(kdepim-runtime-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdepim-runtime-version.h @ONLY) + +############### search-related options ################ + +add_definitions(-DQT_USE_QSTRINGBUILDER) + +############### search packages used by KDE ############### + +set(CMAKE_AUTOMOC ON) + +# Kdelibs +find_package(KDE4 4.13.0 REQUIRED) +include(KDE4Defaults) + +# KdepimLibs +find_package(KdepimLibs 4.14.3) +set_package_properties(KdepimLibs PROPERTIES DESCRIPTION "The KDEPIM libraries" URL "http://www.kde.org" TYPE REQUIRED) + +#Boost +# Don't use BoostConfig.cmake +set(Boost_NO_BOOST_CMAKE TRUE) +find_package(Boost 1.34.0) +set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "http://www.boost.org" TYPE REQUIRED PURPOSE "Boost is required by Akonadi") + +# Akonadi +find_package(Akonadi 1.12.90 QUIET CONFIG) +set_package_properties(Akonadi PROPERTIES DESCRIPTION "Akonadi server libraries" URL "http://pim.kde.org/akonadi" TYPE REQUIRED PURPOSE "Access to PIM storage and services") + +# shared-mime-info +find_package(SharedMimeInfo 0.30) +set_package_properties(SharedMimeInfo PROPERTIES DESCRIPTION "The shared-mime-info utility" URL "http://freedesktop.org/wiki/Software/shared-mime-info" TYPE REQUIRED PURPOSE "Information about filetypes") + +# Libkolab +find_package(Libkolab 0.5.2 QUIET CONFIG) +set_package_properties(Libkolab PROPERTIES DESCRIPTION "libkolab" URL "http://mirror.kolabsys.com/pub/releases" TYPE OPTIONAL PURPOSE "The Kolab Format libraries are required to build the Kolab Groupware Resource") + +# Libkolabxml +find_package(Libkolabxml 1.0 QUIET CONFIG) +set_package_properties(Libkolabxml PROPERTIES DESCRIPTION "Kolabxml" URL "http://mirror.kolabsys.com/pub/releases" TYPE OPTIONAL PURPOSE "The Kolab XML Format Schema Definitions Library is required to build the Kolab Groupware Resource") + +# Libkgapi2 +find_package(LibKGAPI2 2.2.0 QUIET CONFIG) +set_package_properties(LibKGAPI2 PROPERTIES DESCRIPTION "KDE-based library for accessing various Google services" URL "https://projects.kde.org/libkgapi" TYPE OPTIONAL PURPOSE "LibKGAPI is required to build Akonadi resources to access Google Contacts, Calendars and Tasks and Gmail") + +# Libkfbapi +find_package(LibKFbAPI 1.0 QUIET CONFIG) +set_package_properties(LibKFbAPI PROPERTIES DESCRIPTION "A library to access Facebook services" URL "http://projects.kde.org/libkfbapi" TYPE OPTIONAL PURPOSE "LibKFbAPI is required to build Akonadi resources to access Facebook's contacts, events, notes and posts" ) + +if(LibKGAPI2_FOUND OR LibKFbAPI_FOUND) + find_package(QJSON) + set_package_properties(QJSON PROPERTIES DESCRIPTION "Qt library for handling JSON data" URL "http://qjson.sourceforge.net/" TYPE REQUIRED PURPOSE "Required to build the Google and Facebook resources") +endif() + +# Xsltproc +find_package(Xsltproc) +set_package_properties(Xsltproc PROPERTIES DESCRIPTION "XSLT processor from libxslt" TYPE REQUIRED PURPOSE "Required to generate D-Bus interfaces for all Akonadi resources.") + +find_package(AccountsQt 1.11 QUIET CONFIG) +set_package_properties(AccountsQt PROPERTIES DESCRIPTION "Qt bindings for accounts-sso" URL "https://code.google.com/p/accounts-sso/" TYPE OPTIONAL PURPOSE "Required to support AccountsQt in different resources") + +find_package(SignOnQt 8.56 QUIET CONFIG) +set_package_properties(SignOnQt PROPERTIES DESCRIPTION "SignOn Qt bindings for accounts-sso" URL "https://code.google.com/p/accounts-sso/" TYPE OPTIONAL PURPOSE "Required to support SignOn in different resources") + +############### Load the CTest options ############### + +# CMake is irritating and doesn't allow setting the tests timeout globally. +# Let's work around this. The global timeout is now 2 minutes. +set(_DartConfigFile "${CMAKE_BINARY_DIR}/DartConfiguration.tcl") +if(EXISTS ${_DartConfigFile}) + set(DartTestingTimeout "120") + file(READ ${_DartConfigFile} _DartConfigFile_content) + string(REGEX REPLACE "TimeOut: 1500" "TimeOut: ${DartTestingTimeout}" _DartConfigFile_content ${_DartConfigFile_content}) + file(WRITE ${_DartConfigFile} ${_DartConfigFile_content}) +endif() + +# CTestCustom.cmake has to be in the CTEST_BINARY_DIR. +# in the KDE build system, this is the same as CMAKE_BINARY_DIR. +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake) + +option(KDEPIM_RUN_ISOLATED_TESTS "Run the isolated tests." FALSE) + +############### Desktop vs. Mobile options ############## + +option(KDEPIM_MOBILE_UI "Build UI for mobile devices instead of for desktops" FALSE) +if (KDEPIM_MOBILE_UI) + add_definitions( -DKDEPIM_MOBILE_UI ) + if(NOT QT_QTDECLARATIVE_FOUND) + message(FATAL_ERROR "The QtDeclarative library is required for building the mobile UI") + endif() +endif () + +set(LIBRARY_TYPE SHARED) + +#FIXME: kde4_add_plugin doesn't have a parameter to build the plugins statically. +if (RUNTIME_PLUGINS_STATIC) + set(SERIALIZER_TYPE STATIC) +endif () + +############### Enterprise build options ################# + +option(KDEPIM_ENTERPRISE_BUILD "Enable features specific to the enterprise branch, which are normally disabled. Also, it disables many components not needed for Kontact such as the Kolab client." FALSE) + +# config-enterprise.h is needed for both ENTERPRISE_BUILD and BUILD_EVERYTHING +configure_file(config-enterprise.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-enterprise.h ) + +############### Needed commands before building anything ############### + +include_directories(${kdepim-runtime_SOURCE_DIR} ${kdepim-runtime_BINARY_DIR} ${KDEPIMLIBS_INCLUDE_DIRS} ${AKONADI_INCLUDE_DIR} ${KDE4_INCLUDES} ${Boost_INCLUDE_DIR}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +############### Generate akonadi-config.h ############### + +configure_file(akonadi-version.h.cmake "${kdepim-runtime_BINARY_DIR}/akonadi-version.h" @ONLY) + + +############### Macros ############### + +# resource tests +macro( akonadi_add_resourcetest _testname _script ) + if ( ${EXECUTABLE_OUTPUT_PATH} ) + set( _exepath ${EXECUTABLE_OUTPUT_PATH} ) + else () + set( _exepath ${kdepim-runtime_BINARY_DIR}/resourcetester ) + endif () + if (WIN32) + set(_resourcetester ${_exepath}/resourcetester.bat) + else () + set(_resourcetester ${_exepath}/resourcetester) + endif () + if (UNIX) + set(_resourcetester ${_resourcetester}.shell) + endif () + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${_script} ${CMAKE_CURRENT_BINARY_DIR}/${_script} COPYONLY) + if (KDEPIM_RUN_ISOLATED_TESTS) + add_test( akonadi-mysql-db-${_testname} akonaditest -c ${kdepim-runtime_SOURCE_DIR}/resourcetester/tests/unittestenv/config-mysql-db.xml ${_resourcetester} -c ${CMAKE_CURRENT_BINARY_DIR}/${_script} ) + endif () +endmacro( akonadi_add_resourcetest ) + +############### Now, we add the components ############### + +add_subdirectory(resources) +add_subdirectory(agents) +add_subdirectory(libkdepim-copy) +add_subdirectory(plugins) +add_subdirectory(accountwizard) +add_subdirectory(defaultsetup) +add_subdirectory(resourcetester) +add_subdirectory(kioslave) +add_subdirectory(kcm) +add_subdirectory(tray) +add_subdirectory(migration) +if (KDEPIMLIBS_KRESOURCES_LIBS) + add_subdirectory(kresources) +endif () +if (QT_QTDECLARATIVE_FOUND) + add_subdirectory(qml) +endif () + +#if(OPENSYNC_FOUND) +# add_subdirectory(opensync) +#endif(OPENSYNC_FOUND) + +############### Here we install some extra stuff ############### + +## install the MIME type spec file for KDEPIM specific MIME types +install(FILES kdepim-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) +update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) + +feature_summary(WHAT ALL + INCLUDE_QUIET_PACKAGES + FATAL_ON_MISSING_REQUIRED_PACKAGES + ) diff --git a/kdepim-runtime/COPYING b/kdepim-runtime/COPYING new file mode 100644 index 00000000..5185fd3f --- /dev/null +++ b/kdepim-runtime/COPYING @@ -0,0 +1,346 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kdepim-runtime/COPYING.LIB b/kdepim-runtime/COPYING.LIB new file mode 100644 index 00000000..2d2d780e --- /dev/null +++ b/kdepim-runtime/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This 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, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kdepim-runtime/CTestConfig.cmake b/kdepim-runtime/CTestConfig.cmake new file mode 100644 index 00000000..e8bc4ba7 --- /dev/null +++ b/kdepim-runtime/CTestConfig.cmake @@ -0,0 +1,13 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## # The following are required to uses Dart and the Cdash dashboard +## ENABLE_TESTING() +## INCLUDE(CTest) +set(CTEST_PROJECT_NAME "kdepim-runtime") +set(CTEST_NIGHTLY_START_TIME "20:00:00 CET") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=kdepim-runtime") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/kdepim-runtime/CTestCustom.cmake b/kdepim-runtime/CTestCustom.cmake new file mode 100644 index 00000000..33852484 --- /dev/null +++ b/kdepim-runtime/CTestCustom.cmake @@ -0,0 +1,31 @@ +# This file contains all the specific settings that will be used +# when running 'make Experimental' + +# Change the maximum warnings that will be displayed +# on the report page (default 50) +set(CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS 2000) + +# Warnings that will be ignored +set(CTEST_CUSTOM_WARNING_EXCEPTION + + "too big, try a different debug format" + "qlist.h.*increases required alignment of target type" + "qmap.h.*increases required alignment of target type" + "qhash.h.*increases required alignment of target type" + ".*warning.* is deprecated" + ) + +# Errors that will be ignored +set(CTEST_CUSTOM_ERROR_EXCEPTION + + "ICECC" + "Segmentation fault" + "Error 1 (ignored)" + "invoking macro kDebug argument 1" + "GConf Error" + "Client failed to connect to the D-BUS daemon" + "Failed to connect to socket" + ) + +# No coverage for these files +set(CTEST_CUSTOM_COVERAGE_EXCLUDE ".moc$" "moc_" "ui_" "ontologies") diff --git a/kdepim-runtime/Mainpage.dox b/kdepim-runtime/Mainpage.dox new file mode 100644 index 00000000..2b1bd767 --- /dev/null +++ b/kdepim-runtime/Mainpage.dox @@ -0,0 +1,105 @@ +/** +\mainpage Akonadi, the KDE PIM storage framework + +These pages are a combination of design and api documentation. If you are +looking for general information go to the Overview section. +For detailed information and/or api-dox on any of the packages go to the +package main page, either from the menu on the left or from the Building +blocks section below. + + +\section akonadi_overview Overview + +- Design and Concepts + - \ref akonadi_design_communication +- \ref akonadi_usage +- Website +- Wiki +- \ref akonadi_todos + +\section akonadi_building_blocks Building Blocks + +- Domain-specific libraries: + - Contacts (KABC) + - MIME Messages (KMime) + - Events, todo items and journal entries (KCal) + - Core library (libakonadi) + +\authors Tobias König , Volker Krause + +\licenses \lgpl +*/ + + +/** +\page akonadi_design_communication Communication Schemas + +\section akonadi_design_communication_search Search + +The sequence diagrams below show how general communication is done: + +
+ \image html akonadi_client_search_small.png "Akonadi Communication Schema" +
+ + +\image latex akonadi_client_search.eps "Akonadi Communication Schema" height=5cm + +The item search request is probably the call which is used most often +by the clients (components or applications). This call enables the client +to search for a list of items of a given mime type which match a +given search criterion. + +In this case the client will contact the SearchProvider responsible for +the mime type, in order to retrieve the list of matching UIDs. The SearchProvider +already has a list of all available items of this mime type in its memory, so it +can search fast and use indices for optimization. + +To communicate mime type constraints in FETCH and LIST and their responses the +IMAP flags mechanism is used. Unknown flags should be ignored by non-Akonadi +IMAP clients, which keeps compatibility with mutt and regular KMail. + +Examples: +- List +\verbatim +0x8053c68 8 LIST "" "res1/foo/%" +0x8053c68 * LIST (\MimeTypes[text/calendar,directory/inode]) "/" "res1/foo/bar" +\endverbatim +- Fetch +\verbatim +0x8056310 7 UID FETCH 22 (UID RFC822.SIZE FLAGS BODY.PEEK[]) +0x8056310 * 1 FETCH (FLAGS (\Seen \MimeTypes[message/rfc822]) RFC822 {2450} From: Till Adam To: ... +\endverbatim + +\section akonadi_design_communication_agent Agent Handling +
+ \image html akonadi_agent_handling_small.png "Akonadi Agent Handling" +
+ + +\image latex akonadi_agent_handling.eps "Akonadi Agent Handling" height=4cm +*/ + +/** +\page akonadi_overview_uml Akonadi Overview + +This overview does not show a complete class or collaboration diagram, it is rather meant to show the +big picture. + +
+ \image html akonadi_overview_uml_small.png "Akonadi Overview" +
+ + + +*/ + + +// DOXYGEN_NAME=kdepim-runtime +// DOXYGEN_ENABLE=YES diff --git a/kdepim-runtime/README b/kdepim-runtime/README new file mode 100644 index 00000000..646778a3 --- /dev/null +++ b/kdepim-runtime/README @@ -0,0 +1,3 @@ +The KDE project kdepim-runtime contains the Akonadi resources from kdepim which can be used without the applications in kdepim. + +Here is the online code-repostitory of kdepim-runtime: http://quickgit.kde.org/?p=kdepim-runtime.git&a=summary diff --git a/kdepim-runtime/accountwizard/CMakeLists.txt b/kdepim-runtime/accountwizard/CMakeLists.txt new file mode 100644 index 00000000..7802aa0b --- /dev/null +++ b/kdepim-runtime/accountwizard/CMakeLists.txt @@ -0,0 +1,71 @@ +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + +# allow to disable GHNS support on resource-constrained systems +option( ACCOUNTWIZARD_NO_GHNS "Disable GHNS support in the account wizard" ${KDEPIM_MOBILE_UI} ) +if ( ACCOUNTWIZARD_NO_GHNS ) + add_definitions( -DACCOUNTWIZARD_NO_GHNS ) +endif () + +set(accountwizard_srcs + dialog.cpp + typepage.cpp + loadpage.cpp + global.cpp + page.cpp + dynamicpage.cpp + setupmanager.cpp + setuppage.cpp + resource.cpp + setupobject.cpp + transport.cpp + configfile.cpp + ldap.cpp + identity.cpp + servertest.cpp + personaldatapage.cpp + ispdb/ispdb.cpp +) + +kde4_add_ui_files(accountwizard_srcs + ui/typepage.ui + ui/loadpage.ui + ui/setuppage.ui + ui/personaldatapage.ui +) + +set(accountwizard_libs + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDEPIMLIBS_KPIMUTILS_LIBS} + ${KDE4_KROSSCORE_LIBS} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} + ${QT_QTGUI_LIBRARY} + ${QT_QTUITOOLS_LIBRARY} +) + +if ( NOT ACCOUNTWIZARD_NO_GHNS ) + set(accountwizard_srcs ${accountwizard_srcs} providerpage.cpp) + kde4_add_ui_files(accountwizard_srcs ui/providerpage.ui) + set(accountwizard_libs ${accountwizard_libs} ${KDE4_KNEWSTUFF3_LIBS}) +endif () + +kde4_add_executable(accountwizard ${accountwizard_srcs} main.cpp) +target_link_libraries(accountwizard ${accountwizard_libs}) + +kde4_add_plugin(accountwizard_plugin ${accountwizard_srcs} inprocess-main.cpp) +target_link_libraries(accountwizard_plugin ${accountwizard_libs}) + +if ( NOT ACCOUNTWIZARD_NO_GHNS ) + install(FILES accountwizard.knsrc DESTINATION ${CONFIG_INSTALL_DIR}) +endif () +install(TARGETS accountwizard ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(TARGETS accountwizard_plugin DESTINATION ${PLUGIN_INSTALL_DIR}) +install(PROGRAMS accountwizard.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES accountwizard-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) +update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) + +add_subdirectory(wizards) diff --git a/kdepim-runtime/accountwizard/HOWTO b/kdepim-runtime/accountwizard/HOWTO new file mode 100644 index 00000000..4356b3a1 --- /dev/null +++ b/kdepim-runtime/accountwizard/HOWTO @@ -0,0 +1,2 @@ +You can create packages too... Want to read about it? +http://techbase.kde.org/Development/Tutorials/Akonadi/CreatingAccountWizardPackages diff --git a/kdepim-runtime/accountwizard/Messages.sh b/kdepim-runtime/accountwizard/Messages.sh new file mode 100644 index 00000000..7720e834 --- /dev/null +++ b/kdepim-runtime/accountwizard/Messages.sh @@ -0,0 +1,3 @@ +#!/bin/sh +$EXTRACTRC ui/*.ui >> rc.cpp +$XGETTEXT *.cpp ispdb/*.cpp -o $podir/accountwizard.pot diff --git a/kdepim-runtime/accountwizard/TODO b/kdepim-runtime/accountwizard/TODO new file mode 100644 index 00000000..e2d3ad7d --- /dev/null +++ b/kdepim-runtime/accountwizard/TODO @@ -0,0 +1,5 @@ +- figure out i18n for the actual wizards (DONE) +- imap+pop3wizard has no encryption as default i.c.w. plain auth method. Not a good default! +- remember which set up resources/transports/ldap accounts/identities belong together so we can offer to remove them in one go again +- add more wizards (In progress) +- sort out password fields at least for pop3. diff --git a/kdepim-runtime/accountwizard/accountwizard-mime.xml b/kdepim-runtime/accountwizard/accountwizard-mime.xml new file mode 100644 index 00000000..51148282 --- /dev/null +++ b/kdepim-runtime/accountwizard/accountwizard-mime.xml @@ -0,0 +1,7 @@ + + + + KDE Accountwizard Package + + + diff --git a/kdepim-runtime/accountwizard/accountwizard.desktop b/kdepim-runtime/accountwizard/accountwizard.desktop new file mode 100644 index 00000000..3b0da8e7 --- /dev/null +++ b/kdepim-runtime/accountwizard/accountwizard.desktop @@ -0,0 +1,97 @@ +[Desktop Entry] +Name=Account Wizard +Name[bg]=Помощник за Ñметки +Name[bs]=ÄŒarobnjak naloga +Name[ca]=Assistent de comptes +Name[ca@valencia]=Assistent de comptes +Name[cs]=Průvodce nastavením úÄtu +Name[da]=Kontoguide +Name[de]=Zugangsassistent +Name[el]=Οδηγός ΛογαÏÎ¹Î±ÏƒÎ¼Î¿Ï +Name[en_GB]=Account Wizard +Name[es]=Asistente de cuentas +Name[et]=Kontonõustaja +Name[fi]=Opastettu tilien asetus +Name[fr]=Assistant de comptes +Name[ga]=Treoraí Cuntais +Name[gl]=Asistente de contas +Name[hu]=Azonosítóbeállító varázsló +Name[ia]=Assistente de conto +Name[it]=Procedura guidata per la creazione di account +Name[ja]=アカウントウィザード +Name[kk]=Тіркелгі шебері +Name[km]=អ្នក​ជំនួយការ​គណនី +Name[ko]=계정 마법사 +Name[lt]=Paskyros vediklis +Name[lv]=Konta vednis +Name[mai]=खाता विजारà¥à¤¡ +Name[nb]=Konto-veiviser +Name[nds]=Konto-Hölper +Name[nl]=Accountassistent +Name[pa]=ਅਕਾਊਂਟ ਸਹਾਇਕ +Name[pl]=Pomocnik konta +Name[pt]=Assistente de Contas +Name[pt_BR]=Assistente de contas +Name[ro]=Expert conturi +Name[ru]=МаÑтер учётных запиÑей +Name[sk]=Sprievodca úÄtom +Name[sl]=ÄŒarovnik za raÄune +Name[sr]=Чаробњак за налоге +Name[sr@ijekavian]=Чаробњак за налоге +Name[sr@ijekavianlatin]=ÄŒarobnjak za naloge +Name[sr@latin]=ÄŒarobnjak za naloge +Name[sv]=Kontoguide +Name[tr]=Hesap Sihirbazı +Name[ug]=زىيارەت ÙŠÛتەكچىسى +Name[uk]=МайÑтер облікових запиÑів +Name[x-test]=xxAccount Wizardxx +Name[zh_CN]=账户å‘导 +Name[zh_TW]=å¸³è™Ÿç²¾éˆ +Exec=accountwizard --package %u +Type=Application +NoDisplay=true +Comment=Launch the account wizard to configure PIM accounts. +Comment[bs]=Pokreni Äarobnjaka za naloge za konfigurisanje PIM naloga. +Comment[ca]=Llança l'assistent de comptes per a configurar comptes de PIM. +Comment[ca@valencia]=Llança l'assistent de comptes per a configurar comptes de PIM. +Comment[cs]=Spustit průvodce nastavením PIM úÄtů. +Comment[da]=Start kontoguiden for at konfigurere PIM-konti. +Comment[de]=Den Zugangsassistenten zum Einrichten von PIM-Zugängen starten. +Comment[el]=Εκτέλεση του Î¿Î´Î·Î³Î¿Ï Î»Î¿Î³Î±ÏÎ¹Î±ÏƒÎ¼Î¿Ï Î³Î¹Î± τη διαμόÏφωση λογαÏιασμών PIM. +Comment[en_GB]=Launch the account wizard to configure PIM accounts. +Comment[es]=Lanzar el asistente de cuentas para configurar las cuentas PIM. +Comment[et]=Kontonõustaja käivitamine PIM-kontode seadistamiseks. +Comment[fi]=Käynnistä PIM-tilien opastettu asetus. +Comment[fr]=Lance l'assistant de compte pour configurer les comptes PIM. +Comment[gl]=Iniciar o asistente de contas para configurar as contas de información persoal +Comment[hu]=A PIM azonosítókat beállító varázsló elindítása. +Comment[ia]=Aperi le assistente de conto pro configurar contos del Administration de Informationes Personal +Comment[it]=Esegue la procedura guidata per la creazione di account per configurare gli account di PIM. +Comment[kk]=PIM тіркелгілерін баптау үшін шеберін жегеді. +Comment[km]=ចាប់ផ្ដើម​អ្នក​ជំនួយការ​គណនី​ ដើម្បី​កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ​គណនី PIM ។ +Comment[ko]=계정 마법사를 실행하여 PIM ê³„ì •ì„ ì„¤ì •í•©ë‹ˆë‹¤. +Comment[lt]=PIM paskyros nustatymui, paleisti paskyros vediklį. +Comment[lv]=Palaiž konta vedni, lai konfigurÄ“tu PIM kontus. +Comment[nb]=Start kontoveiviseren for Ã¥ sette opp PIM-kontoer. +Comment[nds]=Konto-Hölper för't Inrichten vun PIM-Kontos starten +Comment[nl]=Start de accountassistent om PIM-accounts in te stellen. +Comment[pl]=Uruchom pomocnika konta, aby ustawić konta ZIO. +Comment[pt]=Lançar o assistente de contas para configurar as contas PIM. +Comment[pt_BR]=Executa o assistente para configuração das contas PIM. +Comment[ro]=Lansează expertul de conturi pentru a configura conturi noi. +Comment[ru]=МаÑтер Ð´Ð»Ñ Ð½Ð°Ñтройки учётных запиÑей перÑонального информационного менеджера. +Comment[sk]=Spustí sprievodcu úÄtami na nasatavenie PIM úÄtov. +Comment[sl]=Zaženite Äarovnika za nastavljanje raÄunov za osebne podatke. +Comment[sr]=Покрените чаробњак да подеÑите ПИМ налоге. +Comment[sr@ijekavian]=Покрените чаробњак да подеÑите ПИМ налоге. +Comment[sr@ijekavianlatin]=Pokrenite Äarobnjak da podesite PIM naloge. +Comment[sr@latin]=Pokrenite Äarobnjak da podesite PIM naloge. +Comment[sv]=Starta kontoguiden för att anpassa konton för personlig informationshantering. +Comment[tr]=PIM hesaplarını düzenlemek için hesap sihirbazını baÅŸlat. +Comment[ug]=PIM Ú¾Ûساباتلىرىنى سەپلەشتە ئىشلىتىدىغان Ú¾Ûسابات ÙŠÛتەكچىسىنى ئىجرا قىلىش +Comment[uk]=ЗапуÑтити майÑтер облікових запиÑів Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ. +Comment[x-test]=xxLaunch the account wizard to configure PIM accounts.xx +Comment[zh_CN]=调用账户å‘导é…置个人信æ¯è´¦æˆ·ã€‚ +Comment[zh_TW]=啟動帳號精éˆä¾†è¨­å®š PIM 帳號 +MimeType=application/x-accountwizard-package; +Terminal=false diff --git a/kdepim-runtime/accountwizard/accountwizard.knsrc b/kdepim-runtime/accountwizard/accountwizard.knsrc new file mode 100644 index 00000000..42f00a69 --- /dev/null +++ b/kdepim-runtime/accountwizard/accountwizard.knsrc @@ -0,0 +1,5 @@ +[KNewStuff3] +ProvidersUrl=http://download.kde.org/ocs/providers.xml +Categories=Akonadi Email Providers +TargetDir=akonadi/accountwizard +Uncompress=archive diff --git a/kdepim-runtime/accountwizard/configfile.cpp b/kdepim-runtime/accountwizard/configfile.cpp new file mode 100644 index 00000000..627333fc --- /dev/null +++ b/kdepim-runtime/accountwizard/configfile.cpp @@ -0,0 +1,91 @@ +/* + Copyright (c) 2010 Laurent Montel + + 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 "configfile.h" + +#include +#include +#include +#include + +ConfigFile::ConfigFile( const QString & configName, QObject *parent ) + : SetupObject( parent ) +{ + m_name = configName; + m_config = new KConfig( configName ); +} + +ConfigFile::~ConfigFile() +{ + delete m_config; +} + +void ConfigFile::write() +{ + create(); +} + +void ConfigFile::create() +{ + emit info( i18n( "Writing config file for %1...",m_name ) ); + + foreach ( const Config &c, m_configData ) { + KConfigGroup grp = m_config->group( c.group ); + if ( c.obscure ) + grp.writeEntry( c.key, KStringHandler::obscure( c.value ) ); + else + grp.writeEntry( c.key, c.value ); + } + + m_config->sync(); + emit finished( i18n( "Config file for %1 is writing.", m_name ) ); +} + +void ConfigFile::destroy() +{ + emit info( i18n( "Config file for %1 was not changed.", m_name ) ); +} + +void ConfigFile::setName( const QString &name ) +{ + m_name = name; +} + + +void ConfigFile::setConfig( const QString &group, const QString &key, const QString &value ) +{ + Config conf; + conf.group = group; + conf.key = key; + conf.value = value; + conf.obscure = false; + m_configData.append( conf ); +} + +void ConfigFile::setPassword(const QString& group, const QString& key, const QString& value) +{ + Config conf; + conf.group = group; + conf.key = key; + conf.value = value; + conf.obscure = true; + m_configData.append( conf ); +} + + diff --git a/kdepim-runtime/accountwizard/configfile.h b/kdepim-runtime/accountwizard/configfile.h new file mode 100644 index 00000000..6e11ce90 --- /dev/null +++ b/kdepim-runtime/accountwizard/configfile.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2010 Laurent Montel + + 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 CONFIGFILE_H +#define CONFIGFILE_H + +#include "setupobject.h" + +class KConfig; + +struct Config { + QString group; + QString key; + QString value; + bool obscure; +}; + +class ConfigFile : public SetupObject +{ + Q_OBJECT + public: + explicit ConfigFile( const QString& configName, QObject *parent = 0 ); + ~ConfigFile(); + void create(); + void destroy(); + public slots: + Q_SCRIPTABLE void write(); + Q_SCRIPTABLE void setName( const QString & name ); + Q_SCRIPTABLE void setConfig( const QString &group, const QString &key, const QString &value ); + Q_SCRIPTABLE void setPassword( const QString &group, const QString &key, const QString &value ); + private: + QList m_configData; + QString m_name; + KConfig *m_config; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/dialog.cpp b/kdepim-runtime/accountwizard/dialog.cpp new file mode 100644 index 00000000..32c374d5 --- /dev/null +++ b/kdepim-runtime/accountwizard/dialog.cpp @@ -0,0 +1,206 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2010 Tom Albers + + 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 "dialog.h" +#include "personaldatapage.h" +#ifndef ACCOUNTWIZARD_NO_GHNS +#include "providerpage.h" +#endif +#include "typepage.h" +#include "loadpage.h" +#include "global.h" +#include "dynamicpage.h" +#include "setupmanager.h" +#include "servertest.h" +#include "setuppage.h" + +#include + +#include +#include +#include +#include + +#include + +Dialog::Dialog(QWidget* parent, Qt::WindowFlags flags ) : + KAssistantDialog( parent, flags ) +{ +#if defined (Q_WS_MAEMO_5) || defined (MEEGO_EDITION_HARMATTAN) + setWindowState( Qt::WindowFullScreen ); +#endif + + showButton( Help, false ); // we don't have help for the account wizard atm + + mSetupManager = new SetupManager( this ); + const bool showPersonalDataPage = Global::typeFilter().size() == 1 && Global::typeFilter().first() == KMime::Message::mimeType(); + + if ( showPersonalDataPage ) { + // todo: dont ask these details based on a setting of the desktop file. + PersonalDataPage *pdpage = new PersonalDataPage( this ); + addPage( pdpage, i18n( "Provide personal data" ) ); + connect( pdpage, SIGNAL(manualWanted(bool)), SLOT(slotManualConfigWanted(bool)) ); + if ( !Global::assistant().isEmpty() ) { + pdpage->setHideOptionInternetSearch( true ); + } + } + + if ( Global::assistant().isEmpty() ) { + TypePage* typePage = new TypePage( this ); + connect( typePage->treeview(), SIGNAL(doubleClicked(QModelIndex)), SLOT(slotNextPage()) ); +#ifndef ACCOUNTWIZARD_NO_GHNS + connect( typePage, SIGNAL(ghnsWanted()), SLOT(slotGhnsWanted()) ); +#endif + mTypePage = addPage( typePage, i18n( "Select Account Type" ) ); + setAppropriate( mTypePage, false ); + +#ifndef ACCOUNTWIZARD_NO_GHNS + ProviderPage *ppage = new ProviderPage( this ); + connect( typePage, SIGNAL(ghnsWanted()), ppage, SLOT(startFetchingData()) ); + connect( ppage->treeview(), SIGNAL(doubleClicked(QModelIndex)), SLOT(slotNextPage()) ); + connect( ppage, SIGNAL(ghnsNotWanted()), SLOT(slotGhnsNotWanted()) ); + mProviderPage = addPage( ppage, i18n( "Select Provider" ) ); + setAppropriate( mProviderPage, false ); +#endif + } + + LoadPage *loadPage = new LoadPage( this ); + mLoadPage = addPage( loadPage, i18n( "Loading Assistant" ) ); + setAppropriate( mLoadPage, false ); + loadPage->exportObject( this, QLatin1String( "Dialog" ) ); + loadPage->exportObject( mSetupManager, QLatin1String( "SetupManager" ) ); + loadPage->exportObject( new ServerTest( this ), QLatin1String( "ServerTest" ) ); + connect( loadPage, SIGNAL(aboutToStart()), SLOT(clearDynamicPages()) ); + + SetupPage *setupPage = new SetupPage( this ); + mLastPage = addPage( setupPage, i18n( "Setting up Account" ) ); + mSetupManager->setSetupPage( setupPage ); + + slotManualConfigWanted( !showPersonalDataPage ); + + Page *page = qobject_cast( currentPage()->widget() ); + page->enterPageNext(); + emit page->pageEnteredNext(); + enableButton( KDialog::Help, false ); +} + +KPageWidgetItem* Dialog::addPage(Page* page, const QString &title) +{ + KPageWidgetItem *item = KAssistantDialog::addPage( page, title ); + connect( page, SIGNAL(leavePageNextOk()), SLOT(slotNextOk()) ); + connect( page, SIGNAL(leavePageBackOk()), SLOT(slotBackOk()) ); + page->setPageWidgetItem( item ); + return item; +} + + +void Dialog::slotNextPage() +{ + next(); +} + +void Dialog::next() +{ + Page *page = qobject_cast( currentPage()->widget() ); + page->leavePageNext(); + page->leavePageNextRequested(); +} + +void Dialog::slotNextOk() +{ + Page *page = qobject_cast( currentPage()->widget() ); + emit page->pageLeftNext(); + KAssistantDialog::next(); + page = qobject_cast( currentPage()->widget() ); + page->enterPageNext(); + emit page->pageEnteredNext(); +} + + +void Dialog::back() +{ + Page *page = qobject_cast( currentPage()->widget() ); + page->leavePageBack(); + page->leavePageBackRequested(); +} + +void Dialog::slotBackOk() +{ + Page *page = qobject_cast( currentPage()->widget() ); + emit page->pageLeftBack(); + KAssistantDialog::back(); + page = qobject_cast( currentPage()->widget() ); + page->enterPageBack(); + emit page->pageEnteredBack(); +} + +QObject* Dialog::addPage(const QString& uiFile, const QString &title ) +{ + kDebug() << uiFile; + DynamicPage *page = new DynamicPage( Global::assistantBasePath() + uiFile, this ); + connect( page, SIGNAL(leavePageNextOk()), SLOT(slotNextOk()) ); + connect( page, SIGNAL(leavePageBackOk()), SLOT(slotBackOk()) ); + KPageWidgetItem* item = insertPage( mLastPage, page, title ); + page->setPageWidgetItem( item ); + mDynamicPages.push_back( item ); + return page; +} + +void Dialog::slotManualConfigWanted( bool show ) +{ + Q_ASSERT( mTypePage ); + setAppropriate( mTypePage, show ); + setAppropriate( mLoadPage, show ); +} + +#ifndef ACCOUNTWIZARD_NO_GHNS +void Dialog::slotGhnsWanted() +{ + Q_ASSERT( mProviderPage ); + setAppropriate( mProviderPage, true ); + setCurrentPage( mProviderPage ); +} + +void Dialog::slotGhnsNotWanted() +{ + Q_ASSERT( mProviderPage ); + setAppropriate( mProviderPage, false ); +} +#endif + +SetupManager* Dialog::setupManager() +{ + return mSetupManager; +} + +void Dialog::clearDynamicPages() +{ + foreach ( KPageWidgetItem *item, mDynamicPages ) + removePage( item ); + mDynamicPages.clear(); +} + +void Dialog::reject() +{ + connect( mSetupManager, SIGNAL(rollbackComplete()), SLOT(close()) ); + mSetupManager->requestRollback(); +} + + diff --git a/kdepim-runtime/accountwizard/dialog.h b/kdepim-runtime/accountwizard/dialog.h new file mode 100644 index 00000000..44b0182b --- /dev/null +++ b/kdepim-runtime/accountwizard/dialog.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2009 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 DIALOG_H +#define DIALOG_H + +#include "setupmanager.h" +#include + +class Page; +class TypePage; + +class Dialog : public KAssistantDialog +{ + Q_OBJECT + public: + explicit Dialog( QWidget *parent = 0, Qt::WindowFlags flags = 0 ); + + /* reimpl */ void next(); + /* reimpl */ void back(); + + // give room for certain pages to create objects too. + SetupManager* setupManager(); + + public slots: + Q_SCRIPTABLE QObject* addPage( const QString &uiFile, const QString &title ); + + void reject(); + + private slots: + void slotNextPage(); +#ifndef ACCOUNTWIZARD_NO_GHNS + void slotGhnsWanted(); + void slotGhnsNotWanted(); +#endif + void slotManualConfigWanted( bool ); + void slotNextOk(); + void slotBackOk(); + void clearDynamicPages(); + + private: + KPageWidgetItem* addPage( Page* page, const QString &title ); + + private: + SetupManager* mSetupManager; + KPageWidgetItem* mLastPage; + KPageWidgetItem* mProviderPage; + KPageWidgetItem* mTypePage; + KPageWidgetItem* mLoadPage; + QVector mDynamicPages; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/dynamicpage.cpp b/kdepim-runtime/accountwizard/dynamicpage.cpp new file mode 100644 index 00000000..fe38598f --- /dev/null +++ b/kdepim-runtime/accountwizard/dynamicpage.cpp @@ -0,0 +1,65 @@ +/* + Copyright (c) 2009 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 "dynamicpage.h" + +#include + +#include +#include +#include +#include + +DynamicPage::DynamicPage(const QString& uiFile, KAssistantDialog* parent) : Page( parent ) +{ + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin( 0 ); + setLayout( layout ); + +#ifdef KDEPIM_MOBILE_UI + // for mobile ui we put the page into a scroll area in case it's too big + QScrollArea *pageParent = new QScrollArea( this ); + pageParent->setFrameShape( QFrame::NoFrame ); + pageParent->setWidgetResizable( true ); + layout->addWidget( pageParent ); +#else + QWidget *pageParent = this; +#endif + + QUiLoader loader; + QFile file( uiFile ); + file.open( QFile::ReadOnly ); + kDebug() << uiFile; + m_dynamicWidget = loader.load( &file, pageParent ); + file.close(); + +#ifdef KDEPIM_MOBILE_UI + pageParent->setWidget( m_dynamicWidget ); +#else + layout->addWidget( m_dynamicWidget ); +#endif + + setValid( true ); +} + +QObject* DynamicPage::widget() const +{ + return m_dynamicWidget; +} + diff --git a/kdepim-runtime/accountwizard/dynamicpage.h b/kdepim-runtime/accountwizard/dynamicpage.h new file mode 100644 index 00000000..ae74fb08 --- /dev/null +++ b/kdepim-runtime/accountwizard/dynamicpage.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009 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 DYNAMICPAGE_H +#define DYNAMICPAGE_H + +#include "page.h" + +class DynamicPage : public Page +{ + Q_OBJECT + public: + explicit DynamicPage( const QString &uiFile, KAssistantDialog* parent = 0 ); + + public slots: + /// returns the top-level widget of the UI file + Q_SCRIPTABLE QObject* widget() const; + + private: + QWidget* m_dynamicWidget; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/global.cpp b/kdepim-runtime/accountwizard/global.cpp new file mode 100644 index 00000000..372b3747 --- /dev/null +++ b/kdepim-runtime/accountwizard/global.cpp @@ -0,0 +1,115 @@ +/* + Copyright (c) 2009 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 "global.h" + +#include +#include +#include +#include +#include +#include +#include + +class GlobalPrivate +{ + public: + QStringList filter; + QString assistant; +}; + +K_GLOBAL_STATIC( GlobalPrivate, sInstance ) + + +QString Global::assistant() +{ + return sInstance->assistant; +} + +void Global::setAssistant(const QString& assistant) +{ + const QFileInfo info( assistant ); + if ( info.isAbsolute() ) { + sInstance->assistant = assistant; + return; + } + + const QStringList list = KGlobal::dirs()->findAllResources( + "data", QLatin1String( "akonadi/accountwizard/*.desktop" ), + KStandardDirs::Recursive | KStandardDirs::NoDuplicates ); + foreach ( const QString &entry, list ) { + const QFileInfo info( entry ); + const QDir dir( info.absolutePath() ); + kDebug() << dir.dirName(); + if ( dir.dirName() == assistant ) { + sInstance->assistant = entry; + return; + } + } + + sInstance->assistant.clear(); +} + + +QStringList Global::typeFilter() +{ + return sInstance->filter; +} + +void Global::setTypeFilter(const QStringList& filter) +{ + sInstance->filter = filter; +} + +QString Global::assistantBasePath() +{ + const QFileInfo info( assistant() ); + if ( info.isAbsolute() ) + return info.absolutePath() + QDir::separator(); + return QString(); +} + +QString Global::unpackAssistant( const KUrl& remotePackageUrl ) +{ + QString localPackageFile; + if ( remotePackageUrl.protocol() == QLatin1String( "file" ) ) { + localPackageFile = remotePackageUrl.path(); + } else { + QString remoteFileName = QFileInfo( remotePackageUrl.path() ).fileName(); + localPackageFile = KStandardDirs::locateLocal( "cache", QLatin1String("accountwizard/") + remoteFileName ); + KIO::Job* job = KIO::copy( remotePackageUrl, localPackageFile, KIO::Overwrite | KIO::HideProgressInfo ); + kDebug() << "downloading remote URL" << remotePackageUrl << "to" << localPackageFile; + if ( !KIO::NetAccess::synchronousRun( job, 0 ) ) + return QString(); + } + + const KUrl file( QLatin1String("tar://") + localPackageFile ); + const QFileInfo fi( localPackageFile ); + const QString assistant = fi.baseName(); + const QString dest = KStandardDirs::locateLocal( "appdata", QLatin1String("/") ); + KStandardDirs::makeDir( dest + file.fileName() ); + KIO::Job* getJob = KIO::copy( file, dest, KIO::Overwrite | KIO::HideProgressInfo ); + if ( KIO::NetAccess::synchronousRun( getJob, 0 ) ) { + kDebug() << "worked, unpacked in " << dest; + return dest + file.fileName() + QLatin1Char('/') + assistant + QLatin1Char('/') + assistant + QLatin1String(".desktop"); + } else { + kDebug() << "failed" << getJob->errorString(); + return QString(); + } +} diff --git a/kdepim-runtime/accountwizard/global.h b/kdepim-runtime/accountwizard/global.h new file mode 100644 index 00000000..666e5f4f --- /dev/null +++ b/kdepim-runtime/accountwizard/global.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009 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 GLOBAL_H +#define GLOBAL_H + +#include +#include + +namespace Global +{ + QStringList typeFilter(); + void setTypeFilter( const QStringList &filter ); + + QString assistant(); + void setAssistant( const QString &assistant ); + + QString assistantBasePath(); + + QString unpackAssistant( const KUrl& remotePackageUrl ); +} + +#endif diff --git a/kdepim-runtime/accountwizard/identity.cpp b/kdepim-runtime/accountwizard/identity.cpp new file mode 100644 index 00000000..417e673c --- /dev/null +++ b/kdepim-runtime/accountwizard/identity.cpp @@ -0,0 +1,152 @@ +/* + Copyright (c) 2010 Laurent Montel + + 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 "identity.h" +#include "transport.h" + +#include +#include + +#include + +Identity::Identity( QObject *parent ) + : SetupObject( parent ), + m_transport( 0 ) +{ + m_manager = new KPIMIdentities::IdentityManager( false, this, "mIdentityManager" ); + m_identity = &m_manager->newFromScratch( QString() ); + Q_ASSERT( m_identity != 0 ); +} + +Identity::~Identity() +{ + delete m_manager; +} + +void Identity::create() +{ + emit info( i18n( "Setting up identity..." ) ); + + // store identity information + // TODO now that we have the identity object around anyway we can probably get rid of most of the other members + m_identity->setIdentityName( identityName() ); + m_identity->setFullName( m_realName ); + m_identity->setPrimaryEmailAddress( m_email ); + m_identity->setOrganization( m_organization ); + if ( m_transport && m_transport->transportId() > 0 ) + m_identity->setTransport( QString::number( m_transport->transportId() ) ); + if ( !m_signature.isEmpty() ) { + const KPIMIdentities::Signature sig( m_signature ); + m_identity->setSignature( sig ); + } + if ( !m_prefCryptoFormat.isEmpty() ) + m_identity->setPreferredCryptoMessageFormat( m_prefCryptoFormat ); + if ( !m_xface.isEmpty() ) { + m_identity->setXFaceEnabled( true ); + m_identity->setXFace( m_xface ); + } + m_manager->setAsDefault( m_identity->uoid() ); + m_manager->commit(); + + emit finished( i18n( "Identity set up." ) ); +} + +QString Identity::identityName() const +{ + // create identity name + QString name( m_identityName ); + if ( name.isEmpty() ) { + name = i18nc( "Default name for new email accounts/identities.", "Unnamed" ); + + QString idName = m_email; + int pos = idName.indexOf( QLatin1Char('@') ); + if ( pos != -1 ) { + name = idName.mid( 0, pos ); + } + + // Make the name a bit more human friendly + name.replace( QLatin1Char('.'), QLatin1Char(' ') ); + pos = name.indexOf( QLatin1Char(' ') ); + if ( pos != 0 ) { + name[ pos + 1 ] = name[ pos + 1 ].toUpper(); + } + name[ 0 ] = name[ 0 ].toUpper(); + } + + if ( !m_manager->isUnique( name ) ) { + name = m_manager->makeUnique( name ); + } + return name; +} + + +void Identity::destroy() +{ + m_manager->removeIdentityForced( m_identity->identityName() ); + m_manager->commit(); + m_identity = 0; + emit info( i18n( "Identity removed." ) ); +} + +void Identity::setIdentityName(const QString& name) +{ + m_identityName = name; +} + +void Identity::setRealName( const QString &name ) +{ + m_realName = name; +} + +void Identity::setOrganization( const QString &org ) +{ + m_organization = org; +} + +void Identity::setEmail( const QString &email ) +{ + m_email = email; +} + +uint Identity::uoid() const +{ + return m_identity->uoid(); +} + +void Identity::setTransport(QObject* transport) +{ + m_transport = qobject_cast( transport ); + setDependsOn( qobject_cast( transport ) ); +} + +void Identity::setSignature(const QString& sig) +{ + m_signature = sig; +} + +void Identity::setPreferredCryptoMessageFormat(const QString& format) +{ + m_prefCryptoFormat = format; +} + +void Identity::setXFace(const QString& xface) +{ + m_xface = xface; +} + diff --git a/kdepim-runtime/accountwizard/identity.h b/kdepim-runtime/accountwizard/identity.h new file mode 100644 index 00000000..e5f10396 --- /dev/null +++ b/kdepim-runtime/accountwizard/identity.h @@ -0,0 +1,68 @@ +/* + Copyright (c) 2010 Laurent Montel + + 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 IDENTITY_H +#define IDENTITY_H + +#include "setupobject.h" + +class Transport; + +namespace KPIMIdentities { + class Identity; + class IdentityManager; +} + +class Identity : public SetupObject +{ + Q_OBJECT + public: + explicit Identity( QObject *parent = 0 ); + ~Identity(); + void create(); + void destroy(); + + public slots: + Q_SCRIPTABLE void setIdentityName( const QString &name ); + Q_SCRIPTABLE void setRealName( const QString & name ); + Q_SCRIPTABLE void setEmail( const QString &email ); + Q_SCRIPTABLE void setOrganization( const QString &org ); + Q_SCRIPTABLE void setSignature( const QString &sig ); + Q_SCRIPTABLE uint uoid() const; + Q_SCRIPTABLE void setTransport( QObject* transport ); + Q_SCRIPTABLE void setPreferredCryptoMessageFormat( const QString &format ); + Q_SCRIPTABLE void setXFace( const QString &xface ); + + protected: + QString identityName() const; + + private: + QString m_identityName; + QString m_realName; + QString m_email; + QString m_organization; + QString m_signature; + QString m_prefCryptoFormat; + QString m_xface; + Transport *m_transport; + KPIMIdentities::IdentityManager *m_manager; + KPIMIdentities::Identity *m_identity; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/inprocess-main.cpp b/kdepim-runtime/accountwizard/inprocess-main.cpp new file mode 100644 index 00000000..41f850d3 --- /dev/null +++ b/kdepim-runtime/accountwizard/inprocess-main.cpp @@ -0,0 +1,46 @@ +/* + Copyright (c) 2010 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 "dialog.h" +#include "global.h" + +#include +#include +#include +#include + +class AccountWizard : public QObject +{ + Q_OBJECT + public: + explicit AccountWizard( QObject* parent = 0 ) : QObject( parent ) {} + + public slots: + void run( const QStringList &types, QWidget *parent ) + { + if ( !types.isEmpty() ) + Global::setTypeFilter( types ); + Dialog dlg( parent ); + dlg.exec(); + } +}; + +Q_EXPORT_PLUGIN2( accountwizard, AccountWizard ) + +#include "inprocess-main.moc" diff --git a/kdepim-runtime/accountwizard/ispdb/CMakeLists.txt b/kdepim-runtime/accountwizard/ispdb/CMakeLists.txt new file mode 100644 index 00000000..a2e2be41 --- /dev/null +++ b/kdepim-runtime/accountwizard/ispdb/CMakeLists.txt @@ -0,0 +1,15 @@ + +set(ispdb_srcs + main.cpp + ispdb.cpp +) + +kde4_add_executable(ispdb ${ispdb_srcs}) + +target_link_libraries(ispdb + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} +) + +install(TARGETS ispdb ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/accountwizard/ispdb/ispdb.cpp b/kdepim-runtime/accountwizard/ispdb/ispdb.cpp new file mode 100644 index 00000000..ddc16646 --- /dev/null +++ b/kdepim-runtime/accountwizard/ispdb/ispdb.cpp @@ -0,0 +1,277 @@ +/* + Copyright (c) 2010 Omat Holding B.V. + + 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 "ispdb.h" +#include +#include +#include +#include + +#include +#include + +Ispdb::Ispdb( QObject *parent ) + : QObject( parent ), mServerType( DataBase ) +{ +} + +Ispdb::~Ispdb() +{ +} + +void Ispdb::setEmail( const QString& address ) +{ + KMime::Types::Mailbox box; + box.fromUnicodeString( address ); + mAddr = box.addrSpec(); +} + +void Ispdb::start() +{ + kDebug() << mAddr.asString(); + // we should do different things in here. But lets focus in the db first. + lookupInDb(); +} + +void Ispdb::startJob( const KUrl&url ) +{ + QMap< QString, QVariant > map; + map[QLatin1String("errorPage")] = false; + + KIO::TransferJob* job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo ); + job->setMetaData( map ); + connect( job, SIGNAL(result(KJob*)), + this, SLOT(slotResult(KJob*)) ); + connect( job, SIGNAL(data(KIO::Job*,QByteArray)), + this, SLOT(dataArrived(KIO::Job*,QByteArray)) ); +} + +void Ispdb::lookupInDb() +{ + KUrl url; + switch( mServerType ) + { + case IspAutoConfig: + { + url = KUrl( QLatin1String("http://autoconfig.") + mAddr.domain.toLower() + QLatin1String("/mail/config-v1.1.xml?emailaddress=") + mAddr.asString().toLower() ); + Q_EMIT searchType(i18n("Lookup configuration: Email provider")); + break; + } + case IspWellKnow: + { + url = KUrl( QLatin1String("http://") + mAddr.domain.toLower() + QLatin1String("/.well-known/autoconfig/mail/config-v1.1.xml") ); + Q_EMIT searchType(i18n("Lookup configuration: Trying common server name")); + break; + } + case DataBase: + { + url = KUrl( QLatin1String("https://autoconfig.thunderbird.net/v1.1/") + mAddr.domain.toLower() ); + Q_EMIT searchType(i18n("Lookup configuration: Mozilla database")); + break; + } + } + startJob( url ); +} + +void Ispdb::slotResult( KJob* job ) +{ + if ( job->error() ) { + kDebug() << "Fetching failed" << job->errorString(); + bool lookupFinished = false; + + switch( mServerType ) { + case IspAutoConfig: { + mServerType = IspWellKnow; + break; + } + case IspWellKnow: { + lookupFinished = true; + break; + } + case DataBase: { + mServerType = IspAutoConfig; + break; + } + } + + if ( lookupFinished ) + { + emit finished( false ); + return; + } + lookupInDb(); + return; + } + + //kDebug() << mData; + QDomDocument document; + bool ok = document.setContent( mData ); + if ( !ok ) { + kDebug() << "Could not parse xml" << mData; + emit finished( false ); + return; + } + + QDomElement docElem = document.documentElement(); + QDomNode m = docElem.firstChild(); // emailprovider + QDomNode n = m.firstChild(); // emailprovider + + while ( !n.isNull() ) { + QDomElement e = n.toElement(); + if ( !e.isNull() ) { + //kDebug() << qPrintable( e.tagName() ); + const QString tagName( e.tagName() ); + if ( tagName == QLatin1String( "domain" ) ) + mDomains << e.text(); + else if ( tagName == QLatin1String( "displayName" ) ) + mDisplayName = e.text(); + else if ( tagName == QLatin1String( "displayShortName" ) ) + mDisplayShortName = e.text(); + else if ( tagName == QLatin1String( "incomingServer" ) + && e.attribute( QLatin1String("type") ) == QLatin1String( "imap" ) ) { + server s = createServer( e ); + if (s.isValid()) + mImapServers.append( s ); + } else if ( tagName == QLatin1String( "incomingServer" ) + && e.attribute( QLatin1String("type") ) == QLatin1String( "pop3" ) ) { + server s = createServer( e ); + if (s.isValid()) + mPop3Servers.append( s ); + } else if ( tagName == QLatin1String( "outgoingServer" ) + && e.attribute( QLatin1String("type") ) == QLatin1String( "smtp" ) ) { + server s = createServer( e ); + if (s.isValid()) + mSmtpServers.append( s ); + } + } + n = n.nextSibling(); + } + + // comment this section out when you are tired of it... + kDebug() << "------------------ summary --------------"; + kDebug() << "Domains" << mDomains; + kDebug() << "Name" << mDisplayName << "(" << mDisplayShortName << ")"; + kDebug() << "Imap servers:"; + foreach ( const server& s, mImapServers ) { + kDebug() << s.hostname << s.port << s.socketType << s.username << s.authentication; + } + kDebug() << "pop3 servers:"; + foreach ( const server& s, mPop3Servers ) { + kDebug() << s.hostname << s.port << s.socketType << s.username << s.authentication; + } + kDebug() << "smtp servers:"; + foreach ( const server& s, mSmtpServers ) { + kDebug() << s.hostname << s.port << s.socketType << s.username << s.authentication; + } + // end section. + + emit finished( true ); +} + +server Ispdb::createServer( const QDomElement& n ) +{ + QDomNode o = n.firstChild(); + server s; + while ( !o.isNull() ) { + QDomElement f = o.toElement(); + if ( !f.isNull() ) { + const QString tagName( f.tagName() ); + if ( tagName == QLatin1String( "hostname" ) ) + s.hostname = replacePlaceholders( f.text() ); + else if ( tagName == QLatin1String( "port" ) ) + s.port = f.text().toInt(); + else if ( tagName == QLatin1String( "socketType" ) ) { + const QString type( f.text() ); + if ( type == QLatin1String( "plain" ) ) + s.socketType = None; + else if ( type == QLatin1String( "SSL" ) ) + s.socketType = SSL; + if ( type == QLatin1String( "STARTTLS" ) ) + s.socketType = StartTLS; + } else if ( tagName == QLatin1String( "username" ) ) { + s.username = replacePlaceholders( f.text() ); + } else if ( tagName == QLatin1String( "authentication" ) ) { + const QString type( f.text() ); + if ( type == QLatin1String( "password-cleartext" ) + || type == QLatin1String( "plain" ) ) + s.authentication = Plain; + else if ( type == QLatin1String( "password-encrypted" ) + || type == QLatin1String( "secure" ) ) + s.authentication = CramMD5; + else if ( type == QLatin1String( "NTLM" ) ) + s.authentication = NTLM; + else if ( type == QLatin1String( "GSSAPI" ) ) + s.authentication = GSSAPI; + else if ( type == QLatin1String( "client-ip-based" ) ) + s.authentication = ClientIP; + else if ( type == QLatin1String( "none" ) ) + s.authentication = NoAuth; + } + } + o = o.nextSibling(); + } + return s; +} + +QString Ispdb::replacePlaceholders( const QString& in ) +{ + QString out( in ); + out.replace( QLatin1String("%EMAILLOCALPART%"), mAddr.localPart ); + out.replace( QLatin1String("%EMAILADDRESS%"), mAddr.asString() ); + out.replace( QLatin1String("%EMAILDOMAIN%"), mAddr.domain ); + return out; +} + +void Ispdb::dataArrived( KIO::Job*, const QByteArray& data ) +{ + mData.append( data ); +} + +// The getters + +QStringList Ispdb::relevantDomains() const +{ + return mDomains; +} + +QString Ispdb::name( length l ) const +{ + if ( l == Long ) + return mDisplayName; + else if ( l == Short ) + return mDisplayShortName; + else + return QString(); //make compiler happy. Not me. +} + +QList< server > Ispdb::imapServers() const +{ + return mImapServers; +} + +QList< server > Ispdb::pop3Servers() const +{ + return mPop3Servers; +} + +QList< server > Ispdb::smtpServers() const +{ + return mSmtpServers; +} + diff --git a/kdepim-runtime/accountwizard/ispdb/ispdb.h b/kdepim-runtime/accountwizard/ispdb/ispdb.h new file mode 100644 index 00000000..c29ef6a5 --- /dev/null +++ b/kdepim-runtime/accountwizard/ispdb/ispdb.h @@ -0,0 +1,133 @@ +/* + Copyright (c) 2010 Omat Holding B.V. + + 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 ISPDB_H +#define ISPDB_H + +#include + +#include +#include + +class QDomElement; + +struct server; + +/** + This class will search in Mozilla's database for an xml file + describing the isp data belonging to that address. This class + searches and wraps the result for further usage. References: + https://wiki.mozilla.org/Thunderbird:Autoconfiguration + https://developer.mozilla.org/en/Thunderbird/Autoconfiguration + https://ispdb.mozillamessaging.com/ +*/ +class Ispdb : public QObject +{ + Q_OBJECT +public: + enum socketType { None = 0, SSL, StartTLS }; + + /** + Ispdb uses custom authtyps, hence the enum here. + @see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat + In particular, note that Ispdb's Plain represents both Cleartext and AUTH Plain + We will always treat it as Cleartext + */ + enum authType { Plain = 0, CramMD5, NTLM, GSSAPI, ClientIP, NoAuth }; + enum length { Long = 0, Short }; + + /** Constructor */ + explicit Ispdb( QObject *parent = 0 ); + + /** Destructor */ + ~Ispdb(); + + /** After finished() has been emitted you can + retrieve the domains that are covered by these + settings */ + QStringList relevantDomains() const; + + /** After finished() has been emitted you can + get the name of the provider, you can get a long + name and a short one */ + QString name( length ) const; + + /** After finished() has been emitted you can + get a list of imap servers available for this provider */ + QList< server > imapServers() const; + + /** After finished() has been emitted you can + get a list of pop3 servers available for this provider */ + QList< server > pop3Servers() const; + + /** After finished() has been emitted you can + get a list of smtp servers available for this provider */ + QList< server > smtpServers() const; + +public slots: + /** Sets the emailaddress you want to servers for */ + void setEmail( const QString& ); + + /** Starts looking up the servers which belong to the e-mailaddress */ + void start(); + +private slots: + void slotResult( KJob* ); + void dataArrived( KIO::Job*, const QByteArray& data ); + +signals: + /** emitted when done. **/ + void finished( bool ); + void searchType( const QString &type ); + +private: + enum searchServerType { IspAutoConfig = 0, IspWellKnow, DataBase }; + + server createServer( const QDomElement& n ); + void lookupInDb(); + QString replacePlaceholders( const QString& ); + void startJob( const KUrl&url ); + + KMime::Types::AddrSpec mAddr; // emailaddress + QByteArray mData; // storage of incoming data from kio + + // storage of the results + QStringList mDomains; + QString mDisplayName, mDisplayShortName; + QList< server > mImapServers, mPop3Servers, mSmtpServers; + Ispdb::searchServerType mServerType; +}; + +struct server { + server() { + port = -1; + authentication = Ispdb::Plain; + socketType = Ispdb::None; + } + bool isValid() const { + return (port != -1); + } + QString hostname; + int port; + Ispdb::socketType socketType; + QString username; + Ispdb::authType authentication; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/ispdb/main.cpp b/kdepim-runtime/accountwizard/ispdb/main.cpp new file mode 100644 index 00000000..0f76d7dc --- /dev/null +++ b/kdepim-runtime/accountwizard/ispdb/main.cpp @@ -0,0 +1,61 @@ +/* + Copyright (c) 2010 Omat Holding B.V. + + 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 "ispdb.h" + +#include +#include +#include +#include + +int main( int argc, char **argv ) +{ + KAboutData aboutData( "ispdb", 0, + ki18n( "ISPDB Assistant" ), + "0.1", + ki18n( "Helps setting up PIM accounts" ), + KAboutData::License_LGPL, + ki18n( "(c) 2010 Omat Holding B.V." ), + KLocalizedString(), + "http://pim.kde.org/akonadi/" ); + aboutData.setProgramIconName( "akonadi" ); + aboutData.addAuthor( ki18n( "Tom Albers" ), ki18n( "Author" ), "toma@kde.org" ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineOptions options; + options.add( "email ", ki18n( "Tries to fetch the settings for that email address" ) ); + KCmdLineArgs::addCmdLineOptions( options ); + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + QString email; + if ( !args->getOption( "email" ).isEmpty() ) + email = args->getOption( "email" ); + else + email = "blablabla@gmail.com"; + + + KApplication app; + + Ispdb ispdb; + ispdb.setEmail( email ); + ispdb.start(); + + return app.exec(); + +} diff --git a/kdepim-runtime/accountwizard/ldap.cpp b/kdepim-runtime/accountwizard/ldap.cpp new file mode 100644 index 00000000..14b84f66 --- /dev/null +++ b/kdepim-runtime/accountwizard/ldap.cpp @@ -0,0 +1,117 @@ +/* + Copyright (c) 2010 Laurent Montel + + 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 "ldap.h" + +#include +#include +#include + + +Ldap::Ldap( QObject *parent ) + : SetupObject( parent ) +{ +} + +Ldap::~Ldap() +{ +} + +void Ldap::create() +{ + emit info( i18n( "Setting up LDAP server..." ) ); + + if ( m_server.isEmpty() || m_user.isEmpty() ) + return; + + const QString host = m_server; + + // Figure out the basedn + QString basedn = host; + // If the user gave a full email address, the domain name + // of that overrides the server name for the ldap dn + const QString user = m_user; + int pos = user.indexOf( QLatin1String("@") ); + if ( pos > 0 ) { + const QString h = user.mid( pos+1 ); + if ( !h.isEmpty() ) + // The user did type in a domain on the email address. Use that + basedn = h; + } + { // while we're here, write default domain + KConfig c( QLatin1String("kmail2rc") ); + KConfigGroup group = c.group( "General" ); + group.writeEntry( "Default domain", basedn ); + } + + basedn.replace( QLatin1Char('.'), QLatin1String(",dc=") ); + basedn.prepend( QLatin1String("dc=") ); + + // Set the changes + KConfig c( QLatin1String("kabldaprc") ); + KConfigGroup group = c.group( "LDAP" ); + bool hasMyServer = false; + uint selHosts = group.readEntry( "NumSelectedHosts", 0 ); + for ( uint i = 0 ; i < selHosts && !hasMyServer; ++i ) + if ( group.readEntry( QString::fromLatin1( "SelectedHost%1" ).arg( i ), QString() ) == host ) + hasMyServer = true; + if ( !hasMyServer ) { + group.writeEntry( "NumSelectedHosts", selHosts + 1 ); + group.writeEntry( QString::fromLatin1( "SelectedHost%1" ).arg( selHosts ), host ); + group.writeEntry( QString::fromLatin1( "SelectedBase%1" ).arg( selHosts ), basedn ); + group.writeEntry( QString::fromLatin1( "SelectedPort%1" ).arg( selHosts ), "389" ); + if ( !m_authMethod.isEmpty() ) { + group.writeEntry( QString::fromLatin1( "SelectedAuth%1" ).arg( selHosts ), m_authMethod ); + group.writeEntry( QString::fromLatin1( "SelectedBind%1" ).arg( selHosts ), m_bindDn ); + group.writeEntry( QString::fromLatin1( "SelectedPwdBind%1" ).arg( selHosts ), m_password ); + } + } + emit finished( i18n( "LDAP set up." ) ); +} + +void Ldap::destroy() +{ + emit info( i18n( "LDAP not configuring." ) ); +} + +void Ldap::setUser( const QString &user ) +{ + m_user = user; +} + +void Ldap::setServer( const QString &server ) +{ + m_server = server; +} + +void Ldap::setAuthenticationMethod(const QString& meth) +{ + m_authMethod = meth; +} + +void Ldap::setBindDn(const QString& bindDn) +{ + m_bindDn = bindDn; +} + +void Ldap::setPassword(const QString& password) +{ + m_password = password; +} + diff --git a/kdepim-runtime/accountwizard/ldap.h b/kdepim-runtime/accountwizard/ldap.h new file mode 100644 index 00000000..4da5c02b --- /dev/null +++ b/kdepim-runtime/accountwizard/ldap.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2010 Laurent Montel + + 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 LDAP_H +#define LDAP_H + +#include "setupobject.h" + + +class Ldap : public SetupObject +{ + Q_OBJECT + public: + explicit Ldap( QObject *parent = 0 ); + ~Ldap(); + void create(); + void destroy(); + public slots: + Q_SCRIPTABLE void setUser( const QString & name ); + Q_SCRIPTABLE void setServer( const QString &server ); + Q_SCRIPTABLE void setAuthenticationMethod( const QString &meth ); + Q_SCRIPTABLE void setBindDn( const QString &bindDn ); + Q_SCRIPTABLE void setPassword( const QString &password ); + private: + QString m_user; + QString m_server; + QString m_bindDn; + QString m_authMethod; + QString m_password; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/loadpage.cpp b/kdepim-runtime/accountwizard/loadpage.cpp new file mode 100644 index 00000000..fa763b0f --- /dev/null +++ b/kdepim-runtime/accountwizard/loadpage.cpp @@ -0,0 +1,88 @@ +/* + Copyright (c) 2009 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 "loadpage.h" +#include "global.h" +#include +#include +#include +#include +#include + +LoadPage::LoadPage(KAssistantDialog* parent) : + Page( parent ), + m_action( 0 ) +{ + ui.setupUi( this ); + setValid( false ); +} + +void LoadPage::enterPageNext() +{ + setValid( false ); + // FIXME: deletion seems to delete the exported objects as well, killing the entire wizard... + //delete m_action; + m_action = 0; + emit aboutToStart(); + + const KConfig f( Global::assistant() ); + KConfigGroup grp( &f, "Wizard" ); + const QString scriptFile = grp.readEntry( "Script", QString() ); + if ( scriptFile.isEmpty() ) { + ui.statusLabel->setText( i18n( "No script specified in '%1'.", Global::assistant() ) ); + return; + } + if ( !QFile::exists( Global::assistantBasePath() + scriptFile ) ) { + ui.statusLabel->setText( i18n( "Unable to load assistant: File '%1' does not exist.", Global::assistantBasePath() + scriptFile ) ); + return; + } + ui.statusLabel->setText( i18n( "Loading script '%1'...", Global::assistantBasePath() + scriptFile ) ); + + m_action = new Kross::Action( this, QLatin1String("AccountWizard") ); + typedef QPair ObjectStringPair; + foreach ( const ObjectStringPair &exportedObject, m_exportedObjects ) + m_action->addQObject( exportedObject.first, exportedObject.second ); + + if ( !m_action->setFile( Global::assistantBasePath() + scriptFile ) ) { + ui.statusLabel->setText( i18n( "Failed to load script: '%1'.", m_action->errorMessage() ) ); + return; + } + + KConfigGroup grpTranslate( &f, "Translate" ); + const QString poFileName = grpTranslate.readEntry( "Filename" ); + if ( !poFileName.isEmpty() ) + KGlobal::locale()->insertCatalog( poFileName ); + + m_action->trigger(); + + m_parent->next(); +} + +void LoadPage::enterPageBack() +{ + // TODO: if we are the first page, call enterPageNext(), hm, can we get here then at all? + m_parent->back(); +} + +void LoadPage::exportObject(QObject* object, const QString& name) +{ + m_exportedObjects.push_back( qMakePair( object, name ) ); +} + + diff --git a/kdepim-runtime/accountwizard/loadpage.h b/kdepim-runtime/accountwizard/loadpage.h new file mode 100644 index 00000000..a53f9e2e --- /dev/null +++ b/kdepim-runtime/accountwizard/loadpage.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2009 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 LOADPAGE_H +#define LOADPAGE_H + +#include "page.h" + +#include "ui_loadpage.h" + +namespace Kross { +class Action; +} + +class LoadPage : public Page +{ + Q_OBJECT + public: + explicit LoadPage( KAssistantDialog *parent ); + + virtual void enterPageNext(); + virtual void enterPageBack(); + + void exportObject( QObject *object, const QString &name ); + + Q_SIGNALS: + void aboutToStart(); + + private: + Ui::LoadPage ui; + QVector< QPair< QObject*, QString > > m_exportedObjects; + Kross::Action* m_action; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/main.cpp b/kdepim-runtime/accountwizard/main.cpp new file mode 100644 index 00000000..c091a101 --- /dev/null +++ b/kdepim-runtime/accountwizard/main.cpp @@ -0,0 +1,81 @@ +/* + Copyright (c) 2009 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 "dialog.h" +#include "global.h" + +#include + +#include +#include +#include +#include +#include + + +#include + +int main( int argc, char **argv ) +{ + KAboutData aboutData( "accountwizard", 0, + ki18n( "Account Assistant" ), + "0.1", + ki18n( "Helps setting up PIM accounts" ), + KAboutData::License_LGPL, + ki18n( "(c) 2009 the Akonadi developers" ), + KLocalizedString(), + "http://pim.kde.org/akonadi/" ); + aboutData.setProgramIconName( QLatin1String("akonadi") ); + aboutData.addAuthor( ki18n( "Volker Krause" ), ki18n( "Author" ), "vkrause@kde.org" ); + aboutData.addAuthor( ki18n( "Laurent Montel" ), KLocalizedString() , "montel@kde.org" ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + + KCmdLineOptions options; + options.add( "type ", ki18n( "Only offer accounts that support the given type." ) ); + options.add( "assistant ", ki18n( "Run the specified assistant." ) ); + options.add( "package ", ki18n( "unpack fullpath on startup and launch that assistant" ) ); + KCmdLineArgs::addCmdLineOptions( options ); + KUniqueApplication::addCmdLineOptions(); + + if ( !KUniqueApplication::start() ) { + fprintf( stderr, "accountwizard is already running!\n" ); + exit( 0 ); + } + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KUniqueApplication app; + KGlobal::locale()->insertCatalog( QLatin1String("libakonadi") ); + + Akonadi::Control::start( 0 ); + + if ( !args->getOption( "package" ).isEmpty() ) { + Global::setAssistant( Global::unpackAssistant( KUrl( args->getOption( "package" ) ) ) ); + } else + Global::setAssistant( args->getOption( "assistant" ) ); + + if ( !args->getOption( "type" ).isEmpty() ) + Global::setTypeFilter( args->getOption( "type" ).split( QLatin1Char(',') ) ); + args->clear(); + Dialog dlg( 0/*, Qt::WindowStaysOnTopHint*/ ); + dlg.show(); + + return app.exec(); +} diff --git a/kdepim-runtime/accountwizard/page.cpp b/kdepim-runtime/accountwizard/page.cpp new file mode 100644 index 00000000..78437d9b --- /dev/null +++ b/kdepim-runtime/accountwizard/page.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2009 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 "page.h" +#include +#include + +Page::Page(KAssistantDialog* parent): + QWidget(parent), + m_item( 0 ), + m_parent( parent ), + m_valid( false ) +{ +} + + +void Page::setPageWidgetItem(KPageWidgetItem* item) +{ + m_item = item; + m_parent->setValid( m_item, m_valid ); +} + + +void Page::setValid(bool valid) +{ + if ( !m_item ) + m_valid = valid; + else + m_parent->setValid( m_item, valid ); +} + +void Page::nextPage() +{ + m_parent->next(); +} + +void Page::enterPageBack() {} +void Page::enterPageNext() {} +void Page::leavePageBack() {} +void Page::leavePageNext() {} + +void Page::leavePageBackRequested() { emit leavePageBackOk(); } +void Page::leavePageNextRequested() { emit leavePageNextOk(); } + diff --git a/kdepim-runtime/accountwizard/page.h b/kdepim-runtime/accountwizard/page.h new file mode 100644 index 00000000..1d229f01 --- /dev/null +++ b/kdepim-runtime/accountwizard/page.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2009 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 PAGE_H +#define PAGE_H + +#include + +class KAssistantDialog; +class KPageWidgetItem; + +class Page : public QWidget +{ + Q_OBJECT + public: + explicit Page( KAssistantDialog *parent ); + + void setPageWidgetItem( KPageWidgetItem *item ); + + virtual void enterPageNext(); + virtual void enterPageBack(); + virtual void leavePageNext(); + virtual void leavePageBack(); + virtual void leavePageNextRequested(); + virtual void leavePageBackRequested(); + + signals: + Q_SCRIPTABLE void pageEnteredNext(); + Q_SCRIPTABLE void pageEnteredBack(); + Q_SCRIPTABLE void pageLeftNext(); + Q_SCRIPTABLE void pageLeftBack(); + Q_SCRIPTABLE void leavePageNextOk(); + Q_SCRIPTABLE void leavePageBackOk(); + + public slots: + Q_SCRIPTABLE void setValid( bool valid ); + Q_SCRIPTABLE void nextPage(); + + protected: + KPageWidgetItem *m_item; + KAssistantDialog *m_parent; + + private: + friend class Dialog; + bool m_valid; +}; + +#endif // PAGE_H diff --git a/kdepim-runtime/accountwizard/personaldatapage.cpp b/kdepim-runtime/accountwizard/personaldatapage.cpp new file mode 100644 index 00000000..e7cde5ca --- /dev/null +++ b/kdepim-runtime/accountwizard/personaldatapage.cpp @@ -0,0 +1,278 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2010 Tom Albers + Copyright (c) 2012 Laurent Montel + + 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 "personaldatapage.h" + +#include "config-enterprise.h" +#include "global.h" +#include "dialog.h" +#include "transport.h" +#include "resource.h" +#include "ispdb/ispdb.h" + +#include +#include + +#include + +#include + + +QString accountName(Ispdb *ispdb, QString username) +{ + const int pos(username.indexOf(QLatin1Char('@'))); + username = username.left(pos); + return ispdb->name( Ispdb::Long ) + QString::fromLatin1(" (%1)").arg(username); +} + +PersonalDataPage::PersonalDataPage(Dialog* parent) : + Page( parent ), mIspdb( 0 ), mSetupManager( parent->setupManager() ) +{ + QWidget *pageParent = this; + + ui.setupUi( pageParent ); + + KPIMUtils::EmailValidator* emailValidator = new KPIMUtils::EmailValidator( this ); + ui.emailEdit->setValidator( emailValidator ); + + // KEmailSettings defaults + ui.nameEdit->setText( mSetupManager->name() ); + ui.emailEdit->setText( mSetupManager->email() ); + slotTextChanged(); + connect( ui.emailEdit, SIGNAL(textChanged(QString)), SLOT(slotTextChanged()) ); + connect( ui.nameEdit, SIGNAL(textChanged(QString)), SLOT(slotTextChanged()) ); + connect( ui.createAccountPb, SIGNAL(clicked()), SLOT(slotCreateAccountClicked()) ); + connect( ui.buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)), SLOT(slotRadioButtonClicked(QAbstractButton*)) ); +#ifdef KDEPIM_ENTERPRISE_BUILD + ui.checkOnlineGroupBox->setChecked( false ); +#endif +} + +void PersonalDataPage::setHideOptionInternetSearch( bool hide ) +{ + ui.checkOnlineGroupBox->setChecked( !hide ); + ui.checkOnlineGroupBox->setVisible( !hide ); +} + +void PersonalDataPage::slotRadioButtonClicked( QAbstractButton* button) +{ + QString smptHostname; + if ( !mIspdb->smtpServers().isEmpty() ) { + server s = mIspdb->smtpServers().first(); + smptHostname = s.hostname; + } + ui.outgoingLabel->setText( i18n( "SMTP, %1", smptHostname ) ); + if ( button == ui.imapAccount ) { + server simap = mIspdb->imapServers().first(); // should be ok. + ui.incommingLabel->setText( i18n( "IMAP, %1", simap.hostname ) ); + ui.usernameLabel->setText( simap.username ); + } else if ( button == ui.pop3Account ) { + server spop3 = mIspdb->pop3Servers().first(); // should be ok. + ui.incommingLabel->setText( i18n( "POP3, %1", spop3.hostname ) ); + ui.usernameLabel->setText( spop3.username ); + } +} + +void PersonalDataPage::slotCreateAccountClicked() +{ + configureSmtpAccount(); + if ( ui.imapAccount->isChecked() ) + configureImapAccount(); + else + configurePop3Account(); + emit leavePageNextOk(); // go to the next page + mSetupManager->execute(); +} + +void PersonalDataPage::slotTextChanged() +{ + // Ignore the password field, as that can be empty when auth is based on ip-address. + setValid( !ui.emailEdit->text().isEmpty() && + !ui.nameEdit->text().isEmpty() && + KPIMUtils::isValidSimpleAddress( ui.emailEdit->text() ) ); +} + +void PersonalDataPage::leavePageNext() +{ + ui.stackedPage->setCurrentIndex( 0 ); + ui.imapAccount->setChecked( true ); + mSetupManager->setPersonalDataAvailable( true ); + mSetupManager->setName( ui.nameEdit->text() ); + mSetupManager->setPassword( ui.passwordEdit->text() ); + mSetupManager->setEmail( ui.emailEdit->text().trimmed() ); + + if ( ui.checkOnlineGroupBox->isChecked() ) { + // since the user can go back and forth, explicitly disable the man page + emit manualWanted( false ); + setCursor( Qt::BusyCursor ); + ui.mProgress->start(); + kDebug() << "Searching on internet"; + delete mIspdb; + mIspdb = new Ispdb( this ); + connect(mIspdb, SIGNAL(searchType(QString)), this, SLOT(slotSearchType(QString))); + mIspdb->setEmail( ui.emailEdit->text() ); + mIspdb->start(); + + connect( mIspdb, SIGNAL(finished(bool)), + SLOT(ispdbSearchFinished(bool)) ); + } else { + emit manualWanted( true ); // enable the manual page + emit leavePageNextOk(); // go to the next page + } +} + +void PersonalDataPage::ispdbSearchFinished( bool ok ) +{ + kDebug() << ok; + + unsetCursor(); + ui.mProgress->stop(); + if ( ok ) { + + if ( !mIspdb->imapServers().isEmpty() && !mIspdb->pop3Servers().isEmpty() ) + { + ui.stackedPage->setCurrentIndex( 1 ); + slotRadioButtonClicked( ui.imapAccount ); + } + else + automaticConfigureAccount(); + + } else { + emit manualWanted( true ); // enable the manual page + emit leavePageNextOk(); + } +} + +void PersonalDataPage::slotSearchType(const QString &type) +{ + ui.mProgress->setActiveLabel(type); +} + +void PersonalDataPage::configureSmtpAccount() +{ + if ( !mIspdb->smtpServers().isEmpty() ) { + server s = mIspdb->smtpServers().first(); // should be ok. + kDebug() << "Configuring transport for" << s.hostname; + + QObject* object = mSetupManager->createTransport( QLatin1String("smtp") ); + Transport* t = qobject_cast( object ); + t->setName( accountName(mIspdb,s.username) ); + t->setHost( s.hostname ); + t->setPort( s.port ); + t->setUsername( s.username ); + t->setPassword( ui.passwordEdit->text() ); + switch ( s.authentication ) { + case Ispdb::Plain: t->setAuthenticationType( QLatin1String("plain") ); break; + case Ispdb::CramMD5: t->setAuthenticationType( QLatin1String("cram-md5") ); break; + case Ispdb::NTLM: t->setAuthenticationType( QLatin1String("ntlm") ); break; + case Ispdb::GSSAPI: t->setAuthenticationType( QLatin1String("gssapi") ); break; + case Ispdb::ClientIP: break; + case Ispdb::NoAuth: break; + default: break; + } + switch ( s.socketType ) { + case Ispdb::Plain: t->setEncryption( QLatin1String("none") );break; + case Ispdb::SSL: t->setEncryption( QLatin1String("ssl") );break; + case Ispdb::StartTLS: t->setEncryption( QLatin1String("tls") );break; + default: break; + } + } else + kDebug() << "No transport to be created...."; +} + +void PersonalDataPage::configureImapAccount() +{ + if ( !mIspdb->imapServers().isEmpty() ) { + server s = mIspdb->imapServers().first(); // should be ok. + kDebug() << "Configuring imap for" << s.hostname; + + QObject* object = mSetupManager->createResource( QLatin1String("akonadi_imap_resource") ); + Resource* t = qobject_cast( object ); + t->setName( accountName(mIspdb,s.username) ); + t->setOption( QLatin1String("ImapServer"), s.hostname ); + t->setOption( QLatin1String("ImapPort"), s.port ); + t->setOption( QLatin1String("UserName"), s.username ); + t->setOption( QLatin1String("Password"), ui.passwordEdit->text() ); + switch ( s.authentication ) { + case Ispdb::Plain: t->setOption( QLatin1String("Authentication"), MailTransport::Transport::EnumAuthenticationType::CLEAR ); break; + case Ispdb::CramMD5: t->setOption( QLatin1String("Authentication"), MailTransport::Transport::EnumAuthenticationType::CRAM_MD5 ); break; + case Ispdb::NTLM: t->setOption( QLatin1String("Authentication"), MailTransport::Transport::EnumAuthenticationType::NTLM ); break; + case Ispdb::GSSAPI: t->setOption( QLatin1String("Authentication"), MailTransport::Transport::EnumAuthenticationType::GSSAPI ); break; + case Ispdb::ClientIP: break; + case Ispdb::NoAuth: break; + default: break; + } + switch ( s.socketType ) { + case Ispdb::None: t->setOption( QLatin1String("Safety"), QLatin1String("None") );break; + case Ispdb::SSL: t->setOption( QLatin1String("Safety"), QLatin1String("SSL") );break; + case Ispdb::StartTLS: t->setOption( QLatin1String("Safety"), QLatin1String("STARTTLS") );break; + default: break; + } + } +} + +void PersonalDataPage::configurePop3Account() +{ + if ( !mIspdb->pop3Servers().isEmpty() ) { + server s = mIspdb->pop3Servers().first(); // should be ok. + kDebug() << "No Imap to be created, configuring pop3 for" << s.hostname; + + QObject* object = mSetupManager->createResource( QLatin1String("akonadi_pop3_resource") ); + Resource* t = qobject_cast( object ); + t->setName( accountName(mIspdb,s.username) ); + t->setOption( QLatin1String("Host"), s.hostname ); + t->setOption( QLatin1String("Port"), s.port ); + t->setOption( QLatin1String("Login"), s.username ); + t->setOption( QLatin1String("Password"), ui.passwordEdit->text() ); + switch ( s.authentication ) { + case Ispdb::Plain: t->setOption( QLatin1String("AuthenticationMethod"), MailTransport::Transport::EnumAuthenticationType::PLAIN ); break; + case Ispdb::CramMD5: t->setOption( QLatin1String("AuthenticationMethod"), MailTransport::Transport::EnumAuthenticationType::CRAM_MD5 ); break; + case Ispdb::NTLM: t->setOption( QLatin1String("AuthenticationMethod"), MailTransport::Transport::EnumAuthenticationType::NTLM ); break; + case Ispdb::GSSAPI: t->setOption( QLatin1String("AuthenticationMethod"), MailTransport::Transport::EnumAuthenticationType::GSSAPI ); break; + case Ispdb::ClientIP: + case Ispdb::NoAuth: + default: t->setOption( QLatin1String("AuthenticationMethod"), MailTransport::Transport::EnumAuthenticationType::CLEAR ); break; + } + switch ( s.socketType ) { + case Ispdb::SSL: t->setOption( QLatin1String("UseSSL"), 1 );break; + case Ispdb::StartTLS: t->setOption( QLatin1String("UseTLS"), 1 );break; + case Ispdb::None: + default: t->setOption( QLatin1String("UseTLS"), 1 ); break; + } + } +} + +void PersonalDataPage::automaticConfigureAccount() +{ + configureSmtpAccount(); + configureImapAccount(); + configurePop3Account(); + emit leavePageNextOk(); // go to the next page + mSetupManager->execute(); +} + +void PersonalDataPage::leavePageNextRequested() +{ + // override base class with doing nothing... +} + + + diff --git a/kdepim-runtime/accountwizard/personaldatapage.h b/kdepim-runtime/accountwizard/personaldatapage.h new file mode 100644 index 00000000..bf26a087 --- /dev/null +++ b/kdepim-runtime/accountwizard/personaldatapage.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2010 Tom Albers + Copyright (c) 2012 Laurent Montel + + 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 PERSONALDATA_H +#define PERSONALDATA_H + +#include "page.h" +#include "setupmanager.h" +#include "dialog.h" + +#include "ui_personaldatapage.h" + +class Ispdb; + +class PersonalDataPage : public Page +{ + Q_OBJECT + public: + explicit PersonalDataPage( Dialog* parent = 0 ); + void setHideOptionInternetSearch( bool ); + + virtual void leavePageNext(); + virtual void leavePageNextRequested(); + + private slots: + void ispdbSearchFinished( bool ok ); + void slotTextChanged(); + void slotCreateAccountClicked(); + void slotRadioButtonClicked( QAbstractButton* button); + void slotSearchType(const QString&); + + signals: + void manualWanted( bool ); + + private: + void automaticConfigureAccount(); + void configureSmtpAccount(); + void configureImapAccount(); + void configurePop3Account(); + + Ui::PersonalDataPage ui; + Ispdb* mIspdb; + SetupManager *mSetupManager; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/providerpage.cpp b/kdepim-runtime/accountwizard/providerpage.cpp new file mode 100644 index 00000000..e1afa1c9 --- /dev/null +++ b/kdepim-runtime/accountwizard/providerpage.cpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2010 Tom Albers + + 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 "providerpage.h" +#include "global.h" + +#include +#include + +ProviderPage::ProviderPage(KAssistantDialog* parent) : + Page( parent ), + m_model( new QStandardItemModel( this ) ), + m_newPageWanted( false ), + m_newPageReady( false ) +{ + ui.setupUi( this ); + + QSortFilterProxyModel *proxy = new QSortFilterProxyModel( this ); + proxy->setSourceModel( m_model ); + ui.listView->setModel( proxy ); + ui.searchLine->setProxy( proxy ); + + m_fetchItem = new QStandardItem( i18n( "Fetching provider list..." ) ); + m_fetchItem->setFlags(Qt::NoItemFlags); + m_model->appendRow( m_fetchItem ); + + connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged()) ); + + kDebug(); +} + +void ProviderPage::selectionChanged() +{ + if ( ui.listView->selectionModel()->hasSelection() ) { + setValid( true ); + } else { + setValid( false ); + } +} + +void ProviderPage::leavePageNext() +{ + m_newPageReady = false; + if ( !ui.listView->selectionModel()->hasSelection() ) + return; + const QModelIndex index = ui.listView->selectionModel()->selectedIndexes().first(); + if ( !index.isValid() ) + return; + + const QSortFilterProxyModel *proxy = static_cast( ui.listView->model() ); + const QStandardItem* item = m_model->itemFromIndex( proxy->mapToSource( index ) ); + kDebug() << "Item selected:"<< item->text(); + +} + +void ProviderPage::findDesktopAndSetAssistant( const QStringList& list ) +{ + foreach ( const QString& file, list ) { + kDebug() << file; + if ( file.endsWith( QLatin1String ( ".desktop" ) ) ) { + kDebug() << "Yay, a desktop file!" << file; + Global::setAssistant( file ); + m_newPageReady = true; + if ( m_newPageWanted ) { + kDebug() << "New page was already requested, now we are done, approve it"; + emit leavePageNextOk(); + } + break; + } + } +} + +QTreeView *ProviderPage::treeview() const +{ + return ui.listView; +} + +void ProviderPage::leavePageBackRequested() +{ + emit leavePageBackOk(); +} + +void ProviderPage::leavePageNextRequested() +{ + m_newPageWanted = true; + if ( m_newPageReady ) { + kDebug() << "New page requested and we are done, so ok..."; + emit leavePageNextOk(); + } else { + kDebug() << "New page requested, but we are not done yet..."; + } +} + diff --git a/kdepim-runtime/accountwizard/providerpage.h b/kdepim-runtime/accountwizard/providerpage.h new file mode 100644 index 00000000..9938dd37 --- /dev/null +++ b/kdepim-runtime/accountwizard/providerpage.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2010 Tom Albers + + 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 PROVIDERPAGE_H +#define PROVIDERPAGE_H + +#include "page.h" +#include + +#include "ui_providerpage.h" + +struct Provider { + QString entryId; + QString entryProviderId; +}; + +class ProviderPage : public Page +{ + Q_OBJECT + public: + explicit ProviderPage( KAssistantDialog* parent = 0 ); + + virtual void leavePageNext(); + virtual void leavePageNextRequested(); + virtual void leavePageBackRequested(); + + QTreeView *treeview() const; + + private slots: + void selectionChanged(); + + private: + void findDesktopAndSetAssistant( const QStringList& list ); + + Ui::ProviderPage ui; + QStandardItemModel *m_model; + QStandardItem *m_fetchItem; + Provider m_wantedProvider; + bool m_newPageWanted; + bool m_newPageReady; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/resource.cpp b/kdepim-runtime/accountwizard/resource.cpp new file mode 100644 index 00000000..fab7d1c8 --- /dev/null +++ b/kdepim-runtime/accountwizard/resource.cpp @@ -0,0 +1,165 @@ +/* + Copyright (c) 2009 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 "resource.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace Akonadi; + +static QVariant::Type argumentType( const QMetaObject *mo, const QString &method ) +{ + QMetaMethod m; + for ( int i = 0; i < mo->methodCount(); ++i ) { + const QString signature = QString::fromLatin1( mo->method( i ).signature() ); + if ( signature.contains( method + QLatin1Char( '(' ) ) ) { + m = mo->method( i ); + break; + } + } + + if ( !m.signature() ) { + kWarning() << "Did not find D-Bus method: " << method << " available methods are:"; + for ( int i = 0; i < mo->methodCount(); ++i ) + kWarning() << mo->method( i ).signature(); + return QVariant::Invalid; + } + + const QList argTypes = m.parameterTypes(); + if ( argTypes.count() != 1 ) + return QVariant::Invalid; + + return QVariant::nameToType( argTypes.first() ); +} + +Resource::Resource(const QString& type, QObject* parent) : + SetupObject( parent ), + m_typeIdentifier( type ) +{ +} + +void Resource::setOption( const QString &key, const QVariant &value ) +{ + m_settings.insert( key, value ); +} + +void Resource::setName( const QString &name ) +{ + m_name = name; +} + +void Resource::create() +{ + const AgentType type = AgentManager::self()->type( m_typeIdentifier ); + if ( !type.isValid() ) { + emit error( i18n( "Resource type '%1' is not available.", m_typeIdentifier ) ); + return; + } + + // check if unique instance already exists + kDebug() << type.capabilities(); + if ( type.capabilities().contains( QLatin1String( "Unique" ) ) ) { + foreach ( const AgentInstance &instance, AgentManager::self()->instances() ) { + kDebug() << instance.type().identifier() << ( instance.type() == type ); + if ( instance.type() == type ) { + emit finished( i18n( "Resource '%1' is already set up.", type.name() ) ); + return; + } + } + } + + emit info( i18n( "Creating resource instance for '%1'...", type.name() ) ); + AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(instanceCreateResult(KJob*)) ); + job->start(); +} + + +void Resource::instanceCreateResult(KJob* job) +{ + if ( job->error() ) { + emit error( i18n( "Failed to create resource instance: %1", job->errorText() ) ); + return; + } + + m_instance = qobject_cast( job )->instance(); + + if ( !m_settings.isEmpty() ) { + emit info( i18n( "Configuring resource instance..." ) ); + QDBusInterface iface( QLatin1String("org.freedesktop.Akonadi.Resource.") + m_instance.identifier(), QLatin1String("/Settings") ); + if ( !iface.isValid() ) { + emit error( i18n( "Unable to configure resource instance." ) ); + return; + } + + // configure resource + if ( !m_name.isEmpty() ) + m_instance.setName( m_name ); + QMap::const_iterator end( m_settings.constEnd() ); + for ( QMap::const_iterator it = m_settings.constBegin(); it != end; ++it ) { + kDebug() << "Setting up " << it.key() << " for agent " << m_instance.identifier(); + const QString methodName = QString::fromLatin1( "set%1" ).arg( it.key() ); + QVariant arg = it.value(); + const QVariant::Type targetType = argumentType( iface.metaObject(), methodName ); + if ( !arg.canConvert( targetType ) ) { + emit error( i18n( "Could not convert value of setting '%1' to required type %2.", it.key(), QLatin1String(QVariant::typeToName( targetType )) ) ); + return; + } + arg.convert( targetType ); + QDBusReply reply = iface.call( methodName, arg ); + if ( !reply.isValid() ) { + emit error( i18n( "Could not set setting '%1': %2", it.key(), reply.error().message() ) ); + return; + } + } + m_instance.reconfigure(); + } + + emit finished( i18n( "Resource setup completed." ) ); +} + +void Resource::destroy() +{ + if ( m_instance.isValid() ) { + AgentManager::self()->removeInstance( m_instance ); + emit info( i18n( "Removed resource instance for '%1'.", m_instance.type().name() ) ); + } +} + +QString Resource::identifier() +{ + return m_instance.identifier(); +} + +void Resource::reconfigure() +{ + m_instance.reconfigure(); +} + + diff --git a/kdepim-runtime/accountwizard/resource.h b/kdepim-runtime/accountwizard/resource.h new file mode 100644 index 00000000..ab09ba1f --- /dev/null +++ b/kdepim-runtime/accountwizard/resource.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2009 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 RESOURCE_H +#define RESOURCE_H + +#include "setupobject.h" +#include +#include + +class KJob; + +class Resource : public SetupObject +{ + Q_OBJECT + public: + explicit Resource( const QString &type, QObject *parent = 0 ); + void create(); + void destroy(); + + public slots: + Q_SCRIPTABLE void setName( const QString &name ); + Q_SCRIPTABLE void setOption( const QString &key, const QVariant &value ); + Q_SCRIPTABLE QString identifier(); + Q_SCRIPTABLE void reconfigure(); + + private slots: + void instanceCreateResult( KJob* job ); + + private: + QString m_typeIdentifier, m_name; + QMap m_settings; + Akonadi::AgentInstance m_instance; +}; +#endif diff --git a/kdepim-runtime/accountwizard/servertest.cpp b/kdepim-runtime/accountwizard/servertest.cpp new file mode 100644 index 00000000..89fee3be --- /dev/null +++ b/kdepim-runtime/accountwizard/servertest.cpp @@ -0,0 +1,64 @@ +/* + Copyright (c) 2010 Tom Albers + + 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 "servertest.h" +#include +#include + +#include +#include +#include + +ServerTest::ServerTest( QObject* parent) : + QObject(parent), m_serverTest( new MailTransport::ServerTest( 0 ) ) +{ + kDebug() << "Welcome!"; + connect( m_serverTest, SIGNAL(finished(QList)), + SLOT(testFinished(QList)) ); +} + +ServerTest::~ServerTest() +{ + delete m_serverTest; +} + +void ServerTest::test( const QString server, const QString protocol ) +{ + kDebug() << server << protocol; + m_serverTest->setServer( server ); + m_serverTest->setProtocol( protocol ); + m_serverTest->start(); +} + +void ServerTest::testFinished( QList< int > list ) +{ + kDebug() << "types: " << list; + if ( list.contains( MailTransport::Transport::EnumEncryption::TLS ) ) { + emit testResult( QLatin1String("tls") ); + } else if ( list.contains( MailTransport::Transport::EnumEncryption::SSL ) ) { + emit testResult( QLatin1String("ssl") ); + } else { + KMessageBox::information( 0, i18n( "There seems to be a problem in reaching this server " + "or choosing a safe way to sent the credentials to server. We advise you to " + "check the settings of the account and adjust it manually if needed." ), + i18n( "Autodetecting settings failed" ) ); + emit testFail(); + } +} + diff --git a/kdepim-runtime/accountwizard/servertest.h b/kdepim-runtime/accountwizard/servertest.h new file mode 100644 index 00000000..26ced2db --- /dev/null +++ b/kdepim-runtime/accountwizard/servertest.h @@ -0,0 +1,54 @@ +/* + Copyright (c) 2010 Tom Albers + + 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 SERVERTEST_H +#define SERVERTEST_H + +#include + +namespace MailTransport { + class ServerTest; +} + +class ServerTest : public QObject +{ + Q_OBJECT + public: + explicit ServerTest( QObject *parent ); + ~ServerTest(); + + public slots: + /* @p protocol being 'imap' 'smtp' or 'pop3' */ + Q_SCRIPTABLE void test( const QString server, const QString protocol ); + + signals: + /* returns the advised setting, @p result begin 'ssl' 'tls' or 'none'. */ + void testResult( const QString& result ); + + /* returns if no connection is possible, test failed. */ + void testFail(); + + private slots: + void testFinished( QList< int > list ); + + private: + MailTransport::ServerTest* m_serverTest; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/setupmanager.cpp b/kdepim-runtime/accountwizard/setupmanager.cpp new file mode 100644 index 00000000..75e30231 --- /dev/null +++ b/kdepim-runtime/accountwizard/setupmanager.cpp @@ -0,0 +1,245 @@ +/* + Copyright (c) 2009 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 "setupmanager.h" +#include "resource.h" +#include "setuppage.h" +#include "transport.h" +#include "configfile.h" +#include "ldap.h" +#include "identity.h" + +#include +#include + +SetupManager::SetupManager( QWidget* parent) : + QObject(parent), + m_currentSetupObject( 0 ), + m_page( 0 ), + m_wallet( 0 ), + m_personalDataAvailable( false ), + m_rollbackRequested( false ) +{ + KEMailSettings e; + m_name = e.getSetting( KEMailSettings::RealName ); + m_email = e.getSetting( KEMailSettings::EmailAddress ); +} + +SetupManager::~SetupManager() +{ + delete m_wallet; +} + +void SetupManager::setSetupPage(SetupPage* page) +{ + m_page = page; +} + +QObject* SetupManager::createResource(const QString& type) +{ + return connectObject( new Resource( type, this ) ); +} + +QObject* SetupManager::createTransport(const QString& type) +{ + return connectObject( new Transport( type, this ) ); +} + +QObject* SetupManager::createConfigFile(const QString& fileName) +{ + return connectObject( new ConfigFile( fileName, this ) ); +} + +QObject* SetupManager::createLdap() +{ + return connectObject( new Ldap( this ) ); +} + + +QObject* SetupManager::createIdentity() +{ + return connectObject( new Identity( this ) ); +} + +static bool dependencyCompare( SetupObject *left, SetupObject *right ) +{ + if ( !left->dependsOn() && right->dependsOn() ) + return true; + return false; +} + +void SetupManager::execute() +{ + m_page->setStatus( i18n( "Setting up account..." ) ); + m_page->setValid( false ); + + // ### FIXME this is a bad over-simplification and would need a real topological sort + // but for current usage it is good enough + qStableSort( m_objectToSetup.begin(), m_objectToSetup.end(), dependencyCompare ); + setupNext(); +} + +void SetupManager::setupSucceeded(const QString& msg) +{ + Q_ASSERT( m_page ); + m_page->addMessage( SetupPage::Success, msg ); + if(m_currentSetupObject) { + m_setupObjects.append( m_currentSetupObject ); + m_currentSetupObject = 0; + } + setupNext(); +} + +void SetupManager::setupFailed(const QString& msg) +{ + Q_ASSERT( m_page ); + m_page->addMessage( SetupPage::Error, msg ); + if( m_currentSetupObject ) { + m_setupObjects.append( m_currentSetupObject ); + m_currentSetupObject = 0; + } + rollback(); +} + +void SetupManager::setupInfo(const QString& msg) +{ + Q_ASSERT( m_page ); + m_page->addMessage( SetupPage::Info, msg ); +} + +void SetupManager::setupNext() +{ + // user canceld during the previous setup step + if ( m_rollbackRequested ) { + rollback(); + return; + } + + if ( m_objectToSetup.isEmpty() ) { + m_page->setStatus( i18n( "Setup complete." ) ); + m_page->setProgress( 100 ); + m_page->setValid( true ); + } else { + const int setupObjectCount = m_objectToSetup.size() + m_setupObjects.size(); + const int remainingObjectCount = setupObjectCount - m_objectToSetup.size(); + m_page->setProgress( ( remainingObjectCount * 100 ) / setupObjectCount ); + m_currentSetupObject = m_objectToSetup.takeFirst(); + m_currentSetupObject->create(); + } +} + +void SetupManager::rollback() +{ + m_page->setStatus( i18n( "Failed to set up account, rolling back..." ) ); + const int setupObjectCount = m_objectToSetup.size() + m_setupObjects.size(); + int remainingObjectCount = m_setupObjects.size(); + foreach ( SetupObject* obj, m_setupObjects ) { + m_page->setProgress( ( remainingObjectCount * 100 ) / setupObjectCount ); + if( obj ) { + obj->destroy(); + m_objectToSetup.prepend( obj ); + } + } + m_setupObjects.clear(); + m_page->setProgress( 0 ); + m_page->setStatus( i18n( "Failed to set up account." ) ); + m_page->setValid( true ); + m_rollbackRequested = false; + emit rollbackComplete(); +} + +SetupObject* SetupManager::connectObject(SetupObject* obj) +{ + connect( obj, SIGNAL(finished(QString)), SLOT(setupSucceeded(QString)) ); + connect( obj, SIGNAL(info(QString)), SLOT(setupInfo(QString)) ); + connect( obj, SIGNAL(error(QString)), SLOT(setupFailed(QString)) ); + m_objectToSetup.append( obj ); + return obj; +} + +void SetupManager::setName( const QString& name ) +{ + m_name = name; +} + +QString SetupManager::name() +{ + return m_name; +} + +void SetupManager::setEmail( const QString& email) +{ + m_email = email; +} + +QString SetupManager::email() +{ + return m_email; +} + +void SetupManager::setPassword( const QString& password) +{ + m_password = password; +} + +QString SetupManager::password() +{ + return m_password; +} + +QString SetupManager::country() +{ + return KGlobal::locale()->country(); +} + +void SetupManager::openWallet() +{ + using namespace KWallet; + if ( Wallet::isOpen( Wallet::NetworkWallet() ) ) + return; + + Q_ASSERT( parent()->isWidgetType() ); + m_wallet = Wallet::openWallet( Wallet::NetworkWallet(), qobject_cast( parent() )->effectiveWinId(), Wallet::Asynchronous ); + QEventLoop loop; + connect( m_wallet, SIGNAL(walletOpened(bool)), &loop, SLOT(quit()) ); + loop.exec(); +} + +bool SetupManager::personalDataAvailable() +{ + return m_personalDataAvailable; +} + +void SetupManager::setPersonalDataAvailable(bool available) +{ + m_personalDataAvailable = available; +} + +void SetupManager::requestRollback() +{ + if ( m_setupObjects.isEmpty() ) { + emit rollbackComplete(); + } else { + m_rollbackRequested = true; + if ( !m_currentSetupObject ) + rollback(); + } +} + + diff --git a/kdepim-runtime/accountwizard/setupmanager.h b/kdepim-runtime/accountwizard/setupmanager.h new file mode 100644 index 00000000..e1e0b072 --- /dev/null +++ b/kdepim-runtime/accountwizard/setupmanager.h @@ -0,0 +1,86 @@ +/* + Copyright (c) 2009 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 SETUPMANAGER_H +#define SETUPMANAGER_H + +#include + +namespace KWallet { + class Wallet; +} + +class SetupObject; +class SetupPage; + +class SetupManager : public QObject +{ + Q_OBJECT + public: + explicit SetupManager( QWidget *parent ); + ~SetupManager(); + void setSetupPage( SetupPage* page ); + + void setName( const QString& ); + void setEmail( const QString& ); + void setPassword( const QString& ); + void setPersonalDataAvailable( bool available ); + + public slots: + Q_SCRIPTABLE bool personalDataAvailable(); + Q_SCRIPTABLE QString name(); + Q_SCRIPTABLE QString email(); + Q_SCRIPTABLE QString password(); + Q_SCRIPTABLE QString country(); + /** Ensures the wallet is open for subsequent sync wallet access in the resources. */ + Q_SCRIPTABLE void openWallet(); + Q_SCRIPTABLE QObject* createResource( const QString &type ); + Q_SCRIPTABLE QObject* createTransport( const QString &type ); + Q_SCRIPTABLE QObject* createConfigFile( const QString &configName ); + Q_SCRIPTABLE QObject* createLdap(); + Q_SCRIPTABLE QObject* createIdentity(); + Q_SCRIPTABLE void execute(); + + void requestRollback(); + + signals: + void rollbackComplete(); + + private: + void setupNext(); + void rollback(); + SetupObject* connectObject( SetupObject* obj ); + + private slots: + void setupSucceeded( const QString &msg ); + void setupFailed( const QString &msg ); + void setupInfo( const QString &msg ); + + private: + QString m_name, m_email, m_password; + QList m_objectToSetup; + QList m_setupObjects; + SetupObject* m_currentSetupObject; + SetupPage* m_page; + KWallet::Wallet *m_wallet; + bool m_personalDataAvailable; + bool m_rollbackRequested; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/setupobject.cpp b/kdepim-runtime/accountwizard/setupobject.cpp new file mode 100644 index 00000000..744fe2f4 --- /dev/null +++ b/kdepim-runtime/accountwizard/setupobject.cpp @@ -0,0 +1,35 @@ +/* + Copyright (c) 2009 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 "setupobject.h" + +SetupObject::SetupObject(QObject* parent): QObject(parent), m_dependsOn( 0 ) +{ +} + +SetupObject* SetupObject::dependsOn() const +{ + return m_dependsOn; +} + +void SetupObject::setDependsOn(SetupObject* obj) +{ + m_dependsOn = obj; +} + diff --git a/kdepim-runtime/accountwizard/setupobject.h b/kdepim-runtime/accountwizard/setupobject.h new file mode 100644 index 00000000..7d8f65ee --- /dev/null +++ b/kdepim-runtime/accountwizard/setupobject.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2009 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 SETUPOBJECT_H +#define SETUPOBJECT_H + +#include + +class SetupObject : public QObject +{ + Q_OBJECT + public: + explicit SetupObject( QObject *parent ); + + virtual void create() = 0; + virtual void destroy() = 0; + + SetupObject* dependsOn() const; + void setDependsOn( SetupObject* obj ); + + signals: + void error( const QString &msg ); + void info( const QString &msg ); + void finished( const QString &msg ); + + private: + SetupObject *m_dependsOn; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/setuppage.cpp b/kdepim-runtime/accountwizard/setuppage.cpp new file mode 100644 index 00000000..d44003fc --- /dev/null +++ b/kdepim-runtime/accountwizard/setuppage.cpp @@ -0,0 +1,71 @@ +/* + Copyright (c) 2009 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 "setuppage.h" + +#include + +SetupPage::SetupPage(KAssistantDialog* parent) : + Page(parent), + m_msgModel( new QStandardItemModel( this ) ) +{ + ui.setupUi( this ); + ui.detailView->setModel( m_msgModel ); + connect( ui.detailsButton, SIGNAL(clicked()), SLOT(detailsClicked()) ); +} + +void SetupPage::enterPageNext() +{ + ui.stackWidget->setCurrentIndex( 0 ); +} + +void SetupPage::detailsClicked() +{ + ui.stackWidget->setCurrentIndex( 1 ); +} + +void SetupPage::addMessage(SetupPage::MessageType type, const QString& msg) +{ + QStandardItem *item = new QStandardItem; + item->setText( msg ); + item->setEditable( false ); + switch ( type ) { + case Success: + item->setIcon( KIcon( QLatin1String("dialog-ok") ) ); + break; + case Info: + item->setIcon( KIcon( QLatin1String("dialog-information" )) ); + break; + case Error: + item->setIcon( KIcon( QLatin1String("dialog-error") ) ); + break; + } + m_msgModel->appendRow( item ); +} + +void SetupPage::setStatus(const QString& msg) +{ + ui.progressLabel->setText( msg ); +} + +void SetupPage::setProgress(int percent) +{ + ui.progressBar->setValue( percent ); +} + diff --git a/kdepim-runtime/accountwizard/setuppage.h b/kdepim-runtime/accountwizard/setuppage.h new file mode 100644 index 00000000..2354d8a1 --- /dev/null +++ b/kdepim-runtime/accountwizard/setuppage.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2009 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 SETUPPAGE_H +#define SETUPPAGE_H + +#include "page.h" +#include "ui_setuppage.h" + +class QStandardItemModel; + +class SetupPage : public Page +{ + Q_OBJECT + public: + explicit SetupPage( KAssistantDialog *parent ); + virtual void enterPageNext(); + + enum MessageType { + Success, + Info, + Error + }; + void addMessage( MessageType type, const QString &msg ); + void setStatus( const QString &msg ); + void setProgress( int percent ); + + private slots: + void detailsClicked(); + + private: + Ui::SetupPage ui; + QStandardItemModel* m_msgModel; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/transport.cpp b/kdepim-runtime/accountwizard/transport.cpp new file mode 100644 index 00000000..7d20b421 --- /dev/null +++ b/kdepim-runtime/accountwizard/transport.cpp @@ -0,0 +1,156 @@ +/* + Copyright (c) 2009 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 "transport.h" + +#include + +#include +#include + +#define TABLE_SIZE x + +template +struct StringValueTable { + const char * name; + typename T::type value; + typedef typename T::type value_type; +}; + +static const StringValueTable transportTypeEnums[] = { + { "smtp", MailTransport::Transport::EnumType::SMTP }, + { "sendmail", MailTransport::Transport::EnumType::Sendmail }, + { "akonadi", MailTransport::Transport::EnumType::Akonadi } +}; +static const int transportTypeEnumsSize = sizeof( transportTypeEnums ) / sizeof ( *transportTypeEnums ); + +static const StringValueTable encryptionEnum[] = { + { "none", MailTransport::Transport::EnumEncryption::None }, + { "ssl", MailTransport::Transport::EnumEncryption::SSL }, + { "tls", MailTransport::Transport::EnumEncryption::TLS } +}; +static const int encryptionEnumSize = sizeof( encryptionEnum ) / sizeof( *encryptionEnum ); + +static const StringValueTable authenticationTypeEnum[] = { + { "login", MailTransport::Transport::EnumAuthenticationType::LOGIN }, + { "plain", MailTransport::Transport::EnumAuthenticationType::PLAIN }, + { "cram-md5", MailTransport::Transport::EnumAuthenticationType::CRAM_MD5 }, + { "digest-md5", MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5 }, + { "gssapi", MailTransport::Transport::EnumAuthenticationType::GSSAPI }, + { "ntlm", MailTransport::Transport::EnumAuthenticationType::NTLM }, + { "apop", MailTransport::Transport::EnumAuthenticationType::APOP }, + { "clear", MailTransport::Transport::EnumAuthenticationType::CLEAR }, + { "anonymous", MailTransport::Transport::EnumAuthenticationType::ANONYMOUS } +}; +static const int authenticationTypeEnumSize = sizeof( authenticationTypeEnum ) / sizeof( *authenticationTypeEnum ); + +template +static typename T::value_type stringToValue( const T *table, const int tableSize, const QString &string ) +{ + const QString ref = string.toLower(); + for ( int i = 0; i < tableSize; ++i ) { + if ( ref == QLatin1String( table[i].name ) ) + return table[i].value; + } + return table[0].value; // TODO: error handling +} + +Transport::Transport(const QString& type, QObject* parent) : + SetupObject( parent ), + m_transportId( -1 ), + m_port( -1 ), + m_encr( MailTransport::Transport::EnumEncryption::TLS ), + m_auth( MailTransport::Transport::EnumAuthenticationType::PLAIN ) +{ + m_transportType = stringToValue( transportTypeEnums, transportTypeEnumsSize, type ); + if ( m_transportType == MailTransport::Transport::EnumType::SMTP ) + m_port = 25; +} + +void Transport::create() +{ + emit info( i18n( "Setting up mail transport account..." ) ); + MailTransport::Transport* mt = MailTransport::TransportManager::self()->createTransport(); + mt->setName( m_name ); + mt->setHost( m_host ); + if ( m_port > 0 ) + mt->setPort( m_port ); + if ( !m_user.isEmpty() ) { + mt->setUserName( m_user ); + mt->setRequiresAuthentication( true ); + } + if ( !m_password.isEmpty() ) { + mt->setStorePassword( true ); + mt->setPassword( m_password ); + } + mt->setEncryption( m_encr ); + mt->setAuthenticationType( m_auth ); + m_transportId = mt->id(); + mt->writeConfig(); + MailTransport::TransportManager::self()->addTransport( mt ); + MailTransport::TransportManager::self()->setDefaultTransport( mt->id() ); + emit finished( i18n( "Mail transport account set up." ) ); +} + +void Transport::destroy() +{ + MailTransport::TransportManager::self()->removeTransport( m_transportId ); + emit info( i18n( "Mail transport account deleted." ) ); +} + +void Transport::setName( const QString &name ) +{ + m_name = name; +} + +void Transport::setHost( const QString &host ) +{ + m_host = host; +} + +void Transport::setPort( int port ) +{ + m_port = port; +} + +void Transport::setUsername( const QString &user ) +{ + m_user = user; +} + +void Transport::setPassword( const QString &password ) +{ + m_password = password; +} + +void Transport::setEncryption( const QString &encryption ) +{ + m_encr = stringToValue( encryptionEnum, encryptionEnumSize, encryption ); +} + +void Transport::setAuthenticationType( const QString &authType ) +{ + m_auth = stringToValue( authenticationTypeEnum, authenticationTypeEnumSize, authType ); +} + +int Transport::transportId() const +{ + return m_transportId; +} + diff --git a/kdepim-runtime/accountwizard/transport.h b/kdepim-runtime/accountwizard/transport.h new file mode 100644 index 00000000..36666cb0 --- /dev/null +++ b/kdepim-runtime/accountwizard/transport.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2009 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 TRANSPORT_H +#define TRANSPORT_H + +#include "setupobject.h" +#include + + +class Transport : public SetupObject +{ + Q_OBJECT + public: + explicit Transport( const QString &type, QObject *parent = 0 ); + void create(); + void destroy(); + + int transportId() const; + + public slots: + Q_SCRIPTABLE void setName( const QString &name ); + Q_SCRIPTABLE void setHost( const QString &host ); + Q_SCRIPTABLE void setPort( int port ); + Q_SCRIPTABLE void setUsername( const QString &user ); + Q_SCRIPTABLE void setPassword( const QString &password ); + Q_SCRIPTABLE void setEncryption( const QString &encryption ); + Q_SCRIPTABLE void setAuthenticationType( const QString &authType ); + + private: + MailTransport::Transport::EnumEncryption stringToEncryption( const QString &encryptionString ); + MailTransport::Transport::EnumAuthenticationType stringToAuthType( const QString &authString ); + + private: + int m_transportId; + MailTransport::Transport::EnumType::type m_transportType; + QString m_name; + QString m_host; + int m_port; + QString m_user; + QString m_password; + MailTransport::Transport::EnumEncryption::type m_encr; + MailTransport::Transport::EnumAuthenticationType::type m_auth; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/typepage.cpp b/kdepim-runtime/accountwizard/typepage.cpp new file mode 100644 index 00000000..8a3dfaad --- /dev/null +++ b/kdepim-runtime/accountwizard/typepage.cpp @@ -0,0 +1,117 @@ +/* + Copyright (c) 2009 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 "typepage.h" + +#include +#include +#include + +#include +#include "global.h" +#include + +TypePage::TypePage(KAssistantDialog* parent) : + Page( parent ), + m_model( new QStandardItemModel( this ) ) +{ + ui.setupUi( this ); +#ifdef KDEPIM_MOBILE_UI + ui.label->setHidden( true ); + ui.searchLine->setHidden( true ); +#endif + +#ifdef ACCOUNTWIZARD_NO_GHNS + ui.ghnsButton->hide(); +#endif + + QSortFilterProxyModel *proxy = new QSortFilterProxyModel( this ); + proxy->setSourceModel( m_model ); + ui.listView->setModel( proxy ); + ui.searchLine->setProxy( proxy ); + + const QStringList list = KGlobal::dirs()->findAllResources( "data", QLatin1String( "akonadi/accountwizard/*.desktop" ), + KStandardDirs::Recursive | KStandardDirs::NoDuplicates ); + const QStringList filter = Global::typeFilter(); + foreach ( const QString &entry, list ) { + KDesktopFile f( entry ); + kDebug() << entry << f.readName(); + const KConfig configWizard( entry ); + KConfigGroup grp( &configWizard, "Wizard" ); + const QStringList lstType = grp.readEntry( "Type", QStringList() ); + if ( lstType.isEmpty() ) { + kDebug() << QString::fromLatin1( " %1 doesn't contains specific type" ).arg( f.readName() ); + } + if ( !filter.isEmpty() ) { + // stolen from agentfilterproxymodel + bool found = false; + foreach ( const QString &mimeType, lstType ) { + if ( filter.contains( mimeType ) ) { + found = true; + break; + } else { + foreach ( const QString &type, filter ) { + KMimeType::Ptr typePtr = KMimeType::mimeType( type, KMimeType::ResolveAliases ); + if ( !typePtr.isNull() && typePtr->is( mimeType ) ) { + found = true; + break; + } + } + } + if ( found ) + break; + } + if ( !found ) + continue; + } + QStandardItem *item = new QStandardItem( f.readName() ); + item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); + item->setData( entry, Qt::UserRole ); + if ( !f.readIcon().isEmpty() ) + item->setData( KIcon( f.readIcon() ), Qt::DecorationRole ); + item->setData( f.readComment(), Qt::ToolTipRole ); + m_model->appendRow( item ); + } + + connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged()) ); + connect( ui.ghnsButton, SIGNAL(clicked()), SIGNAL(ghnsWanted()) ); +} + +void TypePage::selectionChanged() +{ + if ( ui.listView->selectionModel()->hasSelection() ) { + setValid( true ); + } else { + setValid( false ); + } +} + +void TypePage::leavePageNext() +{ + if ( !ui.listView->selectionModel()->hasSelection() ) + return; + const QModelIndex index = ui.listView->selectionModel()->selectedIndexes().first(); + Global::setAssistant( index.data( Qt::UserRole ).toString() ); +} + +QTreeView *TypePage::treeview() const +{ + return ui.listView; +} + diff --git a/kdepim-runtime/accountwizard/typepage.h b/kdepim-runtime/accountwizard/typepage.h new file mode 100644 index 00000000..f69ba0f5 --- /dev/null +++ b/kdepim-runtime/accountwizard/typepage.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2009 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 TYPEPAGE_H +#define TYPEPAGE_H + +#include "page.h" +#include + +#include "ui_typepage.h" + +class TypePage : public Page +{ + Q_OBJECT + public: + explicit TypePage( KAssistantDialog* parent = 0 ); + + virtual void leavePageNext(); + QTreeView *treeview() const; + + signals: + void ghnsWanted(); + + private slots: + void selectionChanged(); + private: + Ui::TypePage ui; + QStandardItemModel *m_model; +}; + +#endif diff --git a/kdepim-runtime/accountwizard/ui/loadpage.ui b/kdepim-runtime/accountwizard/ui/loadpage.ui new file mode 100644 index 00000000..8701a1b8 --- /dev/null +++ b/kdepim-runtime/accountwizard/ui/loadpage.ui @@ -0,0 +1,54 @@ + + + LoadPage + + + + 0 + 0 + 400 + 84 + + + + + + + Qt::Vertical + + + + 20 + 129 + + + + + + + + Loading assistant... + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 129 + + + + + + + + + diff --git a/kdepim-runtime/accountwizard/ui/personaldatapage.ui b/kdepim-runtime/accountwizard/ui/personaldatapage.ui new file mode 100644 index 00000000..26e142ce --- /dev/null +++ b/kdepim-runtime/accountwizard/ui/personaldatapage.ui @@ -0,0 +1,283 @@ + + + PersonalDataPage + + + + 0 + 0 + 521 + 289 + + + + + + + With a few simple steps we create the right settings for you. Please follow the steps of this wizard carefully. + + + true + + + + + + + + + Full name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + nameEdit + + + + + + + + 0 + 22 + + + + + + + + E-mail address: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + emailEdit + + + + + + + + 0 + 22 + + + + + + + + + 0 + 22 + + + + QLineEdit::Password + + + + + + + Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + passwordEdit + + + + + + + + + 0 + + + + + + + Find provider settings on the Internet + + + true + + + + + + Check online for the settings needed for this email provider. Only the domain name part of the e-mail address will be sent over the Internet at this point. If this option is unchecked, the account can be set up manually. + + + true + + + + + + + + + + + + + + + + + + + + + + + IMAP account + + + true + + + buttonGroup + + + + + + + POP3 account + + + buttonGroup + + + + + + + + + + + Incoming: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Outgoing: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Username: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Create Account + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KPIMUtils::ProgressIndicatorLabel + QLabel +
kpimutils/progressindicatorlabel.h
+ 1 +
+
+ + + + + +
diff --git a/kdepim-runtime/accountwizard/ui/providerpage.ui b/kdepim-runtime/accountwizard/ui/providerpage.ui new file mode 100644 index 00000000..7c80c67e --- /dev/null +++ b/kdepim-runtime/accountwizard/ui/providerpage.ui @@ -0,0 +1,63 @@ + + + ProviderPage + + + + 0 + 0 + 400 + 172 + + + + + + + Select your provider from the list below or click advanced if your provider is not listed + + + true + + + + + + + + + + false + + + true + + + true + + + true + + + true + + + true + + + true + + + + + + + + KFilterProxySearchLine + QWidget +
kfilterproxysearchline.h
+
+
+ + +
diff --git a/kdepim-runtime/accountwizard/ui/setuppage.ui b/kdepim-runtime/accountwizard/ui/setuppage.ui new file mode 100644 index 00000000..deba7755 --- /dev/null +++ b/kdepim-runtime/accountwizard/ui/setuppage.ui @@ -0,0 +1,103 @@ + + + SetupPage + + + + 0 + 0 + 400 + 143 + + + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 109 + + + + + + + + Setting up account... + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Details... + + + + + + + + + Qt::Vertical + + + + 20 + 74 + + + + + + + + + + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + +
diff --git a/kdepim-runtime/accountwizard/ui/typepage.ui b/kdepim-runtime/accountwizard/ui/typepage.ui new file mode 100644 index 00000000..3358671a --- /dev/null +++ b/kdepim-runtime/accountwizard/ui/typepage.ui @@ -0,0 +1,84 @@ + + + TypePage + + + + 0 + 0 + 400 + 151 + + + + + + + Select which kind of account you want to create: + + + + + + + + + + false + + + true + + + true + + + true + + + true + + + true + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Check for more on Internet + + + + + + + + + + KFilterProxySearchLine + QWidget +
kfilterproxysearchline.h
+
+
+ + +
diff --git a/kdepim-runtime/accountwizard/wizards/CMakeLists.txt b/kdepim-runtime/accountwizard/wizards/CMakeLists.txt new file mode 100644 index 00000000..798868e5 --- /dev/null +++ b/kdepim-runtime/accountwizard/wizards/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(tine20) diff --git a/kdepim-runtime/accountwizard/wizards/tine20/CMakeLists.txt b/kdepim-runtime/accountwizard/wizards/tine20/CMakeLists.txt new file mode 100644 index 00000000..f0d3b667 --- /dev/null +++ b/kdepim-runtime/accountwizard/wizards/tine20/CMakeLists.txt @@ -0,0 +1,2 @@ + +install ( FILES tine20wizard.desktop tine20wizard.es tine20wizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/tine20 ) diff --git a/kdepim-runtime/accountwizard/wizards/tine20/Messages.sh b/kdepim-runtime/accountwizard/wizards/tine20/Messages.sh new file mode 100644 index 00000000..9738d4da --- /dev/null +++ b/kdepim-runtime/accountwizard/wizards/tine20/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_tine20.pot +$XGETTEXT -kqsTr *.es -j -o $podir/accountwizard_tine20.pot diff --git a/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.desktop b/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.desktop new file mode 100644 index 00000000..7ddcb47f --- /dev/null +++ b/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.desktop @@ -0,0 +1,93 @@ +[Desktop Entry] +Name=Tine 2.0 Groupware Server +Name[bs]=Tine 2.0 Groupware Server +Name[ca]=Servidor de treball en grup Tine 2.0 +Name[ca@valencia]=Servidor de treball en grup Tine 2.0 +Name[cs]=Groupware Server Tine 2.0 +Name[da]=Tine 2.0 groupware-server +Name[de]=Tine 2.0 Groupware-Server +Name[el]=Tine 2.0 Groupware Server +Name[en_GB]=Tine 2.0 Groupware Server +Name[es]=Servidor de trabajo en grupo Tine 2.0 +Name[et]=Tine 2.0 grupitöö server +Name[fi]=Tine 2.0 -työryhmäpalvelin +Name[fr]=Serveur de logiciels de collaboration Tine 2.0 +Name[ga]=Freastalaí Groupware Tine 2.0 +Name[gl]=Servidor de grupos Tine 2.0 +Name[hu]=Tine 2.0 csoportmunka-kiszolgáló +Name[ia]=Servitor de Tine 2.0 Groupware +Name[it]=Server di groupware Tine 2.0 +Name[kk]=Tine 2.0 топтық Ñ–Ñ Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ Tine ២.០ Groupware +Name[ko]=Tine 2.0 그룹웨어 서버 +Name[lt]=Tine 2.0 grupinio darbo serveris +Name[lv]=Tine 2.0 grupprogrammatÅ«ras serveris +Name[nb]=Tine 2.0 gruppevare-tjener +Name[nds]=Tine 2.0-Arbeitkoppel-Server +Name[nl]=Tine 2.0 groupwareserver +Name[pl]=Serwer Groupware Tine 2.0 +Name[pt]=Servidor de Groupware Tine 2.0 +Name[pt_BR]=Servidor groupware Tine 2.0 +Name[ru]=Сервер ÑовмеÑтной работы Tine 2.0 +Name[sk]=Tine 2.0 Groupware Server +Name[sl]=Strežnik Tine 2.0 za skupinsko delo +Name[sr]=Тинеов групверÑки Ñервер +Name[sr@ijekavian]=Тинеов групверÑки Ñервер +Name[sr@ijekavianlatin]=Tineov grupverski server +Name[sr@latin]=Tineov grupverski server +Name[sv]=Tine 2.0 grupprogramserver +Name[tr]=Tine 2.0 Groupware Sunucusu +Name[uk]=Сервер групової роботи Tine 2.0 +Name[x-test]=xxTine 2.0 Groupware Serverxx +Name[zh_CN]=Tine 2.0 群件æœåŠ¡å™¨ +Name[zh_TW]=Tine 2.0 群組伺æœå™¨ +Icon=applications-internet +Comment=Tine 2.0 Groupware Server +Comment[bs]=Tine 2.0 Groupware Server +Comment[ca]=Servidor de treball en grup Tine 2.0 +Comment[ca@valencia]=Servidor de treball en grup Tine 2.0 +Comment[cs]=Groupware Server Tine 2.0 +Comment[da]=Tine 2.0 groupware-server +Comment[de]=Tine 2.0 Groupware-Server +Comment[el]=ΕξυπηÏετητής Tine 2.0 Groupware +Comment[en_GB]=Tine 2.0 Groupware Server +Comment[es]=Servidor de trabajo en grupo Tine 2.0 +Comment[et]=Tine 2.0 grupitöö server +Comment[fi]=Tine 2.0 -työryhmäpalvelin +Comment[fr]=Serveur de logiciels de collaboration Tine 2.0 +Comment[ga]=Freastalaí Groupware Tine 2.0 +Comment[gl]=Servidor de grupos Tine 2.0 +Comment[hu]=Tine 2.0 csoportmunka-kiszolgáló +Comment[ia]=Servitor de Tine 2.0 Groupware +Comment[it]=Server di groupware Tine 2.0 +Comment[kk]=Tine 2.0 топтық Ñ–Ñ Ñервері +Comment[km]=ម៉ាស៊ីន​បម្រើ Tine ២.០ Groupware +Comment[ko]=Tine 2.0 그룹웨어 서버 +Comment[lt]=Tine 2.0 grupinio darbo serveris +Comment[lv]=Tine 2.0 grupprogrammatÅ«ras serveris +Comment[nb]=Tine 2.0 gruppevare-tjener +Comment[nds]=Tine 2.0-Arbeitkoppel-Server +Comment[nl]=Tine 2.0 groupwareserver +Comment[pl]=Serwer Groupware Tine 2.0 +Comment[pt]=Servidor de 'Groupware' Tine 2.0 +Comment[pt_BR]=Servidor groupware Tine 2.0 +Comment[ru]=Сервер ÑовмеÑтной работы Tine 2.0 +Comment[sk]=Tine 2.0 Groupware Server +Comment[sl]=Strežnik Tine 2.0 za skupinsko delo +Comment[sr]=Тинеов групверÑки Ñервер +Comment[sr@ijekavian]=Тинеов групверÑки Ñервер +Comment[sr@ijekavianlatin]=Tineov grupverski server +Comment[sr@latin]=Tineov grupverski server +Comment[sv]=Tine 2.0 grupprogramserver +Comment[tr]=Tine 2.0 Groupware Sunucusu +Comment[uk]=Сервер групової роботи Tine 2.0 +Comment[x-test]=xxTine 2.0 Groupware Serverxx +Comment[zh_CN]=Tine 2.0 群件æœåŠ¡å™¨ +Comment[zh_TW]=Tine 2.0 群組伺æœå™¨ + +[Wizard] +Type=message/rfc822,text/directory,text/calendar +Script=tine20wizard.es + +[Translate] +Filename=accountwizard_tine20 diff --git a/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.es b/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.es new file mode 100644 index 00000000..2c799990 --- /dev/null +++ b/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.es @@ -0,0 +1,177 @@ +/* + Copyright (c) 2012 Volker Krause + Copyright (c) 2010 Casey Link + + 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. +*/ + +// add this function to trim user input of whitespace when needed +String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); }; + +var page = Dialog.addPage( "tine20wizard.ui", qsTr("Personal Settings") ); +var userChangedServerAddress = false; + +page.widget().nameEdit.text = SetupManager.name() +page.widget().emailEdit.text = SetupManager.email() +page.widget().passwordEdit.text = SetupManager.password() +guessServerName(); + +if ( SetupManager.personalDataAvailable() ) { + page.widget().nameEdit.visible = false; + page.widget().nameLabel.visible = false; + page.widget().emailEdit.visible = false; + page.widget().emailLabel.visible = false; + page.widget().passwordEdit.visible = false; + page.widget().passwordLabel.visible = false; +} + + +function guessServerName() +{ + if ( userChangedServerAddress == true ) { + return; + } + + var email = page.widget().emailEdit.text; + var pos = email.indexOf( "@" ); + if ( pos >= 0 && (pos + 1) < email.length ) { + var server = email.slice( pos + 1, email.length ); + page.widget().serverAddress.text = server; + } + + userChangedServerAddress = false; +} + +function emailChanged( arg ) +{ + validateInput(); + guessServerName(); +} + +function serverChanged( arg ) +{ + validateInput(); + if ( arg == "" ) { + userChangedServerAddress = false; + } else { + userChangedServerAddress = true; + } +} + +function validateInput() +{ + if ( page.widget().serverAddress.text.trim() == "" || page.widget().emailEdit.text.trim() == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +// stage 1, setup identity and run imap server test +// stage 2, smtp setup (no server test) and Dav resources +var stage = 1; +var identity; // global so it can be accesed in setup and testOk + +function setup() +{ + print("setup() " + stage); + var serverAddress = page.widget().serverAddress.text.trim(); + if ( stage == 1 ) { + SetupManager.openWallet(); + + identity = SetupManager.createIdentity(); + identity.setEmail( page.widget().emailEdit.text ); + identity.setRealName( page.widget().nameEdit.text ); + + ServerTest.test( serverAddress, "imap" ); + } else { // stage 2 + var smtp = SetupManager.createTransport( "smtp" ); + smtp.setName( serverAddress ); + smtp.setHost( serverAddress ); + smtp.setPort( 25 ); + smtp.setEncryption( "TLS" ); + smtp.setAuthenticationType( "plain" ); + smtp.setUsername( page.widget().emailEdit.text ); + smtp.setPassword( page.widget().passwordEdit.text ); + identity.setTransport( smtp ); + + var dav = SetupManager.createResource( "akonadi_davgroupware_resource" ); + print("dav: " + dav ); + try { + dav.setName( "Tine 2.0" ); + dav.setOption( "DisplayName", "Tine 2.0" ); + dav.setOption( "RefreshInterval", 60 ); + dav.setOption( "RemoteUrls", ["$default$|CalDav|https://" + page.widget().serverAddress.text.trim() + "/calendars", + "$default$|CardDav|https://" + page.widget().serverAddress.text.trim() + "/addressbooks"] ); + dav.setOption( "DefaultUsername", page.widget().emailEdit.text.trim() ); + } catch (e) { print(e); } + + print("pre-exec"); + SetupManager.execute(); + print("post-exec"); + } +} + +function testResultFail() +{ + testOk( -1 ); +} + +function testOk( arg ) +{ + print("testOk arg =", arg); + var imapRes = SetupManager.createResource( "akonadi_imap_resource" ); + imapRes.setName( page.widget().serverAddress.text.trim() ); + imapRes.setOption( "ImapServer", page.widget().serverAddress.text.trim() ); + imapRes.setOption( "UserName", page.widget().emailEdit.text.trim() ); + imapRes.setOption( "Password", page.widget().passwordEdit.text.trim() ); + imapRes.setOption( "UseDefaultIdentity", false ); + imapRes.setOption( "AccountIdentity", identity.uoid() ); + imapRes.setOption( "DisconnectedModeEnabled", true ); + imapRes.setOption( "IntervalCheckTime", 60 ); + imapRes.setOption( "SubscriptionEnabled", true ); + imapRes.setOption( "SieveSupport", false ); + imapRes.setOption( "Authentication", 7 ); // ClearText + if ( arg == "ssl" ) { + // The ENUM used for authentication (in the imap resource only) + imapRes.setOption( "Safety", "SSL" ); // SSL/TLS + imapRes.setOption( "ImapPort", 993 ); + } else if ( arg == "tls" ) { // tls is really STARTTLS + imapRes.setOption( "Safety", "STARTTLS" ); // STARTTLS + imapRes.setOption( "ImapPort", 143 ); + } else if ( arg == "none" ) { + imapRes.setOption( "Safety", "NONE" ); // No encryption + imapRes.setOption( "ImapPort", 143 ); + } else { + // safe default fallback in case server test failed + imapRes.setOption( "Safety", "STARTTLS" ); + imapRes.setOption( "ImapPort", 143 ); + } + stage = 2; + setup(); +} + +try { + ServerTest.testFail.connect(testResultFail); + ServerTest.testResult.connect(testOk); + page.widget().emailEdit.textChanged.connect(emailChanged); + page.widget().serverAddress.textChanged.connect(serverChanged); + page.pageLeftNext.connect(setup); +} catch (e) { + print(e); +} + +validateInput(); diff --git a/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.ui b/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.ui new file mode 100644 index 00000000..db2d106f --- /dev/null +++ b/kdepim-runtime/accountwizard/wizards/tine20/tine20wizard.ui @@ -0,0 +1,87 @@ + + + tine20Wizard + + + + 0 + 0 + 368 + 124 + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Name: + + + nameEdit + + + + + + + + + + &Email: + + + emailEdit + + + + + + + + + + &Password: + + + passwordEdit + + + + + + + QLineEdit::Password + + + + + + + &Server Address: + + + serverAddress + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + serverAddress + + + +
diff --git a/kdepim-runtime/agents/.krazy b/kdepim-runtime/agents/.krazy new file mode 100644 index 00000000..4152f9b2 --- /dev/null +++ b/kdepim-runtime/agents/.krazy @@ -0,0 +1 @@ +SKIP /ontologies/ diff --git a/kdepim-runtime/agents/CMakeLists.txt b/kdepim-runtime/agents/CMakeLists.txt new file mode 100644 index 00000000..226edbd6 --- /dev/null +++ b/kdepim-runtime/agents/CMakeLists.txt @@ -0,0 +1,20 @@ +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + +macro(kdepim_add_agent _target) + if(KDEPIM_BUILD_AGENTS_AS_PLUGINS) + add_definitions(-DKDEPIM_PLUGIN_AGENT) + kde4_add_plugin(${_target} ${ARGN}) + install( TARGETS ${_target} DESTINATION ${PLUGIN_INSTALL_DIR}/ ) + else() + kde4_add_executable(${_target} ${ARGN}) + install(TARGETS ${_target} ${INSTALL_TARGETS_DEFAULT_ARGS}) + endif() +endmacro() + +add_subdirectory( maildispatcher ) +add_subdirectory( newmailnotifier ) +add_subdirectory( migration ) +add_subdirectory( invitations ) + +install(FILES akonadinepomukfeederagent.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") diff --git a/kdepim-runtime/agents/Info.plist.template b/kdepim-runtime/agents/Info.plist.template new file mode 100644 index 00000000..c39ddb95 --- /dev/null +++ b/kdepim-runtime/agents/Info.plist.template @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + LSUIElement + 1 + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + diff --git a/kdepim-runtime/agents/Mainpage.dox b/kdepim-runtime/agents/Mainpage.dox new file mode 100644 index 00000000..c99c6ac3 --- /dev/null +++ b/kdepim-runtime/agents/Mainpage.dox @@ -0,0 +1,2 @@ +// DOXYGEN_NAME=Akonadi Agents +// DOXYGEN_ENABLE=YES diff --git a/kdepim-runtime/agents/akonadinepomukfeederagent.desktop b/kdepim-runtime/agents/akonadinepomukfeederagent.desktop new file mode 100644 index 00000000..69329c4b --- /dev/null +++ b/kdepim-runtime/agents/akonadinepomukfeederagent.desktop @@ -0,0 +1,53 @@ +[Desktop Entry] +Name=Akonadi Nepomuk Feeder +Name[bs]=Akonadi Nepomuk napajaÄ +Name[ca]=Alimentador del Nepomuk per a l'Akonadi +Name[ca@valencia]=Alimentador del Nepomuk per a l'Akonadi +Name[cs]=Akonadi podavaÄ Nepomuku +Name[da]=Akonadi Nepomuk-feeder +Name[de]=Nepomuk-Modul für Akonadi +Name[el]=ΤÏοφοδότης Nepomuk του Akonadi +Name[en_GB]=Akonadi Nepomuk Feeder +Name[es]=Alimentador Nepomuk de Akonadi +Name[et]=Akonadi Nepomuki söötur +Name[fi]=Akonadi Nepomuk Feeder +Name[fr]=Connecteur Nepomuk pour Akonadi +Name[ga]=Fothaire Nepomuk Akonadi +Name[gl]=Alimentador de Nepomuk para Akonadi +Name[hu]=Akonadi Nepomuk feltöltÅ‘ +Name[ia]=Syndication de Akonadi Nepomuk +Name[it]=Fornitore Akonadi per Nepomuk +Name[kk]=Nepomuk Ñл.пошта бергіші +Name[km]=កម្មវិធី​មážáž·â€‹áž–áŸážáŸŒáž˜áž¶áž“ Akonadi Nepomuk +Name[ko]=Akonadi Nepomuk ê³µê¸‰ìž +Name[lt]=Akonadi Nepomuk Å¡altinis +Name[lv]=Akonadi Nepomuk barotÄjs +Name[nb]=Akonadi innmating til Nepomuk +Name[nds]=Nepomuk-Ingaav för Akonadi +Name[nl]=Akonadi Nepumuk-feeder +Name[pl]=Podajnik Akonadi Nepomuk +Name[pt]=Fontes do Nepomuk para o Akonadi +Name[pt_BR]=Alimentador do Nepomuk para o Akonadi +Name[ru]=Akonadi Nepomuk Feeder +Name[sk]=KÅ•mitko Nepomuk Akonadi +Name[sl]=Podajalnik za Nepomuk +Name[sr]=Ðепомуков уводник Ðконадија +Name[sr@ijekavian]=Ðепомуков уводник Ðконадија +Name[sr@ijekavianlatin]=Nepomukov uvodnik Akonadija +Name[sr@latin]=Nepomukov uvodnik Akonadija +Name[sv]=Akonadi-inmatning till Nepomuk +Name[tr]=Akonadi Nepomuk Besleyici +Name[uk]=Передавач даних Akonadi Nepomuk +Name[x-test]=xxAkonadi Nepomuk Feederxx +Name[zh_CN]=Akonadi Nepomuk 邮件采集器 +Name[zh_TW]=Akonadi Nepomuk Feeder + +Type=AkonadiAgent +Exec=akonadi_nepomuk_feeder +Icon=nepomuk + +# This agent is disabled, do not autostart it. +# KDE5: remove this file +X-Akonadi-MimeTypes= +X-Akonadi-Capabilities= +X-Akonadi-Identifier=akonadi_nepomuk_feeder diff --git a/kdepim-runtime/agents/invitations/CMakeLists.txt b/kdepim-runtime/agents/invitations/CMakeLists.txt new file mode 100644 index 00000000..62b044a2 --- /dev/null +++ b/kdepim-runtime/agents/invitations/CMakeLists.txt @@ -0,0 +1,35 @@ +set( invitationsagent_SRCS + invitationsagent.cpp + incidenceattribute.cpp +) + +#kde4_add_ui_files(invitationsagent_SRCS settings.ui) +#kde4_add_kcfg_files(invitationsagent_SRCS settings.kcfgc) +#kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/invitationsagent.kcfg org.kde.Akonadi.MailDispatcher.Settings) +#qt4_add_dbus_adaptor(invitationsagent_SRCS +# ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.MailDispatcher.Settings.xml settings.h Settings +#) +## qt4_add_dbus_adaptor( invitationsagent_SRCS +## ${KDE4_DBUS_INTERFACES_DIR}/org.kde.Akonadi.MailDispatcher.xml invitationsagent.h MailDispatcherAgent +## ) + +kde4_add_executable(akonadi_invitations_agent ${invitationsagent_SRCS}) + +target_link_libraries(akonadi_invitations_agent + ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} +) + +if (Q_WS_MAC) + set_target_properties(akonadi_invitations_agent PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_invitations_agent PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.invitationsagent") + set_target_properties(akonadi_invitations_agent PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Invitations Calendar") +endif () + + +install(TARGETS akonadi_invitations_agent ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES invitationsagent.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") diff --git a/kdepim-runtime/agents/invitations/Messages.sh b/kdepim-runtime/agents/invitations/Messages.sh new file mode 100755 index 00000000..6f0436ae --- /dev/null +++ b/kdepim-runtime/agents/invitations/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +$XGETTEXT *.cpp -o $podir/akonadi_invitations_agent.pot diff --git a/kdepim-runtime/agents/invitations/incidenceattribute.cpp b/kdepim-runtime/agents/invitations/incidenceattribute.cpp new file mode 100644 index 00000000..20531ea1 --- /dev/null +++ b/kdepim-runtime/agents/invitations/incidenceattribute.cpp @@ -0,0 +1,92 @@ +/* + Copyright (c) 2009 Sebastian Sauer + + 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 "incidenceattribute.h" + +#include +#include + +using namespace Akonadi; + +class IncidenceAttribute::Private +{ + public: + QString status; + Akonadi::Item::Id referenceId; + + explicit Private() : referenceId( -1 ) {} +}; + +IncidenceAttribute::IncidenceAttribute() + : Attribute(), d( new Private ) +{ +} + +IncidenceAttribute::~IncidenceAttribute() +{ + delete d; +} + +QByteArray IncidenceAttribute::type() const +{ + return "incidence"; +} + +Attribute* IncidenceAttribute::clone() const +{ + IncidenceAttribute *other = new IncidenceAttribute; + return other; +} + +QByteArray IncidenceAttribute::serialized() const +{ + QString data; + QTextStream out( &data ); + out << d->status; + out << d->referenceId; + return data.toUtf8(); +} + +void IncidenceAttribute::deserialize( const QByteArray &data ) +{ + QString s( QString::fromUtf8( data ) ); + QTextStream in( &s ); + in >> d->status; + in >> d->referenceId; +} + +QString IncidenceAttribute::status() const +{ + return d->status; +} + +void IncidenceAttribute::setStatus( const QString &newstatus ) const +{ + d->status = newstatus; +} + +Akonadi::Item::Id IncidenceAttribute::reference() const +{ + return d->referenceId; +} + +void IncidenceAttribute::setReference( Akonadi::Item::Id id ) +{ + d->referenceId = id; +} diff --git a/kdepim-runtime/agents/invitations/incidenceattribute.h b/kdepim-runtime/agents/invitations/incidenceattribute.h new file mode 100644 index 00000000..68c7c827 --- /dev/null +++ b/kdepim-runtime/agents/invitations/incidenceattribute.h @@ -0,0 +1,63 @@ +/* + Copyright (c) 2009 Sebastian Sauer + + 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 INCIDENCEATTRIBUTE_H +#define INCIDENCEATTRIBUTE_H + +#include +#include + +namespace Akonadi { + +class IncidenceAttribute : public Akonadi::Attribute +{ + public: + explicit IncidenceAttribute(); + ~IncidenceAttribute(); + + virtual QByteArray type() const; + virtual Attribute* clone() const; + + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + + /** + * The status the invitation is in. + * + * One of; + * "new", "accepted", "tentative", "counter", "cancel", "reply", "delegated" + */ + QString status() const; + void setStatus( const QString &newstatus ) const; + + /** + * The referenced item. This is used e.g. in the invitationagent to + * let users know where the original mail message is. + */ + Akonadi::Item::Id reference() const; + void setReference( Akonadi::Item::Id id ); + + private: + class Private; + Private *const d; +}; + +} + +#endif diff --git a/kdepim-runtime/agents/invitations/invitationsagent.cpp b/kdepim-runtime/agents/invitations/invitationsagent.cpp new file mode 100644 index 00000000..dfa8368f --- /dev/null +++ b/kdepim-runtime/agents/invitations/invitationsagent.cpp @@ -0,0 +1,560 @@ +/* + Copyright 2009 Sebastian Sauer + + 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 "invitationsagent.h" + +#include "incidenceattribute.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace Akonadi; + +class InvitationsCollectionRequestJob : public SpecialCollectionsRequestJob +{ + public: + InvitationsCollectionRequestJob( SpecialCollections* collection, InvitationsAgent *agent ) + : SpecialCollectionsRequestJob( collection, agent ) + { + setDefaultResourceType( QLatin1String( "akonadi_ical_resource" ) ); + + QVariantMap options; + options.insert( QLatin1String( "Path" ), QString( KGlobal::dirs()->localxdgdatadir() + QLatin1String( "akonadi_invitations" ) ) ); + options.insert( QLatin1String( "Name" ), i18n( "Invitations" ) ); + setDefaultResourceOptions( options ); + + QMap displayNameMap; + displayNameMap.insert( "invitations", i18n( "Invitations" ) ); + setTypes( displayNameMap.keys() ); + setNameForTypeMap( displayNameMap ); + + QMap iconNameMap; + iconNameMap.insert( "invitations", QLatin1String( "folder" ) ); + setIconForTypeMap( iconNameMap ); + } + + virtual ~InvitationsCollectionRequestJob() + { + } +}; + +class InvitationsCollection : public SpecialCollections +{ + public: + + class Settings : public KCoreConfigSkeleton + { + public: + Settings() + { + setCurrentGroup( QLatin1String("Invitations") ); + addItemString( QLatin1String("DefaultResourceId"), m_id, QString() ); + } + + virtual ~Settings() + { + } + + private: + QString m_id; + }; + + InvitationsCollection( InvitationsAgent *agent ) + : Akonadi::SpecialCollections( new Settings ), m_agent( agent ), sInvitationsType( "invitations" ) + { + } + + virtual ~InvitationsCollection() + { + } + + void clear() + { + m_collection = Collection(); + } + + bool hasDefaultCollection() const + { + return SpecialCollections::hasDefaultCollection( sInvitationsType ); + } + + Collection defaultCollection() const + { + if ( !m_collection.isValid() ) + m_collection = SpecialCollections::defaultCollection( sInvitationsType ); + + return m_collection; + } + + void registerDefaultCollection() + { + defaultCollection(); + registerCollection( sInvitationsType, m_collection ); + } + + SpecialCollectionsRequestJob* reguestJob() const + { + InvitationsCollectionRequestJob *job = new InvitationsCollectionRequestJob( const_cast( this ), + m_agent ); + job->requestDefaultCollection( sInvitationsType ); + + return job; + } + + private: + InvitationsAgent *m_agent; + const QByteArray sInvitationsType; + mutable Collection m_collection; +}; + +InvitationsAgentItem::InvitationsAgentItem( InvitationsAgent *agent, const Item &originalItem ) + : QObject( agent ), m_agent( agent ), m_originalItem( originalItem ) +{ +} + +InvitationsAgentItem::~InvitationsAgentItem() +{ +} + +void InvitationsAgentItem::add( const Item &item ) +{ + kDebug(); + const Collection collection = m_agent->collection(); + Q_ASSERT( collection.isValid() ); + + ItemCreateJob *job = new ItemCreateJob( item, collection, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(createItemResult(KJob*)) ); + + m_jobs << job; + + job->start(); +} + +void InvitationsAgentItem::createItemResult( KJob *job ) +{ + ItemCreateJob *createJob = qobject_cast( job ); + m_jobs.removeAll( createJob ); + if ( createJob->error() ) { + kWarning() << "Failed to create new Item in invitations collection." << createJob->errorText(); + return; + } + + if ( !m_jobs.isEmpty() ) + return; + + ItemFetchJob *fetchJob = new ItemFetchJob( m_originalItem, this ); + connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchItemDone(KJob*)) ); + fetchJob->start(); +} + +void InvitationsAgentItem::fetchItemDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Failed to fetch Item in invitations collection." << job->errorText(); + return; + } + + ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob->items().count() == 1 ); + + Item modifiedItem = fetchJob->items().first(); + Q_ASSERT( modifiedItem.isValid() ); + + modifiedItem.setFlag( Akonadi::MessageFlags::HasInvitation ); + ItemModifyJob *modifyJob = new ItemModifyJob( modifiedItem, this ); + connect( modifyJob, SIGNAL(result(KJob*)), this, SLOT(modifyItemDone(KJob*)) ); + modifyJob->start(); +} + +void InvitationsAgentItem::modifyItemDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Failed to modify Item in invitations collection." << job->errorText(); + return; + } + + //ItemModifyJob *mj = qobject_cast( job ); + //kDebug()<<"Job successful done."; +} + +InvitationsAgent::InvitationsAgent( const QString &id ) + : AgentBase( id ), AgentBase::ObserverV2() + , m_invitationsCollection( new InvitationsCollection( this ) ) +{ + kDebug(); + + changeRecorder()->setChangeRecordingEnabled( false ); // behave like Monitor + changeRecorder()->itemFetchScope().fetchFullPayload(); + changeRecorder()->setMimeTypeMonitored( QLatin1String("message/rfc822"), true ); + //changeRecorder()->setCollectionMonitored( Collection::root(), true ); + + connect( this, SIGNAL(reloadConfiguration()), this, SLOT(initStart()) ); + QTimer::singleShot( 0, this, SLOT(initStart()) ); +} + +InvitationsAgent::~InvitationsAgent() +{ + delete m_invitationsCollection; +} + +void InvitationsAgent::initStart() +{ + kDebug(); + + m_invitationsCollection->clear(); + if ( m_invitationsCollection->hasDefaultCollection() ) { + initDone(); + } else { + SpecialCollectionsRequestJob *job = m_invitationsCollection->reguestJob(); + connect( job, SIGNAL(result(KJob*)), this, SLOT(initDone(KJob*)) ); + job->start(); + } + + /* + KConfig config( "akonadi_invitations_agent" ); + KConfigGroup group = config.group( "General" ); + m_resourceId = group.readEntry( "DefaultCalendarAgent", "default_ical_resource" ); + newAgentCreated = false; + m_invitations = Akonadi::Collection(); + AgentInstance resource = AgentManager::self()->instance( m_resourceId ); + if ( resource.isValid() ) { + emit status( AgentBase::Running, i18n( "Reading..." ) ); + QMetaObject::invokeMethod( this, "createAgentResult", Qt::QueuedConnection ); + } else { + emit status( AgentBase::Running, i18n( "Creating..." ) ); + AgentType type = AgentManager::self()->type( QLatin1String( "akonadi_ical_resource" ) ); + AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(createAgentResult(KJob*)) ); + job->start(); + } + */ +} + +void InvitationsAgent::initDone( KJob *job ) +{ + if ( job ) { + if ( job->error() ) { + kWarning() << "Failed to request default collection:" << job->errorText(); + return; + } + + m_invitationsCollection->registerDefaultCollection(); + } + + Q_ASSERT( m_invitationsCollection->defaultCollection().isValid() ); + emit status( AgentBase::Idle, i18n( "Ready to dispatch invitations" ) ); +} + +Collection InvitationsAgent::collection() +{ + return m_invitationsCollection->defaultCollection(); +} + +#if 0 +KPIMIdentities::IdentityManager* InvitationsAgent::identityManager() +{ + if ( !m_IdentityManager ) + m_IdentityManager = new KPIMIdentities::IdentityManager( true /* readonly */, this ); + return m_IdentityManager; +} +#endif + +void InvitationsAgent::configure( WId windowId ) +{ + kDebug() << windowId; + /* + QWidget *parent = QWidget::find( windowId ); + KDialog *dialog = new KDialog( parent ); + QVBoxLayout *layout = new QVBoxLayout( dialog->mainWidget() ); + //layout->addWidget( ); + dialog->mainWidget()->setLayout( layout ); + */ + initStart(); //reload +} + +#if 0 +void InvitationsAgent::createAgentResult( KJob *job ) +{ + kDebug(); + AgentInstance agent; + if ( job ) { + if ( job->error() ) { + kWarning() << job->errorString(); + emit status( AgentBase::Broken, i18n( "Failed to create resource: %1", job->errorString() ) ); + return; + } + + AgentInstanceCreateJob *j = static_cast( job ); + agent = j->instance(); + agent.setName( i18n( "Invitations" ) ); + m_resourceId = agent.identifier(); + + QDBusInterface conf( QString::fromLatin1( "org.freedesktop.Akonadi.Resource." ) + m_resourceId, + QString::fromLatin1( "/Settings" ), + QString::fromLatin1( "org.kde.Akonadi.ICal.Settings" ) ); + QDBusReply reply = conf.call( QString::fromLatin1( "setPath" ), + KGlobal::dirs()->localxdgdatadir() + "akonadi_ical_resource" ); + + if ( !reply.isValid() ) { + kWarning() << "dbus call failed, m_resourceId=" << m_resourceId; + emit status( AgentBase::Broken, i18n( "Failed to set the directory for invitations via D-Bus" ) ); + AgentManager::self()->removeInstance( agent ); + return; + } + + KConfig config( "akonadi_invitations_agent" ); + KConfigGroup group = config.group( "General" ); + group.writeEntry( "DefaultCalendarAgent", m_resourceId ); + + newAgentCreated = true; + agent.reconfigure(); + } else { + agent = AgentManager::self()->instance( m_resourceId ); + Q_ASSERT( agent.isValid() ); + } + + ResourceSynchronizationJob *j = new ResourceSynchronizationJob( agent, this ); + connect( j, SIGNAL(result(KJob*)), this, SLOT(resourceSyncResult(KJob*)) ); + j->start(); +} + +void InvitationsAgent::resourceSyncResult( KJob *job ) +{ + kDebug(); + if ( job->error() ) { + kWarning() << job->errorString(); + emit status( AgentBase::Broken, i18n( "Failed to synchronize collection: %1", job->errorString() ) ); + if ( newAgentCreated ) + AgentManager::self()->removeInstance( AgentManager::self()->instance( m_resourceId ) ); + return; + } + CollectionFetchJob *fjob = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this ); + fjob->fetchScope().setContentMimeTypes( QStringList() << "text/calendar" ); + fjob->fetchScope().setResource( m_resourceId ); + connect( fjob, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*)) ); + fjob->start(); +} + +void InvitationsAgent::collectionFetchResult( KJob *job ) +{ + kDebug(); + + if ( job->error() ) { + kWarning() << job->errorString(); + emit status( AgentBase::Broken, i18n( "Failed to fetch collection: %1", job->errorString() ) ); + if ( newAgentCreated ) + AgentManager::self()->removeInstance( AgentManager::self()->instance( m_resourceId ) ); + return; + } + + CollectionFetchJob *fj = static_cast( job ); + + if ( newAgentCreated ) { + // if the agent was just created then there is exactly one collection already + // and we just use that one. + Q_ASSERT( fj->collections().count() == 1 ); + m_invitations = fj->collections().first(); + initDone(); + return; + } + + KConfig config( "akonadi_invitations_agent" ); + KConfigGroup group = config.group( "General" ); + const QString collectionId = group.readEntry( "DefaultCalendarCollection", QString() ); + if ( !collectionId.isEmpty() ) { + // look if the collection is still there. It may the case that there exists such + // a collection with the defined collectionId but that this is not a valid one + // and therefore not in the resultset. + const int id = collectionId.toInt(); + foreach ( const Collection &c, fj->collections() ) { + if ( c.id() == id ) { + m_invitations = c; + initDone(); + return; + } + } + } + + // we need to create a new collection and use that one... + Collection c; + c.setName( "invitations" ); + c.setParent( Collection::root() ); + Q_ASSERT( !m_resourceId.isNull() ); + c.setResource( m_resourceId ); + c.setContentMimeTypes( QStringList() + << "text/calendar" + << "application/x-vnd.akonadi.calendar.event" + << "application/x-vnd.akonadi.calendar.todo" + << "application/x-vnd.akonadi.calendar.journal" + << "application/x-vnd.akonadi.calendar.freebusy" ); + CollectionCreateJob *cj = new CollectionCreateJob( c, this ); + connect( cj, SIGNAL(result(KJob*)), this, SLOT(collectionCreateResult(KJob*)) ); + cj->start(); +} + +void InvitationsAgent::collectionCreateResult( KJob *job ) +{ + kDebug(); + if ( job->error() ) { + kWarning() << job->errorString(); + emit status( AgentBase::Broken, i18n( "Failed to create collection: %1", job->errorString() ) ); + if ( newAgentCreated ) + AgentManager::self()->removeInstance( AgentManager::self()->instance( m_resourceId ) ); + return; + } + CollectionCreateJob *j = static_cast( job ); + m_invitations = j->collection(); + initDone(); +} +#endif + +Item InvitationsAgent::handleContent( const QString &vcal, + const KCalCore::MemoryCalendar::Ptr &calendar, + const Item &item ) +{ + KCalCore::ICalFormat format; + KCalCore::ScheduleMessage::Ptr message = format.parseScheduleMessage( calendar, vcal ); + if ( !message ) { + kWarning() << "Invalid invitation:" << vcal; + return Item(); + } + + kDebug() << "id=" << item.id() << "remoteId=" << item.remoteId() << "vcal=" << vcal; + + KCalCore::Incidence::Ptr incidence = message->event().staticCast(); + Q_ASSERT( incidence ); + + IncidenceAttribute *attr = new IncidenceAttribute; + attr->setStatus( QLatin1String("new") ); //TODO + //attr->setFrom( message->from()->asUnicodeString() ); + attr->setReference( item.id() ); + + Item newItem; + newItem.setMimeType( incidence->mimeType() ); + newItem.addAttribute( attr ); + newItem.setPayload( KCalCore::Incidence::Ptr( incidence->clone() ) ); + return newItem; +} + +void InvitationsAgent::itemAdded( const Item &item, const Collection &collection ) +{ + kDebug() << item.id() << collection; + Q_UNUSED( collection ); + + if ( !m_invitationsCollection->defaultCollection().isValid() ) { + kDebug() << "No default collection found"; + return; + } + + if ( collection.isVirtual() ) + return; + + if ( !item.hasPayload() ) { + kDebug() << "Item has no payload"; + return; + } + + KMime::Message::Ptr message = item.payload(); + + //TODO check if we are the sender and need to ignore the message... + //const QString sender = message->sender()->asUnicodeString(); + //if ( identityManager()->thatIsMe( sender ) ) return; + + KCalCore::MemoryCalendar::Ptr calendar( new KCalCore::MemoryCalendar( KSystemTimeZones::local() ) ); + if ( message->contentType()->isMultipart() ) { + kDebug() << "message is multipart:" << message->attachments().size(); + + InvitationsAgentItem *it = 0; + foreach ( KMime::Content *content, message->contents() ) { + + KMime::Headers::ContentType *ct = content->contentType(); + Q_ASSERT( ct ); + kDebug() << "Mimetype of the body part is " << ct->mimeType(); + if ( ct->mimeType() != "text/calendar" ) + continue; + + Item newItem = handleContent( QLatin1String(content->body()), calendar, item ); + if ( !newItem.hasPayload() ) { + kDebug() << "new item has no payload"; + continue; + } + + if ( !it ) + it = new InvitationsAgentItem( this, item ); + + it->add( newItem ); + } + } else { + kDebug() << "message is not multipart"; + + KMime::Headers::ContentType *ct = message->contentType(); + Q_ASSERT( ct ); + kDebug() << "Mimetype of the body is " << ct->mimeType(); + if ( ct->mimeType() != "text/calendar" ) + return; + + kDebug() << "Message has an invitation in the body, processing"; + + Item newItem = handleContent( QLatin1String(message->body()), calendar, item ); + if ( !newItem.hasPayload() ) { + kDebug() << "new item has no payload"; + return; + } + + InvitationsAgentItem *it = new InvitationsAgentItem( this, item ); + it->add( newItem ); + } +} + +AKONADI_AGENT_MAIN( InvitationsAgent ) + diff --git a/kdepim-runtime/agents/invitations/invitationsagent.desktop b/kdepim-runtime/agents/invitations/invitationsagent.desktop new file mode 100644 index 00000000..e5000d30 --- /dev/null +++ b/kdepim-runtime/agents/invitations/invitationsagent.desktop @@ -0,0 +1,97 @@ +[Desktop Entry] +Name=Invitations Dispatcher Agent +Name[bs]=Agent za raspodjelu poziva +Name[ca]=Agent distribuïdor d'invitacions +Name[ca@valencia]=Agent distribuïdor d'invitacions +Name[cs]=Agent odesílatele pozvánek +Name[da]=Invitationsafsendingsagent +Name[de]=Agent zur Einladungs-Auslieferung +Name[el]=ΠÏάκτοÏας αποστολής Ï€Ïοσκλήσεων +Name[en_GB]=Invitations Dispatcher Agent +Name[es]=Agente despachador de invitaciones +Name[et]=Kutsete edastamise agent +Name[fi]=Kutsunlähetysagentti +Name[fr]=Agent de diffusions d'invitations +Name[gl]=Axente de Despacho de Convites +Name[hu]=Meghívófeladó ügynök +Name[ia]=Agente Distributor de Invitationes +Name[it]=Agente per la consegna degli inviti +Name[ja]=正体ディスパッãƒãƒ£ãƒ¼ã‚¨ãƒ¼ã‚¸ã‚§ãƒ³ãƒˆ +Name[kk]=Шақыру реттеуш агенті +Name[km]=ការ​អញ្ជើញ​​ភ្នាក់ងារ​​កម្មវិធី​បញ្ជូន +Name[ko]=초대장 가져오기 ì—ì´ì „트 +Name[lt]=LaiÅ¡kų gijų iÅ¡dÄ—stymo agentas +Name[lv]=IelÅ«gumu nosÅ«tÄ«Å¡anas aÄ£ents +Name[nb]=Agent for invitasjonssendinger +Name[nds]=Inladenverdeel-Hölper +Name[nl]=Uitnodigingen-verspreidingsagent +Name[pl]=Agent wysyÅ‚ania zaproszeÅ„ +Name[pt]=Agente de Despacho de Convites +Name[pt_BR]=Agente de encaminhamento de convites +Name[ro]=Agent de remitere a invitaÈ›iilor +Name[ru]=Ðгент диÑпетчера приглашений +Name[sk]=Agent spracovania pozvánok +Name[sl]=Posrednik za razpoÅ¡iljanje povabil +Name[sr]=Ðгент отпремања позивница +Name[sr@ijekavian]=Ðгент отпремања позивница +Name[sr@ijekavianlatin]=Agent otpremanja pozivnica +Name[sr@latin]=Agent otpremanja pozivnica +Name[sv]=Modul för inbjudningssändning +Name[tr]=Davetiye Dağıtıcı Programı +Name[uk]=Ðгент розподілу запрошень +Name[x-test]=xxInvitations Dispatcher Agentxx +Name[zh_CN]=邀请签å‘ä»£ç† +Name[zh_TW]=邀請é…é€ä»£ç†ç¨‹å¼ +Comment=Dispatches invitations from your calendar +Comment[bs]=RasporeÄ‘uje pozive iz vaÅ¡eg kalendara +Comment[ca]=Distribueix invitacions des del calendari +Comment[ca@valencia]=Distribueix invitacions des del calendari +Comment[da]=Udsender invitationer fra din kalender +Comment[de]=Verschickt Einladungen aus Ihren Kalender +Comment[el]=Διανέμει Ï€Ïοσκλήσεις από το ημεÏολόγιό σας +Comment[en_GB]=Dispatches invitations from your calendar +Comment[es]=Remite invitaciones desde su calendario +Comment[et]=Kutsete edastamine sinu kalendrist +Comment[fi]=Lähettää kutsuja kalenteristasi +Comment[fr]=Diffuse les invitations à partir de votre agenda +Comment[gl]=Xestiona invitacións do calendario. +Comment[hu]=Meghívásokat kézbesít a naptárából +Comment[ia]=Expedi invitationes ex tu calendario +Comment[it]=Consegna inviti dal tuo calendario +Comment[kk]=Күнтізбеңіздегі шақыруларды үлеÑтіру +Comment[ko]=달력ì—ì„œ ì´ˆëŒ€ìž¥ì„ ê°€ì ¸ì˜´ +Comment[lt]=IÅ¡siunÄia pakvietimus iÅ¡ JÅ«sų kalendoriaus +Comment[nb]=Sender ut invitasjoner fra din kalender +Comment[nds]=Verdeelt Inladen ut Dien Kalenner +Comment[nl]=Brengt uitnodigingen uit uw agenda naar elders +Comment[pl]=RozsyÅ‚a zaproszenia z twojego kalendarza +Comment[pt]=Trata dos convites do seu calendário +Comment[pt_BR]=Encaminhamento de convites do seu calendário +Comment[ro]=Remite invitaÈ›ii din calendar +Comment[ru]=РаÑÑылает Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ Ð¸Ð· ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ +Comment[sk]=Vybavuje pozvánky z vášho kalendára +Comment[sl]=OdpoÅ¡lje povabila z vaÅ¡ega koledarja +Comment[sr]=Отпрема позивнице из вашег календара +Comment[sr@ijekavian]=Отпрема позивнице из вашег календара +Comment[sr@ijekavianlatin]=Otprema pozivnice iz vaÅ¡eg kalendara +Comment[sr@latin]=Otprema pozivnice iz vaÅ¡eg kalendara +Comment[sv]=Skickar inbjudningar frÃ¥n kalendern +Comment[tr]=Takviminizdeki davetleri yollar +Comment[uk]=РоповÑюджує Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð½Ð° оÑнові даних ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ +Comment[x-test]=xxDispatches invitations from your calendarxx +Comment[zh_CN]=从您的日历å‘é€é‚€è¯· +Comment[zh_TW]=從您的行事曆中分é…邀請 +Type=AkonadiAgent +Exec=akonadi_invitations_agent +Icon=mail-folder-outbox + +X-Akonadi-Identifier=akonadi_invitations_agent +#X-Akonadi-Capabilities=Unique,Autostart,NoConfig +X-Akonadi-Capabilities=NoConfig + +#X-Akonadi-MimeTypes=message/rfc822 +#X-Akonadi-MimeTypes=text/calendar +X-Akonadi-MimeTypes=application/x-vnd.akonadi.calendar.event +#X-Akonadi-MimeTypes=application/x-vnd.akonadi.calendar.todo +#X-Akonadi-MimeTypes=application/x-vnd.akonadi.calendar.journal +#X-Akonadi-MimeTypes=application/x-vnd.akonadi.calendar.freebusy diff --git a/kdepim-runtime/agents/invitations/invitationsagent.h b/kdepim-runtime/agents/invitations/invitationsagent.h new file mode 100644 index 00000000..907928e5 --- /dev/null +++ b/kdepim-runtime/agents/invitations/invitationsagent.h @@ -0,0 +1,101 @@ +/* + Copyright 2009 Sebastian Sauer + + 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 INVITATIONSAGENT_H +#define INVITATIONSAGENT_H + +#include + +#include +#include +#include +#include + +#include + +class KJob; + +class InvitationsAgent; +class InvitationsCollection; + +class InvitationsAgentItem : public QObject +{ + Q_OBJECT + + public: + InvitationsAgentItem(InvitationsAgent *a, const Akonadi::Item &originalItem); + virtual ~InvitationsAgentItem(); + void add(const Akonadi::Item &newItem); + + private Q_SLOTS: + void createItemResult( KJob *job ); + void fetchItemDone( KJob* ); + void modifyItemDone( KJob *job ); + + private: + InvitationsAgent *m_agent; + const Akonadi::Item m_originalItem; + QList m_jobs; +}; + +class InvitationsAgent : public Akonadi::AgentBase, public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + explicit InvitationsAgent( const QString &id ); + virtual ~InvitationsAgent(); + + Akonadi::Collection collection(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + private Q_SLOTS: + void initStart(); + void initDone( KJob *job = 0 ); + + private: + Akonadi::Item handleContent( const QString &vcal, + const KCalCore::MemoryCalendar::Ptr &calendar, + const Akonadi::Item &item ); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + + /* + virtual void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); + virtual void itemRemoved( const Akonadi::Item &item ); + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection ); + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + virtual void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination ); + virtual void itemLinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemUnlinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination ); + virtual void collectionChanged( const Akonadi::Collection &collection, const QSet &changedAttributes ); + */ + + private: + QString m_resourceId; + InvitationsCollection *m_invitationsCollection; + Akonadi::Collection m_collection; +}; + +#endif // MAILDISPATCHERAGENT_H diff --git a/kdepim-runtime/agents/maildispatcher/CMakeLists.txt b/kdepim-runtime/agents/maildispatcher/CMakeLists.txt new file mode 100644 index 00000000..06951500 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/CMakeLists.txt @@ -0,0 +1,41 @@ +add_subdirectory( tests ) + +set( maildispatcheragent_SRCS + maildispatcheragent.cpp + outboxqueue.cpp + sendjob.cpp + sentactionhandler.cpp + storeresultjob.cpp + #configdialog.cpp +) + +kde4_add_ui_files(maildispatcheragent_SRCS settings.ui) +kde4_add_kcfg_files(maildispatcheragent_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/maildispatcheragent.kcfg org.kde.Akonadi.MailDispatcher.Settings) +qt4_add_dbus_adaptor(maildispatcheragent_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.MailDispatcher.Settings.xml settings.h Settings +) +qt4_add_dbus_adaptor( maildispatcheragent_SRCS + org.freedesktop.Akonadi.MailDispatcherAgent.xml maildispatcheragent.h MailDispatcherAgent +) + +if (RUNTIME_PLUGINS_STATIC) + add_definitions(-DMAIL_SERIALIZER_PLUGIN_STATIC) +endif () + +kdepim_add_agent(akonadi_maildispatcher_agent ${maildispatcheragent_SRCS}) + +if (RUNTIME_PLUGINS_STATIC) + target_link_libraries(akonadi_maildispatcher_agent akonadi_serializer_mail) +endif () + +if (Q_WS_MAC) + set_target_properties(akonadi_maildispatcher_agent PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_maildispatcher_agent PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.maildispatcher") + set_target_properties(akonadi_maildispatcher_agent PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Maildispatcher") +endif () + +target_link_libraries(akonadi_maildispatcher_agent ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_MAILTRANSPORT_LIBS} ${KDE4_KNOTIFYCONFIG_LIBS}) + +install( FILES maildispatcheragent.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) +install( FILES akonadi_maildispatcher_agent.notifyrc DESTINATION "${DATA_INSTALL_DIR}/akonadi_maildispatcher_agent" ) diff --git a/kdepim-runtime/agents/maildispatcher/Messages.sh b/kdepim-runtime/agents/maildispatcher/Messages.sh new file mode 100755 index 00000000..87206060 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/Messages.sh @@ -0,0 +1,4 @@ +#! /bin/sh +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_maildispatcher_agent.pot +rm -f rc.cpp diff --git a/kdepim-runtime/agents/maildispatcher/TODO b/kdepim-runtime/agents/maildispatcher/TODO new file mode 100644 index 00000000..d45568ea --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/TODO @@ -0,0 +1,16 @@ +* Once MoveJobs work, enable and test moving to sent-mail. Here is why it + doesn't work currently: Akonadi::Monitor sends itemMoved(...), but it is + not handled in AgentBase::Observer, and so it never gets "processed", and + is stuck in the Monitor/ChangeRecorder forever. (I.e. the resource will + not get any new notifications.) This has to be fixed in Akonadi, but that + means adding ObserverV2 or something. +* Figure out which / whether error strings should be i18n'd +* Should probably use progressMessage instead of statusMessage, but it seems + to be unimplemented in AgentInstance, and only a stub in AgentBase. +* Do something about timeouts. +* Test aborting and progress reporting for resource-based transports. + +Bugs: +* Incorrect size reporting in itemChanged() from Monitor. Leads to displaying + >100% progress. + (found with: offline, queue some, online, abort, clearerror) diff --git a/kdepim-runtime/agents/maildispatcher/akonadi_maildispatcher_agent.notifyrc b/kdepim-runtime/agents/maildispatcher/akonadi_maildispatcher_agent.notifyrc new file mode 100644 index 00000000..3b89cd00 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/akonadi_maildispatcher_agent.notifyrc @@ -0,0 +1,194 @@ +[Global] +IconName=mail-folder-outbox +Comment=KDE e-mail client +Comment[bg]=ПощенÑки клиент за KDE +Comment[bs]=KDE klijent elektronske poÅ¡te +Comment[ca]=Client de correu electrònic pel KDE +Comment[ca@valencia]=Client de correu electrònic pel KDE +Comment[cs]=Klient KDE pro Ätení elektronické poÅ¡ty +Comment[da]=KDE e-mail-klient +Comment[de]=E-Mail-Programm für KDE +Comment[el]=Πελάτης ηλ.ταχυδÏομείου KDE +Comment[en_GB]=KDE e-mail client +Comment[es]=Cliente de correo de KDE +Comment[et]=KDE e-posti klient +Comment[fi]=KDE:n sähköpostiohjelma +Comment[fr]=Logiciel KDE de courrier électronique +Comment[ga]=Cliant Ríomhphoist KDE +Comment[gl]=Cliente de correo do KDE +Comment[hu]=KDE e-mail kliens +Comment[ia]=Programma KDE de e-posta +Comment[it]=Programma KDE di posta elettronica +Comment[ja]=KDE メールクライアント +Comment[kk]=KDE Ñл.пошта клиенті +Comment[km]=កម្មវិធី​អ៊ីមែល​របស់ KDE +Comment[ko]=KDE ì´ë©”ì¼ í´ë¼ì´ì–¸íŠ¸ +Comment[lt]=KDE el. paÅ¡to klientas +Comment[lv]=KDE e-pasta klients +Comment[mr]=केडीई इ-मेल गà¥à¤°à¤¾à¤¹à¤• +Comment[nb]=KDE E-postklient +Comment[nds]=Nettpostprogramm för KDE +Comment[nl]=KDE e-mailclient +Comment[pa]=KDE ਈ-ਮੇਲ ਕਲਾਇਟ +Comment[pl]=Program pocztowy KDE +Comment[pt]=Cliente de e-mail do KDE +Comment[pt_BR]=Cliente de e-mail do KDE +Comment[ro]=Program de poÈ™tă electronică pentru KDE +Comment[ru]=Почтовый клиент KDE +Comment[sk]=KDE poÅ¡tový klient +Comment[sl]=KDE-jev poÅ¡tni odjemalec +Comment[sr]=КДЕ клијент е‑поште +Comment[sr@ijekavian]=КДЕ клијент е‑поште +Comment[sr@ijekavianlatin]=KDE klijent e‑poÅ¡te +Comment[sr@latin]=KDE klijent e‑poÅ¡te +Comment[sv]=KDE E-postklient +Comment[tr]=KDE e-posta istemcisi +Comment[uk]=Поштовий клієнт KDE +Comment[x-test]=xxKDE e-mail clientxx +Comment[zh_CN]=KDE 邮件客户端 +Comment[zh_TW]=KDE 收發信軟體 +Name=KDE Mail +Name[bg]=KDE поща +Name[bs]=KDE Mail +Name[ca]=Correu del KDE +Name[ca@valencia]=Correu del KDE +Name[cs]=PoÅ¡ta v KDE +Name[da]=KDE Mail +Name[de]=KDE E-Mail +Name[el]=ΑλληλογÏαφία KDE +Name[en_GB]=KDE Mail +Name[es]=Correo de KDE +Name[et]=KDE e-post +Name[fa]=نامه‌ی کی‌دی‌ای +Name[fi]=KDE Mail +Name[fr]=Messagerie KDE +Name[ga]=Ríomhphost KDE +Name[gl]=Correo do KDE +Name[hu]=KDE Mail +Name[ia]=Posta KDE +Name[it]=KDE Mail +Name[ja]=KDE Mail +Name[kk]=KDE поштаÑÑ‹ +Name[km]=សំបុážáŸ’រ​របស់ KDE +Name[ko]=KDE ë©”ì¼ +Name[lt]=KDE paÅ¡tas +Name[lv]=KDE pasts +Name[mr]=केडीई मेल +Name[nb]=KDE Mail +Name[nds]=KDE-Nettpost +Name[nl]=KDE E-mail +Name[pa]=KDE ਮੇਲ +Name[pl]=Poczta KDE +Name[pt]=Correio do KDE +Name[pt_BR]=E-mail do KDE +Name[ro]=PoÈ™ta KDE +Name[ru]=Почта KDE +Name[sk]=KDE Mail +Name[sl]=KDE-jeva poÅ¡ta +Name[sr]=КДЕ пошта +Name[sr@ijekavian]=КДЕ пошта +Name[sr@ijekavianlatin]=KDE poÅ¡ta +Name[sr@latin]=KDE poÅ¡ta +Name[sv]=KDE:s e-postprogram +Name[tr]=KDE E-Posta +Name[uk]=Пошта KDE +Name[x-test]=xxKDE Mailxx +Name[zh_CN]=KDE 电å­é‚®ä»¶ +Name[zh_TW]=KDE 郵件軟體 + +[Event/emailsent] +Name=E-mail successfully sent +Name[bs]=E-mail uspjeÅ¡no poslan +Name[ca]=Correu electrònic enviat correctament +Name[ca@valencia]=Correu electrònic enviat correctament +Name[cs]=E-mail úspěšnÄ› odeslán +Name[da]=E-mail sendt +Name[de]=E-Mail erfolgreich gesendet +Name[el]=Η αλληλογÏαφία στάλθηκε με επιτυχία +Name[en_GB]=E-mail successfully sent +Name[es]=Correo enviado satisfactoriamente +Name[et]=E-kiri saadeti edukalt ära +Name[fa]=ای‌میل با موÙقیت ارسال شد +Name[fi]=Sähköpostin lähetys onnistui +Name[fr]=Courrier électronique envoyé avec succès +Name[gl]=Enviouse a mensaxe sen problemas +Name[hu]=Az e-mail sikeresen elküldve +Name[ia]=E-posta inviate successosemente +Name[it]=Messaggio di posta inviato con successo +Name[kk]=Эл.пошта Ñәтті жіберілді +Name[km]=បាន​ផ្ញើ​អ៊ីមែល​ដោយ​ជោគជáŸáž™ +Name[ko]=ì´ë©”ì¼ì„ 성공ì ìœ¼ë¡œ 전송함 +Name[lt]=El. laiÅ¡kas sÄ—kmingai iÅ¡siųstas +Name[lv]=E-pasta veiksmÄ«gi nosÅ«tÄ«ts +Name[nb]=E.post vellykket sendt +Name[nds]=Nettbreef mit Spood loosstüert +Name[nl]=E-mailbericht met succes verzonden +Name[pa]=ਈਮੇਲ ਠੀਕ ਤਰà©à¨¹à¨¾à¨‚ ਭੇਜੀ ਗਈ +Name[pl]=Poczta zostaÅ‚a wysÅ‚ana pomyÅ›lnie +Name[pt]=O e-mail foi enviado com sucesso +Name[pt_BR]=E-mail enviado com sucesso +Name[ro]=Scrisoare expediată cu succes +Name[ru]=Почта уÑпешно отправлена +Name[sk]=E-mail úspeÅ¡ne odoslaný +Name[sl]=E-poÅ¡ta uspeÅ¡no poslana +Name[sr]=Е‑пошта је уÑпешно поÑлата +Name[sr@ijekavian]=Е‑пошта је уÑпешно поÑлата +Name[sr@ijekavianlatin]=E‑poÅ¡ta je uspeÅ¡no poslata +Name[sr@latin]=E‑poÅ¡ta je uspeÅ¡no poslata +Name[sv]=E-post skickad med lyckat resultat +Name[tr]=E-posta baÅŸarıyla gönderildi +Name[ug]=ئÛلخەت مۇۋەپپەقىيەتلىك يوللاندى +Name[uk]=Пошту уÑпішно надіÑлано +Name[x-test]=xxE-mail successfully sentxx +Name[zh_CN]=邮件已æˆåŠŸå‘é€ +Name[zh_TW]=é›»å­éƒµä»¶å·²æˆåŠŸå‚³é€ +Action=Popup +IconName=mail-folder-outbox + +[Event/sendingfailed] +Name=E-mail sending failed +Name[bs]=Slanje elektronske poÅ¡te neuspjelo +Name[ca]=Ha fallat en enviar el correu electrònic +Name[ca@valencia]=Ha fallat en enviar el correu electrònic +Name[cs]=Odesílání e-mailu selhalo +Name[da]=Afsendelse af e-mail mislykkedes +Name[de]=Fehler beim Versenden der E-Mail +Name[el]=Η αποστολή της αλληλογÏαφίας απέτυχε +Name[en_GB]=E-mail sending failed +Name[es]=Fallo en el envío del correo +Name[et]=E-kirja saatmine nurjus +Name[fa]=ارسال ای‌میل شکست خورد +Name[fi]=Sähköpostin lähetys epäonnistui +Name[fr]=Erreur lors de l'envoi du courrier électronique +Name[gl]=Produciuse un fallo ao enviar o correo +Name[hu]=Az e-mail küldése sikertelen +Name[ia]=Expedition de e-posta falleva +Name[it]=Invio del messaggio di posta non riuscito +Name[kk]=Эл.поштаны жолдау жаңылыÑÑ‹ +Name[km]=បាន​បរាជáŸáž™â€‹áž€áŸ’នុង​ការ​ផ្ញើ​អ៊ីមែល +Name[ko]=ì´ë©”ì¼ ì „ì†¡ 실패 +Name[lt]=El. paÅ¡to siuntimas nepavyko +Name[lv]=E-pasta sÅ«tÄ«Å¡ana neizdevÄs +Name[nb]=E-postsending mislyktes +Name[nds]=Nettbreef lett sik nich loosstüern +Name[nl]=Verzenden van e-mail is mislukt +Name[pa]=ਈਮੇਲ ਭੇਜਣ ਲਈ ਫੇਲà©à¨¹ +Name[pl]=Poczta nie zostaÅ‚a wysÅ‚ana pomyÅ›lnie +Name[pt]=O envio do e-mail foi mal-sucedido +Name[pt_BR]=Falha no envio do e-mail +Name[ro]=Expedierea scrisorii a eÈ™uat +Name[ru]=Ðе удалоÑÑŒ отправить почту +Name[sk]=Poslanie správy zlyhalo +Name[sl]=PoÅ¡iljanje e-poÅ¡te ni uspelo +Name[sr]=Слање е‑поште није уÑпело +Name[sr@ijekavian]=Слање е‑поште није уÑпело +Name[sr@ijekavianlatin]=Slanje e‑poÅ¡te nije uspelo +Name[sr@latin]=Slanje e‑poÅ¡te nije uspelo +Name[sv]=Misslyckades skicka brev +Name[tr]=E-posta gönderimi baÅŸarısız oldu +Name[uk]=Спроба надÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð·Ð½Ð°Ð»Ð° невдачі +Name[x-test]=xxE-mail sending failedxx +Name[zh_CN]=å‘é€é‚®ä»¶å¤±è´¥ +Name[zh_TW]=傳é€ä¿¡ä»¶å¤±æ•— +Action=Popup +IconName=mail-mark-junk diff --git a/kdepim-runtime/agents/maildispatcher/configdialog.cpp b/kdepim-runtime/agents/maildispatcher/configdialog.cpp new file mode 100644 index 00000000..2a697590 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/configdialog.cpp @@ -0,0 +1,80 @@ +/* + Copyright 2008 Ingo Klöcker + + 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 "configdialog.h" + +#include "settings.h" + +#include +#include +#include +#include +#include + +using namespace Akonadi; + +ConfigDialog::ConfigDialog( QWidget *parent ) + : KDialog( parent ) +{ + mUi.setupUi( mainWidget() ); + + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); + + ui.outboxSelector->setMimeTypeFilter( QStringList() << KMime::Message::mimeType() ); + + // collection has no name if I skip the fetch job and just do Collection c(id) + CollectionFetchJob *job = new CollectionFetchJob( Collection( Settings::self()->outbox() ), CollectionFetchJob::Base ); + if ( job->exec() ) { + const Collection::List collections = job->collections(); + + if ( !collections.isEmpty() ) + ui.outboxSelector->setCollection( collections.first() ); + } + + ui.sentMailSelector->setMimeTypeFilter( QStringList() << KMime::Message::mimeType() ); + job = new CollectionFetchJob( Collection( Settings::self()->sentMail() ), CollectionFetchJob::Base ); + if ( job->exec() ) { + const Collection::List collections = job->collections(); + if ( !collections.isEmpty() ) + ui.sentMailSelector->setCollection( collections.first() ); + } + + connect( this, SIGNAL(okClicked()), this, SLOT(save()) ); +} + +void ConfigDialog::save() +{ + mManager->updateSettings(); + + const Collection outbox = ui.outboxSelector->collection(); + if ( outbox.isValid() ) { + kDebug() << "Collection" << outbox.id() << "selected for outbox."; + Settings::self()->setOutbox( outbox.id() ); + } + + const Collection sentMail = ui.sentMailSelector->collection(); + if ( sentMail.isValid() ) { + kDebug() << "Collection" << sentMail.id() << "selected for sentMail."; + Settings::self()->setSentMail( sentMail.id() ); + } + + Settings::self()->writeConfig(); +} + diff --git a/kdepim-runtime/agents/maildispatcher/configdialog.h b/kdepim-runtime/agents/maildispatcher/configdialog.h new file mode 100644 index 00000000..797adbcb --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/configdialog.h @@ -0,0 +1,44 @@ +/* + Copyright 2008 Ingo Klöcker + + 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +#include "ui_settings.h" + +class KConfigDialogManager; + +class ConfigDialog : public KDialog +{ + Q_OBJECT + + public: + explicit ConfigDialog( QWidget *parent = 0 ); + + private Q_SLOTS: + void save(); + + private: + Ui::ConfigDialog mUi; + KConfigDialogManager *mManager; +}; + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/maildispatcheragent.cpp b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.cpp new file mode 100644 index 00000000..67a84cd1 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.cpp @@ -0,0 +1,373 @@ +/* + Copyright 2008 Ingo Klöcker + Copyright 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 "maildispatcheragent.h" + +//#include "configdialog.h" +#include "maildispatcheragentadaptor.h" +#include "outboxqueue.h" +#include "sendjob.h" +#include "sentactionhandler.h" +#include "settings.h" +#include "settingsadaptor.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef KDEPIM_STATIC_LIBS +extern bool ___MailTransport____INIT(); +#endif + +#ifdef MAIL_SERIALIZER_PLUGIN_STATIC +#include + +Q_IMPORT_PLUGIN(akonadi_serializer_mail) +#endif + +using namespace Akonadi; + +class MailDispatcherAgent::Private +{ + public: + Private( MailDispatcherAgent *parent ) + : q( parent ), + queue( 0 ), + currentJob( 0 ), + aborting( false ), + sendingInProgress( false ), + sentAnything( false ), + errorOccurred( false ), + sentItemsSize( 0 ), + sentActionHandler( 0 ) + { + } + + ~Private() + { + } + + MailDispatcherAgent * const q; + + OutboxQueue *queue; + SendJob *currentJob; + Item currentItem; + bool aborting; + bool sendingInProgress; + bool sentAnything; + bool errorOccurred; + qulonglong sentItemsSize; + SentActionHandler *sentActionHandler; + + // slots: + void abort(); + void dispatch(); + void itemFetched( const Item &item ); + void queueError( const QString &message ); + void sendPercent( KJob *job, unsigned long percent ); + void sendResult( KJob *job ); + void emitStatusReady(); +}; + + +void MailDispatcherAgent::Private::abort() +{ + if ( !q->isOnline() ) { + kDebug() << "Offline. Ignoring call."; + return; + } + + if ( aborting ) { + kDebug() << "Already aborting."; + return; + } + + if ( !sendingInProgress && queue->isEmpty() ) { + kDebug() << "MDA is idle."; + Q_ASSERT( q->status() == AgentBase::Idle ); + } else { + kDebug() << "Aborting..."; + aborting = true; + if ( currentJob ) { + currentJob->abort(); + } + // Further SendJobs will mark remaining items in the queue as 'aborted'. + } +} + +void MailDispatcherAgent::Private::dispatch() +{ + Q_ASSERT( queue ); + + if ( !q->isOnline() || sendingInProgress ) { + kDebug() << "Offline or busy. See you later."; + return; + } + + if ( !queue->isEmpty() ) { + if ( !sentAnything ) { + sentAnything = true; + sentItemsSize = 0; + emit q->percent( 0 ); + } + emit q->status( AgentBase::Running, + i18np( "Sending messages (1 item in queue)...", + "Sending messages (%1 items in queue)...", queue->count() ) ); + kDebug() << "Attempting to dispatch the next message."; + sendingInProgress = true; + queue->fetchOne(); // will trigger itemFetched + } else { + kDebug() << "Empty queue."; + if ( aborting ) { + // Finished marking messages as 'aborted'. + aborting = false; + sentAnything = false; + emit q->status( AgentBase::Idle, i18n( "Sending canceled." ) ); + QTimer::singleShot( 3000, q, SLOT(emitStatusReady()) ); + } else { + if ( sentAnything ) { + // Finished sending messages in queue. + sentAnything = false; + emit q->percent( 100 ); + emit q->status( AgentBase::Idle, i18n( "Finished sending messages." ) ); + + if ( !errorOccurred ) { + KNotification *notify = new KNotification( QLatin1String("emailsent") ); + notify->setComponentData( q->componentData() ); + notify->setTitle( i18nc( "Notification title when email was sent", "E-Mail Successfully Sent" ) ); + notify->setText( i18nc( "Notification when the email was sent", "Your E-Mail has been sent successfully." ) ); + notify->sendEvent(); + } + } else { + // Empty queue. + emit q->status( AgentBase::Idle, i18n( "No items in queue." ) ); + } + QTimer::singleShot( 3000, q, SLOT(emitStatusReady()) ); + } + + errorOccurred = false; + } +} + + +MailDispatcherAgent::MailDispatcherAgent( const QString &id ) + : AgentBase( id ), + d( new Private( this ) ) +{ + kDebug() << "maildispatcheragent: At your service, sir!"; + +#ifdef KDEPIM_STATIC_LIBS + ___MailTransport____INIT(); +#endif + + KGlobal::locale()->insertCatalog( QLatin1String("libakonadi-kmime") ); // for special collection translation + + new SettingsAdaptor( Settings::self() ); + new MailDispatcherAgentAdaptor( this ); + + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/MailDispatcherAgent" ), + this, QDBusConnection::ExportAdaptors ); + DBusConnectionPool::threadConnection().registerService( QLatin1String( "org.freedesktop.Akonadi.MailDispatcherAgent" ) ); + + d->queue = new OutboxQueue( this ); + connect( d->queue, SIGNAL(newItems()), + this, SLOT(dispatch()) ); + connect( d->queue, SIGNAL(itemReady(Akonadi::Item)), + this, SLOT(itemFetched(Akonadi::Item)) ); + connect( d->queue, SIGNAL(error(QString)), + this, SLOT(queueError(QString)) ); + connect( this, SIGNAL(itemProcessed(Akonadi::Item,bool)), + d->queue, SLOT(itemProcessed(Akonadi::Item,bool)) ); + connect( this, SIGNAL(abortRequested()), + this, SLOT(abort()) ); + + d->sentActionHandler = new SentActionHandler( this ); + + setNeedsNetwork( true ); +} + +MailDispatcherAgent::~MailDispatcherAgent() +{ + delete d; +} + +void MailDispatcherAgent::configure( WId windowId ) +{ + Q_UNUSED( windowId ); + KNotifyConfigWidget::configure( 0 ); +} + +void MailDispatcherAgent::doSetOnline( bool online ) +{ + Q_ASSERT( d->queue ); + if ( online ) { + kDebug() << "Online. Dispatching messages."; + emit status( AgentBase::Idle, i18n( "Online, sending messages in queue." ) ); + QTimer::singleShot( 0, this, SLOT(dispatch()) ); + } else { + kDebug() << "Offline."; + emit status( AgentBase::Idle, i18n( "Offline, message sending suspended." ) ); + + // TODO: This way, the OutboxQueue will continue to react to changes in + // the outbox, but the MDA will just not send anything. Is this what we + // want? + } + + AgentBase::doSetOnline( online ); +} + +void MailDispatcherAgent::Private::itemFetched( const Item &item ) +{ + kDebug() << "Fetched item" << item.id() << "; creating SendJob."; + Q_ASSERT( sendingInProgress ); + Q_ASSERT( !currentItem.isValid() ); + currentItem = item; + Q_ASSERT( currentJob == 0 ); + emit q->itemDispatchStarted(); + + currentJob = new SendJob( item, q ); + if ( aborting ) { + currentJob->setMarkAborted(); + } + + q->status( AgentBase::Running, i18nc( "Message with given subject is being sent.", "Sending: %1", + item.payload()->subject()->asUnicodeString() ) ); + + connect( currentJob, SIGNAL(result(KJob*)), + q, SLOT(sendResult(KJob*)) ); + connect( currentJob, SIGNAL(percent(KJob*,ulong)), + q, SLOT(sendPercent(KJob*,ulong)) ); + + currentJob->start(); +} + +void MailDispatcherAgent::Private::queueError( const QString &message ) +{ + emit q->error( message ); + errorOccurred = true; + // FIXME figure out why this does not set the status to Broken, etc. +} + +void MailDispatcherAgent::Private::sendPercent( KJob *job, unsigned long ) +{ + Q_ASSERT( sendingInProgress ); + Q_ASSERT( job == currentJob ); + // The progress here is actually the TransportJob, not the entire SendJob, + // because the post-job doesn't report progress. This should be fine, + // since the TransportJob is the lengthiest operation. + + // Give the transport 80% of the weight, and move-to-sendmail 20%. + const double transportWeight = 0.8; + + const int percent = 100 * ( sentItemsSize + job->processedAmount( KJob::Bytes ) * transportWeight ) + / ( sentItemsSize + currentItem.size() + queue->totalSize() ); + + kDebug() << "sentItemsSize" << sentItemsSize + << "this job processed" << job->processedAmount( KJob::Bytes ) + << "queue totalSize" << queue->totalSize() + << "total total size (sent+current+queue)" << ( sentItemsSize + currentItem.size() + queue->totalSize() ) + << "new percentage" << percent << "old percentage" << q->progress(); + + if ( percent != q->progress() ) { + // The progress can decrease too, if messages got added to the queue. + emit q->percent( percent ); + } + + // It is possible that the number of queued messages has changed. + emit q->status( AgentBase::Running, + i18np( "Sending messages (1 item in queue)...", + "Sending messages (%1 items in queue)...", 1 + queue->count() ) ); +} + +void MailDispatcherAgent::Private::sendResult( KJob *job ) +{ + Q_ASSERT( sendingInProgress ); + Q_ASSERT( job == currentJob ); + currentJob->disconnect( q ); + currentJob = 0; + + Q_ASSERT( currentItem.isValid() ); + sentItemsSize += currentItem.size(); + emit q->itemProcessed( currentItem, !job->error() ); + + const Akonadi::Item sentItem = currentItem; + currentItem = Item(); + + if ( job->error() ) { + // The SendJob gave the item an ErrorAttribute, so we don't have to + // do anything. + kDebug() << "Sending failed. error:" << job->errorString(); + + KNotification *notify = new KNotification( QLatin1String("sendingfailed") ); + notify->setComponentData( q->componentData() ); + notify->setTitle( i18nc( "Notification title when email sending failed", "E-Mail Sending Failed" ) ); + notify->setText( job->errorString() ); + notify->sendEvent(); + + errorOccurred = true; + } else { + kDebug() << "Sending succeeded."; + + // handle possible sent actions + const MailTransport::SentActionAttribute *attribute = sentItem.attribute(); + if ( attribute ) { + foreach ( const MailTransport::SentActionAttribute::Action &action, attribute->actions() ) + sentActionHandler->runAction( action ); + } + } + + // dispatch next message + sendingInProgress = false; + QTimer::singleShot( 0, q, SLOT(dispatch()) ); +} + +void MailDispatcherAgent::Private::emitStatusReady() +{ + if ( q->status() == AgentBase::Idle ) { + // If still idle after aborting, clear 'aborted' status. + emit q->status( AgentBase::Idle, i18n( "Ready to dispatch messages." ) ); + } +} + +#ifdef KDEPIM_PLUGIN_AGENT +AKONADI_AGENT_FACTORY( MailDispatcherAgent, akonadi_maildispatcher_agent ) +#else +AKONADI_AGENT_MAIN( MailDispatcherAgent ) +#endif + +#include "moc_maildispatcheragent.cpp" diff --git a/kdepim-runtime/agents/maildispatcher/maildispatcheragent.desktop b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.desktop new file mode 100644 index 00000000..fc87d9c1 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.desktop @@ -0,0 +1,93 @@ +[Desktop Entry] +Name=Mail Dispatcher Agent +Name[bs]=DispaÄer mail agent +Name[ca]=Agent distribuïdor de correu +Name[ca@valencia]=Agent distribuïdor de correu +Name[cs]=Agent odesílatele zpráv +Name[da]=Mailafsendingsagent (MDA) +Name[de]=Agent zur Nachrichten-Auslieferung +Name[el]=ΠÏάκτοÏας αποστολής αλληλογÏαφίας +Name[en_GB]=Mail Dispatcher Agent +Name[es]=Agente despachador de correo +Name[et]=Kirjade edastamise agent +Name[fi]=Postinvälitysagentti +Name[fr]=Agent de diffusion de messages +Name[gl]=Axente de Despacho de Correo +Name[hu]=Levélfeladó ügynök +Name[ia]=Agente Distributor de Posta +Name[it]=Agente per la consegna della posta +Name[ja]=メールé€ä¿¡ã‚¨ãƒ¼ã‚¸ã‚§ãƒ³ãƒˆ +Name[kk]=Пошта реттеуш агенті +Name[km]=ភ្នាក់ងារ​កម្មវិធី​បញ្ជូន​សំបុážáŸ’ážš +Name[ko]=ë©”ì¼ ê°€ì ¸ì˜¤ê¸° 마법사 +Name[lt]=LaiÅ¡kų gijų iÅ¡dÄ—stymo agentas +Name[lv]=Pasta nosÅ«tÄ«Å¡anas aÄ£ents +Name[nb]=Agent for e-postsending +Name[nds]=Nettpostverdeel-Hölper +Name[nl]=Agent voor het verzenden van e-mail +Name[pa]=ਮੇਲ ਡਿਸਪੈਚਰ à¨à¨œà©°à¨Ÿ +Name[pl]=Agent przyjmowania poczty +Name[pt]=Agente de Despacho do Correio +Name[pt_BR]=Agente de encaminhamento de e-mails +Name[ro]=Agent de livrare a mesajelor +Name[ru]=Ðгент почтового диÑпетчера +Name[sk]=Agent spracovania poÅ¡ty +Name[sl]=Posrednik za razpoÅ¡iljanje poÅ¡te +Name[sr]=Ðгент отпремања поште +Name[sr@ijekavian]=Ðгент отпремања поште +Name[sr@ijekavianlatin]=Agent otpremanja poÅ¡te +Name[sr@latin]=Agent otpremanja poÅ¡te +Name[sv]=E-postsändningsmodul +Name[tr]=E-posta Yönetim Aracı +Name[ug]=Mail Dispatcher Agent +Name[uk]=Ðгент розподілу пошти +Name[x-test]=xxMail Dispatcher Agentxx +Name[zh_CN]=邮件签å‘ä»£ç† +Name[zh_TW]=郵件é…é€ä»£ç†ç¨‹å¼ +Comment=Dispatches email messages +Comment[bs]=RasporeÄ‘uje E-mail poruke +Comment[ca]=Distribueix missatges de correu electrònic +Comment[ca@valencia]=Distribueix missatges de correu electrònic +Comment[cs]=Odesílá e-mailové zprávy +Comment[da]=Udsender e-mails +Comment[de]=Verteilt E-Mail-Nachrichten +Comment[el]=Διανέμει μηνÏματα ηλ. ταχυδÏομείου +Comment[en_GB]=Dispatches email messages +Comment[es]=Remite mensajes de correo +Comment[et]=Kirjade edastamine +Comment[fi]=Lähettää sähköpostiviestejä +Comment[fr]=Diffuse les courriers électroniques +Comment[gl]=Xestiona mensaxes de correo. +Comment[hu]=E-mail üzeneteket kézbesít +Comment[ia]=Expedi messages de e-posta +Comment[it]=Invia messaggi di posta elettronica +Comment[kk]=Пошта хаттарын үлеÑтіру +Comment[ko]=ì´ë©”ì¼ ë©”ì‹œì§€ë¥¼ 가져옴 +Comment[lt]=IÅ¡siunÄia el. laiÅ¡kus +Comment[nb]=Sender ut e-postmeldinger +Comment[nds]=Verdeelt Nettpost +Comment[nl]=Verstuurt e-mailberichten +Comment[pl]=RozsyÅ‚a wiadomoÅ›ci e-mail +Comment[pt]=Trata das mensagens de e-mail +Comment[pt_BR]=Encaminhamento de e-mails +Comment[ro]=Remite scrisori electronice +Comment[ru]=РаÑÑылает почтовые ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ +Comment[sk]=Vybavuje e-mailové správy +Comment[sl]=OdpoÅ¡lje e-poÅ¡tna sporoÄila +Comment[sr]=Отпрема е‑поштанÑке поруке +Comment[sr@ijekavian]=Отпрема е‑поштанÑке поруке +Comment[sr@ijekavianlatin]=Otprema e‑poÅ¡tanske poruke +Comment[sr@latin]=Otprema e‑poÅ¡tanske poruke +Comment[sv]=Skickar brev med e-post +Comment[tr]=E-posta mesajlarını yollar +Comment[uk]=РозповÑюджує Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти +Comment[x-test]=xxDispatches email messagesxx +Comment[zh_CN]=å‘é€ç”µå­é‚®ä»¶ +Comment[zh_TW]=分é…é›»å­éƒµä»¶è¨Šæ¯ +Type=AkonadiAgent +Exec=akonadi_maildispatcher_agent +Icon=mail-folder-outbox + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Unique,Autostart +X-Akonadi-Identifier=akonadi_maildispatcher_agent diff --git a/kdepim-runtime/agents/maildispatcher/maildispatcheragent.h b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.h new file mode 100644 index 00000000..a01e568c --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.h @@ -0,0 +1,75 @@ +/* + Copyright 2008 Ingo Klöcker + Copyright 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 MAILDISPATCHERAGENT_H +#define MAILDISPATCHERAGENT_H + +#include + +namespace Akonadi { +class Item; +} + +/** + * @short This agent dispatches mail put into the outbox collection. + */ +class MailDispatcherAgent : public Akonadi::AgentBase +{ + Q_OBJECT + + Q_CLASSINFO( "D-Bus Interface", "org.freedesktop.Akonadi.MailDispatcherAgent" ) + + public: + explicit MailDispatcherAgent( const QString &id ); + ~MailDispatcherAgent(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + Q_SIGNALS: + /** + * Emitted when the MDA has attempted to send an item. + */ + void itemProcessed( const Akonadi::Item &item, bool result ); + + /** + * Emitted when the MDA has begun processing an item + */ + Q_SCRIPTABLE void itemDispatchStarted(); + + protected: + virtual void doSetOnline( bool online ); + + private: + //@cond PRIVATE + class Private; + Private* const d; + + Q_PRIVATE_SLOT( d, void abort() ) + Q_PRIVATE_SLOT( d, void dispatch() ) + Q_PRIVATE_SLOT( d, void itemFetched( const Akonadi::Item& ) ) + Q_PRIVATE_SLOT( d, void queueError( const QString& ) ) + Q_PRIVATE_SLOT( d, void sendPercent( KJob*, unsigned long ) ) + Q_PRIVATE_SLOT( d, void sendResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void emitStatusReady() ) + //@endcond +}; + +#endif // MAILDISPATCHERAGENT_H diff --git a/kdepim-runtime/agents/maildispatcher/maildispatcheragent.kcfg b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.kcfg new file mode 100644 index 00000000..922fd13c --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/maildispatcheragent.kcfg @@ -0,0 +1,18 @@ + + + + + + + -1 + + + + -1 + + + diff --git a/kdepim-runtime/agents/maildispatcher/org.freedesktop.Akonadi.MailDispatcherAgent.xml b/kdepim-runtime/agents/maildispatcher/org.freedesktop.Akonadi.MailDispatcherAgent.xml new file mode 100644 index 00000000..917af22c --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/org.freedesktop.Akonadi.MailDispatcherAgent.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/kdepim-runtime/agents/maildispatcher/outboxqueue.cpp b/kdepim-runtime/agents/maildispatcher/outboxqueue.cpp new file mode 100644 index 00000000..2bf93011 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/outboxqueue.cpp @@ -0,0 +1,459 @@ +/* + 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 "outboxqueue.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using namespace Akonadi; +using namespace MailTransport; + +static const int OUTBOX_DISCOVERY_RETRIES = 3; // number of times we try to find or create the outbox +static const int OUTBOX_DISCOVERY_WAIT_TIME = 5000; // number of ms to wait before retrying + +/** + * @internal + */ +class OutboxQueue::Private +{ + public: + Private( OutboxQueue *qq ) + : q( qq ), + outbox( -1 ), + monitor( 0 ), + futureTimer( 0 ), + totalSize( 0 ), + outboxDiscoveryRetries( 0 ) + { + } + + OutboxQueue *const q; + + Collection outbox; + Monitor *monitor; + QList queue; + QSet futureItems; // keeps track of items removed in the meantime + QMultiMap futureMap; + QTimer *futureTimer; + qulonglong totalSize; + int outboxDiscoveryRetries; + +#if 0 + // If an item is modified externally between the moment we pass it to + // the MDA and the time the MDA marks it as sent, then we will get + // itemChanged() and may mistakenly re-add the item to the queue. + // So we ignore the item that we pass to the MDA, until the MDA finishes + // sending it. + Item currentItem; +#endif + // HACK: The above is not enough. + // Apparently change notifications are delayed sometimes (???) + // and we re-add an item long after it was sent. So keep a list of sent + // items. + // TODO debug and figure out why this happens. + QSet ignore; + + void initQueue(); + void addIfComplete( const Item &item ); + + // slots: + void checkFuture(); + void collectionFetched( KJob *job ); + void itemFetched( KJob *job ); + void localFoldersChanged(); + void localFoldersRequestResult( KJob *job ); + void itemAdded( const Item &item ); + void itemChanged( const Item &item ); + void itemMoved( const Item &item, const Collection &source, const Collection &dest ); + void itemRemoved( const Item &item ); + void itemProcessed( const Item &item, bool result ); +}; + + +void OutboxQueue::Private::initQueue() +{ + totalSize = 0; + queue.clear(); + + kDebug() << "Fetching items in collection" << outbox.id(); + ItemFetchJob *job = new ItemFetchJob( outbox ); + job->fetchScope().fetchAllAttributes(); + job->fetchScope().fetchFullPayload( false ); + connect( job, SIGNAL(result(KJob*)), q, SLOT(collectionFetched(KJob*)) ); +} + +void OutboxQueue::Private::addIfComplete( const Item &item ) +{ + if ( ignore.contains( item.id() ) ) { + kDebug() << "Item" << item.id() << "is ignored."; + return; + } + + if ( queue.contains( item ) ) { + kDebug() << "Item" << item.id() << "already in queue!"; + return; + } + + if ( !item.hasAttribute() ) { + kWarning() << "Item" << item.id() << "does not have the required attribute Address."; + return; + } + + if ( !item.hasAttribute() ) { + kWarning() << "Item" << item.id() << "does not have the required attribute DispatchMode."; + return; + } + + if ( !item.hasAttribute() ) { + kWarning() << "Item" << item.id() << "does not have the required attribute SentBehaviour."; + return; + } + + if ( !item.hasAttribute() ) { + kWarning() << "Item" << item.id() << "does not have the required attribute Transport."; + return; + } + + if ( !item.hasFlag( Akonadi::MessageFlags::Queued ) ) { + kDebug() << "Item" << item.id() << "has no '$QUEUED' flag."; + return; + } + + const DispatchModeAttribute *dispatchModeAttribute = item.attribute(); + Q_ASSERT( dispatchModeAttribute ); + if ( dispatchModeAttribute->dispatchMode() == DispatchModeAttribute::Manual ) { + kDebug() << "Item" << item.id() << "is queued to be sent manually."; + return; + } + + const TransportAttribute *transportAttribute = item.attribute(); + Q_ASSERT( transportAttribute ); + if ( transportAttribute->transport() == 0 ) { + kWarning() << "Item" << item.id() << "has invalid transport."; + return; + } + + const SentBehaviourAttribute *sentBehaviourAttribute = item.attribute(); + Q_ASSERT( sentBehaviourAttribute ); + if ( sentBehaviourAttribute->sentBehaviour() == SentBehaviourAttribute::MoveToCollection && + !sentBehaviourAttribute->moveToCollection().isValid() ) { + kWarning() << "Item" << item.id() << "has invalid sent-mail collection."; + return; + } + + // This check requires fetchFullPayload. -> slow (?) + /* + if ( !item.hasPayload() ) { + kWarning() << "Item" << item.id() << "does not have KMime::Message::Ptr payload."; + return; + } + */ + + if ( dispatchModeAttribute->dispatchMode() == DispatchModeAttribute::Automatic && + dispatchModeAttribute->sendAfter().isValid() && + dispatchModeAttribute->sendAfter() > QDateTime::currentDateTime() ) { + // All the above was OK, so accept it for the future. + kDebug() << "Item" << item.id() << "is accepted to be sent in the future."; + futureMap.insert( dispatchModeAttribute->sendAfter(), item ); + Q_ASSERT( !futureItems.contains( item ) ); + futureItems.insert( item ); + checkFuture(); + return; + } + + kDebug() << "Item" << item.id() << "is accepted into the queue (size" << item.size() << ")."; + Q_ASSERT( !queue.contains( item ) ); + totalSize += item.size(); + queue.append( item ); + emit q->newItems(); +} + +void OutboxQueue::Private::checkFuture() +{ + kDebug() << "The future is here." << futureMap.count() << "items in futureMap."; + Q_ASSERT( futureTimer ); + futureTimer->stop(); + // By default, re-check in one hour. + futureTimer->setInterval( 60 * 60 * 1000 ); + + // Check items in ascending order of date. + while ( !futureMap.isEmpty() ) { + QMap::iterator it = futureMap.begin(); + kDebug() << "Item with due date" << it.key(); + if ( it.key() > QDateTime::currentDateTime() ) { + const int secs = QDateTime::currentDateTime().secsTo( it.key() ) + 1; + kDebug() << "Future, in" << secs << "seconds."; + Q_ASSERT( secs >= 0 ); + if ( secs < 60 * 60 ) { + futureTimer->setInterval( secs * 1000 ); + } + break; // all others are in the future too + } + if ( !futureItems.contains( it.value() ) ) { + kDebug() << "Item disappeared."; + } else { + kDebug() << "Due date is here. Queuing."; + addIfComplete( it.value() ); + futureItems.remove( it.value() ); + } + futureMap.erase( it ); + } + + kDebug() << "Timer set to checkFuture again in" << futureTimer->interval() / 1000 << "seconds" + << "(that is" << futureTimer->interval() / 1000 / 60 << "minutes)."; + + futureTimer->start(); +} + +void OutboxQueue::Private::collectionFetched( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Failed to fetch outbox collection. Queue will be empty until the outbox changes."; + return; + } + + const ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob ); + kDebug() << "Fetched" << fetchJob->items().count() << "items."; + + foreach ( const Item &item, fetchJob->items() ) { + addIfComplete( item ); + } +} + +void OutboxQueue::Private::itemFetched( KJob *job ) +{ + if ( job->error() ) { + kDebug() << "Error fetching item:" << job->errorString() << ". Trying next item in queue."; + q->fetchOne(); + } + + const ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob ); + if ( fetchJob->items().count() != 1 ) { + kDebug() << "Fetched" << fetchJob->items().count() << ", expected 1. Trying next item in queue."; + q->fetchOne(); + } + + if ( !fetchJob->items().isEmpty() ) { + emit q->itemReady( fetchJob->items().first() ); + } +} + +void OutboxQueue::Private::localFoldersChanged() +{ + // Called on startup, and whenever the local folders change. + + if ( SpecialMailCollections::self()->hasDefaultCollection( SpecialMailCollections::Outbox ) ) { + // Outbox is ready, init the queue from it. + const Collection collection = SpecialMailCollections::self()->defaultCollection( SpecialMailCollections::Outbox ); + Q_ASSERT( collection.isValid() ); + + if ( outbox != collection ) { + monitor->setCollectionMonitored( outbox, false ); + monitor->setCollectionMonitored( collection, true ); + outbox = collection; + kDebug() << "Changed outbox to" << outbox.id(); + initQueue(); + } + } else { + // Outbox is not ready. Request it, since otherwise we will not know when + // new messages appear. + // (Note that we are a separate process, so we get no notification when + // MessageQueueJob requests the Outbox.) + monitor->setCollectionMonitored( outbox, false ); + outbox = Collection( -1 ); + + SpecialMailCollectionsRequestJob *job = new SpecialMailCollectionsRequestJob( q ); + job->requestDefaultCollection( SpecialMailCollections::Outbox ); + connect( job, SIGNAL(result(KJob*)), q, SLOT(localFoldersRequestResult(KJob*)) ); + + kDebug() << "Requesting outbox folder."; + job->start(); + } + + // make sure we have a place to dump the sent mails as well + if ( !SpecialMailCollections::self()->hasDefaultCollection( SpecialMailCollections::SentMail ) ) { + SpecialMailCollectionsRequestJob *job = new SpecialMailCollectionsRequestJob( q ); + job->requestDefaultCollection( SpecialMailCollections::SentMail ); + + kDebug() << "Requesting sent-mail folder"; + job->start(); + } +} + +void OutboxQueue::Private::localFoldersRequestResult( KJob *job ) +{ + if ( job->error() ) { + // We tried to create the outbox, but that failed. This could be because some + // other process, the mail app, for example, tried to create it at the + // same time. So try again, once or twice, but wait a little in between, longer + // each time. If we still haven't managed to create it after a few retries, + // error hard. + + if ( ++outboxDiscoveryRetries <= OUTBOX_DISCOVERY_RETRIES ) { + const int timeout = OUTBOX_DISCOVERY_WAIT_TIME * outboxDiscoveryRetries; + kWarning() << "Failed to get outbox folder. Retrying in: " << timeout; + QTimer::singleShot( timeout, q, SLOT(localFoldersChanged()) ); + } else { + kWarning() << "Failed to get outbox folder. Giving up."; ; + emit q->error( i18n( "Could not access the outbox folder (%1).", job->errorString() ) ); + } + return; + } + + localFoldersChanged(); +} + +void OutboxQueue::Private::itemAdded( const Item &item ) +{ + addIfComplete( item ); +} + +void OutboxQueue::Private::itemChanged( const Item &item ) +{ + addIfComplete( item ); + // TODO: if the item is moved out of the outbox, will I get itemChanged? +} + +void OutboxQueue::Private::itemMoved( const Item &item, const Collection &source, const Collection &destination ) +{ + if ( source == outbox ) { + itemRemoved( item ); + } else if ( destination == outbox ) { + addIfComplete( item ); + } +} + +void OutboxQueue::Private::itemRemoved( const Item &removedItem ) +{ + // @p item has size=0, so get the size from our own copy. + const int index = queue.indexOf( removedItem ); + if ( index == -1 ) { + // Item was not in queue at all. + return; + } + + Item item( queue.takeAt( index ) ); + kDebug() << "Item" << item.id() << "(size" << item.size() << ") was removed from the queue."; + totalSize -= item.size(); + + futureItems.remove( removedItem ); +} + +void OutboxQueue::Private::itemProcessed( const Item &item, bool result ) +{ + Q_ASSERT( ignore.contains( item.id() ) ); + if ( !result ) { + // Give the user a chance to re-send the item if it failed. + ignore.remove( item.id() ); + } +} + + +OutboxQueue::OutboxQueue( QObject *parent ) + : QObject( parent ), + d( new Private( this ) ) +{ + d->monitor = new Monitor( this ); + d->monitor->itemFetchScope().fetchAllAttributes(); + d->monitor->itemFetchScope().fetchFullPayload( false ); + connect( d->monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), + this, SLOT(itemAdded(Akonadi::Item)) ); + connect( d->monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), + this, SLOT(itemChanged(Akonadi::Item)) ); + connect( d->monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), + this, SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) ); + connect( d->monitor, SIGNAL(itemRemoved(Akonadi::Item)), + this, SLOT(itemRemoved(Akonadi::Item)) ); + + connect( SpecialMailCollections::self(), SIGNAL(defaultCollectionsChanged()), this, SLOT(localFoldersChanged()) ); + d->localFoldersChanged(); + + d->futureTimer = new QTimer( this ); + connect( d->futureTimer, SIGNAL(timeout()), this, SLOT(checkFuture()) ); + d->futureTimer->start( 60 * 60 * 1000 ); // 1 hour +} + +OutboxQueue::~OutboxQueue() +{ + delete d; +} + +bool OutboxQueue::isEmpty() const +{ + return d->queue.isEmpty(); +} + +int OutboxQueue::count() const +{ + if ( d->queue.count() == 0 ) { + // TODO Is this asking for too much? + Q_ASSERT( d->totalSize == 0 ); + } + return d->queue.count(); +} + +qulonglong OutboxQueue::totalSize() const +{ + return d->totalSize; +} + +void OutboxQueue::fetchOne() +{ + if ( isEmpty() ) { + kDebug() << "Empty queue."; + return; + } + + const Item item = d->queue.takeFirst(); + + d->totalSize -= item.size(); + Q_ASSERT( !d->ignore.contains( item.id() ) ); + d->ignore.insert( item.id() ); + + ItemFetchJob *job = new ItemFetchJob( item ); + job->fetchScope().fetchAllAttributes(); + job->fetchScope().fetchFullPayload(); + connect( job, SIGNAL(result(KJob*)), this, SLOT(itemFetched(KJob*)) ); +} + +#include "moc_outboxqueue.cpp" diff --git a/kdepim-runtime/agents/maildispatcher/outboxqueue.h b/kdepim-runtime/agents/maildispatcher/outboxqueue.h new file mode 100644 index 00000000..667b8167 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/outboxqueue.h @@ -0,0 +1,96 @@ +/* + 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 OUTBOXQUEUE_H +#define OUTBOXQUEUE_H + +#include + +#include +#include + +class KJob; + + +/** + * @short Monitors the outbox collection and provides a queue of messages for the MDA to send. + */ +class OutboxQueue : public QObject +{ + Q_OBJECT + friend class MailDispatcherAgent; + + public: + /** + * Creates a new outbox queue. + * + * @param parent The parent object. + */ + explicit OutboxQueue( QObject *parent = 0 ); + + /** + * Destroys the outbox queue. + */ + virtual ~OutboxQueue(); + + /** + * Returns whether the queue is empty. + */ + bool isEmpty() const; + + /** + * Returns the number of items in the queue. + */ + int count() const; + + /** + * Returns the size (in bytes) of all items in the queue. + */ + qulonglong totalSize() const; + + /** + * Fetches an item and emits itemReady() when done. + */ + void fetchOne(); + + Q_SIGNALS: + void itemReady( const Akonadi::Item &item ); + void newItems(); + void error( const QString &error ); + + private: + //@cond PRIVATE + class Private; + Private* const d; + + Q_PRIVATE_SLOT( d, void checkFuture() ) + Q_PRIVATE_SLOT( d, void collectionFetched( KJob* ) ) + Q_PRIVATE_SLOT( d, void itemFetched( KJob* ) ) + Q_PRIVATE_SLOT( d, void localFoldersChanged() ) + Q_PRIVATE_SLOT( d, void localFoldersRequestResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void itemAdded( Akonadi::Item ) ) + Q_PRIVATE_SLOT( d, void itemChanged( Akonadi::Item ) ) + Q_PRIVATE_SLOT( d, void itemMoved( Akonadi::Item, Akonadi::Collection, Akonadi::Collection ) ) + Q_PRIVATE_SLOT( d, void itemRemoved( Akonadi::Item ) ) + Q_PRIVATE_SLOT( d, void itemProcessed( Akonadi::Item, bool ) ) + //@endcond +}; + + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/sendjob.cpp b/kdepim-runtime/agents/maildispatcher/sendjob.cpp new file mode 100644 index 00000000..7cf837f2 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/sendjob.cpp @@ -0,0 +1,484 @@ +/* + 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 "sendjob.h" + +#include "storeresultjob.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace Akonadi; +using namespace KMime; +using namespace MailTransport; + +/** + * Private class that helps to provide binary compatibility between releases. + * @internal + */ +class SendJob::Private +{ + public: + Private( const Item &itm, SendJob *qq ) + : q( qq ), + item( itm ), + currentJob( 0 ), + interface( 0 ), + mailfilterInterface( 0 ), + aborting( false ) + { + } + + SendJob *const q; + Item item; + KJob *currentJob; + QString resourceId; + QDBusInterface *interface; + QDBusInterface *mailfilterInterface; + bool aborting; + + void doAkonadiTransport(); + void doTraditionalTransport(); + void doPostJob( bool transportSuccess, const QString &transportMessage ); + void storeResult( bool success, const QString &message = QString() ); + void abortPostJob(); + bool filterItem( int filterset ); + + // slots + void doTransport(); + void transportPercent( KJob *job, unsigned long percent ); + void transportResult( KJob *job ); + void resourceProgress( const AgentInstance &instance ); + void resourceResult( qlonglong itemId, int result, const QString &message ); + void postJobResult( KJob *job ); + void doEmitResult( KJob *job ); + void slotSentMailCollectionFetched( KJob *job ); +}; + + +void SendJob::Private::doTransport() +{ + kDebug() << "Transporting message."; + + if ( aborting ) { + kDebug() << "Marking message as aborted."; + q->setError( UserDefinedError ); + q->setErrorText( i18n( "Message sending aborted." ) ); + storeResult( false, i18n( "Message sending aborted." ) ); + return; + } + + // Is it an Akonadi transport or a traditional one? + const TransportAttribute *transportAttribute = item.attribute(); + Q_ASSERT( transportAttribute ); + if ( !transportAttribute->transport() ) { + storeResult( false, i18n( "Could not initiate message transport. Possibly invalid transport." ) ); + return; + } + + const TransportType type = transportAttribute->transport()->transportType(); + if ( !type.isValid() ) { + storeResult( false, i18n( "Could not send message. Invalid transport." ) ); + return; + } + + if ( !filterItem( 8 ) ) //BeforeOutbound + return; + + if ( type.type() == Transport::EnumType::Akonadi ) { + // Send the item directly to the resource that will send it. + resourceId = transportAttribute->transport()->host(); + doAkonadiTransport(); + } else { + // Use a traditional transport job. + doTraditionalTransport(); + } +} + +void SendJob::Private::doAkonadiTransport() +{ + Q_ASSERT( !resourceId.isEmpty() ); + Q_ASSERT( interface == 0 ); + + interface = new QDBusInterface( + QLatin1String( "org.freedesktop.Akonadi.Resource." ) + resourceId, + QLatin1String( "/Transport" ), QLatin1String( "org.freedesktop.Akonadi.Resource.Transport" ), + DBusConnectionPool::threadConnection(), q ); + + if ( !interface->isValid() ) { + storeResult( false, i18n( "Failed to get D-Bus interface of resource %1.", resourceId ) ); + delete interface; + interface = 0; + return; + } + + // Signals. + QObject::connect( AgentManager::self(), SIGNAL(instanceProgressChanged(Akonadi::AgentInstance)), + q, SLOT(resourceProgress(Akonadi::AgentInstance)) ); + QObject::connect( interface, SIGNAL(transportResult(qlonglong,int,QString)), + q, SLOT(resourceResult(qlonglong,int,QString)) ); + + // Start sending. + const QDBusReply reply = interface->call( QLatin1String( "send" ), item.id() ); + if ( !reply.isValid() ) { + storeResult( false, i18n( "Invalid D-Bus reply from resource %1.", resourceId ) ); + return; + } +} + +void SendJob::Private::doTraditionalTransport() +{ + const TransportAttribute *transportAttribute = item.attribute(); + TransportJob *job = TransportManager::self()->createTransportJob( transportAttribute->transportId() ); + + Q_ASSERT( job ); + Q_ASSERT( currentJob == 0 ); + + currentJob = job; + + // Message. + Q_ASSERT( item.hasPayload() ); + const Message::Ptr message = item.payload(); + bool needAssemble = false; + if ( message->hasHeader( "Bcc" ) ) { + message->removeHeader( "Bcc" ); + needAssemble = true; + } + if( message->hasHeader( "X-KMail-Identity" ) ) { + message->removeHeader( "X-KMail-Identity" ); + needAssemble = true; + } + if(needAssemble) { + message->assemble(); + } + const QByteArray content = message->encodedContent( true ) + "\r\n"; + Q_ASSERT( !content.isEmpty() ); + + // Addresses. + const AddressAttribute *addressAttribute = item.attribute(); + Q_ASSERT( addressAttribute ); + + job->setData( content ); + job->setSender( addressAttribute->from() ); + job->setTo( addressAttribute->to() ); + job->setCc( addressAttribute->cc() ); + job->setBcc( addressAttribute->bcc() ); + + // Signals. + connect( job, SIGNAL(result(KJob*)), + q, SLOT(transportResult(KJob*)) ); + connect( job, SIGNAL(percent(KJob*,ulong)), + q, SLOT(transportPercent(KJob*,ulong)) ); + + job->start(); +} + +void SendJob::Private::transportPercent( KJob *job, unsigned long ) +{ + Q_ASSERT( currentJob == job ); + kDebug() << "Processed amount" << job->processedAmount( KJob::Bytes ) + << "total amount" << job->totalAmount( KJob::Bytes ); + + q->setTotalAmount( KJob::Bytes, job->totalAmount( KJob::Bytes ) ); // Is not set at the time of start(). + q->setProcessedAmount( KJob::Bytes, job->processedAmount( KJob::Bytes ) ); +} + +void SendJob::Private::transportResult( KJob *job ) +{ + Q_ASSERT( currentJob == job ); + currentJob = 0; + doPostJob( !job->error(), job->errorString() ); +} + +void SendJob::Private::resourceProgress( const AgentInstance &instance ) +{ + if ( !interface ) { + // We might have gotten a very late signal. + kWarning() << "called but no resource job running!"; + return; + } + + if ( instance.identifier() == resourceId ) { + // This relies on the resource's progress representing the progress of + // sending this item. + q->setPercent( instance.progress() ); + } +} + +void SendJob::Private::resourceResult( qlonglong itemId, int result, + const QString &message ) +{ + Q_UNUSED( itemId ); + Q_ASSERT( interface ); + delete interface; // So that abort() knows the transport job is over. + interface = 0; + + const TransportResourceBase::TransportResult transportResult = + static_cast( result ); + + const bool success = ( transportResult == TransportResourceBase::TransportSucceeded ); + + Q_ASSERT( itemId == item.id() ); + doPostJob( success, message ); +} + +void SendJob::Private::abortPostJob() +{ + // We were unlucky and LocalFolders is recreating its stuff right now. + // We will not wait for it. + kWarning() << "Default sent mail collection unavailable, not moving the mail after sending."; + q->setError( UserDefinedError ); + q->setErrorText( i18n( "Default sent-mail folder unavailable. Keeping message in outbox." ) ); + storeResult( false, q->errorString() ); +} + +void SendJob::Private::doPostJob( bool transportSuccess, const QString &transportMessage ) +{ + kDebug() << "success" << transportSuccess << "message" << transportMessage; + + if ( !transportSuccess ) { + kDebug() << "Error transporting."; + q->setError( UserDefinedError ); + + const QString error = aborting ? i18n( "Message transport aborted." ) + : i18n( "Failed to transport message." ); + + q->setErrorText( error + QLatin1Char(' ') + transportMessage ); + storeResult( false, q->errorString() ); + } else { + kDebug() << "Success transporting."; + + // Delete or move to sent-mail. + const SentBehaviourAttribute *attribute = item.attribute(); + Q_ASSERT( attribute ); + + if ( attribute->sentBehaviour() == SentBehaviourAttribute::Delete ) { + kDebug() << "Deleting item from outbox."; + currentJob = new ItemDeleteJob( item ); + QObject::connect( currentJob, SIGNAL(result(KJob*)), q, SLOT(postJobResult(KJob*)) ); + } else { + if ( attribute->sentBehaviour() == SentBehaviourAttribute::MoveToDefaultSentCollection ) { + if ( SpecialMailCollections::self()->hasDefaultCollection( SpecialMailCollections::SentMail ) ) { + currentJob = new ItemMoveJob( item, SpecialMailCollections::self()->defaultCollection( SpecialMailCollections::SentMail ) , q ); + QObject::connect( currentJob, SIGNAL(result(KJob*)), q, SLOT(postJobResult(KJob*)) ); + } else { + abortPostJob(); + } + } else { + kDebug() << "sentBehaviour=" << attribute->sentBehaviour() << "using collection from attribute"; + currentJob = new CollectionFetchJob( attribute->moveToCollection(), Akonadi::CollectionFetchJob::Base ); + QObject::connect( currentJob, SIGNAL(result(KJob*)), + q, SLOT(slotSentMailCollectionFetched(KJob*)) ); + + } + } + } +} + +bool SendJob::Private::filterItem( int filterset ) +{ + Q_ASSERT( mailfilterInterface == 0 ); + + // TODO: create on stack + mailfilterInterface = new QDBusInterface( + QLatin1String( "org.freedesktop.Akonadi.MailFilterAgent" ), + QLatin1String( "/MailFilterAgent" ), QLatin1String( "org.freedesktop.Akonadi.MailFilterAgent" ), + DBusConnectionPool::threadConnection(), q ); + + + if ( !mailfilterInterface->isValid() ) { + storeResult( false, i18n( "Failed to get D-Bus interface of mailfilteragent." ) ); + delete mailfilterInterface; + mailfilterInterface = 0; + return false; + } + + //Outbound = 0x2 + const QDBusReply reply = mailfilterInterface->call( QLatin1String( "filterItem" ), item.id(), filterset, QString() ); + if ( !reply.isValid() ) { + storeResult( false, i18n( "Invalid D-Bus reply from mailfilteragent" ) ); + delete mailfilterInterface; + mailfilterInterface = 0; + return false; + } + + delete mailfilterInterface; + mailfilterInterface = 0; + return true; +} + +void SendJob::Private::slotSentMailCollectionFetched(KJob* job) +{ + Akonadi::Collection fetchCol; + bool ok = false; + if ( !job->error() ) { + const CollectionFetchJob *const fetchJob = qobject_cast( job ); + if ( !fetchJob->collections().isEmpty() ) { + fetchCol = fetchJob->collections().first(); + ok = true; + } + } + if ( !ok ) { + if ( !SpecialMailCollections::self()->hasDefaultCollection( SpecialMailCollections::SentMail ) ) { + abortPostJob(); + return; + } + fetchCol = SpecialMailCollections::self()->defaultCollection( SpecialMailCollections::SentMail ); + } + currentJob = new ItemMoveJob( item, fetchCol, q ); + QObject::connect( currentJob, SIGNAL(result(KJob*)), q, SLOT(postJobResult(KJob*)) ); +} + +void SendJob::Private::postJobResult( KJob *job ) +{ + Q_ASSERT( currentJob == job ); + currentJob = 0; + const SentBehaviourAttribute *attribute = item.attribute(); + Q_ASSERT( attribute ); + + + if ( job->error() ) { + kDebug() << "Error deleting or moving to sent-mail."; + + QString errorString; + switch( attribute->sentBehaviour() ) { + case SentBehaviourAttribute::Delete: + errorString = + i18n( "Sending succeeded, but failed to remove the message from the outbox." ); + break; + default: + errorString = + i18n( "Sending succeeded, but failed to move the message to the sent-mail folder." ); + break; + } + q->setError( UserDefinedError ); + q->setErrorText( errorString + QLatin1Char(' ') + job->errorString() ); + storeResult( false, q->errorString() ); + } else { + kDebug() << "Success deleting or moving to sent-mail."; + if ( !filterItem( 2 ) ) //Outbound + return; + if ( attribute->sentBehaviour() == SentBehaviourAttribute::Delete ) + q->emitResult(); + else + storeResult( true ); + } +} + +void SendJob::Private::storeResult( bool success, const QString &message ) +{ + kDebug() << "success" << success << "message" << message; + + Q_ASSERT( currentJob == 0 ); + currentJob = new StoreResultJob( item, success, message ); + connect( currentJob, SIGNAL(result(KJob*)), q, SLOT(doEmitResult(KJob*)) ); +} + +void SendJob::Private::doEmitResult( KJob *job ) +{ + Q_ASSERT( currentJob == job ); + currentJob = 0; + + if ( job->error() ) { + kWarning() << "Error storing result."; + q->setError( UserDefinedError ); + q->setErrorText( q->errorString() + QLatin1Char(' ') + i18n( "Failed to store result in item." ) + QLatin1Char(' ') + job->errorString() ); + } else { + kDebug() << "Success storing result."; + // It is still possible that the transport failed. + StoreResultJob* srJob = static_cast( job ); + if ( !srJob->success() ) { + q->setError( UserDefinedError ); + q->setErrorText( srJob->message() ); + } + } + + q->emitResult(); +} + + +SendJob::SendJob( const Item &item, QObject *parent ) + : KJob( parent ), + d( new Private( item, this ) ) +{ +} + +SendJob::~SendJob() +{ + delete d; +} + +void SendJob::start() +{ + QTimer::singleShot( 0, this, SLOT(doTransport()) ); +} + +void SendJob::setMarkAborted() +{ + Q_ASSERT( !d->aborting ); + d->aborting = true; +} + +void SendJob::abort() +{ + setMarkAborted(); + + if ( dynamic_cast( d->currentJob ) ) { + kDebug() << "Abort called, active transport job."; + // Abort transport. + d->currentJob->kill( KJob::EmitResult ); + } else if ( d->interface != 0 ) { + kDebug() << "Abort called, propagating to resource."; + // Abort resource doing transport. + AgentInstance instance = AgentManager::self()->instance( d->resourceId ); + instance.abortCurrentTask(); + } else { + kDebug() << "Abort called, but no transport job is active."; + // Either transport has not started, in which case doTransport will + // mark the item as aborted, or the item has already been sent, in which + // case there is nothing we can do. + } +} + +#include "moc_sendjob.cpp" diff --git a/kdepim-runtime/agents/maildispatcher/sendjob.h b/kdepim-runtime/agents/maildispatcher/sendjob.h new file mode 100644 index 00000000..aaa3c3ae --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/sendjob.h @@ -0,0 +1,91 @@ +/* + 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 SENDJOB_H +#define SENDJOB_H + +#include + +namespace Akonadi { +class Item; +} + +/** + * @short A job to send a mail + * + * This class takes a prevalidated Item with all the required attributes, + * sends it using MailTransport, and then stores the result of the sending + * operation in the item. + */ +class SendJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new send job. + * + * @param item The item to send. + * @param parent The parent object. + */ + explicit SendJob( const Akonadi::Item &item, QObject *parent = 0 ); + + /** + * Destroys the send job. + */ + virtual ~SendJob(); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * If this function is called before the job is started, the SendJob will + * just mark the item as aborted, instead of sending it. + * Do not call this function more than once. + */ + void setMarkAborted(); + + /** + * Aborts sending the item. + * + * This will give the item an ErrorAttribute of "aborted". + * (No need to call setMarkAborted() if you call abort().) + */ + void abort(); + + private: + //@cond PRIVATE + class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void doTransport() ) + Q_PRIVATE_SLOT( d, void transportPercent( KJob*, unsigned long ) ) + Q_PRIVATE_SLOT( d, void transportResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void resourceProgress( const Akonadi::AgentInstance& ) ) + Q_PRIVATE_SLOT( d, void resourceResult( qlonglong, int, const QString& ) ) + Q_PRIVATE_SLOT( d, void postJobResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void doEmitResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void slotSentMailCollectionFetched( KJob* ) ) + //@endcond +}; + + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/sentactionhandler.cpp b/kdepim-runtime/agents/maildispatcher/sentactionhandler.cpp new file mode 100644 index 00000000..97281d87 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/sentactionhandler.cpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#include "sentactionhandler.h" + +#include +#include +#include + +using namespace MailTransport; + +SentActionHandler::SentActionHandler( QObject *parent ) + : QObject( parent ) +{ +} + +void SentActionHandler::runAction( const SentActionAttribute::Action &action ) +{ + if ( action.type() == SentActionAttribute::Action::MarkAsReplied || + action.type() == SentActionAttribute::Action::MarkAsForwarded ) { + + const Akonadi::Item item( action.value().toLongLong() ); + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( item ); + connect( job, SIGNAL(result(KJob*)), SLOT(itemFetchResult(KJob*)) ); + job->setProperty( "type", static_cast( action.type() ) ); + } +} + +void SentActionHandler::itemFetchResult( KJob *job ) +{ + if ( job->error() ) { + kWarning() << job->errorText(); + return; + } + + Akonadi::ItemFetchJob *fetchJob = qobject_cast( job ); + if ( fetchJob->items().isEmpty() ) + return; + + Akonadi::Item item = fetchJob->items().first(); + + const SentActionAttribute::Action::Type type = static_cast( job->property( "type" ).toInt() ); + if ( type == SentActionAttribute::Action::MarkAsReplied ) { + item.setFlag( Akonadi::MessageFlags::Replied ); + } else if ( type == SentActionAttribute::Action::MarkAsForwarded ) { + item.setFlag( Akonadi::MessageFlags::Forwarded ); + } + + Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob( item ); + modifyJob->setIgnorePayload( true ); +} + diff --git a/kdepim-runtime/agents/maildispatcher/sentactionhandler.h b/kdepim-runtime/agents/maildispatcher/sentactionhandler.h new file mode 100644 index 00000000..319fce22 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/sentactionhandler.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef SENTACTIONHANDLER_H +#define SENTACTIONHANDLER_H + +#include + +#include + +class KJob; + +class SentActionHandler : public QObject +{ + Q_OBJECT + + public: + explicit SentActionHandler( QObject *parent = 0 ); + + void runAction( const MailTransport::SentActionAttribute::Action &action ); + + private Q_SLOTS: + void itemFetchResult( KJob *job ); +}; + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/settings.kcfgc b/kdepim-runtime/agents/maildispatcher/settings.kcfgc new file mode 100644 index 00000000..8c813ddb --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/settings.kcfgc @@ -0,0 +1,8 @@ +File=maildispatcheragent.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/agents/maildispatcher/settings.ui b/kdepim-runtime/agents/maildispatcher/settings.ui new file mode 100644 index 00000000..e1868bb4 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/settings.ui @@ -0,0 +1,77 @@ + + + Till Adam <adam@kde.org> + ConfigDialog + + + + 0 + 0 + 400 + 250 + + + + Mail Dispatcher Agent Settings + + + + + + Select the collection to be used as outbox: + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + Select the collection to move sent messages into: + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + Akonadi::CollectionRequester + QFrame +
akonadi/collectionrequester.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/agents/maildispatcher/storeresultjob.cpp b/kdepim-runtime/agents/maildispatcher/storeresultjob.cpp new file mode 100644 index 00000000..3e92964d --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/storeresultjob.cpp @@ -0,0 +1,140 @@ +/* + 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 "storeresultjob.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; +using namespace MailTransport; + +/** + * @internal + */ +class StoreResultJob::Private +{ + public: + Private( StoreResultJob *qq ) + : q( qq ), success( false ) + { + } + + StoreResultJob *const q; + Item item; + bool success; + QString message; + + // slots: + void fetchDone( KJob *job ); + void modifyDone( KJob *job ); +}; + + +void StoreResultJob::Private::fetchDone( KJob *job ) +{ + if ( job->error() ) + return; + + kDebug(); + + const ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob ); + if ( fetchJob->items().count() != 1 ) { + kError() << "Fetched" << fetchJob->items().count() << "items, expected 1."; + q->setError( Unknown ); + q->setErrorText( i18n( "Failed to fetch item." ) ); + q->commit(); + return; + } + + // Store result in item. + Item item = fetchJob->items().first(); + if ( success ) { + item.clearFlag( Akonadi::MessageFlags::Queued ); + item.setFlag( Akonadi::MessageFlags::Sent ); + item.setFlag( Akonadi::MessageFlags::Seen ); + } else { + item.setFlag( Akonadi::MessageFlags::HasError ); + ErrorAttribute *errorAttribute = new ErrorAttribute( message ); + item.addAttribute( errorAttribute ); + + // If dispatch failed, set the DispatchModeAttribute to Manual. + // Otherwise, the user will *never* be able to try sending the mail again, + // as Send Queued Messages will ignore it. + if ( item.hasAttribute() ) { + item.attribute()->setDispatchMode( MailTransport::DispatchModeAttribute::Manual ); + } else { + item.addAttribute( new DispatchModeAttribute( MailTransport::DispatchModeAttribute::Manual ) ); + } + } + + ItemModifyJob *modifyJob = new ItemModifyJob( item, q ); + QObject::connect( modifyJob, SIGNAL(result(KJob*)), q, SLOT(modifyDone(KJob*)) ); +} + +void StoreResultJob::Private::modifyDone( KJob *job ) +{ + if ( job->error() ) + return; + + kDebug(); + + q->commit(); +} + + +StoreResultJob::StoreResultJob( const Item &item, bool success, const QString &message, QObject *parent ) + : TransactionSequence( parent ), + d( new Private( this ) ) +{ + d->item = item; + d->success = success; + d->message = message; +} + +StoreResultJob::~StoreResultJob() +{ + delete d; +} + +void StoreResultJob::doStart() +{ + // Fetch item in case it was modified elsewhere. + ItemFetchJob *job = new ItemFetchJob( d->item, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(fetchDone(KJob*)) ); +} + +bool StoreResultJob::success() const +{ + return d->success; +} + +QString StoreResultJob::message() const +{ + return d->message; +} + +#include "moc_storeresultjob.cpp" diff --git a/kdepim-runtime/agents/maildispatcher/storeresultjob.h b/kdepim-runtime/agents/maildispatcher/storeresultjob.h new file mode 100644 index 00000000..e203fdfa --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/storeresultjob.h @@ -0,0 +1,74 @@ +/* + 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 STORERESULTJOB_H +#define STORERESULTJOB_H + +#include + +#include + +namespace Akonadi { +class Item; +} + +/** + * This class stores the result of a StoreResultJob in an item. + * First, it removes the 'queued' flag. + * After that, if the result was success, it stores the 'sent' flag. + * If the result was failure, it stores the 'error' flag and an ErrorAttribute. + */ +class StoreResultJob : public Akonadi::TransactionSequence +{ + Q_OBJECT + + public: + /** + * Creates a new store result job. + * + * @param item The item to store. + * @param success Whether the mail could be dispatched or not. + * @param message An error message in case the mail could not be dispatched. + * @param parent The parent object. + */ + explicit StoreResultJob( const Akonadi::Item &item, bool success, const QString &message, QObject *parent = 0 ); + + /** + * Destroys the store result job. + */ + virtual ~StoreResultJob(); + + bool success() const; + QString message() const; + + protected: + // reimpl from TransactionSequence + virtual void doStart(); + + private: + //@cond PRIVATE + class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void fetchDone( KJob *job ) ) + Q_PRIVATE_SLOT( d, void modifyDone( KJob *job ) ) + //@endcond +}; + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/tests/CMakeLists.txt b/kdepim-runtime/agents/maildispatcher/tests/CMakeLists.txt new file mode 100644 index 00000000..cf308f8f --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +# Stolen from kdepimlibs/akonadi/tests +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} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ) + + # based on kde4_add_unit_test + if (WIN32) + get_target_property( _loc ${_targetName} LOCATION ) + set(_executable ${_loc}.bat) + else () + set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_targetName}) + endif () + if (UNIX) + set(_executable ${_executable}.shell) + endif () + + find_program(_testrunner akonaditest) + + if (KDEPIM_RUN_ISOLATED_TESTS) + add_test( maildispatcheragent-${_targetName} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config.xml ${_executable} ) + endif () +endmacro(add_akonadi_isolated_test) + + + +add_akonadi_isolated_test( aborttest.cpp ) +add_akonadi_isolated_test( dupetest.cpp ) + diff --git a/kdepim-runtime/agents/maildispatcher/tests/TODO b/kdepim-runtime/agents/maildispatcher/tests/TODO new file mode 100644 index 00000000..1cdff105 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/TODO @@ -0,0 +1,6 @@ +* test online / offline +* figure out why ksyscoca is started (it's not the wallet apparently) +* aborttest: test aborting when there is >1 message queued +* test the various SendBehaviours and DispatchModes +* dupetest -> probably faster and more effective if I just use random intervals + of time between queuing messages diff --git a/kdepim-runtime/agents/maildispatcher/tests/aborttest.cpp b/kdepim-runtime/agents/maildispatcher/tests/aborttest.cpp new file mode 100644 index 00000000..6546d481 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/aborttest.cpp @@ -0,0 +1,237 @@ +/* + Copyright 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 "aborttest.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SPAM_ADDRESS "idanoka@gmail.com" +// NOTE: This test relies on a large SMTP message taking long enough to deliver, +// for it to call abort. So we need a valid receiver and a not-too-fast connection. +#define MESSAGE_MB 1 + +using namespace Akonadi; +using namespace KMime; +using namespace MailTransport; + +void AbortTest::initTestCase() +{ + QVERIFY( Control::start() ); + QTest::qWait( 1000 ); + + qRegisterMetaType(); + qRegisterMetaType(); + + // Get the outbox and clear it. + SpecialMailCollectionsRequestJob *rjob = new SpecialMailCollectionsRequestJob( this ); + rjob->requestDefaultCollection( SpecialMailCollections::Outbox ); + QTest::kWaitForSignal( rjob, SIGNAL(result(KJob*)) ); + outbox = SpecialMailCollections::self()->defaultCollection( SpecialMailCollections::Outbox ); + QVERIFY( outbox.isValid() ); + ItemDeleteJob *djob = new ItemDeleteJob( outbox ); + djob->exec(); // may give error if outbox empty + + // Verify transports. + akoTid = TransportManager::self()->defaultTransportId(); + Transport *t = TransportManager::self()->transportById( akoTid ); + QVERIFY( t ); + QCOMPARE( t->type(), int( Transport::EnumType::Akonadi ) ); + QList tids = TransportManager::self()->transportIds(); + tids.removeAll( akoTid ); + QCOMPARE( tids.count(), 1 ); + smtpTid = tids.first(); + t = TransportManager::self()->transportById( smtpTid ); + QVERIFY( t ); + QCOMPARE( t->type(), int( Transport::EnumType::SMTP ) ); + + // Set sink collection. + t = TransportManager::self()->transportById( akoTid ); + const QString rid = t->host(); + const AgentInstance agent = AgentManager::self()->instance( rid ); + QVERIFY( agent.isValid() ); + CollectionPathResolver *resolver = new CollectionPathResolver( QLatin1String("sink"), this ); + QVERIFY( resolver->exec() ); + sink = Collection( resolver->collection() ); + QVERIFY( sink.isValid() ); + QDBusInterface conf( QLatin1String("org.freedesktop.Akonadi.Resource.") + rid, + QLatin1String("/Settings"), QLatin1String("org.kde.Akonadi.MailTransportDummy.Settings") ); + QVERIFY( conf.isValid() ); + QDBusReply reply = conf.call( QLatin1String("setSink"), sink.id() ); + QVERIFY( reply.isValid() ); + agent.reconfigure(); + + // Watch sink collection. + monitor = new Monitor( this ); + monitor->setCollectionMonitored( sink ); +} + +void AbortTest::testAbort() +{ + // Get the MDA interface. + DispatcherInterface iface; + QVERIFY( iface.dispatcherInstance().isValid() ); + QVERIFY( iface.dispatcherInstance().isOnline() ); + + // Create a large message. + kDebug() << "Building message."; + Message::Ptr msg = Message::Ptr( new Message ); + QByteArray line( 70, 'a' ); + line.append( "\n" ); + QByteArray content( "\n" ); + for ( int i = 0; i < MESSAGE_MB * 1024 * 1024 / line.length() + 10; i++ ) { + content.append( line ); + } + QVERIFY( content.length() > MESSAGE_MB * 1024 * 1024 ); // >10MiB + msg->setContent( content ); + + // Queue the message. + kDebug() << "Queuing message."; + MessageQueueJob *qjob = new MessageQueueJob( this ); + qjob->setMessage( msg ); + qjob->transportAttribute().setTransportId( smtpTid ); + // default dispatch mode + // default sent-mail collection + qjob->addressAttribute().setFrom( QLatin1String("naiba") ); + qjob->addressAttribute().setTo( QStringList() <fetchScope().fetchAllAttributes(); + AKVERIFYEXEC( fjob ); + QCOMPARE( fjob->items().count(), 1 ); + Item item = fjob->items().first(); + QVERIFY( item.hasAttribute() ); + ErrorAttribute *eA = item.attribute(); + kDebug() << "Stored error:" << eA->message(); + + // "Fix" the item and send again, this time with the default (Akonadi) transport. + item.removeAttribute(); + item.clearFlag( Akonadi::MessageFlags::HasError ); + item.setFlag( Akonadi::MessageFlags::Queued ); + TransportAttribute *newTA = new TransportAttribute( akoTid ); + item.addAttribute( newTA ); + ItemModifyJob *cjob = new ItemModifyJob( item ); + QSignalSpy *addSpy = new QSignalSpy( monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)) ); + AKVERIFYEXEC( cjob ); + + // Verify that the item got sent. + for ( int ds = 0; addSpy->isEmpty(); ds++ ) { + QTest::qWait( 100 ); + if ( ds % 10 == 0 ) { + kDebug() << "Waiting for an item to be sent." << ds / 10 << "seconds elapsed."; + } + + QVERIFY2( ds <= 100, "Timeout" ); + } + QCOMPARE( addSpy->count(), 1 ); + QCOMPARE( iface.dispatcherInstance().status(), AgentInstance::Idle ); +} + +void AbortTest::testAbortWhileIdle() +{ + // Get the MDA interface. + DispatcherInterface iface; + QVERIFY( iface.dispatcherInstance().isValid() ); + QVERIFY( iface.dispatcherInstance().isOnline() ); + + // Abort thin air. + QCOMPARE( iface.dispatcherInstance().status(), AgentInstance::Idle ); + iface.dispatcherInstance().abortCurrentTask(); + QCOMPARE( iface.dispatcherInstance().status(), AgentInstance::Idle ); + + // Queue a message (to check that subsequent messages are being sent). + QVERIFY( monitor ); + QSignalSpy *addSpy = new QSignalSpy( monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)) ); + Message::Ptr msg = Message::Ptr( new Message ); + msg->setContent( "\ntestAbortWhileIdle" ); + MessageQueueJob *qjob = new MessageQueueJob( this ); + qjob->setMessage( msg ); + qjob->transportAttribute().setTransportId( akoTid ); + // default dispatch mode + // default sent-mail collection + qjob->addressAttribute().setFrom( QLatin1String("naiba") ); + qjob->addressAttribute().setTo( QStringList()<< QLatin1String("dracu" ) ); + QCOMPARE( iface.dispatcherInstance().status(), AgentInstance::Idle ); + AKVERIFYEXEC( qjob ); + + // Verify that the item got sent. + for ( int s = 0; addSpy->isEmpty(); s++ ) { + QTest::qWait( 1000 ); + QVERIFY2( s <= 10, "Timeout" ); + } + QCOMPARE( addSpy->count(), 1 ); + QCOMPARE( iface.dispatcherInstance().status(), AgentInstance::Idle ); +} + +QTEST_AKONADIMAIN( AbortTest, NoGUI ) + diff --git a/kdepim-runtime/agents/maildispatcher/tests/aborttest.h b/kdepim-runtime/agents/maildispatcher/tests/aborttest.h new file mode 100644 index 00000000..25eacc19 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/aborttest.h @@ -0,0 +1,54 @@ +/* + Copyright 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 ABORTTEST_H +#define ABORTTEST_H + +#include + +#include + +namespace Akonadi { + class Monitor; +} + +/** + This attempts to send a large message, then aborts it, then tries to send + it again and verify that it succeeds. + */ +class AbortTest : public QObject +{ + Q_OBJECT + + private Q_SLOTS: + void initTestCase(); + void testAbort(); + void testAbortWhileIdle(); + + private: + int akoTid; + int smtpTid; + Akonadi::Collection outbox; + Akonadi::Collection sink; + Akonadi::Monitor *monitor; + +}; + + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/tests/dupetest.cpp b/kdepim-runtime/agents/maildispatcher/tests/dupetest.cpp new file mode 100644 index 00000000..c5afea75 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/dupetest.cpp @@ -0,0 +1,221 @@ +/* + Copyright 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 "dupetest.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +static const int TIMEOUT_SECONDS = 60; +static const int MAXCOUNT = 99; // must be 2-digit! + +using namespace Akonadi; +using namespace KMime; +using namespace MailTransport; + +void DupeTest::initTestCase() +{ + QVERIFY( Control::start() ); + QTest::qWait( 1000 ); // give the MDA time to start + + qRegisterMetaType(); + qRegisterMetaType(); + + // we need a default Akonadi transport + int tid = TransportManager::self()->defaultTransportId(); + Transport *t = TransportManager::self()->transportById( tid ); + QVERIFY( t ); + QCOMPARE( t->type(), int( Transport::EnumType::Akonadi ) ); + + // set the sink collection + const QString rid = t->host(); + const AgentInstance agent = AgentManager::self()->instance( rid ); + QVERIFY( agent.isValid() ); + CollectionPathResolver *resolver = new CollectionPathResolver( QLatin1String("sink"), this ); + QVERIFY( resolver->exec() ); + sink = Collection( resolver->collection() ); + QVERIFY( sink.isValid() ); + QDBusInterface conf( QLatin1String("org.freedesktop.Akonadi.Resource.") + rid, + QLatin1String("/Settings"), QLatin1String("org.kde.Akonadi.MailTransportDummy.Settings") ); + QVERIFY( conf.isValid() ); + QDBusReply reply = conf.call( QLatin1String("setSink"), sink.id() ); + QVERIFY( reply.isValid() ); + agent.reconfigure(); + + // set up monitor + monitor = new Monitor( this ); + monitor->setCollectionMonitored( sink ); + monitor->itemFetchScope().fetchFullPayload(); +} + +void DupeTest::testDupes_data() +{ + QTest::addColumn( "message" ); // the prefix of the message to send (-msg## is appended) + QTest::addColumn( "count" ); // how many copies to send + QTest::addColumn( "delay" ); // number of ms to wait before sending next copy + + QTest::newRow( "1-nodelay" ) << "\n1-nodelay" << 1 << 0; + QTest::newRow( "2-nodelay" ) << "\n2-nodelay" << 2 << 0; + QTest::newRow( "5-nodelay" ) << "\n5-nodelay" << 5 << 0; + QTest::newRow( "10-nodelay" ) << "\n10-nodelay" << 10 << 0; + QTest::newRow( "20-nodelay" ) << "\n20-nodelay" << 20 << 0; + QTest::newRow( "50-nodelay" ) << "\n50-nodelay" << 50 << 0; + QTest::newRow( "99-nodelay" ) << "\n99-nodelay" << 99 << 0; + QTest::newRow( "2-veryshortdelay" ) << "\n2-veryshortdelay" << 2 << 20; + QTest::newRow( "5-veryshortdelay" ) << "\n5-veryshortdelay" << 5 << 20; + QTest::newRow( "10-veryshortdelay" ) << "\n10-veryshortdelay" << 10 << 20; + QTest::newRow( "20-veryshortdelay" ) << "\n20-veryshortdelay" << 20 << 20; + QTest::newRow( "50-veryshortdelay" ) << "\n50-veryshortdelay" << 50 << 20; + QTest::newRow( "99-veryshortdelay" ) << "\n99-veryshortdelay" << 99 << 20; + QTest::newRow( "2-shortdelay" ) << "\n2-shortdelay" << 2 << 100; + QTest::newRow( "5-shortdelay" ) << "\n5-shortdelay" << 5 << 100; + QTest::newRow( "10-shortdelay" ) << "\n10-shortdelay" << 10 << 100; + QTest::newRow( "20-shortdelay" ) << "\n20-shortdelay" << 20 << 100; + QTest::newRow( "50-shortdelay" ) << "\n50-shortdelay" << 50 << 100; + QTest::newRow( "99-shortdelay" ) << "\n99-shortdelay" << 99 << 99; + QTest::newRow( "2-longdelay" ) << "\n2-longdelay" << 2 << 1000; + QTest::newRow( "5-longdelay" ) << "\n5-longdelay" << 5 << 1000; + QTest::newRow( "5-verylongdelay" ) << "\n5-verylongdelay" << 5 << 4000; + Q_ASSERT( 99 <= MAXCOUNT ); + Q_ASSERT( MAXCOUNT < 100 ); // 2-digit + + // TODO I'm not sure more items means a better test + // TODO test large items + // TODO test modifying items while they are being sent... +} + +void DupeTest::testDupes() +{ + QFETCH( QString, message ); + QFETCH( int, count ); + QFETCH( int, delay ); + + // clean sink + ItemFetchJob *fjob = new ItemFetchJob( sink, this ); + AKVERIFYEXEC( fjob ); + if ( fjob->items().count() > 0 ) { + // this test is needed because ItemDeleteJob gives error if no items are found + ItemDeleteJob *djob = new ItemDeleteJob( sink, this ); + AKVERIFYEXEC( djob ); + } + fjob = new ItemFetchJob( sink, this ); + AKVERIFYEXEC( fjob ); + QCOMPARE( fjob->items().count(), 0 ); + + // queue messages + Q_ASSERT( monitor ); + QSignalSpy *addSpy = new QSignalSpy( monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)) ); + kDebug() << "Queuing" << count << "messages..."; + for ( int i = 0; i < count; i++ ) { + //kDebug() << "Queuing message" << i + 1 << "of" << count; + + Message::Ptr msg = Message::Ptr( new Message ); + msg->setContent( QString::fromLatin1( "%1-msg%2\n" ).arg( message ).arg( i + 1, 2, 10, QLatin1Char( '0' ) ).toLatin1() ); + + MessageQueueJob *job = new MessageQueueJob( this ); + job->setMessage( msg ); + job->transportAttribute().setTransportId( TransportManager::self()->defaultTransportId() ); + // default dispatch mode + // default sent-mail collection + job->addressAttribute().setFrom( QLatin1String("naiba") ); + job->addressAttribute().setTo( QStringList() << QLatin1String( "dracu" ) ); + //AKVERIFYEXEC( job ); + job->start(); + QTest::qWait( delay ); + } + kDebug() << "Queued" << count << "messages."; + + // wait for the MDA to send them + int seconds = 0; + while ( true ) { + seconds++; + QTest::qWait( 1000 ); + kDebug() << seconds << "seconds elapsed." << addSpy->count() << "messages got to sink."; + if ( addSpy->count() >= count ) + break; + +#if 0 + if ( seconds >= TIMEOUT_SECONDS ) { + kDebug() << "Timeout, gdb master!"; + QTest::qWait( 1000*1000 ); + } +#endif + QVERIFY2( seconds < TIMEOUT_SECONDS, "Timeout" ); + } + + // TODO I should verify that the MDA has actually finished its work and has an empty queue + QTest::qWait( 2000 ); + + // verify what has been sent + fjob = new ItemFetchJob( sink, this ); + fjob->fetchScope().fetchFullPayload(); + AKVERIFYEXEC( fjob ); + const Item::List items = fjob->items(); + int found[ MAXCOUNT ]; + for ( int i = 0; i < count; i++ ) { + found[i] = 0; + } + for ( int i = 0; i < items.count(); i++ ) { + QVERIFY( items[i].hasPayload() ); + Message::Ptr msg = items[i].payload(); + const QByteArray content = msg->encodedContent(); + //kDebug() << "i" << i << "content" << content; + int loc = content.indexOf( "-msg" ); + QVERIFY( loc >= 0 ); + bool ok; + int who = content.mid( loc + 4, 2 ).toInt( &ok ); + QVERIFY( ok ); + //kDebug() << "identified msg" << who; + QVERIFY( who > 0 && who <= count ); + found[ who - 1 ]++; + } + for ( int i = 0; i < count; i++ ) { + if ( found[i] > 1 ) { + kDebug() << "found duplicate message" << i + 1 << "(" << found[i] << "times )"; + } else if ( found[i] < 1 ) { + kDebug() << "didn't find message" << i + 1; + } + QCOMPARE( found[i], 1 ); + } + QCOMPARE( addSpy->count(), count ); + QCOMPARE( items.count(), count ); +} + +QTEST_AKONADIMAIN( DupeTest, NoGUI ) + diff --git a/kdepim-runtime/agents/maildispatcher/tests/dupetest.h b/kdepim-runtime/agents/maildispatcher/tests/dupetest.h new file mode 100644 index 00000000..580b563f --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/dupetest.h @@ -0,0 +1,51 @@ +/* + Copyright 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 DUPETEST_H +#define DUPETEST_H + +#include +#include + +#include +#include + + +/** + This queues a bunch of messages very quickly one after the other, lets the + MDA send them via the dummy mailtransport resource, and then verify that the + correct number of messages has been sent. + */ +class DupeTest : public QObject +{ + Q_OBJECT + + private Q_SLOTS: + void initTestCase(); + void testDupes_data(); + void testDupes(); + + private: + Akonadi::Collection sink; + Akonadi::Monitor *monitor; + +}; + + +#endif diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/config.xml b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/config.xml new file mode 100644 index 00000000..248ebb58 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/config.xml @@ -0,0 +1,7 @@ + + kdehome + xdgconfig + xdglocal + akonadi_knut_resource + akonadi_mailtransport_dummy_resource + diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc new file mode 100644 index 00000000..1cac492a --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc @@ -0,0 +1,3 @@ +[ProcessedDefaults] +defaultaddressbook=done +defaultcalendar=done diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc new file mode 100644 index 00000000..fb457b18 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc @@ -0,0 +1,4 @@ +[General] +DataFile[$e]=$KDEHOME/testdata.xml +FileWatchingEnabled=false + diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_mailtransport_dummy_resource_0rc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_mailtransport_dummy_resource_0rc new file mode 100644 index 00000000..6d160492 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/akonadi_mailtransport_dummy_resource_0rc @@ -0,0 +1,2 @@ +[General] +Sink=123 diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdebugrc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdebugrc new file mode 100644 index 00000000..32317f74 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdebugrc @@ -0,0 +1,110 @@ +[0] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5250] +InfoOutput=2 + +[5251] +InfoOutput=2 + +[5252] +InfoOutput=2 + +[5253] +InfoOutput=2 + +[5254] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5255] +InfoOutput=2 + +[5256] +InfoOutput=2 + +[5257] +InfoOutput=2 + +[5258] +InfoOutput=2 + +[5259] +InfoOutput=2 + +[5260] +InfoOutput=2 + +[5261] +InfoOutput=2 + +[5262] +InfoOutput=2 + +[5263] +InfoOutput=2 + +[5264] +InfoOutput=2 + +[5265] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5266] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5295] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5324] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[7129] +InfoOutput=2 diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdedrc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdedrc new file mode 100644 index 00000000..41d17814 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kdedrc @@ -0,0 +1,3 @@ +[General] +CheckSycoca=false +CheckFileStamps=false diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kwalletrc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kwalletrc new file mode 100644 index 00000000..8ba29ca1 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/kwalletrc @@ -0,0 +1,2 @@ +[Wallet] +Enabled=false diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/mailtransports b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/mailtransports new file mode 100644 index 00000000..13ac7b09 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/mailtransports @@ -0,0 +1,23 @@ +[$Version] +update_info=mailtransports.upd:initial-kmail-migration,mailtransports.upd:initial-knode-migration + +[General] +default-transport=666 + +[Transport 666] +host=akonadi_mailtransport_dummy_resource_0 +id=666 +name=Dummy Akonadi Transport +type=Akonadi + +[Transport 549190884] +auth=true +encryption=SSL +host=smtp.gmail.com +id=549190884 +name=idanoka2-stored +password=ᄒᄡᄚᄆᄒᄏᄊᆱᄎᆲᆱ +port=465 +storepass=true +user=idanoka2@gmail.com + diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/qttestrc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/qttestrc new file mode 100644 index 00000000..2e2f28ea --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/share/config/qttestrc @@ -0,0 +1,2 @@ +[Notification Messages] +WalletMigrate=false diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/testdata.xml b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/testdata.xml new file mode 100644 index 00000000..8cf871b9 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/kdehome/testdata.xml @@ -0,0 +1,4 @@ + + + + diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc new file mode 100644 index 00000000..7f738ce2 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc @@ -0,0 +1,4 @@ +[%General] + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdglocal/.keep b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdglocal/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/agents/maildispatcher/tests/unittestenv/xdglocal/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/agents/migration/CMakeLists.txt b/kdepim-runtime/agents/migration/CMakeLists.txt new file mode 100644 index 00000000..38e7bb03 --- /dev/null +++ b/kdepim-runtime/agents/migration/CMakeLists.txt @@ -0,0 +1,40 @@ + +include_directories( + ${Boost_INCLUDE_DIR} + tests +) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS} -fPIC") + +set(migrationagent_SRCS + migrationagent.cpp + migrationstatuswidget.cpp + migrationexecutor.cpp + migrationscheduler.cpp + tests/dummymigrator.cpp +) + +kde4_add_executable(akonadi_migration_agent ${migrationagent_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_migration_agent PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_migration_agent PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.migrationagent") + set_target_properties(akonadi_migration_agent PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Migrationagent") +endif () + +target_link_libraries(akonadi_migration_agent + gidmigration + ${KDE4_KDEUI_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${KDE4_KIDLETIME_LIBRARY} + ${KDE4_SOLID_LIBS} +) + +install(TARGETS akonadi_migration_agent ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES migrationagent.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") + +add_subdirectory(tests) \ No newline at end of file diff --git a/kdepim-runtime/agents/migration/Messages.sh b/kdepim-runtime/agents/migration/Messages.sh new file mode 100755 index 00000000..dd6f739c --- /dev/null +++ b/kdepim-runtime/agents/migration/Messages.sh @@ -0,0 +1,2 @@ +#! /bin/sh +$XGETTEXT *.cpp -o $podir/akonadi_migration_agent.pot diff --git a/kdepim-runtime/agents/migration/migrationagent.cpp b/kdepim-runtime/agents/migration/migrationagent.cpp new file mode 100644 index 00000000..9c5ddc46 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationagent.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "migrationagent.h" + +#include "migrationstatuswidget.h" + +#include +#include +#include +#include +#include +#include + +namespace Akonadi { + +MigrationAgent::MigrationAgent(const QString &id) +: AgentBase(id), + mScheduler(new KUiServerJobTracker) +{ + mScheduler.addMigrator(QSharedPointer(new GidMigrator(KABC::Addressee::mimeType()))); +} + +void MigrationAgent::configure(WId windowId) +{ + KDialog *dlg = new KDialog(); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setMainWidget(new MigrationStatusWidget(mScheduler, dlg)); + dlg->setCaption(i18nc("Title of the window that shows the status of the migration agent and offers controls to start/stop individual migration jobs.", "Migration Status")); + dlg->setButtons(KDialog::Close); + dlg->resize(600, 300); + + if (windowId) { + #ifndef Q_WS_WIN + KWindowSystem::setMainWindow(dlg, windowId); + #else + KWindowSystem::setMainWindow(dlg, (HWND)windowId); + #endif + } + dlg->show(); +} + +} + +AKONADI_AGENT_MAIN(Akonadi::MigrationAgent) + diff --git a/kdepim-runtime/agents/migration/migrationagent.desktop b/kdepim-runtime/agents/migration/migrationagent.desktop new file mode 100644 index 00000000..c246fa33 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationagent.desktop @@ -0,0 +1,44 @@ +[Desktop Entry] +Name=Migration Agent +Name[bs]=Migracijski agent +Name[ca]=Agent de migració +Name[ca@valencia]=Agent de migració +Name[da]=Migreringsagent +Name[de]=Migrations-Assistent +Name[el]=ΠÏάκτοÏας μεταφοÏάς +Name[en_GB]=Migration Agent +Name[es]=Agente de migración +Name[et]=Migreerimisagent +Name[fi]=Siirtoagentti +Name[fr]=Agent de migration +Name[gl]=Axente de migración +Name[hu]=KöltöztetÅ‘ ügynök +Name[ia]=Agente de migration +Name[it]=Agente di migrazione +Name[kk]=Көшіп ауыÑу агенті +Name[ko]=ì´ì „ 마법사 +Name[lt]=Migravimo agentas +Name[nb]=Migreringsagent +Name[nds]=Ãœmwannelhölper +Name[nl]=Migratie-agent +Name[pl]=Agent migracji +Name[pt]=Agente de Migração +Name[pt_BR]=Agente de Migração +Name[ru]=Ðгент переноÑа данных +Name[sk]=Agent migrácie +Name[sr]=Ðгент Ñелидбе +Name[sr@ijekavian]=Ðгент Ñелидбе +Name[sr@ijekavianlatin]=Agent selidbe +Name[sr@latin]=Agent selidbe +Name[sv]=Överföringsmodul +Name[tr]=Taşıma Yardımcısı +Name[uk]=Ðгент перенеÑÐµÐ½Ð½Ñ +Name[x-test]=xxMigration Agentxx +Name[zh_CN]=è¿ç§»åŠ©æ‰‹ +Name[zh_TW]=移æ¤ä»£ç†ç¨‹å¼ +Type=AkonadiAgent +Exec=akonadi_migration_agent +Icon=system-reboot + +X-Akonadi-Capabilities=Unique,Autostart +X-Akonadi-Identifier=akonadi_migration_agent diff --git a/kdepim-runtime/agents/migration/migrationagent.h b/kdepim-runtime/agents/migration/migrationagent.h new file mode 100644 index 00000000..c70ffe84 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationagent.h @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MIGRATIONAGENT_H +#define MIGRATIONAGENT_H + +#include +#include "migrationscheduler.h" + +namespace Akonadi { + +class MigrationAgent : public AgentBase, public AgentBase::ObserverV2 +{ + Q_OBJECT +public: + explicit MigrationAgent(const QString &id); + virtual void configure(WId windowId); +private: + MigrationScheduler mScheduler; +}; + +} + +#endif diff --git a/kdepim-runtime/agents/migration/migrationexecutor.cpp b/kdepim-runtime/agents/migration/migrationexecutor.cpp new file mode 100644 index 00000000..1d58fd48 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationexecutor.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "migrationexecutor.h" + +#include + +MigrationExecutor::MigrationExecutor() +: KJob(), + mSuspended(false), + mTotalAmount(0), + mAlreadyProcessed(0) +{ + setCapabilities(Suspendable); +} + +void MigrationExecutor::start() +{ + setPercent(0); + emit description(this, i18nc("User visible name of ongoing Akonadi migration jobs", "PIM Maintenance")); +} + +void MigrationExecutor::add(const QSharedPointer &migrator) +{ + mTotalAmount++; + mQueue.enqueue(migrator.toWeakRef()); + executeNext(); +} + +void MigrationExecutor::executeNext() +{ + if (mCurrentMigrator || mSuspended) { + return; + } + QSharedPointer migrator; + while (!migrator && !mQueue.isEmpty()) { + mCurrentMigrator = mQueue.dequeue(); + migrator = mCurrentMigrator.toStrongRef(); + } + if (migrator) { + emit infoMessage(this, i18nc("PIM-Maintenance is in progress.", "In progress...")); + connect(migrator.data(), SIGNAL(stoppedProcessing()), this, SLOT(onStoppedProcessing())); + migrator->start(); + } else { + // Reset the notification status, otherwise we get notification "In progress...[finished]" + // without any description that it's related to PIM-Maintenance + emit infoMessage(this, i18n("PIM Maintenance")); + emitResult(); + } +} + +void MigrationExecutor::onStoppedProcessing() +{ + mAlreadyProcessed++; + Q_ASSERT(mTotalAmount > 0); + //TODO: setProcessedAmount would be better, but we need support for suitable units first (there's only files, folders, bytes). + setPercent(mAlreadyProcessed*100.0/mTotalAmount); + mCurrentMigrator.clear(); + executeNext(); +} + +bool MigrationExecutor::doSuspend() +{ + if (mCurrentMigrator) { + QSharedPointer migrator = mCurrentMigrator.toStrongRef(); + if (migrator) { + migrator->pause(); + } else { + mCurrentMigrator.clear(); + } + } + emit infoMessage(this, i18nc("PIM-Maintenance is paused.", "Paused.")); + mSuspended = true; + return true; +} + +bool MigrationExecutor::doResume() +{ + mSuspended = false; + if (mCurrentMigrator) { + QSharedPointer migrator = mCurrentMigrator.toStrongRef(); + if (migrator) { + migrator->resume(); + } else { + mCurrentMigrator.clear(); + } + } + executeNext(); + return true; +} + diff --git a/kdepim-runtime/agents/migration/migrationexecutor.h b/kdepim-runtime/agents/migration/migrationexecutor.h new file mode 100644 index 00000000..cd25a650 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationexecutor.h @@ -0,0 +1,61 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MIGRATIONEXECUTOR_H +#define MIGRATIONEXECUTOR_H + +#include +#include +#include +#include + +/** + * An executor can contain multiple jobs that are scheduled by the executor. + * + * The executor is responsible for starting/pausing/stopping the individual migrators. + * + * This job is used to give overall progress information and start/stop controls to KUIServer via KUiServerJobTracker. + */ +class MigrationExecutor : public KJob +{ + Q_OBJECT +public: + MigrationExecutor(); + void add(const QSharedPointer &); + virtual void start(); + +protected: + virtual bool doResume(); + virtual bool doSuspend(); + +private slots: + void onStoppedProcessing(); + void executeNext(); + +private: + QQueue< QWeakPointer > mQueue; + QWeakPointer mCurrentMigrator; + bool mSuspended; + int mTotalAmount; + int mAlreadyProcessed; +}; + +#endif diff --git a/kdepim-runtime/agents/migration/migrationscheduler.cpp b/kdepim-runtime/agents/migration/migrationscheduler.cpp new file mode 100644 index 00000000..0d02f9f3 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationscheduler.cpp @@ -0,0 +1,299 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "migrationscheduler.h" + +#include +#include +#include +#include + +#include "migrationexecutor.h" + +void LogModel::message(MigratorBase::MessageType type, const QString &msg) +{ + switch ( type ) { + case MigratorBase::Success: { + QStandardItem *item = new QStandardItem(KIcon(QLatin1String("dialog-ok-apply")), msg); + item->setEditable(false); + appendRow(item); + break; + } + case MigratorBase::Skip: { + QStandardItem *item = new QStandardItem(KIcon(QLatin1String("dialog-ok")), msg); + item->setEditable(false); + appendRow(item); + break; + } + case MigratorBase::Info: { + QStandardItem *item = new QStandardItem(KIcon(QLatin1String("dialog-information")), msg); + item->setEditable(false); + appendRow(item); + break; + } + case MigratorBase::Warning: { + QStandardItem *item = new QStandardItem(KIcon(QLatin1String("dialog-warning")), msg); + item->setEditable(false); + appendRow(item); + break; + } + case MigratorBase::Error: { + QStandardItem *item = new QStandardItem(KIcon(QLatin1String("dialog-error")), msg); + item->setEditable(false); + appendRow(item); + break; + } + default: + kError() << "unknown type " << type; + } +} + + +Row::Row(const QSharedPointer &migrator, MigratorModel &model) +: QObject(), + mMigrator(migrator), + mModel(model) +{ + connect(migrator.data(), SIGNAL(stateChanged(MigratorBase::MigrationState)), this, SLOT(stateChanged(MigratorBase::MigrationState))); + connect(migrator.data(), SIGNAL(progress(int)), this, SLOT(progress(int))); +} + +bool Row::operator==(const Row &other) const +{ + return mMigrator->identifier() == other.mMigrator->identifier(); +} + +void Row::stateChanged(MigratorBase::MigrationState /*newState*/) +{ + mModel.columnChanged(*this, MigratorModel::State); +} + +void Row::progress(int /*prog*/) +{ + mModel.columnChanged(*this, MigratorModel::Progress); +} + +int MigratorModel::positionOf(const Row &row) +{ + int pos = 0; + foreach (const QSharedPointer &r, mMigrators) { + if (row == *r) { + return pos; + } + pos++; + } + return -1; +} + +void MigratorModel::columnChanged(const Row &row, int col) +{ + const int p = positionOf(row); + Q_ASSERT(p >= 0); + if (p >= 0) { + const QModelIndex idx = index(p, col); + emit dataChanged(idx, idx); + } +} + +bool MigratorModel::addMigrator(const QSharedPointer &m) +{ + if (migrator(m->identifier())) { + kWarning() << "Model already contains a migrator with the identifier: " << m; + return false; + } + const int pos = mMigrators.size(); + beginInsertRows(QModelIndex(), pos, pos); + mMigrators.append(QSharedPointer(new Row(m, *this))); + endInsertRows(); + return true; +} + +int MigratorModel::columnCount(const QModelIndex &/*parent*/) const +{ + return ColumnCount; +} + +int MigratorModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return mMigrators.size(); + } + return 0; +} + +QModelIndex MigratorModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row >= rowCount(parent) || row < 0) { + return QModelIndex(); + } + return createIndex(row, column, static_cast(mMigrators.at(row).data())); +} + +QModelIndex MigratorModel::parent(const QModelIndex &/*child*/) const +{ + return QModelIndex(); +} + +QVariant MigratorModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const +{ + if (role == Qt::DisplayRole) { + switch (section) { + case Name: + return i18nc("Name of the migrator in this row", "Name"); + case Progress: + return i18nc("Progress of the mgirator in %", "Progress"); + case State: + return i18nc("Current status of the migrator (done, in progress, ...)", "Status"); + default: + Q_ASSERT(false); + } + } + return QVariant(); +} + +QVariant MigratorModel::data(const QModelIndex &index, int role) const +{ + const Row *row = static_cast(index.internalPointer()); + const QSharedPointer migrator(row->mMigrator); + if (!migrator) { + kWarning() << "migrator not found"; + return QVariant(); + } + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case Name: + return migrator->displayName(); + case Progress: + return QString::fromLatin1("%1 %").arg(migrator->progress()); + case State: + return migrator->status(); + default: + Q_ASSERT(false); + } + case IdentifierRole: + return migrator->identifier(); + case LogfileRole: + return migrator->logfile(); + case Qt::ToolTipRole: + return migrator->description(); + default: + break; + } + return QVariant(); +} + +QSharedPointer MigratorModel::migrator(const QString &identifier) const +{ + foreach (const QSharedPointer &row, mMigrators) { + if (row->mMigrator->identifier() == identifier) { + return row->mMigrator; + } + } + return QSharedPointer(); +} + +QList< QSharedPointer > MigratorModel::migrators() const +{ + QList< QSharedPointer > migrators; + foreach (const QSharedPointer &row, mMigrators) { + return migrators << row->mMigrator; + } + return migrators; +} + +MigrationScheduler::MigrationScheduler(KJobTrackerInterface *jobTracker, QObject *parent) + :QObject(parent), + mModel(new MigratorModel), + mJobTracker(jobTracker) +{ +} + +MigrationScheduler::~MigrationScheduler() +{ + delete mAutostartExecutor; +} + +void MigrationScheduler::addMigrator(const QSharedPointer &migrator) +{ + if (mModel->addMigrator(migrator)) { + QSharedPointer logModel(new LogModel); + connect(migrator.data(), SIGNAL(message(MigratorBase::MessageType,QString)), logModel.data(), SLOT(message(MigratorBase::MessageType,QString))); + mLogModel.insert(migrator->identifier(), logModel); + if (migrator->shouldAutostart()) { + checkForAutostart(migrator); + } + } +} + +QAbstractItemModel& MigrationScheduler::model() +{ + return *mModel; +} + +QStandardItemModel& MigrationScheduler::logModel(const QString &identifier) +{ + Q_ASSERT(mLogModel.contains(identifier)); + return *mLogModel.value(identifier); +} + +void MigrationScheduler::checkForAutostart(const QSharedPointer &migrator) +{ + if (migrator->migrationState() != MigratorBase::Complete) { + + if (!mAutostartExecutor) { + mAutostartExecutor = new MigrationExecutor; + if (mJobTracker) { + mJobTracker->registerJob(mAutostartExecutor); + } + + mAutostartExecutor->start(); + } + + mAutostartExecutor->add(migrator); + } +} + +void MigrationScheduler::start(const QString &identifier) +{ + //TODO create separate executor? + const QSharedPointer m = mModel->migrator(identifier); + if (m) { + m->start(); + } +} + +void MigrationScheduler::pause(const QString &identifier) +{ + const QSharedPointer m = mModel->migrator(identifier); + if (m) { + m->pause(); + } +} + +void MigrationScheduler::abort(const QString &identifier) +{ + const QSharedPointer m = mModel->migrator(identifier); + if (m) { + m->abort(); + } +} + diff --git a/kdepim-runtime/agents/migration/migrationscheduler.h b/kdepim-runtime/agents/migration/migrationscheduler.h new file mode 100644 index 00000000..968f4d25 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationscheduler.h @@ -0,0 +1,133 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MIGRATIONSCHEDULER_H +#define MIGRATIONSCHEDULER_H + +#include +#include +#include +#include +#include +#include +#include + +class MigrationExecutor; +class KJobTrackerInterface; +class MigratorModel; + +class LogModel : public QStandardItemModel +{ + Q_OBJECT +public slots: + void message(MigratorBase::MessageType type, const QString &msg); +}; + +class Row: public QObject +{ + Q_OBJECT +public: + QSharedPointer mMigrator; + MigratorModel &mModel; + + explicit Row(const QSharedPointer &migrator, MigratorModel &model); + + bool operator==(const Row &other) const; + +private slots: + void stateChanged(MigratorBase::MigrationState); + void progress(int); +}; + +/** + * The model serves as container for the migrators and exposes the status of each migrator. + * + * It can be plugged into a Listview to inform about the migration progress. + */ +class MigratorModel: public QAbstractItemModel +{ +public: + enum Roles { + IdentifierRole = Qt::UserRole + 1, + LogfileRole + }; + bool addMigrator(const QSharedPointer &migrator); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + QSharedPointer migrator(const QString &identifier) const; + QList< QSharedPointer > migrators() const; + +private: + enum Columns { + Name = 0, + Progress = 1, + State = 2, + ColumnCount + }; + friend class Row; + int positionOf(const Row &); + void columnChanged(const Row &, int column); + QList< QSharedPointer > mMigrators; +}; + +/** + * Scheduler for migration jobs. + * + * Status information is exposed via getModel, which returns a list model containing all migrators with basic information. + * Additionally a logmodel is available via getLogModel for each migrator. The logmodel is continuously filled with information, and can be requested and displayed at any time. + * + * Migrators which return true on shouldAutostart() automatically enter a queue to be processed one after the other. + * When manually triggered it is possible though to run multiple jobs in parallel. + */ +class MigrationScheduler : public QObject +{ + Q_OBJECT +public: + explicit MigrationScheduler(KJobTrackerInterface *jobTracker = 0, QObject *parent = 0); + virtual ~MigrationScheduler(); + + void addMigrator(const QSharedPointer &migrator); + + //A model for the view + QAbstractItemModel &model(); + QStandardItemModel &logModel(const QString &identifier); + + //Control + void start(const QString &identifier); + void pause(const QString &identifier); + void abort(const QString &identifier); + +private: + void checkForAutostart(const QSharedPointer &migrator); + + QScopedPointer mModel; + QHash > mLogModel; + QPointer mAutostartExecutor; + KJobTrackerInterface *mJobTracker; +}; + +#endif // MIGRATIONSCHEDULER_H diff --git a/kdepim-runtime/agents/migration/migrationstatuswidget.cpp b/kdepim-runtime/agents/migration/migrationstatuswidget.cpp new file mode 100644 index 00000000..6c3089f5 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationstatuswidget.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "migrationstatuswidget.h" +#include "migrationscheduler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MigrationStatusWidget::MigrationStatusWidget(MigrationScheduler &scheduler, QWidget *parent) + :QWidget(parent), + mScheduler(scheduler) +{ + QVBoxLayout *vboxLayout = new QVBoxLayout; + { + KToolBar *toolbar = new KToolBar(QLatin1String("MigrationControlToolbar"), this); + + QAction *start = toolbar->addAction(QLatin1String("Start")); + start->setIcon(KIcon(QLatin1String("media-playback-start"))); + connect(start, SIGNAL(triggered(bool)), this, SLOT(startSelected())); + + QAction *pause = toolbar->addAction(QLatin1String("Pause")); + pause->setIcon(KIcon(QLatin1String("media-playback-pause"))); + connect(pause, SIGNAL(triggered(bool)), this, SLOT(pauseSelected())); + + QAction *abort = toolbar->addAction(QLatin1String("Abort")); + abort->setIcon(KIcon(QLatin1String("media-playback-stop"))); + connect(abort, SIGNAL(triggered(bool)), this, SLOT(abortSelected())); + + vboxLayout->addWidget(toolbar); + } + { + QTreeView *treeView = new QTreeView(this); + treeView->setModel(&mScheduler.model()); + mSelectionModel = treeView->selectionModel(); + Q_ASSERT(mSelectionModel); + //Not sure why this is required, but otherwise the view doesn't load anything from the model + treeView->update(QModelIndex()); + connect(treeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onItemActivated(QModelIndex))); + + vboxLayout->addWidget(treeView); + } + setLayout(vboxLayout); +} + +void MigrationStatusWidget::startSelected() +{ + foreach (const QModelIndex &index, mSelectionModel->selectedRows()) { + mScheduler.start(index.data(MigratorModel::IdentifierRole).toString()); + } +} + +void MigrationStatusWidget::pauseSelected() +{ + foreach (const QModelIndex &index, mSelectionModel->selectedRows()) { + mScheduler.pause(index.data(MigratorModel::IdentifierRole).toString()); + } +} + +void MigrationStatusWidget::abortSelected() +{ + foreach (const QModelIndex &index, mSelectionModel->selectedRows()) { + mScheduler.abort(index.data(MigratorModel::IdentifierRole).toString()); + } +} + +void MigrationStatusWidget::onItemActivated(const QModelIndex &index) +{ + KDialog *dlg = new KDialog(this); + QWidget *widget = new QWidget(dlg); + + QVBoxLayout *vboxLayout = new QVBoxLayout; + { + QListView *listView = new QListView(widget); + listView->setModel(&mScheduler.logModel(index.data(MigratorModel::IdentifierRole).toString())); + listView->setAutoScroll(true); + listView->scrollToBottom(); + vboxLayout->addWidget(listView); + } + { + QHBoxLayout *hboxLayout = new QHBoxLayout; + QLabel *label = new QLabel(QString::fromLatin1("%2").arg(index.data(MigratorModel::LogfileRole).toString()).arg(ki18n("Logfile").toString()), widget); + label->setOpenExternalLinks(true); + hboxLayout->addWidget(label); + hboxLayout->addStretch(); + vboxLayout->addLayout(hboxLayout); + } + widget->setLayout(vboxLayout); + dlg->setMainWidget(widget); + + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setCaption(i18nc("Title of the window displaying the log of a single migration job.", "Migration Info")); + dlg->setButtons(KDialog::Close); + dlg->resize(600, 300); + dlg->show(); +} + + diff --git a/kdepim-runtime/agents/migration/migrationstatuswidget.h b/kdepim-runtime/agents/migration/migrationstatuswidget.h new file mode 100644 index 00000000..77748e82 --- /dev/null +++ b/kdepim-runtime/agents/migration/migrationstatuswidget.h @@ -0,0 +1,46 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MIGRATIONSTATUSWIDGET_H +#define MIGRATIONSTATUSWIDGET_H + +#include "migrationscheduler.h" +#include +#include + +class MigrationStatusWidget: public QWidget +{ + Q_OBJECT +public: + explicit MigrationStatusWidget(MigrationScheduler &scheduler, QWidget *parent = 0); +private slots: + void startSelected(); + void pauseSelected(); + void abortSelected(); +private: + MigrationScheduler &mScheduler; + QItemSelectionModel *mSelectionModel; +public slots: + void onItemActivated(const QModelIndex &); +}; + +#endif // MIGRATIONCONFIGDIALOG_H + diff --git a/kdepim-runtime/agents/migration/tests/CMakeLists.txt b/kdepim-runtime/agents/migration/tests/CMakeLists.txt new file mode 100644 index 00000000..9d08f08e --- /dev/null +++ b/kdepim-runtime/agents/migration/tests/CMakeLists.txt @@ -0,0 +1,16 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +kde4_add_executable(schedulertest schedulertest.cpp ../migrationscheduler.cpp ../migrationexecutor.cpp) +target_link_libraries(schedulertest + gidmigration + ${KDE4_KDEUI_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTTEST_LIBRARY} +) +add_test(schedulertest schedulertest) \ No newline at end of file diff --git a/kdepim-runtime/agents/migration/tests/dummymigrator.cpp b/kdepim-runtime/agents/migration/tests/dummymigrator.cpp new file mode 100644 index 00000000..a188155a --- /dev/null +++ b/kdepim-runtime/agents/migration/tests/dummymigrator.cpp @@ -0,0 +1,68 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "dummymigrator.h" +#include +#include + +DummyMigrator::DummyMigrator(const QString &identifier) +: MigratorBase(QLatin1String("dummymigrator") + identifier, QString(), QString()) +{} + +QString DummyMigrator::displayName() const +{ + return QLatin1String("dummymigrator"); +} + +void DummyMigrator::startWork() +{ + kDebug(); + QTimer::singleShot(10000, this, SLOT(onTimerElapsed())); +} + +void DummyMigrator::onTimerElapsed() +{ + kDebug(); + setMigrationState(Complete); +} + +bool DummyMigrator::shouldAutostart() const +{ + return true; +} + +bool DummyMigrator::canStart() +{ + return true; +} + +void DummyMigrator::pause() +{ + kDebug(); + MigratorBase::pause(); +} + +void DummyMigrator::abort() +{ + kDebug(); + MigratorBase::abort(); +} + diff --git a/kdepim-runtime/agents/migration/tests/dummymigrator.h b/kdepim-runtime/agents/migration/tests/dummymigrator.h new file mode 100644 index 00000000..bd570157 --- /dev/null +++ b/kdepim-runtime/agents/migration/tests/dummymigrator.h @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef DUMMYMIGRATOR_H +#define DUMMYMIGRATOR_H + +#include + +/** + * Dummy migrator that simply completes after 10s and always autostarts. + * Add to the scheduler to play with the migrationagent. + */ +class DummyMigrator : public MigratorBase +{ + Q_OBJECT +public: + explicit DummyMigrator(const QString &identifier); + + virtual QString displayName() const; + virtual void startWork(); + + virtual bool shouldAutostart() const; + virtual bool canStart(); + virtual void pause(); + + virtual void abort(); +private slots: + void onTimerElapsed(); +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/agents/migration/tests/schedulertest.cpp b/kdepim-runtime/agents/migration/tests/schedulertest.cpp new file mode 100644 index 00000000..c3fb9976 --- /dev/null +++ b/kdepim-runtime/agents/migration/tests/schedulertest.cpp @@ -0,0 +1,293 @@ +/* + * Copyright 2013 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include "../migrationscheduler.h" +#include + +Q_DECLARE_METATYPE(QModelIndex) + +class Testmigrator: public MigratorBase +{ + Q_OBJECT +public: + explicit Testmigrator(const QString &identifier, QObject *parent = 0): + MigratorBase(QLatin1String("testmigrator") + identifier, QString(), QString(), parent), mAutostart(false) + {} + + virtual QString displayName() const + { + return QLatin1String("name"); + } + + virtual void startWork() + {} + + virtual void abort() + { + setMigrationState(Aborted); + } + + virtual void complete() + { + setMigrationState(Complete); + } + + virtual bool shouldAutostart() const + { + return mAutostart; + } + + virtual void pause() + { + setMigrationState(Paused); + } + + virtual void resume() + { + setMigrationState(InProgress); + } + + bool mAutostart; +}; + +class TestJobTracker : public KJobTrackerInterface +{ +public: + TestJobTracker() : mPercent(0) + {} + + virtual void registerJob(KJob* job) + { + KJobTrackerInterface::registerJob(job); + mJobs << job; + } + + virtual void unregisterJob(KJob* job) + { + mJobs.removeAll(job); + } + + virtual void finished(KJob* job) + { + mJobs.removeAll(job); + } + + virtual void percent(KJob* job, long unsigned int percent) + { + Q_UNUSED(job); + mPercent = percent; + } + + QList mJobs; + int mPercent; +}; + +class SchedulerTest: public QObject +{ + Q_OBJECT +private slots: + + void initTestcase() + { + qRegisterMetaType(); + } + + void testInsertRow() + { + MigrationScheduler scheduler; + QAbstractItemModel &model(scheduler.model()); + + QCOMPARE(model.rowCount(), 0); + + QSignalSpy rowsInsertedSpy(&model, SIGNAL(rowsInserted(QModelIndex,int,int))); + QVERIFY(rowsInsertedSpy.isValid()); + + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id")))); + QCOMPARE(model.rowCount(), 1); + QCOMPARE(rowsInsertedSpy.count(), 1); + + QVERIFY(model.index(0, 0).isValid()); + QVERIFY(!model.index(1, 0).isValid()); + + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id2")))); + QCOMPARE(model.rowCount(), 2); + QCOMPARE(rowsInsertedSpy.count(), 2); + } + + void testDisplayName() + { + MigrationScheduler scheduler; + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id")))); + QAbstractItemModel &model(scheduler.model()); + QCOMPARE(model.data(model.index(0, 0)).toString(), QLatin1String("name")); + } + + void testStartStop() + { + MigrationScheduler scheduler; + QSharedPointer migrator(new Testmigrator(QLatin1String("id"))); + scheduler.addMigrator(migrator); + + scheduler.start(migrator->identifier()); + QCOMPARE(migrator->migrationState(), MigratorBase::InProgress); + + scheduler.abort(migrator->identifier()); + QCOMPARE(migrator->migrationState(), MigratorBase::Aborted); + } + + void testNoDuplicates() + { + MigrationScheduler scheduler; + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id")))); + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id")))); + QAbstractItemModel &model(scheduler.model()); + QCOMPARE(model.rowCount(), 1); + } + + void testMigrationStateChanged() + { + MigrationScheduler scheduler; + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id1")))); + QSharedPointer migrator(new Testmigrator(QLatin1String("id2"))); + scheduler.addMigrator(migrator); + scheduler.addMigrator(QSharedPointer(new Testmigrator(QLatin1String("id3")))); + QAbstractItemModel &model(scheduler.model()); + + QSignalSpy spy(&model, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + QVERIFY(spy.isValid()); + migrator->start(); + + QCOMPARE(spy.count(), 1); + const QVariantList args = spy.takeFirst(); + QCOMPARE(args.at(0).value().row(), 1); + QCOMPARE(args.at(1).value().row(), 1); + } + + void testRunMultiple() + { + MigrationScheduler scheduler; + + QSharedPointer m1(new Testmigrator(QLatin1String("id1"))); + scheduler.addMigrator(m1); + + QSharedPointer m2(new Testmigrator(QLatin1String("id2"))); + scheduler.addMigrator(m2); + + scheduler.start(m1->identifier()); + scheduler.start(m2->identifier()); + + QCOMPARE(m1->migrationState(), MigratorBase::InProgress); + QCOMPARE(m2->migrationState(), MigratorBase::InProgress); + } + + void testRunAutostart() + { + MigrationScheduler scheduler; + + QSharedPointer m1(new Testmigrator(QLatin1String("id1"))); + m1->mAutostart = true; + scheduler.addMigrator(m1); + + QSharedPointer m2(new Testmigrator(QLatin1String("id2"))); + m2->mAutostart = true; + scheduler.addMigrator(m2); + + QCOMPARE(m1->migrationState(), MigratorBase::InProgress); + kDebug() << m2->migrationState(); + QCOMPARE(m2->migrationState(), MigratorBase::None); + m1->complete(); + QCOMPARE(m2->migrationState(), MigratorBase::InProgress); + + } + + void testJobTracker() + { + TestJobTracker jobTracker; + MigrationScheduler scheduler(&jobTracker); + QSharedPointer m1(new Testmigrator(QLatin1String("id1"))); + m1->mAutostart = true; + scheduler.addMigrator(m1); + + QCOMPARE(jobTracker.mJobs.size(), 1); + + m1->complete(); + + //Give the job some time to delete itself + QTest::qWait(500); + + QCOMPARE(jobTracker.mJobs.size(), 0); + } + + void testSuspend() + { + TestJobTracker jobTracker; + MigrationScheduler scheduler(&jobTracker); + QSharedPointer m1(new Testmigrator(QLatin1String("id1"))); + m1->mAutostart = true; + scheduler.addMigrator(m1); + jobTracker.mJobs.first()->suspend(); + QCOMPARE(m1->migrationState(), MigratorBase::Paused); + jobTracker.mJobs.first()->resume(); + QCOMPARE(m1->migrationState(), MigratorBase::InProgress); + } + + /* + * Even if the migrator doesn't implement suspend, the executor suspends after completing the current job and waits with starting the second job. + */ + void testJobFinishesDuringSuspend() + { + TestJobTracker jobTracker; + MigrationScheduler scheduler(&jobTracker); + QSharedPointer m1(new Testmigrator(QLatin1String("id1"))); + m1->mAutostart = true; + scheduler.addMigrator(m1); + QSharedPointer m2(new Testmigrator(QLatin1String("id2"))); + m2->mAutostart = true; + scheduler.addMigrator(m2); + jobTracker.mJobs.first()->suspend(); + m1->complete(); + QCOMPARE(m2->migrationState(), MigratorBase::None); + jobTracker.mJobs.first()->resume(); + QCOMPARE(m2->migrationState(), MigratorBase::InProgress); + } + + void testProgressReporting() + { + TestJobTracker jobTracker; + MigrationScheduler scheduler(&jobTracker); + QSharedPointer m1(new Testmigrator(QLatin1String("id1"))); + m1->mAutostart = true; + scheduler.addMigrator(m1); + QCOMPARE(jobTracker.mPercent, 0); + m1->complete(); + QCOMPARE(jobTracker.mPercent, 100); + } + +}; + +QTEST_MAIN(SchedulerTest) + +#include "schedulertest.moc" diff --git a/kdepim-runtime/agents/newmailnotifier/CMakeLists.txt b/kdepim-runtime/agents/newmailnotifier/CMakeLists.txt new file mode 100644 index 00000000..27e72eba --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/CMakeLists.txt @@ -0,0 +1,59 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + + + +set(newmailnotifieragent_SRCS + newmailnotifiersettingsdialog.cpp + newmailnotifieragent.cpp + newmailnotifierattribute.cpp + specialnotifierjob.cpp + newmailnotifierselectcollectionwidget.cpp + newmailnotifiershowmessagejob.cpp + util.cpp +) + +kde4_add_kcfg_files(newmailnotifieragent_SRCS + newmailnotifieragentsettings.kcfgc + ) + + +qt4_add_dbus_adaptor(newmailnotifieragent_SRCS org.freedesktop.Akonadi.NewMailNotifier.xml newmailnotifieragent.h NewMailNotifierAgent) + + +kde4_add_executable( akonadi_newmailnotifier_agent ${newmailnotifieragent_SRCS}) + + +target_link_libraries( akonadi_newmailnotifier_agent + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${KDE4_KNOTIFYCONFIG_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_KPIMUTILS_LIBS} + ${KDEPIMLIBS_AKONADI_CONTACT_LIBS} + ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} +) + + +if (Q_WS_MAC) + set_target_properties( akonadi_newmailnotifier_agent PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist.template) + set_target_properties( akonadi_newmailnotifier_agent PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.newmailnotifier") + set_target_properties( akonadi_newmailnotifier_agent PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE New Mail Notifier") +endif () + +install(TARGETS akonadi_newmailnotifier_agent ${INSTALL_TARGETS_DEFAULT_ARGS} ) + + +install(FILES newmailnotifieragent.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") +install(FILES akonadi_newmailnotifier_agent.notifyrc DESTINATION "${DATA_INSTALL_DIR}/akonadi_newmailnotifier_agent" ) + +add_subdirectory(kconf_update) diff --git a/kdepim-runtime/agents/newmailnotifier/Messages.sh b/kdepim-runtime/agents/newmailnotifier/Messages.sh new file mode 100755 index 00000000..9d815ddb --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/Messages.sh @@ -0,0 +1,4 @@ +#! /bin/sh +$EXTRACTRC `find . -name '*.kcfg'` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_newmailnotifier_agent.pot +rm -f rc.cpp diff --git a/kdepim-runtime/agents/newmailnotifier/NEWS b/kdepim-runtime/agents/newmailnotifier/NEWS new file mode 100644 index 00000000..4d8cc3af --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/NEWS @@ -0,0 +1,3 @@ +4.14: +----- +Add button to show email diff --git a/kdepim-runtime/agents/newmailnotifier/TODO b/kdepim-runtime/agents/newmailnotifier/TODO new file mode 100644 index 00000000..9f04060b --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/TODO @@ -0,0 +1,4 @@ +for 4.12: +--------- +- Look at https://github.com/shellscape/Gmail-Notifier-Plus/tree/master/Promotional for idea +- Show if we can display full collection path (for the moment we can't) diff --git a/kdepim-runtime/agents/newmailnotifier/akonadi_newmailnotifier_agent.notifyrc b/kdepim-runtime/agents/newmailnotifier/akonadi_newmailnotifier_agent.notifyrc new file mode 100644 index 00000000..a1942cdb --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/akonadi_newmailnotifier_agent.notifyrc @@ -0,0 +1,174 @@ +[Global] +IconName=kmail +Comment=New email notify +Comment[bs]=Obavijest o novoj poÅ¡ti +Comment[ca]=Notificador de correu electrònic nou +Comment[ca@valencia]=Notificador de correu electrònic nou +Comment[cs]=UpozornÄ›ní na nový e-mail +Comment[da]=Bekendtgørelse af nye e-mails +Comment[de]=E-Mail-Benachrichtigung +Comment[el]=Ειδοποίηση νέας αλληλογÏαφίας +Comment[en_GB]=New email notify +Comment[es]=Nueva notificación de correo +Comment[et]=Uue kirja teavitaja +Comment[fi]=Ilmoitus uudesta postista +Comment[fr]=Notification de nouveaux courriers électroniques +Comment[ga]=Fógairt ríomhphoist nua +Comment[gl]=Notificación de nova mensaxe. +Comment[hu]=Új e-mail értesítés +Comment[ia]=Notifica de nove messages de e-posta +Comment[it]=Notifica dei nuovi messaggi di posta +Comment[kk]=Жаңа Ñл.пошта туралы хабарлау +Comment[km]=ជូនដំណឹង​អ៊ីមែល​ážáŸ’មី +Comment[ko]=새 ë©”ì¼ ì•Œë¦¼ +Comment[lt]=Naujo paÅ¡to praneÅ¡imas +Comment[lv]=Jauna pasta paziņojums +Comment[nb]=Varsling om ny e-post +Comment[nds]=Bescheed över nieg Nettpost +Comment[nl]=Melding van nieuwe e-mail +Comment[pl]=Powiadomienie o nowej poczcie +Comment[pt]=Notificação de correio novo +Comment[pt_BR]=Notificação de novo e-mail +Comment[ru]=Уведомление о новой почте +Comment[sk]=Oznámenie novej poÅ¡ty +Comment[sl]=Obvestilo o novi e-poÅ¡ti +Comment[sr]=Обавештење о приÑтиглој е‑пошти +Comment[sr@ijekavian]=Обавештење о приÑтиглој е‑пошти +Comment[sr@ijekavianlatin]=ObaveÅ¡tenje o pristigloj e‑poÅ¡ti +Comment[sr@latin]=ObaveÅ¡tenje o pristigloj e‑poÅ¡ti +Comment[sv]=Ny brevunderrättelse +Comment[tr]=Yeni e-posta bildirimi +Comment[uk]=Сповіщувач про нові Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ +Comment[x-test]=xxNew email notifyxx +Comment[zh_CN]=新邮件æ醒 +Comment[zh_TW]=新郵件通知 +Name=New email notify +Name[bs]=Obavijest o novoj poÅ¡ti +Name[ca]=Notificador de correu electrònic nou +Name[ca@valencia]=Notificador de correu electrònic nou +Name[cs]=UpozornÄ›ní na nový e-mail +Name[da]=Bekendtgørelse af nye e-mails +Name[de]=E-Mail-Benachrichtigung +Name[el]=Ειδοποίηση νέας αλληλογÏαφίας +Name[en_GB]=New email notify +Name[es]=Nueva notificación de correo +Name[et]=Uue kirja teavitaja +Name[fi]=Ilmoitus uudesta postista +Name[fr]=Notification de nouveaux courriers électroniques +Name[ga]=Fógairt ríomhphoist nua +Name[gl]=Notificación de nova mensaxe +Name[hu]=Új e-mail értesítés +Name[ia]=Notifica de nove messages de e-posta +Name[it]=Notifica dei nuovi messaggi di posta +Name[kk]=Жаңа Ñл.пошта туралы хабарлау +Name[km]=ជូនដំណឹង​អ៊ីមែល​ážáŸ’មី +Name[ko]=새 ë©”ì¼ ì•Œë¦¼ +Name[lt]=Naujo paÅ¡to praneÅ¡imas +Name[lv]=Jauna pasta paziņojums +Name[nb]=Varsling om ny e-post +Name[nds]=Nieg Nettpost +Name[nl]=Melding van nieuwe e-mail +Name[pl]=Powiadomienie o nowej poczcie +Name[pt]=Notificação de correio novo +Name[pt_BR]=Notificação de novo e-mail +Name[ro]=Notificare mesaje noi +Name[ru]=Уведомление о новой почте +Name[sk]=Oznámenie novej poÅ¡ty +Name[sl]=Obvestilo o novi e-poÅ¡ti +Name[sr]=Обавештење о приÑтиглој е‑пошти +Name[sr@ijekavian]=Обавештење о приÑтиглој е‑пошти +Name[sr@ijekavianlatin]=ObaveÅ¡tenje o pristigloj e‑poÅ¡ti +Name[sr@latin]=ObaveÅ¡tenje o pristigloj e‑poÅ¡ti +Name[sv]=Ny brevunderrättelse +Name[tr]=Yeni e-posta bildirimi +Name[uk]=Сповіщувач про нові Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ +Name[x-test]=xxNew email notifyxx +Name[zh_CN]=新邮件æ醒 +Name[zh_TW]=新郵件通知 + +[Event/new-email] +Name=New email arrived +Name[bs]=Nova poÅ¡ta stigla +Name[ca]=Ha arribat correu electrònic nou +Name[ca@valencia]=Ha arribat correu electrònic nou +Name[cs]=PÅ™iÅ¡el nový e-mail +Name[da]=Ny e-mail ankommet +Name[de]=Neue Nachrichten sind eingetroffen +Name[el]=Ελήφθη νέα αλληλογÏαφία +Name[en_GB]=New email arrived +Name[es]=Llegó correo nuevo +Name[et]=Saabus uus kiri +Name[fi]=Uutta postia saapunut +Name[fr]=Un nouveau courrier électronique est arrivé +Name[ga]=Tháinig ríomhphost nua +Name[gl]=Chegou unha mensaxe nova +Name[hu]=Új e-mail érkezett +Name[ia]=Nove message de e-posta arrivava +Name[it]=Nuova posta ricevuta +Name[kk]=Жаңа пошта келді +Name[km]=មាន​អ៊ីមែល​ážáŸ’មី​មក​ដល់ +Name[ko]=새 ë©”ì¼ì´ ë„착했습니다 +Name[lt]=Naujas laiÅ¡kas gautas +Name[lv]=Saņemts jauns e-pasts +Name[nb]=Ny e-post ankommet +Name[nds]=Niege Nettpost ankamen +Name[nl]=Nieuwe e-mail aangekomen +Name[pl]=NadeszÅ‚a nowa poczta +Name[pt]=Notificação de correio novo +Name[pt_BR]=Notificação de novo e-mail +Name[ro]=Mesaje noi sosite +Name[ru]=Получена Ð½Ð¾Ð²Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° +Name[sk]=PriÅ¡la nová poÅ¡ta +Name[sl]=Prispela je nova e-poÅ¡ta +Name[sr]=Стигла је нова е‑пошта +Name[sr@ijekavian]=Стигла је нова е‑пошта +Name[sr@ijekavianlatin]=Stigla je nova e‑poÅ¡ta +Name[sr@latin]=Stigla je nova e‑poÅ¡ta +Name[sv]=Ny post har anlänt +Name[tr]=Yeni e-posta alındı +Name[uk]=Ðадійшла нова пошта +Name[x-test]=xxNew email arrivedxx +Name[zh_CN]=新邮件到达 +Name[zh_TW]=æ–°éƒµä»¶å·²æŠµé” +Action=Popup + +[Event/text-to-speak-not-found] +Name=Jovie Text-to-Speech Service not found +Name[bs]=Jovie Tservis za prebacivanje teksta u govor nije naÄ‘en +Name[ca]=No s'ha trobat el servei Jovie de text a veu +Name[ca@valencia]=No s'ha trobat el servei Jovie de text a veu +Name[da]=Jovie tekst-til-tale-tjeneste ikke fundet +Name[de]=Der Sprachausgabedienstes Jovie wurde nicht gefunden +Name[el]=Η υπηÏεσία Jovie Text-to-Speech δε βÏέθηκε +Name[en_GB]=Jovie Text-to-Speech Service not found +Name[es]=Servicio de texto a voz Jovie no encontrado +Name[et]=Jovie teksti ettelugemise teenust ei leitud +Name[fi]=Jovie-puhesyntetisaattoria ei löytynyt +Name[fr]=Impossible de trouver le service de synthèse vocale Jovie +Name[gl]=Non foi posíbel atopar o servizo de texto lido Jovie +Name[hu]=A Jovie szövegfelolvasó szolgáltatás nem található +Name[ia]=Il non trovava le servicio de texto-a-Voce Jovie +Name[it]=Servizio di sintesi vocale Jovie non trovato +Name[kk]=Jovie мәтінді дауыÑтап оқу қызметі табылған жоқ +Name[ko]=Jovie í…스트 ìŒì„± 합성 서비스를 ì°¾ì„ ìˆ˜ ì—†ìŒ +Name[lt]=Jovie Tekstas į kalbÄ… tarnyba nerasta +Name[nb]=Fant ikke Jovie tekst-til-tale-tjeneste +Name[nds]=Vörleesdeenst „Jovie“ lett sik nich finnen +Name[nl]=Tekst-naar-spraak-service van Jovie niet gevonden +Name[pl]=NIe znaleziono usÅ‚ugi tekst-do-mowy Jovie +Name[pt]=Serviço de Texto-para-Fala Jovie Não Encontrado +Name[pt_BR]=Não foi encontrado o serviço de Texto-para-Fala do Jovie +Name[ru]=Ðе удалоÑÑŒ найти Ñлужбу Ñинтеза речи Jovie +Name[sk]=Služba textu na reÄ Jovie sa nenaÅ¡la +Name[sr]=Ð¡ÐµÑ€Ð²Ð¸Ñ Ð·Ð° текÑт‑у‑говор Ðови није нађен +Name[sr@ijekavian]=Ð¡ÐµÑ€Ð²Ð¸Ñ Ð·Ð° текÑт‑у‑говор Ðови није нађен +Name[sr@ijekavianlatin]=Servis za tekst‑u‑govor Džovi nije naÄ‘en +Name[sr@latin]=Servis za tekst‑u‑govor Džovi nije naÄ‘en +Name[sv]=Jovie text-till-taltjänst hittades inte +Name[tr]=Jovie Metin Okuma Hizmeti bulunamadı +Name[uk]=Ðе знайдено Ñлужби Ñинтезу Ð¼Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Jovie +Name[x-test]=xxJovie Text-to-Speech Service not foundxx +Name[zh_CN]=未找到 Jovie 语音åˆæˆæœåŠ¡ +Name[zh_TW]=找ä¸åˆ° Jovie 文字轉語音æœå‹™ +Action=Popup + diff --git a/kdepim-runtime/agents/newmailnotifier/kconf_update/CMakeLists.txt b/kdepim-runtime/agents/newmailnotifier/kconf_update/CMakeLists.txt new file mode 100644 index 00000000..66f1288e --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/kconf_update/CMakeLists.txt @@ -0,0 +1 @@ +install( FILES newmailnotifier.upd DESTINATION ${KCONF_UPDATE_INSTALL_DIR}) diff --git a/kdepim-runtime/agents/newmailnotifier/kconf_update/newmailnotifier.upd b/kdepim-runtime/agents/newmailnotifier/kconf_update/newmailnotifier.upd new file mode 100644 index 00000000..c34c9bbc --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/kconf_update/newmailnotifier.upd @@ -0,0 +1,6 @@ +# Migrate kmail's notifier setting to Akonadi new mail notifier +Id=initial-newmailnotifier-migration +File=kmail2rc,akonadi_newmailnotifier_agentrc +Group=General,General +Key=beep-on-mail,beepOnNewMails +Key=VerboseNewMailNotification,verboseNotification diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifieragent.cpp b/kdepim-runtime/agents/newmailnotifier/newmailnotifieragent.cpp new file mode 100644 index 00000000..d381b7b6 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifieragent.cpp @@ -0,0 +1,563 @@ +/* + Copyright (c) 2013 Laurent Montel + + Copyright (c) 2010 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 "newmailnotifieragent.h" + +#include "util.h" + +#include "newmailnotifierattribute.h" +#include "specialnotifierjob.h" +#include "newmailnotifieradaptor.h" +#include "newmailnotifieragentsettings.h" +#include "newmailnotifiersettingsdialog.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; + +NewMailNotifierAgent::NewMailNotifierAgent( const QString &id ) + : AgentBase( id ) +{ + Akonadi::AttributeFactory::registerAttribute(); + new NewMailNotifierAdaptor( this ); + + mIdentityManager = new KPIMIdentities::IdentityManager( false, this ); + connect(mIdentityManager, SIGNAL(changed()), SLOT(slotIdentitiesChanged())); + slotIdentitiesChanged(); + + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/NewMailNotifierAgent" ), + this, QDBusConnection::ExportAdaptors ); + DBusConnectionPool::threadConnection().registerService( QLatin1String( "org.freedesktop.Akonadi.NewMailNotifierAgent" ) ); + + connect( Akonadi::AgentManager::self(), SIGNAL(instanceStatusChanged(Akonadi::AgentInstance)), + this, SLOT(slotInstanceStatusChanged(Akonadi::AgentInstance)) ); + connect( Akonadi::AgentManager::self(), SIGNAL(instanceRemoved(Akonadi::AgentInstance)), + this, SLOT(slotInstanceRemoved(Akonadi::AgentInstance)) ); + connect( Akonadi::AgentManager::self(), SIGNAL(instanceAdded(Akonadi::AgentInstance)), + this, SLOT(slotInstanceAdded(Akonadi::AgentInstance)) ); + connect( Akonadi::AgentManager::self(), SIGNAL(instanceNameChanged(Akonadi::AgentInstance)), + this, SLOT(slotInstanceNameChanged(Akonadi::AgentInstance)) ); + + + changeRecorder()->setMimeTypeMonitored( KMime::Message::mimeType() ); + changeRecorder()->itemFetchScope().setCacheOnly( true ); + changeRecorder()->itemFetchScope().setFetchModificationTime( false ); + changeRecorder()->fetchCollection( true ); + changeRecorder()->setChangeRecordingEnabled( false ); + changeRecorder()->ignoreSession( Akonadi::Session::defaultSession() ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::All ); + changeRecorder()->setCollectionMonitored(Collection::root(), true); + mTimer.setInterval( 5 * 1000 ); + connect( &mTimer, SIGNAL(timeout()), SLOT(slotShowNotifications()) ); + + if (NewMailNotifierAgentSettings::textToSpeakEnabled()) + Util::testJovieService(); + + if (isActive()) { + mTimer.setSingleShot( true ); + } +} + +void NewMailNotifierAgent::slotIdentitiesChanged() +{ + mListEmails = mIdentityManager->allEmails(); +} + +void NewMailNotifierAgent::doSetOnline(bool online) +{ + if (!online) { + clearAll(); + } +} + +void NewMailNotifierAgent::setExcludeMyselfFromNotification(bool b) +{ + NewMailNotifierAgentSettings::setExcludeEmailsFromMe(b); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::excludeMyselfFromNotification() const +{ + return NewMailNotifierAgentSettings::excludeEmailsFromMe(); +} + +void NewMailNotifierAgent::setShowPhoto(bool show) +{ + NewMailNotifierAgentSettings::setShowPhoto(show); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::showPhoto() const +{ + return NewMailNotifierAgentSettings::showPhoto(); +} + +void NewMailNotifierAgent::setShowFrom(bool show) +{ + NewMailNotifierAgentSettings::setShowFrom(show); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::showFrom() const +{ + return NewMailNotifierAgentSettings::showFrom(); +} + +void NewMailNotifierAgent::setShowSubject(bool show) +{ + NewMailNotifierAgentSettings::setShowSubject(show); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::showSubject() const +{ + return NewMailNotifierAgentSettings::showSubject(); +} + +void NewMailNotifierAgent::setShowFolderName(bool show) +{ + NewMailNotifierAgentSettings::setShowFolder(show); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::showFolderName() const +{ + return NewMailNotifierAgentSettings::showFolder(); +} + +void NewMailNotifierAgent::setEnableAgent(bool enabled) +{ + NewMailNotifierAgentSettings::setEnabled(enabled); + NewMailNotifierAgentSettings::self()->writeConfig(); + if (!enabled) { + clearAll(); + } +} + +void NewMailNotifierAgent::setVerboseMailNotification(bool verbose) +{ + NewMailNotifierAgentSettings::setVerboseNotification(verbose); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::verboseMailNotification() const +{ + return NewMailNotifierAgentSettings::verboseNotification(); +} + +void NewMailNotifierAgent::setBeepOnNewMails(bool beep) +{ + NewMailNotifierAgentSettings::setBeepOnNewMails(beep); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::beepOnNewMails() const +{ + return NewMailNotifierAgentSettings::beepOnNewMails(); +} + +void NewMailNotifierAgent::setTextToSpeakEnabled(bool enabled) +{ + NewMailNotifierAgentSettings::setTextToSpeakEnabled(enabled); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +bool NewMailNotifierAgent::textToSpeakEnabled() const +{ + return NewMailNotifierAgentSettings::textToSpeakEnabled(); +} + +void NewMailNotifierAgent::setTextToSpeak(const QString &msg) +{ + NewMailNotifierAgentSettings::setTextToSpeak(msg); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + +QString NewMailNotifierAgent::textToSpeak() const +{ + return NewMailNotifierAgentSettings::textToSpeak(); +} + +void NewMailNotifierAgent::clearAll() +{ + mNewMails.clear(); + mInstanceNameInProgress.clear(); +} + +bool NewMailNotifierAgent::enabledAgent() const +{ + return NewMailNotifierAgentSettings::enabled(); +} + +bool NewMailNotifierAgent::showButtonToDisplayMail() const +{ + return NewMailNotifierAgentSettings::showButtonToDisplayMail(); +} + +void NewMailNotifierAgent::setShowButtonToDisplayMail(bool b) +{ + NewMailNotifierAgentSettings::setShowButtonToDisplayMail(b); + NewMailNotifierAgentSettings::self()->writeConfig(); +} + + +void NewMailNotifierAgent::showConfigureDialog(qlonglong windowId) +{ + configure( windowId ); +} + +void NewMailNotifierAgent::configure( WId windowId ) +{ + QPointer dialog = new NewMailNotifierSettingsDialog; + if (windowId) { +#ifndef Q_WS_WIN + KWindowSystem::setMainWindow( dialog, windowId ); +#else + KWindowSystem::setMainWindow( dialog, (HWND)windowId ); +#endif + } + dialog->exec(); + delete dialog; +} + +bool NewMailNotifierAgent::excludeSpecialCollection(const Akonadi::Collection &collection) const +{ + if ( collection.hasAttribute() ) + return true; + + if ( collection.hasAttribute() ) { + if (collection.attribute()->ignoreNewMail()) { + return true; + } + } + + if (!collection.contentMimeTypes().contains( KMime::Message::mimeType()) ) { + return true; + } + + SpecialMailCollections::Type type = SpecialMailCollections::self()->specialCollectionType(collection); + switch(type) { + case SpecialMailCollections::Invalid: //Not a special collection + case SpecialMailCollections::Inbox: + return false; + default: + return true; + } + +} + +void NewMailNotifierAgent::itemsRemoved(const Item::List &items ) +{ + if (!isActive()) + return; + + QHash< Akonadi::Collection, QList >::iterator end(mNewMails.end()); + for ( QHash< Akonadi::Collection, QList >::iterator it = mNewMails.begin(); it != end; ++it ) { + QList idList = it.value(); + bool itemFound = false; + Q_FOREACH( const Item &item, items ) { + if (idList.contains(item.id())) { + idList.removeAll( item.id() ); + itemFound = true; + } + } + if (itemFound) { + if (mNewMails[it.key()].isEmpty()) { + mNewMails.remove( it.key() ); + } else { + mNewMails[it.key()] = idList; + } + } + } +} + +void NewMailNotifierAgent::itemsFlagsChanged( const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags ) +{ + if (!isActive()) + return; + Q_FOREACH (const Akonadi::Item &item, items) { + QHash< Akonadi::Collection, QList >::iterator end(mNewMails.end()); + for ( QHash< Akonadi::Collection, QList >::iterator it = mNewMails.begin(); it != end; ++it ) { + QList idList= it.value(); + if (idList.contains(item.id()) && addedFlags.contains("\\SEEN")) { + idList.removeAll( item.id() ); + if ( idList.isEmpty() ) { + mNewMails.remove( it.key() ); + break; + } else { + (*it) = idList; + } + } + } + } +} + +void NewMailNotifierAgent::itemsMoved( const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination ) +{ + if (!isActive()) + return; + + Q_FOREACH (const Akonadi::Item &item, items) { + if (ignoreStatusMail(item)) { + continue; + } + + if ( excludeSpecialCollection(collectionSource) ) { + continue; // outbox, sent-mail, trash, drafts or templates. + } + + if ( mNewMails.contains( collectionSource ) ) { + QList idListFrom = mNewMails[ collectionSource ]; + if ( idListFrom.contains( item.id() ) ) { + idListFrom.removeAll( item.id() ); + + if ( idListFrom.isEmpty() ) { + mNewMails.remove( collectionSource ); + } else { + mNewMails[ collectionSource ] = idListFrom; + } + if ( !excludeSpecialCollection(collectionDestination) ) { + QList idListTo = mNewMails[ collectionDestination ]; + idListTo.append( item.id() ); + mNewMails[ collectionDestination ] = idListTo; + } + } + } + } +} + +bool NewMailNotifierAgent::ignoreStatusMail(const Akonadi::Item &item) +{ + Akonadi::MessageStatus status; + status.setStatusFromFlags( item.flags() ); + if ( status.isRead() || status.isSpam() || status.isIgnored() ) + return true; + return false; +} + +void NewMailNotifierAgent::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + if (!isActive()) + return; + + if ( excludeSpecialCollection(collection) ) { + return; // outbox, sent-mail, trash, drafts or templates. + } + + if (ignoreStatusMail(item)) { + return; + } + + if ( !mTimer.isActive() ) { + mTimer.start(); + } + mNewMails[ collection ].append( item.id() ); +} + +void NewMailNotifierAgent::slotShowNotifications() +{ + if (mNewMails.isEmpty()) + return; + + if (!isActive()) + return; + + if (!mInstanceNameInProgress.isEmpty()) { + //Restart timer until all is done. + mTimer.start(); + return; + } + + QString message; + if (NewMailNotifierAgentSettings::verboseNotification()) { + bool hasUniqMessage = true; + Akonadi::Item::Id item = -1; + QString currentPath; + QStringList texts; + QHash< Akonadi::Collection, QList >::const_iterator end(mNewMails.constEnd()); + const int numberOfCollection(mNewMails.count()); + if (numberOfCollection > 1) + hasUniqMessage = false; + + for ( QHash< Akonadi::Collection, QList >::const_iterator it = mNewMails.constBegin(); it != end; ++it ) { + Akonadi::EntityDisplayAttribute *attr = it.key().attribute(); + QString displayName; + if ( attr && !attr->displayName().isEmpty() ) + displayName = attr->displayName(); + else + displayName = it.key().name(); + + if (hasUniqMessage) { + if (it.value().count() == 0) { + //You can have an unique folder with 0 message + return; + } else if (it.value().count() == 1 ) { + item = it.value().first(); + currentPath = displayName; + break; + } else { + hasUniqMessage = false; + } + } + QString resourceName; + if (!mCacheResourceName.contains(it.key().resource())) { + Q_FOREACH ( const Akonadi::AgentInstance &instance, Akonadi::AgentManager::self()->instances() ) { + if (instance.identifier() == it.key().resource()) { + mCacheResourceName.insert(instance.identifier(), instance.name()); + resourceName = instance.name(); + break; + } + } + } else { + resourceName = mCacheResourceName.value(it.key().resource()); + } + const int numberOfEmails(it.value().count()); + if (numberOfEmails>0) { + texts.append( i18np( "One new email in %2 from \"%3\"", "%1 new emails in %2 from \"%3\"", numberOfEmails, displayName, + resourceName ) ); + } + } + if (hasUniqMessage) { + SpecialNotifierJob *job = new SpecialNotifierJob(mListEmails, currentPath, item, this); + connect(job, SIGNAL(displayNotification(QPixmap,QString)), SLOT(slotDisplayNotification(QPixmap,QString))); + mNewMails.clear(); + return; + } else { + message = texts.join( QLatin1String("
") ); + } + } else { + message = i18n( "New mail arrived" ); + } + + kDebug() << message; + + slotDisplayNotification(Util::defaultPixmap(), message); + + mNewMails.clear(); +} + + +void NewMailNotifierAgent::slotDisplayNotification(const QPixmap &pixmap, const QString &message) +{ + Util::showNotification(pixmap, message); + + if ( NewMailNotifierAgentSettings::beepOnNewMails() ) { + KNotification::beep(); + } +} + +void NewMailNotifierAgent::slotInstanceNameChanged(const Akonadi::AgentInstance &instance) +{ + if (!isActive()) + return; + + const QString identifier(instance.identifier()); + if (mCacheResourceName.contains(identifier)) { + mCacheResourceName.remove(identifier); + mCacheResourceName.insert(identifier, instance.name()); + } +} + +void NewMailNotifierAgent::slotInstanceStatusChanged(const Akonadi::AgentInstance &instance) +{ + if (!isActive()) + return; + + const QString identifier(instance.identifier()); + switch(instance.status()) { + case Akonadi::AgentInstance::Broken: + case Akonadi::AgentInstance::Idle: + { + if (mInstanceNameInProgress.contains(identifier)) { + mInstanceNameInProgress.removeAll(identifier); + } + break; + } + case Akonadi::AgentInstance::Running: + { + if (!Util::excludeAgentType(instance)) { + if (!mInstanceNameInProgress.contains(identifier)) { + mInstanceNameInProgress.append(identifier); + } + } + break; + } + case Akonadi::AgentInstance::NotConfigured: + //Nothing + break; + } +} + +void NewMailNotifierAgent::slotInstanceRemoved(const Akonadi::AgentInstance &instance) +{ + if (!isActive()) + return; + + const QString identifier(instance.identifier()); + if (mInstanceNameInProgress.contains(identifier)) { + mInstanceNameInProgress.removeAll(identifier); + } +} + +void NewMailNotifierAgent::slotInstanceAdded(const Akonadi::AgentInstance &instance) +{ + mCacheResourceName.insert(instance.identifier(), instance.name()); +} + +void NewMailNotifierAgent::printDebug() +{ + kDebug()<<"instance in progress: "< + + Copyright (c) 2010 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 NEWMAILNOTIFIERAGENT_H +#define NEWMAILNOTIFIERAGENT_H + +#include // make sure this is included before QHash, otherwise it wont find the correct qHash implementation for some reason +#include + +#include +#include + +namespace Akonadi { +class AgentInstance; +} + +namespace KPIMIdentities { +class IdentityManager; +} + +class NewMailNotifierAgent : public Akonadi::AgentBase, public Akonadi::AgentBase::ObserverV3 +{ + Q_OBJECT + +public: + explicit NewMailNotifierAgent( const QString &id ); + + void showConfigureDialog(qlonglong windowId = 0); + + void setEnableAgent(bool b); + bool enabledAgent() const; + + void setVerboseMailNotification(bool b); + bool verboseMailNotification() const; + + void setBeepOnNewMails(bool b); + bool beepOnNewMails() const; + + void setShowPhoto(bool b); + bool showPhoto() const; + + void setShowFrom(bool b); + bool showFrom() const; + + void setShowSubject(bool b); + bool showSubject() const; + + void setShowFolderName(bool b); + bool showFolderName() const; + + void setExcludeMyselfFromNotification(bool b); + bool excludeMyselfFromNotification() const; + + void setTextToSpeakEnabled(bool enabled); + bool textToSpeakEnabled() const; + + QString textToSpeak() const; + void setTextToSpeak(const QString &msg); + + void printDebug(); + + bool showButtonToDisplayMail() const; + void setShowButtonToDisplayMail(bool b); + +protected: + void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + void itemsMoved( const Akonadi::Item::List &items, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destinationCollection ); + void itemsRemoved( const Akonadi::Item::List &items ); + void itemsFlagsChanged( const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags ); + void doSetOnline(bool online); + +private slots: + void slotShowNotifications(); + void configure(WId windowId); + void slotInstanceStatusChanged(const Akonadi::AgentInstance &instance); + void slotInstanceRemoved(const Akonadi::AgentInstance &instance); + void slotInstanceAdded(const Akonadi::AgentInstance &instance); + void slotDisplayNotification(const QPixmap &pixmap, const QString &message); + void slotIdentitiesChanged(); + void slotInstanceNameChanged(const Akonadi::AgentInstance &instance); + +private: + bool ignoreStatusMail(const Akonadi::Item &item); + bool isActive() const; + void clearAll(); + bool excludeSpecialCollection(const Akonadi::Collection &collection) const; + QStringList mListEmails; + QHash > mNewMails; + QHash mCacheResourceName; + QTimer mTimer; + QStringList mInstanceNameInProgress; + KPIMIdentities::IdentityManager *mIdentityManager; +}; + +#endif diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfg b/kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfg new file mode 100644 index 00000000..b376e789 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfg @@ -0,0 +1,42 @@ + + + + + true + + + false + + + true + + + true + + + true + + + true + + + true + + + false + + + false + + + i18nc("%s is a variable for agent. Do not change it", "A message was received from %s") + + + + false + + + diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfgc b/kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfgc new file mode 100644 index 00000000..e7f02441 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifieragentsettings.kcfgc @@ -0,0 +1,6 @@ +# Code generation options for kconfig_compiler +File=newmailnotifieragentsettings.kcfg +ClassName=NewMailNotifierAgentSettings +Singleton=true +Mutators=true +SetUserTexts=true diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.cpp b/kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.cpp new file mode 100644 index 00000000..1e1aba51 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.cpp @@ -0,0 +1,80 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "newmailnotifierattribute.h" + +#include +#include +#include + +class NewMailNotifierAttributePrivate +{ +public: + NewMailNotifierAttributePrivate() + : ignoreNewMail(false) + { + } + bool ignoreNewMail; +}; + +NewMailNotifierAttribute::NewMailNotifierAttribute() + : d(new NewMailNotifierAttributePrivate) +{ +} + +NewMailNotifierAttribute::~NewMailNotifierAttribute() +{ + delete d; +} + +NewMailNotifierAttribute *NewMailNotifierAttribute::clone() const +{ + NewMailNotifierAttribute *attr = new NewMailNotifierAttribute(); + attr->setIgnoreNewMail(ignoreNewMail()); + return attr; +} + +QByteArray NewMailNotifierAttribute::type() const +{ + static const QByteArray sType( "newmailnotifierattribute" ); + return sType; +} + +QByteArray NewMailNotifierAttribute::serialized() const +{ + QByteArray result; + QDataStream s( &result, QIODevice::WriteOnly ); + s << ignoreNewMail(); + return result; +} + +void NewMailNotifierAttribute::deserialize( const QByteArray &data ) +{ + QDataStream s( data ); + s >> d->ignoreNewMail; +} + +bool NewMailNotifierAttribute::ignoreNewMail() const +{ + return d->ignoreNewMail; +} + +void NewMailNotifierAttribute::setIgnoreNewMail(bool b) +{ + d->ignoreNewMail = b; +} diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.h b/kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.h new file mode 100644 index 00000000..cdf4782b --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifierattribute.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef NEWMAILNOTIFIERATTRIBUTE_H +#define NEWMAILNOTIFIERATTRIBUTE_H + +#include + + +class NewMailNotifierAttributePrivate; +class NewMailNotifierAttribute : public Akonadi::Attribute +{ +public: + NewMailNotifierAttribute(); + ~NewMailNotifierAttribute(); + + /* reimpl */ + NewMailNotifierAttribute *clone() const; + QByteArray type() const; + QByteArray serialized() const; + void deserialize( const QByteArray &data ); + + bool ignoreNewMail() const; + void setIgnoreNewMail(bool b); + +private: + friend class NewMailNotifierAttributePrivate; + NewMailNotifierAttributePrivate * const d; +}; + +#endif // NEWMAILNOTIFIERATTRIBUTE_H diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.cpp b/kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.cpp new file mode 100644 index 00000000..0af02f4b --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.cpp @@ -0,0 +1,222 @@ +/* + Copyright (c) 2013 Laurent Montel + + 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 "newmailnotifierselectcollectionwidget.h" +#include "newmailnotifierattribute.h" + + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +NewMailNotifierSelectCollectionWidget::NewMailNotifierSelectCollectionWidget(QWidget *parent) + : QWidget(parent), + mNeedUpdate(false) +{ + QVBoxLayout *vbox = new QVBoxLayout; + + QLabel *label = new QLabel(i18n("Select which folders to monitor for new message notifications:")); + vbox->addWidget(label); + + // Create a new change recorder. + mChangeRecorder = new Akonadi::ChangeRecorder( this ); + mChangeRecorder->setMimeTypeMonitored( KMime::Message::mimeType() ); + mChangeRecorder->fetchCollection( true ); + mChangeRecorder->setAllMonitored( true ); + + mModel = new Akonadi::EntityTreeModel( mChangeRecorder, this ); + // Set the model to show only collections, not items. + mModel->setItemPopulationStrategy( Akonadi::EntityTreeModel::NoItemPopulation ); + connect(mModel, SIGNAL(collectionTreeFetched(Akonadi::Collection::List)), SLOT(slotCollectionTreeFetched())); + + Akonadi::CollectionFilterProxyModel *mimeTypeProxy = new Akonadi::CollectionFilterProxyModel( this ); + mimeTypeProxy->setExcludeVirtualCollections( true ); + mimeTypeProxy->addMimeTypeFilters( QStringList() << KMime::Message::mimeType() ); + mimeTypeProxy->setSourceModel( mModel ); + + // Create the Check proxy model. + mSelectionModel = new QItemSelectionModel( mimeTypeProxy ); + mCheckProxy = new KCheckableProxyModel( this ); + mCheckProxy->setSelectionModel( mSelectionModel ); + mCheckProxy->setSourceModel( mimeTypeProxy ); + + + mCollectionFilter = new KRecursiveFilterProxyModel(this); + mCollectionFilter->setSourceModel(mCheckProxy); + mCollectionFilter->setDynamicSortFilter(true); + mCollectionFilter->setFilterCaseSensitivity(Qt::CaseInsensitive); + + + KLineEdit *searchLine = new KLineEdit(this); + searchLine->setPlaceholderText(i18n("Search...")); + searchLine->setClearButtonShown(true); + connect(searchLine, SIGNAL(textChanged(QString)), + this, SLOT(slotSetCollectionFilter(QString))); + + vbox->addWidget(searchLine); + + mFolderView = new QTreeView; + mFolderView->setEditTriggers(QAbstractItemView::NoEditTriggers); + mFolderView->setAlternatingRowColors(true); + vbox->addWidget(mFolderView); + + mFolderView->setModel( mCollectionFilter ); + + QHBoxLayout *hbox = new QHBoxLayout; + vbox->addLayout(hbox); + + KPushButton *button = new KPushButton(i18n("&Select All"), this); + connect(button, SIGNAL(clicked(bool)), this, SLOT(slotSelectAllCollections())); + hbox->addWidget(button); + + button = new KPushButton(i18n("&Unselect All"), this); + connect(button, SIGNAL(clicked(bool)), this, SLOT(slotUnselectAllCollections())); + hbox->addWidget(button); + hbox->addStretch(1); + setLayout(vbox); +} + +NewMailNotifierSelectCollectionWidget::~NewMailNotifierSelectCollectionWidget() +{ + +} + +void NewMailNotifierSelectCollectionWidget::slotCollectionTreeFetched() +{ + if (!mNeedUpdate) { + mNeedUpdate = true; + QTimer::singleShot(1000, this, SLOT(slotUpdateCollectionStatus())); + } + mFolderView->expandAll(); +} + +void NewMailNotifierSelectCollectionWidget::slotSetCollectionFilter(const QString &filter) +{ + mCollectionFilter->setFilterWildcard(filter); + mFolderView->expandAll(); +} + +void NewMailNotifierSelectCollectionWidget::slotUpdateCollectionStatus() +{ + updateStatus(QModelIndex()); +} + +void NewMailNotifierSelectCollectionWidget::slotSelectAllCollections() +{ + forceStatus(QModelIndex(), true); +} + +void NewMailNotifierSelectCollectionWidget::slotUnselectAllCollections() +{ + forceStatus(QModelIndex(), false); +} + +void NewMailNotifierSelectCollectionWidget::updateStatus(const QModelIndex &parent) +{ + const int nbCol = mCheckProxy->rowCount( parent ); + for ( int i = 0; i < nbCol; ++i ) { + const QModelIndex child = mCheckProxy->index( i, 0, parent ); + + const Akonadi::Collection collection = + mCheckProxy->data( child, Akonadi::EntityTreeModel::CollectionRole ).value(); + + NewMailNotifierAttribute *attr = collection.attribute(); + if (!attr || !attr->ignoreNewMail()) { + mCheckProxy->setData( child, Qt::Checked, Qt::CheckStateRole ); + } + updateStatus( child ); + } + mNeedUpdate = false; +} + +void NewMailNotifierSelectCollectionWidget::forceStatus(const QModelIndex &parent, bool status) +{ + const int nbCol = mCheckProxy->rowCount( parent ); + for ( int i = 0; i < nbCol; ++i ) { + const QModelIndex child = mCheckProxy->index( i, 0, parent ); + mCheckProxy->setData( child, status ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole ); + forceStatus( child, status ); + } +} + +void NewMailNotifierSelectCollectionWidget::updateCollectionsRecursive(const QModelIndex &parent) +{ + const int nbCol = mCheckProxy->rowCount( parent ); + for ( int i = 0; i < nbCol; ++i ) { + const QModelIndex child = mCheckProxy->index( i, 0, parent ); + + Akonadi::Collection collection = + mCheckProxy->data( child, Akonadi::EntityTreeModel::CollectionRole ).value(); + + NewMailNotifierAttribute *attr = collection.attribute(); + Akonadi::CollectionModifyJob *modifyJob = 0; + const bool selected = (mCheckProxy->data( child, Qt::CheckStateRole ).value() != 0); + if (selected && attr && attr->ignoreNewMail()) { + collection.removeAttribute(); + modifyJob = new Akonadi::CollectionModifyJob(collection); + modifyJob->setProperty("AttributeAdded", true); + } else if (!selected && (!attr || !attr->ignoreNewMail())) { + attr = collection.attribute(Akonadi::Entity::AddIfMissing); + attr->setIgnoreNewMail(true); + modifyJob = new Akonadi::CollectionModifyJob(collection); + modifyJob->setProperty("AttributeAdded", false); + } + + if (modifyJob) { + connect(modifyJob, SIGNAL(finished(KJob*)), SLOT(slotModifyJobDone(KJob*))); + } + updateCollectionsRecursive(child); + } +} + +void NewMailNotifierSelectCollectionWidget::slotModifyJobDone(KJob* job) +{ + Akonadi::CollectionModifyJob *modifyJob = qobject_cast(job); + if (modifyJob && job->error()) { + if (job->property("AttributeAdded").toBool()) { + kWarning() << "Failed to append NewMailNotifierAttribute to collection" + << modifyJob->collection().id() << ":" + << job->errorString(); + } else { + kWarning() << "Failed to remove NewMailNotifierAttribute from collection" + << modifyJob->collection().id() << ":" + << job->errorString(); + } + } +} + + diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.h b/kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.h new file mode 100644 index 00000000..c24eb93a --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifierselectcollectionwidget.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2013 Laurent Montel + + 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 NEWMAILNOTIFIERSELECTCOLLECTIONWIDGET_H +#define NEWMAILNOTIFIERSELECTCOLLECTIONWIDGET_H + +#include +#include +#include + +class QItemSelectionModel; +class KRecursiveFilterProxyModel; +namespace Akonadi { +class EntityTreeModel; +class ChangeRecorder; +} +class QTreeView; +class KCheckableProxyModel; +class KJob; + +class NewMailNotifierSelectCollectionWidget : public QWidget +{ + Q_OBJECT +public: + explicit NewMailNotifierSelectCollectionWidget(QWidget *parent=0); + ~NewMailNotifierSelectCollectionWidget(); + + void updateCollectionsRecursive(const QModelIndex &parent); + +private Q_SLOTS: + void slotSelectAllCollections(); + void slotUnselectAllCollections(); + void slotModifyJobDone(KJob* job); + void slotUpdateCollectionStatus(); + void slotSetCollectionFilter(const QString&); + + void slotCollectionTreeFetched(); + +private: + void updateStatus(const QModelIndex &parent); + void forceStatus(const QModelIndex &parent, bool status); + QTreeView *mFolderView; + QItemSelectionModel *mSelectionModel; + Akonadi::EntityTreeModel *mModel; + Akonadi::ChangeRecorder *mChangeRecorder; + KCheckableProxyModel *mCheckProxy; + KRecursiveFilterProxyModel *mCollectionFilter; + bool mNeedUpdate; +}; + +#endif // NEWMAILNOTIFIERSELECTCOLLECTIONWIDGET_H diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.cpp b/kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.cpp new file mode 100644 index 00000000..a82734a8 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.cpp @@ -0,0 +1,225 @@ +/* + Copyright (c) 2013 Laurent Montel + + 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 "newmailnotifiersettingsdialog.h" +#include "newmailnotifierattribute.h" +#include "newmailnotifierselectcollectionwidget.h" +#include "newmailnotifieragentsettings.h" + +#include "kdepim-runtime-version.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static const char * textToSpeakMessage = + I18N_NOOP( "" + "

Here you can define message. " + "You can use:

" + "
    " + "
  • %s set subject
  • " + "
  • %f set from
  • " + "
" + "
" ); + +NewMailNotifierSettingsDialog::NewMailNotifierSettingsDialog(QWidget *parent) + : KDialog(parent) +{ + setCaption( i18n("New Mail Notifier settings") ); + setWindowIcon( KIcon( QLatin1String("kmail") ) ); + setButtons( Help | Ok|Cancel ); + connect(this, SIGNAL(okClicked()), this, SLOT(slotOkClicked())); + + QWidget *w = new QWidget; + QVBoxLayout *lay = new QVBoxLayout; + w->setLayout(lay); + QTabWidget *tab = new QTabWidget; + lay->addWidget(tab); + + QWidget *settings = new QWidget; + QVBoxLayout *vbox = new QVBoxLayout; + settings->setLayout(vbox); + + QGroupBox *grp = new QGroupBox(i18n("Choose which fields to show:")); + vbox->addWidget(grp); + QVBoxLayout *groupboxLayout = new QVBoxLayout; + grp->setLayout(groupboxLayout); + + mShowPhoto = new QCheckBox(i18n("Show Photo")); + mShowPhoto->setChecked(NewMailNotifierAgentSettings::showPhoto()); + groupboxLayout->addWidget(mShowPhoto); + + mShowFrom = new QCheckBox(i18n("Show From")); + mShowFrom->setChecked(NewMailNotifierAgentSettings::showFrom()); + groupboxLayout->addWidget(mShowFrom); + + mShowSubject = new QCheckBox(i18n("Show Subject")); + mShowSubject->setChecked(NewMailNotifierAgentSettings::showSubject()); + groupboxLayout->addWidget(mShowSubject); + + mShowFolders = new QCheckBox(i18n("Show Folders")); + mShowFolders->setChecked(NewMailNotifierAgentSettings::showFolder()); + groupboxLayout->addWidget(mShowFolders); + + mExcludeMySelf = new QCheckBox(i18n("Do not notify when email was sent by me")); + mExcludeMySelf->setChecked(NewMailNotifierAgentSettings::excludeEmailsFromMe()); + vbox->addWidget(mExcludeMySelf); + + mAllowToShowMail = new QCheckBox(i18n("Show button to display mail")); + mAllowToShowMail->setChecked(NewMailNotifierAgentSettings::showButtonToDisplayMail()); + vbox->addWidget(mAllowToShowMail); + + vbox->addStretch(); + tab->addTab(settings, i18n("Display")); + + + QWidget *textSpeakWidget = new QWidget; + vbox = new QVBoxLayout; + textSpeakWidget->setLayout(vbox); + mTextToSpeak = new QCheckBox(i18n("Enabled")); + mTextToSpeak->setChecked(NewMailNotifierAgentSettings::textToSpeakEnabled()); + vbox->addWidget(mTextToSpeak); + + QLabel *howIsItWork = new QLabel(i18n( "How does this work?" )); + howIsItWork->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + howIsItWork->setContextMenuPolicy(Qt::NoContextMenu); + vbox->addWidget(howIsItWork); + connect(howIsItWork, SIGNAL(linkActivated(QString)),SLOT(slotHelpLinkClicked(QString)) ); + + QHBoxLayout *textToSpeakLayout = new QHBoxLayout; + textToSpeakLayout->setMargin(0); + QLabel *lab = new QLabel(i18n("Message:")); + textToSpeakLayout->addWidget(lab); + mTextToSpeakSetting = new KLineEdit; + mTextToSpeakSetting->setClearButtonShown(true); + mTextToSpeakSetting->setText(NewMailNotifierAgentSettings::textToSpeak()); + mTextToSpeakSetting->setEnabled(mTextToSpeak->isChecked()); + mTextToSpeakSetting->setWhatsThis(i18n(textToSpeakMessage)); + textToSpeakLayout->addWidget(mTextToSpeakSetting); + vbox->addLayout(textToSpeakLayout); + vbox->addStretch(); + tab->addTab(textSpeakWidget, i18n("Text to Speak")); + connect(mTextToSpeak, SIGNAL(toggled(bool)), mTextToSpeakSetting, SLOT(setEnabled(bool))); + + mNotify = new KNotifyConfigWidget(this); + mNotify->setApplication(QLatin1String("akonadi_newmailnotifier_agent")); + tab->addTab(mNotify, i18n("Notify")); + + mSelectCollection = new NewMailNotifierSelectCollectionWidget; + tab->addTab(mSelectCollection, i18n("Folders")); + + setMainWidget(w); + + mAboutData = new KAboutData( + QByteArray( "newmailnotifieragent" ), + QByteArray(), + ki18n( "New Mail Notifier Agent" ), + QByteArray( KDEPIM_RUNTIME_VERSION ), + ki18n( "Notifies about new mail." ), + KAboutData::License_GPL_V2, + ki18n( "Copyright (C) 2013 Laurent Montel" ) ); + + mAboutData->addAuthor( ki18n( "Laurent Montel" ), + ki18n( "Maintainer" ), "montel@kde.org" ); + + mAboutData->setProgramIconName( QLatin1String("kmail") ); + mAboutData->setTranslator( ki18nc( "NAME OF TRANSLATORS", "Your names" ), + ki18nc( "EMAIL OF TRANSLATORS", "Your emails" ) ); + + + KHelpMenu *helpMenu = new KHelpMenu(this, mAboutData, true); + //Initialize menu + KMenu *menu = helpMenu->menu(); + helpMenu->action(KHelpMenu::menuAboutApp)->setIcon(KIcon(QLatin1String("kmail"))); + setButtonMenu( Help, menu ); + readConfig(); +} + +NewMailNotifierSettingsDialog::~NewMailNotifierSettingsDialog() +{ + writeConfig(); + delete mAboutData; +} + +static const char *myConfigGroupName = "NewMailNotifierDialog"; + +void NewMailNotifierSettingsDialog::readConfig() +{ + KConfigGroup group( KGlobal::config(), myConfigGroupName ); + + const QSize size = group.readEntry( "Size", QSize(500, 300) ); + if ( size.isValid() ) { + resize( size ); + } +} + +void NewMailNotifierSettingsDialog::writeConfig() +{ + KConfigGroup group( KGlobal::config(), myConfigGroupName ); + group.writeEntry( "Size", size() ); + group.sync(); +} + + +void NewMailNotifierSettingsDialog::slotHelpLinkClicked(const QString &) +{ + const QString help = + i18n( textToSpeakMessage); + + QWhatsThis::showText( QCursor::pos(), help ); +} + +void NewMailNotifierSettingsDialog::slotOkClicked() +{ + mSelectCollection->updateCollectionsRecursive(QModelIndex()); + + NewMailNotifierAgentSettings::setShowPhoto(mShowPhoto->isChecked()); + NewMailNotifierAgentSettings::setShowFrom(mShowFrom->isChecked()); + NewMailNotifierAgentSettings::setShowSubject(mShowSubject->isChecked()); + NewMailNotifierAgentSettings::setShowFolder(mShowFolders->isChecked()); + NewMailNotifierAgentSettings::setExcludeEmailsFromMe(mExcludeMySelf->isChecked()); + NewMailNotifierAgentSettings::setTextToSpeakEnabled(mTextToSpeak->isChecked()); + NewMailNotifierAgentSettings::setTextToSpeak(mTextToSpeakSetting->text()); + NewMailNotifierAgentSettings::setShowButtonToDisplayMail(mAllowToShowMail->isChecked()); + NewMailNotifierAgentSettings::self()->writeConfig(); + mNotify->save(); + accept(); +} + + + diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.h b/kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.h new file mode 100644 index 00000000..a4570f70 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifiersettingsdialog.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2013 Laurent Montel + + 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 NEWMAILNOTIFIERSETTINGSDIALOG_H +#define NEWMAILNOTIFIERSETTINGSDIALOG_H + +#include +#include + +class KNotifyConfigWidget; +class QCheckBox; +class KLineEdit; +class KAboutData; +class NewMailNotifierSelectCollectionWidget; +class NewMailNotifierSettingsDialog : public KDialog +{ + Q_OBJECT +public: + explicit NewMailNotifierSettingsDialog(QWidget *parent=0); + ~NewMailNotifierSettingsDialog(); + +private Q_SLOTS: + void slotOkClicked(); + void slotHelpLinkClicked(const QString &); + +private: + void writeConfig(); + void readConfig(); + QCheckBox *mShowPhoto; + QCheckBox *mShowFrom; + QCheckBox *mShowSubject; + QCheckBox *mShowFolders; + QCheckBox *mExcludeMySelf; + QCheckBox *mAllowToShowMail; + KNotifyConfigWidget *mNotify; + QCheckBox *mTextToSpeak; + KLineEdit *mTextToSpeakSetting; + NewMailNotifierSelectCollectionWidget *mSelectCollection; + KAboutData *mAboutData; +}; + +#endif // NEWMAILNOTIFIERSETTINGSDIALOG_H diff --git a/kdepim-runtime/agents/newmailnotifier/newmailnotifiershowmessagejob.cpp b/kdepim-runtime/agents/newmailnotifier/newmailnotifiershowmessagejob.cpp new file mode 100644 index 00000000..cbbcbeda --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/newmailnotifiershowmessagejob.cpp @@ -0,0 +1,57 @@ +/* + Copyright (c) 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "newmailnotifiershowmessagejob.h" + +#include +#include +#include +#include +#include + +NewMailNotifierShowMessageJob::NewMailNotifierShowMessageJob(Akonadi::Item::Id id, QObject *parent) + : KJob(parent), + mId(id) +{ +} + +NewMailNotifierShowMessageJob::~NewMailNotifierShowMessageJob() +{ +} + +void NewMailNotifierShowMessageJob::start() +{ + if (mId < 0) { + Q_EMIT emitResult(); + return; + } + const QString kmailInterface = QLatin1String("org.kde.kmail"); + QDBusReply reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(kmailInterface); + if (!reply.isValid() || !reply.value()) { + // Program is not already running, so start it + QString errmsg; + if (KToolInvocation::startServiceByDesktopName(QLatin1String("kmail2"), QString(), &errmsg)) { + qDebug()<<" Can not start kmail"< + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef NEWMAILNOTIFIERSHOWMESSAGEJOB_H +#define NEWMAILNOTIFIERSHOWMESSAGEJOB_H + +#include +#include + +class NewMailNotifierShowMessageJob : public KJob +{ + Q_OBJECT +public: + explicit NewMailNotifierShowMessageJob(Akonadi::Item::Id id, QObject *parent=0); + ~NewMailNotifierShowMessageJob(); + + void start(); + +private: + Akonadi::Item::Id mId; + +}; + + +#endif // NEWMAILNOTIFIERSHOWMESSAGEJOB_H diff --git a/kdepim-runtime/agents/newmailnotifier/org.freedesktop.Akonadi.NewMailNotifier.xml b/kdepim-runtime/agents/newmailnotifier/org.freedesktop.Akonadi.NewMailNotifier.xml new file mode 100644 index 00000000..b8f2c848 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/org.freedesktop.Akonadi.NewMailNotifier.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kdepim-runtime/agents/newmailnotifier/specialnotifierjob.cpp b/kdepim-runtime/agents/newmailnotifier/specialnotifierjob.cpp new file mode 100644 index 00000000..5107a8e3 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/specialnotifierjob.cpp @@ -0,0 +1,179 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "newmailnotifiershowmessagejob.h" +#include "specialnotifierjob.h" +#include "util.h" +#include "newmailnotifieragentsettings.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +SpecialNotifierJob::SpecialNotifierJob(const QStringList &listEmails, const QString &path, Akonadi::Item::Id id, QObject *parent) + : QObject(parent), + mListEmails(listEmails), + mPath(path), + mItemId(id) +{ + Akonadi::Item item(mItemId); + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( item, this ); + job->fetchScope().fetchPayloadPart( Akonadi::MessagePart::Envelope, true ); + + connect( job, SIGNAL(result(KJob*)), SLOT(slotItemFetchJobDone(KJob*)) ); +} + +SpecialNotifierJob::~SpecialNotifierJob() +{ + +} + +void SpecialNotifierJob::slotItemFetchJobDone(KJob *job) +{ + if ( job->error() ) { + kWarning() << job->errorString(); + deleteLater(); + return; + } + + const Akonadi::Item::List lst = qobject_cast( job )->items(); + if (lst.count() == 1) { + const Akonadi::Item item = lst.first(); + if ( !item.hasPayload() ) { + kDebug()<<" message has not payload."; + deleteLater(); + return; + } + + const KMime::Message::Ptr mb = item.payload(); + mFrom = mb->from()->asUnicodeString(); + mSubject = mb->subject()->asUnicodeString(); + if (NewMailNotifierAgentSettings::showPhoto()) { + Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob( this ); + job->setLimit( 1 ); + job->setQuery( Akonadi::ContactSearchJob::Email, KPIMUtils::firstEmailAddress(mFrom).toLower(), Akonadi::ContactSearchJob::ExactMatch ); + connect( job, SIGNAL(result(KJob*)), SLOT(slotSearchJobFinished(KJob*)) ); + } else { + emitNotification(Util::defaultPixmap()); + deleteLater(); + } + } else { + kWarning()<<" Found item different from 1: "<( job ); + if ( searchJob->error() ) { + kWarning() << "Unable to fetch contact:" << searchJob->errorText(); + emitNotification(Util::defaultPixmap()); + return; + } + if (!searchJob->contacts().isEmpty()) { + const KABC::Addressee addressee = searchJob->contacts().first(); + const KABC::Picture photo = addressee.photo(); + const QImage image = photo.data(); + if (image.isNull()) { + emitNotification(Util::defaultPixmap()); + } else { + emitNotification(QPixmap::fromImage(image)); + } + } else { + emitNotification(Util::defaultPixmap()); + } +} + +void SpecialNotifierJob::emitNotification(const QPixmap &pixmap) +{ + if (NewMailNotifierAgentSettings::excludeEmailsFromMe()) { + Q_FOREACH( const QString &email, mListEmails) { + if (mFrom.contains(email)) { + //Exclude this notification + deleteLater(); + return; + } + } + } + + QStringList result; + if (NewMailNotifierAgentSettings::showFrom()) { + result << i18n("From: %1", Qt::escape(mFrom)); + } + if (NewMailNotifierAgentSettings::showSubject()) { + QString subject(mSubject); + if (subject.length()> 80) { + subject.truncate(80); + subject += QLatin1String("..."); + } + result << i18n("Subject: %1", Qt::escape(subject)); + } + if (NewMailNotifierAgentSettings::showFolder()) { + result << i18n("In: %1", mPath); + } + + if (NewMailNotifierAgentSettings::textToSpeakEnabled()) { + if (!NewMailNotifierAgentSettings::textToSpeak().isEmpty()) { + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String("org.kde.kttsd"))) { + QDBusInterface ktts(QLatin1String("org.kde.kttsd"), QLatin1String("/KSpeech"), QLatin1String("org.kde.KSpeech")); + QString message = NewMailNotifierAgentSettings::textToSpeak(); + message.replace(QLatin1String("%s"), Qt::escape(mSubject)); + message.replace(QLatin1String("%f"), Qt::escape(mFrom)); + ktts.asyncCall(QLatin1String("say"), message, 0); + } + } + } + + if (NewMailNotifierAgentSettings::showButtonToDisplayMail()) { + KNotification *notification= new KNotification ( QLatin1String("new-email"), 0, KNotification::CloseOnTimeout); + notification->setText( result.join(QLatin1String("\n")) ); + notification->setPixmap( pixmap ); + notification->setActions( QStringList() << i18n( "Show mail..." ) ); + + connect(notification, SIGNAL(activated(uint)), this, SLOT(slotOpenMail()) ); + connect(notification, SIGNAL(closed()), this, SLOT(deleteLater())); + + notification->sendEvent(); + if ( NewMailNotifierAgentSettings::beepOnNewMails() ) { + KNotification::beep(); + } + } else { + emit displayNotification(pixmap, result.join(QLatin1String("\n"))); + deleteLater(); + } +} + +void SpecialNotifierJob::slotOpenMail() +{ + NewMailNotifierShowMessageJob *job = new NewMailNotifierShowMessageJob(mItemId); + job->start(); +} diff --git a/kdepim-runtime/agents/newmailnotifier/specialnotifierjob.h b/kdepim-runtime/agents/newmailnotifier/specialnotifierjob.h new file mode 100644 index 00000000..86cacae2 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/specialnotifierjob.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SPECIALNOTIFIERJOB_H +#define SPECIALNOTIFIERJOB_H + + +#include +#include +#include +class KJob; + +class SpecialNotifierJob : public QObject +{ + Q_OBJECT +public: + explicit SpecialNotifierJob(const QStringList &listEmails, const QString &path, Akonadi::Item::Id id, QObject *parent = 0); + ~SpecialNotifierJob(); + +Q_SIGNALS: + void displayNotification(const QPixmap &pixmap, const QString &message); + +private Q_SLOTS: + void slotSearchJobFinished( KJob *job ); + void slotItemFetchJobDone(KJob*); + void slotOpenMail(); +private: + void emitNotification(const QPixmap &pixmap); + QStringList mListEmails; + QString mSubject; + QString mFrom; + QString mPath; + Akonadi::Item::Id mItemId; +}; + +#endif // SPECIALNOTIFIERJOB_H diff --git a/kdepim-runtime/agents/newmailnotifier/util.cpp b/kdepim-runtime/agents/newmailnotifier/util.cpp new file mode 100644 index 00000000..fcfb5746 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/util.cpp @@ -0,0 +1,80 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "util.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + +void Util::testJovieService() +{ + if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String("org.kde.kttsd"))) { + QString error; + if (KToolInvocation::startServiceByDesktopName(QLatin1String("kttsd"), QStringList(), &error)) { + KNotification::event( QLatin1String("text-to-speak-not-found"), + i18n("Starting Jovie Text-to-Speech Service Failed %1", error), + Util::defaultPixmap(), + 0, + KNotification::CloseOnTimeout, + KGlobal::mainComponent()); + } + } +} + +void Util::showNotification(const QPixmap &pixmap, const QString &message) +{ + KNotification::event( QLatin1String("new-email"), + message, + pixmap, + 0, + KNotification::CloseOnTimeout, + KGlobal::mainComponent()); +} + +QPixmap Util::defaultPixmap() +{ + const QPixmap pixmap = KIcon( QLatin1String("kmail") ).pixmap( KIconLoader::SizeMedium, KIconLoader::SizeMedium ); + return pixmap; +} + +bool Util::excludeAgentType(const Akonadi::AgentInstance &instance) +{ + if ( instance.type().mimeTypes().contains( KMime::Message::mimeType() ) ) { + const QStringList capabilities( instance.type().capabilities() ); + if ( capabilities.contains( QLatin1String("Resource") ) && + !capabilities.contains( QLatin1String("Virtual") ) && + !capabilities.contains( QLatin1String("MailTransport") ) ) { + return false; + } else { + return true; + } + } + return true; +} diff --git a/kdepim-runtime/agents/newmailnotifier/util.h b/kdepim-runtime/agents/newmailnotifier/util.h new file mode 100644 index 00000000..19e165d7 --- /dev/null +++ b/kdepim-runtime/agents/newmailnotifier/util.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +namespace Akonadi { +class AgentInstance; +} + +namespace Util { +void showNotification(const QPixmap &pixmap, const QString &message); +QPixmap defaultPixmap(); +bool excludeAgentType(const Akonadi::AgentInstance &instance); +void testJovieService(); +} + +#endif // UTIL_H diff --git a/kdepim-runtime/akonadi-prefix.h.cmake b/kdepim-runtime/akonadi-prefix.h.cmake new file mode 100644 index 00000000..b2753f10 --- /dev/null +++ b/kdepim-runtime/akonadi-prefix.h.cmake @@ -0,0 +1,6 @@ +/* This file contains all the paths that change when changing the installation prefix */ + +#define AKONADIPREFIX "${CMAKE_INSTALL_PREFIX}" +#define AKONADIDATA "${SHARE_INSTALL_PREFIX}" +#define AKONADICONFIG "${CONFIG_INSTALL_DIR}" + diff --git a/kdepim-runtime/akonadi-version.h.cmake b/kdepim-runtime/akonadi-version.h.cmake new file mode 100644 index 00000000..58c028e4 --- /dev/null +++ b/kdepim-runtime/akonadi-version.h.cmake @@ -0,0 +1,25 @@ +/* + This file is part of kdepim. + Copyright (C) 2009 Christophe Giboudeaux + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef AKONADI_VERSION_H +#define AKONADI_VERSION_H + +#define AKONADI_VERSION "@AKONADI_VERSION@" + +#endif // AKONADI_VERSION_H \ No newline at end of file diff --git a/kdepim-runtime/cmake/modules/FindXsltproc.cmake b/kdepim-runtime/cmake/modules/FindXsltproc.cmake new file mode 100644 index 00000000..346c444f --- /dev/null +++ b/kdepim-runtime/cmake/modules/FindXsltproc.cmake @@ -0,0 +1,31 @@ +# Find xsltproc executable and provide a macro to generate D-Bus interfaces. +# +# The following variables are defined : +# XSLTPROC_EXECUTABLE - path to the xsltproc executable +# Xsltproc_FOUND - true if the program was found +# +find_program(XSLTPROC_EXECUTABLE xsltproc DOC "Path to the xsltproc executable") +mark_as_advanced(XSLTPROC_EXECUTABLE) + +if(XSLTPROC_EXECUTABLE) + set(Xsltproc_FOUND TRUE) + + # We depend on kdepimlibs, make sure it's found + if(NOT DEFINED KDEPIMLIBS_DATA_DIR) + find_package(KdepimLibs REQUIRED) + endif() + + # Macro to generate a D-Bus interface description from a KConfigXT file + macro(kcfg_generate_dbus_interface _kcfg _name) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml + COMMAND ${XSLTPROC_EXECUTABLE} --stringparam interfaceName ${_name} + ${KDEPIMLIBS_DATA_DIR}/akonadi-kde/kcfg2dbus.xsl + ${_kcfg} + > ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml + DEPENDS ${KDEPIMLIBS_DATA_DIR}/akonadi-kde/kcfg2dbus.xsl + ${_kcfg} + ) + endmacro() +endif() + diff --git a/kdepim-runtime/config-enterprise.h.cmake b/kdepim-runtime/config-enterprise.h.cmake new file mode 100644 index 00000000..56a2d340 --- /dev/null +++ b/kdepim-runtime/config-enterprise.h.cmake @@ -0,0 +1,2 @@ +#cmakedefine KDEPIM_ENTERPRISE_BUILD 1 + diff --git a/kdepim-runtime/defaultsetup/CMakeLists.txt b/kdepim-runtime/defaultsetup/CMakeLists.txt new file mode 100644 index 00000000..d8b73470 --- /dev/null +++ b/kdepim-runtime/defaultsetup/CMakeLists.txt @@ -0,0 +1,8 @@ +configure_file(defaultaddressbook.desktop ${CMAKE_CURRENT_BINARY_DIR}/defaultaddressbook) +configure_file(defaultcalendar.desktop ${CMAKE_CURRENT_BINARY_DIR}/defaultcalendar) +configure_file(defaultnotebook.desktop ${CMAKE_CURRENT_BINARY_DIR}/defaultnotebook) + +install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/defaultcalendar + ${CMAKE_CURRENT_BINARY_DIR}/defaultaddressbook + ${CMAKE_CURRENT_BINARY_DIR}/defaultnotebook + DESTINATION ${DATA_INSTALL_DIR}/akonadi/firstrun ) diff --git a/kdepim-runtime/defaultsetup/defaultaddressbook.desktop b/kdepim-runtime/defaultsetup/defaultaddressbook.desktop new file mode 100644 index 00000000..0ea5cd72 --- /dev/null +++ b/kdepim-runtime/defaultsetup/defaultaddressbook.desktop @@ -0,0 +1,55 @@ +[Agent] +Id=defaultaddressbook +Type=akonadi_contacts_resource +Name=Personal Contacts +Name[bg]=Лични контакти +Name[bs]=LiÄni kontakti +Name[ca]=Contactes personals +Name[ca@valencia]=Contactes personals +Name[cs]=Osobní kontakty +Name[da]=Personlige kontakter +Name[de]=Persönliche Kontakte +Name[el]=ΠÏοσωπικές επαφές +Name[en_GB]=Personal Contacts +Name[es]=Contactos personales +Name[et]=Isiklikud kontaktid +Name[fi]=Omat yhteystiedot +Name[fr]=Contacts personnels +Name[ga]=Teagmhálacha Pearsanta +Name[gl]=Contactos Persoais +Name[hu]=Személyes névjegyek +Name[ia]=Contactos personal +Name[it]=Contatti personali +Name[ja]=個人ã®é€£çµ¡å…ˆ +Name[kk]=Ð”ÐµÑ€Ð±ÐµÑ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ñ‚Ð°Ñ€ +Name[km]=ទំនាក់ទំនង​ផ្ទាល់ážáŸ’លួន +Name[ko]=ê°œì¸ ì—°ë½ì²˜ +Name[lt]=Asmeniniai kontaktai +Name[lv]=PersonÄ«gie kontakti +Name[nb]=Personlige kontakter +Name[nds]=Persöönlich Kontakten +Name[nl]=Persoonlijke contacten +Name[nn]=Personlege kontaktar +Name[pa]=ਨਿੱਜੀ ਸੰਪਰਕ +Name[pl]=Kontakty osobiste +Name[pt]=Contactos Pessoais +Name[pt_BR]=Contatos pessoais +Name[ro]=Contacte personale +Name[ru]=Личные контакты +Name[sk]=Osobné kontakty +Name[sl]=Osebni stiki +Name[sr]=Лични контакти +Name[sr@ijekavian]=Лични контакти +Name[sr@ijekavianlatin]=LiÄni kontakti +Name[sr@latin]=LiÄni kontakti +Name[sv]=Personliga kontakter +Name[tr]=KiÅŸisel BaÄŸlantılar +Name[ug]=شەخسىي ئالاقەداشلار +Name[uk]=ОÑобиÑÑ‚Ñ– контакти +Name[x-test]=xxPersonal Contactsxx +Name[zh_CN]=个人è”系人 +Name[zh_TW]=個人è¯çµ¡äºº + +[Settings] +IsConfigured=true +Path[$e]=$HOME/.local/share/contacts/ diff --git a/kdepim-runtime/defaultsetup/defaultcalendar.desktop b/kdepim-runtime/defaultsetup/defaultcalendar.desktop new file mode 100644 index 00000000..1229e43f --- /dev/null +++ b/kdepim-runtime/defaultsetup/defaultcalendar.desktop @@ -0,0 +1,50 @@ +[Agent] +Id=defaultcalendar +Type=akonadi_ical_resource +Name=Personal Calendar +Name[bs]=LiÄni kalendar +Name[ca]=Calendari personal +Name[ca@valencia]=Calendari personal +Name[cs]=Osobní kalendář +Name[da]=Personlig kalender +Name[de]=Persönlicher Kalender +Name[el]=ΠÏοσωπικό ημεÏολόγιο +Name[en_GB]=Personal Calendar +Name[es]=Calendario personal +Name[et]=Isiklik kalender +Name[fi]=Oma kalenteri +Name[fr]=Agenda personnel +Name[ga]=Féilire Pearsanta +Name[gl]=Calendario persoal +Name[hu]=Személyes naptár +Name[ia]=Calendario Personal +Name[it]=Calendario personale +Name[kk]=Ð”ÐµÑ€Ð±ÐµÑ ÐºÒ¯Ð½Ñ‚Ñ–Ð·Ð±Ðµ +Name[km]=ប្រážáž·áž‘ិន​ផ្ទាល់ážáŸ’លួន +Name[ko]=ê°œì¸ ë‹¬ë ¥ +Name[lt]=Asmeninis kalendorius +Name[lv]=PersonÄ«gais kalendÄrs +Name[nb]=Personlig kalender +Name[nds]=Persöönlich Kalenner +Name[nl]=Persoonlijke agenda +Name[pl]=Kalendarz osobisty +Name[pt]=Calendário Pessoal +Name[pt_BR]=Calendário pessoal +Name[ro]=Calendar personal +Name[ru]=Личный календарь +Name[sk]=Osobný kalendár +Name[sl]=Osebni koledar +Name[sr]=Лични календар +Name[sr@ijekavian]=Лични календар +Name[sr@ijekavianlatin]=LiÄni kalendar +Name[sr@latin]=LiÄni kalendar +Name[sv]=Personlig kalender +Name[tr]=KiÅŸisel Takvim +Name[uk]=ОÑобиÑтий календар +Name[x-test]=xxPersonal Calendarxx +Name[zh_CN]=个人日历 +Name[zh_TW]=個人行事曆 + +[Settings] +Path[$e]=$HOME/${KDE_DEFAULT_HOME}/share/apps/korganizer/std.ics + diff --git a/kdepim-runtime/defaultsetup/defaultnotebook.desktop b/kdepim-runtime/defaultsetup/defaultnotebook.desktop new file mode 100644 index 00000000..b3178bdb --- /dev/null +++ b/kdepim-runtime/defaultsetup/defaultnotebook.desktop @@ -0,0 +1,79 @@ +[Agent] +Id=defaultnotebook +Type=akonadi_akonotes_resource +Name=Notes +Name[af]=Notas +Name[ar]=ملاحظات +Name[be]=Заметкі +Name[bg]=Бележки +Name[br]=Notennoù +Name[bs]=BiljeÅ¡ke +Name[ca]=Notes +Name[ca@valencia]=Notes +Name[cs]=Poznámky +Name[cy]=Nodiadau +Name[da]=Noter +Name[de]=Notizen +Name[el]=Σημειώσεις +Name[en_GB]=Notes +Name[eo]=Notoj +Name[es]=Notas +Name[et]=Sedelid +Name[eu]=Oharrak +Name[fa]=یادداشتها +Name[fi]=Muistiinpanot +Name[fr]=Notes +Name[fy]=Notysjes +Name[ga]=Nótaí +Name[gl]=Notas +Name[he]=×¤×ª×§×™× +Name[hu]=Feljegyzések +Name[ia]=Notas +Name[is]=Minnismiðar +Name[it]=Note +Name[ja]=メモ +Name[ka]=ჩáƒáƒœáƒ˜áƒ¨áƒ•áƒœáƒ”ბი +Name[kk]=Жазбалар +Name[km]=ចំណាំ +Name[ko]=노트 +Name[lt]=UžraÅ¡ai +Name[lv]=PiezÄ«mes +Name[mai]=टिपà¥à¤ªà¤£à¥€ +Name[mk]=Белешки +Name[ms]=Nota +Name[nb]=Notater +Name[nds]=Notizen +Name[ne]=टिपोट +Name[nl]=Notities +Name[nn]=Notat +Name[oc]=Nòtas +Name[pa]=ਨੋਟਿਸ +Name[pl]=Notatki +Name[pt]=Notas +Name[pt_BR]=Notas +Name[ro]=NotiÈ›e +Name[ru]=Заметки +Name[se]=Nohtat +Name[sk]=Poznámky +Name[sl]=Notice +Name[sq]=Shënimet +Name[sr]=Белешке +Name[sr@ijekavian]=Биљешке +Name[sr@ijekavianlatin]=BiljeÅ¡ke +Name[sr@latin]=BeleÅ¡ke +Name[sv]=Anteckningar +Name[ta]=கà¯à®±à®¿à®ªà¯à®ªà¯à®•à®³à¯ +Name[tg]=Ðхборот +Name[th]=บันทึà¸à¸¢à¹ˆà¸­ +Name[tr]=Notlar +Name[ug]=ئىزاھ +Name[uk]=Примітки +Name[uz]=Yozma xotira +Name[uz@cyrillic]=Ðзма хотира +Name[wa]=Notes +Name[x-test]=xxNotesxx +Name[zh_CN]=便笺 +Name[zh_TW]=備忘錄 + +[Settings] +Path[$e]=$HOME/.local/share/notes/ diff --git a/kdepim-runtime/doc/git-migration.txt b/kdepim-runtime/doc/git-migration.txt new file mode 100644 index 00000000..2908dd65 --- /dev/null +++ b/kdepim-runtime/doc/git-migration.txt @@ -0,0 +1,7 @@ +Migrated to git using the following: + +svn2git: http://gitorious.org/svn2git version: 409d8bc4cbaade82672f251c45178c3cfed4619d +kde-ruleset: http://projects.kde.org/projects/playground/sdk/kde-ruleset/ version: d71b11d9434d951bdaaff0f7cd3de9dcae000018 +kde svn repo synced at revision: 1208541 + +command: time ionice -c3 nice svn-all-fast-export --identity-map ../kde-ruleset/account-map --rules ../kde-ruleset/kdepim-runtime-rules --add-metadata --debug-rules --stats --svn-branches ../svn/ diff --git a/kdepim-runtime/doc/libakonadi.xmi b/kdepim-runtime/doc/libakonadi.xmi new file mode 100644 index 00000000..72f35f35 --- /dev/null +++ b/kdepim-runtime/doc/libakonadi.xmi @@ -0,0 +1,9639 @@ + + + + + umbrello uml modeller http://uml.sf.net + 1.5.3 + UnicodeUTF8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+
diff --git a/kdepim-runtime/doc/pics/akonadi_agent_handling.eps b/kdepim-runtime/doc/pics/akonadi_agent_handling.eps new file mode 100644 index 00000000..974edc15 --- /dev/null +++ b/kdepim-runtime/doc/pics/akonadi_agent_handling.eps @@ -0,0 +1,1142 @@ +%!PS-Adobe-1.0 EPSF-3.0 +%%BoundingBox: 35 656 610 819 +%%Creator: Qt 3.3.6 +%%CreationDate: Do Sep 28 16:06:56 2006 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: BitstreamVeraSerif-Roman + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2005 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.417 3.125 ] [ 3.125 10.417 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +% Fonts and encodings used +/BitstreamVeraSerif-RomanList [ +[ /BitstreamVeraSerif-Roman 1.0 0.0 ] + [ /BitstreamVeraSerif 1.0 0.0 ] + [ /Helvetica 0.988 0.000 ] +] d +%%BeginFont: Bitstream Vera Serif +%!PS-Adobe-3.0 Resource-Font +%%Copyright: Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. +%%Creator: Converted from TrueType by Qt +25 dict begin +/_d{bind def}bind def +/_m{moveto}_d +/_l{lineto}_d +/_cl{closepath eofill}_d +/_c{curveto}_d +/_sc{7 -1 roll{setcachedevice}{pop pop pop pop pop pop}ifelse}_d +/_e{exec}_d +/FontName /BitstreamVeraSerif-Roman def +/PaintType 0 def +/FontMatrix[.001 0 0 .001 0 0]def +/FontBBox[-182 -235 1287 928]def +/FontType 3 def +/Encoding StandardEncoding def +/FontInfo 10 dict dup begin +/FamilyName (Bitstream Vera Serif) def +/FullName (Bitstream Vera Serif) def +/Notice (Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.) def +/Weight (Roman) def +/Version (Release 1.10) def +/ItalicAngle 0.0 def +/isFixedPitch false def +/UnderlinePosition -213 def +/UnderlineThickness 133 def +end readonly def +/CharStrings 32 dict dup begin +/.notdef{600 0 50 -176 550 705 _sc +50 -176 _m +50 705 _l +550 705 _l +550 -176 _l +50 -176 _l +106 -120 _m +494 -120 _l +494 649 _l +106 649 _l +106 -120 _l +_cl}_d +/space{318 0 0 0 0 0 _sc +}_d +/parenleft{390 0 79 -155 319 760 _sc +319 -155 _m +239 -119 179 -63 139 13 _c +99 89 79 186 79 302 _c +79 418 99 514 139 591 _c +179 668 239 724 319 760 _c +319 712 _l +269 677 233 628 211 566 _c +189 503 178 415 178 302 _c +178 188 189 100 211 38 _c +233 -24 269 -72 319 -107 _c +319 -155 _l +_cl}_d +/parenright{390 0 71 -155 311 760 _sc +71 -155 _m +71 -107 _l +121 -72 157 -24 179 38 _c +201 100 212 188 212 302 _c +212 415 201 503 179 566 _c +157 628 121 677 71 712 _c +71 760 _l +150 724 210 668 250 591 _c +290 514 311 418 311 302 _c +311 186 290 89 250 13 _c +210 -63 150 -119 71 -155 _c +_cl}_d +/colon{337 0 104 -13 234 434 _sc +104 51 _m +104 69 110 84 123 97 _c +135 109 151 116 169 116 _c +187 116 202 109 215 97 _c +227 84 234 69 234 51 _c +234 32 227 17 215 5 _c +203 -7 187 -13 169 -13 _c +150 -13 134 -7 122 5 _c +110 17 104 32 104 51 _c +104 369 _m +104 387 110 402 123 415 _c +135 427 151 434 169 434 _c +187 434 203 427 215 415 _c +227 403 234 387 234 369 _c +234 350 227 334 215 322 _c +203 310 187 304 169 304 _c +151 304 135 310 123 323 _c +110 335 104 351 104 369 _c +_cl}_d +/A{722 0 -5 0 732 729 _sc +200 264 _m +468 264 _l +334 611 _l +200 264 _l +-5 0 _m +-5 52 _l +58 52 _l +318 729 _l +400 729 _l +660 52 _l +732 52 _l +732 0 _l +467 0 _l +467 52 _l +548 52 _l +487 212 _l +180 212 _l +119 52 _l +199 52 _l +199 0 _l +-5 0 _l +_cl}_d +/B{{735 0 55 0 674 729 _sc +247 52 _m +393 52 _l +451 52 494 64 521 90 _c +548 115 562 155 562 211 _c +562 265 548 305 521 331 _c +494 356 451 369 393 369 _c +247 369 _l +247 52 _l +55 0 _m +55 52 _l +148 52 _l +148 677 _l +55 677 _l +55 729 _l +415 729 _l +488 729 543 714 581 684 _c +618 654 637 609 637 549 _c +637 505 624 471 598 445 _c +572 419 535 404 485 398 _c +547 390 594 370 626 338 _c +658 306 674 264 674 211 _c +674 139 651 85 605 51 _c +559 17 488 0 392 0 _c +55 0 _l +247 421 _m +371 421 _l +424 421 463 431 488 451 _c +512 471 525 504 525 549 _c +}_e{525 593 512 626 488 646 _c +463 666 424 677 371 677 _c +247 677 _l +247 421 _l +_cl}_e}_d +/C{{765 0 56 -13 705 742 _sc +705 193 _m +683 125 647 73 597 39 _c +546 4 482 -13 405 -13 _c +357 -13 312 -5 272 11 _c +231 27 195 50 164 82 _c +127 118 100 160 82 206 _c +64 252 56 305 56 364 _c +56 477 88 568 154 638 _c +219 707 305 742 413 742 _c +453 742 495 736 540 726 _c +584 716 633 700 685 679 _c +685 511 _l +630 511 _l +618 572 593 617 557 646 _c +521 675 470 690 405 690 _c +327 690 268 662 228 607 _c +188 551 168 470 168 364 _c +168 257 188 176 228 121 _c +268 65 327 38 405 38 _c +459 38 503 51 539 77 _c +574 103 599 141 615 193 _c +705 193 _l +_cl}_e}_d +/D{802 0 55 0 744 729 _sc +247 52 _m +338 52 _l +432 52 505 79 556 133 _c +606 187 632 264 632 365 _c +632 466 606 543 556 597 _c +505 650 432 677 338 677 _c +247 677 _l +247 52 _l +55 0 _m +55 52 _l +148 52 _l +148 677 _l +55 677 _l +55 729 _l +345 729 _l +471 729 569 697 639 633 _c +709 569 744 479 744 365 _c +744 250 708 160 638 96 _c +568 32 470 0 345 0 _c +55 0 _l +_cl}_d +/I{395 0 55 0 340 729 _sc +247 52 _m +340 52 _l +340 0 _l +55 0 _l +55 52 _l +148 52 _l +148 677 _l +55 677 _l +55 729 _l +340 729 _l +340 677 _l +247 677 _l +247 52 _l +_cl}_d +/M{1024 0 50 0 973 729 _sc +55 0 _m +55 52 _l +148 52 _l +148 677 _l +50 677 _l +50 729 _l +262 729 _l +518 210 _l +774 729 _l +973 729 _l +973 677 _l +876 677 _l +876 52 _l +969 52 _l +969 0 _l +684 0 _l +684 52 _l +777 52 _l +777 615 _l +527 107 _l +458 107 _l +208 615 _l +208 52 _l +301 52 _l +301 0 _l +55 0 _l +_cl}_d +/O{{820 0 56 -13 764 742 _sc +410 38 _m +490 38 550 65 591 120 _c +631 175 652 256 652 364 _c +652 471 631 552 591 607 _c +550 662 490 690 410 690 _c +330 690 269 662 229 607 _c +188 552 168 471 168 364 _c +168 256 188 175 229 120 _c +269 65 330 38 410 38 _c +410 -13 _m +360 -13 315 -5 273 11 _c +231 27 195 50 164 82 _c +127 118 100 160 82 206 _c +64 252 56 304 56 364 _c +56 422 64 475 82 521 _c +100 567 127 609 164 646 _c +196 678 232 702 273 718 _c +314 734 360 742 410 742 _c +516 742 601 707 666 638 _c +731 568 764 477 764 364 _c +764 305 755 252 737 206 _c +719 159 692 118 656 82 _c +624 50 587 26 546 10 _c +505 -5 460 -13 410 -13 _c +_cl}_e}_d +/Q{{820 0 56 -159 764 742 _sc +422 -13 _m +310 -13 221 21 155 89 _c +89 157 56 248 56 364 _c +56 422 64 475 82 521 _c +100 567 127 609 164 646 _c +196 678 232 702 273 718 _c +314 734 360 742 410 742 _c +516 742 601 707 666 638 _c +731 568 764 477 764 364 _c +764 267 739 186 691 120 _c +642 54 575 13 489 -5 _c +506 -27 527 -43 553 -53 _c +578 -63 608 -69 644 -69 _c +659 -69 _l +659 -159 _l +604 -156 557 -142 518 -118 _c +478 -94 446 -59 422 -13 _c +410 38 _m +490 38 550 65 591 120 _c +631 175 652 256 652 364 _c +652 471 631 552 591 607 _c +550 662 490 690 410 690 _c +330 690 269 662 229 607 _c +188 552 168 471 168 364 _c +168 256 188 175 229 120 _c +269 65 330 38 410 38 _c +_cl}_e}_d +/R{{753 0 55 0 777 729 _sc +479 362 _m +501 356 521 345 538 330 _c +554 315 569 294 582 268 _c +688 52 _l +777 52 _l +777 0 _l +605 0 _l +491 232 _l +469 276 449 305 431 319 _c +413 332 388 339 356 339 _c +247 339 _l +247 52 _l +350 52 _l +350 0 _l +55 0 _l +55 52 _l +148 52 _l +148 677 _l +55 677 _l +55 729 _l +425 729 _l +495 729 550 712 589 678 _c +627 644 647 596 647 534 _c +647 484 633 444 605 416 _c +577 387 535 369 479 362 _c +247 391 _m +391 391 _l +440 391 476 402 500 426 _c +523 449 535 485 535 534 _c +}_e{535 582 523 618 500 642 _c +476 665 440 677 391 677 _c +247 677 _l +247 391 _l +_cl}_e}_d +/a{{596 0 50 -13 568 533 _sc +398 163 _m +398 273 _l +282 273 _l +237 273 204 263 182 244 _c +160 224 150 195 150 156 _c +150 120 161 91 183 70 _c +205 48 235 38 273 38 _c +310 38 340 49 363 72 _c +386 95 398 125 398 163 _c +488 324 _m +488 52 _l +568 52 _l +568 0 _l +398 0 _l +398 56 _l +378 32 355 14 329 3 _c +303 -7 272 -13 238 -13 _c +180 -13 134 2 100 32 _c +66 62 50 104 50 156 _c +50 209 69 250 108 280 _c +146 310 201 325 272 325 _c +398 325 _l +398 361 _l +398 400 386 430 362 452 _c +338 474 304 485 261 485 _c +225 485 197 476 176 460 _c +154 444 141 420 136 388 _c +90 388 _l +}_e{90 493 _l +121 506 151 516 181 523 _c +210 529 239 533 267 533 _c +339 533 393 515 431 479 _c +469 443 488 392 488 324 _c +_cl}_e}_d +/b{{640 0 29 -13 590 760 _sc +115 52 _m +115 708 _l +29 708 _l +29 760 _l +205 760 _l +205 438 _l +222 470 244 494 272 510 _c +299 525 333 533 373 533 _c +437 533 489 507 529 457 _c +569 407 590 341 590 260 _c +590 178 569 112 529 62 _c +489 12 437 -13 373 -13 _c +333 -13 299 -5 272 9 _c +244 24 222 48 205 81 _c +205 0 _l +29 0 _l +29 52 _l +115 52 _l +205 234 _m +205 171 217 123 241 91 _c +265 58 299 42 345 42 _c +391 42 425 60 449 97 _c +473 133 485 188 485 260 _c +485 332 473 386 449 422 _c +425 458 391 477 345 477 _c +299 477 265 460 241 427 _c +217 394 205 347 205 285 _c +205 234 _l +}_e{_cl}_e}_d +/c{{560 0 50 -13 514 533 _sc +514 156 _m +501 100 477 58 441 30 _c +405 1 358 -13 301 -13 _c +225 -13 165 11 119 61 _c +73 111 50 177 50 260 _c +50 342 73 408 119 458 _c +165 508 225 533 301 533 _c +333 533 366 529 399 521 _c +431 513 464 502 497 487 _c +497 354 _l +445 354 _l +438 399 423 432 400 453 _c +377 474 344 485 302 485 _c +253 485 216 466 192 428 _c +167 390 155 334 155 260 _c +155 184 167 128 192 90 _c +216 52 253 34 302 34 _c +340 34 371 44 394 64 _c +417 84 433 115 442 156 _c +514 156 _l +_cl}_e}_d +/d{{640 0 50 -13 611 760 _sc +525 52 _m +611 52 _l +611 0 _l +435 0 _l +435 81 _l +417 48 395 24 368 9 _c +340 -5 307 -13 267 -13 _c +203 -13 150 12 110 62 _c +70 112 50 178 50 260 _c +50 341 70 407 110 457 _c +150 507 203 533 267 533 _c +307 533 340 525 368 510 _c +395 494 417 470 435 438 _c +435 708 _l +350 708 _l +350 760 _l +525 760 _l +525 52 _l +435 234 _m +435 285 _l +435 347 423 394 399 427 _c +375 460 340 477 295 477 _c +249 477 214 458 190 422 _c +166 386 155 332 155 260 _c +155 188 166 133 190 97 _c +214 60 249 42 295 42 _c +340 42 375 58 399 91 _c +423 123 435 171 435 234 _c +}_e{_cl}_e}_d +/e{{592 0 50 -13 542 533 _sc +542 250 _m +155 250 _l +155 246 _l +155 176 168 123 194 87 _c +220 51 259 34 311 34 _c +350 34 382 44 408 65 _c +433 85 451 116 461 157 _c +533 157 _l +519 100 492 57 454 29 _c +415 1 364 -13 302 -13 _c +226 -13 165 11 119 61 _c +73 111 50 177 50 260 _c +50 342 72 408 118 458 _c +163 508 222 533 296 533 _c +374 533 435 508 477 460 _c +519 412 540 342 542 250 _c +436 302 _m +434 362 421 408 397 439 _c +373 469 340 485 296 485 _c +254 485 222 469 198 438 _c +174 407 160 362 155 302 _c +436 302 _l +_cl}_e}_d +/g{{640 0 50 -221 611 533 _sc +525 467 _m +525 11 _l +525 -63 504 -120 463 -160 _c +422 -200 364 -221 288 -221 _c +254 -221 221 -218 190 -212 _c +158 -206 128 -196 100 -184 _c +100 -75 _l +147 -75 _l +153 -109 166 -133 188 -149 _c +210 -165 241 -173 282 -173 _c +334 -173 373 -158 398 -128 _c +422 -98 435 -51 435 11 _c +435 81 _l +417 48 395 24 368 9 _c +340 -5 307 -13 267 -13 _c +203 -13 150 12 110 62 _c +70 112 50 178 50 260 _c +50 341 70 407 110 457 _c +150 507 203 533 267 533 _c +307 533 340 525 368 510 _c +395 494 417 470 435 438 _c +435 519 _l +611 519 _l +611 467 _l +525 467 _l +435 285 _m +}_e{435 347 423 394 399 427 _c +375 460 340 477 295 477 _c +249 477 214 458 190 422 _c +166 386 155 332 155 260 _c +155 188 166 133 190 97 _c +214 60 249 42 295 42 _c +340 42 375 58 399 91 _c +423 123 435 171 435 234 _c +435 285 _l +_cl}_e}_d +/h{{644 0 36 0 616 760 _sc +41 0 _m +41 52 _l +122 52 _l +122 708 _l +36 708 _l +36 760 _l +212 760 _l +212 427 _l +228 461 250 488 276 506 _c +302 524 333 533 369 533 _c +426 533 468 516 495 484 _c +522 451 536 400 536 330 _c +536 52 _l +616 52 _l +616 0 _l +368 0 _l +368 52 _l +446 52 _l +446 302 _l +446 365 438 408 422 432 _c +406 455 379 467 340 467 _c +298 467 266 451 244 421 _c +222 391 212 347 212 289 _c +212 52 _l +290 52 _l +290 0 _l +41 0 _l +_cl}_e}_d +/i{320 0 36 0 297 736 _sc +97 680 _m +97 695 102 708 113 719 _c +124 730 137 736 153 736 _c +167 736 180 730 191 719 _c +202 708 208 695 208 680 _c +208 664 202 651 192 641 _c +181 630 168 625 153 625 _c +137 625 124 630 113 641 _c +102 651 97 664 97 680 _c +212 52 _m +297 52 _l +297 0 _l +36 0 _l +36 52 _l +122 52 _l +122 467 _l +36 467 _l +36 519 _l +212 519 _l +212 52 _l +_cl}_d +/k{{606 0 29 0 613 760 _sc +286 0 _m +34 0 _l +34 52 _l +115 52 _l +115 708 _l +29 708 _l +29 760 _l +205 760 _l +205 265 _l +424 467 _l +349 467 _l +349 519 _l +584 519 _l +584 467 _l +495 467 _l +341 324 _l +538 52 _l +613 52 _l +613 0 _l +357 0 _l +357 52 _l +431 52 _l +276 265 _l +205 199 _l +205 52 _l +286 52 _l +286 0 _l +_cl}_e}_d +/l{320 0 29 0 290 760 _sc +205 52 _m +290 52 _l +290 0 _l +29 0 _l +29 52 _l +115 52 _l +115 708 _l +29 708 _l +29 760 _l +205 760 _l +205 52 _l +_cl}_d +/n{{644 0 36 0 616 533 _sc +41 0 _m +41 52 _l +122 52 _l +122 467 _l +36 467 _l +36 519 _l +212 519 _l +212 427 _l +228 461 250 488 276 506 _c +302 524 333 533 369 533 _c +426 533 468 516 495 484 _c +522 451 536 400 536 330 _c +536 52 _l +616 52 _l +616 0 _l +368 0 _l +368 52 _l +446 52 _l +446 302 _l +446 365 438 408 422 432 _c +406 456 379 468 340 468 _c +298 468 266 452 244 422 _c +222 391 212 347 212 289 _c +212 52 _l +290 52 _l +290 0 _l +41 0 _l +_cl}_e}_d +/o{602 0 50 -13 552 533 _sc +301 34 _m +349 34 385 53 410 91 _c +434 129 447 185 447 260 _c +447 334 434 390 410 428 _c +385 466 349 485 301 485 _c +253 485 216 466 192 428 _c +167 390 155 334 155 260 _c +155 185 167 129 192 91 _c +216 53 253 34 301 34 _c +301 -13 _m +225 -13 165 11 119 61 _c +73 111 50 177 50 260 _c +50 342 72 408 118 458 _c +164 508 225 533 301 533 _c +377 533 437 508 483 458 _c +529 408 552 342 552 260 _c +552 177 529 111 483 61 _c +437 11 377 -13 301 -13 _c +_cl}_d +/r{478 0 36 0 478 533 _sc +478 520 _m +478 390 _l +426 390 _l +424 416 417 435 405 448 _c +392 460 373 467 349 467 _c +305 467 271 451 247 421 _c +223 390 212 346 212 289 _c +212 52 _l +316 52 _l +316 0 _l +41 0 _l +41 52 _l +122 52 _l +122 468 _l +36 468 _l +36 519 _l +212 519 _l +212 427 _l +229 463 251 489 279 507 _c +307 524 341 533 381 533 _c +395 533 411 531 427 529 _c +443 527 460 524 478 520 _c +_cl}_d +/s{{513 0 56 -13 462 533 _sc +56 29 _m +56 150 _l +108 150 _l +109 111 121 82 144 63 _c +167 43 201 34 246 34 _c +286 34 317 41 338 57 _c +359 72 370 94 370 123 _c +370 145 362 164 347 178 _c +331 192 299 207 249 223 _c +184 245 _l +139 259 107 277 87 299 _c +67 320 57 347 57 381 _c +57 428 74 465 109 492 _c +144 519 192 533 254 533 _c +281 533 310 529 340 522 _c +370 515 402 505 434 491 _c +434 378 _l +382 378 _l +380 411 369 437 347 456 _c +325 475 295 485 257 485 _c +219 485 190 478 171 465 _c +151 451 142 431 142 405 _c +142 383 149 365 164 352 _c +178 339 208 326 252 312 _c +323 290 _l +372 274 407 255 429 232 _c +451 209 462 180 462 144 _c +}_e{462 94 443 56 405 28 _c +367 0 316 -13 250 -13 _c +216 -13 184 -9 152 -3 _c +120 3 88 14 56 29 _c +_cl}_e}_d +/t{402 0 29 -13 394 680 _sc +108 467 _m +29 467 _l +29 519 _l +108 519 _l +108 680 _l +198 680 _l +198 519 _l +367 519 _l +367 467 _l +198 467 _l +198 137 _l +198 93 202 64 211 52 _c +219 40 235 34 258 34 _c +281 34 298 41 309 55 _c +319 69 325 91 326 122 _c +394 122 _l +391 74 378 40 355 19 _c +332 -2 297 -13 250 -13 _c +198 -13 161 -1 140 21 _c +118 43 108 82 108 137 _c +108 467 _l +_cl}_d +/u{{644 0 27 -13 607 519 _sc +354 519 _m +522 519 _l +522 52 _l +607 52 _l +607 0 _l +432 0 _l +432 92 _l +415 57 393 31 367 13 _c +341 -4 310 -13 276 -13 _c +218 -13 175 3 148 35 _c +121 67 108 119 108 189 _c +108 467 _l +27 467 _l +27 519 _l +198 519 _l +198 217 _l +198 153 205 110 221 87 _c +237 63 264 52 304 52 _c +346 52 377 67 399 98 _c +421 128 432 173 432 231 _c +432 467 _l +354 467 _l +354 519 _l +_cl}_e}_d +/v{565 0 -2 0 562 519 _sc +247 0 _m +56 467 _l +-2 467 _l +-2 519 _l +236 519 _l +236 467 _l +153 467 _l +299 110 _l +445 467 _l +367 467 _l +367 519 _l +562 519 _l +562 467 _l +504 467 _l +313 0 _l +247 0 _l +_cl}_d +/w{856 0 16 0 843 519 _sc +480 519 _m +613 114 _l +730 467 _l +655 467 _l +655 519 _l +843 519 _l +843 467 _l +785 467 _l +631 0 _l +556 0 _l +428 388 _l +300 0 _l +228 0 _l +74 467 _l +16 467 _l +16 519 _l +251 519 _l +251 467 _l +167 467 _l +283 114 _l +417 519 _l +480 519 _l +_cl}_d +end readonly def + +/BuildGlyph + {exch begin + CharStrings exch + 2 copy known not{pop /.notdef}if + true 3 1 roll get exec + end}_d + +/BuildChar { + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec +}_d + +FontName currentdict end definefont pop +%% Font Page 00 +/BitstreamVeraSerif-Roman-ENC-00 [ +/.notdef/space/colon/C/l/i/e/n/t/A/g/M/a/r/parenleft/b/k/o/d/parenright/R/s/u +/c/h/I/D/B/v/O/w/Q/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef] def +/BitstreamVeraSerif-Roman-Uni-00 BitstreamVeraSerif-Roman-ENC-00 /BitstreamVeraSerif-Roman MFEmb +%%BeginFont: BitstreamVeraSerif-Roman +%!PS-AdobeFont-1.0 Composite Font +%%FontName: BitstreamVeraSerif-Roman-Uni +%%Creator: Composite font created by Qt +25 dict begin +/FontName /BitstreamVeraSerif-Roman-Uni def +/PaintType 0 def +/FontMatrix[1 0 0 1 0 0]def +/FontType 0 def +/FMapType 2 def +/Encoding [ +0]def +/FDepVector [ +/BitstreamVeraSerif-Roman-Uni-00 findfont +]def +FontName currentdict end definefont pop +%%EndFont +%%EndFont +/F1 8.33333/BitstreamVeraSerif-Roman-Uni DF +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -42 -48]ST +0 0 B 0 0 PE +WB +W BC +42 48 597 168 R +2 0 255 0 0 0 0 PE +NB +67 216 69 VL +252 216 69 VL +432 202 69 VL +610 189 69 VL +0 0 B 0 0 PE +WB +243 100 17 32 R +255 0 0 P1 +NB +243 100 17 32 R +243 104 67 HL +1 255 0 0 BR +NP +242 104 MT +238 101 LT +238 107 LT +CP BF QS +71 126 67 129 DL +71 132 67 129 DL +3 0 255 0 0 0 0 PE +243 129 67 HL +0 0 B 0 0 PE +WB +423 101 17 32 R +255 0 0 P1 +NB +423 101 17 32 R +423 105 260 HL +1 255 0 0 BR +NP +422 105 MT +418 102 LT +418 108 LT +CP BF QS +264 127 260 130 DL +264 133 260 130 DL +3 0 255 0 0 0 0 PE +423 130 260 HL +255 0 0 P1 +NB +604 110 608 113 DL +604 116 608 113 DL +608 113 440 HL +255 0 0 P1 +437 162 433 165 DL +437 168 433 165 DL +608 165 433 HL +255 0 0 P1 +257 175 253 178 DL +257 181 253 178 DL +430 178 253 HL +255 0 0 P1 +72 189 68 192 DL +72 195 68 192 DL +250 192 68 HL +255 0 0 P1 +1 255 255 192 BR +42 48 50 21 R +CLSTART +5 5 40 11 ACR +CLEND +B P1 +F1 F +61 Y<000100020001000300040005000600070008>[3 0 3 0 3 0 6 0 3 0 3 0 5 0 5 0 0 0]34 50 63 1 Tl XYT +CLO +[1 0 0 1 -42 -48]ST +255 0 0 P1 +1 255 255 192 BR +186 48 133 21 R +CLO +[1 0 0 1 -42 -48]ST +CLSTART +149 5 123 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<0001000200010009000A000600070008000B000C0007000C000A0006000D0001000E00040005000F000C001000110007000C001200050013>[3 0 3 0 3 0 6 0 5 0 5 0 5 0 3 0 8 0 5 0 5 0 5 0 5 0 5 0 4 0 3 0 3 0 3 0 3 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 3 0 0 0]123 191 63 1 Tl XYT +CLO +[1 0 0 1 -42 -48]ST +255 0 0 P1 +1 255 255 192 BR +407 48 50 21 R +CLO +[1 0 0 1 -42 -48]ST +CLSTART +370 5 40 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<0001000200010003001100070008000D00110004>[3 0 3 0 3 0 6 0 5 0 5 0 3 0 4 0 5 0 0 0]40 412 63 1 Tl XYT +CLO +[1 0 0 1 -42 -48]ST +255 0 0 P1 +1 255 255 192 BR +582 48 57 21 R +CLO +[1 0 0 1 -42 -48]ST +CLSTART +545 5 47 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<00010002000100140006001500110016000D00170006>[3 0 3 0 3 0 6 0 5 0 4 0 5 0 5 0 4 0 4 0 0 0]47 587 63 1 Tl XYT +CLO +[1 0 0 1 -42 -48]ST +CLSTART +35 37 147 15 ACR +CLEND +50 d2 P1 +NB +F1 F +95 Y<000B0006000800180011001200010017000C00040004000200010017000D0006000C000800060009000A0006000700080019000700150008000C000700170006>[8 0 5 0 3 0 5 0 5 0 5 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 4 0 4 0 5 0 5 0 3 0 5 0 6 0 5 0 5 0 5 0 3 0 3 0 5 0 4 0 3 0 5 0 5 0 4 0 0 0]139 81 XYT +CLO +[1 0 0 1 -42 -48]ST +CLSTART +215 38 137 15 ACR +CLEND +50 d2 P1 +NB +F1 F +96 Y<001A001B0016001500010017000C00040004000200010017000D0006000C000800060009000A0006000700080019000700150008000C000700170006>[6 0 6 0 5 0 4 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 4 0 4 0 5 0 5 0 3 0 5 0 6 0 5 0 5 0 5 0 3 0 3 0 5 0 4 0 3 0 5 0 5 0 4 0 0 0]129 261 XYT +CLO +[1 0 0 1 -42 -48]ST +CLSTART +399 46 118 15 ACR +CLEND +50 d2 P1 +NB +F1 F +104 Y<0009001700080005001100070002000100150008000C000D0008001500010008001800060001000D0006001500110016000D00170006>[6 0 4 0 3 0 3 0 5 0 5 0 3 0 3 0 4 0 3 0 5 0 4 0 3 0 4 0 3 0 3 0 5 0 5 0 3 0 4 0 5 0 4 0 5 0 5 0 4 0 4 0 0 0]110 445 XYT +CLO +[1 0 0 1 -42 -48]ST +CLSTART +399 98 155 15 ACR +CLEND +50 d2 P1 +NB +F1 F +156 Y<001A001B00160015000100150005000A0007000C00040002000100150006000D001C000500170006001D001E00070006000D00030018000C0007000A00060012>[6 0 6 0 5 0 4 0 3 0 4 0 3 0 5 0 5 0 5 0 3 0 3 0 3 0 4 0 5 0 4 0 4 0 3 0 4 0 5 0 6 0 7 0 5 0 5 0 4 0 6 0 5 0 5 0 5 0 5 0 5 0 0 0]147 445 XYT +CLO +[1 0 0 1 -42 -48]ST +CLSTART +216 111 155 15 ACR +CLEND +50 d2 P1 +NB +F1 F +169 Y<001A001B00160015000100150005000A0007000C000400020001000C000A0006000700080019000700150008000C00070017000600090012001200060012>[6 0 6 0 5 0 4 0 3 0 4 0 3 0 5 0 5 0 5 0 3 0 3 0 3 0 5 0 5 0 5 0 5 0 3 0 3 0 5 0 4 0 3 0 5 0 5 0 4 0 5 0 6 0 5 0 5 0 5 0 0 0]138 266 XYT +CLO +[1 0 0 1 -42 -48]ST +CLSTART +39 125 134 15 ACR +CLEND +50 d2 P1 +NB +F1 F +183 Y<001F0008000100150005000A0007000C000400020001000C000A0006000700080019000700150008000C00070017000600090012001200060012>[6 0 3 0 3 0 4 0 3 0 5 0 5 0 5 0 3 0 3 0 3 0 5 0 5 0 5 0 5 0 3 0 3 0 5 0 4 0 3 0 5 0 5 0 4 0 5 0 6 0 5 0 5 0 5 0 0 0]126 85 XYT + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: BitstreamVeraSerif-Roman +%%EOF diff --git a/kdepim-runtime/doc/pics/akonadi_agent_handling.png b/kdepim-runtime/doc/pics/akonadi_agent_handling.png new file mode 100644 index 0000000000000000000000000000000000000000..ef14ec5114e1a591f618063a2908dc550c8e5e91 GIT binary patch literal 9008 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[3 0 3 0 3 0 6 0 3 0 3 0 5 0 5 0 0 0]34 55 63 1 Tl XYT +CLO +[1 0 0 1 -39 -48]ST +255 0 0 P1 +1 255 255 192 BR +328 48 51 21 R +CLO +[1 0 0 1 -39 -48]ST +CLSTART +294 5 41 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<00010002000100090008000A000B000C000D0006>[3 0 3 0 3 0 5 0 3 0 5 0 4 0 5 0 5 0 0 0]41 333 63 1 Tl XYT +CLO +[1 0 0 1 -39 -48]ST +255 0 0 P1 +1 255 255 192 BR +472 48 57 21 R +CLO +[1 0 0 1 -39 -48]ST +CLSTART +438 5 47 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<000100020001000E0006000F000A0010000B00110006>[3 0 3 0 3 0 6 0 5 0 4 0 5 0 5 0 4 0 4 0 0 0]47 477 63 1 Tl XYT +CLO +[1 0 0 1 -39 -48]ST +255 0 0 P1 +1 255 255 192 BR +186 48 85 21 R +CLO +[1 0 0 1 -39 -48]ST +CLSTART +152 5 75 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<00010002000100090006000C000B0011001200010013000B000A0014000500150006000B>[3 0 3 0 3 0 5 0 5 0 5 0 4 0 4 0 5 0 3 0 5 0 4 0 5 0 4 0 3 0 5 0 5 0 0 0]75 191 63 1 Tl XYT +CLO +[1 0 0 1 -39 -48]ST +255 0 0 P1 +1 255 255 192 BR +562 48 107 21 R +CLO +[1 0 0 1 -39 -48]ST +CLSTART +528 5 97 11 ACR +CLEND +B P1 +1 255 255 192 BR +F1 F +<0001000200010016001700080006000B0007000C000400010018000C0008000C00010009000A0010000B00110006>[3 0 3 0 3 0 6 0 4 0 3 0 5 0 4 0 5 0 5 0 3 0 3 0 6 0 5 0 3 0 5 0 3 0 5 0 5 0 5 0 4 0 4 0 0 0]97 567 63 1 Tl XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +39 23 102 15 ACR +CLEND +50 d2 P1 +NB +F1 F +81 Y<001800190010000F00010011000C0004000400020001000F0006000C000B00110012001A00080006001B000F>[6 0 6 0 5 0 4 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 4 0 5 0 5 0 4 0 4 0 5 0 3 0 3 0 5 0 7 0 0 0]94 82 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +319 48 136 15 ACR +CLEND +50 d2 P1 +NB +F1 F +106 Y<001800190010000F00010011000C0004000400020001000B0006001C00100006000F0008001A00080006001B001800060004000500140006000B001D>[6 0 6 0 5 0 4 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 4 0 5 0 5 0 5 0 5 0 4 0 3 0 3 0 3 0 5 0 7 0 6 0 5 0 3 0 3 0 4 0 5 0 4 0 0 0]128 362 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +39 104 138 15 ACR +CLEND +50 d2 P1 +NB +F1 F +162 Y<001800190010000F0001000F0005000D0007000C000400020001000F0006000C000B00110012001A00080006001B000F000E0006000F001000040008>[6 0 6 0 5 0 4 0 3 0 4 0 3 0 5 0 5 0 5 0 3 0 3 0 3 0 4 0 5 0 5 0 4 0 4 0 5 0 3 0 3 0 5 0 7 0 4 0 6 0 5 0 4 0 5 0 3 0 0 0]130 82 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +198 49 74 15 ACR +CLEND +50 d2 P1 +NB +F1 F +107 Y<001A001E001F001300010011000C000400040002000100200006000800110012>[3 0 8 0 6 0 5 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 3 0 5 0 3 0 4 0 0 0]66 241 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +467 48 92 15 ACR +CLEND +50 d2 P1 +NB +F1 F +106 Y<001F001100080005000A000700020001000B0006001C00100006000F000800010015000C0008000C>[6 0 4 0 3 0 3 0 5 0 5 0 3 0 3 0 4 0 5 0 5 0 5 0 5 0 4 0 3 0 3 0 5 0 5 0 3 0 0 0]84 510 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +467 68 94 15 ACR +CLEND +50 d2 P1 +NB +F1 F +126 Y<001F001100080005000A000700020001000B00060008000B000500060014000600010015000C0008000C>[6 0 4 0 3 0 3 0 5 0 5 0 3 0 3 0 4 0 5 0 3 0 4 0 3 0 5 0 4 0 5 0 3 0 5 0 5 0 3 0 0 0]86 510 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +320 68 75 15 ACR +CLEND +50 d2 P1 +NB +F1 F +<001A001E001F001300010011000C0004000400020001000F0008000A000B0006>[3 0 8 0 6 0 5 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 4 0 3 0 5 0 4 0 0 0]67 363 XYT +CLO +[1 0 0 1 -39 -48]ST +CLSTART +38 148 74 15 ACR +CLEND +50 d2 P1 +NB +F1 F +206 Y<001A001E001F001300010011000C000400040002000100200006000800110012>[3 0 8 0 6 0 5 0 3 0 4 0 5 0 3 0 3 0 3 0 3 0 3 0 5 0 3 0 4 0 0 0]66 81 XYT + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: BitstreamVeraSerif-Roman +%%EOF diff --git a/kdepim-runtime/doc/pics/akonadi_client_search.png b/kdepim-runtime/doc/pics/akonadi_client_search.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5915afda520f28627dd0e0cc5047a6c832bef0 GIT binary patch literal 10244 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 V(`-i`@b7#OMOjsud}(8!{|8!$nr8q2 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/doc/pics/akonadi_client_search_small.png b/kdepim-runtime/doc/pics/akonadi_client_search_small.png new file mode 100644 index 0000000000000000000000000000000000000000..c089a302c4cec594fefc5c0c9b524111ec470328 GIT binary patch literal 3726 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 + + + + umbrello uml modeller http://uml.sf.net + 1.5.3 + UnicodeUTF8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kdepim-runtime/doc/pics/akonadi_concept_schema.sla b/kdepim-runtime/doc/pics/akonadi_concept_schema.sla new file mode 100644 index 00000000..9ffc59b6 --- /dev/null +++ b/kdepim-runtime/doc/pics/akonadi_concept_schema.sla @@ -0,0 +1,721 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kdepim-runtime/doc/pics/akonadi_overview_uml.png b/kdepim-runtime/doc/pics/akonadi_overview_uml.png new file mode 100644 index 0000000000000000000000000000000000000000..70817a61924fb0b864a315d6e0ed678486994940 GIT binary patch literal 14070 zcma)jc|26%_wZQ8GGkwc$WFqD77-$(LdiO=C1yy)P?BBtHHuP3q_j}t(jA5DV{BQX z$k-``82cKM_fGwm@B9As&gZ)KKF@QW^PK0L^PK0L^IW$q%ujQp#83zXg4^WGNh<_` z1wbHJK@JwUCU0Uu9De9poV77tSXh8x|KrEU$0rRJnV6VMN=iQL?k+4WE-o%DFR!ev zt!->P=C>AtM{R!IjaHRzw6}4Ct~AJ`B7Rj z(YwHTeBNSEc6D}QH;Cv~^Pcu{Mj-`7pW67ZR#^d%+o(<~YQjOhKm?#LV<6^wD_;)a z``WmLACRSFO<~6Q0I#_LPT(G1$z~V#q0W~-Gv%nY`DCj2{XkDj}|B3Oxk!x#XYtA?o!%UF} zrWFjeU83MFwmdbv_ZeLakAiCU3?3>s%v@y9QovRCuR$5a!bMLMN%C^O>$^$KFQICT zG6$`ad#C)cZFIUj3~qpLy|Bd2^(sPyvg30_cXncR`pcT+oMXiU-O1eL%q00YH%**4u%gXS(;72HPV?fg zn+pE7zD5jG#Eh^;?T>t(IPUlf5c{+1@<^=1%*)GikAapZ&Jp)z)vwu-4GU42(SL8? z&_Ld_ZK(6v^y6w2Wu`)%QmOkiOn=_%AHn4T9?XRZ@Xh>`B5BA{kPp@g&^t4;hwB<@&D=sK_3j)h4X;WD> z4V#|CraLA!3Lb{Q9_Q}EP=A^;yUO8X50V5L%_=oxGFGFjf2Z}Qk$PB@ zP4LNMFAO{&zQ^v_nCA1ott2CE+F2+K<5Z}Ub(}#yGbAj08p6ElJB-G0m0RxiH;i@U zQ@thv8lx4Q1af18JD1|Z# zb9=&3RubqHsh}c`kLqQ|!!i|Y$RrG`7-rRlTyFGL%|m&mG0{{?_$iY|TM_Nz?aGVK zJA8E6*~wSP)^WFAWtz0K z8H+Qw_q>0r22osLwE?|2u#nR`CJe9-uJPScfEqEta-|5cyic6jY;+$An=Eok9io(! z5Pa%O7W(YcKO@_V$23BY`Ld!xZd>=r`jRnC+f$-g@BAbz^p0QD9`9k~&dGxHF3@9X zACIH;*<|oSlvs@+~;jWLD%`HiGCM4{tk>o;6GHgBnT$COQUlF>Drnuf`=9J$4meuznh9AH6@P~ z!QAuCN5S+wqru8uDZv#G92y*ElR96;b3T(y={|R|FSUKUA;fRYyORZt9pU!AvHh(6 z$P{qMmRr;snqTlsQBdRI#jHTtZk(^T9fB;TEi5kAeQ|vXuon_9!Av>u?t(_`NZ9oh zBrUk0s9|VFO$nlC8}Y2V?1;(x+ zNxFNAwlld0@U2W=4}o_a;dxsg?~UC9xOygll#-xtzpgcy!vcCocqI(r9|u|PTtDXp zv&XnMRiiW*X4B{ViaEIbtnCVd5k~{Wevo(bmYsjI9%PQwxws*Eesa+Go2k3Ah3x3=-d=66&Nh8I7*k)D;RY22^SPjux(tLSU(cFAIJ;ZWiD>O>?| z#(Nae<-#-Ce0dws0!8_r036wM5RUuV7gF70ZRIZJ)uG$T$^>_*9GJ1YE;S|zy(6&` zxxx2&=(3=1kn=H|0+x^$yV=e56pFQFB5|M4QHxC%awV`c$}F^R4Ns;Qr~W;d<4a=-2j|p9yAyOREL~$vFJ!>_NoX@8A}l}1E|{d@|;OA zpD6{80c}2NR`_8Kq6mOEt$lDmuZa4hRh9-#s*s$bU0X8UH8gL&=!WlRWkM&q59izk z8jJgbuLl9_5-#7y78**&q%h=tw58iE%6QeVCZ3f!-7q`gBX#EL5d5dp5Di2m2;1OO zF72T$6Q4vRLW8mX1EMr{7eJl3ER2uaOR{MSZ-<_Qr$Mp)hQNfF%3W-|ScWk`1K_#! zDc($ABR@9j^POs{t~oP1qe07wZTbc1*UEyzbzDHHVi7OjVBCjVHgQpcZzpHFQ_|;WF&s%`@EA;5@ zKvmz!(J1Li>K*Zo-#+q+<2z%eR`1rY(9_ZMgZ2%X<3r>3_=es@?YSb$7KvPPIFYw% zmxfQ*&rld`xvNjE-FUm};-n1zu0?z7q~8R{tXN)ToW`H?;PRR2H?w;&i**uR*;~@m zAQRr*X^8aM#Uxs|J-Oc1-2ZW?OCm1^y$R1f;Np=nB!g}GvjET*EmpYw;|Y!a`QnRbgN8GV#%i=+ zG2NX49%RHHX)C`HXlI3Q3Olwsk}QWM++kB$jV-{D-`lgA=lJ}-lRzEpc!n3m!1iHc zogLpaEod8)S2-^M_ktH>tj4!kbUPoZZg|@~8@6%YY=W;+=kd=K=`qAd_2Y3+8HzqE z6*iDvQMLzeRyCKOxOaCXgE1);G>nt>QZX&aS@qEZlE6u%L;ED#F)-5JK^(4Qa!b8A zy7W|b08?^Mxp{z-%S-S&zG-fcS%?xPjpwXk1iVH7SIF;d4}5R?Y#l zk_Q^1;JOXjtLXHm8*Kh%KyEgFFzwuq3GkgN`(&UMA*&vQ+M8=BliSCcOJlI#ys zQE||z1mo%hKQq-~c2j3mH?59qyv;ttU_SMbHeMm)=IfjxdzcH5WTg9jD}ZV`o#(Ui zI=yuJSo2{~!seToQ1<1&(C~;@f@;4%N4p+8mw)nO^qY12iz^o1ngP<#W2ZOO3C#w$c6-v=lvERJ(F!)MDN`nJbyMlYJaULI4 zdNX1qs>G;_qi~WF{Maz_t00@=5OmGsB9e);V3IIY9xjv5mhM1$HdDtHQ;HMxje0i}C! zbL?jG4JZKLdPC`miLH7;u4NbTrY%6+EwWEf%uNX&w(6cy?4D2v7ZwTwFHg#dHNx)O z7J#?!X2k$R>hd)?c#2Phm`Hm66F*EGR&&1pZBZV4?5_3GZSIFep_%XA^^FwUR+bgG zQ)TiTY z_k2Fg!yuuJ;Yh-iFl2>ujzp5ik^f!%C(3EJzd?VNUNaE?=F$IR8RLA!&4cUPO5KknT?HW?8bx@$)ReLTqn{V#Ms@xvMYK`Ha)w zP4C>x|CqE9(P#S!YezC6s2c6PaaC#kwxC?F@2^F^<*=M-mtO6Ky=*!`z z_)bNZ2IL|N)u5}6(f%x8sT>lBL;{YH#*%0V0bhj%5SHS=mi)FNjFLud8=S)?<}19( zeRqRWqI+^Z_eGxKg)97^Mw+DbwB?>6@=;uU;n&2xY5n`Iuh~e;osI}IFQXOWj znSgITB6=84W+KRplL3Z-on4%QWHuZYu(z-ov`)J)S8mP|stkj|PniMqIM69ojaJlC1e@=?`$K02N zo4c&!qo#a)sV(WRj?@T&`Wrcerczg?=4|F~MnLl1j!_dqU(hVU;D!1@yS4JZ3Dt9X zT2(M(p@&X!i6IlfyKD>n9zD5=(kyAGD<`q%7+WrKK zA?=)B%3D?Xwdck*m`WAGo-onp>MQ@Khu<;D;H89wLRTYuvsRG+S^&?4yMcfGM~f56 zVlji$SO7CvI&>F7e~LHhQAR!bu6=e}{+tG7tJHEYIR#c(X2AwG&*hBUojwTK{sqsp zod;A6cxt#lJW`<>U*IK%4dztDx8?{S9%D*4|IZ~|+>4RRThNnZLI zS-$6{9hv9=egKOoys=r**_s4TyvvBCa*xKQ3v}!00-s)@OCbC3y6l>6Y zkEn~hH=>OXWg84HmFpbDdIfGbVOsfcF2LrF3kQ8wN@sIL0hGWSaJ+TBz(gBUTd3}@ za)mvFbyQhGI%H=7M>l=J28J+xoMlqgklU%U?~Jin8c+Ql@}(SP$8b%|{f@x3a8n5Z zcK@7Vho!v}O*oeF&+q7qK{DtYY%oPo4^d6|#pam#Nc^}2BetLHXvAU`2R-R{qhInl zC!j&+nFFRPXQ)PAe%`%;zpCXrmT+OJJ8k?`--@M>Kq{55YGXu%CN1+){Ezc@_bnC= zRoe^EUYkP$e+mmAXtuilZG((KQoQE&;r14`ft5Lef~&R2-WCDeE}|!(z|Jzq&}u!% z>#dyop~@xf04mvrRH}@{5=wa+13aUHZ#is7L`5ZSS=&Y%&L?rYRy{+Z&4A;|G+i)A zew+QqGpemHXz#Eoa}}hfxN#P?1cFzb5liG-zjcJrAMX|wD_^v9itCwVpqzW?tCxEf zV)nl-Y+h*FpX466ZE4b*kaZMY*ka^`E`YAYt_>jkRNB6fe0?GTz0DS-70)mPsAPP=mf(CG{p+k@L#HiO4nt4!qjveO&BsQ^3A%}m{kuU*1h zA03R%E4bRO#*nd~N7Ziu7}dQjM3BteuR_9vdj%}!eoh>WvF4aA^XZ(xf-kROBdu4= zqM_J{$08EUM*|2&$P<7#3srj$)?sSXG>1cd_4Q)Ct_#(ZmtN)lEBHy?r^eUW?<|^w zQS@KX)zltJa>Wn_S99|`b(a1tICw0t_xkoS`$CUK@9-<0 zXngaAok!l{)R+OdZcd$3(|Cx(>OC6n{L%?kne&#r*kShzq}xRXy58f=8^Nl_02L7W z?$|O;ZvWd$noOB)gk0cakyV?aqss*&T)5HWB>z~TTI!e1fu1u7ci+*Y3=)PM$ull3 zb}ZB5ElcCk(>=5`W6-nT>QyL6PqKAHdf2%#1*Zo@Ex8)_6*zv0@M)}l zcX&veJV@;RQdYWl@6yZS;-2EKrA^TeFL|3h6GEbntpifYwl5z<HG~^jw~V z9QvB3|N4bm#)CtBTN`hz$(6}#Q)_uDH3zI(AZ==rRs4@nb>>Y;!2Rex)UMby-B&=8 z0*vfAFsf`zK1mib6EZtlc1uBaL~B=&zUmX6y+_hraWX`AD#4ncKJTZmG2E`#;m`W2 z%4LkDvL@B9UYCm^Eo>&#?qp1E)NPbDhWo?6S!!mXXyn7fgdt-x4Diub$gIN%#{WM6 z>jo{hSCJ6|(*MODL=K0i(gDv3? zTfF^RLaTWvwUFTyE375>DAz8bKIODaM+1V(iY(9iv}^m}cbmxI{iOZ0km{xE4|cP4 zD!lshqxHIV2VBF6U%%x7#Ba+biJL`HD>be~af(fc>iidBhPsFUx`+VQHC;h3H5~T~6d<8s_US}mMCUTz|?XP0N zloGVKzR#8|ap{_;-rQZNtVgNIJU4bbH#qgUzr!|gYAnYi3&+^M?DhYJuwH9{zi)9|`8mw!wyQ zR49CRSQwkA9tyt3t43T(NH-V=55*Ebva95=CPAlSZHI-j1#q2G{4@ll?8pJ1b5sZs zxLMF8!00C2grao=B&u)pX8@+AeTxw_3y|Ut($2%1TsUJpi*+Y-e4{I3-FE)PuT)VO z4hCLRhZ9AG_yN95MZLD4v>yz}Wi*LpTgn+~P@? zSehlz>(QxVN*CdTQ7+~2ZuVWK{6ObOH&D2%5^Co3@W*Ab`38CHr;7F2J}b1E3LdjC ziq$w1bP&ARP1@cY>kpnY_=yuXH6*r5@T&QyOM_&udj)=K2PF&TK^Cd*#vWUlrJ(h- zCHpZ2@LY>NPZ_S+jTFd~d`I$QJ&B*zP_Jn(2S(K25s(9y2*>)ff^*}c_=E{Ou}s!A zRYM)CDcN#he5(X$?P-aBZqfdlq zz^$GJnO`Pwv#V@3k4HK6Wi&b9-Sghvn0j#)MJr(wWbN)Q zc48Ll?sO|r1rzIPbrkf?OHaw5o}O4M)LRpeWO%Np&8|ixgyPk9(T6|B!jW|6k&bMF zA&cA@M0k0<}y#P{Zy6GX8)%gT*&m%w~5;&{HfI^px?Web10*?K`^vN z{4vR(&;zMii$Cv9R0R7~5k{D4M8`tvpxh80r%EE6L$_kS!7`TTRSm9Nsal7rvIp$qO2K%IW7tb^!o^&40&tc5 zXDXo>brRf=TPubBD)ZTy&v>;Bt1YHN|kqLzO|8J21cUnn!(rD0|@?OW~2j#RR2a zA3D3WVDr;jTCrPw|zyZgyrLMm7FKF;C7_J5Rif1bGpiZYUJA zAH)N527<}7*b~JEA_;SmYk@lhUkeYs4t^oKbjr4w{oMR8qP=*lzMxKXQ(GMj0U~y< znBa2q_Z_&2I;m0crKa@L7guD1Hai%^65or)DcQ*-yORe##>l7Q1$ zI)s`{B)azpVaH)~4ubgs&;8iNm>jWGM^bL$rEE2fYK_gmn_JDwAz z0{b)(l`KDhASLj~2Ce@TifL2sHgrm ztGy>V?YORlVX=sZ610*|&g3JR2JZ2;eXbLP*OSM~nwVWDRk5M>v&_qrAn@T)<<`Pv^FYB}bI$r78_>O?}D50Wz?U)g>qw0-` z#2l`x@HF6*9d6`NsfCnc|20r|x`97u}dXR+&B!ds~;V4k2kUF;E z5cuaHSV2(YigLH(zJX8@r3Dj^T=Z2)I`k3YBZ~?ic-5AVNv5(a+EbwQS{=QSa3cWr z8w@G%!e%Ui=}&9#Lf@-*@9>5Oz8l)l#cHv-WNP-#WCBi5k0hANp~cQzcTKH$VZ&tP zXyJRL|270YDVGKBG3%jJoKJoeIITSL5zgNWOGOI5E|?Uq@O;%hlJ-)rNHM1-2tjfa zQM)AA@0oB3Ldae#az2mtEfUs+Pch!Y*16mE#dmKu6%`)2$Q0 zwODsvdf+Y8Bz!y)tI@$BHhn=N27X=pEZM6I1&Fg_&!E{nA1o&tB;EEB-ONqyPr)Q!Ytu{_XQwQv-YXc|R0x-iHQ>yLt^LYPT{W;9^e;B#|N=Imy|S=K@y zexM`pyU0T{4n7Mm$%1nw%5lXn--q1$Q{QkJXvKf#?VTuGjhpZZO)AlUh+#ktn%I!0 z;mm@!<%OCjzZ%K#iC2+%H(8%>0gXv{r5I&5Idsr(z?<-wZ_$umuq2yJ}!zgJ(*c{W2WNYsxDV)*_oy#bei` zNVnQHAGnN}`vNCGX>7}%=163t)5-&@uDFLxI3=+Bo;YSSw~aG-%dxfm_2v8Php^y+ zlqr_7VPy_H%s)hBHmw#+8ow`d*S3lmT^Mdmv;w#V(`!K9`sX1o^@C^a8n+=RGUORT zBue;l0eJ8*j$-?+gg9sop{0!C|OPvd9Y`;oq@)1N+h80B{aI%PQqfQBV%GcdKVEi%>0- z1ydlroTU=c518$IuLyQRrjPSu)0uFO%bu6iobbxCv7KL&^h@yHN8*B*(@O;ElWa|G z%1#^A7}l`+bpfe$?Q`YicR{9t*Y2EuzFdFk4#C((nPSl(YlW*h9SFFjZZEuOx`EtW zp?%d4my-iO*{IPR-DK8(BH-AMmslYpH}C3<3YmkcUiVruI?&ER5h~ zVe>hhl~ZLVaMGnh1Ixbe=Q6fjy>2FhlHT2|qZxrt_7;h)@)SL9nfTnHf=jkuZqbl3 zE3EUZ81)=etR00a6G#~0pxspB{gA8yWk<+>VSmEPa_OmY$h^J2GS@QD%f!S~UFlIX zO43(bSHc`~A@xR;Cnf37utCM)-PX{=wfd@4_~5EUsFt_TgLlr&KvdamloJV7+ZVUA z!x}6Vx2DU5`?<-Bb7lo!FT){ItgtK_w4V^h!$}mq`CjvZN9@`@rU^*u+J5p|AC^UF z+IO|D`amQU{+@Kb3fVR1;XLw~Feet@FjrR6Y2EsMBsw6$uLfG@SbP{camQY<`~08+ z)z`FV9d@p%1^kRK>DaKIFzf=u6{mpmNL!tvDcG(FU@C5M zXR69PEA_seCP4>y+wGzB82FSQk6`e&eD}bl#`VcK=r$Bvs)&7LN;P$EDd21$B|)8N zqFI(UlxuqoD6d=_`tw_58)y^2#Z9@`Mo#RKp-Ze0esErLcPbLjYni}QmB3$AD1cRg zWiW9qWAM7+hGx_E3WBt(#TOp4ZozCh!nhsrYa2B$ov6!yhGL>i9q!c2r6g4r8}VzMytfYKuNVKzLx3FN z*j-B*K!&h~kl73A8uN0~PQ$(86ey)ob{l@k22Q8K;bl#%fqLEDVi8(th+}SS4lu^^ z#vHi84CerUe|kr)bk1QD?{?uiPx#22jUoWNy2f^sk!0TdbFli zvm8h-<7kck<<}9Zb&@GwORVpPn_wafO!VU%6G~8Pf~(usl?s8Od*Ae`y-xyt+cvs362uCGkMz=s^6cjbOw?` z9lx<0s##BK7P^`a6zF(Fl^J&03cE|{rVjjV}Q|8u{s26}&EG*d{itONw zD{kjR1aOD(M)S#(qRqjENCUN~0nPJjs>SxEF_P{e3byD1BqAfUJ4ZDFE}q88XzA5O zeb9s3_*wj-()ZDtIc@BR5{z|#z3XHpbo-HLWU_a(x2np-_R`x2BSQ;=JA61;E>a0J zC|Y_saryMc`)krA88t705SF|+PCc17d3n`G?3NzsBdNZp`b9Ap6;i_hh(%?lUN-5K zlJtW0*v@gEMqjz_Awazja56hGJx>q%AVdF1mtEEq;uRw8LE=84Fqcn(T5P+QKwssb zu7%wJItnj9ri-QOr!AFL21JE1RNvAG?~CKO(8}F3a*&kVjz6YCpvj@JtpNJ(U~tI;WUmiD~zXOjR}T)-wcm=i8Jz3oV#-N z2iQhrq7)gcaj7QSMT&5a6)hUJ672;m!N)_cOmO|?(9j$-${H8S;jB@%gY|}Iqzi=%o5hqS7~|G6668owe2nlX3EY-a&BfEqDkiGmVX`&8+VK74R#B*lDh;e2x3K`O zUfY#q=s*wk`4l~B|ErK=w@Df$HKUBSnEIKj-IR=sxHVyGgNI1WSAQ&_qIua(j=B{9 z{H0P3!+6kNMZ=FheXu^#bfFUfb*pPsVzvy$LJ)F(g0wU|0-qoS*4v)KaHM~I{Zy~c z!skrV8T$EVp7$@GMn`LEquPaRE~Sk+DrL^+?VkT1^eC8oR&2deiX;WHQxb+(zi7k``4ETN_ z$xw52`n&vCf6q%J!?M^LBETL`JaVy#iKG2DH}Iuid-asB{m}_xB-9axUoy)aBa4(} ztSb;QN1vJ6C=GLU*~7#*zu*4zDuXd@rC1p+8aBQDu}FS?tswqWK!WE(7e3e*gQW>6 zZ}56uVOo%z!bfgU@HZi-pJ>19rw1CJ~wLZD&1hJ-KVjfaC}0@da_E zUa+~3^=HK$7;q}W546C@cqp`Hq|Mm!6={;KA*%Hh+S-KK)_#5MxlChZJ z6CizK#?n>5-bhVydCagYe6-)$lIfWbVbCRf&6J(@rM=aBv^B$FJy@YR+g(8D_qI48 z^IEjjcPX8~clXt~A_+olD|MgH=f-v~#Bp75O1wqZcywIx44n#`U3d42q{5#BR=_4l zfTVvC*mdg9vmcB#W%%uk65JHv zOEA!ZMf6jeAM;KTWadD}#%093h({hdJl2raxq@E3=j~lkx2Y?EX_;oNx#;LPb4^?F zY2ELUcmyE|!ru@e7=)60VoFZff9+=eWR{DjMu)McOP&`GI~Zg?z4Y$0@QWk}@8WK3 zce~ghD8`>iE%9qw>@Gtn0zT;+=yfZcU(?+*-Ivtp0N>BkJ@ZRUA@Qjy83w>72km9v z52Apn;<5AWd^oqvzxQB%#^gEB0ef(5H*P^+rwCy#z+Up#z|)}9LfUYuwUWuzWXX0} zY>;f8sl=P|;eBfxX$a1v+_@l%rkPo>A4i zMcwjRNWlpvMj?`BaF2NxcXxZKT3nn4l^^tMx-4n(s$o@NdozlDm{tsBlkt5Txbh=4 z!W|xeC>oge23%BllR(EJ|2__Z?>DXwb7`q{&I-q-U(Iiu&d9evXU2j4BZ>2KIoK=I z<-wKk4Tv}e+%^6p*P=!h!`%2%xTcJ)9p%H7mU0jWN4~spF1iQdg>fku!o-ayTy@t1 zb!-6oo!J@-YeIc7r2T4hMXoLcw~}vpU`i`t6h%D@V!mUCrQHTs z{D55cHfd^)-<~M6G624cq5n%`{(m38>Y^8=yff%?e0}W!(bxP(!+A;`pQlU(BSwoH Wf^~SJsu=&eW@2o9ve*zG{eJ*Y7ZKY4 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/doc/pics/akonadi_overview_uml.ps b/kdepim-runtime/doc/pics/akonadi_overview_uml.ps new file mode 100644 index 00000000..91f3cc0a --- /dev/null +++ b/kdepim-runtime/doc/pics/akonadi_overview_uml.ps @@ -0,0 +1,2551 @@ +%!PS-Adobe-3.0 +%%BeginProcSet: reencode 1.0 0 +/RE +{ findfont begin + currentdict dup length dict begin + {1 index /FID ne {def} {pop pop} ifelse} forall + /FontName exch def dup length 0 ne + { /Encoding Encoding 256 array copy def + 0 exch + { dup type /nametype eq + { Encoding 2 index 2 index put + pop 1 add + } + { exch pop + } ifelse + } forall + } if pop + currentdict dup end end + /FontName get exch definefont pop + } bind def +%%EndProcSet: reencode 1.0 0 +%%BeginProcSet: ellipse 1.0 0 +/ellipsedict 8 dict def +ellipsedict /mtrx matrix put +/ellipse { ellipsedict begin +/endangle exch def +/startangle exch def +/yrad exch def +/xrad exch def +/y exch def +/x exch def +/savematrix mtrx currentmatrix def +x y translate +xrad yrad scale +0 0 1 0 360 arc +savematrix setmatrix end } def +%%EndProcSet: ellipse 1.0 0 +%%EndProlog +%%BeginSetup +/isolatin1encoding +[ 32 /space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quoteright + /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash /zero /one + /two /three /four /five /six /seven /eight /nine /colon /semicolon + /less /equal /greater /question /at /A /B /C /D /E + /F /G /H /I /J /K /L /M /N /O + /P /Q /R /S /T /U /V /W /X /Y + /Z /bracketleft /backslash /bracketright /asciicircum /underscore /quoteleft /a /b /c + /d /e /f /g /h /i /j /k /l /m + /n /o /p /q /r /s /t /u /v /w + /x /y /z /braceleft /bar /braceright /asciitilde /.notdef /.notdef /.notdef + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /space /exclamdown /cent /sterling /currency /yen /brokenbar /section /dieresis /copyright + /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron /degree /plusminus /twosuperior /threesuperior + /acute /mu /paragraph /periodcentered /cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf + /threequarters /questiondown /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla + /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis /Eth /Ntilde + /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply /Oslash /Ugrave /Uacute /Ucircumflex + /Udieresis /Yacute /Thorn /germandbls /agrave /aacute /acircumflex /atilde /adieresis /aring + /ae /ccedilla /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis + /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide /oslash /ugrave + /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] def +%%EndSetup +1 setlinewidth +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +12 scalefont setfont +0.0 0.0 0.0 setrgbcolor +32 810 translate +0.625 0.625 scale +-20 -12 translate +newpath +20 -12 moveto +855 0 rlineto +0 -1195 rlineto +-855 0 rlineto +closepath +clip +1.0 1.0 1.0 setrgbcolor +newpath +24 -640 moveto +798 0 rlineto +0 -20 rlineto +-798 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -640 moveto +798 0 rlineto +0 -20 rlineto +-798 0 rlineto +closepath +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +28 -653 moveto +(server) show +1.0 1.0 1.0 setrgbcolor +newpath +24 -660 moveto +847 0 rlineto +0 -459 rlineto +-847 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -660 moveto +847 0 rlineto +0 -459 rlineto +-847 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +392 -920 moveto +390 0 rlineto +0 -20 rlineto +-390 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +392 -920 moveto +390 0 rlineto +0 -20 rlineto +-390 0 rlineto +closepath +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +396 -933 moveto +(storage) show +1.0 1.0 1.0 setrgbcolor +newpath +392 -940 moveto +439 0 rlineto +0 -147 rlineto +-439 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +392 -940 moveto +439 0 rlineto +0 -147 rlineto +-439 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +488 -1032 moveto +95 0 rlineto +0 -45 rlineto +-95 0 rlineto +closepath +eofill +newpath +488 -1032 moveto +95 0 rlineto +0 -1 rlineto +-95 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +488 -1032 moveto +96 0 rlineto +0 -2 rlineto +-96 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +488 -1034 moveto +95 0 rlineto +0 -19 rlineto +-95 0 rlineto +closepath +eofill +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +0.0 0.0 0.0 setrgbcolor +512 -1047 moveto +(DataStore) show +newpath +488 -1032 moveto +96 0 rlineto +0 -46 rlineto +-96 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +408 -944 moveto +108 0 rlineto +0 -21 rlineto +-108 0 rlineto +closepath +eofill +newpath +408 -944 moveto +108 0 rlineto +0 -1 rlineto +-108 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +408 -944 moveto +109 0 rlineto +0 -2 rlineto +-109 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +408 -946 moveto +108 0 rlineto +0 -19 rlineto +-108 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +412 -959 moveto +(NotificationCollector) show +newpath +408 -944 moveto +109 0 rlineto +0 -22 rlineto +-109 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +736 -1048 moveto +67 0 rlineto +0 -21 rlineto +-67 0 rlineto +closepath +eofill +newpath +736 -1048 moveto +67 0 rlineto +0 -1 rlineto +-67 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +736 -1048 moveto +68 0 rlineto +0 -2 rlineto +-68 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +736 -1050 moveto +67 0 rlineto +0 -19 rlineto +-67 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +740 -1063 moveto +(DbInitializer) show +newpath +736 -1048 moveto +68 0 rlineto +0 -22 rlineto +-68 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +520 -1032 moveto +520 -984 lineto +stroke +newpath +520 -984 moveto +517 -966 lineto +stroke +[] 0 setdash +newpath +525 -976 moveto +517 -966 lineto +stroke +newpath +512 -978 moveto +517 -966 lineto +stroke +545 -1005 moveto +(send changes) show +1.0 1.0 1.0 setrgbcolor +newpath +656 -1144 moveto +88 0 rlineto +0 -59 rlineto +-88 0 rlineto +closepath +eofill +newpath +656 -1144 moveto +88 0 rlineto +0 -14 rlineto +-88 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +656 -1144 moveto +89 0 rlineto +0 -15 rlineto +-89 0 rlineto +closepath +stroke +662 -1158 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +656 -1159 moveto +88 0 rlineto +0 -19 rlineto +-88 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +660 -1172 moveto +(MySQL Database) show +1.0 1.0 1.0 setrgbcolor +newpath +657 -1180 moveto +87 0 rlineto +0 -22 rlineto +-87 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +657 -1180 moveto +745 -1180 lineto +stroke +newpath +656 -1144 moveto +89 0 rlineto +0 -60 rlineto +-89 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +737 -1070 moveto +737 -1144 lineto +stroke +[] 0 setdash +newpath +730 -1132 moveto +737 -1144 lineto +stroke +newpath +744 -1132 moveto +737 -1144 lineto +stroke +[5.0 5.0 ] 0 setdash +newpath +584 -1078 moveto +656 -1144 lineto +stroke +[] 0 setdash +newpath +642 -1141 moveto +656 -1144 lineto +stroke +newpath +651 -1130 moveto +656 -1144 lineto +stroke +0.78431374 1.0 1.0 setrgbcolor +newpath +40 -864 moveto +94 0 rlineto +0 -21 rlineto +-94 0 rlineto +closepath +eofill +newpath +40 -864 moveto +94 0 rlineto +0 -1 rlineto +-94 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +40 -864 moveto +95 0 rlineto +0 -2 rlineto +-95 0 rlineto +closepath +stroke +0.78431374 1.0 1.0 setrgbcolor +newpath +40 -866 moveto +94 0 rlineto +0 -19 rlineto +-94 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +44 -879 moveto +(RecourceManager) show +newpath +40 -864 moveto +95 0 rlineto +0 -22 rlineto +-95 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +584 -1056 moveto +736 -1056 lineto +stroke +[] 0 setdash +newpath +724 -1063 moveto +736 -1056 lineto +stroke +newpath +724 -1049 moveto +736 -1056 lineto +stroke +615 -1037 moveto +(build db) show +1.0 1.0 1.0 setrgbcolor +newpath +696 -720 moveto +102 0 rlineto +0 -20 rlineto +-102 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +696 -720 moveto +102 0 rlineto +0 -20 rlineto +-102 0 rlineto +closepath +stroke +700 -733 moveto +(handler) show +1.0 1.0 1.0 setrgbcolor +newpath +696 -740 moveto +151 0 rlineto +0 -163 rlineto +-151 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +696 -740 moveto +151 0 rlineto +0 -163 rlineto +-151 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +704 -744 moveto +59 0 rlineto +0 -21 rlineto +-59 0 rlineto +closepath +eofill +newpath +704 -744 moveto +59 0 rlineto +0 -1 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +704 -744 moveto +60 0 rlineto +0 -2 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +704 -746 moveto +59 0 rlineto +0 -19 rlineto +-59 0 rlineto +closepath +eofill +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +0.0 0.0 0.0 setrgbcolor +715 -759 moveto +(Handler) show +newpath +704 -744 moveto +60 0 rlineto +0 -22 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +736 -872 moveto +59 0 rlineto +0 -21 rlineto +-59 0 rlineto +closepath +eofill +newpath +736 -872 moveto +59 0 rlineto +0 -1 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +736 -872 moveto +60 0 rlineto +0 -2 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +736 -874 moveto +59 0 rlineto +0 -19 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +751 -887 moveto +(Status) show +newpath +736 -872 moveto +60 0 rlineto +0 -22 rlineto +-60 0 rlineto +closepath +stroke +newpath +744 -872 moveto +744 -766 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +744 -766 moveto +751 -778 lineto +737 -778 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +744 -766 moveto +751 -778 lineto +737 -778 lineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +784 -800 moveto +59 0 rlineto +0 -21 rlineto +-59 0 rlineto +closepath +eofill +newpath +784 -800 moveto +59 0 rlineto +0 -1 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +784 -800 moveto +60 0 rlineto +0 -2 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +784 -802 moveto +59 0 rlineto +0 -19 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +795 -815 moveto +(Append) show +newpath +784 -800 moveto +60 0 rlineto +0 -22 rlineto +-60 0 rlineto +closepath +stroke +newpath +784 -800 moveto +764 -766 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +764 -766 moveto +776 -772 lineto +764 -779 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +764 -766 moveto +776 -772 lineto +764 -779 lineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +768 -840 moveto +59 0 rlineto +0 -21 rlineto +-59 0 rlineto +closepath +eofill +newpath +768 -840 moveto +59 0 rlineto +0 -1 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +768 -840 moveto +60 0 rlineto +0 -2 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +768 -842 moveto +59 0 rlineto +0 -19 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +782 -855 moveto +(Delete) show +newpath +768 -840 moveto +60 0 rlineto +0 -22 rlineto +-60 0 rlineto +closepath +stroke +newpath +768 -840 moveto +764 -766 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +764 -766 moveto +771 -777 lineto +757 -778 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +764 -766 moveto +771 -777 lineto +757 -778 lineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +536 -704 moveto +102 0 rlineto +0 -36 rlineto +-102 0 rlineto +closepath +eofill +newpath +536 -704 moveto +102 0 rlineto +0 -14 rlineto +-102 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +536 -704 moveto +103 0 rlineto +0 -15 rlineto +-103 0 rlineto +closepath +stroke +555 -718 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +536 -719 moveto +102 0 rlineto +0 -19 rlineto +-102 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +540 -732 moveto +(AkonadiConnection) show +newpath +536 -704 moveto +103 0 rlineto +0 -37 rlineto +-103 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +568 -840 moveto +75 0 rlineto +0 -36 rlineto +-75 0 rlineto +closepath +eofill +newpath +568 -840 moveto +75 0 rlineto +0 -14 rlineto +-75 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +568 -840 moveto +76 0 rlineto +0 -15 rlineto +-76 0 rlineto +closepath +stroke +574 -854 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +568 -855 moveto +75 0 rlineto +0 -19 rlineto +-75 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +572 -868 moveto +(CacheCleaner) show +newpath +568 -840 moveto +76 0 rlineto +0 -37 rlineto +-76 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +639 -728 moveto +696 -728 lineto +stroke +[] 0 setdash +newpath +684 -735 moveto +696 -728 lineto +stroke +newpath +684 -721 moveto +696 -728 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +368 -776 moveto +183 0 rlineto +0 -34 rlineto +-183 0 rlineto +closepath +eofill +newpath +368 -776 moveto +183 0 rlineto +0 -14 rlineto +-183 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +368 -776 moveto +184 0 rlineto +0 -15 rlineto +-184 0 rlineto +closepath +stroke +420 -790 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +368 -791 moveto +183 0 rlineto +0 -19 rlineto +-183 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +425 -804 moveto +(AkonadiServer) show +newpath +368 -776 moveto +184 0 rlineto +0 -35 rlineto +-184 0 rlineto +closepath +stroke +newpath +368 -811 moveto +135 -864 lineto +stroke +newpath +368 -811 moveto +359 -818 lineto +349 -815 lineto +357 -808 lineto +closepath +eofill +newpath +368 -811 moveto +359 -818 lineto +349 -815 lineto +357 -808 lineto +closepath +stroke +353 -833 moveto +(1) show +149 -879 moveto +(1) show +newpath +552 -811 moveto +568 -840 lineto +stroke +newpath +552 -811 moveto +561 -817 lineto +561 -828 lineto +552 -822 lineto +closepath +eofill +newpath +552 -811 moveto +561 -817 lineto +561 -828 lineto +552 -822 lineto +closepath +stroke +569 -822 moveto +(1) show +570 -824 moveto +(1) show +newpath +536 -811 moveto +536 -1032 lineto +stroke +newpath +536 -811 moveto +541 -821 lineto +536 -831 lineto +531 -821 lineto +closepath +eofill +newpath +536 -811 moveto +541 -821 lineto +536 -831 lineto +531 -821 lineto +closepath +stroke +548 -831 moveto +(1) show +548 -1022 moveto +(1) show +1.0 1.0 0.78431374 setrgbcolor +newpath +208 -760 moveto +107 0 rlineto +0 -21 rlineto +-107 0 rlineto +closepath +eofill +newpath +208 -760 moveto +107 0 rlineto +0 -1 rlineto +-107 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +208 -760 moveto +108 0 rlineto +0 -2 rlineto +-108 0 rlineto +closepath +stroke +1.0 1.0 0.78431374 setrgbcolor +newpath +208 -762 moveto +107 0 rlineto +0 -19 rlineto +-107 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +212 -775 moveto +(NotificationManager) show +newpath +208 -760 moveto +108 0 rlineto +0 -22 rlineto +-108 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +336 -712 moveto +108 0 rlineto +0 -21 rlineto +-108 0 rlineto +closepath +eofill +newpath +336 -712 moveto +108 0 rlineto +0 -1 rlineto +-108 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +336 -712 moveto +109 0 rlineto +0 -2 rlineto +-109 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +336 -714 moveto +108 0 rlineto +0 -19 rlineto +-108 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +340 -727 moveto +(CachePolicyManager) show +newpath +336 -712 moveto +109 0 rlineto +0 -22 rlineto +-109 0 rlineto +closepath +stroke +newpath +376 -776 moveto +376 -734 lineto +stroke +newpath +376 -776 moveto +371 -766 lineto +376 -756 lineto +381 -766 lineto +closepath +eofill +newpath +376 -776 moveto +371 -766 lineto +376 -756 lineto +381 -766 lineto +closepath +stroke +358 -766 moveto +(1) show +358 -754 moveto +(1) show +newpath +368 -782 moveto +316 -782 lineto +stroke +newpath +368 -782 moveto +358 -787 lineto +348 -782 lineto +358 -777 lineto +closepath +eofill +newpath +368 -782 moveto +358 -787 lineto +348 -782 lineto +358 -777 lineto +closepath +stroke +350 -802 moveto +(1) show +328 -802 moveto +(1) show +[5.0 5.0 ] 0 setdash +newpath +135 -886 moveto +488 -1032 lineto +stroke +[] 0 setdash +newpath +474 -1033 moveto +488 -1032 lineto +stroke +newpath +479 -1020 moveto +488 -1032 lineto +stroke +285 -942 moveto +(keep DS up to date) show +[5.0 5.0 ] 0 setdash +newpath +544 -776 moveto +544 -741 lineto +stroke +[] 0 setdash +newpath +551 -753 moveto +544 -741 lineto +stroke +newpath +537 -753 moveto +544 -741 lineto +stroke +575 -765 moveto +(use) show +1.0 1.0 1.0 setrgbcolor +newpath +24 -496 moveto +798 0 rlineto +0 -20 rlineto +-798 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -496 moveto +798 0 rlineto +0 -20 rlineto +-798 0 rlineto +closepath +stroke +28 -509 moveto +(server/control) show +1.0 1.0 1.0 setrgbcolor +newpath +24 -516 moveto +847 0 rlineto +0 -91 rlineto +-847 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -516 moveto +847 0 rlineto +0 -91 rlineto +-847 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +0.78431374 1.0 1.0 setrgbcolor +newpath +104 -576 moveto +79 0 rlineto +0 -21 rlineto +-79 0 rlineto +closepath +eofill +newpath +104 -576 moveto +79 0 rlineto +0 -1 rlineto +-79 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +104 -576 moveto +80 0 rlineto +0 -2 rlineto +-80 0 rlineto +closepath +stroke +0.78431374 1.0 1.0 setrgbcolor +newpath +104 -578 moveto +79 0 rlineto +0 -19 rlineto +-79 0 rlineto +closepath +eofill +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +0.0 0.0 0.0 setrgbcolor +108 -591 moveto +(AgentManager) show +newpath +104 -576 moveto +80 0 rlineto +0 -22 rlineto +-80 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +304 -576 moveto +82 0 rlineto +0 -21 rlineto +-82 0 rlineto +closepath +eofill +newpath +304 -576 moveto +82 0 rlineto +0 -1 rlineto +-82 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +304 -576 moveto +83 0 rlineto +0 -2 rlineto +-83 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +304 -578 moveto +82 0 rlineto +0 -19 rlineto +-82 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +308 -591 moveto +(ProfileManager) show +newpath +304 -576 moveto +83 0 rlineto +0 -22 rlineto +-83 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +496 -576 moveto +122 0 rlineto +0 -21 rlineto +-122 0 rlineto +closepath +eofill +newpath +496 -576 moveto +122 0 rlineto +0 -1 rlineto +-122 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +496 -576 moveto +123 0 rlineto +0 -2 rlineto +-123 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +496 -578 moveto +122 0 rlineto +0 -19 rlineto +-122 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +500 -591 moveto +(SearchProviderManager) show +newpath +496 -576 moveto +123 0 rlineto +0 -22 rlineto +-123 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +632 -640 moveto +81 0 rlineto +0 -40 rlineto +-81 0 rlineto +closepath +eofill +newpath +632 -640 moveto +81 0 rlineto +0 -14 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +632 -640 moveto +82 0 rlineto +0 -15 rlineto +-82 0 rlineto +closepath +stroke +635 -654 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +632 -655 moveto +81 0 rlineto +0 -19 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +660 -668 moveto +(IMAP) show +newpath +632 -640 moveto +82 0 rlineto +0 -41 rlineto +-82 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +632 -704 moveto +632 -681 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +632 -681 moveto +639 -693 lineto +625 -693 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +632 -681 moveto +639 -693 lineto +625 -693 lineto +closepath +stroke +669 -706 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +184 -640 moveto +81 0 rlineto +0 -40 rlineto +-81 0 rlineto +closepath +eofill +newpath +184 -640 moveto +81 0 rlineto +0 -14 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +184 -640 moveto +82 0 rlineto +0 -15 rlineto +-82 0 rlineto +closepath +stroke +187 -654 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +184 -655 moveto +81 0 rlineto +0 -19 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +213 -668 moveto +(DBus) show +newpath +184 -640 moveto +82 0 rlineto +0 -41 rlineto +-82 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +258 -760 moveto +258 -681 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +258 -681 moveto +265 -693 lineto +251 -693 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +258 -681 moveto +265 -693 lineto +251 -693 lineto +closepath +stroke +295 -734 moveto +(<>) show +[5.0 5.0 ] 0 setdash +newpath +135 -864 moveto +184 -681 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +184 -681 moveto +187 -694 lineto +174 -690 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +184 -681 moveto +187 -694 lineto +174 -690 lineto +closepath +stroke +194 -781 moveto +(<>) show +[5.0 5.0 ] 0 setdash +newpath +336 -712 moveto +266 -681 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +266 -681 moveto +279 -679 lineto +274 -692 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +266 -681 moveto +279 -679 lineto +274 -692 lineto +closepath +stroke +296 -688 moveto +(<>) show +[5.0 5.0 ] 0 setdash +newpath +184 -598 moveto +184 -640 lineto +stroke +[] 0 setdash +newpath +177 -628 moveto +184 -640 lineto +stroke +newpath +191 -628 moveto +184 -640 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +336 -496 moveto +81 0 rlineto +0 -40 rlineto +-81 0 rlineto +closepath +eofill +newpath +336 -496 moveto +81 0 rlineto +0 -14 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +336 -496 moveto +82 0 rlineto +0 -15 rlineto +-82 0 rlineto +closepath +stroke +339 -510 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +336 -511 moveto +81 0 rlineto +0 -19 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +365 -524 moveto +(DBus) show +newpath +336 -496 moveto +82 0 rlineto +0 -41 rlineto +-82 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +184 -576 moveto +336 -537 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +336 -537 moveto +326 -546 lineto +322 -533 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +336 -537 moveto +326 -546 lineto +322 -533 lineto +closepath +stroke +241 -546 moveto +(<>) show +[5.0 5.0 ] 0 setdash +newpath +377 -576 moveto +377 -537 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +377 -537 moveto +384 -549 lineto +370 -549 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +377 -537 moveto +384 -549 lineto +370 -549 lineto +closepath +stroke +414 -570 moveto +(<>) show +[5.0 5.0 ] 0 setdash +newpath +496 -576 moveto +418 -537 lineto +stroke +1.0 1.0 1.0 setrgbcolor +[] 0 setdash +newpath +418 -537 moveto +431 -536 lineto +425 -548 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +418 -537 moveto +431 -536 lineto +425 -548 lineto +closepath +stroke +453 -548 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +32 -384 moveto +158 0 rlineto +0 -20 rlineto +-158 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +32 -384 moveto +158 0 rlineto +0 -20 rlineto +-158 0 rlineto +closepath +stroke +36 -397 moveto +(resources) show +1.0 1.0 1.0 setrgbcolor +newpath +32 -404 moveto +207 0 rlineto +0 -71 rlineto +-207 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +32 -404 moveto +207 0 rlineto +0 -71 rlineto +-207 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +296 -384 moveto +190 0 rlineto +0 -20 rlineto +-190 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +296 -384 moveto +190 0 rlineto +0 -20 rlineto +-190 0 rlineto +closepath +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +300 -397 moveto +(searchprovider) show +1.0 1.0 1.0 setrgbcolor +newpath +296 -404 moveto +239 0 rlineto +0 -75 rlineto +-239 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +296 -404 moveto +239 0 rlineto +0 -75 rlineto +-239 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +24 -128 moveto +798 0 rlineto +0 -20 rlineto +-798 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -128 moveto +798 0 rlineto +0 -20 rlineto +-798 0 rlineto +closepath +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +28 -141 moveto +(libakonadi) show +1.0 1.0 1.0 setrgbcolor +newpath +24 -148 moveto +847 0 rlineto +0 -203 rlineto +-847 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -148 moveto +847 0 rlineto +0 -203 rlineto +-847 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +48 -440 moveto +81 0 rlineto +0 -21 rlineto +-81 0 rlineto +closepath +eofill +newpath +48 -440 moveto +81 0 rlineto +0 -1 rlineto +-81 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +48 -440 moveto +82 0 rlineto +0 -2 rlineto +-82 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +48 -442 moveto +81 0 rlineto +0 -19 rlineto +-81 0 rlineto +closepath +eofill +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +0.0 0.0 0.0 setrgbcolor +52 -455 moveto +(VCardResource) show +newpath +48 -440 moveto +82 0 rlineto +0 -22 rlineto +-82 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +152 -440 moveto +70 0 rlineto +0 -21 rlineto +-70 0 rlineto +closepath +eofill +newpath +152 -440 moveto +70 0 rlineto +0 -1 rlineto +-70 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +152 -440 moveto +71 0 rlineto +0 -2 rlineto +-71 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +152 -442 moveto +70 0 rlineto +0 -19 rlineto +-70 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +156 -455 moveto +(ICalResource) show +newpath +152 -440 moveto +71 0 rlineto +0 -22 rlineto +-71 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +96 -288 moveto +99 0 rlineto +0 -36 rlineto +-99 0 rlineto +closepath +eofill +newpath +96 -288 moveto +99 0 rlineto +0 -14 rlineto +-99 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +96 -288 moveto +100 0 rlineto +0 -15 rlineto +-100 0 rlineto +closepath +stroke +99 -302 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +96 -303 moveto +99 0 rlineto +0 -19 rlineto +-99 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +112 -316 moveto +(ResourceBase) show +newpath +96 -288 moveto +100 0 rlineto +0 -37 rlineto +-100 0 rlineto +closepath +stroke +1.0 1.0 0.78431374 setrgbcolor +newpath +248 -288 moveto +59 0 rlineto +0 -21 rlineto +-59 0 rlineto +closepath +eofill +newpath +248 -288 moveto +59 0 rlineto +0 -1 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +248 -288 moveto +60 0 rlineto +0 -2 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 0.78431374 setrgbcolor +newpath +248 -290 moveto +59 0 rlineto +0 -19 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +259 -303 moveto +(Monitor) show +newpath +248 -288 moveto +60 0 rlineto +0 -22 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +432 -280 moveto +123 0 rlineto +0 -36 rlineto +-123 0 rlineto +closepath +eofill +newpath +432 -280 moveto +123 0 rlineto +0 -14 rlineto +-123 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +432 -280 moveto +124 0 rlineto +0 -15 rlineto +-124 0 rlineto +closepath +stroke +435 -294 moveto +(<>) show +1.0 1.0 1.0 setrgbcolor +newpath +432 -295 moveto +123 0 rlineto +0 -19 rlineto +-123 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +446 -308 moveto +(SearchProviderBase) show +newpath +432 -280 moveto +124 0 rlineto +0 -37 rlineto +-124 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +256 -310 moveto +256 -640 lineto +stroke +[] 0 setdash +newpath +249 -628 moveto +256 -640 lineto +stroke +newpath +263 -628 moveto +256 -640 lineto +stroke +[5.0 5.0 ] 0 setdash +newpath +144 -576 moveto +144 -325 lineto +stroke +[] 0 setdash +newpath +151 -337 moveto +144 -325 lineto +stroke +newpath +137 -337 moveto +144 -325 lineto +stroke +183 -427 moveto +(create) show +newpath +114 -440 moveto +114 -325 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +114 -325 moveto +121 -337 lineto +107 -337 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +114 -325 moveto +121 -337 lineto +107 -337 lineto +closepath +stroke +newpath +168 -440 moveto +168 -325 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +168 -325 moveto +175 -337 lineto +161 -337 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +168 -325 moveto +175 -337 lineto +161 -337 lineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +548 -576 moveto +548 -317 lineto +stroke +[] 0 setdash +newpath +555 -329 moveto +548 -317 lineto +stroke +newpath +541 -329 moveto +548 -317 lineto +stroke +573 -453 moveto +(create) show +1.0 1.0 1.0 setrgbcolor +newpath +312 -448 moveto +122 0 rlineto +0 -21 rlineto +-122 0 rlineto +closepath +eofill +newpath +312 -448 moveto +122 0 rlineto +0 -1 rlineto +-122 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +312 -448 moveto +123 0 rlineto +0 -2 rlineto +-123 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +312 -450 moveto +122 0 rlineto +0 -19 rlineto +-122 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +316 -463 moveto +(MessageSearchProvider) show +newpath +312 -448 moveto +123 0 rlineto +0 -22 rlineto +-123 0 rlineto +closepath +stroke +newpath +432 -448 moveto +432 -317 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +432 -317 moveto +439 -329 lineto +425 -329 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +432 -317 moveto +439 -329 lineto +425 -329 lineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +576 -877 moveto +576 -1032 lineto +stroke +[] 0 setdash +newpath +569 -1020 moveto +576 -1032 lineto +stroke +newpath +583 -1020 moveto +576 -1032 lineto +stroke +601 -960 moveto +(clear old values) show +1.0 1.0 1.0 setrgbcolor +newpath +624 -176 moveto +59 0 rlineto +0 -21 rlineto +-59 0 rlineto +closepath +eofill +newpath +624 -176 moveto +59 0 rlineto +0 -1 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +624 -176 moveto +60 0 rlineto +0 -2 rlineto +-60 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +624 -178 moveto +59 0 rlineto +0 -19 rlineto +-59 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +646 -191 moveto +(Job) show +newpath +624 -176 moveto +60 0 rlineto +0 -22 rlineto +-60 0 rlineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +308 -288 moveto +624 -198 lineto +stroke +[] 0 setdash +newpath +614 -208 moveto +624 -198 lineto +stroke +newpath +610 -194 moveto +624 -198 lineto +stroke +[5.0 5.0 ] 0 setdash +newpath +656 -198 moveto +656 -640 lineto +stroke +[] 0 setdash +newpath +649 -628 moveto +656 -640 lineto +stroke +newpath +663 -628 moveto +656 -640 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +752 -240 moveto +76 0 rlineto +0 -21 rlineto +-76 0 rlineto +closepath +eofill +newpath +752 -240 moveto +76 0 rlineto +0 -1 rlineto +-76 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +752 -240 moveto +77 0 rlineto +0 -2 rlineto +-77 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +752 -242 moveto +76 0 rlineto +0 -19 rlineto +-76 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +756 -255 moveto +(ItemDeleteJob) show +newpath +752 -240 moveto +77 0 rlineto +0 -22 rlineto +-77 0 rlineto +closepath +stroke +newpath +752 -240 moveto +684 -198 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +684 -198 moveto +697 -198 lineto +690 -210 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +684 -198 moveto +697 -198 lineto +690 -210 lineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +752 -272 moveto +71 0 rlineto +0 -21 rlineto +-71 0 rlineto +closepath +eofill +newpath +752 -272 moveto +71 0 rlineto +0 -1 rlineto +-71 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +752 -272 moveto +72 0 rlineto +0 -2 rlineto +-72 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +752 -274 moveto +71 0 rlineto +0 -19 rlineto +-71 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +783 -287 moveto +(...) show +newpath +752 -272 moveto +72 0 rlineto +0 -22 rlineto +-72 0 rlineto +closepath +stroke +newpath +752 -272 moveto +684 -198 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +684 -198 moveto +697 -202 lineto +686 -211 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +684 -198 moveto +697 -202 lineto +686 -211 lineto +closepath +stroke +[5.0 5.0 ] 0 setdash +newpath +316 -782 moveto +408 -944 lineto +stroke +[] 0 setdash +newpath +395 -937 moveto +408 -944 lineto +stroke +newpath +408 -930 moveto +408 -944 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +24 -16 moveto +198 0 rlineto +0 -20 rlineto +-198 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -16 moveto +198 0 rlineto +0 -20 rlineto +-198 0 rlineto +closepath +stroke +28 -29 moveto +(kabc) show +1.0 1.0 1.0 setrgbcolor +newpath +24 -36 moveto +247 0 rlineto +0 -79 rlineto +-247 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +24 -36 moveto +247 0 rlineto +0 -79 rlineto +-247 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +304 -16 moveto +90 0 rlineto +0 -20 rlineto +-90 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +304 -16 moveto +90 0 rlineto +0 -20 rlineto +-90 0 rlineto +closepath +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +308 -29 moveto +(kmime) show +1.0 1.0 1.0 setrgbcolor +newpath +304 -36 moveto +139 0 rlineto +0 -79 rlineto +-139 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +304 -36 moveto +139 0 rlineto +0 -79 rlineto +-139 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +480 -16 moveto +90 0 rlineto +0 -20 rlineto +-90 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +480 -16 moveto +90 0 rlineto +0 -20 rlineto +-90 0 rlineto +closepath +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +484 -29 moveto +(kioslave) show +1.0 1.0 1.0 setrgbcolor +newpath +480 -36 moveto +139 0 rlineto +0 -79 rlineto +-139 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +480 -36 moveto +139 0 rlineto +0 -79 rlineto +-139 0 rlineto +closepath +stroke +isolatin1encoding /_TimesRoman /TimesRoman RE +/_TimesRoman findfont +10 scalefont setfont +1.0 1.0 1.0 setrgbcolor +newpath +40 -48 moveto +239 -48 lineto +249 -58 lineto +249 -105 lineto +40 -105 lineto +40 -48 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +40 -48 moveto +239 -48 lineto +249 -58 lineto +249 -105 lineto +40 -105 lineto +40 -48 lineto +stroke +0.69803923 0.69803923 0.69803923 setrgbcolor +newpath +239 -48 moveto +249 -58 lineto +239 -58 lineto +239 -48 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +239 -48 moveto +249 -58 lineto +239 -58 lineto +239 -48 lineto +stroke +isolatin1encoding /_Helvetica /Helvetica RE +/_Helvetica findfont +10 scalefont setfont +42 -63 moveto +(some widgets to manipulate addressees) show +42 -76 moveto +(using Jobs and Monitor) show +[5.0 5.0 ] 0 setdash +newpath +126 -48 moveto +126 -16 lineto +stroke +newpath +264 -116 moveto +264 -288 lineto +stroke +[] 0 setdash +newpath +257 -276 moveto +264 -288 lineto +stroke +newpath +271 -276 moveto +264 -288 lineto +stroke +[5.0 5.0 ] 0 setdash +newpath +272 -116 moveto +624 -176 lineto +stroke +[] 0 setdash +newpath +610 -180 moveto +624 -176 lineto +stroke +newpath +613 -167 moveto +624 -176 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +328 -64 moveto +80 0 rlineto +0 -21 rlineto +-80 0 rlineto +closepath +eofill +newpath +328 -64 moveto +80 0 rlineto +0 -1 rlineto +-80 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +328 -64 moveto +81 0 rlineto +0 -2 rlineto +-81 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +328 -66 moveto +80 0 rlineto +0 -19 rlineto +-80 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +332 -79 moveto +(MessageModel) show +newpath +328 -64 moveto +81 0 rlineto +0 -22 rlineto +-81 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +512 -64 moveto +72 0 rlineto +0 -21 rlineto +-72 0 rlineto +closepath +eofill +newpath +512 -64 moveto +72 0 rlineto +0 -1 rlineto +-72 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +512 -64 moveto +73 0 rlineto +0 -2 rlineto +-73 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +512 -66 moveto +72 0 rlineto +0 -19 rlineto +-72 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +516 -79 moveto +(AkonadiSlave) show +newpath +512 -64 moveto +73 0 rlineto +0 -22 rlineto +-73 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +448 -440 moveto +72 0 rlineto +0 -21 rlineto +-72 0 rlineto +closepath +eofill +newpath +448 -440 moveto +72 0 rlineto +0 -1 rlineto +-72 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +448 -440 moveto +73 0 rlineto +0 -2 rlineto +-73 0 rlineto +closepath +stroke +1.0 1.0 1.0 setrgbcolor +newpath +448 -442 moveto +72 0 rlineto +0 -19 rlineto +-72 0 rlineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +452 -455 moveto +(StrigiProvider) show +newpath +448 -440 moveto +73 0 rlineto +0 -22 rlineto +-73 0 rlineto +closepath +stroke +newpath +494 -440 moveto +494 -317 lineto +stroke +1.0 1.0 1.0 setrgbcolor +newpath +494 -317 moveto +501 -329 lineto +487 -329 lineto +closepath +eofill +0.0 0.0 0.0 setrgbcolor +newpath +494 -317 moveto +501 -329 lineto +487 -329 lineto +closepath +stroke +showpage +%%Trailer diff --git a/kdepim-runtime/doc/pics/akonadi_overview_uml_small.png b/kdepim-runtime/doc/pics/akonadi_overview_uml_small.png new file mode 100644 index 0000000000000000000000000000000000000000..06112b1d9893199161d690dcccbb6fee07afad0a GIT binary patch literal 57683 zcmYJb1zc6j_da|M9g-4CcOFWS?hXl2@PNbtq@=qW6a+-+kOl!kJRqHi22l`Dy1S8X zcn9zI_x`;fFYG;g@0nRMvu3U5dB#X>O=SW+Dm(}TLZGUmpaX%R!66WIB^)ephwQs~`xgpH`*;uB#CB8FP{ba^A|(|PTOk-W0JpHr)Rh$=H@E*~Hxo!fC>;XG^%Atac3NesfK^eeeh$!%|NA3#Um}AfTBgy;FQ-&1yKDL4%fmYbL?*T~na?>Ps9cgp zi#`f5zf*p*;IUfgyyFv8bWst9j#{TkyM}&>`{szK8;`l8Z#;Kr3T#Kq27jgtN6xqW6g&7kFP|E5hk!tJ z((7o0T;O?`VNPZy9ZnKHUQ+wKR}<^p_&2MLujv+Il9D{jA|fIxY?OS)`7<-dB;RUl zMa(89CQiiyVrKcW%ttbci#azoH}mi*d1a80U;4=YOi6WJUEK#>WR#SymQhywOz`sZ z@(05uB_(-#d(L8Bf3poAK71&z$1En62Eo`I`SM~4>!s5jT-=t z$<;rJputb|^eFo9wr5*RGNrxu6)H^GH9uxgO&KP^aFRChC{Ao7iUs=;F2vUt328(a zD*Se)M8(`U$i}yiy+d^M^em5)_cISx2hx2-6&qveB)Qx_*Vj{pUvH& zv+}!NeaU?;(y_2u7RQXL^W3Ggu(V8LI;s|9$a?XisKnLIN2c0YPHHyLayhQwMivn$oSj8-;_dtfoHnAj}_;l9J+BbHfd5EWZ8( zM)e+#O~&!IiP)6lqjbJneVnBKoWf^GHs!=YwVGK}RDa_8(dH;4yo?q8F_F~963pGA z=H?lg{0trAvYxRqZ|c?1kWMOG%I7emMpcEr_b*Gr*Mp;@#Cq4&e%gMwJ!4qE$9`PJ zWlMfOMhGV4oec!iEjX8gMKc^^P6dGw!yv682wAXrz;6t2-YCB)Qkj2OI22%EhHLB) z!*D3T_+hr9AtG^C2-1vBli#IJGPfc<(j*NrTX+ z&+F>^C{UF39J}DRC$Dx_CcQlJR0dAamnMAZ>Z|GT;rKEU4HbehX(vP{L4qF^8ygEe z&L1;it^9<~2BoPd6IHgf^|OlwggfyGIyv-=@p@}y1lzkOI!Zo1V#!Be`{AD;DAF9M zj_&TePMk}POa;GpU(e4+vdY=E`N!$(2+_HfD_Gks{W30yp1otyx?a{m?I-Eu4$D zvGllhrq!SJHnFx8d-mMZ-Tlw7iLO5@E|LYB1QXMd5VZcqu*>gqbA7dQak7gBQw{pB z45I!P8xcbzI#gvd+*MFeVA@NHQ2k#q27G82ZKT$Daq2YzwPE{fg6R*;%E*`hzR7}$ z!+@i~);*!Ni(N6WnYb#?U1OO3|Nmh)=>1#-rinbil)Gu8A<>1--^Q-;-1&1 zRo%JwA6nMlW!DU|zd2lw_dQ(u(3jFS{OWhnlb!RU%~ULS$@8xsJO-87i{eQzXwtvh z;vju5gD@MT?+LU`w*=1SfQ_e)oLf)iaBKWw4OsLwUVCOWHp6T+0ardXerGOkGNl7| z+NYa*g&|_y?`U25j2oSNkGGt&qys&GOCo&2`JzCr77Y_mWH=1>?%2wre8+%$Hm^~A z?OSs0>#)*9?&tMYwxeRh!RXkAGmYNUz-iK=Nr;R#cC&f7UeAU<<76~37qCO z%fm0%hYENV^2tr%kcuK3-D*31&%FgjME`75!Jm%EyOM_~a>E;~q1Ye$X6Y9KMoaY< z9~+(|a6bLmJG8bvSz{Vxp$;6pcfU*$M4c2N3jOyZ9HLj_FfGB*=zY*-KVARlh3U#b z7SS{9NZ%8zZ&m{yqOL1$t}8#Bbw59IE&>i#<~gb!E&2%wu;I+}erzH-H(*(Uj_&66 zH$)G)e$IuSu20JQNzH)0vq8YT>vS_!+>@8QuI8ovOyjtGKQI%T_VdrrI6W7;2uucR z?8b+(TAmhanx6DkZ~Q5_OF6m?UEW*lM%{N>kX`V{&2dZ-AcIk#7U52m6yXNphfZ2kJ$Q+O=sCi_^#Ty}+a)gt#R!7FG{c5n z5a4mFPu6^`$v159Aoz>uNTt0g`4!X#*#sM6nNkduGdir)HTvCTg@yWS$mZ$ZBK!<3 zsLFbf3HBTw%>+%-FGI`a!eX+1jjn0^DYv*-D@$sI$@${g9uoF_wmnpL2#1?s6%0`|zOI_uTQH8sYvsW= zALE_=eAa!4gW5;ShY)nAU(%pwI~iS{J51Lvr#HpL#rb{HEee8ULkD51@U-j4IeX|5 zK{&)17%l@SOpPosH9eGL={n!aXK}$~f{};e3sYe7xJGQ=<3~WU<=H*X-wocpbv3&x zcXjcHP@Z&4rD=)A?haFM?IdrLb3Mz}7nrX6{H&Pt!tXSmUF&-4`B!7fcd;pY1XO~M z9Tlytt|jt!1-)_hAUu1S@kzapUXXt1kP;oSO@8Y|*-^L7w6vv11D$qF++z!82_ls? z@OSrVE*iyqjhP=C_~=g|Hc-9VdtUJcnMaj2!>8Hc4IL&3Mc%|3y@JJY4L)~b0)LRA zi4ZJsX`r;8KRc|I8~z9rzAhvG&GKi(_k*rn&6X!Y20^TY-~*tQLqt@#58mX~drbbptZW~7m`#-OT?eXrO>fK}x1o87{9H=mM}0P^;>yEtrbf9hDnU_gqU`hfr@J-F zeaW%zT$BxgN=x(FcO&*SWx*~bD-rOUo(qm-Ab-6Z&BscE`0yOfACrgg%4YA0%h86e z6;t4XC3Bh+A+>BYiv=4RRIdvfW!3r+H{ZSSAbZ~PU%v|42{COz-@hq zP~fLdwcU8nQ%mnob_0|Gol`57rl_?A?pf#Y zhY^W*ZG16$H#>7G7{22}+qtdelPFmsJ!6ujMNvuzeVo|-ENQJ~CWn4TR9yZKELd{~ z2zGZLKcR`~x{E~Xf2gq{araFjBuSL2KvVz=m8(zGqAR$qhz>h`g6PD9Q{7`gF@5#w z`=asXS&1IcKG`m=|0z;zE+fmPuqPC5THKRz-K%w#kd(9nwr7+F#Wy7cqs4UKGlolK z`Akb-U}*t_!aM6E!ZV~e4Fb!K^!w4Uav0Ta$n=nXG6wx1G zPiuNs=!+?})`P!iz5sC-KQvdL?2!9z3Xqr_Z;dG`DLx2pqt zm-~bZEdz5zJr$QdRX9i%&TkO>pqvWpiOOQ~4R$%|$p`tsRe^WdCA+`!#T(wdM|{*gO=e-2w|^zNSxPHgOHQPY$7g;t}9seRLun zdGY75hyBWVFrViNTBF-KDJI!yCzuB1v76aUcfvY{WGocDxC9q+a;wbq!?0jMcv<#K zQIOhUzsIa<%}l%R-<+!O!&k57ll?{NSS93FhSM1#wl=+StmS4kD&O84z`V_{X zllYYrl~uC|&TC3;y*uiukWFD|6erRNr7mi^OcX!(RL|6Y;cS89eow0yQ8YR{p#&nd zi%)UkeXz1;81hgn>_#W)gC|Fc92B3uokpH6M~idV5W4Ty0@*)9beRgADFEMela`K^ zHh;>6?eo}=6|J3xgzLEwTm)3<>PY+Ep8w9@(dtSdt#vB87q4mWgh1N_9&82O5OnIq61j%6YdJ|HV813kYs~dmLm_`gjK}T+C0n z_}8BiMmBFp(`$97J*?=c^qqGajUi%nUf9QU6LK4nYw4$P zgj5>%nLb{bOSIqR+U65XaA=6(%UF7TuOsO+NrBDgd6ekIViN?a1<9Gvf?uFa^8uI zTF+x|N4J?R9%T@*in8`e#SO+p!w+_kL@hYt!Z0XsU>Hd#QaGAAA{Zryz=Z{~B5!Yk z+WueRf0wrFG8X7+e(3eP1D^BTq6XMudEjDH4Zp-Ac!Iy$m%k=o5^tKtq@Wnlm7LGI{ zpR+o$e_y<9?~nJOY7Nj3xql62M9P4Q{_6rhXn1KjJ81v2)^@#;BYRuh&0kwwWy9-t z25zReJ&2xi%@jVfElk+y-09yB_p-O&ww;P~&F!c%@Fa{fTi=t$DSf`}F#BSxFZt(K{u=Zfj=TxJ??VFWj zCZQt^p(DX#*6P4&&+_6#iPiSN^0pi4Zt9@3ajkEI5&jaB-^Sv{e9$1o@P~U6BeByF z7Dr=mTqiY9M8`Wk`JZ9$7Eh(yv>AUdOa$cWKQen_k$SwKb`6|952 z$C$sbGp6`UhojSHFNa#xG8aMFs3?3#ZutB6vI)h91l$(`*L;Sr+G*HyHfaV<(4{r& zS=W9`T^&!@ej$LQv^U?V)kIP}R;^FZn&!w#mlJN$!sX5cQ0OPl)}JO%D}Kf7*Z0zE zm7+r+#7q=eXJ{Wf_MXMe_Fw)}$8f=SWM{oK5~7kRn49sc%bAG17Z3j0?N!mnl}8N)E%b}w>{+E7VwVV}x@ zXgk^Io6EA=c$IDPM-k_?rOqfyG)PLN^%Jw2#Zxmk`bT&aGOg!!0F}~t^2Bs#bd>#b zZ7qCgXy|dZ{UjTNxF3^S?EKBH+$V&)hsXEsSb9bJATcp9E+TrVgq$2k+@-6F6BiI^ zr{X%!HYfU??DW1NqR%fWQHMMd5#dPj<2%2&&?qx%U{iiaL!z!e3hFPYs`|5ZeRY8b zan&_5GxM~wGh8Dp1<7{L*jUO(knj4X>`v7^1wbXs`_+N0zO+ZSFWIK*-PIpFe5g?_ z?!J)#IrRfANuHXTiWC$QdJ7Os*5&18GzeL#qapGT{d|46@DqYZzd~VufB)snmkMRZ zO|KlrPp<;Jk2e0WW0o{G8-Fe-Sq7=qiWLYK__(;a)d9ejUs0i>+~}qmDcbCR;c=gi zPKfzAz%!By)RSMeKA4>ZcrrxIG1k<_?d9oGZ@kZp%@2EJb>()qO7*-|c8E68vK(PQ zhLoj~@Iojf4>v~rWJ*BnuHxZQ-9=Ukdfo?;{rvp=j{elMrAMr+ipM+C-08xONdOdn zA|{uZmiDTwbRa{#uzMRPsgAYo)gLbCRtxhFI-VEQ!VZGtHI7+=j~?NIike+ohXy%~ zrrrL|wMK>{e*6eiRaF(+Cqp9E1zsTE(TcNyQApx>QLT-XX~kpm+50^>$T*TI>HFit z(RJ+?WBH-0+^RYn1alJ~e`II6p#X{0X75jntir^^)Oq$SU%mI|OE=#rCxEHB064Fy zsgU`ZLDKa%#Cmx}#e?3S9)4-pt8@1z*5W(_8B7}~7Z!k3YSLmnU=@FS>;Z+~=>>)S z`SlB-M?`POjI7n()=95?^0x*P6jeci3v8kelYV4C6Q-rj* z-?-b{G?T6*XPSK3lX;DXOg2jb?3Jp(7oCP(r!u>JpqA?qw;cP zgLEKMG9CbuJUuJA>z&EQ3gc^-G{8_01eIIBC@_fKq0r)Q^Lp>ksZ0>?9wKR72rcCT z3k&N<#0UTqeikKXOxg%D?rd-WLGHmIcoZla*pDz^b`>9=dR0x$cjHfqAeh{1_&Rzw>f>3`o|l9b zO?RZ{qA3P`G{Urh=!*rc;mZ#&OEq5E+G0X1<2*QOUR=$KXtH49lfl9Wrd^bTRsPUO z`aFY8>sU_LySJ^<2dz_-M&OvlEs99tQOLdJ-Nv>|lX`XyaFgS+&mOLek@jqWP>}dA z`qcyJXkBy(gb|Ik$HdgAQjRU=(i|xRgL4jip>3VB<{O=aODt_2E z8yi=lc%`NnpQ{jKmh(i1Zl9Ptfg_cK8lRd?l`T*8rL0-pd+p2svFEfuRi;0sPTo7X z`mM_0aV~aq&_WoDxzxX-6QjViF7L7(ojiL)MN3N#LBnT221ytg7<{jNBXI9d{ns;# z6A?59Bq-=I=-|*#IBsR@>N?B>%mLY9!g$}i?J-`96PVrn^()`v zhq?a#cVE7GlpG%){~jK0&+#P&bfMqq#>QtcadCThcXu08Q`szgJG-%ymDN>DVmOMZ zuDO{aXd(GA+cuA!ql?SF`DjUQuAGI1MOsEi)%@p9q_MFv1~Gi@4n>g3>hZDO-Me?I zeSCd&jg8CB&wU;V30-dhKWnkbW^7`Di57Jm;mkc(VIlooTwI=FU}#tg7#NEz%66yu zl@-hF-QCTzGkxMy+PcP$*%fep1^~cqyaakj_G@RStgE~G z2!u!&6xuk+$;oKicrHYx2M4Z_5)x~7g+*s*AG3o91`5F`wR3lK^Lu15WAu7kN%r>r zphL#j;64nk&!c=|a?<{KWN|SMQ_sSp;Nr}oi1Rk^0)3J}SWv^;+uIF{jXl7OALnZc z1^d}283vi#2I9biP)xTKU{D01Ve1J}RjC)hdHuSwqGAE9@5-*>ofQHBW=$|KBqaEV zKTmH%+919c77gH2SGXf z@w`)tY~JqH^dvXyt9#vhfiKh2&xm@m5WHj-DyoE!UNEZVkm>X~b?p`&0E-opVn3tDlvN>j7Qds!h@qYdG9sm~AeSCbDK>iK5r(9tQdq9%* z9vxLxy;afDO1`-8g+lQe5Ku3HM~@y61%3(s?o3m}Lsn^$h#A+=AYRwhcoSd9`=0YG6VL;%{71*IkL1tZ@of+}*@eOPX=o9R^ zSB2{*%}q-h8eUAT6}*w0Bz$3KS9;lH$>8AV_(D=&MkWZx!-EUD1d%8qmseB_7qPRq zSGBbKG(+K5U1FhZ%lE_Q<GF`ImPDRkf0m5(Wk=Cd2|C)&nbUIcIv>$k({< z`*+ZG)Z#x6JGtiYc0|+W>0{gG_3WCf!{Fq;g&FA&!cJa1UT zRxTL?AGr%!4Qc*aUr0$o@d`O)(V*v;Rb}&QQB+It`5%U(vC+y;KRsH&6qFb$B}NTj zUY=NUa|FJm--uRlXbhW~{j@ArTQPGqZg1A4f3BwL>wz_^3xo zX=!-?UxYRg6%pa2H|5X(KRY|~kiYxS;9i+@N8{9|zkes7ywkCW27QFZ)7&i`95kt` zHf(K#MPzR$e-QQ>VdAylT6+1;J8N->2%pPIKG^uTBP|Z_=ySRwjtwlWtTwIbdC&eE zgOI}*{R@sK#M=$fzQJuH6ZK@DaNt8#_4JZrn9!j3PQsf4=#9)Ci$6{6?KPxdu2FwG z*3VTfaKwf^6cyzJAuVrHl61i3FEsBATkGzRV68w5E#*V1par}Q^+fdc!*l1FN#QU` z{gO97emq{^+`KiT=iFWK87f;jZx0VPLT+wu9RmX{;1sHXE&;_>2~6VUvEoSasN_|C2_Jbp2;SC90C76*@l%9vvW6CTQ>-8ziF z&0osO)QpWYK`7dLcvv~7JC2i`lti&7H`10UM2G@g19Ibpiuh zMn=Z~v#c6fG&%X=og0Ts;4|Qf2t%(;;(40`Zydx8Ha9oX&?sb3=jlz~yo+;V7fxq~ zx@0QDhjfVX%MHRJF?d+`i7EAxu=MaFtC?7upAo0>-A`09`-DTT&uE>($K6V)%`%kj zgJRf_7+6I~+=)x7QQf6&p+9}{gZ4bwckYshbYw-Mwp7pajT*f^ zBveujs3F6Mna(_~j*gD>Q6PenJYv_uW>N?Z4drCq^?~d6we)zaa z#MBNN6lUz%pTco;pFH^>RupBQ*!umu+KU%2zDb*Ud5vKM&!{yCVmJ*IWSBNV(%1(M zT`Q|X9vBSnB@sg`4t2TRgk4=-Kion8T3kH%><;)SDXiT8qo`|_=0}lv|6&lQ7$OxU zCIuw`eW(+8cII7DR`!nN?LZG}z^KhLE*7|)3Q{4z(f=Zegw=SJbY|uH?7-?`e>n=5 zypG*GvZKy*RfSsV0eR>X@u{a-65ef9#0`oMXTjzG+Vma~xgFHMp8vvnMP(wk(3`PU ztw8jxpJodnVRMk1`PgGaony@cd!(Zy43~srH#iuoZkj?UgZ=_H2@K4d z$1n~Fs`kD^cIm`~!Nv7e1C)eY>9^ncVQ*N3O&=9Y)QleKZLK~-q=S2Vi^|EL!JvGT z*k5+D7XX7h(Uk$39ssHPrf@$|%c{_By z=g*tetB4~jZPs>ns_&t2Xz=RqkCOHJR}~lcvXe)@d&ia#^5gsW3Xt(MxGaB9+FkVA znHqNz`~?I!M%N>)zAtAAG1PHzlT`A3#iG)Pz89m;e zbp$dZW}Pj`S~Yd`ZJ<55Mhwr+4n)h})Pr1Wml}vZNUn{qjX@H-vHPXG{KMk444Kp_ zfCH{iKxQ=olIHF4Z{3=i68HU@T08(xs&f;JIOF{el3#>o9=;w(U=kA&82U+Jj4W_7 z$)3A0Wcq-P6#`c;LR1LbNK`R4LFgZ$#X!Kz4}4-z@>lcjcjG`AV;w$s^$Dp>yt1jOsR2e{+Zk(HU0Pl~+pDY;q@Q&?KeS2D$ao~;G#AuT4cN7zm|%p!(+^pj zPbzLnQxe$3mtV|}Ls&bqwysHviG&-Mmo4zEZEe4ST{SZ#^|vPr_wkLHLs(l$ z`+9Er64+kwFp*59Q?3pPli-0o_+XNWhtZ z!2Q6GhL#l~ACC^ST>kyDzkU&`V{XV@(yr|(PTw7>uxMrSX{@L)lR4z5eXXpdbXkA( zcOE_J(?E1$?oOoZrJ+oa2;Y>Bm5oiLd8Sa<-rioeNAw~r_u2gD`_FTa=(>oDvPF@U zb6Of2m<^FvAAv(C=`q6jgXpAYLOvLqVA9Ux1<;39309VYj)Pw z_!;AfhYa&eJmguT1V3V^hp>iWaO-9t2RDmvp)K5mm8}#1HpIzcI$8Qq3W9YVhkuKP zwDjehX+^TM2cP9}aP@TL2gTx8TjLP8;-s^f-E+n!3Ln(0k zd@3edAUmcNbqO26DK`M|2AF0F`>jIO=R?0Vj3R0!iw1oVHL|{#EVi`yL^jD4U~(RhGzV+ef6qQwCFUs>7jp&nfl`2zz0Ql3=>i>WScb0?t~c1%g746 zyTDP(eW%o2kBd$hjs`RRc7>`9~YKir#b1Nb!|#!VoRNLd{)M2k!4wV~=c&nT|<+|A_X z;&O=ttQ!U#cMTvtI}4prDc$moxXckLm?oTLES#iEnY{pXas|4S%44~I$T)X1|B3l= z;TVCjp`o3Fj4F6J>+W5aVPoPuytv5xJCp(nsl2FlAaBsSRhtkW1PF+7hu3wmsFEW5 zBO}_kCaSnIMicmq>fKB=<a@KmjHdz`;o7TO3v zp+5eWh`(3}xc)mI8vj31Ls5}b;@h=^&tXpv5VX~SA#HehtJOhLz~9%ifTpo}N;S#9 zAr$@B{RhUiV_aNZ5q)i3FJ(XXGVMPZlmSR!D_9l^ky0&ui23W>j!$)S204PuwUPOM z5S3QB7diZkEf94zVHDn|hZjK~P&cUwfvl_h%(k*~=mA1@vc{3VlaySIhY0@IAU!K< zmrWz>kUH7f+q-Uax}iqH>FSY~^8&^}<%i|-s6v`lep9sjM_)eNNHzsrO&rTg5Z{7J zaGeP#MaBXU-qe2n%+)aP)u=&jvfKmI1TVb!_tUJaqs+1%v4r<7LQK}L1Vu9dma zrIH3Y-dn^zucuXASqL?|*#?r50_;DY5Ew(GdjcTB>ln&RMxffYW?#XsnY*nEemF`0 zmA+rA{K#!h8}a)0y;>&%Gmu@W#6nL~bY139!7|j7P{YGYD3iFQscB9FkjlKMv?g)Q zfJ)VdeGxFJaWI0w)I|jlD99V2B2&)THU_wnlhr_GL9-0ig@6+}rKO6n8GohoqEb>Y z(}r2bTd<4jUkw7JSQL9oc<=!G_uku1ji3Ss&)fM)`4~UKbW+04dMw^L3_%wgna|sD zT|aQEmZMA1Ie=0SqkH|$L_;z#VA+yy%8`yJK=3^oV;q3Acn%G5p96E$Rm8){h?Y3q zc5q{`%t-v&6}=RozGmfU2@qJR!Q&SSd32OW>(JbFC9mw0-Pu`~IY85Ec4eVRfIDx3 zV56AwC@=GOvd@bv5{RN?s#N5;;#73$s;9Q{e+vjTggN{m=tvqm@*FxMec6886rVRiI_e8-r7u)s4Yh68k1F&yS*NJw1mQx2%OtPGJJx3P^O`pa^{WCOR# zRUqS~Y3sy!hIy;9+7=aJAbWw0UMd@kmC8*5g^!PqmjRh@Enm3|^Z{cTepE8;0f_Vn zp<<1N2=YbWhykEoPAE9W$+G~!^>-%GVdTiX8ODN?r&P~Zte0F^>NZr(1{q@R3lnNb zA&(|Z4$w;TF=5BdSYgJj%#m_P&b$PrSUOtT0JZk+>b;o*)Lg49Q4*GrSc15RFJ3v_ zVRdLa%M9_(8vkfCTB)e({*_~y<@n9@tH3h03s;{t&zz#1`REtbTUoGgEfDUe1H@pl zP1)l!i1u&EA5Cgc=wO_*3U5LQ(QrRJt$+6H@(sz1#4HA!2Lgx3ui1CmFSQ4h=zV$H z?@SqDoY&1F6q-VWU#kc^*&2^G7G%4Czz;3gk0v}pJeWwM$p}yMx4wiWwSpL&@II2FDZW0(njjSsVjY_4YAY?+(+Ah&Lkb)#7@yu5aK z?o(f$OBXc;sM%hxP`q+sb!WkJIz$ zs1O6LCiqz)Pp2zVQ(P|#$mc)#%#Xij1P((Iir>R%(jR^QVQwTtF!{)fXpD`)JZuK} z>c2(!Xvr9P-a%j2_Pk@L5!Cp%p&Y9vvsmjFe@7s4w7JafeJE;qFPw}Tp?)PF#Iu8` zr0@K34e5sF7|CraIhikku(0yn-{Z==ll1BF{e^(Df;{NJd^@YuO8&3ztmx_|0lK=j zhI%7ejZqe*yFbY;P7~s#^jNza5F_>UftPunH(Q4elkL|Z2@ZIZi^;^ViKH)Eu?b@T zLi=_95$D|K3KDb=`BdT{C6PgjTNrr`)^@i_G-7C(UHf~Rk-IRe<2(T~uB5ron^ScU zH|5VLf)w9#2^ef;;UE;1`NPNfH)0c`$nHFe=i7vGg@iu6kGwoPE#c*LM5)8`z^FqD7YTN5 zT%^uqh+>GW1BamRgP1YSva+A;pxe8{>R00UiCzoj>ZUP_NV`4JPLLyLT6TXS)c zylYh9N`+Y(7bi#0_f4DxweYYLQ2;?W&xUW(3VS^zqg9Nk4FvHS-9$v#gQIVbWJR^Y zb;{Hv6wV<_XHp?wFoA?YH7N010DkBP&4xo|2A_u=MKCTlVm0|+P*@AtJ~{l>*eF4D z1rfxkzALQQuI`ZWYnUJ}971zk2=UAMf({`y>q2@B9$;1j6>s~4tj>plcV)781>hYf zm_3X=w3JzakR4%96WP|;#!10}tk}D#q?!?pgH_kBfr!}3l+Oa@=M&9|iBX)_vabE1 zb|3nFZf^`cwGe5Ba0A18DJ%WKBzf4jCRW-yPsdCc=L=NU7p86udan?8#CW(7P}ry8 z`NR}eDRTtKH5qi#o-Y65%xywcGVprt1gh&%bv!60xtG{=)guM68Ej+_l^kBgmzUC| zhL*)3s+6Lzx5PrdRqT$QC50_bwJ;(xPdcn$ACg?sS(*_8Ssc)E?E7#KB@GFx+*s{S z85#8D@?Qs@7LieJW9>nuRC6L~?!6#Uuy~2r>dsn>4S#yAG=S6bDZ-uDNElc{$vRZqC^& zk{<{D4DZ08aWjJWVgL^hN{d^_6-eA zSrmT1UH$8e23P14(WFcJmn-}ynfXqLAaT#$$48k({@kY;S6;Myc-Nu<9CgvI@J1!! zi(=JE;KN%#eV0WoOu+^Q7eIUh)y_RnDI)6&z=Gd|s9^f`t-N?pXO)@Ecjp<4sDxan0)(r*C9_0xO_TC^19FYn%G%2^HqkH92)7}W-93|J2#eNF!7VYj zSq20NU|-H@HX`P+kP!b?)kIU=ze`r{dmJLylh8xP?|(HPS`M`nMvJqxcd2rmiuj11 zxtmN&j9dRP-EokOEzki?53AqVj&UD~JU?%qb0%LeZ@zATs7t~3TXo_ZKot)#N|9g5 zbG*J+>4!i9AXL@G5*44~?Cw@;ZG7%xU}? z8L1x`(F>)Ri|8Q@crF;!ITLJ%pQ~&`9g{{$nDH0N+EMqtug|~Z$M+^`YHFy+jX}qz zs3U-PK0K%{remrbiCtJQdlx_O;UFlymIAfBzTP4rBC-a6TW^}wRt>xh{Qp;5=xY3; zjKX(F)S8+~@y98B!hD#-+R`-*Vm>f_MD%AbZ8%rTJ_9y)RS_0*u|8-a8f^JiE=+?B zQ->GqzoFsWF9%# zC2|gy`DIZwL)X{P(UB1fsicVPn`Q2FR?#~3e=8=+LT{Q3Z&9h+g`d3g*%YZw! zeEF)WNz!n(cQCrc-Sj4;b)&zZ*Po!=6PIq)bqU{zh4p`LtBb!D?aM;gsg_HY08pGttbH{h45{PGhOtz~4TYj(SKlbzTaszyUyvs%u`VALb z$?ojUOL6WzaBd5r#&wRf*Iq}W03L2!yl8DT6)KBt!M^LX6mMJ(bfx0ulBw31idLaT z)(#Hus`dEVMzJ|L6$YjHD(mXjS0CPj6T8l#KxA*fzkt~Pn33TQNL`llirp{hq0(|E zDWNW!T;30)x_|t*Gy6_o$<#Zbi)h`26|v3Y#8zQ_f#gQ(I=`_aLahk}}|!qWxrSeZ3eE|7}aXSb8`wK;BsW z{rmSDdtpJrQ-yJY|g1)$fP}X8yO*-;lfH}NP{Be;?Id9%zPvvpi6SBwZ z{Or)_Ea~r!V<5Sgjgv(mrc4mZTA$o;^vmk+^|e%EWn0z%=7)O7zo>(n?TzWt$;smH zONy^F=%tTyf+H*D*q{6_mGRL^e+}jpJD43OslWH;RbRd8A!V%7zaR0P-9@OU)rSHJ zPWnx)tre*k{Q09REG@koXXELad~J67an|=Fx8h4~AM)3z;lF?R)g4roH8f(xrKLUB zUxAZsV=})dnI3Cuell`CJUv|lOuzFtO&<-w7nKzhU}CTP3Fjv0hC*_%E=btfs`x~<;O#GbVwi|X zesLnqzU^ zerzUzFAj`--+hG(I{{)xO+)`ud?2&bl|j%>E%5@mBU+!h?fvVKPxMrP-%w#3r*%!7)kF_|5A!GATKF%zyqDBMbg9WfRed= z_Vduz&W_&2#RWO|!aSR&0SWvnpamfE91eiaI_lkC-KhsOLdm=nd}^~CXslwC^3Hv2 z&o-p(utH_c%RB=Dnombd*GqQyz+{76dn4DsIuvPIS=u3te)!%Syzkb~R1lYx^`4T% z#`k;dnFQ07WWRa-@B0M_%}K{T3i-1cnkN=lO;x^}?k0r^0YYn52U(l1E(fMc5{AAG zWnrgZ>!j(wT{DinWbIm8I>MVWoT=fIUO8%{@Aiu>XskjriT%dU6Q5@P4&fJ@2Z2?Be=rQ{=v1Zv%P4T)@nQ^*j4fOsDE#cTPtQ z1Z{703*^>LJ%tbs1|;FFu;U}Q@IewfG5krUt#^|QP|SYXHJ*6`p`wO=^Nz(#@&&rp z!a02gBuZoFpYk=F5Y~nR!Rt1E@(BwB7?>C?r-Xo_ZT?MpU4yo%;`7c1+_7ZJ zLPrIX;N8|Tzk8t!#VO?&(Croka@g>6Ai;=vm|g=zPKAb}#4x#mx6nf%q)5#Or>tg4C6fZ@%b7o6)O9oY8KiRh| zpx9E^@{+4<-Xm0zVQh8yjIHOtga}$xk%HLjD#&_jjfo@#ZjS2CEo%QBVe?W0;QO6? zdeM=8d%oqi4p^)h46d2qFVK5-?&Oz7PKZ0guDW!lgTbm+T7)-PUrr##0w?Jnm`KqL z!^;G!O9Y^wNT@FZfS@9P#}x=9RWvn^i=#m(5=|>!-3PpU_szl0TM66|WlobuFM)wn zCX@g;Toz6Bh-+!%kF`wmjYYgXz4DSjVh21$=;ZcmJ{k!xVsK7Q z{y8`i=9u1h^mDZ9ULh`D^Bt#*K0d=*2#Hr(S{i@gwO<$&2-EqyQtcHVr$qdiRl;Fx zee2g+fm@I0>^k;ghRXq-XCnb2nju+{q4_*o=`E8>zx`*D+uLvDTF(dH^0gTuU0hw~ z`BKz;(?QzS=(TrSNSe09%Os1WlMaXmBtqV&PgnpBYy+Xo`vaGV1R>^ z82T*%lJDpw@!ANt0J2=`u}uZgj&O0$ow=S5%wr9nd_6!Js;#Sw>2k7Tg#1|3=>SBe zV8bN5+gdS@h|%{(6y8b-tA1MD0&u}#yP3xO0FlG0^E-QRIsrt8GCQ*^1Dhy^?#px_ zQMfPVCjw47#c)3_3!)Kmf=C<#sF1;`|D)`S7wP_6K=$gi(18meXdFMj`Epq3E~DLZ zSROdrX6@*RtzY9n3%+P+5~@dg7J#eghC4#91bE$3yb+M*kx){;0jEnRgu$Ug;Sk|d z0RLuZXG6B`ahQA*X|@`mgTavya99o95e{J5ih^KT135XgvHOn%1%q}S?d>sBA6Vc4 z_*lTS4TD%yrmU>&)5q`_#Gc{u(vk|Y?J<%=7Z8OA@f94BNiobQW)(8OYopDL4Vn8B z_3qq&seB9co8|x-%wB>S)sq2rF$1uCH-KigK9mAJ!yL?w6iHtp4p|s8Cnqj27G^;t z5idA63Z<9ux+9&nojFQr5kp2n5mF%x&LCdFm4)m=HI8VL3ML|aVuJ~!8*%;Ck;hT?B{slSh^2f%;hLZyhj_6^{ zqj!SS%5&j5hy7Z3fF`Wrw;h3L2XVmt-ZS{6fWypKEBzVjAM&0(Df8O$?V}Pf!{C5l zZUL5bBz8!tHMfIPk)xHAe)bdNG!Du4kj_dY<)PtWQM|v5t!7q7s;y@1L(uQk;*x;w z=4Nn3)=FYM{X+(OcgH)YQQIu#JO2GvGulR#TfQ&Ci45i-DPzj?*o0rQm0D`mR6wyO z@Zq#F;BoNqu*6CFd(Es+Zj1%Nu3yGDy(H*}o4fj7ogc0rOHd|tUO6{%%DqE8nQBu& zZn=V0MP*Xn->ODZvF)rmw>kQ?)kDI0LE*3GW7BV6bPmt5->0Ri$s%u*{OkvF6%n65 zK28_f&d4Hz(Y3oVeZ~ER0bn@7{iWW!+8Ze@tF|R#q)1q-FC4~W>2MX%3o=O=;vbFA;5+(^zB?2cYl|CEu9)XLSL}VS;a7%Xr+S2Cs zHw6qj{4z-N*xbqDl6hcKuP!1Kn+W)}xH!^iYT+{A+d4aMHl0Ln9(27l*t2@S*q}`T zv%TTUe5e={8uj89jdeL%*m%4-v3JkB%gwr*N4C}j3qJoIaTbHY{UDl{F{m&%H$=PoW`CX)CkqP-&w4D{( ztNri(8yK+ps*RywG{(x&_OwNUeS!Ml7gP5$FW5p|;mI3@B73kFIG zi+AJ}*$nVJHGh|*`R{!3x5deHU5^wJgvaD$Sou-v`ySyGyTuZw+fUwp)kv*oCXC@v zV;x6v(tBEtUOVOEYNJOjY|m;O$L_M)VpvA3sZn9d6(7&2e_ptqPm4xw8uKLHej3S9 znE&IV3eVEV?3eX>`Mr~Lw8D4ec4xvC{;q{CD68Y>kx+O?j>>>40y%3W3H}2?ZlOmt zxUz!HyAaCJJer4Y(99tvrfMF2fd0D#yooju4dO&O8VtHT;e4DYN7x+@nIK+9P!4uf zx#BNIoXCF{K4V{x%IKxiBLNlJJWADkFaT05^vHy92IT*KR#^PMZ^~i}7rKkT5LL4V z{u)%xg!pTLZw@SUK2#kO_O5Fm2F-_3X2dpffq(-_I}ZJ=h=3kx!9W_{I}zvqzoXFo ze@7uUCNusal(V^zl!_kPK##TKQiA~%c5=h}{P#IBiZ(I^u8hC>REy;;L1hqr`1g~4 zp9o-(883}A_4O$t-zql;^(MdjH@+=v+$HY+Zy@DHA1~83g)w3Qc+vIgLt;;lv4N4% zPVfl`z>JAOeLw6(888O#T3T96xZla2L!JI!M9@4M`DAh%cpDVMwfQQ6k!aIdTH45) z$0k-*u0B3KL7dEk4BXtFmqLIBG*<2E;^`@C*FT{uDd{tLU)|C1IWQMB9Mou7SF+ID zO1FhQw)D)+)8y|HtKk^oUU}2rO^=)YArNdPu&!@;2ax@1&1M<*; zZw!hTuMWvcNmZ<}0hTl84^)m_YbXZ~@hfP17WL@SqoTm`a&k^55bY+piXUuk%^7fV zbIX1k7`Ul)8PIO4(Ex<7TzIk-G4F&UUS3Cp$NYK4=~cHG!)e#JHNU_qA6*@~u9LQ~ zE1~-E)YSf8Y27gd??$9Q*vmTasmOQ;==$7hph%H53!u8rgv7+xFJ9by8Gn^zUdcjV z@i|HNDZA#ahO(t4*FLtTQ6R5WbK?bIAYSb|W*kCO4Mw>2JqcF0c(H)oWKqlu(7HoNNldN(1XtDilKF9~?-4^Id4jYIAeBl>O$DKvQBZ>(A=inw$?fLQD@;xPw8!0)qTXgU8}o9|rqAfE0z) z>r_z*iFY8vFbaGi&Qn;Zx@P5f+6kRI*Hv}>^5BQa7t45*77&JV!g_jos|$!E!(5fX z?`uCA+(F;z+t~RzQF)2hV3K9fjVPKC4B(fRz7nVtZct2x52L>%nvbw|Y*zGX7Cp)r z6eJcbBhA_ab_D~f>V?u+J#S+_&Dv@h}AbpqLI-n)R9zt zRF5OA^v1tP?GxQvJ3G!L@|a_lqb_!>WRi~O*N+b`*Ip}yt}9M%tMn@F)GRBKpzH?c zNBiFXZK!S>_+x!Dd#G{n&O4T(BW!he|DWg87teO7CXgaf9IAYX@H)Q*W7p3?F&}O( za$Fq!5L&mJ#&Ba7LXI)ZbFi9i379dM@p?<5R|J)lw^GO6{3J4Qjh094OI|=(?z;hcd#mBLpw29H zCZHEy^|KUzk1rN|aN-8g^RD1@KYCPb~q(GLg{S7b7xj`OW8=EWwFR=qe02ZRcCfsMK3K@gt6UYxs2I0#%H zDzs~AFu@?W(0P{6REh=f4y{;8I0~*FVEUH(`^|-@%}y?hJo9==BjAE=b9kBL-32&7 z(TjbR0A>BP`0d#Mz}!k;Q(lFDtn@ZNgdQFQ77?*>VDz@Ph)HpTs3>COuFpk`Hc8MZ zX0EoV7BL8WZem8o?6v!EkBcf%tORnH1Er=dtkO#YyTD0)ecws5F*`TqenMm|%1&lT zaeP+a)=dREY+Hr!&|282=Dm9G}~=k||CZo2>TC z`Fhngsb;77u>-^@NE#LjXNp6>4mAXUa*q_EyZ9>V!GeCd@2wk}-$&e2EwL!mw*-kD zx>f~;uoud1Ogi((pwr*`s`5Qr!$k&}T7HtlT-AsMD?I>`LM6cAGXHe&VXGhDv8J0I zuP#o`w8T)cT+XO4bgqO7YY7bsG}hj}79hOL+Z#x54Izqoi$)m4^@h573(e}nWTl@P zV8TWJq1I42^lb9&`9W`)ug)V{M=CeU)vHN>%y(J{+4!kWfGwJkqOV%c&C;$(&2UY1 zXVp$*$1Q@?!`0ww&ZZjwWecGYJ#_npzH&}aMgsBh#|VRz2lZshr@E3&bc|VglXTKM zdq)+VNCjL!tHpdPInQ%By*(o!H7%Xl0MYZoCP|$jW4-UXW!cNAGqqR#$a@SwDN7?k zsRVG?Vy^0}t5_bd)vS}2g~-!1yl0#zzC1B>)l!Mf#Etm}4MTFrrF(PIoi{1aI|yW6 z@F6?mASxqQ{ltssA>D%zO+JZ2K*wI~>E^i*g4z)XG+49ZH~E;Of@qRb{uGyL|Q9c)@o518=G3QUzDA7x?O7ROKgCA9HP!q-H8s*>pq4w zg9u|r1zk{dD)-$JS=&)F+kjk5b!erdd)D(O)Ouv2DVx8P3cM>qf10=0&Zaw?3y*A; z^-}yEtG#q{i`c7F%4xSmboE{+K081ugSDz`zkmjGGAqZI`>J%;E|#t?TK@eQQZ7}_Z1e(C`Px}Wx4 zK@YE5$no)4#a?!`DfVk1HKosglbvzM-1eA9vE89Rl6i>5Y~6TVE>MHL_MwWEQ&WWG zQ|GzB(&+qdLDotu^@3H~M`y>^F$vBC&ZIOdSR4}+PTDA7A_4Wqd3BL=+} z(67i$2vrQb*GZ9lb$1CPNAjDJMp)Z_(V!Zd3st34epIUHOEeK0uV(o)u=aWov9e9S zka}dfxOp8j_hCJj^t_uiMm8L?({0S$ln~2-!+vw@int+X8&9Y2`%bQ>@J$r(bU(ok zU)tcj&n$}(5)}SjR5q(jkuxH`?}upnY&@mvl+)o387>WfHJyD%{xqcLWXroSg62Pf z{0Vq5Yu>fHE#K|U98l@CX;koz$`IQz%eHQAhiRJui4&pj$B1($!OOUFbi|bCuAzs^ z&&9kwH`==AvNfKaMxZ|Q;;IBR-u#l+Un9T@V|Hv^g#~*CX^tCjf6(+ek;~DmxYdYx zkrExv8qB?abUgIoa)xWB^Q)ou$`@-DhMJF6?mltU8)ZCkU)4h_j}5$h`}aMQb|M*$ zQjKkdwk}<#oUV!Ey^`Bc5sYjjA-js|=TO$BUk?x1TJ8v9SYT_qlrgeO8i*Xs>#te4 z$WSGxygxu-NB-?vVT|l460q&h%0BadmZC95h$iUVd}>-b`}ud`mx3&Hc33E(E>%jj z_WEP3JC&7%Q=gi*w)Mk=kG6YXI&6D5$zS^+c%Xyvf5Jq9D?|*E7Yo+vYC;L`p?{Ey zSb3nF_uNarLcv-x_@xDN69<1&0{m4cvTq~Dm|2Xtsl?K)+8gb$=JY`SBAwLMPScT1 z_d}r>m?n5j)BYC){k=Oc9ULbAdW3}V%juS?$|?T}%HiG#La*f+J0IzW3YML`^FWAl z`0o&)>~0qdU%P$n`|Ue3<-=sdcMtMNSeg=au4id0;BIpK@h=ok&@m|p>E^*sdi|*1 zF9m36hdtG&qN1V?CAGhP3=i)TqV#?57Z!i(c)Pl)B_`I%g)PrsxNyAq>~y%4Vyz6T zwaMU;VMsXbeq!UI77UVu;cr#7gz`6g1d?M$HnENMN%G&`;B>)VxLKHZIx?(qLINRb zyJ*0wh!4Jim%#s~%-Sb6*6Xi5?`%0gUUv&^Au)dQ#tt2Mq|epg_P<*&@6WVia0vtw zhQ-1n^SPCAl=eMrk`5exw3#2NQxq4HmL`J4kwL9AGd+fv?>5qLwZHmRl7H(^c_;VE z(tPd*sb#srJZpg_|75No)>C6f4ppiKIJo?}ZlR zdZfG4MHe!N#P)90xx?cXmcO$8aj5ENXfSJi_z=_14udmf&@TV(4R->N?ta$qf`xjQ z7njn5(pT0athcJu($Zuu9nLPSFCT?AVF;w!XT@7bKu$HS6HvE0pmX&Vr1%^S!=@pZ zHd^U?-*DQbP^;^=-fTw!D)d&f)ck2szli~%9dvVoUvr6Fqxwepz8RjTD7ha4`kF4l zf0v3ELY+I;$a*QL+5WfppB1Y)R>*Aj!CArLZrd34x*>S?lVbtoN`C?0pA}nE&|HC@ zV;QUVd{mAW5*0GYFqA-T$WyU$H-ji%`t{Tbko{bo%v1@i070Wdl;8RfRgf>#fhoEY z`PROT642KAC-=YubN~88ofQi}t%>(itVk-0T=KzqrnBZC;^jGBV+w@yKJf1bs$8$+ zDf;P{ytu>y;9gze*F}PtgO|or3fy|Whywd8x+Srv81&oaTlvS8k;=Cq*WknH0&0cR zx!%9}&&hfWo2dK&HzHfc_2Ka3YioJQ<*N-r{v`3u@5BDSFEY$31UDz)FZjq7&vcSo z*MWKd4sC^Fw7{j7Le!JUJ_;m^1U68mVJ!y9oS?1QcnsU@k(#0RI3(!?>h5}d%e>y; zJw@pAPC3oybb2yd4eyQD*WP}IJ8t!9r4-l)q6eQv z?!9KZjetK80@$60$_1a(H()|!+p0;rKz>0_4!k!G18i^e-xuC4ihXN;R0OmmZ zh@LYfBXBh&10*5ORl|Z5cv=y=L-uvF%JmU2YB<_?Kj$f1l3#u6HlQ``oevX2p*XG5 z$pI-LYmjYVu54*#y)QI~h}@!_t_3fhiU0Jb%-voU(`cB7odV zsw68`fdRRty7-k2gmiVvmtzX;jnwyYz?q*_uo9^c~)JJQ!;( zE_ul9H-kse=HpQpesa7J8H0PE+|nk04OfGX?wA00E;G_$8j@Z7TDb}o&QUc&y_kqS zheO!;B;O)o;36Q=xStrQy+X*VnoCtkQVKG~mo8s+{>mtZI#CeNz;5ks*nGbBaesTE zts2I%snYs{q*qrbRE%{N)KJfP{)rGVhJvzTHY!%iBBUZ-VT~^63l|F<# zwulQzA7s2&0QqEkLNZ+VNmzBvVdpvZYBp6(i1ZZlxtM2ZFp0FiM8tH5hU1aZXRl_F zIw{AWY6bm8Z7ciK)$t+$+D>OEp%ka`5#Lj92R4BA1%bm9izVr z`Gf&*6G^C1nSWvWv2Ft;LiSLt@U21vI&_-LC|iaBGRDSGz*<=6BfY54T=~^Qb1NkA zt3uZV3THzmx~neEU^K&peC&!t&&iEf%fi!>K_V*Q{jiRt8}_v?-sD>0{+U-WKx2g%9~OWAOr)g2k2IyuBo)dJp*3xsD$Ev%_FAE+6bRi3M6iHRlKEs_$1R z`;o{C5(3_@zN;U$OQYHaJE>W3nxSI4MKnJt)`y?i0&LppEgB^f^N-jB@IFO zXzMjaezQ~;;ft7hMAz)KNuHXkZa=R~THy@CqNNaNaKkt4a4q(Y>*f_>%_`b>>fKa0KB+6fSr6R`Vsb=IKMSS2aD)4>AyD3L_v4!qeArl|7x+$M_BzG0_s-w2Hsp&|vj@DQ9$OKxBJB0$p z`ed!N*PaN4s%x9x-XIR#KK!s?FE`UJ*>U^+mm?n3@Cy5G{z&jx(0u6R+YFL!Hdsge z3Q~Tg-cVFjwAZ=&>kXc?h@|}Du}*$!nVsKWOF2_EpP7$a+TR*)GSiV;y#LPHq!WQEH2b3pQ0+l

T%nDNVm+ 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%0ANfBnU$9b<(P|s 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 b_=(-MM>nDHIyWQmY#atpS3j3^P6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Page 1 content

+

]]> + + + Page 2 + 3 + false + + +

Page 2 content

+

]]>
+
+ + diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/f23552.book b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/f23552.book new file mode 100644 index 00000000..be147218 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/f23552.book @@ -0,0 +1,57 @@ + + + + + Book2 + 5 + false + 1 + + Page 1 + 4 + false + + +

book2 page 1 content

]]>
+
+ + Page 2 + 6 + false + + +

book 2 page 2

+

]]>
+
+ + Nested book + 8 + false + 1 + + nested page 1 + 7 + false + + +

nested page 1 content

]]>
+
+ + nested page 2 + 9 + false + + +

nested page 2 content

]]>
+
+
+
+
diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/k21863.book b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/k21863.book new file mode 100644 index 00000000..ece736b7 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/k21863.book @@ -0,0 +1,30 @@ + + + + + rich content book + 11 + false + 1 + + rich content page 1 + 10 + false + + +

rich content 1

]]>
+
+ + rich content page 2 + 12 + false + + +

More rich content page 2

]]>
+
+
+
diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_TDHwQa.book b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_TDHwQa.book new file mode 100644 index 00000000..70fea0dd --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_TDHwQa.book @@ -0,0 +1,28 @@ + + + + kde3_test1 + 2 + 1 + + kde3_book2 + 6 + 1 + + kde3_nested Page 1 + 7 + + + + + kde3_Page 1 + 3 + + + + kde3_Page 2 + 4 + + + + diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_hu4mua.book b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_hu4mua.book new file mode 100644 index 00000000..df37532e --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/kjots/kde3_hu4mua.book @@ -0,0 +1,13 @@ + + + + kde3_book3 + 9 + 1 + + kde3_book3 Page 1 + 10 + + + + diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes.ics b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes.ics new file mode 100644 index 00000000..b65162a8 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes.ics @@ -0,0 +1,31 @@ +BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN +VERSION:2.0 +BEGIN:VJOURNAL +DTSTAMP:20100208T161533Z +X-KDE-KNotes-BgColor:#ffffbd +X-KDE-KNotes-FgColor:#000000 +X-KDE-KNotes-RichText:false +CREATED:20100208T111228Z +UID:libkcal-297296717.52 +LAST-MODIFIED:20100208T112745Z +DESCRIPTION:xcvxcvx2010-02-08 12:13 +SUMMARY:2010-02-08 12:12 +BEGIN:VALARM +DESCRIPTION: +ACTION:DISPLAY +TRIGGER;VALUE=DATE-TIME:20100208T120000Z +END:VALARM +END:VJOURNAL +BEGIN:VJOURNAL +DTSTAMP:20100208T161533Z +X-KDE-KNotes-BgColor:#ffffbd +X-KDE-KNotes-FgColor:#000000 +X-KDE-KNotes-RichText:false +CREATED:20100208T112938Z +UID:libkcal-2098450417.977 +LAST-MODIFIED:20100208T161533Z +DESCRIPTION:Some kind of content +SUMMARY:2010-02-08 12:29 +END:VJOURNAL +END:VCALENDAR diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-2098450417.977 b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-2098450417.977 new file mode 100644 index 00000000..5679efc7 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-2098450417.977 @@ -0,0 +1,10 @@ +[Display] +bgcolor=255,255,189 + +[General] +version=3.9 + +[WindowDisplay] +HideNote=true +desktop=1 +position=484,154 diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-297296717.52 b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-297296717.52 new file mode 100644 index 00000000..d83d863b --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/apps/knotes/notes/libkcal-297296717.52 @@ -0,0 +1,10 @@ +[Display] +bgcolor=255,255,189 + +[General] +version=3.9 + +[WindowDisplay] +HideNote=true +desktop=1 +position=1195,158 diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc new file mode 100644 index 00000000..c5e90d84 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc @@ -0,0 +1,4 @@ +[ProcessedDefaults] +defaultaddressbook=done +defaultcalendar=done +defaultnotebook=done diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc new file mode 100644 index 00000000..61bb2f0c --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc @@ -0,0 +1,4 @@ +[General] +DataFile[$e]=$KDEHOME/testdata-res1.xml +FileWatchingEnabled=false + diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdebugrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdebugrc new file mode 100644 index 00000000..a8438b93 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdebugrc @@ -0,0 +1,80 @@ +DisableAll=false + +[0] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[264] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[5250] +InfoOutput=2 + +[7009] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7011] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7012] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7014] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=0 +FatalFilename[$e]=kdebug.dbg +FatalOutput=0 +InfoFilename[$e]=kdebug.dbg +InfoOutput=0 +WarnFilename[$e]=kdebug.dbg +WarnOutput=0 + +[7021] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdedrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdedrc new file mode 100644 index 00000000..41d17814 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kdedrc @@ -0,0 +1,3 @@ +[General] +CheckSycoca=false +CheckFileStamps=false diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kres-migratorrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kres-migratorrc new file mode 100644 index 00000000..5dde57e2 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kres-migratorrc @@ -0,0 +1,2 @@ +[Migration] +Enabled=false diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kresources/notes/stdrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kresources/notes/stdrc new file mode 100644 index 00000000..423ed4b0 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kresources/notes/stdrc @@ -0,0 +1,15 @@ +[$Version] +update_info=kolab-resource.upd:kolab-notes-resource-rename + +[General] +PassiveResourceKeys= +ResourceKeys=670EbuwOq8 +Standard=670EbuwOq8 + +[Resource_670EbuwOq8] +NotesURL[$e]=file://$KDEHOME/share/apps/knotes/notes.ics +ResourceIdentifier=670EbuwOq8 +ResourceIsActive=true +ResourceIsReadOnly=false +ResourceName=Notes +ResourceType=file diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kwalletrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kwalletrc new file mode 100644 index 00000000..8ba29ca1 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/kwalletrc @@ -0,0 +1,2 @@ +[Wallet] +Enabled=false diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/qttestrc b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/qttestrc new file mode 100644 index 00000000..2e2f28ea --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/share/config/qttestrc @@ -0,0 +1,2 @@ +[Notification Messages] +WalletMigrate=false diff --git a/kdepim-runtime/migration/tests/unittestenv/kdehome/testdata-res1.xml b/kdepim-runtime/migration/tests/unittestenv/kdehome/testdata-res1.xml new file mode 100644 index 00000000..638904cd --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/kdehome/testdata-res1.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + testmailbody + From: <test@user.tst> + \SEEN + \FLAGGED + \DRAFT + + + testmailbody1 + From: <test1@user.tst> + \FLAGGED + + + testmailbody2 + From: <test2@user.tst> + + + testmailbody3 + From: <test3@user.tst> + + + testmailbody4 + From: <test4@user.tst> + + + testmailbody5 + From: <test5@user.tst> + + + testmailbody6 + From: <test6@user.tst> + + + testmailbody7 + From: <test7@user.tst> + + + testmailbody8 + From: <test8@user.tst> + + + testmailbody9 + From: <test9@user.tst> + + + testmailbody10 + From: <test10@user.tst> + + + testmailbody11 + From: <test11@user.tst> + + + testmailbody12 + From: <test12@user.tst> + + + testmailbody13 + From: <test13@user.tst> + + + testmailbody14 + From: <test14@user.tst> + + + + diff --git a/kdepim-runtime/migration/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc b/kdepim-runtime/migration/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc new file mode 100644 index 00000000..3ba0bb89 --- /dev/null +++ b/kdepim-runtime/migration/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc @@ -0,0 +1,5 @@ +[%General] +Driver=QSQLITE3 + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/opensync/CMakeLists.txt b/kdepim-runtime/opensync/CMakeLists.txt new file mode 100644 index 00000000..9d5fc800 --- /dev/null +++ b/kdepim-runtime/opensync/CMakeLists.txt @@ -0,0 +1,29 @@ +include ( OpenSyncInternal ) +include ( OpenSyncPlugin ) + +LINK_DIRECTORIES( ${OPENSYNC_LIBRARY_DIRS} ${GLIB2_LIBRARY_DIRS} ) +INCLUDE_DIRECTORIES( ${OPENSYNC_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ) + +set( akonadi_opensync_srcs + akonadi_opensync.cpp + sinkbase.cpp + akonadisink.cpp + datasink.cpp +) + +# for boost +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +automoc4( akonadi_opensync_plugin akonadi_opensync_srcs ) +OPENSYNC_PLUGIN_ADD( akonadi_opensync_plugin ${akonadi_opensync_srcs} ) + +TARGET_LINK_LIBRARIES( akonadi_opensync_plugin + ${OPENSYNC_LIBRARIES} + ${GLIB2_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KCAL_LIBS} +) + +OPENSYNC_PLUGIN_INSTALL( akonadi_opensync_plugin ) +OPENSYNC_PLUGIN_CONFIG( akonadi-sync ) diff --git a/kdepim-runtime/opensync/akonadi-sync b/kdepim-runtime/opensync/akonadi-sync new file mode 100644 index 00000000..d99a681d --- /dev/null +++ b/kdepim-runtime/opensync/akonadi-sync @@ -0,0 +1,14 @@ + + + + + 1 + + + vcard30 + + + contact + + + diff --git a/kdepim-runtime/opensync/akonadi_opensync.cpp b/kdepim-runtime/opensync/akonadi_opensync.cpp new file mode 100644 index 00000000..a65dd76b --- /dev/null +++ b/kdepim-runtime/opensync/akonadi_opensync.cpp @@ -0,0 +1,201 @@ +/* + Copyright (c) 2008 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 "akonadisink.h" +#include "datasink.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +static KComponentData *kcd = 0; +static QCoreApplication *app = 0; + +static int fakeArgc = 0; +static char** fakeArgv = 0; + +extern "C" +{ + +static void* akonadi_initialize(OSyncPlugin *plugin, OSyncPluginInfo *info, OSyncError **error) +{ + osync_trace( TRACE_ENTRY, "%s(%p, %p, %p)", __func__, plugin, info, error ); + + if ( !app ) + app = new QCoreApplication( fakeArgc, fakeArgv ); + if ( !kcd ) + kcd = new KComponentData( "akonadi_opensync" ); + + // main sink + AkonadiSink *mainSink = new AkonadiSink(); + if ( !mainSink->initialize( plugin, info, error ) ) { + delete mainSink; + osync_trace( TRACE_EXIT_ERROR, "%s: NULL", __func__ ); + return 0; + } + + // object type sinks + const int numobjs = osync_plugin_info_num_objtypes( info ); + for ( int i = 0; i < numobjs; ++i ) { + OSyncObjTypeSink *sink = osync_plugin_info_nth_objtype( info, i ); + QString sinkName( osync_objtype_sink_get_name( sink ) ); + kDebug() << "###" << i << sinkName; + + DataSink *ds; + if ( sinkName == "event" ) + ds = new DataSink( DataSink::Calendar ); + else if ( sinkName == "contact" ) + ds = new DataSink( DataSink::Contacts ); + + if ( !ds->initialize( plugin, info, sink, error ) ) { + delete ds; + delete mainSink; + osync_trace( TRACE_EXIT_ERROR, "%s: NULL", __func__ ); + return 0; + } + } + + osync_trace( TRACE_EXIT, "%s: %p", __func__, mainSink ); + return mainSink; +} + +static osync_bool akonadi_discover(void *userdata, OSyncPluginInfo *info, OSyncError **error) +{ + osync_trace( TRACE_ENTRY, "%s(%p, %p, %p)", __func__, userdata, info, error ); + kDebug(); + + if ( !Akonadi::Control::start() ) + return false; + + // fetch all akonadi collections + Akonadi::CollectionFetchJob *job = new Akonadi::CollectionFetchJob( + Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive ); + if ( !job->exec() ) + return false; + + Akonadi::Collection::List cols = job->collections(); + kDebug() << "found" << cols.count() << "collections"; + + OSyncPluginConfig *config = osync_plugin_info_get_config( info ); + + Akonadi::MimeTypeChecker contactMimeChecker; + contactMimeChecker.addWantedMimeType( KABC::Addressee::mimeType() ); + Akonadi::MimeTypeChecker calendarMimeTypeChecker; + calendarMimeTypeChecker.addWantedMimeType( QLatin1String( "text/calendar" ) ); + + const int num_objtypes = osync_plugin_info_num_objtypes( info ); + for ( int i = 0; i < num_objtypes; ++i ) { + OSyncObjTypeSink *sink = osync_plugin_info_nth_objtype( info, i ); + foreach ( const Akonadi::Collection &col, cols ) { + kDebug() << "creating resource for" << col.name() << col.contentMimeTypes(); +// if ( !contactMimeChecker.isWantedCollection( col ) ) // ### TODO +// continue; + if ( col.contentMimeTypes().isEmpty() ) + continue; + + OSyncPluginResource *res = osync_plugin_resource_new( error ); + // TODO error handling? + osync_plugin_resource_enable( res, TRUE ); + osync_plugin_resource_set_name( res, col.name().toUtf8() ); // TODO: full path instead of the name + osync_plugin_resource_set_objtype( res, osync_objtype_sink_get_name( sink ) ); + osync_plugin_resource_set_url( res, col.url().url().toLatin1() ); + + QString formatName; + if ( calendarMimeChecker.isWantedCollection( col ) ) + formatName = "vevent20"; + else if ( contactMimeChecker.isWantedCollection( col ) ) + formatName = "vcard30"; + else + continue; // if the collection is not calendar or contact one, skip it + + /*OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env( info ); + OSyncObjFormat *format = osync_format_env_find_objformat( formatenv, formatName.toLatin1() ); + osync_plugin_resource_set_objformat( res, format );*/ + + osync_plugin_resource_add_objformat_sink( res, osync_objformat_sink_new( formatName.toLatin1(), error ) ); + + osync_plugin_config_add_resource( config, res ); + } + osync_objtype_sink_set_available( sink, TRUE ); + } + + osync_trace( TRACE_EXIT, "%s", __func__ ); + return TRUE; +} + +static void akonadi_finalize(void *userdata) +{ + osync_trace( TRACE_ENTRY, "%s(%p)", __func__, userdata ); + kDebug(); + AkonadiSink *sink = reinterpret_cast( userdata ); + delete sink; + delete kcd; + kcd = 0; + delete app; + app = 0; + osync_trace( TRACE_EXIT, "%s", __func__ ); +} + +KDE_EXPORT osync_bool get_sync_info(OSyncPluginEnv *env, OSyncError **error) +{ + osync_trace( TRACE_ENTRY, "%s(%p)", __func__, env ); + + OSyncPlugin *plugin = osync_plugin_new( error ); + if ( !plugin ) { + osync_trace( TRACE_EXIT_ERROR, "%s: Unable to register: %s", __func__, osync_error_print( error ) ); + return FALSE; + } + + osync_plugin_set_name( plugin, "akonadi-sync" ); + osync_plugin_set_longname( plugin, "Akonadi" ); + osync_plugin_set_description( plugin, "Plugin to synchronize with Akonadi" ); +// osync_plugin_set_config_type(plugin, OSYNC_PLUGIN_NO_CONFIGURATION); + osync_plugin_set_start_type( plugin, OSYNC_START_TYPE_PROCESS ); + + osync_plugin_set_initialize( plugin, akonadi_initialize ); + osync_plugin_set_finalize( plugin, akonadi_finalize ); + osync_plugin_set_discover( plugin, akonadi_discover ); + + osync_plugin_env_register_plugin( env, plugin ); + osync_plugin_unref( plugin ); + + osync_trace( TRACE_EXIT, "%s", __func__ ); + return TRUE; +} + +KDE_EXPORT int get_version(void) +{ + return 1; +} + +}// extern "C" diff --git a/kdepim-runtime/opensync/akonadisink.cpp b/kdepim-runtime/opensync/akonadisink.cpp new file mode 100644 index 00000000..f56bb150 --- /dev/null +++ b/kdepim-runtime/opensync/akonadisink.cpp @@ -0,0 +1,58 @@ +/* + Copyright (c) 2008 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 "akonadisink.h" + +#include + +#include + +AkonadiSink::AkonadiSink() : + SinkBase( Connect ) +{ +} + +AkonadiSink::~AkonadiSink() +{ +} + +bool AkonadiSink::initialize(OSyncPlugin * plugin, OSyncPluginInfo * info, OSyncError ** error) +{ + kDebug(); + OSyncObjTypeSink *sink = osync_objtype_main_sink_new( error ); + wrapSink( sink ); + osync_plugin_info_set_main_sink( info, sink ); + return true; +} + +void AkonadiSink::connect() +{ + osync_trace( TRACE_ENTRY, "%s(%p, %p)", __PRETTY_FUNCTION__, pluginInfo(), context() ); + kDebug(); + + if ( !Akonadi::Control::start() ) { + error( OSYNC_ERROR_NO_CONNECTION, "Could not start Akonadi." ); + osync_trace( TRACE_EXIT_ERROR, "%s: %s", __PRETTY_FUNCTION__, "Could not start Akonadi." ); + return; + } + + success(); + osync_trace( TRACE_EXIT, "%s", __PRETTY_FUNCTION__ ); +} + diff --git a/kdepim-runtime/opensync/akonadisink.h b/kdepim-runtime/opensync/akonadisink.h new file mode 100644 index 00000000..dc9a9f06 --- /dev/null +++ b/kdepim-runtime/opensync/akonadisink.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2008 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 AKONADISINK_H +#define AKONADISINK_H + +#include "sinkbase.h" + +/** + * Main sink, does nothing but ensure Akonadi is running. + */ +class AkonadiSink : public SinkBase +{ + Q_OBJECT + + public: + AkonadiSink(); + ~AkonadiSink(); + + bool initialize( OSyncPlugin *plugin, OSyncPluginInfo *info, OSyncError **error ); + + void connect(); +}; + +#endif diff --git a/kdepim-runtime/opensync/datasink.cpp b/kdepim-runtime/opensync/datasink.cpp new file mode 100644 index 00000000..d419d73a --- /dev/null +++ b/kdepim-runtime/opensync/datasink.cpp @@ -0,0 +1,433 @@ +/* + Copyright (c) 2008 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 "datasink.h" + +// calendar includes +#include +#include + +// contact includes +#include +#include + +// notes includes +// TODO + +#include +#include +#include + +using namespace Akonadi; + +typedef boost::shared_ptr IncidencePtr; + + +DataSink::DataSink( int type ) : + SinkBase( GetChanges | Commit | SyncDone ) +{ + m_type = type; +} + +DataSink::~DataSink() { + if ( m_hashtable ) + osync_hashtable_unref( m_hashtable ); +} + +bool DataSink::initialize(OSyncPlugin * plugin, OSyncPluginInfo * info, OSyncObjTypeSink *sink, OSyncError ** error) +{ + Q_UNUSED( plugin ); + Q_UNUSED( info ); + Q_UNUSED( error ); + + bool enabled = osync_objtype_sink_is_enabled( sink ); + if ( !enabled ) { + kDebug() << "sink is not enabled.."; + return false; + } + + QString configdir( osync_plugin_info_get_configdir( info ) ); + QString hashfile = QString( "%1/%2-hash.db" ).arg( configdir, osync_objtype_sink_get_name( sink ) ); + + m_hashtable = osync_hashtable_new( hashfile.toUtf8(), osync_objtype_sink_get_name( sink ), error ); + + if ( !osync_hashtable_load( m_hashtable, error ) ) { + osync_trace( TRACE_EXIT_ERROR, "%s: %s", __PRETTY_FUNCTION__, osync_error_print( error ) ); + return false; + } + + wrapSink( sink ); + + return true; +} + +Akonadi::Collection DataSink::collection() const +{ + OSyncPluginConfig *config = osync_plugin_info_get_config( pluginInfo() ); + Q_ASSERT( config ); + + const char *objtype = osync_objtype_sink_get_name( sink() ); + + OSyncPluginResource *res = osync_plugin_config_find_active_resource( config, objtype ); + + if ( !res ) { + error( OSYNC_ERROR_MISCONFIGURATION, i18n( "No active resource for type \"%1\" found", objtype ) ); + return Collection(); + } + + const KUrl url = KUrl( osync_plugin_resource_get_url( res ) ); + if ( url.isEmpty() ) { + error( OSYNC_ERROR_MISCONFIGURATION, i18n( "Url for object type \"%1\" is not configured.", objtype ) ); + return Collection(); + } + + return Collection::fromUrl( url ); +} + +void DataSink::getChanges() +{ + // ### broken in OpenSync, I don't get valid configuration here! +#if 1 + Collection col = collection(); + if ( !col.isValid() ) + return; +#else + Collection col( 409 ); +#endif + + OSyncError *oerror = 0; + + if ( osync_objtype_sink_get_slowsync( sink() ) ) { + kDebug() << "we're in the middle of slow-syncing..."; + osync_trace( TRACE_INTERNAL, "Got slow-sync, resetting hashtable" ); + if ( !osync_hashtable_slowsync( m_hashtable, &oerror ) ) { + warning( oerror ); + osync_trace( TRACE_EXIT_ERROR, "%s: %s", __PRETTY_FUNCTION__, osync_error_print( &oerror ) ); + return; + } + } + + ItemFetchJob *job = new ItemFetchJob( col ); + job->fetchScope().fetchFullPayload(); + + QObject::connect( job, SIGNAL(itemsReceived(Akonadi::Item::List)), this, SLOT(slotItemsReceived(Akonadi::Item::List)) ); + QObject::connect( job, SIGNAL(result(KJob*)), this, SLOT(slotGetChangesFinished(KJob*)) ); + + // FIXME give me a real eventloop please! + if ( !job->exec() ) { + error( OSYNC_ERROR_IO_ERROR, job->errorText() ); + return; + } +} + +void DataSink::slotItemsReceived( const Item::List &items ) +{ + kDebug() << "retrieved" << items.count() << "items"; + Q_FOREACH ( const Item& item, items ) { + //kDebug() << item.payloadData(); + reportChange( item ); + } +} + +void DataSink::reportChange( const Item& item ) +{ + OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env( pluginInfo() ); + OSyncObjFormat *format = osync_format_env_find_objformat( formatenv, formatName().toLatin1() ); + + OSyncError *error = 0; + + OSyncChange *change = osync_change_new( &error ); + if ( !change ) { + warning( error ); + return; + } + + osync_change_set_uid( change, QString::number( item.id() ).toLatin1() ); + + error = 0; + + OSyncData *odata = osync_data_new( item.payloadData().data(), item.payloadData().size(), format, &error ); + if ( !odata ) { + osync_change_unref( change ); + warning( error ); + return; + } + + osync_data_set_objtype( odata, osync_objtype_sink_get_name( sink() ) ); + + osync_change_set_data( change, odata ); + + kDebug() << item.id() << "DATA:" << osync_data_get_printable( odata ) << "\n" << "ORIG:" << item.payloadData().data(); + + osync_data_unref( odata ); + + osync_change_set_hash( change, QString::number( item.revision() ).toLatin1() ); + + OSyncChangeType changeType = osync_hashtable_get_changetype( m_hashtable, change ); + osync_change_set_changetype( change, changeType ); + + osync_hashtable_update_change( m_hashtable, change ); + +/* +kDebug() << "changeid:" << osync_change_get_uid( change ) + << "itemid:" << item.id() + << "revision:" << item.revision() + << "changetype:" << changeType + << "hash:" << osync_hashtable_get_hash( m_hashtable, osync_change_get_uid( change ) ) + << "objtype:" << osync_change_get_objtype( change ) + << "objform:" << osync_objformat_get_name( osync_change_get_objformat( change ) ) + << "sinkname:" << osync_objtype_sink_get_name( sink() ); +*/ + if ( changeType != OSYNC_CHANGE_TYPE_UNMODIFIED ) + osync_context_report_change( context(), change ); + + // perhaps this should be only called when an error has happened ie. never after _context_report_change()? + //osync_change_unref( change ); // if this gets called, we get broken pipes. any ideas? +} + +void DataSink::slotGetChangesFinished( KJob * ) +{ + OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env( pluginInfo() ); + OSyncObjFormat *format = osync_format_env_find_objformat( formatenv, formatName().toLatin1() ); + + // after the items have been processed, see what's been deleted and send them to opensync + OSyncError *error = 0; + OSyncList *u, *uids = osync_hashtable_get_deleted( m_hashtable ); + + for ( u = uids; u; u = u->next ) { + QString uid( (char *)u->data ); + kDebug() << "going to delete with uid:" << uid; + + OSyncChange *change = osync_change_new( &error ); + if ( !change ) { + warning( error ); + continue; + } + + osync_change_set_changetype( change, OSYNC_CHANGE_TYPE_DELETED ); + osync_change_set_uid( change, uid.toLatin1() ); + + error = 0; + OSyncData *data = osync_data_new( 0, 0, format, &error ); + if ( !data ) { + osync_change_unref( change ); + warning( error ); + continue; + } + + osync_data_set_objtype( data, osync_objtype_sink_get_name( sink() ) ); + osync_change_set_data( change, data ); + + osync_hashtable_update_change( m_hashtable, change ); + + osync_change_unref( change ); + } + osync_list_free( uids ); + + kDebug() << "got all changes.."; + success(); +} + +void DataSink::commit(OSyncChange *change) +{ + kDebug() << "change uid:" << osync_change_get_uid( change ); + kDebug() << "objtype:" << osync_change_get_objtype( change ); + kDebug() << "objform:" << osync_objformat_get_name( osync_change_get_objformat( change ) ); + + switch( osync_change_get_changetype( change ) ) { + case OSYNC_CHANGE_TYPE_ADDED: { + const Item item = createItem( change ); + osync_change_set_uid( change, QString::number( item.id() ).toLatin1() ); + osync_change_set_hash( change, QString::number( item.revision() ).toLatin1() ); + kDebug() << "ADDED:" << osync_change_get_uid( change ); + break; } + + case OSYNC_CHANGE_TYPE_MODIFIED: { + const Item item = modifyItem( change ); + osync_change_set_uid( change, QString::number( item.id() ).toLatin1() ); + osync_change_set_hash( change, QString::number( item.revision() ).toLatin1() ); + kDebug() << "MODIFIED:" << osync_change_get_uid( change ); + break; } + + case OSYNC_CHANGE_TYPE_DELETED: { + deleteItem( change ); + kDebug() << "DELETED:" << osync_change_get_uid( change ); + break; } + + case OSYNC_CHANGE_TYPE_UNMODIFIED: { + kDebug() << "UNMODIFIED"; + // should we do something here? + break; } + + default: + kDebug() << "got invalid changetype?"; + } + + osync_hashtable_update_change( m_hashtable, change ); + success(); +} + +const Item DataSink::createItem( OSyncChange *change ) +{ + Collection col = collection(); + if ( !col.isValid() ) // error handling + return Item(); + kDebug() << "cuid:" << osync_change_get_uid( change ); + + ItemCreateJob *job = new Akonadi::ItemCreateJob( createAkonadiItem( change ), col ); + + if ( !job->exec() ) + kDebug() << "creating an item failed"; + + return job->item(); // handle !job->exec in return too.. +} + +const Item DataSink::modifyItem( OSyncChange *change ) +{ + char *plain = 0; + osync_data_get_data( osync_change_get_data( change ), &plain, /*size*/0 ); + QString str = QString::fromUtf8( plain ); + + QString id = QString( osync_change_get_uid( change ) ); + Item item = fetchItem( id ); + if ( !item.isValid() ) // TODO proper error handling + return Item(); + + //event.setMimeType( "application/x-vnd.akonadi.calendar.event" ); + //Item newItem = createAkonadiItem( change ); + setPayload( &item, str ); + ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob( item ); + if ( modifyJob->exec() ) { + kDebug() << "modification completed"; + return modifyJob->item(); + } else + kDebug() << "unable to modify"; + + return Item(); +} + +void DataSink::deleteItem( OSyncChange *change ) +{ + QString id = QString( osync_change_get_uid( change ) ); + Item item = fetchItem( id ); + if ( !item.isValid() ) // TODO proper error handling + return; + + kDebug() << "delete with id: " << item.id(); + /*ItemDeleteJob *job = new ItemDeleteJob( item ); + + if ( job->exec() ) { + kDebug() << "item deleted"; + } + else + kDebug() << "unable to delete item";*/ +} + +bool DataSink::setPayload( Item *item, const QString &str ) +{ + switch( m_type ) { + case Calendar: { + KCal::ICalFormat format; + KCal::Incidence *calEntry = format.fromString( str ); + + item->setMimeType( "application/x-vnd.akonadi.calendar.event" ); + item->setPayload( IncidencePtr( calEntry->clone() ) ); + + break; + } + case Contacts: { + KABC::VCardConverter converter; + KABC::Addressee vcard = converter.parseVCard( str.toUtf8() ); + kDebug() << vcard.uid() << vcard.name(); + + item->setMimeType( Addressee::mimeType() ); + item->setPayload( vcard ); + break; + } + case Notes: { + kDebug() << "notes"; + break; + } + default: + // should not happen + return false; + } + + return true; +} + +const Item DataSink::createAkonadiItem( OSyncChange *change ) +{ + char *plain = 0; + osync_data_get_data( osync_change_get_data( change ), &plain, /*size*/0 ); + QString str = QString::fromUtf8( plain ); + Akonadi::Item item; + setPayload( &item, str ); + return item; +} + +const QString DataSink::formatName() +{ + QString formatName; + switch( m_type ) { + case Calendar: + formatName = "vevent20"; + break; + case Contacts: + formatName = "vcard10"; + break; + case Notes: + formatName = "vnote10"; + break; + default: + kDebug() << "invalid datasink type"; + return QString(); + } + + return formatName; +} + +const Item DataSink::fetchItem( const QString& id ) +{ + ItemFetchJob *fetchJob = new ItemFetchJob( Item( id ) ); + fetchJob->fetchScope().fetchFullPayload(); + + if ( fetchJob->exec() ) { + foreach ( const Item &item, fetchJob->items() ) { + if ( QString::number( item.id() ) == id ) { + kDebug() << "got item"; + return item; + } + } + } + + // no such item found? + return Item(); +} + +void DataSink::syncDone() +{ + kDebug(); + OSyncError *error = 0; + osync_hashtable_save( m_hashtable, &error ); + + //TODO check for errors + success(); +} + diff --git a/kdepim-runtime/opensync/datasink.h b/kdepim-runtime/opensync/datasink.h new file mode 100644 index 00000000..f72f962e --- /dev/null +++ b/kdepim-runtime/opensync/datasink.h @@ -0,0 +1,103 @@ +/* + Copyright (c) 2008 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 DATASINK_H +#define DATASINK_H + +#include "sinkbase.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace Akonadi; + +/** + * Base class for data sink classes, dealing with the type-independent stuff. + */ +class DataSink : public SinkBase +{ + Q_OBJECT + + public: + enum Type { Calendar = 0, Contacts, Notes }; + + DataSink( int type ); + ~DataSink(); + + bool initialize( OSyncPlugin *plugin, OSyncPluginInfo *info, OSyncObjTypeSink *sink, OSyncError **error ); + + void getChanges(); + void commit( OSyncChange *change ); + void syncDone(); + + public slots: + void slotGetChangesFinished( KJob * ); + void slotItemsReceived( const Akonadi::Item::List & ); + + protected: + /** + * Returns the collection we are supposed to sync with. + */ + Akonadi::Collection collection() const; + + /** + * This reports the change back to opensync + */ + void reportChange( const Item& item, const QString& format ); + + /** + * Reimplement in subclass to provide data about changes to opensync. Note, that you must call DataSink::reportChange( Item, QString, int ) after you have organized data to be send to opensync. + */ + void reportChange( const Item & item ); + + /** + * Creates a new item based on the data given by opensync. + */ + const Item createItem( OSyncChange *change ); + /** + * Modified an item based on the data given by opensync. + */ + const Item modifyItem( OSyncChange *change ); + /** + * Deletes an item based on the data given by opensync. + */ + void deleteItem( OSyncChange *change ); + + private: + const Item createAkonadiItem( OSyncChange *change ); + const Item fetchItem( const QString& id ); + const QString formatName(); + bool setPayload( Item *item, const QString &str ); + + private: + OSyncHashTable *m_hashtable; + int m_type; +}; + +#endif diff --git a/kdepim-runtime/opensync/sinkbase.cpp b/kdepim-runtime/opensync/sinkbase.cpp new file mode 100644 index 00000000..e198149b --- /dev/null +++ b/kdepim-runtime/opensync/sinkbase.cpp @@ -0,0 +1,158 @@ +/* + Copyright (c) 2008 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 "sinkbase.h" +#include + +#define WRAP(X) \ + osync_trace( TRACE_ENTRY, "%s(%p, %p, %p)", __PRETTY_FUNCTION__, userdata, info, ctx ); \ + SinkBase *sink = reinterpret_cast( \ + osync_objtype_sink_get_userdata( osync_plugin_info_get_sink( info ) ) ); \ + sink->setContext( ctx ); \ + sink->setPluginInfo( info ); \ + sink->X; \ + osync_trace( TRACE_EXIT, "%s", __PRETTY_FUNCTION__ ); + +extern "C" +{ + +static void connect_wrapper( void *userdata, OSyncPluginInfo *info, OSyncContext *ctx ) +{ + WRAP( connect() ) +} + +static void disconnect_wrapper(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + WRAP( disconnect() ) +} + +static void get_changes_wrapper(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + WRAP( getChanges() ) +} + +static void commit_wrapper(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *chg) +{ +kDebug() << "commitwrap"; + WRAP( commit( chg ) ) +} + +static void sync_done_wrapper(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + WRAP( syncDone() ) +} + +} // extern C + + +SinkBase::SinkBase( int features ) : + mContext( 0 ), + mSink( 0 ), + mPluginInfo( 0 ) +{ + mWrapedFunctions.connect = ( features & Connect ) ? connect_wrapper : 0; + mWrapedFunctions.disconnect = ( features & Disconnect ) ? disconnect_wrapper : 0; + mWrapedFunctions.get_changes = ( features & GetChanges ) ? get_changes_wrapper : 0; + mWrapedFunctions.commit = ( features & Commit ) ? commit_wrapper : 0; + mWrapedFunctions.write = ( features & Write ) ? 0 : 0; + mWrapedFunctions.committed_all = ( features & CommittedAll ) ? 0 : 0; + mWrapedFunctions.read = ( features & Read ) ? 0 : 0; + mWrapedFunctions.batch_commit = ( features & BatchCommit ) ? 0 : 0; + mWrapedFunctions.sync_done = ( features & SyncDone ) ? sync_done_wrapper : 0; +} + +SinkBase::~SinkBase() +{ + if ( mSink ) + osync_objtype_sink_unref( mSink ); +} + +void SinkBase::connect() +{ + Q_ASSERT( false ); +} + +void SinkBase::disconnect() +{ + Q_ASSERT( false ); +} + +void SinkBase::getChanges() +{ + Q_ASSERT( false ); +} + +void SinkBase::commit(OSyncChange * chg) +{ + Q_UNUSED( chg ); + Q_ASSERT( false ); +} + +void SinkBase::syncDone() +{ + Q_ASSERT( false ); +} + +void SinkBase::success() const +{ +kDebug(); + Q_ASSERT( mContext ); + osync_context_report_success( mContext ); + mContext = 0; +} + +void SinkBase::error(OSyncErrorType type, const QString &msg) const +{ +kDebug(); + Q_ASSERT( mContext ); + osync_context_report_error( mContext, type, msg.toUtf8() ); + mContext = 0; +} + +void SinkBase::warning(OSyncError * error) const +{ +kDebug(); + Q_ASSERT( mContext ); + osync_context_report_osyncwarning( mContext, error ); + osync_error_unref( &error ); +} + +void SinkBase::wrapSink(OSyncObjTypeSink * sink) +{ +kDebug(); + Q_ASSERT( sink ); + Q_ASSERT( mSink == 0 ); + mSink = sink; + + osync_objtype_sink_set_functions( sink, mWrapedFunctions, this ); +} + +void SinkBase::setPluginInfo(OSyncPluginInfo * info) +{ + Q_ASSERT( mPluginInfo == 0 || mPluginInfo == info ); + mPluginInfo = info; +} + +void SinkBase::setContext(OSyncContext * context) +{ + Q_ASSERT( mContext == 0 || context == mContext ); + // ### do I need to ref() that here? and then probably deref() the old one somewhere above? + mContext = context; +} + diff --git a/kdepim-runtime/opensync/sinkbase.h b/kdepim-runtime/opensync/sinkbase.h new file mode 100644 index 00000000..bf37cabe --- /dev/null +++ b/kdepim-runtime/opensync/sinkbase.h @@ -0,0 +1,80 @@ +/* + Copyright (c) 2008 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 SINKBASE_H +#define SINKBASE_H + +#include + +#include +#include +#include +#include +#include + +/** + * Base class providing wraping of OSyncObjTypeSink. + */ +class SinkBase : public QObject +{ + Q_OBJECT + public: + enum Feature { + Connect = 1, + Disconnect = 2, + GetChanges = 4, + Commit = 8, + Write = 16, + CommittedAll = 32, + Read = 64, + BatchCommit = 128, + SyncDone = 256 + }; + + SinkBase( int features ); + virtual ~SinkBase(); + + virtual void connect(); + virtual void disconnect(); + virtual void getChanges(); + virtual void commit( OSyncChange *chg ); + virtual void syncDone(); + + OSyncContext* context() const { return mContext; } + void setContext( OSyncContext *context ); + + OSyncPluginInfo *pluginInfo() const { return mPluginInfo; } + void setPluginInfo( OSyncPluginInfo *info ); + + protected: + void success() const; + void error( OSyncErrorType type, const QString &msg ) const; + void warning( OSyncError *error ) const; + void wrapSink( OSyncObjTypeSink *sink ); + + OSyncObjTypeSink* sink() const { return mSink; } + + private: + OSyncObjTypeSinkFunctions mWrapedFunctions; + mutable OSyncContext *mContext; + OSyncObjTypeSink *mSink; + OSyncPluginInfo *mPluginInfo; +}; + +#endif diff --git a/kdepim-runtime/plugins/CMakeLists.txt b/kdepim-runtime/plugins/CMakeLists.txt new file mode 100644 index 00000000..1a44a417 --- /dev/null +++ b/kdepim-runtime/plugins/CMakeLists.txt @@ -0,0 +1,74 @@ +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + +add_subdirectory( tests ) + +include_directories( + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +set(akonadi_serializer_addressee_PART_SRCS akonadi_serializer_addressee.cpp ) +kde4_add_plugin(akonadi_serializer_addressee ${SERIALIZER_TYPE} ${akonadi_serializer_addressee_PART_SRCS}) +target_link_libraries(akonadi_serializer_addressee ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KABC_LIBS} ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KABC_LIBS}) +install(TARGETS akonadi_serializer_addressee DESTINATION ${PLUGIN_INSTALL_DIR}) + +set(akonadi_serializer_mail_PART_SRCS akonadi_serializer_mail.cpp ) +kde4_add_plugin(akonadi_serializer_mail ${SERIALIZER_TYPE} ${akonadi_serializer_mail_PART_SRCS}) +target_link_libraries(akonadi_serializer_mail ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${AKONADI_COMMON_LIBRARIES} ) +install(TARGETS akonadi_serializer_mail DESTINATION ${PLUGIN_INSTALL_DIR}) + +if(KDEPIMLIBS_KCAL_LIBRARY) +set(akonadi_serializer_kcal_SRCS akonadi_serializer_kcal.cpp) +kde4_add_plugin(akonadi_serializer_kcal ${SERIALIZER_TYPE} ${akonadi_serializer_kcal_SRCS}) +target_link_libraries(akonadi_serializer_kcal ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KCAL_LIBS} ${KDEPIMLIBS_AKONADI_LIBS}) +install(TARGETS akonadi_serializer_kcal DESTINATION ${PLUGIN_INSTALL_DIR}) +endif() + +set(akonadi_serializer_kcalcore_SRCS akonadi_serializer_kcalcore.cpp) +kde4_add_plugin(akonadi_serializer_kcalcore ${SERIALIZER_TYPE} ${akonadi_serializer_kcalcore_SRCS}) +target_link_libraries(akonadi_serializer_kcalcore ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KCALUTILS_LIBS} ${KDEPIMLIBS_KCALCORE_LIBS} ${KDEPIMLIBS_AKONADI_LIBS}) +install(TARGETS akonadi_serializer_kcalcore DESTINATION ${PLUGIN_INSTALL_DIR}) + +set(akonadi_serializer_bookmark_SRCS akonadi_serializer_bookmark.cpp) +kde4_add_plugin(akonadi_serializer_bookmark ${SERIALIZER_TYPE} ${akonadi_serializer_bookmark_SRCS}) +target_link_libraries(akonadi_serializer_bookmark ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_AKONADI_LIBS}) +install(TARGETS akonadi_serializer_bookmark DESTINATION ${PLUGIN_INSTALL_DIR}) + +set(akonadi_serializer_contactgroup_PART_SRCS akonadi_serializer_contactgroup.cpp ) +kde4_add_plugin(akonadi_serializer_contactgroup ${SERIALIZER_TYPE} ${akonadi_serializer_contactgroup_PART_SRCS}) +target_link_libraries(akonadi_serializer_contactgroup ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KABC_LIBS} ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_KABC_LIBS} ${KDEPIMLIBS_AKONADI_CONTACT_LIBS}) +install(TARGETS akonadi_serializer_contactgroup DESTINATION ${PLUGIN_INSTALL_DIR}) + +set( akonadi_serializer_microblog_SRCS akonadi_serializer_microblog.cpp ) +kde4_add_plugin(akonadi_serializer_microblog ${SERIALIZER_TYPE} ${akonadi_serializer_microblog_SRCS}) +target_link_libraries(akonadi_serializer_microblog ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTCORE_LIBRARY} ${KDE4_KDECORE_LIBS} ${QT_QTXML_LIBRARY} ${KDEPIMLIBS_KPIMUTILS_LIBS} ${KDEPIMLIBS_MICROBLOG_LIBS}) +install(TARGETS akonadi_serializer_microblog DESTINATION ${PLUGIN_INSTALL_DIR}) + +set(akonadi_serializer_kalarm_SRCS akonadi_serializer_kalarm.cpp kaeventformatter.cpp) +kde4_add_plugin(akonadi_serializer_kalarm ${SERIALIZER_TYPE} ${akonadi_serializer_kalarm_SRCS}) +target_link_libraries(akonadi_serializer_kalarm + ${KDEPIMLIBS_KALARMCAL_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDEPIMLIBS_KCALUTILS_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDE4_KDECORE_LIBS} + ${QT_QTGUI_LIBRARY} + ) +install(TARGETS akonadi_serializer_kalarm DESTINATION ${PLUGIN_INSTALL_DIR}) + +########### install files ############### + +install( FILES + akonadi_serializer_addressee.desktop + akonadi_serializer_mail.desktop + akonadi_serializer_kcal.desktop + akonadi_serializer_kcalcore.desktop + akonadi_serializer_bookmark.desktop + akonadi_serializer_contactgroup.desktop + akonadi_serializer_microblog.desktop + akonadi_serializer_kalarm.desktop +DESTINATION ${DATA_INSTALL_DIR}/akonadi/plugins/serializer) + diff --git a/kdepim-runtime/plugins/Messages.sh b/kdepim-runtime/plugins/Messages.sh new file mode 100644 index 00000000..0e943ddf --- /dev/null +++ b/kdepim-runtime/plugins/Messages.sh @@ -0,0 +1,2 @@ +#!/bin/sh +$XGETTEXT *.cpp -o $podir/akonadi_serializer_plugins.pot diff --git a/kdepim-runtime/plugins/akonadi_serializer_addressee.cpp b/kdepim-runtime/plugins/akonadi_serializer_addressee.cpp new file mode 100644 index 00000000..bafc4106 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_addressee.cpp @@ -0,0 +1,267 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "akonadi_serializer_addressee.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace Akonadi; + +//// ItemSerializerPlugin interface + +bool SerializerPluginAddressee::deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ) +{ + Q_UNUSED( version ); + + KABC::Addressee addr; + if ( label == Item::FullPayload ) { + addr = m_converter.parseVCard( data.readAll() ); + } else if ( label == Akonadi::ContactPart::Standard ) { + addr = m_converter.parseVCard( data.readAll() ); + + // remove pictures and sound + addr.setPhoto( KABC::Picture() ); + addr.setLogo( KABC::Picture() ); + addr.setSound( KABC::Sound() ); + } else if ( label == Akonadi::ContactPart::Lookup ) { + const KABC::Addressee temp = m_converter.parseVCard( data.readAll() ); + + // copy only uid, name and email addresses + addr.setUid( temp.uid() ); + addr.setPrefix( temp.prefix() ); + addr.setGivenName( temp.givenName() ); + addr.setAdditionalName( temp.additionalName() ); + addr.setFamilyName( temp.familyName() ); + addr.setSuffix( temp.suffix() ); + addr.setEmails( temp.emails() ); + } else { + return false; + } + + if ( !addr.isEmpty() ) { + item.setPayload( addr ); + } else { + kWarning( 5261 ) << "Empty addressee object!"; + } + + return true; +} + +void SerializerPluginAddressee::serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload && label != Akonadi::ContactPart::Standard && label != Akonadi::ContactPart::Lookup ) + return; + + if ( !item.hasPayload() ) + return; + + KABC::Addressee addr, temp; + + temp = item.payload(); + + if ( label == Item::FullPayload ) { + addr = temp; + } else if ( label == Akonadi::ContactPart::Standard ) { + addr = temp; + + // remove pictures and sound + addr.setPhoto( KABC::Picture() ); + addr.setLogo( KABC::Picture() ); + addr.setSound( KABC::Sound() ); + } else if ( label == Akonadi::ContactPart::Lookup ) { + // copy only uid, name and email addresses + addr.setUid( temp.uid() ); + addr.setPrefix( temp.prefix() ); + addr.setGivenName( temp.givenName() ); + addr.setAdditionalName( temp.additionalName() ); + addr.setFamilyName( temp.familyName() ); + addr.setSuffix( temp.suffix() ); + addr.setEmails( temp.emails() ); + } + + data.write( m_converter.createVCard( addr ) ); +} + +//// DifferencesAlgorithmInterface interface + +static bool compareString( const QString &left, const QString &right ) +{ + if ( left.isEmpty() && right.isEmpty() ) + return true; + else + return left == right; +} + +static QString toString( const KABC::PhoneNumber &phoneNumber ) +{ + return phoneNumber.number(); +} + +static QString toString( const KABC::Address &address ) +{ + return address.toString(); +} + +static QString toString( const QString &value ) +{ + return value; +} + +template +static void compareList( Akonadi::AbstractDifferencesReporter *reporter, const QString &id, const QList &left, const QList &right ) +{ + for ( int i = 0; i < left.count(); ++i ) { + if ( !right.contains( left[ i ] ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalLeftMode, id, toString( left[ i ] ), QString() ); + } + + for ( int i = 0; i < right.count(); ++i ) { + if ( !left.contains( right[ i ] ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalRightMode, id, QString(), toString( right[ i ] ) ); + } +} + +void SerializerPluginAddressee::compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ) +{ + Q_ASSERT( reporter ); + Q_ASSERT( leftItem.hasPayload() ); + Q_ASSERT( rightItem.hasPayload() ); + + reporter->setLeftPropertyValueTitle( i18n( "Changed Contact" ) ); + reporter->setRightPropertyValueTitle( i18n( "Conflicting Contact" ) ); + + const KABC::Addressee leftContact = leftItem.payload(); + const KABC::Addressee rightContact = rightItem.payload(); + + if ( !compareString( leftContact.uid(), rightContact.uid() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::uidLabel(), + leftContact.uid(), rightContact.uid() ); + + if ( !compareString( leftContact.name(), rightContact.name() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::nameLabel(), + leftContact.name(), rightContact.name() ); + + if ( !compareString( leftContact.formattedName(), rightContact.formattedName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::formattedNameLabel(), + leftContact.formattedName(), rightContact.formattedName() ); + + if ( !compareString( leftContact.familyName(), rightContact.familyName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::familyNameLabel(), + leftContact.familyName(), rightContact.familyName() ); + + if ( !compareString( leftContact.givenName(), rightContact.givenName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::givenNameLabel(), + leftContact.givenName(), rightContact.givenName() ); + + if ( !compareString( leftContact.additionalName(), rightContact.additionalName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::additionalNameLabel(), + leftContact.additionalName(), rightContact.additionalName() ); + + if ( !compareString( leftContact.prefix(), rightContact.prefix() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::prefixLabel(), + leftContact.prefix(), rightContact.prefix() ); + + if ( !compareString( leftContact.suffix(), rightContact.suffix() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::suffixLabel(), + leftContact.suffix(), rightContact.suffix() ); + + if ( !compareString( leftContact.nickName(), rightContact.nickName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::nickNameLabel(), + leftContact.nickName(), rightContact.nickName() ); + + if ( leftContact.birthday() != rightContact.birthday() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::birthdayLabel(), + leftContact.birthday().toString(), rightContact.birthday().toString() ); + + if ( !compareString( leftContact.mailer(), rightContact.mailer() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::mailerLabel(), + leftContact.mailer(), rightContact.mailer() ); + + if ( leftContact.timeZone() != rightContact.timeZone() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::timeZoneLabel(), + leftContact.timeZone().toString(), rightContact.timeZone().toString() ); + + if ( leftContact.geo() != rightContact.geo() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::geoLabel(), + leftContact.geo().toString(), rightContact.geo().toString() ); + + if ( !compareString( leftContact.title(), rightContact.title() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::titleLabel(), + leftContact.title(), rightContact.title() ); + + if ( !compareString( leftContact.role(), rightContact.role() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::roleLabel(), + leftContact.role(), rightContact.role() ); + + if ( !compareString( leftContact.organization(), rightContact.organization() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::organizationLabel(), + leftContact.organization(), rightContact.organization() ); + + if ( !compareString( leftContact.note(), rightContact.note() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::noteLabel(), + leftContact.note(), rightContact.note() ); + + if ( !compareString( leftContact.productId(), rightContact.productId() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::productIdLabel(), + leftContact.productId(), rightContact.productId() ); + + if ( !compareString( leftContact.sortString(), rightContact.sortString() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::sortStringLabel(), + leftContact.sortString(), rightContact.sortString() ); + + if ( leftContact.secrecy() != rightContact.secrecy() ) { + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::secrecyLabel(), + leftContact.secrecy().toString(), rightContact.secrecy().toString() ); + } + + if ( leftContact.url() != rightContact.url() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, KABC::Addressee::urlLabel(), + leftContact.url().prettyUrl(), rightContact.url().prettyUrl() ); + + compareList( reporter, i18n( "Emails" ), leftContact.emails(), rightContact.emails() ); + compareList( reporter, i18n( "Phone Numbers" ), leftContact.phoneNumbers(), rightContact.phoneNumbers() ); + compareList( reporter, i18n( "Addresses" ), leftContact.addresses(), rightContact.addresses() ); + + //TODO: logo/photo/custom entries +} + +//// GidExtractorInterface + +QString SerializerPluginAddressee::extractGid( const Item& item ) const +{ + if ( !item.hasPayload() ) { + return QString(); + } + return item.payload().uid(); +} + + +Q_EXPORT_PLUGIN2( akonadi_serializer_addressee, Akonadi::SerializerPluginAddressee ) + diff --git a/kdepim-runtime/plugins/akonadi_serializer_addressee.desktop b/kdepim-runtime/plugins/akonadi_serializer_addressee.desktop new file mode 100644 index 00000000..2955868a --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_addressee.desktop @@ -0,0 +1,101 @@ +[Misc] +Name=Addressee Serializer +Name[ar]=مسلسل المÙرسَل +Name[bs]=Serializator adresiranog +Name[ca]=Serialitzador de destinataris +Name[ca@valencia]=Serialitzador de destinataris +Name[da]=Serieordning af adressater +Name[de]=Empfänger-Serialisierung +Name[el]=ΣειÏιακοποιητής παÏαληπτών +Name[en_GB]=Addressee Serialiser +Name[es]=Serializador de destinatarios +Name[et]=Aadresside jadasti +Name[fi]=Vastaanottajaserialisoija +Name[fr]=Sérialiseur de destinataires +Name[ga]=Srathóir Seolaithe +Name[gl]=Serializador de destinatarios +Name[hu]=CímzettkezelÅ‘ +Name[ia]=Divulgator partial pro adresses +Name[it]=Serializzatore degli indirizzi +Name[ja]=å—信者用シリアライザ +Name[kk]=ÐÐ´Ñ€ÐµÑ Ñ‚Ñ–Ð·Ð±ÐµÐºÑ‚ÐµÑƒÑ–ÑˆÑ– +Name[km]=ម៉ាស៊ីន​បោះពុម្ព​អ្នក​ទទួល​សំបុážáŸ’ážš +Name[ko]=ì£¼ì†Œë¡ ì‹œë¦¬ì–¼ë¼ì´ì € +Name[lt]=Adresatų serializatorius +Name[lv]=AdreÅ¡u serializÄ“tÄjs +Name[nb]=Adressatserialisator +Name[nds]=Adressaten-Reegmoduul +Name[ne]=पà¥à¤°à¤¾à¤ªà¤• मिलानकरà¥à¤¤à¤¾ +Name[nl]=Adressenadministratie +Name[nn]=Adressatserialisator +Name[pa]=à¨à¨¡à¨°à©ˆà©±à¨¸ ਸੀਰੀਅਲਾਈਜ਼ਰ +Name[pl]=Szeregowanie adresatów +Name[pt]=Serializador de Destinatários +Name[pt_BR]=Serializador de destinatários +Name[ro]=Serializator destinatar +Name[ru]=Сохранение контактов +Name[sk]=Serializátor adresátov +Name[sl]=RazvrÅ¡Äevalnik naslovnikov v zaporedje +Name[sr]=Серијализатор адреÑаната +Name[sr@ijekavian]=Серијализатор адреÑаната +Name[sr@ijekavianlatin]=Serijalizator adresanata +Name[sr@latin]=Serijalizator adresanata +Name[sv]=Adressatserialisering +Name[tr]=Adres Sıralandırıcı +Name[uk]=Серіалізатор Ð°Ð´Ñ€ÐµÑ +Name[x-test]=xxAddressee Serializerxx +Name[zh_CN]=收信人åºåˆ—转æ¢å™¨ +Name[zh_TW]=地å€åºåˆ—器 +Comment=An Akonadi serializer plugin for addressee objects +Comment[ar]=ملحق مسلسل اكوندا لكائنات المÙرسَل +Comment[bs]=Akonadi serializator za adresirane objekte +Comment[ca]=Un connector de serialització de l'Akonadi pels objectes destinataris +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels objectes destinataris +Comment[da]=Et Akonadi-plugin til serieordning af adressatobjekter +Comment[de]=Akonadi-Modul zur Serialisierung von Adressobjekten +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή του Akonadi για αντικείμενα παÏαληπτών +Comment[en_GB]=An Akonadi serialiser plugin for addressee objects +Comment[es]=Un complemento serializador de Akonadi para objetos destinatario +Comment[et]=Akonadi aadressiobjektide jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen vastaanottajaobjekteille +Comment[fr]=Un module externe Akonadi pour la sérialisation des destinataires +Comment[ga]=Breiseán srathóra Akonadi le haghaidh seolaithe +Comment[gl]=Engadido de serialización do Akonadi para obxectos destinatario +Comment[hu]=Akonadi-modul a címzettek kezeléséhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro objectos de adresses +Comment[it]=Un'estensione di Akonadi per la serializzazione degli indirizzi +Comment[ja]=å—信者オブジェクトã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi адреÑат ныÑандарының тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​បោះពុម្ព Akonadi សម្រាប់​វážáŸ’ážáž»â€‹áž¢áŸ’នក​ទទួល​សំបុážáŸ’ážš +Comment[ko]=주소 ê°ì œë¥¼ 위한 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi adresatų objektų serializatoriaus priedas +Comment[lv]=Akonadi adreÅ¡u serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av adressat-objekter +Comment[nds]=Akonadi-Inreegmoduul för Adressaten +Comment[ne]=पà¥à¤°à¤¾à¤ªà¤• वसà¥à¤¤à¥à¤•à¤¾ लागि à¤à¤‰à¤Ÿà¤¾ à¤à¤•à¥‹à¤¨à¤¾à¤¡à¥€ मिलानकरà¥à¤¤à¤¾ पà¥à¤²à¤—इन +Comment[nl]=Een administratieplug-in voor Akonadi voor adressen +Comment[nn]=Eit Akonadi-serialisatortillegg for adressatobjekt +Comment[pa]=à¨à¨¡à¨°à©ˆà©±à¨¸ ਆਬਜੈਕਟਾਂ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Akonadi do szeregowania obiektów adresata +Comment[pt]=Um 'plugin' de serialização do Akonadi para os objectos endereçados +Comment[pt_BR]=Um plugin de serialização do Akonadi para os objetos dos destinatários +Comment[ro]=Modul de serializare Akonadi pentru obiecte „destinatar†+Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð¾Ð² Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre objekty adresátov +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov naslovnikov v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за објекте адреÑаната +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за објекте адреÑаната +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za objekte adresanata +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za objekte adresanata +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av adressatobjekt +Comment[tr]=Adres nesneleri için bir Akonadi sıralandırıcısı +Comment[uk]=Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¾Ð±'єктів адреÑатів +Comment[x-test]=xxAn Akonadi serializer plugin for addressee objectsxx +Comment[zh_CN]=对收信人对象进行åºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=地å€ç‰©ä»¶çš„ Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=text/vcard,text/directory +X-Akonadi-Class=legacy;default;KABC::Addressee; +X-KDE-Library=akonadi_serializer_addressee +X-KDE-ClassName=Akonadi::SerializerPluginAddressee diff --git a/kdepim-runtime/plugins/akonadi_serializer_addressee.h b/kdepim-runtime/plugins/akonadi_serializer_addressee.h new file mode 100644 index 00000000..89c30078 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_addressee.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2007 Till Adam + + 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_SERIALIZER_ADDRESSEE_H__ +#define __AKONADI_SERIALIZER_ADDRESSEE_H__ + +#include + +#include +#include +#include +#include + +namespace Akonadi { + +class SerializerPluginAddressee : public QObject, + public ItemSerializerPlugin, + public DifferencesAlgorithmInterface, + public GidExtractorInterface +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + Q_INTERFACES( Akonadi::DifferencesAlgorithmInterface ) + Q_INTERFACES( Akonadi::GidExtractorInterface ) + + public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); + + void compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ); + + QString extractGid( const Item& item ) const; + + private: + KABC::VCardConverter m_converter; +}; + +} + +#endif diff --git a/kdepim-runtime/plugins/akonadi_serializer_bookmark.cpp b/kdepim-runtime/plugins/akonadi_serializer_bookmark.cpp new file mode 100644 index 00000000..f61f598c --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_bookmark.cpp @@ -0,0 +1,76 @@ +/* + Copyright (c) 2007 Bruno Virlet + + 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 "akonadi_serializer_bookmark.h" + +#include +#include +#include +#include + +#include + + +using namespace Akonadi; + +#ifndef KIO_KBOOKMARK_METATYPE_DEFINED +Q_DECLARE_METATYPE( KBookmark ) +#endif + +bool SerializerPluginBookmark::deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload ) + return false; + + KBookmark bk; + QMimeData *mimeData = new QMimeData(); + mimeData->setData( QString::fromLatin1( "application/x-xbel" ), data.readAll() ); + QDomDocument doc; + KBookmark::List bkl = KBookmark::List::fromMimeData( mimeData, doc ); + + if ( !bkl.isEmpty() ) + item.setPayload( bkl[0] ); + return true; +} + +void SerializerPluginBookmark::serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload ) + return; + + if ( item.mimeType() != QString::fromLatin1( "application/x-xbel" ) ) + return; + + KBookmark bk; + if ( item.hasPayload() ) + bk = item.payload(); + + QMimeData *mimeData = new QMimeData(); + bk.populateMimeData( mimeData ); + + data.write( mimeData->data( QString::fromLatin1( "application/x-xbel" ) ) ); + +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_bookmark, SerializerPluginBookmark ) + diff --git a/kdepim-runtime/plugins/akonadi_serializer_bookmark.desktop b/kdepim-runtime/plugins/akonadi_serializer_bookmark.desktop new file mode 100644 index 00000000..4b79f9c5 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_bookmark.desktop @@ -0,0 +1,99 @@ +[Misc] +Name=Bookmark serializer +Name[ar]=مسلسل العلامات +Name[bs]=Serializator oznaka +Name[ca]=Serialitzador d'adreces d'interès +Name[ca@valencia]=Serialitzador de punts +Name[da]=Serieordning af bogmærker +Name[de]=Lesezeichen-Serialisierung +Name[el]=ΣειÏιακοποιητής σελιδοδεικτών +Name[en_GB]=Bookmark serialiser +Name[es]=Serializador de marcadores +Name[et]=Järjehoidjate jadasti +Name[fi]=Kirjanmerkkiserialisoija +Name[fr]=Sérialiseur de signets +Name[ga]=Srathóir leabharmharcanna +Name[gl]=Serializador de marcadores +Name[hu]=KönyvjelzÅ‘kezelÅ‘ +Name[ia]=Divulgator partial de favoritos +Name[it]=Serializzatore dei segnalibri +Name[ja]=ブックマーク用シリアライザ +Name[kk]=Бетбелгі тізбектеуіші +Name[km]=ម៉ាស៊ីន​បោះពុម្ព​ចំណាំ +Name[ko]=책갈피 시리얼ë¼ì´ì € +Name[lt]=Žymelių serializatorius +Name[lv]=GrÄmatzÄ«mju serializÄ“tÄjs +Name[nb]=Bokmerkeserialisator +Name[nds]=Leesteken-Reegmoduul +Name[nl]=Bladwijzeradministratie +Name[nn]=Bokmerkeserialisator +Name[pa]=ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਸੀਰੀਲਾਈਜ਼ਰ +Name[pl]=Szeregowanie zakÅ‚adek +Name[pt]=Serializador do favorito +Name[pt_BR]=Serializador de favoritos +Name[ro]=Serializator semne de carte +Name[ru]=Сохранение закладок +Name[sk]=Serializátor záložiek +Name[sl]=RazvrÅ¡Äevalnik zaznamkov v zaporedje +Name[sr]=Серијализатор обележивача +Name[sr@ijekavian]=Серијализатор обиљеживача +Name[sr@ijekavianlatin]=Serijalizator obilježivaÄa +Name[sr@latin]=Serijalizator obeleživaÄa +Name[sv]=Bokmärkesserialisering +Name[tr]=Yer imi sıralandırıcı +Name[uk]=Серіалізатор закладок +Name[x-test]=xxBookmark serializerxx +Name[zh_CN]=书签åºåˆ—转æ¢å™¨ +Name[zh_TW]=書籤åºåˆ—器 +Comment=An Akonadi serializer plugin for bookmark objects +Comment[ar]=ملحق مسلسل اكوندا لكائنات العلامة +Comment[bs]=Akonadi dodatak serializatora za oznacene objekte +Comment[ca]=Un connector de serialització de l'Akonadi pels objectes d'adreces d'interès +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels objectes punts +Comment[da]=Et Akonadi-plugin til serieordning af bogmærkeobjekter +Comment[de]=Akonadi-Modul zur Serialisierung von Lesezeichen +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για αντικείμενα σελιδοδεικτών +Comment[en_GB]=An Akonadi serialiser plugin for bookmark objects +Comment[es]=Un complemento serializador de Akonadi para objetos marcador +Comment[et]=Akonadi järjehoidjaobjektide jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen kirjanmerkkiobjekteille +Comment[fr]=Un module externe Akonadi pour la sérialisation des signets +Comment[ga]=Breiseán srathóra Akonadi le haghaidh leabharmharcanna +Comment[gl]=Engadido de serialización do Akonadi para obxectos marcador +Comment[hu]=Akonadi-modul könyvjelzÅ‘k kezeléséhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro objectos favorite +Comment[it]=Un'estensione di Akonadi per la serializzazione dei segnalibri +Comment[ja]=ブックマークオブジェクトã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi бетбелгі ныÑандарының тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​បោះពុម្ព Akonadi សម្រាប់​វážáŸ’ážáž»â€‹áž…ំណាំ +Comment[ko]=책갈피 ê°ì²´ë¥¼ 위한 Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis žymelių objektams +Comment[lv]=Akonadi grÄmatzÄ«mju serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av bokmerke-objekter +Comment[nds]=Akonadi-Inreegmoduul för Leestekens +Comment[nl]=Een administratieplug-in voor Akonadi voor bladwijzers +Comment[nn]=Eit Akonadi-serialisatortillegg for bokmerkeobjekt +Comment[pa]=ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਆਬਜੈਕਟਾਂ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Akonadi do szeregowania obiektów zakÅ‚adek +Comment[pt]=Um 'plugin' de serialização do Akonadi para os objectos de favoritos +Comment[pt_BR]=Um plugin de serialização do Akonadi para os objetos dos favoritos +Comment[ro]=Modul de serializare Akonadi pentru obiecte „semn de carte†+Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð·Ð°ÐºÐ»Ð°Ð´Ð¾Ðº Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre objekty záložiek +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov zaznamkov v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за објекте обележивача +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за објекте обиљеживача +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za objekte obilježivaÄa +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za objekte obeleživaÄa +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av bokmärkesobjekt +Comment[tr]=Yer imi nesneleri için bir Akonadi sıralandırıcısı +Comment[uk]=Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¾Ð±'єктів закладок +Comment[x-test]=xxAn Akonadi serializer plugin for bookmark objectsxx +Comment[zh_CN]=对书签对象进行åºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=書籤物件的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=application/x-xbel +X-Akonadi-Class=legacy;default;KBookmark; +X-KDE-Library=akonadi_serializer_bookmark +X-KDE-ClassName=Akonadi::SerializerPluginBookmark diff --git a/kdepim-runtime/plugins/akonadi_serializer_bookmark.h b/kdepim-runtime/plugins/akonadi_serializer_bookmark.h new file mode 100644 index 00000000..96a2ae18 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_bookmark.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2007 Bruno Virlet + + 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_SERIALIZER_BOOKMARK_H__ +#define __AKONADI_SERIALIZER_BOOKMARK_H__ + +#include + +#include + +class QIODevice; +class QString; + +namespace Akonadi { + +class Item; + +class SerializerPluginBookmark : public QObject, public ItemSerializerPlugin +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + +public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); +}; + +} + +#endif diff --git a/kdepim-runtime/plugins/akonadi_serializer_contactgroup.cpp b/kdepim-runtime/plugins/akonadi_serializer_contactgroup.cpp new file mode 100644 index 00000000..19d27286 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_contactgroup.cpp @@ -0,0 +1,134 @@ +/* + Copyright (c) 2008 Kevin Krammer + + 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 "akonadi_serializer_contactgroup.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace Akonadi; + +//// ItemSerializerPlugin interface + +bool SerializerPluginContactGroup::deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ) +{ + Q_UNUSED( label ); + Q_UNUSED( version ); + + KABC::ContactGroup contactGroup; + + if ( !KABC::ContactGroupTool::convertFromXml( &data, contactGroup ) ) { + // TODO: error reporting + return false; + } + + item.setPayload( contactGroup ); + + return true; +} + +void SerializerPluginContactGroup::serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ) +{ + Q_UNUSED( label ); + Q_UNUSED( version ); + + if ( !item.hasPayload() ) + return; + + KABC::ContactGroupTool::convertToXml( item.payload(), &data ); +} + +//// DifferencesAlgorithmInterface interface + +static bool compareString( const QString &left, const QString &right ) +{ + if ( left.isEmpty() && right.isEmpty() ) + return true; + else + return left == right; +} + +static QString toString( const KABC::Addressee &contact ) +{ + return contact.fullEmail(); +} + +template +static void compareList( AbstractDifferencesReporter *reporter, const QString &id, const QList &left, const QList &right ) +{ + for ( int i = 0; i < left.count(); ++i ) { + if ( !right.contains( left[ i ] ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalLeftMode, id, toString( left[ i ] ), QString() ); + } + + for ( int i = 0; i < right.count(); ++i ) { + if ( !left.contains( right[ i ] ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalRightMode, id, QString(), toString( right[ i ] ) ); + } +} + +void SerializerPluginContactGroup::compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ) +{ + Q_ASSERT( reporter ); + Q_ASSERT( leftItem.hasPayload() ); + Q_ASSERT( rightItem.hasPayload() ); + + reporter->setLeftPropertyValueTitle( i18n( "Changed Contact Group" ) ); + reporter->setRightPropertyValueTitle( i18n( "Conflicting Contact Group" ) ); + + const KABC::ContactGroup leftContactGroup = leftItem.payload(); + const KABC::ContactGroup rightContactGroup = rightItem.payload(); + + if ( !compareString( leftContactGroup.name(), rightContactGroup.name() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Name" ), + leftContactGroup.name(), rightContactGroup.name() ); + + // using job->exec() is ok here, not a hot path + Akonadi::ContactGroupExpandJob *leftJob = new Akonadi::ContactGroupExpandJob( leftContactGroup ); + leftJob->exec(); + + Akonadi::ContactGroupExpandJob *rightJob = new Akonadi::ContactGroupExpandJob( rightContactGroup ); + rightJob->exec(); + + compareList( reporter, i18n( "Member" ), leftJob->contacts(), rightJob->contacts() ); +} + +//// GidExtractorInterface + +QString SerializerPluginContactGroup::extractGid( const Item &item ) const +{ + if ( !item.hasPayload() ) { + return QString(); + } + return item.payload().id(); +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_contactgroup, Akonadi::SerializerPluginContactGroup ) + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/plugins/akonadi_serializer_contactgroup.desktop b/kdepim-runtime/plugins/akonadi_serializer_contactgroup.desktop new file mode 100644 index 00000000..bd9c9abc --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_contactgroup.desktop @@ -0,0 +1,99 @@ +[Misc] +Name=Contact Group Serializer +Name[ar]=مسلسل مجموعة جهات الإتصال +Name[bs]=Serializator kontaktne grupe +Name[ca]=Serialitzador de grup de contactes +Name[ca@valencia]=Serialitzador de grup de contactes +Name[da]=Serieordning af kontaktgruppe +Name[de]=Kontaktgruppen-Serialisierung +Name[el]=ΣειÏιακοποιητής ομάδας επαφών +Name[en_GB]=Contact Group Serialiser +Name[es]=Serializador de grupos de contactos +Name[et]=Kontaktirühma jadasti +Name[fi]=Yhteystietoryhmäserialisoija +Name[fr]=Sérialiseur de groupes de contacts +Name[ga]=Srathóir Grúpa Teagmhálacha +Name[gl]=Serializador de grupos de contacto +Name[hu]=Névjegycsoport-kezelÅ‘ +Name[ia]=Divulgator partial de gruppo de contactos +Name[it]=Serializzatore gruppi di contatti +Name[ja]=連絡先グループ用シリアライザ +Name[kk]=Контакт тобын тізбектеуіші +Name[km]=អ្នក​ដាក់​លáŸážâ€‹ážŸáŸ€ážšáŸ€áž›â€‹áž€áŸ’រុម​ទំនាក់ទំនង +Name[ko]=ì—°ë½ì²˜ 그룹 시리얼ë¼ì´ì € +Name[lt]=Adresatų grupių serializatorius +Name[lv]=Kontaktu grupu serializÄ“tÄjs +Name[nb]=Kontaktgruppeserialisator +Name[nds]=Kontaktkoppel-Reegmoduul +Name[nl]=Contactgroepadministratie +Name[nn]=Kontaktgruppeserialisator +Name[pa]=ਸੰਪਰਕ ਗਰà©à©±à¨ª ਸੀਰੀਅਲਾਈਜ਼ਰ +Name[pl]=Szeregowanie grup kontaktów +Name[pt]=Serializador de Grupos de Contactos +Name[pt_BR]=Serializador de grupos de contatos +Name[ro]=Serializator grupuri de contacte +Name[ru]=Сохранение групп контактов +Name[sk]=Serializátor skupín kontaktov +Name[sl]=RazvrÅ¡Äevalnik skupin stikov v zaporedje +Name[sr]=Серијализатор група контаката +Name[sr@ijekavian]=Серијализатор група контаката +Name[sr@ijekavianlatin]=Serijalizator grupa kontakata +Name[sr@latin]=Serijalizator grupa kontakata +Name[sv]=Kontaktgruppserialisering +Name[tr]=KiÅŸi Grubu Sıralandırıcı +Name[uk]=Серіалізатор груп контактів +Name[x-test]=xxContact Group Serializerxx +Name[zh_CN]=è”系人分组åºåˆ—转æ¢å™¨ +Name[zh_TW]=è¯çµ¡äººç¾¤çµ„åºåˆ—器 +Comment=An Akonadi serializer plugin for contact group objects +Comment[ar]=ملحق مسلسل اكوندا لكائنات مجموعة الإتصال +Comment[bs]=Akonadi dodatak serializatora za objekte kontakt grupe +Comment[ca]=Un connector de serialització de l'Akonadi pels objectes de grup de contactes +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels objectes de grup de contactes +Comment[da]=Et Akonadi-plugin til serieordning af kontaktgruppeobjekter +Comment[de]=Akonadi-Modul zur Serialisierung von Kontaktgruppen +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για αντικείμενα ομάδων επαφών +Comment[en_GB]=An Akonadi serialiser plugin for contact group objects +Comment[es]=Un complemento serializador de Akonadi para objetos de grupos de contacto +Comment[et]=Akonadi kontaktirühma objektide jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen yhteystietoryhmäobjekteille +Comment[fr]=Un module externe Akonadi pour la sérialisation des groupes de contacts +Comment[ga]=Breiseán srathóra Akonadi le haghaidh grúpaí teagmhálacha +Comment[gl]=Engadido de serialización do Akonadi para obxectos de grupo de contacto +Comment[hu]=Akonadi-modul névjegycsoportok kezeléséhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro objectos de gruppo de contactos +Comment[it]=Un'estensione di Akonadi per la serializzazione dei gruppi di contatti +Comment[ja]=連絡先グループオブジェクトã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi контакт тобы ныÑандарының тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​អ្នក​ដាក់លáŸážâ€‹ážŸáŸ€ážšáŸ€áž› Akonadi សម្រាប់​វážáŸ’ážáž»â€‹áž€áŸ’រុម​ទំនាក់ទំនង +Comment[ko]=ì—°ë½ì²˜ 그룹 ê°ì²´ë¥¼ 위한 Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis adresatų grupių objektams +Comment[lv]=Akonadi kontaktu grupu serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av kontaktgruppe-objekter +Comment[nds]=Akonadi-Inreegmoduul för Kontaktkoppeln +Comment[nl]=Een administratieplug-in voor Akonadi voor contactgroepobjecten +Comment[nn]=Eit Akonadi-serialisatortillegg for kontaktgruppeobjekt +Comment[pa]=ਸੰਪਰਕ ਗਰà©à©±à¨ª ਆਬਜੈਕਟਾਂ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Akonadi do szeregowania obiektów grup kontaktów +Comment[pt]=Um 'plugin' de serialização do Akonadi para os objectos de grupos de contactos +Comment[pt_BR]=Um plugin de serialização do Akonadi para os objetos de grupos de contatos +Comment[ro]=Modul de serializare Akonadi pentru obiecte „grup de contacte†+Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¿ контактов Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre objekty skupín kontaktov +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov skupin stikov v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за објекте група контаката +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за објекте група контаката +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za objekte grupa kontakata +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za objekte grupa kontakata +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av kontaktgruppobjekt +Comment[tr]=KiÅŸi grubu nesneleri için bir Akonadi sıralandırıcısı +Comment[uk]=Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¾Ð±'єктів груп контактів +Comment[x-test]=xxAn Akonadi serializer plugin for contact group objectsxx +Comment[zh_CN]=对è”系人分组对象进行åºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=è¯çµ¡äººç¾¤çµ„物件的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=application/x-vnd.kde.contactgroup +X-Akonadi-Class=legacy;default;KABC::ContactGroup; +X-KDE-Library=akonadi_serializer_contactgroup +X-KDE-ClassName=Akonadi::SerializerPluginContactGroup diff --git a/kdepim-runtime/plugins/akonadi_serializer_contactgroup.h b/kdepim-runtime/plugins/akonadi_serializer_contactgroup.h new file mode 100644 index 00000000..5b7beaca --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_contactgroup.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2008 Kevin Krammer + + 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_SERIALIZER_CONTACTGROUP_H__ +#define __AKONADI_SERIALIZER_CONTACTGROUP_H__ + +#include + +#include +#include +#include + +namespace Akonadi { + +/** + * @since 4.2 + */ +class SerializerPluginContactGroup : public QObject, + public ItemSerializerPlugin, + public DifferencesAlgorithmInterface, + public GidExtractorInterface +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + Q_INTERFACES( Akonadi::DifferencesAlgorithmInterface ) + Q_INTERFACES( Akonadi::GidExtractorInterface ) + + public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); + + void compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ); + + QString extractGid( const Item& item ) const; +}; + +} + +#endif +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/plugins/akonadi_serializer_kalarm.cpp b/kdepim-runtime/plugins/akonadi_serializer_kalarm.cpp new file mode 100644 index 00000000..db993143 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kalarm.cpp @@ -0,0 +1,275 @@ +/* + * akonadi_serializer_kalarm.cpp - Akonadi resource serializer for KAlarm + * Copyright © 2009-2012 by David Jarvie + * + * 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 "akonadi_serializer_kalarm.h" +#include "kaeventformatter.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace Akonadi; +using namespace KAlarmCal; + + +// Convert from backend data stream to a KAEvent, and set it into the item's payload. +bool SerializerPluginKAlarm::deserialize(Item& item, const QByteArray& label, QIODevice& data, int version) +{ + Q_UNUSED(version); + + if (label != Item::FullPayload) + return false; + + KCalCore::Incidence::Ptr i = mFormat.fromString(QString::fromUtf8(data.readAll())); + if (!i) + { + kWarning(5954) << "Failed to parse incidence!"; + data.seek(0); + kWarning(5954) << QString::fromUtf8(data.readAll()); + return false; + } + if (i->type() != KCalCore::Incidence::TypeEvent) + { + kWarning(5954) << "Incidence with uid" << i->uid() << "is not an Event!"; + data.seek(0); + return false; + } + KAEvent event(i.staticCast()); + const QString mime = CalEvent::mimeType(event.category()); + if (mime.isEmpty() || !event.isValid()) + { + kWarning(5954) << "Event with uid" << event.id() << "contains no usable alarms!"; + data.seek(0); + return false; + } + event.setItemId(item.id()); + + // Set additional event data contained in attributes + if (mRegistered.isEmpty()) + { + AttributeFactory::registerAttribute(); + mRegistered = QLatin1String("x"); // set to any non-null string + } + const EventAttribute dummy; + if (item.hasAttribute(dummy.type())) + { + Attribute* a = item.attribute(dummy.type()); + if (!a) + kError(5954) << "deserialize(): Event with uid" << event.id() << "contains null attribute"; + else + { + EventAttribute* evAttr = dynamic_cast(a); + if (!evAttr) + { + // Registering EventAttribute doesn't work in the serializer + // unless the application also registers it. This doesn't + // matter unless the application uses KAEvent class. + kError(5954) << "deserialize(): Event with uid" << event.id() << "contains unknown type EventAttribute (application must call AttributeFactory::registerAttribute())"; + } + else + { + KAEvent::CmdErrType err = evAttr->commandError(); + event.setCommandError(err); + } + } + } + + item.setMimeType(mime); + item.setPayload(event); + return true; +} + +// Convert an item's KAEvent payload to backend data stream. +void SerializerPluginKAlarm::serialize(const Item& item, const QByteArray& label, QIODevice& data, int& version) +{ + Q_UNUSED(version); + + if (label != Item::FullPayload || !item.hasPayload()) + return; + const KAEvent e = item.payload(); + KCalCore::Event::Ptr kcalEvent(new KCalCore::Event); + e.updateKCalEvent(kcalEvent, KAEvent::UID_SET); + QByteArray head = "BEGIN:VCALENDAR\nPRODID:"; + head += KACalendar::icalProductId(); + head += "\nVERSION:2.0\nX-KDE-KALARM-VERSION:"; + head += KAEvent::currentCalendarVersionString(); + head += '\n'; + data.write(head); + data.write(mFormat.toString(kcalEvent.staticCast()).toUtf8()); + data.write("\nEND:VCALENDAR"); +} + +#include + +void SerializerPluginKAlarm::compare(AbstractDifferencesReporter* reporter, const Item& left, const Item& right) +{ + Q_ASSERT(reporter); + Q_ASSERT(left.hasPayload()); + Q_ASSERT(right.hasPayload()); + + KAEvent eventL = left.payload(); + KAEvent eventR = right.payload(); + // Note that event attributes are not included, since they are not part of the payload + mValueL = KAEventFormatter(eventL, false); + mValueR = KAEventFormatter(eventR, false); + + reporter->setLeftPropertyValueTitle(i18nc("@title:column", "Changed Alarm")); + reporter->setRightPropertyValueTitle(i18nc("@title:column", "Conflicting Alarm")); + + reportDifference(reporter, KAEventFormatter::Id); + if (eventL.revision() != eventR.revision()) + reportDifference(reporter, KAEventFormatter::Revision); + if (eventL.actionSubType() != eventR.actionSubType()) + reportDifference(reporter, KAEventFormatter::AlarmType); + if (eventL.category() != eventR.category()) + reportDifference(reporter, KAEventFormatter::AlarmCategory); + if (eventL.templateName() != eventR.templateName()) + reportDifference(reporter, KAEventFormatter::TemplateName); + if (eventL.createdDateTime() != eventR.createdDateTime()) + reportDifference(reporter, KAEventFormatter::CreatedTime); + if (eventL.startDateTime() != eventR.startDateTime()) + reportDifference(reporter, KAEventFormatter::StartTime); + if (eventL.templateAfterTime() != eventR.templateAfterTime()) + reportDifference(reporter, KAEventFormatter::TemplateAfterTime); + if (*eventL.recurrence() != *eventR.recurrence()) + reportDifference(reporter, KAEventFormatter::Recurrence); + if (eventL.mainDateTime(true) != eventR.mainDateTime(true)) + reportDifference(reporter, KAEventFormatter::NextRecurrence); + if (eventL.repetition() != eventR.repetition()) + reportDifference(reporter, KAEventFormatter::SubRepetition); + if (eventL.repetition().interval() != eventR.repetition().interval()) + reportDifference(reporter, KAEventFormatter::RepeatInterval); + if (eventL.repetition().count() != eventR.repetition().count()) + reportDifference(reporter, KAEventFormatter::RepeatCount); + if (eventL.nextRepetition() != eventR.nextRepetition()) + reportDifference(reporter, KAEventFormatter::NextRepetition); + if (eventL.holidaysExcluded() != eventR.holidaysExcluded()) + reportDifference(reporter, KAEventFormatter::HolidaysExcluded); + if (eventL.workTimeOnly() != eventR.workTimeOnly()) + reportDifference(reporter, KAEventFormatter::WorkTimeOnly); + if (eventL.lateCancel() != eventR.lateCancel()) + reportDifference(reporter, KAEventFormatter::LateCancel); + if (eventL.autoClose() != eventR.autoClose()) + reportDifference(reporter, KAEventFormatter::AutoClose); + if (eventL.copyToKOrganizer() != eventR.copyToKOrganizer()) + reportDifference(reporter, KAEventFormatter::CopyKOrganizer); + if (eventL.enabled() != eventR.enabled()) + reportDifference(reporter, KAEventFormatter::Enabled); + if (eventL.isReadOnly() != eventR.isReadOnly()) + reportDifference(reporter, KAEventFormatter::ReadOnly); + if (eventL.toBeArchived() != eventR.toBeArchived()) + reportDifference(reporter, KAEventFormatter::Archive); + if (eventL.customProperties() != eventR.customProperties()) + reportDifference(reporter, KAEventFormatter::CustomProperties); + if (eventL.message() != eventR.message()) + reportDifference(reporter, KAEventFormatter::MessageText); + if (eventL.fileName() != eventR.fileName()) + reportDifference(reporter, KAEventFormatter::MessageFile); + if (eventL.fgColour() != eventR.fgColour()) + reportDifference(reporter, KAEventFormatter::FgColour); + if (eventL.bgColour() != eventR.bgColour()) + reportDifference(reporter, KAEventFormatter::BgColour); + if (eventL.font() != eventR.font()) + reportDifference(reporter, KAEventFormatter::Font); + if (eventL.preAction() != eventR.preAction()) + reportDifference(reporter, KAEventFormatter::PreAction); + if (eventL.cancelOnPreActionError() != eventR.cancelOnPreActionError()) + reportDifference(reporter, KAEventFormatter::PreActionCancel); + if (eventL.dontShowPreActionError() != eventR.dontShowPreActionError()) + reportDifference(reporter, KAEventFormatter::PreActionNoError); + if (eventL.postAction() != eventR.postAction()) + reportDifference(reporter, KAEventFormatter::PostAction); + if (eventL.confirmAck() != eventR.confirmAck()) + reportDifference(reporter, KAEventFormatter::ConfirmAck); + if (eventL.kmailSerialNumber() != eventR.kmailSerialNumber()) + reportDifference(reporter, KAEventFormatter::KMailSerial); + if (eventL.beep() != eventR.beep() + || eventL.speak() != eventR.speak() + || eventL.audioFile() != eventR.audioFile()) + reportDifference(reporter, KAEventFormatter::Sound); + if (eventL.repeatSound() != eventR.repeatSound()) + reportDifference(reporter, KAEventFormatter::SoundRepeat); + if (eventL.soundVolume() != eventR.soundVolume()) + reportDifference(reporter, KAEventFormatter::SoundVolume); + if (eventL.fadeVolume() != eventR.fadeVolume()) + reportDifference(reporter, KAEventFormatter::SoundFadeVolume); + if (eventL.fadeSeconds() != eventR.fadeSeconds()) + reportDifference(reporter, KAEventFormatter::SoundFadeTime); + if (eventL.reminderMinutes() != eventR.reminderMinutes()) + reportDifference(reporter, KAEventFormatter::Reminder); + if (eventL.reminderOnceOnly() != eventR.reminderOnceOnly()) + reportDifference(reporter, KAEventFormatter::ReminderOnce); + if (eventL.deferred() != eventR.deferred()) + reportDifference(reporter, KAEventFormatter::DeferralType); + if (eventL.deferDateTime() != eventR.deferDateTime()) + reportDifference(reporter, KAEventFormatter::DeferralTime); + if (eventL.deferDefaultMinutes() != eventR.deferDefaultMinutes()) + reportDifference(reporter, KAEventFormatter::DeferDefault); + if (eventL.deferDefaultDateOnly() != eventR.deferDefaultDateOnly()) + reportDifference(reporter, KAEventFormatter::DeferDefaultDate); + if (eventL.command() != eventR.command()) + reportDifference(reporter, KAEventFormatter::Command); + if (eventL.logFile() != eventR.logFile()) + reportDifference(reporter, KAEventFormatter::LogFile); + if (eventL.commandXterm() != eventR.commandXterm()) + reportDifference(reporter, KAEventFormatter::CommandXTerm); + if (eventL.emailSubject() != eventR.emailSubject()) + reportDifference(reporter, KAEventFormatter::EmailSubject); + if (eventL.emailFromId() != eventR.emailFromId()) + reportDifference(reporter, KAEventFormatter::EmailFromId); + if (eventL.emailAddresses() != eventR.emailAddresses()) + reportDifference(reporter, KAEventFormatter::EmailTo); + if (eventL.emailBcc() != eventR.emailBcc()) + reportDifference(reporter, KAEventFormatter::EmailBcc); + if (eventL.emailMessage() != eventR.emailMessage()) + reportDifference(reporter, KAEventFormatter::EmailBody); + if (eventL.emailAttachments() != eventR.emailAttachments()) + reportDifference(reporter, KAEventFormatter::EmailAttachments); + + KLocale* locale = KGlobal::locale(); + reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18nc("@label", "Item revision"), + locale->convertDigits(QString::number(left.revision()), locale->digitSet()), + locale->convertDigits(QString::number(right.revision()), locale->digitSet())); +} + +void SerializerPluginKAlarm::reportDifference(AbstractDifferencesReporter* reporter, KAEventFormatter::Parameter id) +{ + if (mValueL.isApplicable(id) || mValueR.isApplicable(id)) + reporter->addProperty(AbstractDifferencesReporter::ConflictMode, KAEventFormatter::label(id), mValueL.value(id), mValueR.value(id)); +} + +QString SerializerPluginKAlarm::extractGid(const Item& item) const +{ + return item.hasPayload() ? item.payload().id() : QString(); +} + +Q_EXPORT_PLUGIN2(akonadi_serializer_kalarm, SerializerPluginKAlarm) + + +// vim: et sw=4: diff --git a/kdepim-runtime/plugins/akonadi_serializer_kalarm.desktop b/kdepim-runtime/plugins/akonadi_serializer_kalarm.desktop new file mode 100644 index 00000000..4c28a630 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kalarm.desktop @@ -0,0 +1,94 @@ +[Misc] +Name=KAlarm Event Serializer +Name[bs]=KAlarm dogaÄ‘ajni serializer +Name[ca]=Serialitzador d'esdeveniments del KAlarm +Name[ca@valencia]=Serialitzador d'esdeveniments del KAlarm +Name[da]=Serieordning af KAlarm-hændelser +Name[de]=Kalarm Ereignis-Serialisierung +Name[el]=ΣειÏιακοποιητής γεγονότων KAlarm +Name[en_GB]=KAlarm Event Serialiser +Name[es]=Serializador de eventos de KAlarm +Name[et]=KAlarmi sündmuste jadasti +Name[fi]=KAlarm-tapahtumasarjoittaja +Name[fr]=Sérialiseur d'évènements KAlarm +Name[ga]=Srathóir Imeachtaí KAlarm +Name[gl]=Serializador de actividades de KAlarm +Name[hu]=KAlarm eseménykezelÅ‘ +Name[ia]=Divulgator partial de evento de KAlarm +Name[it]=Serializzatore degli eventi di KAlarm +Name[ja]=KAlarm イベント用シリアライザ +Name[kk]=KAlarm оқиғалар тізбектеуіші +Name[km]=កម្មវិធី​បោះពុម្ព​ព្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸ KAlarm +Name[ko]=KAlarm ì´ë²¤íŠ¸ 시리얼ë¼ì´ì € +Name[lt]=KAlarm įvykių serializatorius +Name[lv]=KAlarm notikumu serializÄ“tÄjs +Name[nb]=KAlarm hendelsesserialisator +Name[nds]=KAlarm-Begeefnis-Reegmoduul +Name[nl]=KAlarm gebeurtenissen in volgorde zetten +Name[nn]=Serialisator for KAlarm-hendingar +Name[pa]=ਕੇਅਲਾਰਮ ਈਵੈਂਟ ਸੀਰੀਅਲਾਈਜ਼ਰ +Name[pl]=Szeregowanie zdarzeÅ„ KAlarm +Name[pt]=Serializador de Eventos do KAlarm +Name[pt_BR]=Serializador de eventos do KAlarm +Name[ru]=Сохранение Ñобытий KAlarm +Name[sk]=Serializátor udalostí KAlarm +Name[sl]=RazvrÅ¡Äevalnik dogodkov KAlarm v zaporedje +Name[sr]=Серијализатор К‑алармових догађаја +Name[sr@ijekavian]=Серијализатор К‑алармових догађаја +Name[sr@ijekavianlatin]=Serijalizator K‑alarmovih dogaÄ‘aja +Name[sr@latin]=Serijalizator K‑alarmovih dogaÄ‘aja +Name[sv]=Kalarm händelseserialisering +Name[tr]=KAlarm Olay Sıralandırıcı +Name[uk]=Серіалізатор подій KAlarm +Name[x-test]=xxKAlarm Event Serializerxx +Name[zh_CN]=KAlarm 事件åºåˆ—器 +Name[zh_TW]=KAlarm 事件åºåˆ—器 +Comment=An Akonadi serializer plugin for KAlarm events +Comment[bs]=Akonadi serializirajući dodatak za KAlarm dogaÄ‘aje +Comment[ca]=Un connector de serialització de l'Akonadi pels esdeveniments del KAlarm +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels esdeveniments del KAlarm +Comment[da]=Et Akonadi-plugin til serieordning af KAlarm-hændelser +Comment[de]=Akonadi-Modul zur Serialisierung von KAlarm-Ereignissen +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για γεγονότα KAlarm +Comment[en_GB]=An Akonadi serialiser plugin for KAlarm events +Comment[es]=Un complemento serializador de Akonadi para eventos de KAlarm +Comment[et]=Akonadi KAlarmi sündmuste jadastamisplugin +Comment[fi]=Akonadi-sarjoitusliitännäinen KAlarmin tapahtumille +Comment[fr]=Un module externe Akonadi pour la sérialisation des évènements KAlarm +Comment[ga]=Breiseán srathóra Akonadi le haghaidh imeachtaí KAlarm +Comment[gl]=Un engadido serializador do Akonadi para as actividades do KAlarm +Comment[hu]=Akonadi modul KAlarm események kezeléséhez +Comment[ia]=Un plug-in de Akonadi pro divulgar partialmente eventos de alarmas in KAlarm +Comment[it]=Un'estensione di Akonadi per la serializzazione di eventi di KAlarm +Comment[ja]=KAlarm ã®ã‚¤ãƒ™ãƒ³ãƒˆã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=KAlarm оқиғалардың Akonadi тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​​របស់​កម្មវិធី​បោះពុម្ព Akonadi សម្រាប់​ព្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸ KAlarm +Comment[ko]=KAlarm ì´ë²¤íŠ¸ë¥¼ 위한 Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis KAlarm įvykiai +Comment[lv]=Akonadi KAlarm notikumu serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av KAlarm-hendelser +Comment[nds]=Akonadi-Inreegmoduul för KAlarm-Begeefnissen +Comment[nl]=Een Akonadi-plugin voor het in volgorde zetten van KAlarm gebeurtenissen +Comment[nn]=Eit Akonadi-serialisatortillegg for KAlarm-hendingar +Comment[pl]=Wtyczka Akonadi do szeregowania zdarzeÅ„ KAlarm +Comment[pt]=Um 'plugin' de serialização do Akonadi para os eventos do KAlarm +Comment[pt_BR]=Um plugin de serialização do Akonadi para eventos do KAlarm +Comment[ru]=Модуль Akonadi ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñобытий KAlarm +Comment[sk]=Plugin serializátora Akonadi pre udalosti KAlarm +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov dogodkov KAlarm v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за К‑алармове догађаје +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за К‑алармове догађаје +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za K‑alarmove dogaÄ‘aje +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za K‑alarmove dogaÄ‘aje +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av Kalarm-händelser +Comment[tr]=KAlarm olayları için bir Akonadi sıralandırıcısı +Comment[uk]=Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ–Ð¹ KAlarm +Comment[x-test]=xxAn Akonadi serializer plugin for KAlarm eventsxx +Comment[zh_CN]=用于æ“作 KAlarm 事件的 Akonadi åºåˆ—æ’件 +Comment[zh_TW]=KAlarm 事件使用的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=application/x-vnd.kde.alarm,application/x-vnd.kde.alarm.active,application/x-vnd.kde.alarm.archived,application/x-vnd.kde.alarm.template +X-Akonadi-Class=default;KAlarmCal::KAEvent; +X-KDE-Library=akonadi_serializer_kalarm +X-KDE-ClassName=SerializerPluginKAlarm diff --git a/kdepim-runtime/plugins/akonadi_serializer_kalarm.h b/kdepim-runtime/plugins/akonadi_serializer_kalarm.h new file mode 100644 index 00000000..c534cc08 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kalarm.h @@ -0,0 +1,66 @@ +/* + * akonadi_serializer_kalarm.h - Akonadi resource serializer for KAlarm + * Copyright © 2009-2014 by David Jarvie + * + * 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_SERIALIZER_KALARM_H +#define AKONADI_SERIALIZER_KALARM_H + +#include "kaeventformatter.h" + +#include +#include +#include +#include + +#include + +namespace Akonadi +{ + class Item; + class AbstractDifferencesReporter; +} + +class SerializerPluginKAlarm : public QObject, + public Akonadi::ItemSerializerPlugin, + public Akonadi::DifferencesAlgorithmInterface, + public Akonadi::GidExtractorInterface +{ + Q_OBJECT + Q_INTERFACES(Akonadi::ItemSerializerPlugin) + Q_INTERFACES(Akonadi::DifferencesAlgorithmInterface) + Q_INTERFACES(Akonadi::GidExtractorInterface) + + public: + bool deserialize(Akonadi::Item& item, const QByteArray& label, QIODevice& data, int version); + void serialize(const Akonadi::Item& item, const QByteArray& label, QIODevice& data, int& version); + void compare(Akonadi::AbstractDifferencesReporter*, const Akonadi::Item& left, const Akonadi::Item& right); + QString extractGid(const Akonadi::Item& item) const; + + private: + void reportDifference(Akonadi::AbstractDifferencesReporter*, KAEventFormatter::Parameter); + + KCalCore::ICalFormat mFormat; + KAEventFormatter mValueL; + KAEventFormatter mValueR; + QString mRegistered; +}; + +#endif // AKONADI_SERIALIZER_KALARM_H + +// vim: et sw=4: diff --git a/kdepim-runtime/plugins/akonadi_serializer_kcal.cpp b/kdepim-runtime/plugins/akonadi_serializer_kcal.cpp new file mode 100644 index 00000000..9abdfe1f --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kcal.cpp @@ -0,0 +1,302 @@ +/* + 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 "akonadi_serializer_kcal.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +typedef boost::shared_ptr IncidencePtr; + +using namespace Akonadi; + +//// ItemSerializerPlugin interface + +bool SerializerPluginKCal::deserialize(Item & item, const QByteArray & label, QIODevice & data, int version) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload ) { + return false; + } + + KCal::Incidence* i = mFormat.fromString( QString::fromUtf8( data.readAll() ) ); + if ( !i ) { + kWarning( 5263 ) << "Failed to parse incidence!"; + data.seek( 0 ); + kWarning( 5263 ) << QString::fromUtf8( data.readAll() ); + return false; + } + item.setPayload( IncidencePtr( i ) ); + return true; +} + +void SerializerPluginKCal::serialize(const Item & item, const QByteArray & label, QIODevice & data, int &version) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload || !item.hasPayload() ) + return; + IncidencePtr i = item.payload(); + // ### I guess this can be done without hardcoding stuff + data.write( "BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\nVERSION:2.0\n" ); + data.write( mFormat.toString( i.get() ).toUtf8() ); + data.write( "\nEND:VCALENDAR" ); +} + +//// DifferencesAlgorithmInterface + +static bool compareString( const QString &left, const QString &right ) +{ + if ( left.isEmpty() && right.isEmpty() ) + return true; + else + return left == right; +} + +static QString toString( KCal::Attendee *attendee ) +{ + return attendee->name() + QLatin1Char( '<' ) + attendee->email() + QLatin1Char( '>' ); +} + +static QString toString( KCal::Alarm * ) +{ + return QString(); +} + +/* +static QString toString( KCal::Incidence * ) +{ + return QString(); +} +*/ + +static QString toString( KCal::Attachment * ) +{ + return QString(); +} + +static QString toString( const QDate &date ) +{ + return date.toString(); +} + +static QString toString( const KDateTime &dateTime ) +{ + return dateTime.dateTime().toString(); +} + +static QString toString( const QString &str ) +{ + return str; +} + +static QString toString( bool value ) +{ + if ( value ) + return i18n( "Yes" ); + else + return i18n( "No" ); +} + +template +static void compareList( AbstractDifferencesReporter *reporter, + const QString &id, + const QList &left, + const QList &right ) +{ + for ( int i = 0; i < left.count(); ++i ) { + if ( !right.contains( left[ i ] ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalLeftMode, id, toString( left[ i ] ), QString() ); + } + + for ( int i = 0; i < right.count(); ++i ) { + if ( !left.contains( right[ i ] ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalRightMode, id, QString(), toString( right[ i ] ) ); + } +} + +static void compareIncidenceBase( AbstractDifferencesReporter *reporter, + const KCal::IncidenceBase *left, + const KCal::IncidenceBase *right ) +{ + compareList( reporter, i18n( "Attendees" ), left->attendees(), right->attendees() ); + + if ( !compareString( left->organizer().fullName(), right->organizer().fullName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Organizer" ), + left->organizer().fullName(), right->organizer().fullName() ); + + if ( !compareString( left->uid(), right->uid() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "UID" ), + left->uid(), right->uid() ); + + if ( left->allDay() != right->allDay() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Is all-day" ), + toString( left->allDay() ), toString( right->allDay() ) ); + + if ( left->hasDuration() != right->hasDuration() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has duration" ), + toString( left->hasDuration() ), toString( right->hasDuration() ) ); + + if ( left->duration() != right->duration() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Duration" ), + QString::number( left->duration().asSeconds() ), QString::number( right->duration().asSeconds() ) ); +} + +static void compareIncidence( AbstractDifferencesReporter *reporter, + const KCal::Incidence *left, + const KCal::Incidence *right ) +{ + if ( !compareString( left->description(), right->description() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Description" ), + left->description(), right->description() ); + + if ( !compareString( left->summary(), right->summary() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Summary" ), + left->summary(), right->summary() ); + + if ( left->status() != right->status() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Status" ), + left->statusStr(), right->statusStr() ); + + if ( left->secrecy() != right->secrecy() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Secrecy" ), + toString( left->secrecy() ), toString( right->secrecy() ) ); + + if ( left->priority() != right->priority() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Priority" ), + toString( left->priority() ), toString( right->priority() ) ); + + if ( !compareString( left->location(), right->location() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Location" ), + left->location(), right->location() ); + + compareList( reporter, i18n( "Categories" ), left->categories(), right->categories() ); + compareList( reporter, i18n( "Alarms" ), left->alarms(), right->alarms() ); + compareList( reporter, i18n( "Resources" ), left->resources(), right->resources() ); + compareList( reporter, i18n( "Attachments" ), left->attachments(), right->attachments() ); + compareList( reporter, i18n( "Exception Dates" ), left->recurrence()->exDates(), right->recurrence()->exDates() ); + compareList( reporter, i18n( "Exception Times" ), left->recurrence()->exDateTimes(), right->recurrence()->exDateTimes() ); + // TODO: recurrence dates and date/times, exrules, rrules + + if ( left->created() != right->created() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, + i18n( "Created" ), left->created().toString(), right->created().toString() ); + + if ( !compareString( left->relatedToUid(), right->relatedToUid() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, + i18n( "Related Uid" ), left->relatedToUid(), right->relatedToUid() ); +} + +static void compareEvent( AbstractDifferencesReporter *reporter, + const KCal::Event *left, + const KCal::Event *right ) +{ + + if ( left->dtStart() != right->dtStart() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Start time" ), + left->dtStart().toString(), right->dtStart().toString() ); + + if ( left->hasEndDate() != right->hasEndDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has End Date" ), + toString( left->hasEndDate() ), toString( right->hasEndDate() ) ); + + if ( left->dtEnd() != right->dtEnd() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "End Date" ), + left->dtEnd().toString(), right->dtEnd().toString() ); + + // TODO: check transparency +} + +static void compareTodo( AbstractDifferencesReporter *reporter, + const KCal::Todo *left, + const KCal::Todo *right ) +{ + if ( left->hasStartDate() != right->hasStartDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Start Date" ), + toString( left->hasStartDate() ), toString( right->hasStartDate() ) ); + + if ( left->hasDueDate() != right->hasDueDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Due Date" ), + toString( left->hasDueDate() ), toString( right->hasDueDate() ) ); + + if ( left->dtDue() != right->dtDue() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Due Date" ), + left->dtDue().toString(), right->dtDue().toString() ); + + if ( left->hasCompletedDate() != right->hasCompletedDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Complete Date" ), + toString( left->hasCompletedDate() ), toString( right->hasCompletedDate() ) ); + + if ( left->percentComplete() != right->percentComplete() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Complete" ), + QString::number( left->percentComplete() ), QString::number( right->percentComplete() ) ); + + if ( left->completed() != right->completed() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Completed" ), + toString( left->completed() ), toString( right->completed() ) ); +} + +void SerializerPluginKCal::compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ) +{ + Q_ASSERT( reporter ); + Q_ASSERT( leftItem.hasPayload() ); + Q_ASSERT( rightItem.hasPayload() ); + + const IncidencePtr leftIncidencePtr = leftItem.payload(); + const IncidencePtr rightIncidencePtr = rightItem.payload(); + + if ( leftIncidencePtr->type() == "Event" ) { + reporter->setLeftPropertyValueTitle( i18n( "Changed Event" ) ); + reporter->setRightPropertyValueTitle( i18n( "Conflicting Event" ) ); + } else if ( leftIncidencePtr->type() == "Todo" ) { + reporter->setLeftPropertyValueTitle( i18n( "Changed Todo" ) ); + reporter->setRightPropertyValueTitle( i18n( "Conflicting Todo" ) ); + } + + compareIncidenceBase( reporter, leftIncidencePtr.get(), rightIncidencePtr.get() ); + compareIncidence( reporter, leftIncidencePtr.get(), rightIncidencePtr.get() ); + + const KCal::Event *leftEvent = dynamic_cast( leftIncidencePtr.get() ); + const KCal::Event *rightEvent = dynamic_cast( rightIncidencePtr.get() ) ; + if ( leftEvent && rightEvent ) { + compareEvent( reporter, leftEvent, rightEvent ); + } else { + const KCal::Todo *leftTodo = dynamic_cast( leftIncidencePtr.get() ); + const KCal::Todo *rightTodo = dynamic_cast( rightIncidencePtr.get() ); + if ( leftTodo && rightTodo ) { + compareTodo( reporter, leftTodo, rightTodo ); + } + } +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_kcal, SerializerPluginKCal ) + diff --git a/kdepim-runtime/plugins/akonadi_serializer_kcal.desktop b/kdepim-runtime/plugins/akonadi_serializer_kcal.desktop new file mode 100644 index 00000000..12035b6e --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kcal.desktop @@ -0,0 +1,100 @@ +[Misc] +Name=Incidence Serializer +Name[ar]=مسلسل الحدث +Name[bs]=Seralizator dogaÄ‘aja +Name[ca]=Serialitzador d'incidències +Name[ca@valencia]=Serialitzador d'incidències +Name[da]=Serieordning af hændelser +Name[de]=Ereignis-Serialisierung +Name[el]=ΣειÏιακοποιητής πεÏιστατικών +Name[en_GB]=Incidence Serialiser +Name[es]=Serializador de incidencias +Name[et]=Sündmuste jadasti +Name[fi]=Merkintäserialisoija +Name[fr]=Sérialiseur d'évènements +Name[ga]=Srathóir Imeachtaí +Name[gl]=Serializador de incidencias +Name[hu]=EseménykezelÅ‘ +Name[ia]=Divulgator partial de incidentias +Name[it]=Serializzatore occorrenze +Name[ja]=イベント用シリアライザ +Name[kk]=ІÑтерді тізбектеуіші +Name[km]=Incidence Serializer +Name[ko]=사건 시리얼ë¼ì´ì € +Name[lt]=Ä®vykių serializatorius +Name[lv]=Notikumu serializÄ“tÄjs +Name[nb]=Forekomst-serialisering +Name[nds]=Begeefnis-Reegmoduul +Name[ne]=घटना मिलानकरà¥à¤¤à¤¾ +Name[nl]=Agenda-item-administratie +Name[nn]=Hendingsserialisator +Name[pl]=Szeregowanie zdarzeÅ„ +Name[pt]=Serializador de Incidências +Name[pt_BR]=Serializador de incidências +Name[ro]=Serializator incidență +Name[ru]=Сохранение Ñобытий +Name[sk]=Serializátor výskytu +Name[sl]=RazvrÅ¡Äevalnik pojavitev v zaporedje +Name[sr]=Серијализатор Ñлучајева +Name[sr@ijekavian]=Серијализатор Ñлучајева +Name[sr@ijekavianlatin]=Serijalizator sluÄajeva +Name[sr@latin]=Serijalizator sluÄajeva +Name[sv]=Förekomstserialisering +Name[tr]=Olay Sıralandırıcısı +Name[uk]=Серіалізатор подій +Name[x-test]=xxIncidence Serializerxx +Name[zh_CN]=事件åºåˆ—转æ¢å™¨ +Name[zh_TW]=頻率åºåˆ—器 +Comment=An Akonadi serializer plugin for events, tasks and journal entries +Comment[ar]=ملحق مسلسل اكوندا لمدخلات الأحداث Ùˆ المهمات Ùˆ السّجل اليومي +Comment[bs]=Akonadi dodatak serializatora za dogaÄ‘aje, zadatke i žurnal upise +Comment[ca]=Un connector de serialització de l'Akonadi pels objectes d'incidències +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels objectes d'incidències +Comment[da]=Et Akonadi-plugin til serieordning af hændelser, opgaver og journalindgange +Comment[de]=Akonadi-Modul zur Serialisierung von Ereignissen +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για γεγονότα, εÏγασίες και καταχωÏήσεις χÏÎ¿Î½Î¹ÎºÎ¿Ï +Comment[en_GB]=An Akonadi serialiser plugin for events, tasks and journal entries +Comment[es]=Un complemento serializador de Akonadi para eventos, tareas y entradas del diario +Comment[et]=Akonadi sündmuste, ülesannete ja päevikusissekannate jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen tapahtumia, tehtäviä ja päiväkirjamerkintöjä varten. +Comment[fr]=Un module externe Akonadi pour la sérialisation des évènements +Comment[ga]=Breiseán srathóra Akonadi le haghaidh imeachtaí, tascanna, agus iontrálacha dialainne +Comment[gl]=Un complemento de serialización do Akonadi para actividades, tarefas e entradas do diario +Comment[hu]=Akonadi-modul események, feladatok és naplóbejegyzések kezeléséhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro eventos, cargas e jornales +Comment[it]=Un'estensione di Akonadi per la serializzazione di eventi, attività e diari +Comment[ja]=イベントã€ã‚¿ã‚¹ã‚¯ã€æ—¥è¨˜ã®ã‚¨ãƒ³ãƒˆãƒªã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi оқиға, тапÑырма, күнделік жазуларының тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​បោះពុម្ព Akonadi សម្រាប់​ធាážáž»â€‹â€‹áž–្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸ ភារកិច្ច និង​ទិនានុប្បវážáŸ’ážáž· +Comment[ko]=행사, ìž‘ì—…, ì €ë„ í•­ëª©ì„ ìœ„í•œ Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis įvykiams, užduotims ir dienoraÅ¡Äio įraÅ¡ams +Comment[lv]=Akonadi notikumu, uzdevumu un dienasgrÄmatas ierakstu serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av hendelser, gjøremÃ¥l og dagboksnotater +Comment[nds]=Akonadi-Inreegmoduul för Begeefnissen, Opgaven un Daagbookindrääg +Comment[ne]=घटना, कारà¥à¤¯ र जरà¥à¤¨à¤² भौचरका लागि à¤à¤‰à¤Ÿà¤¾ à¤à¤•à¥‹à¤¨à¤¾à¤¡à¥€ मिलानकरà¥à¤¤à¤¾ पà¥à¤²à¤—इन +Comment[nl]=Een administratieplug-in voor Akonadi voor evenementen, taken en journalen +Comment[nn]=Eit Akonadi-serialisatortillegg for hendingar, oppgÃ¥ver og dagboktekstar +Comment[pa]=ਈਵੈਂਟ, ਟਾਸਕ ਅਤੇ ਜਰਨਲ à¨à¨‚ਟਰੀਆਂ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Akonadi do szeregowania zdarzeÅ„, zadaÅ„ i wpisów w dzienniku +Comment[pt]=Um 'plugin' de serialização do Akonadi para os eventos, tarefas e itens do diário +Comment[pt_BR]=Um plugin de serialização do Akonadi para os eventos, tarefas e entradas do diário +Comment[ro]=Modul de serializare Akonadi pentru evenimente, sarcini È™i întregistrări de agendă +Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñобытий, задач и запиÑей дневника Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre udalosti, úlohy a položky denníka +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov pojavitev v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за догађаје, поÑлове и уноÑе дневника +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за догађаје, поÑлове и уноÑе дневника +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za dogaÄ‘aje, poslove i unose dnevnika +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za dogaÄ‘aje, poslove i unose dnevnika +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av händelser, uppgifter och journalanteckningar +Comment[tr]=Olay, görev ve günlük nesneleri için bir Akonadi sıralandırıcısı +Comment[uk]="Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ–Ð¹, завдань Ñ– запиÑів журналів" +Comment[x-test]=xxAn Akonadi serializer plugin for events, tasks and journal entriesxx +Comment[zh_CN]=对事项ã€ä»»åŠ¡å’Œæ—¥è®°é¡¹è¿›è¡Œåºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=事件ã€å·¥ä½œèˆ‡æ—¥èªŒé …目物件的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=text/calendar,application/x-vnd.akonadi.note,application/x-vnd.kde.notes +X-Akonadi-Class=legacy;KCal::Incidence*; +X-KDE-Library=akonadi_serializer_kcal +X-KDE-ClassName=Akonadi::SerializerPluginKCal diff --git a/kdepim-runtime/plugins/akonadi_serializer_kcal.h b/kdepim-runtime/plugins/akonadi_serializer_kcal.h new file mode 100644 index 00000000..4dd09c1d --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kcal.h @@ -0,0 +1,53 @@ +/* + 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_SERIALIZER_KCAL_H +#define AKONADI_SERIALIZER_KCAL_H + +#include + +#include +#include +#include + +namespace Akonadi { + +class SerializerPluginKCal : public QObject, + public ItemSerializerPlugin, + public DifferencesAlgorithmInterface + +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + Q_INTERFACES( Akonadi::DifferencesAlgorithmInterface ) + + public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); + + void compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ); + private: + KCal::ICalFormat mFormat; +}; + +} + +#endif diff --git a/kdepim-runtime/plugins/akonadi_serializer_kcalcore.cpp b/kdepim-runtime/plugins/akonadi_serializer_kcalcore.cpp new file mode 100644 index 00000000..91a76d48 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kcalcore.cpp @@ -0,0 +1,361 @@ +/* + 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 "akonadi_serializer_kcalcore.h" + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +using namespace KCalCore; +using namespace KCalUtils; +using namespace Akonadi; + +//// ItemSerializerPlugin interface + +bool SerializerPluginKCalCore::deserialize( Item &item, const QByteArray &label, + QIODevice &data, int version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload ) { + return false; + } + + qint32 type; + quint32 magic, incidenceVersion; + QDataStream input( &data ); + input >> magic; + input >> incidenceVersion; + input >> type; + data.seek( 0 ); + + Incidence::Ptr incidence; + + if (magic == IncidenceBase::magicSerializationIdentifier()) { + IncidenceBase::Ptr base; + switch ( static_cast( type ) ) { + case KCalCore::Incidence::TypeEvent: { + base = Event::Ptr( new Event() ); + break; + } + case KCalCore::Incidence::TypeTodo: { + base = Todo::Ptr( new Todo() ); + break; + } + case KCalCore::Incidence::TypeJournal: { + base = Journal::Ptr( new Journal() ); + break; + } + default: + break; + } + input >> base; + incidence = base.staticCast(); + } else { + // Use the old format + incidence = mFormat.fromString( QString::fromUtf8( data.readAll() ) ); + } + + if ( !incidence ) { + kWarning( 5263 ) << "Failed to parse incidence! Item id = " << item.id() + << "Storage collection id " << item.storageCollectionId() + << "parentCollectionId = " << item.parentCollection().id(); + data.seek( 0 ); + kWarning( 5263 ) << QString::fromUtf8( data.readAll() ); + return false; + } + + item.setPayload( incidence ); + return true; +} + +void SerializerPluginKCalCore::serialize( const Item &item, + const QByteArray &label, + QIODevice &data, int &version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload || !item.hasPayload() ) + return; + Incidence::Ptr i = item.payload(); + + // Using an env variable for now while testing + if (qgetenv("KCALCORE_BINARY_SERIALIZER") == QByteArray("1")) { + QDataStream output(&data); + IncidenceBase::Ptr base = i; + output << base; + } else { + // ### I guess this can be done without hardcoding stuff + data.write( "BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN\nVERSION:2.0\nX-KDE-ICAL-IMPLEMENTATION-VERSION:1.0\n" ); + data.write( mFormat.toRawString( i ) ); + data.write( "\nEND:VCALENDAR" ); + } +} + +//// DifferencesAlgorithmInterface + +static bool compareString( const QString &left, const QString &right ) +{ + if ( left.isEmpty() && right.isEmpty() ) + return true; + else + return left == right; +} + +static QString toString( const Attendee::Ptr &attendee ) +{ + return attendee->name() + QLatin1Char( '<' ) + attendee->email() + QLatin1Char( '>' ); +} + +static QString toString( const Alarm::Ptr & ) +{ + return QString(); +} + +/* +static QString toString( const Incidence::Ptr & ) +{ + return QString(); +} +*/ + +static QString toString( const Attachment::Ptr & ) +{ + return QString(); +} + +static QString toString( const QDate &date ) +{ + return date.toString(); +} + +static QString toString( const KDateTime &dateTime ) +{ + return dateTime.dateTime().toString(); +} + +static QString toString( const QString &str ) +{ + return str; +} + +static QString toString( bool value ) +{ + if ( value ) + return i18n( "Yes" ); + else + return i18n( "No" ); +} + +template +static void compareList( AbstractDifferencesReporter *reporter, + const QString &id, + const C &left, + const C &right ) +{ + for ( typename C::const_iterator it = left.begin(), end = left.end() ; it != end ; ++it ) { + if ( !right.contains( *it ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalLeftMode, id, toString( *it ), QString() ); + } + + for ( typename C::const_iterator it = right.begin(), end = right.end() ; it != end ; ++it ) { + if ( !left.contains( *it ) ) + reporter->addProperty( AbstractDifferencesReporter::AdditionalRightMode, id, QString(), toString( *it ) ); + } +} + +static void compareIncidenceBase( AbstractDifferencesReporter *reporter, + const IncidenceBase::Ptr &left, + const IncidenceBase::Ptr &right ) +{ + compareList( reporter, i18n( "Attendees" ), left->attendees(), right->attendees() ); + + if ( !compareString( left->organizer()->fullName(), right->organizer()->fullName() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Organizer" ), + left->organizer()->fullName(), right->organizer()->fullName() ); + + if ( !compareString( left->uid(), right->uid() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "UID" ), + left->uid(), right->uid() ); + + if ( left->allDay() != right->allDay() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Is all-day" ), + toString( left->allDay() ), toString( right->allDay() ) ); + + if ( left->hasDuration() != right->hasDuration() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has duration" ), + toString( left->hasDuration() ), toString( right->hasDuration() ) ); + + if ( left->duration() != right->duration() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Duration" ), + QString::number( left->duration().asSeconds() ), QString::number( right->duration().asSeconds() ) ); +} + +static void compareIncidence( AbstractDifferencesReporter *reporter, + const Incidence::Ptr &left, + const Incidence::Ptr &right ) +{ + if ( !compareString( left->description(), right->description() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Description" ), + left->description(), right->description() ); + + if ( !compareString( left->summary(), right->summary() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Summary" ), + left->summary(), right->summary() ); + + if ( left->status() != right->status() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Status" ), + Stringify::incidenceStatus( left ), Stringify::incidenceStatus( right ) ); + + if ( left->secrecy() != right->secrecy() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Secrecy" ), + toString( left->secrecy() ), toString( right->secrecy() ) ); + + if ( left->priority() != right->priority() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Priority" ), + toString( left->priority() ), toString( right->priority() ) ); + + if ( !compareString( left->location(), right->location() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Location" ), + left->location(), right->location() ); + + compareList( reporter, i18n( "Categories" ), left->categories(), right->categories() ); + compareList( reporter, i18n( "Alarms" ), left->alarms(), right->alarms() ); + compareList( reporter, i18n( "Resources" ), left->resources(), right->resources() ); + compareList( reporter, i18n( "Attachments" ), left->attachments(), right->attachments() ); + compareList( reporter, i18n( "Exception Dates" ), left->recurrence()->exDates(), right->recurrence()->exDates() ); + compareList( reporter, i18n( "Exception Times" ), left->recurrence()->exDateTimes(), right->recurrence()->exDateTimes() ); + // TODO: recurrence dates and date/times, exrules, rrules + + if ( left->created() != right->created() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, + i18n( "Created" ), left->created().toString(), right->created().toString() ); + + if ( !compareString( left->relatedTo(), right->relatedTo() ) ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, + i18n( "Related Uid" ), left->relatedTo(), right->relatedTo() ); +} + +static void compareEvent( AbstractDifferencesReporter *reporter, + const Event::Ptr &left, + const Event::Ptr &right ) +{ + + if ( left->dtStart() != right->dtStart() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Start time" ), + left->dtStart().toString(), right->dtStart().toString() ); + + if ( left->hasEndDate() != right->hasEndDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has End Date" ), + toString( left->hasEndDate() ), toString( right->hasEndDate() ) ); + + if ( left->dtEnd() != right->dtEnd() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "End Date" ), + left->dtEnd().toString(), right->dtEnd().toString() ); + + // TODO: check transparency +} + +static void compareTodo( AbstractDifferencesReporter *reporter, + const Todo::Ptr &left, + const Todo::Ptr &right ) +{ + if ( left->hasStartDate() != right->hasStartDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Start Date" ), + toString( left->hasStartDate() ), toString( right->hasStartDate() ) ); + + if ( left->hasDueDate() != right->hasDueDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Due Date" ), + toString( left->hasDueDate() ), toString( right->hasDueDate() ) ); + + if ( left->dtDue() != right->dtDue() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Due Date" ), + left->dtDue().toString(), right->dtDue().toString() ); + + if ( left->hasCompletedDate() != right->hasCompletedDate() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Complete Date" ), + toString( left->hasCompletedDate() ), toString( right->hasCompletedDate() ) ); + + if ( left->percentComplete() != right->percentComplete() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Complete" ), + QString::number( left->percentComplete() ), QString::number( right->percentComplete() ) ); + + if ( left->completed() != right->completed() ) + reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Completed" ), + toString( left->completed() ), toString( right->completed() ) ); +} + +void SerializerPluginKCalCore::compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ) +{ + Q_ASSERT( reporter ); + Q_ASSERT( leftItem.hasPayload() ); + Q_ASSERT( rightItem.hasPayload() ); + + const Incidence::Ptr leftIncidencePtr = leftItem.payload(); + const Incidence::Ptr rightIncidencePtr = rightItem.payload(); + + if ( leftIncidencePtr->type() == Incidence::TypeEvent ) { + reporter->setLeftPropertyValueTitle( i18n( "Changed Event" ) ); + reporter->setRightPropertyValueTitle( i18n( "Conflicting Event" ) ); + } else if ( leftIncidencePtr->type() == Incidence::TypeTodo ) { + reporter->setLeftPropertyValueTitle( i18n( "Changed Todo" ) ); + reporter->setRightPropertyValueTitle( i18n( "Conflicting Todo" ) ); + } + + compareIncidenceBase( reporter, leftIncidencePtr, rightIncidencePtr ); + compareIncidence( reporter, leftIncidencePtr, rightIncidencePtr ); + + const Event::Ptr leftEvent = leftIncidencePtr.dynamicCast() ; + const Event::Ptr rightEvent = rightIncidencePtr.dynamicCast() ; + if ( leftEvent && rightEvent ) { + compareEvent( reporter, leftEvent, rightEvent ); + } else { + const Todo::Ptr leftTodo = leftIncidencePtr.dynamicCast(); + const Todo::Ptr rightTodo = rightIncidencePtr.dynamicCast(); + if ( leftTodo && rightTodo ) { + compareTodo( reporter, leftTodo, rightTodo ); + } + } +} + +//// GidExtractorInterface + +QString SerializerPluginKCalCore::extractGid( const Item &item ) const +{ + if ( !item.hasPayload() ) { + return QString(); + } + return item.payload()->instanceIdentifier(); +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_kcalcore, SerializerPluginKCalCore ) + diff --git a/kdepim-runtime/plugins/akonadi_serializer_kcalcore.desktop b/kdepim-runtime/plugins/akonadi_serializer_kcalcore.desktop new file mode 100644 index 00000000..5f3fae6e --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kcalcore.desktop @@ -0,0 +1,100 @@ +[Misc] +Name=Incidence Serializer +Name[ar]=مسلسل الحدث +Name[bs]=Seralizator dogaÄ‘aja +Name[ca]=Serialitzador d'incidències +Name[ca@valencia]=Serialitzador d'incidències +Name[da]=Serieordning af hændelser +Name[de]=Ereignis-Serialisierung +Name[el]=ΣειÏιακοποιητής πεÏιστατικών +Name[en_GB]=Incidence Serialiser +Name[es]=Serializador de incidencias +Name[et]=Sündmuste jadasti +Name[fi]=Merkintäserialisoija +Name[fr]=Sérialiseur d'évènements +Name[ga]=Srathóir Imeachtaí +Name[gl]=Serializador de incidencias +Name[hu]=EseménykezelÅ‘ +Name[ia]=Divulgator partial de incidentias +Name[it]=Serializzatore occorrenze +Name[ja]=イベント用シリアライザ +Name[kk]=ІÑтерді тізбектеуіші +Name[km]=Incidence Serializer +Name[ko]=사건 시리얼ë¼ì´ì € +Name[lt]=Ä®vykių serializatorius +Name[lv]=Notikumu serializÄ“tÄjs +Name[nb]=Forekomst-serialisering +Name[nds]=Begeefnis-Reegmoduul +Name[ne]=घटना मिलानकरà¥à¤¤à¤¾ +Name[nl]=Agenda-item-administratie +Name[nn]=Hendingsserialisator +Name[pl]=Szeregowanie zdarzeÅ„ +Name[pt]=Serializador de Incidências +Name[pt_BR]=Serializador de incidências +Name[ro]=Serializator incidență +Name[ru]=Сохранение Ñобытий +Name[sk]=Serializátor výskytu +Name[sl]=RazvrÅ¡Äevalnik pojavitev v zaporedje +Name[sr]=Серијализатор Ñлучајева +Name[sr@ijekavian]=Серијализатор Ñлучајева +Name[sr@ijekavianlatin]=Serijalizator sluÄajeva +Name[sr@latin]=Serijalizator sluÄajeva +Name[sv]=Förekomstserialisering +Name[tr]=Olay Sıralandırıcısı +Name[uk]=Серіалізатор подій +Name[x-test]=xxIncidence Serializerxx +Name[zh_CN]=事件åºåˆ—转æ¢å™¨ +Name[zh_TW]=頻率åºåˆ—器 +Comment=An Akonadi serializer plugin for events, tasks and journal entries +Comment[ar]=ملحق مسلسل اكوندا لمدخلات الأحداث Ùˆ المهمات Ùˆ السّجل اليومي +Comment[bs]=Akonadi dodatak serializatora za dogaÄ‘aje, zadatke i žurnal upise +Comment[ca]=Un connector de serialització de l'Akonadi pels objectes d'incidències +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels objectes d'incidències +Comment[da]=Et Akonadi-plugin til serieordning af hændelser, opgaver og journalindgange +Comment[de]=Akonadi-Modul zur Serialisierung von Ereignissen +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για γεγονότα, εÏγασίες και καταχωÏήσεις χÏÎ¿Î½Î¹ÎºÎ¿Ï +Comment[en_GB]=An Akonadi serialiser plugin for events, tasks and journal entries +Comment[es]=Un complemento serializador de Akonadi para eventos, tareas y entradas del diario +Comment[et]=Akonadi sündmuste, ülesannete ja päevikusissekannate jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen tapahtumia, tehtäviä ja päiväkirjamerkintöjä varten. +Comment[fr]=Un module externe Akonadi pour la sérialisation des évènements +Comment[ga]=Breiseán srathóra Akonadi le haghaidh imeachtaí, tascanna, agus iontrálacha dialainne +Comment[gl]=Un complemento de serialización do Akonadi para actividades, tarefas e entradas do diario +Comment[hu]=Akonadi-modul események, feladatok és naplóbejegyzések kezeléséhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro eventos, cargas e jornales +Comment[it]=Un'estensione di Akonadi per la serializzazione di eventi, attività e diari +Comment[ja]=イベントã€ã‚¿ã‚¹ã‚¯ã€æ—¥è¨˜ã®ã‚¨ãƒ³ãƒˆãƒªã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi оқиға, тапÑырма, күнделік жазуларының тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​បោះពុម្ព Akonadi សម្រាប់​ធាážáž»â€‹â€‹áž–្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸ ភារកិច្ច និង​ទិនានុប្បវážáŸ’ážáž· +Comment[ko]=행사, ìž‘ì—…, ì €ë„ í•­ëª©ì„ ìœ„í•œ Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis įvykiams, užduotims ir dienoraÅ¡Äio įraÅ¡ams +Comment[lv]=Akonadi notikumu, uzdevumu un dienasgrÄmatas ierakstu serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av hendelser, gjøremÃ¥l og dagboksnotater +Comment[nds]=Akonadi-Inreegmoduul för Begeefnissen, Opgaven un Daagbookindrääg +Comment[ne]=घटना, कारà¥à¤¯ र जरà¥à¤¨à¤² भौचरका लागि à¤à¤‰à¤Ÿà¤¾ à¤à¤•à¥‹à¤¨à¤¾à¤¡à¥€ मिलानकरà¥à¤¤à¤¾ पà¥à¤²à¤—इन +Comment[nl]=Een administratieplug-in voor Akonadi voor evenementen, taken en journalen +Comment[nn]=Eit Akonadi-serialisatortillegg for hendingar, oppgÃ¥ver og dagboktekstar +Comment[pa]=ਈਵੈਂਟ, ਟਾਸਕ ਅਤੇ ਜਰਨਲ à¨à¨‚ਟਰੀਆਂ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Akonadi do szeregowania zdarzeÅ„, zadaÅ„ i wpisów w dzienniku +Comment[pt]=Um 'plugin' de serialização do Akonadi para os eventos, tarefas e itens do diário +Comment[pt_BR]=Um plugin de serialização do Akonadi para os eventos, tarefas e entradas do diário +Comment[ro]=Modul de serializare Akonadi pentru evenimente, sarcini È™i întregistrări de agendă +Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñобытий, задач и запиÑей дневника Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre udalosti, úlohy a položky denníka +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov pojavitev v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за догађаје, поÑлове и уноÑе дневника +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за догађаје, поÑлове и уноÑе дневника +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za dogaÄ‘aje, poslove i unose dnevnika +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za dogaÄ‘aje, poslove i unose dnevnika +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av händelser, uppgifter och journalanteckningar +Comment[tr]=Olay, görev ve günlük nesneleri için bir Akonadi sıralandırıcısı +Comment[uk]="Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ–Ð¹, завдань Ñ– запиÑів журналів" +Comment[x-test]=xxAn Akonadi serializer plugin for events, tasks and journal entriesxx +Comment[zh_CN]=对事项ã€ä»»åŠ¡å’Œæ—¥è®°é¡¹è¿›è¡Œåºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=事件ã€å·¥ä½œèˆ‡æ—¥èªŒé …目物件的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=text/calendar,application/x-vnd.akonadi.note,application/x-vnd.kde.notes +X-Akonadi-Class=default;KCalCore::Incidence*; +X-KDE-Library=akonadi_serializer_kcalcore +X-KDE-ClassName=Akonadi::SerializerPluginKCalCore diff --git a/kdepim-runtime/plugins/akonadi_serializer_kcalcore.h b/kdepim-runtime/plugins/akonadi_serializer_kcalcore.h new file mode 100644 index 00000000..7e99492c --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_kcalcore.h @@ -0,0 +1,59 @@ +/* + 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_SERIALIZER_KCALCORE_H +#define AKONADI_SERIALIZER_KCALCORE_H + +#include + +#include +#include +#include +#include + +namespace Akonadi { + +class SerializerPluginKCalCore : public QObject, + public ItemSerializerPlugin, + public DifferencesAlgorithmInterface, + public GidExtractorInterface + +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + Q_INTERFACES( Akonadi::DifferencesAlgorithmInterface ) + Q_INTERFACES( Akonadi::GidExtractorInterface ) + + public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); + + void compare( Akonadi::AbstractDifferencesReporter *reporter, + const Akonadi::Item &leftItem, + const Akonadi::Item &rightItem ); + + QString extractGid( const Item &item ) const; + + private: + KCalCore::ICalFormat mFormat; +}; + +} + +#endif diff --git a/kdepim-runtime/plugins/akonadi_serializer_mail.cpp b/kdepim-runtime/plugins/akonadi_serializer_mail.cpp new file mode 100644 index 00000000..a6ac4b81 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_mail.cpp @@ -0,0 +1,236 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "akonadi_serializer_mail.h" + +#include + +#include +#include +#include + +#include +#include +#include + +using namespace Akonadi; +using namespace KMime; + +QString StringPool::sharedValue( const QString &value ) +{ + QMutexLocker lock(&m_mutex); + QSet::const_iterator it = m_pool.constFind(value); + if ( it != m_pool.constEnd() ) + return *it; + m_pool.insert(value); + return value; +} + +template static void parseAddrList( const QVarLengthArray &addrList, T *hdr, + int version, StringPool& pool ) +{ + hdr->clear(); + const int count = addrList.count(); + QVarLengthArray addr; + for ( int i = 0; i < count; ++i ) { + ImapParser::parseParenthesizedList( addrList[ i ], addr ); + if ( addr.count() != 4 ) { + kWarning( 5264 ) << "Error parsing envelope address field: " << addrList[ i ]; + continue; + } + KMime::Types::Mailbox addrField; + if ( version == 0 ) + addrField.setNameFrom7Bit( addr[0] ); + else if ( version == 1 ) + addrField.setName( pool.sharedValue( QString::fromUtf8( addr[0] ) ) ); + KMime::Types::AddrSpec addrSpec; + addrSpec.localPart = pool.sharedValue( QString::fromUtf8( addr[2] ) ); + addrSpec.domain = pool.sharedValue( QString::fromUtf8( addr[3] ) ); + addrField.setAddress( addrSpec ); + hdr->addAddress( addrField ); + } +} + + +bool SerializerPluginMail::deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ) +{ + if ( label != MessagePart::Body && label != MessagePart::Envelope && label != MessagePart::Header ) + return false; + + KMime::Message::Ptr msg; + if ( !item.hasPayload() ) { + Message *m = new Message(); + msg = KMime::Message::Ptr( m ); + item.setPayload( msg ); + } else { + msg = item.payload(); + } + + QByteArray buffer = data.readAll(); + if ( buffer.isEmpty() ) + return true; + if ( label == MessagePart::Body ) { + msg->setContent( buffer ); + msg->parse(); + } else if ( label == MessagePart::Header ) { + if ( msg->body().isEmpty() && msg->contents().isEmpty() ) { + msg->setHead( buffer ); + msg->parse(); + } + } else if ( label == MessagePart::Envelope ) { + QVarLengthArray env; + ImapParser::parseParenthesizedList( buffer, env ); + if ( env.count() < 10 ) { + kWarning( 5264 ) << "Akonadi KMime Deserializer: Got invalid envelope: " << buffer; + return false; + } + Q_ASSERT( env.count() >= 10 ); + // date + msg->date()->from7BitString( env[0] ); + // subject + msg->subject()->from7BitString( env[1] ); + // from + QVarLengthArray addrList; + ImapParser::parseParenthesizedList( env[2], addrList ); + if ( !addrList.isEmpty() ) + parseAddrList( addrList, msg->from(), version, m_stringPool ); + // sender + ImapParser::parseParenthesizedList( env[3], addrList ); + if ( !addrList.isEmpty() ) + parseAddrList( addrList, msg->sender(), version, m_stringPool ); + // reply-to + ImapParser::parseParenthesizedList( env[4], addrList ); + if ( !addrList.isEmpty() ) + parseAddrList( addrList, msg->replyTo(), version, m_stringPool ); + // to + ImapParser::parseParenthesizedList( env[5], addrList ); + if ( !addrList.isEmpty() ) + parseAddrList( addrList, msg->to(), version, m_stringPool ); + // cc + ImapParser::parseParenthesizedList( env[6], addrList ); + if ( !addrList.isEmpty() ) + parseAddrList( addrList, msg->cc(), version, m_stringPool ); + // bcc + ImapParser::parseParenthesizedList( env[7], addrList ); + if ( !addrList.isEmpty() ) + parseAddrList( addrList, msg->bcc(), version, m_stringPool ); + // in-reply-to + msg->inReplyTo()->from7BitString( env[8] ); + // message id + msg->messageID()->from7BitString( env[9] ); + // references + if ( env.count() > 10 ) + msg->references()->from7BitString( env[10] ); + } + + return true; +} + +static QByteArray quoteImapListEntry( const QByteArray &b ) +{ + if ( b.isEmpty() ) + return "NIL"; + return ImapParser::quote( b ); +} + +static QByteArray buildImapList( const QList &list ) +{ + if ( list.isEmpty() ) + return "NIL"; + return QByteArray( "(" ) + ImapParser::join( list, " " ) + QByteArray( ")" ); +} + +template static QByteArray buildAddrStruct( T const *hdr ) +{ + QList addrList; + KMime::Types::Mailbox::List mb = hdr->mailboxes(); + foreach ( const KMime::Types::Mailbox &mbox, mb ) { + QList addrStruct; + addrStruct << quoteImapListEntry( mbox.name().toUtf8() ); + addrStruct << quoteImapListEntry( QByteArray() ); + addrStruct << quoteImapListEntry( mbox.addrSpec().localPart.toUtf8() ); + addrStruct << quoteImapListEntry( mbox.addrSpec().domain.toUtf8() ); + addrList << buildImapList( addrStruct ); + } + return buildImapList( addrList ); +} + +void SerializerPluginMail::serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ) +{ + version = 1; + + boost::shared_ptr m = item.payload< boost::shared_ptr >(); + if ( label == MessagePart::Body ) { + data.write( m->encodedContent() ); + } else if ( label == MessagePart::Envelope ) { + QList env; + env << quoteImapListEntry( m->date()->as7BitString( false ) ); + env << quoteImapListEntry( m->subject()->as7BitString( false ) ); + env << buildAddrStruct( m->from() ); + env << buildAddrStruct( m->sender() ); + env << buildAddrStruct( m->replyTo() ); + env << buildAddrStruct( m->to() ); + env << buildAddrStruct( m->cc() ); + env << buildAddrStruct( m->bcc() ); + env << quoteImapListEntry( m->inReplyTo()->as7BitString( false ) ); + env << quoteImapListEntry( m->messageID()->as7BitString( false ) ); + env << quoteImapListEntry( m->references()->as7BitString( false ) ); + data.write( buildImapList( env ) ); + } else if ( label == MessagePart::Header ) { + data.write( m->head() ); + } +} + +QSet SerializerPluginMail::parts( const Item &item ) const +{ + QSet set; + + if ( !item.hasPayload() ) { + return set; + } + + KMime::Message::Ptr msg = item.payload(); + if ( !msg ) { + return set; + } + + // FIXME: we really want "has any header" here, but the kmime api doesn't offer that yet + if ( msg->hasContent() || msg->hasHeader( "Message-ID" ) ) { + set << MessagePart::Envelope << MessagePart::Header; + if ( !msg->body().isEmpty() || !msg->contents().isEmpty() ) { + set << MessagePart::Body; + } + } + return set; +} + +QString SerializerPluginMail::extractGid(const Item& item) const +{ + if (!item.hasPayload()) + return QString(); + const KMime::Message::Ptr msg = item.payload(); + KMime::Headers::MessageID *mid = msg->messageID( false ); + if (mid) + return mid->asUnicodeString(); + return QString(); +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_mail, SerializerPluginMail ) + +#include "moc_akonadi_serializer_mail.cpp" diff --git a/kdepim-runtime/plugins/akonadi_serializer_mail.desktop b/kdepim-runtime/plugins/akonadi_serializer_mail.desktop new file mode 100644 index 00000000..2b354d6b --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_mail.desktop @@ -0,0 +1,101 @@ +[Misc] +Name=Mail Serializer +Name[ar]=مسلسل البريد +Name[bs]=Serializator maila +Name[ca]=Serialitzador de correu +Name[ca@valencia]=Serialitzador de correu +Name[da]=Serieordning af post +Name[de]=E-Mail-Serialisierung +Name[el]=ΣειÏιακοποιητής αλληλογÏαφίας +Name[en_GB]=Mail Serialiser +Name[es]=Serializador de correo +Name[et]=Kirjade jadasti +Name[fi]=Sähköpostiserialisoija +Name[fr]=Sérialiseur de courriers électroniques +Name[ga]=Srathóir Ríomhphoist +Name[gl]=Serializador de correo +Name[hu]=LevélkezelÅ‘ +Name[ia]=Divulgator partial de posta +Name[it]=Serializzatore posta elettronica +Name[ja]=メール用シリアライザ +Name[kk]=Пошта тізбектеуіші +Name[km]=ម៉ាស៊ីន​បោះពុម្ព​សំបុážáŸ’ážš +Name[ko]=ë©”ì¼ ì‹œë¦¬ì–¼ë¼ì´ì € +Name[lt]=PaÅ¡to serializatorius +Name[lv]=Pasta serializÄ“tÄjs +Name[nb]=E-postserialisator +Name[nds]=Nettpost-Reegmoduul +Name[ne]=पतà¥à¤° मलानकरà¥à¤¤à¤¾ +Name[nl]=E-mailadministratie +Name[nn]=E-postserialisator +Name[pa]=ਮੇਲ ਸੀਰੀਅਲਾਈਜ਼ਰ +Name[pl]=Szeregowanie poczty +Name[pt]=Serializador de Correio +Name[pt_BR]=Serializador de mensagem +Name[ro]=Serializator de poÈ™tă +Name[ru]=Сохранение пиÑем +Name[sk]=Serializátor poÅ¡ty +Name[sl]=RazvrÅ¡Äevalnik poÅ¡te v zaporedje +Name[sr]=Серијализатор поште +Name[sr@ijekavian]=Серијализатор поште +Name[sr@ijekavianlatin]=Serijalizator poÅ¡te +Name[sr@latin]=Serijalizator poÅ¡te +Name[sv]=E-postserialisering +Name[tr]=E-posta Sıralandırıcı +Name[uk]=Серіалізатор пошти +Name[x-test]=xxMail Serializerxx +Name[zh_CN]=邮件åºåˆ—转æ¢å™¨ +Name[zh_TW]=信件åºåˆ—器 +Comment=An Akonadi serializer plugin for mail objects +Comment[ar]=ملحق مسلسل اكوندا لكائنات البريد +Comment[bs]=Akonadi dodatak serializatora za mail objekte +Comment[ca]=Un connector de serialització de l'Akonadi pels objectes correu +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels objectes correu +Comment[da]=Et Akonadi-plugin til serieordning af postobjekter +Comment[de]=Akonadi-Modul zur Serialisierung von E-Mail-Objekten +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για αντικείμενα αλληλογÏαφίας +Comment[en_GB]=An Akonadi serialiser plugin for mail objects +Comment[es]=Un complemento serializador de Akonadi para objetos correo +Comment[et]=Akonadi kirjaobjektide jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen sähköpostiobjekteille +Comment[fr]=Un module externe Akonadi pour la sérialisation des courriers électroniques +Comment[ga]=Breiseán srathóra Akonadi le haghaidh ríomhphoist +Comment[gl]=Un engadido de serialización do Akonadi para obxectos correo +Comment[hu]=Akonadi-modul levelek kezeléséhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro objectos de posta +Comment[it]=Un'estensione di Akonadi per la serializzazione di posta elettronica +Comment[ja]=メールオブジェクトã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi пошта ныÑандарының тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​បោះពុម្ព Akonadi សម្រាប់​វážáŸ’ážáž»â€‹ážŸáŸ†áž”áž»ážáŸ’ážš +Comment[ko]=ë©”ì¼ ê°ì²´ë¥¼ 위한 Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis paÅ¡to objektams +Comment[lv]=Akonadi pasta serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av e-postobjekter +Comment[nds]=Akonadi-Inreegmoduul för Nettbreven +Comment[ne]=पतà¥à¤° वसà¥à¤¤à¥à¤•à¤¾ लागि à¤à¤‰à¤Ÿà¤¾ à¤à¤•à¥‹à¤¨à¤¾à¤¡à¥€ मिलानकरà¥à¤¤à¤¾ पà¥à¤²à¤—इन +Comment[nl]=Een administratieplug-in voor Akonadi voor e-mails +Comment[nn]=Eit Akonadi-serialisatortillegg for e-postobjekt +Comment[pa]=ਮੇਲ ਆਬਜੈਕਟ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ +Comment[pl]=Wtyczka Akonadi do szeregowania obiektów pocztowych +Comment[pt]=Um 'plugin' de serialização do Akonadi para os objectos de correio +Comment[pt_BR]=Um plugin de serialização do Akonadi para os objetos de mensagem +Comment[ro]=Modul de serializare Akonadi pentru obiecte poÈ™tă +Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¸Ñем Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre poÅ¡tové objekty +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov poÅ¡te v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за објекте поште +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за објекте поште +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za objekte poÅ¡te +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za objekte poÅ¡te +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av e-postobjekt +Comment[tr]=E-posta nesneleri için bir Akonadi sıralandırıcısı +Comment[uk]=Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¾Ð±'єктів пошти +Comment[x-test]=xxAn Akonadi serializer plugin for mail objectsxx +Comment[zh_CN]=对邮件对象进行åºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=信件物件的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=message/rfc822,message/news,text/x-vnd.akonadi.note +X-Akonadi-Class=legacy;default;KMime::Message*; +X-KDE-Library=akonadi_serializer_mail +X-KDE-ClassName=Akonadi::SerializerPluginMail diff --git a/kdepim-runtime/plugins/akonadi_serializer_mail.h b/kdepim-runtime/plugins/akonadi_serializer_mail.h new file mode 100644 index 00000000..edbd3402 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_mail.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2007 Till Adam + + 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_SERIALIZER_MAIL_H__ +#define __AKONADI_SERIALIZER_MAIL_H__ + +#include +#include + +#include +#include + +namespace Akonadi { + +/** + * Levare QString implicit sharing to decrease memory consumption. + * + * This class is thread safe. Apparenlty required for usage in + * legacy KRes compat bridges. + */ +class StringPool +{ +public: + /** + * Lookup @p value in the pool and return the known value + * to reuse it and leverage the implicit sharing. Otherwise + * add the value to the pool and return it again. + */ + QString sharedValue(const QString& value); +private: + QMutex m_mutex; + QSet m_pool; +}; + +class SerializerPluginMail : public QObject, public ItemSerializerPlugin, public GidExtractorInterface +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin Akonadi::GidExtractorInterface) + +public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); + QSet parts( const Item &item ) const; + QString extractGid(const Item& item) const; +private: + StringPool m_stringPool; +}; + + +} + +#endif diff --git a/kdepim-runtime/plugins/akonadi_serializer_microblog.cpp b/kdepim-runtime/plugins/akonadi_serializer_microblog.cpp new file mode 100644 index 00000000..254af08c --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_microblog.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2009 Omat Holding B.V. + + 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 "akonadi_serializer_microblog.h" + +#include +#include + +#include + +using namespace Akonadi; +using namespace Microblog; + +bool SerializerPluginmicroblog::deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload ) + return false; + + StatusItem status; + status.setData( data.readAll() ); + + item.setPayload( status ); + + return true; +} + +void SerializerPluginmicroblog::serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ) +{ + Q_UNUSED( version ); + + if ( label != Item::FullPayload || !item.hasPayload() ) + return; + + const StatusItem status = item.payload(); + data.write( status.data() ); +} + +QSet SerializerPluginmicroblog::parts( const Item &item ) const +{ + // only need to reimplement this when implementing partial serialization + // i.e. when using the "label" parameter of the other two methods + return ItemSerializerPlugin::parts( item ); +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_microblog, Akonadi::SerializerPluginmicroblog ) + diff --git a/kdepim-runtime/plugins/akonadi_serializer_microblog.desktop b/kdepim-runtime/plugins/akonadi_serializer_microblog.desktop new file mode 100644 index 00000000..6933dd48 --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_microblog.desktop @@ -0,0 +1,99 @@ +[Misc] +Name=Microblog Serializer +Name[ar]=مسلسل التدوين المصغر +Name[bs]=Siralizator mikrobloga +Name[ca]=Serialitzador de micro-blog +Name[ca@valencia]=Serialitzador de micro-blog +Name[da]=Serieordning af microblogs +Name[de]=Microblog-Serialisierung +Name[el]=ΣειÏιακοποιητής Microblog +Name[en_GB]=Microblog Serialiser +Name[es]=Serializador de Microblog +Name[et]=Mikroblogi jadasti +Name[fi]=Microblog-serialisoija +Name[fr]=Sérialiseur de microblogues +Name[ga]=Srathóir Micreabhlagadóireachta +Name[gl]=Serializador de microblogues +Name[hu]=Microblog-leképezÅ‘ modul +Name[ia]=Divulgator partial de microblog +Name[it]=Serializzatore microblog +Name[ja]=マイクロブログ用シリアライザ +Name[kk]=Микроблог +Name[km]=Microblog Serializer +Name[ko]=마ì´í¬ë¡œë¸”로그 시리얼ë¼ì´ì € +Name[lt]=Mikro dienoraÅ¡Äio serializatorius +Name[lv]=Mikroblogu serializÄ“tÄjs +Name[nb]=Mikroblogg-serialisator +Name[nds]=Lüttdaagbook-Reegmoduul +Name[nl]=Microblogadministratie +Name[nn]=Mikroblogg-serialisator +Name[pa]=ਮਾਈਕਰੋਬਲੌਗ ਸੀਰੀਅਲਾਈਜ਼ਰ +Name[pl]=Szeregowanie mikrobloga +Name[pt]=Serializador do Micro-blog +Name[pt_BR]=Serializador de microblog +Name[ro]=Serializator de microbloguri +Name[ru]=Сохранение запиÑей микроблога +Name[sk]=Serializátor mikroblogu +Name[sl]=RazvrÅ¡Äevalnik mikroblogov v zaporedje +Name[sr]=Серијализатор микроблогова +Name[sr@ijekavian]=Серијализатор микроблогова +Name[sr@ijekavianlatin]=Serijalizator mikroblogova +Name[sr@latin]=Serijalizator mikroblogova +Name[sv]=Serialisering av mikroblogg +Name[tr]=Mini Günlük Sıralandırıcı +Name[uk]=Серіалізатор мікроблогів +Name[x-test]=xxMicroblog Serializerxx +Name[zh_CN]=å¾®åšå®¢åºåˆ—转æ¢å™¨ +Name[zh_TW]=Microblog åºåˆ—器 +Comment=An Akonadi serializer plugin for Microblog +Comment[ar]=ملحق مسلسل اكوندا للتدوين المصغر +Comment[bs]=Akonadi dodatak serializatora za Microblog +Comment[ca]=Un connector de serialització de l'Akonadi pels micro-blogs +Comment[ca@valencia]=Un connector de serialització de l'Akonadi pels micro-blogs +Comment[da]=Et Akonadi-plugin til serieordning af microblogs +Comment[de]=Akonadi-Modul zur Serialisierung von Microblog-Objekten +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για το Microblog +Comment[en_GB]=An Akonadi serialiser plugin for Microblog +Comment[es]=Un complemento serializador de Akonadi para Microblog +Comment[et]=Akonadi mikroblogi jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen Microblogiin +Comment[fr]=Un module externe Akonadi pour la sérialisation de micro-blogs +Comment[ga]=Breiseán srathóra Akonadi le haghaidh micreabhlagadóireachta +Comment[gl]=Un engadido de serialización do Akonadi para Microblog +Comment[hu]=Akonadi-leképezÅ‘ modul a Microbloghoz +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro microblog +Comment[it]=Un'estensione di Akonadi per la serializzazione di microblog +Comment[ja]=マイクロブログã®ãŸã‚ã® Akonadi シリアライザプラグイン +Comment[kk]=Akonadi микроблогты тізбектеуіш плагині +Comment[km]=កម្មវិធី​ជំនួយ​​កម្មវិធី​ដាក់​លáŸážâ€‹ážŸáŸ€ážšáŸ€áž› Akonadi សម្រាប់ Microblog +Comment[ko]=마ì´í¬ë¡œë¸”로그를 위한 Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis mikro dienoraÅ¡Äiui +Comment[lv]=Akonadi Microblog serializÄ“Å¡anas spraudnis +Comment[nb]=Et Akonadi programtillegg for serialisering av Microblog +Comment[nds]=Akonadi-Inreegmoduul för Lüttdaagböker +Comment[nl]=Een administratieplug-in voor Akonadi voor microblog +Comment[nn]=Eit Akonadi-serialisatortillegg for mikrobloggar +Comment[pa]=ਮਾਈਕਰੋਬਲੌਗ ਲਈ ਅਕੌਂਡੀ ਸੀਰੀਲਾਈਜ਼ਰ +Comment[pl]=Wtyczka Akonadi do szeregowania mikrobloga +Comment[pt]=Um 'plugin' de serialização do Akonadi para o Micro-blog +Comment[pt_BR]=Um plugin de serialização do Akonadi para microblog +Comment[ro]=Modul de serializare Akonadi pentru Microblog +Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñей микроблога Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre mikroblog +Comment[sl]=Akonadijev vstavek za razvrÅ¡Äanje predmetov mikroblogov v zaporedje +Comment[sr]=Ðконадијев прикључак Ñеријализатора за микроблогове +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за микроблогове +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za mikroblogove +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za mikroblogove +Comment[sv]=Ett insticksprogram till Akonadi för serialisering av mikrobloggar +Comment[tr]=Mini günlükler için bir Akonadi sıralandırıcı +Comment[uk]=Додаток Ñеріалізації Akonadi Ð´Ð»Ñ Ð¾Ð±'єктів мікроблогів +Comment[x-test]=xxAn Akonadi serializer plugin for Microblogxx +Comment[zh_CN]=对微åšå®¢ä¿¡æ¯è¿›è¡Œè½¬æ¢çš„ Akonadi åºåˆ—器 +Comment[zh_TW]=Microblog çš„ Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=application/x-vnd.kde.microblog +X-Akonadi-Class=legacy;default;Microblog::StatusItem; +X-KDE-Library=akonadi_serializer_microblog +X-KDE-ClassName=Akonadi::SerializerPluginmicroblog diff --git a/kdepim-runtime/plugins/akonadi_serializer_microblog.h b/kdepim-runtime/plugins/akonadi_serializer_microblog.h new file mode 100644 index 00000000..e79121ce --- /dev/null +++ b/kdepim-runtime/plugins/akonadi_serializer_microblog.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2009 Omat Holding B.V. + + 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_SERIALIZER_MICROBLOG_H +#define AKONADI_SERIALIZER_MICROBLOG_H + +#include + +#include + +namespace Akonadi { + +class SerializerPluginmicroblog : public QObject, public ItemSerializerPlugin +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + +public: + bool deserialize( Item& item, const QByteArray& label, QIODevice& data, int version ); + void serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version ); + QSet parts( const Item &item ) const; +}; + +} + +#endif diff --git a/kdepim-runtime/plugins/kaeventformatter.cpp b/kdepim-runtime/plugins/kaeventformatter.cpp new file mode 100644 index 00000000..51f676e1 --- /dev/null +++ b/kdepim-runtime/plugins/kaeventformatter.cpp @@ -0,0 +1,344 @@ +/* + * kaeventformatter.cpp - converts KAlarmCal::KAEvent properties to text + * Copyright © 2010,2011 by David Jarvie + * + * 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 "kaeventformatter.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +static QString trueFalse(bool value); +static QString number(unsigned long n); +static QString minutes(int n); +static QString minutesHoursDays(int minutes); +static QString dateTime(const KDateTime&); + +KAEventFormatter::KAEventFormatter(const KAEvent& e, bool falseForUnspecified) + : mEvent(e) +{ + if (falseForUnspecified) + mUnspecifiedValue = trueFalse(false); +} + +QString KAEventFormatter::label(Parameter param) +{ + switch (param) + { + case Id: return i18nc("@label Unique identifier", "UID"); + case AlarmType: return i18nc("@label", "Alarm type"); + case AlarmCategory: return i18nc("@label", "Alarm status"); + case TemplateName: return i18nc("@label", "Template name"); + case CreatedTime: return i18nc("@label", "Creation time"); + case StartTime: return i18nc("@label", "Start time"); + case TemplateAfterTime: return i18nc("@label Start delay configured in an alarm template", "Template after time"); + case Recurs: return i18nc("@label", "Recurs"); + case Recurrence: return i18nc("@label", "Recurrence"); + case SubRepetition: return i18nc("@label", "Sub-repetition"); + case RepeatInterval: return i18nc("@label", "Sub-repetition interval"); + case RepeatCount: return i18nc("@label", "Sub-repetition count"); + case NextRepetition: return i18nc("@label", "Next sub-repetition"); + case WorkTimeOnly: return i18nc("@label", "Work time only"); + case HolidaysExcluded: return i18nc("@label", "Holidays excluded"); + case NextRecurrence: return i18nc("@label", "Next recurrence"); + case LateCancel: return i18nc("@label", "Late cancel"); + case AutoClose: return i18nc("@label Automatically close window", "Auto close"); + case CopyKOrganizer: return i18nc("@label", "Copy to KOrganizer"); + case Enabled: return i18nc("@label", "Enabled"); + case ReadOnly: return i18nc("@label", "Read-only"); + case Archive: return i18nc("@label Whether alarm should be archived", "Archive"); + case Revision: return i18nc("@label", "Revision"); + case CustomProperties: return i18nc("@label", "Custom properties"); + + case MessageText: return i18nc("@label", "Message text"); + case MessageFile: return i18nc("@label File to provide text for message", "Message file"); + case FgColour: return i18nc("@label", "Foreground color"); + case BgColour: return i18nc("@label", "Background color"); + case Font: return i18nc("@label", "Font"); + case PreAction: return i18nc("@label Shell command to execute before alarm", "Pre-alarm action"); + case PreActionCancel: return i18nc("@label", "Pre-alarm action cancel"); + case PreActionNoError: return i18nc("@label", "Pre-alarm action no error"); + case PostAction: return i18nc("@label Shell command to execute after alarm", "Post-alarm action"); + case ConfirmAck: return i18nc("@label", "Confirm acknowledgement"); + case KMailSerial: return i18nc("@label", "KMail serial number"); + case Sound: return i18nc("@label Audio method", "Sound"); + case SoundRepeat: return i18nc("@label Whether audio should repeat", "Sound repeat"); + case SoundVolume: return i18nc("@label", "Sound volume"); + case SoundFadeVolume: return i18nc("@label", "Sound fade volume"); + case SoundFadeTime: return i18nc("@label", "Sound fade time"); + case Reminder: return i18nc("@label Whether the alarm has a reminder", "Reminder"); + case ReminderOnce: return i18nc("@label Whether reminder is on first recurrence only", "Reminder once only"); + case DeferralType: return i18nc("@label Deferral type", "Deferral"); + case DeferralTime: return i18nc("@label", "Deferral time"); + case DeferDefault: return i18nc("@label Default deferral delay", "Deferral default"); + case DeferDefaultDate: return i18nc("@label Whether deferral time is date-only by default", "Deferral default date only"); + + case Command: return i18nc("@label A shell command", "Command"); + case LogFile: return i18nc("@label", "Log file"); + case CommandXTerm: return i18nc("@label Execute in terminal window", "Execute in terminal"); + + case EmailSubject: return i18nc("@label", "Email subject"); + case EmailFromId: return i18nc("@label Email address", "Email sender ID"); + case EmailTo: return i18nc("@label Email address", "Email to"); + case EmailBcc: return i18nc("@label true/false", "Email bcc"); + case EmailBody: return i18nc("@label", "Email body"); + case EmailAttachments: return i18nc("@label", "Email attachments"); + } + return QString(); +} + +bool KAEventFormatter::isApplicable(Parameter param) const +{ + switch (param) + { + case Id: + case AlarmType: + case AlarmCategory: + case CreatedTime: + case StartTime: + case Recurs: + case LateCancel: + case Enabled: + case ReadOnly: + case Archive: + case Revision: + case CustomProperties: + case CopyKOrganizer: + return true; + case TemplateName: + case TemplateAfterTime: + return mEvent.isTemplate(); + case Recurrence: + case RepeatCount: + case SubRepetition: + case WorkTimeOnly: + case HolidaysExcluded: + case NextRecurrence: + return mEvent.recurs(); + case RepeatInterval: + case NextRepetition: + return mEvent.repetition(); + case AutoClose: + return mEvent.lateCancel(); + + + case MessageText: + return mEvent.actionSubType() == KAEvent::MESSAGE; + case MessageFile: + return mEvent.actionSubType() == KAEvent::FILE; + case FgColour: + case BgColour: + case Font: + case PreAction: + case PostAction: + case ConfirmAck: + case KMailSerial: + case Reminder: + case DeferralType: + case DeferDefault: + return mEvent.actionTypes() & KAEvent::ACT_DISPLAY; + case ReminderOnce: + return mEvent.reminderMinutes() && mEvent.recurs(); + case DeferralTime: + return mEvent.deferred(); + case DeferDefaultDate: + return mEvent.deferDefaultMinutes() > 0; + case PreActionCancel: + case PreActionNoError: + return !mEvent.preAction().isEmpty(); + case Sound: + return mEvent.actionSubType() == KAEvent::MESSAGE || mEvent.actionSubType() == KAEvent::AUDIO; + case SoundRepeat: + return !mEvent.audioFile().isEmpty(); + case SoundVolume: + return mEvent.soundVolume() >= 0; + case SoundFadeVolume: + case SoundFadeTime: + return mEvent.fadeVolume() >= 0; + + case Command: + case LogFile: + case CommandXTerm: + return mEvent.actionSubType() == KAEvent::COMMAND; + + case EmailSubject: + case EmailFromId: + case EmailTo: + case EmailBcc: + case EmailBody: + case EmailAttachments: + return mEvent.actionSubType() == KAEvent::EMAIL; + } + return false; +} + +QString KAEventFormatter::value(Parameter param) const +{ + switch (param) + { + case Id: return mEvent.id(); + case AlarmType: + switch (mEvent.actionSubType()) + { + case KAEvent::MESSAGE: return i18nc("@info/plain Alarm type", "Display (text)"); + case KAEvent::FILE: return i18nc("@info/plain Alarm type", "Display (file)"); + case KAEvent::COMMAND: return mEvent.commandDisplay() + ? i18nc("@info/plain Alarm type", "Display (command)") + : i18nc("@info/plain Alarm type", "Command"); + case KAEvent::EMAIL: return i18nc("@info/plain Alarm type", "Email"); + case KAEvent::AUDIO: return i18nc("@info/plain Alarm type", "Audio"); + } + break; + case AlarmCategory: + switch (mEvent.category()) + { + case CalEvent::ACTIVE: return i18nc("@info/plain Alarm type", "Active"); + case CalEvent::ARCHIVED: return i18nc("@info/plain Alarm type", "Archived"); + case CalEvent::TEMPLATE: return i18nc("@info/plain Alarm type", "Template"); + default: + break; + } + break; + case TemplateName: return mEvent.templateName(); + case CreatedTime: return mEvent.createdDateTime().toUtc().toString(QLatin1String("%Y-%m-%d %H:%M:%SZ")); + case StartTime: return dateTime(mEvent.startDateTime().kDateTime()); + case TemplateAfterTime: return (mEvent.templateAfterTime() >= 0) ? number(mEvent.templateAfterTime()) : trueFalse(false); + case Recurs: return trueFalse(mEvent.recurs()); + case Recurrence: + { + if (mEvent.repeatAtLogin(true)) + return i18nc("@info/plain Repeat at login", "At login until %1", dateTime(mEvent.mainDateTime().kDateTime())); + KCalCore::Event::Ptr eptr(new KCalCore::Event); + mEvent.updateKCalEvent(eptr, KAEvent::UID_SET); + return KCalUtils::IncidenceFormatter::recurrenceString(eptr); + } + case NextRecurrence: return dateTime(mEvent.mainDateTime().kDateTime()); + case SubRepetition: return trueFalse(mEvent.repetition()); + case RepeatInterval: return mEvent.repetitionText(true); + case RepeatCount: return mEvent.repetition() ? number(mEvent.repetition().count()) : QString(); + case NextRepetition: return mEvent.repetition() ? number(mEvent.nextRepetition()) : QString(); + case WorkTimeOnly: return trueFalse(mEvent.workTimeOnly()); + case HolidaysExcluded: return trueFalse(mEvent.holidaysExcluded()); + case LateCancel: return mEvent.lateCancel() ? minutesHoursDays(mEvent.lateCancel()) : trueFalse(false); + case AutoClose: return trueFalse(mEvent.lateCancel() ? mEvent.autoClose() : false); + case CopyKOrganizer: return trueFalse(mEvent.copyToKOrganizer()); + case Enabled: return trueFalse(mEvent.enabled()); + case ReadOnly: return trueFalse(mEvent.isReadOnly()); + case Archive: return trueFalse(mEvent.toBeArchived()); + case Revision: return number(mEvent.revision()); + case CustomProperties: + { + if (mEvent.customProperties().isEmpty()) + return QString(); + QString value; + for (QMap::ConstIterator it = mEvent.customProperties().constBegin(); it != mEvent.customProperties().constEnd(); ++it) + value += QString::fromLatin1(it.key()) + QLatin1String(":") + it.value() + QLatin1String(""); + return i18nc("@info/plain", "%1", value); + } + + case MessageText: return (mEvent.actionSubType() == KAEvent::MESSAGE) ? mEvent.cleanText() : QString(); + case MessageFile: return (mEvent.actionSubType() == KAEvent::FILE) ? mEvent.cleanText() : QString(); + case FgColour: return mEvent.fgColour().name(); + case BgColour: return mEvent.bgColour().name(); + case Font: return mEvent.useDefaultFont() ? i18nc("@info/plain Using default font", "Default") : mEvent.font().toString(); + case PreActionCancel: return trueFalse(mEvent.cancelOnPreActionError()); + case PreActionNoError: return trueFalse(mEvent.dontShowPreActionError()); + case PreAction: return mEvent.preAction(); + case PostAction: return mEvent.postAction(); + case Reminder: return mEvent.reminderMinutes() ? minutesHoursDays(mEvent.reminderMinutes()) : trueFalse(false); + case ReminderOnce: return trueFalse(mEvent.reminderOnceOnly()); + case DeferralType: return mEvent.reminderDeferral() ? i18nc("@info/plain", "Reminder") : trueFalse(mEvent.deferred()); + case DeferralTime: return mEvent.deferred() ? dateTime(mEvent.deferDateTime().kDateTime()) : trueFalse(false); + case DeferDefault: return (mEvent.deferDefaultMinutes() > 0) ? minutes(mEvent.deferDefaultMinutes()) : trueFalse(false); + case DeferDefaultDate: return trueFalse(mEvent.deferDefaultDateOnly()); + case ConfirmAck: return trueFalse(mEvent.confirmAck()); + case KMailSerial: return mEvent.kmailSerialNumber() ? number(mEvent.kmailSerialNumber()) : trueFalse(false); + case Sound: return !mEvent.audioFile().isEmpty() ? mEvent.audioFile() + : mEvent.speak() ? i18nc("@info/plain", "Speak") + : mEvent.beep() ? i18nc("@info/plain", "Beep") : trueFalse(false); + case SoundRepeat: return trueFalse(mEvent.repeatSound()); + case SoundVolume: return mEvent.soundVolume() >= 0 + ? i18nc("@info/plain Percentage", "%1%%", static_cast(mEvent.soundVolume() * 100)) + : mUnspecifiedValue; + case SoundFadeVolume: return mEvent.fadeVolume() >= 0 + ? i18nc("@info/plain Percentage", "%1%%", static_cast(mEvent.fadeVolume() * 100)) + : mUnspecifiedValue; + case SoundFadeTime: return mEvent.fadeSeconds() + ? i18ncp("@info/plain", "1 Second", "%1 Seconds", mEvent.fadeSeconds()) + : mUnspecifiedValue; + + case Command: return (mEvent.actionSubType() == KAEvent::COMMAND) ? mEvent.cleanText() : QString(); + case LogFile: return mEvent.logFile(); + case CommandXTerm: return trueFalse(mEvent.commandXterm()); + + case EmailSubject: return mEvent.emailSubject(); + case EmailFromId: return (mEvent.actionSubType() == KAEvent::EMAIL) ? number(mEvent.emailFromId()) : QString(); + case EmailTo: return mEvent.emailAddresses(QLatin1String(", ")); + case EmailBcc: return trueFalse(mEvent.emailBcc()); + case EmailBody: return mEvent.emailMessage(); + case EmailAttachments: return mEvent.emailAttachments(QLatin1String(", ")); + } + return i18nc("@info/plain Error indication", "error!"); +} + +QString trueFalse(bool value) +{ + return value ? i18nc("@info/plain General purpose status indication: yes or no", "Yes") + : i18nc("@info/plain General purpose status indication: yes or no", "No"); +} + +// Convert an integer to digits for the locale. +// Do not use for date/time or monetary numbers (which have their own digit sets). +QString number(unsigned long n) +{ + KLocale* locale = KGlobal::locale(); + return locale->convertDigits(QString::number(n), locale->digitSet()); +} + +QString minutes(int n) +{ + return i18ncp("@info/plain", "1 Minute", "%1 Minutes", n); +} + +QString dateTime(const KDateTime& dt) +{ + if (dt.isDateOnly()) + return dt.toString(QLatin1String("%Y-%m-%d %:Z")); + else + return dt.toString(QLatin1String("%Y-%m-%d %H:%M %:Z")); +} + +QString minutesHoursDays(int minutes) +{ + if (minutes % 60) + return i18ncp("@info/plain", "1 Minute", "%1 Minutes", minutes); + else if (minutes % 1440) + return i18ncp("@info/plain", "1 Hour", "%1 Hours", minutes/60); + else + return i18ncp("@info/plain", "1 Day", "%1 Days", minutes/1440); +} + +// vim: et sw=4: diff --git a/kdepim-runtime/plugins/kaeventformatter.h b/kdepim-runtime/plugins/kaeventformatter.h new file mode 100644 index 00000000..4c111a33 --- /dev/null +++ b/kdepim-runtime/plugins/kaeventformatter.h @@ -0,0 +1,112 @@ +/* + * kaeventformatter.h - converts KAlarmCal::KAEvent properties to text + * Copyright © 2010-2011 by David Jarvie + * + * 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 KAEVENTFORMATTER_H +#define KAEVENTFORMATTER_H + +#include + +#include + +using namespace KAlarmCal; + +class KAEventFormatter +{ + public: + // KAEvent parameter identifiers. + // Note that parameters stored in Akonadi attributes are not included. + enum Parameter + { + Id, + AlarmType, + AlarmCategory, + TemplateName, + CreatedTime, + StartTime, + TemplateAfterTime, + Recurs, // does the event recur? + Recurrence, + NextRecurrence, // next alarm time excluding repetitions, including reminder/deferral + SubRepetition, // is there a sub-repetition? + RepeatInterval, + RepeatCount, + NextRepetition, // next repetition count + LateCancel, + AutoClose, + WorkTimeOnly, + HolidaysExcluded, + CopyKOrganizer, + Enabled, + ReadOnly, + Archive, + Revision, + CustomProperties, + + MessageText, + MessageFile, + FgColour, + BgColour, + Font, + PreAction, + PreActionCancel, + PreActionNoError, + PostAction, + ConfirmAck, + KMailSerial, + Sound, + SoundRepeat, + SoundVolume, + SoundFadeVolume, + SoundFadeTime, + Reminder, + ReminderOnce, + DeferralType, + DeferralTime, + DeferDefault, + DeferDefaultDate, + + Command, + LogFile, + CommandXTerm, + + EmailSubject, + EmailFromId, + EmailTo, + EmailBcc, + EmailBody, + EmailAttachments + + }; + + KAEventFormatter() {} + KAEventFormatter(const KAEvent& e, bool falseForUnspecified); + bool isApplicable(Parameter) const; + QString value(Parameter) const; + const KAEvent& event() const { return mEvent; } + static QString label(Parameter); + + private: + KAEvent mEvent; + QString mUnspecifiedValue; +}; + +#endif // KAEVENTFORMATTER_H + +// vim: et sw=4: diff --git a/kdepim-runtime/plugins/tests/CMakeLists.txt b/kdepim-runtime/plugins/tests/CMakeLists.txt new file mode 100644 index 00000000..326c506a --- /dev/null +++ b/kdepim-runtime/plugins/tests/CMakeLists.txt @@ -0,0 +1,31 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +include_directories( + ${kdepim-runtime_SOURCE_DIR}/plugins + ${kdepim-runtime_BINARY_DIR}/plugins + ${Boost_INCLUDE_DIR} +) + +# convenience macro to add akonadi qtestlib unit-tests +macro(add_akonadiplugin_test _source _libs _additionalSources) + set(_test ${_source}) + get_filename_component(_name ${_source} NAME_WE) + set(srcs ${_test} ${_additionalSources}) + kde4_add_unit_test(${_name} TESTNAME akonadiplugin-${_name} ${srcs}) + target_link_libraries(${_name} ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${KDE4_KDECORE_LIBS} + ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} ${AKONADI_COMMON_LIBRARIES} + ${AKONADI_COMMON_LIBRARIES} ${_libs}) +endmacro() + +# qtestlib unit tests +add_akonadiplugin_test(mailserializertest.cpp "${KDEPIMLIBS_KMIME_LIBS}" "") +add_akonadiplugin_test(mailserializerplugintest.cpp "${KDEPIMLIBS_KMIME_LIBS}" "") + +add_akonadiplugin_test(kcalcoreserializertest.cpp "${KDEPIMLIBS_KCALCORE_LIBS}" "") + +add_akonadiplugin_test(addresseeserializertest.cpp "${KDEPIMLIBS_KABC_LIBS};${KDEPIMLIBS_AKONADI_KABC_LIBS}" "../akonadi_serializer_addressee.cpp") + +if (KDEPIMLIBS_KCAL_LIBS) + add_akonadiplugin_test(kcalserializertest.cpp "${KDEPIMLIBS_KCAL_LIBS}" "") +endif () diff --git a/kdepim-runtime/plugins/tests/addresseeserializertest.cpp b/kdepim-runtime/plugins/tests/addresseeserializertest.cpp new file mode 100644 index 00000000..1bb81ab2 --- /dev/null +++ b/kdepim-runtime/plugins/tests/addresseeserializertest.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 +#include +#include +#include + +using namespace Akonadi; + +class AddresseeSerializerTest : public QObject +{ + Q_OBJECT + private slots: + void testGid() + { + const QString uid(QLatin1String("uid")); + KABC::Addressee addressee; + addressee.setUid(uid); + Akonadi::Item item; + item.setMimeType(addressee.mimeType()); + item.setPayload(addressee); + SerializerPluginAddressee plugin; + const QString gid = plugin.extractGid(item); + QCOMPARE(gid, uid); + } +}; + +QTEST_KDEMAIN( AddresseeSerializerTest, NoGUI ) + +#include "addresseeserializertest.moc" diff --git a/kdepim-runtime/plugins/tests/kcalcoreserializertest.cpp b/kdepim-runtime/plugins/tests/kcalcoreserializertest.cpp new file mode 100644 index 00000000..ebf928e5 --- /dev/null +++ b/kdepim-runtime/plugins/tests/kcalcoreserializertest.cpp @@ -0,0 +1,133 @@ +/* + Copyright (c) 2009 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 +#include +#include +#include + +using namespace Akonadi; +using namespace KCalCore; + +class KCalCoreSerializerTest : public QObject +{ + Q_OBJECT + private slots: + void testEventSerialize_data() + { + QTest::addColumn( "mimeType" ); + QTest::newRow( "specific" ) << "application/x-vnd.akonadi.calendar.event"; + QTest::newRow( "generic" ) << "text/calendar"; + } + void testCharsets_data() + { + testEventSerialize_data(); + } + + void testEventSerialize() + { + QFETCH( QString, mimeType ); + + QByteArray serialized = + "BEGIN:VCALENDAR\n" + "PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN\n" + "VERSION:2.0\n" + "BEGIN:VEVENT\n" + "DTSTAMP:20070109T100625Z\n" + "ORGANIZER;CN=\"Volker Krause\":MAILTO:vkrause@kde.org\n" + "CREATED:20070109T100553Z\n" + "UID:libkcal-1135684253.945\n" + "SEQUENCE:1\n" + "LAST-MODIFIED:20070109T100625Z\n" + "SUMMARY:Test event\n" + "LOCATION:here\n" + "CLASS:PUBLIC\n" + "PRIORITY:5\n" + "CATEGORIES:KDE\n" + "DTSTART:20070109T183000Z\n" + "DTEND:20070109T225900Z\n" + "TRANSP:OPAQUE\n" + "BEGIN:VALARM\n" + "DESCRIPTION:\n" + "ACTION:DISPLAY\n" + "TRIGGER;VALUE=DURATION:-PT45M\n" + "END:VALARM\n" + "END:VEVENT\n" + "END:VCALENDAR\n"; + + // deserializing + Item item; + item.setMimeType( mimeType ); + item.setPayloadFromData( serialized ); + + QVERIFY( item.hasPayload() ); + const Event::Ptr event = item.payload(); + QVERIFY( event != 0 ); + + QCOMPARE( event->summary(), QLatin1String( "Test event" ) ); + QCOMPARE( event->location(), QLatin1String( "here" ) ); + + // serializing + const QByteArray data = item.payloadData(); + QVERIFY( !data.isEmpty() ); + } + + void testCharsets() + { + QFETCH( QString, mimeType ); + + // 0 defaults to latin1. + QVERIFY( QTextCodec::codecForCStrings() == 0 ); + + const QDate currentDate = QDate::currentDate(); + + Event::Ptr event = Event::Ptr( new Event() ); + event->setUid( QLatin1String("12345") ); + event->setDtStart( KDateTime( currentDate ) ); + event->setDtEnd( KDateTime( currentDate.addDays( 1 ) ) ); + + // ü + const char latin1_umlaut[] = { 0xFC, '\0' }; + event->setSummary( QLatin1String(latin1_umlaut) ); + + Item item; + item.setMimeType( mimeType ); + item.setPayload( event ); + + // Serializer the item, the serialization should be in UTF-8: + const char utf_umlaut[] = { 0xC3, 0XBC, '\0' }; + const QByteArray bytes = item.payloadData(); + QVERIFY( bytes.contains( utf_umlaut ) ); + QVERIFY( !bytes.contains( latin1_umlaut ) ); + + // Deserialize the data: + Item item2; + item2.setMimeType( mimeType ); + item2.setPayloadFromData( bytes ); + + Event::Ptr event2 = item2.payload(); + QVERIFY( event2 != 0 ); + QVERIFY( event2->summary().toUtf8() == QByteArray( utf_umlaut ) ); + QVERIFY( event2->summary().toLatin1() == QByteArray( latin1_umlaut ) ); + } +}; + +QTEST_KDEMAIN( KCalCoreSerializerTest, NoGUI ) + +#include "kcalcoreserializertest.moc" diff --git a/kdepim-runtime/plugins/tests/kcalserializertest.cpp b/kdepim-runtime/plugins/tests/kcalserializertest.cpp new file mode 100644 index 00000000..50955cc4 --- /dev/null +++ b/kdepim-runtime/plugins/tests/kcalserializertest.cpp @@ -0,0 +1,135 @@ +/* + Copyright (c) 2009 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 +#include +#include +#include +#include + +using namespace Akonadi; +using namespace KCal; + +class KCalSerializerTest : public QObject +{ + Q_OBJECT + private slots: + void testEventSerialize_data() + { + QTest::addColumn( "mimeType" ); + QTest::newRow( "specific" ) << "application/x-vnd.akonadi.calendar.event"; + QTest::newRow( "generic" ) << "text/calendar"; + } + + void testCharsets_data() + { + testEventSerialize_data(); + } + + void testEventSerialize() + { + QFETCH( QString, mimeType ); + + QByteArray serialized = + "BEGIN:VCALENDAR\n" + "PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN\n" + "VERSION:2.0\n" + "BEGIN:VEVENT\n" + "DTSTAMP:20070109T100625Z\n" + "ORGANIZER;CN=\"Volker Krause\":MAILTO:vkrause@kde.org\n" + "CREATED:20070109T100553Z\n" + "UID:libkcal-1135684253.945\n" + "SEQUENCE:1\n" + "LAST-MODIFIED:20070109T100625Z\n" + "SUMMARY:Test event\n" + "LOCATION:here\n" + "CLASS:PUBLIC\n" + "PRIORITY:5\n" + "CATEGORIES:KDE\n" + "DTSTART:20070109T183000Z\n" + "DTEND:20070109T225900Z\n" + "TRANSP:OPAQUE\n" + "BEGIN:VALARM\n" + "DESCRIPTION:\n" + "ACTION:DISPLAY\n" + "TRIGGER;VALUE=DURATION:-PT45M\n" + "END:VALARM\n" + "END:VEVENT\n" + "END:VCALENDAR\n"; + + // deserializing + Item item; + item.setMimeType( mimeType ); + item.setPayloadFromData( serialized ); + + QVERIFY( item.hasPayload() ); + const Event::Ptr event = item.payload(); + QVERIFY( event != 0 ); + + QCOMPARE( event->summary(), QLatin1String( "Test event" ) ); + QCOMPARE( event->location(), QLatin1String( "here" ) ); + + // serializing + const QByteArray data = item.payloadData(); + QVERIFY( !data.isEmpty() ); + } + + void testCharsets() + { + QFETCH( QString, mimeType ); + + // 0 defaults to latin1. + QVERIFY( QTextCodec::codecForCStrings() == 0 ); + + const QDate currentDate = QDate::currentDate(); + + Event::Ptr event = Event::Ptr( new Event() ); + event->setUid( QLatin1String("12345") ); + event->setDtStart( KDateTime( currentDate ) ); + event->setDtEnd( KDateTime( currentDate.addDays( 1 ) ) ); + + // ü + const char latin1_umlaut[] = { 0xFC, '\0' }; + event->setSummary( QLatin1String(latin1_umlaut) ); + + Item item; + item.setMimeType( mimeType ); + item.setPayload( event ); + + // Serializer the item, the serialization should be in UTF-8: + const char utf_umlaut[] = { 0xC3, 0XBC, '\0' }; + const QByteArray bytes = item.payloadData(); + QVERIFY( bytes.contains( utf_umlaut ) ); + QVERIFY( !bytes.contains( latin1_umlaut ) ); + + // Deserialize the data: + Item item2; + item2.setMimeType( mimeType ); + item2.setPayloadFromData( bytes ); + + Event::Ptr event2 = item2.payload(); + QVERIFY( event2 != 0 ); + QVERIFY( event2->summary().toUtf8() == QByteArray( utf_umlaut ) ); + QVERIFY( event2->summary().toLatin1() == QByteArray( latin1_umlaut ) ); + } +}; + +QTEST_KDEMAIN( KCalSerializerTest, NoGUI ) + +#include "kcalserializertest.moc" diff --git a/kdepim-runtime/plugins/tests/mailserializerplugintest.cpp b/kdepim-runtime/plugins/tests/mailserializerplugintest.cpp new file mode 100644 index 00000000..7aedd8e0 --- /dev/null +++ b/kdepim-runtime/plugins/tests/mailserializerplugintest.cpp @@ -0,0 +1,95 @@ +/* + 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 "mailserializerplugintest.h" + +#include +#include +#include + +#include + +QTEST_KDEMAIN( MailSerializerPluginTest, NoGUI ) + +using namespace Akonadi; +using namespace KMime; + +void MailSerializerPluginTest::testMailPlugin() +{ + QByteArray serialized = + "From: sender@test.org\n" + "Subject: Serializer Test\n" + "To: receiver@test.org\n" + "Date: Fri, 22 Jun 2007 17:24:24 +0000\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain\n" + "\n" + "Body data."; + + // deserializing + Item item; + item.setMimeType( QLatin1String("message/rfc822") ); + item.setPayloadFromData( serialized ); + + QVERIFY( item.hasPayload() ); + KMime::Message::Ptr msg = item.payload(); + QVERIFY( msg != 0 ); + + QCOMPARE( msg->to()->asUnicodeString(), QLatin1String( "receiver@test.org" ) ); + QCOMPARE( msg->body(), QByteArray( "Body data." ) ); + + // serializing + QByteArray data = item.payloadData(); + QCOMPARE( data, serialized ); +} + +void MailSerializerPluginTest::testMessageIntegrity() +{ + // A message that will be slightly modified if KMime::Content::assemble() is + // called. We want to avoid this, because it breaks signatures. + QByteArray serialized = + "from: sender@example.com\n" + "to: receiver@example.com\n" + "Subject: Serializer Test\n" + "Date: Thu, 30 Jul 2009 13:46:31 +0300\n" + "MIME-Version: 1.0\n" + "Content-type: text/plain; charset=us-ascii\n" + "\n" + "Bla bla bla."; + + // Deserialize. + Item item; + item.setMimeType( QLatin1String("message/rfc822") ); + item.setPayloadFromData( serialized ); + + QVERIFY( item.hasPayload() ); + KMime::Message::Ptr msg = item.payload(); + QVERIFY( msg != 0 ); + + kDebug() << "original data:" << serialized; + kDebug() << "message content:" << msg->encodedContent(); + QCOMPARE( msg->encodedContent(), serialized ); + + // Serialize. + QByteArray data = item.payloadData(); + kDebug() << "original data:" << serialized; + kDebug() << "serialized data:" << data; + QCOMPARE( data, serialized ); +} + diff --git a/kdepim-runtime/plugins/tests/mailserializerplugintest.h b/kdepim-runtime/plugins/tests/mailserializerplugintest.h new file mode 100644 index 00000000..4f29674e --- /dev/null +++ b/kdepim-runtime/plugins/tests/mailserializerplugintest.h @@ -0,0 +1,34 @@ +/* + 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 MAILSERIALIZERPLUGINTEST_H +#define MAILSERIALIZERPLUGINTEST_H + +#include + +class MailSerializerPluginTest : public QObject +{ + Q_OBJECT + private slots: + void testMailPlugin(); + void testMessageIntegrity(); +}; + + +#endif diff --git a/kdepim-runtime/plugins/tests/mailserializertest.cpp b/kdepim-runtime/plugins/tests/mailserializertest.cpp new file mode 100644 index 00000000..67f39159 --- /dev/null +++ b/kdepim-runtime/plugins/tests/mailserializertest.cpp @@ -0,0 +1,264 @@ +/* + 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 "mailserializertest.h" + +#include "akonadi_serializer_mail.cpp" + +#include + +#include + +QTEST_KDEMAIN( MailSerializerTest, NoGUI ) + +void MailSerializerTest::testEnvelopeDeserialize() +{ + Item i; + i.setMimeType( QLatin1String("message/rfc822") ); + + SerializerPluginMail *serializer = new SerializerPluginMail(); + + // envelope + QByteArray env( "(\"Wed, 1 Feb 2006 13:37:19 UT\" \"IMPORTANT: Akonadi Test\" ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) NIL ((\"Ingo Kloecker\" NIL \"kloecker\" \"kde.org\")) NIL NIL NIL <{7b55527e-77f4-489d-bf18-e805be96718c}@server.kde.org>)" ); + QBuffer buffer; + buffer.setData( env ); + buffer.open( QIODevice::ReadOnly ); + buffer.seek( 0 ); + QBENCHMARK { + serializer->deserialize( i, MessagePart::Envelope, buffer, 0 ); + } + QVERIFY( i.hasPayload() ); + + KMime::Message::Ptr msg = i.payload(); + QCOMPARE( msg->subject()->asUnicodeString(), QString::fromUtf8( "IMPORTANT: Akonadi Test" ) ); + QCOMPARE( msg->from()->asUnicodeString(), QString::fromUtf8( "Tobias Koenig " ) ); + QCOMPARE( msg->to()->asUnicodeString(), QString::fromUtf8( "Ingo Kloecker " ) ); + + delete serializer; +} + +void MailSerializerTest::testEnvelopeDeserializeWithReferencesHeader() +{ + Item i; + i.setMimeType( QLatin1String("message/rfc822") ); + + SerializerPluginMail *serializer = new SerializerPluginMail(); + + // envelope + QByteArray env( "(\"Wed, 1 Feb 2006 13:37:19 UT\" \"IMPORTANT: Akonadi Test\" ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) NIL ((\"Ingo Kloecker\" NIL \"kloecker\" \"kde.org\")) NIL NIL NIL <{7b55527e-77f4-489d-bf18-e805be96718c}@server.kde.org> \"<{8888827e-77f4-489d-bf18-e805be96718c}@server.kde.org> <{9999927e-77f4-489d-bf18-e805be96718c}@server.kde.org>\")" ); + QBuffer buffer; + buffer.setData( env ); + buffer.open( QIODevice::ReadOnly ); + buffer.seek( 0 ); + QBENCHMARK { + serializer->deserialize( i, MessagePart::Envelope, buffer, 1 ); + } + QVERIFY( i.hasPayload() ); + + KMime::Message::Ptr msg = i.payload(); + QCOMPARE( msg->subject()->asUnicodeString(), QString::fromUtf8( "IMPORTANT: Akonadi Test" ) ); + QCOMPARE( msg->from()->asUnicodeString(), QString::fromUtf8( "Tobias Koenig " ) ); + QCOMPARE( msg->to()->asUnicodeString(), QString::fromUtf8( "Ingo Kloecker " ) ); + QCOMPARE( msg->references()->asUnicodeString(), QString::fromUtf8( "<{8888827e-77f4-489d-bf18-e805be96718c}@server.kde.org> <{9999927e-77f4-489d-bf18-e805be96718c}@server.kde.org>" ) ); + + delete serializer; +} + +void MailSerializerTest::testEnvelopeSerialize() +{ + Item i; + i.setMimeType( QLatin1String("message/rfc822") ); + Message* msg = new Message(); + msg->date()->from7BitString( "Wed, 1 Feb 2006 13:37:19 UT" ); + msg->subject()->from7BitString( "IMPORTANT: Akonadi Test" ); + msg->from()->from7BitString( "Tobias Koenig " ); + msg->sender()->from7BitString( "Tobias Koenig " ); + msg->to()->from7BitString( "Ingo Kloecker " ); + msg->messageID()->from7BitString( "<{7b55527e-77f4-489d-bf18-e805be96718c}@server.kde.org>" ); + i.setPayload( KMime::Message::Ptr( msg ) ); + + SerializerPluginMail *serializer = new SerializerPluginMail(); + + // envelope + QByteArray expEnv( "(\"Wed, 01 Feb 2006 13:37:19 +0000\" \"IMPORTANT: Akonadi Test\" ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) NIL ((\"Ingo Kloecker\" NIL \"kloecker\" \"kde.org\")) NIL NIL NIL \"<{7b55527e-77f4-489d-bf18-e805be96718c}@server.kde.org>\" NIL)" ); + QByteArray env; + QBuffer buffer; + buffer.setBuffer( &env ); + buffer.open( QIODevice::ReadWrite ); + int version = 0; + QBENCHMARK { + buffer.seek( 0 ); + serializer->serialize( i, MessagePart::Envelope, buffer, version ); + } + QCOMPARE( env, expEnv ); + + // envelop with references header + msg->references()->from7BitString( "<{8888827e-77f4-489d-bf18-e805be96718c}@server.kde.org>" ); + expEnv = QByteArray( "(\"Wed, 01 Feb 2006 13:37:19 +0000\" \"IMPORTANT: Akonadi Test\" ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) NIL ((\"Ingo Kloecker\" NIL \"kloecker\" \"kde.org\")) NIL NIL NIL \"<{7b55527e-77f4-489d-bf18-e805be96718c}@server.kde.org>\" \"<{8888827e-77f4-489d-bf18-e805be96718c}@server.kde.org>\")" ); + + buffer.close(); + buffer.open( QIODevice::ReadWrite ); + buffer.seek( 0 ); + serializer->serialize( i, MessagePart::Envelope, buffer, version ); + QCOMPARE( env, expEnv ); + + // envelop with references header with multiple entries + msg->references()->from7BitString( "<{8888827e-77f4-489d-bf18-e805be96718c}@server.kde.org> <{9999927e-77f4-489d-bf18-e805be96718c}@server.kde.org>" ); + expEnv = QByteArray( "(\"Wed, 01 Feb 2006 13:37:19 +0000\" \"IMPORTANT: Akonadi Test\" ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) ((\"Tobias Koenig\" NIL \"tokoe\" \"kde.org\")) NIL ((\"Ingo Kloecker\" NIL \"kloecker\" \"kde.org\")) NIL NIL NIL \"<{7b55527e-77f4-489d-bf18-e805be96718c}@server.kde.org>\" \"<{8888827e-77f4-489d-bf18-e805be96718c}@server.kde.org> <{9999927e-77f4-489d-bf18-e805be96718c}@server.kde.org>\")" ); + + buffer.close(); + buffer.open( QIODevice::ReadWrite ); + buffer.seek( 0 ); + serializer->serialize( i, MessagePart::Envelope, buffer, version ); + QCOMPARE( env, expEnv ); + + delete serializer; +} + +void MailSerializerTest::testParts() +{ + Item item; + item.setMimeType( QLatin1String("message/rfc822") ); + KMime::Message *m = new Message; + KMime::Message::Ptr msg( m ); + item.setPayload( msg ); + + SerializerPluginMail *serializer = new SerializerPluginMail(); + QVERIFY( serializer->parts( item ).isEmpty() ); + + msg->setHead( "foo" ); + QSet parts = serializer->parts( item ); + QCOMPARE( parts.count(), 2 ); + QVERIFY( parts.contains( MessagePart::Envelope ) ); + QVERIFY( parts.contains( MessagePart::Header ) ); + + msg->setBody( "bar" ); + parts = serializer->parts( item ); + QCOMPARE( parts.count(), 3 ); + QVERIFY( parts.contains( MessagePart::Envelope ) ); + QVERIFY( parts.contains( MessagePart::Header ) ); + QVERIFY( parts.contains( MessagePart::Body ) ); + + delete serializer; +} + +void MailSerializerTest::testHeaderFetch() +{ + Item i; + i.setMimeType( QLatin1String("message/rfc822") ); + + SerializerPluginMail *serializer = new SerializerPluginMail(); + + + QByteArray headerData( "From: David Johnson \n" + "To: kde-commits@kde.org\n" + "MIME-Version: 1.0\n" + "Date: Sun, 01 Feb 2009 06:25:22 +0000\n" + "Message-Id: <1233469522.741324.18468.nullmailer@svn.kde.org>\n" + "Subject: [kde-doc-english] KDE/kdeutils/kcalc\n" ); + + QString expectedSubject = QString::fromUtf8( "[kde-doc-english] KDE/kdeutils/kcalc" ); + QString expectedFrom = QString::fromUtf8( "David Johnson " ); + QString expectedTo = QString::fromUtf8( "kde-commits@kde.org" ); + + // envelope + QBuffer buffer; + buffer.setData( headerData ); + buffer.open( QIODevice::ReadOnly ); + buffer.seek( 0 ); + serializer->deserialize( i, MessagePart::Header, buffer, 0 ); + QVERIFY( i.hasPayload() ); + + KMime::Message::Ptr msg = i.payload(); + QCOMPARE( msg->subject()->asUnicodeString(), expectedSubject ); + QCOMPARE( msg->from()->asUnicodeString(), expectedFrom ); + QCOMPARE( msg->to()->asUnicodeString(), expectedTo ); + + delete serializer; +} + +void MailSerializerTest::testMultiDeserialize() +{ + // The Body part includes the Header. + // When serialization is done a second time, we should already have the header deserialized. + // We change the header data for the second deserialization (which is an unrealistic scenario) + // to demonstrate that it is not deserialized again. + + Item i; + i.setMimeType( QLatin1String("message/rfc822") ); + + SerializerPluginMail *serializer = new SerializerPluginMail(); + + + QByteArray messageData( "From: David Johnson \n" + "To: kde-commits@kde.org\n" + "MIME-Version: 1.0\n" + "Date: Sun, 01 Feb 2009 06:25:22 +0000\n" + "Subject: [kde-doc-english] KDE/kdeutils/kcalc\n" + "Content-Type: text/plain\n" + "\n" + "This is content" ); + + QString expectedSubject = QString::fromUtf8( "[kde-doc-english] KDE/kdeutils/kcalc" ); + QString expectedFrom = QString::fromUtf8( "David Johnson " ); + QString expectedTo = QString::fromUtf8( "kde-commits@kde.org" ); + QByteArray expectedBody( "This is content" ); + + // envelope + QBuffer buffer; + buffer.setData( messageData ); + buffer.open( QIODevice::ReadOnly ); + buffer.seek( 0 ); + serializer->deserialize( i, MessagePart::Body, buffer, 0 ); + QVERIFY( i.hasPayload() ); + + KMime::Message::Ptr msg = i.payload(); + QCOMPARE( msg->subject()->asUnicodeString(), expectedSubject ); + QCOMPARE( msg->from()->asUnicodeString(), expectedFrom ); + QCOMPARE( msg->to()->asUnicodeString(), expectedTo ); + QCOMPARE( msg->body(), expectedBody ); + + buffer.close(); + + messageData = QByteArray ( "From: DIFFERENT CONTACT \n" + "To: kde-commits@kde.org\n" + "MIME-Version: 1.0\n" + "Date: Sun, 01 Feb 2009 06:25:22 +0000\n" + "Message-Id: <1233469522.741324.18468.nullmailer@svn.kde.org>\n" + "Subject: [kde-doc-english] KDE/kdeutils/kcalc\n" + "Content-Type: text/plain\n" + "\r\n" + "This is content" ); + + buffer.setData( messageData ); + buffer.open( QIODevice::ReadOnly ); + buffer.seek( 0 ); + + serializer->deserialize( i, MessagePart::Header, buffer, 0 ); + QVERIFY( i.hasPayload() ); + + msg = i.payload(); + QCOMPARE( msg->subject()->asUnicodeString(), expectedSubject ); + QCOMPARE( msg->from()->asUnicodeString(), expectedFrom ); + QCOMPARE( msg->to()->asUnicodeString(), expectedTo ); + + delete serializer; +} + + diff --git a/kdepim-runtime/plugins/tests/mailserializertest.h b/kdepim-runtime/plugins/tests/mailserializertest.h new file mode 100644 index 00000000..60973965 --- /dev/null +++ b/kdepim-runtime/plugins/tests/mailserializertest.h @@ -0,0 +1,38 @@ +/* + 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 MAILSERIALIZERTEST_H +#define MAILSERIALIZERTEST_H + +#include + +class MailSerializerTest : public QObject +{ + Q_OBJECT + private slots: + void testEnvelopeDeserialize(); + void testEnvelopeDeserializeWithReferencesHeader(); + void testEnvelopeSerialize(); + void testParts(); + void testHeaderFetch(); + void testMultiDeserialize(); +}; + + +#endif diff --git a/kdepim-runtime/qml/CMakeLists.txt b/kdepim-runtime/qml/CMakeLists.txt new file mode 100644 index 00000000..b93eb02f --- /dev/null +++ b/kdepim-runtime/qml/CMakeLists.txt @@ -0,0 +1,5 @@ +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + +add_subdirectory( kde ) +add_subdirectory( akonadi ) diff --git a/kdepim-runtime/qml/Messages.sh b/kdepim-runtime/qml/Messages.sh new file mode 100644 index 00000000..72075f72 --- /dev/null +++ b/kdepim-runtime/qml/Messages.sh @@ -0,0 +1,8 @@ +#! /bin/sh +$EXTRACTRC --ignore-no-input `find . -name '*.ui' -or -name '*.rc' -or -name '*.kcfg' -or -name '*.kcfg.cmake'` >> rc.cpp || exit 11 +$XGETTEXT -ktranslate `find . -name '*.cpp' -o -name '*.h' | grep -v '/tests/'` -o $podir/kdepim-runtime-qml.pot +if [ -e $podir/kdepim-runtime-qml.pot ]; then + extraparam="-j" +fi +$XGETTEXT -ktranslate `find . -name '*.qml' | grep -v '/tests/'` $extraparam -L Java -o $podir/kdepim-runtime-qml.pot +rm -f rc.cpp diff --git a/kdepim-runtime/qml/akonadi/AkonadiBreadcrumbNavigationView.qml b/kdepim-runtime/qml/akonadi/AkonadiBreadcrumbNavigationView.qml new file mode 100644 index 00000000..10aece1c --- /dev/null +++ b/kdepim-runtime/qml/akonadi/AkonadiBreadcrumbNavigationView.qml @@ -0,0 +1,146 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 +import org.kde 4.5 +import org.kde.akonadi 4.5 + +Item { + id : _top + property alias breadcrumbComponentFactory : breadcrumbView.breadcrumbComponentFactory + + property int indentation : 35 + + property alias topDelegate : breadcrumbView.topDelegate + property alias breadcrumbDelegate : breadcrumbView.breadcrumbDelegate + property alias selectedItemDelegate : breadcrumbView.selectedItemDelegate + property alias childItemsDelegate : breadcrumbView.childItemsDelegate + property alias multipleSelectionText : breadcrumbView.multipleSelectionText + + property alias itemHeight : breadcrumbView.itemHeight + property alias _transitionSelect : breadcrumbView._transitionSelect + + property alias hasChildren : breadcrumbView.hasChildren + property alias hasSelection : breadcrumbView.hasSelection + property alias hasBreadcrumbs : breadcrumbView.hasBreadcrumbs + + property alias numBreadcrumbs : breadcrumbView.numBreadcrumbs + property alias numSelected : breadcrumbView.numSelected + + property alias breadcrumbSelectionModel : breadcrumbView.breadcrumbSelectionModel + property alias selectedItemSelectionModel : breadcrumbView.selectedItemSelectionModel + property alias childSelectionModel : breadcrumbView.childSelectionModel + + + property alias showCheckboxes : breadcrumbView.showCheckboxes + property alias checkable : breadcrumbView.checkable + property alias showUnread : breadcrumbView.showUnread + + property bool clickToBulkAction : true + + signal selectedClicked() + signal homeClicked() + + Item { + id :dragOverlay + anchors.fill : parent + } + + Connections { + target: breadcrumbView + onHomeClicked: homeClicked() + } + + BreadcrumbNavigationView { + id : breadcrumbView + anchors.fill : parent + + property bool showCheckboxes : false + property bool checkable : false + property bool showUnread : false + + topDelegate : Item { + clip: true + + MouseArea { + anchors.fill: parent + onClicked: { + breadcrumbView._transitionSelect = -1; + breadcrumbView.state = "before_select_home"; + } + } + Text { + id : textElement + x : 90 + width: parent.width - 48 - 50 + text : KDE.i18nc( "Go to the Home screen of the application", "Home") + color: "black" + } + } + + breadcrumbDelegate : CollectionDelegate { + indentation : _top.indentation + fullClickArea : true + dragParent : dragOverlay + height : itemHeight + checkModel : breadcrumbComponentFactory.qmlBreadcrumbCheckModel() + showUnread : breadcrumbView.showUnread + showCheckbox : breadcrumbView.showCheckboxes + checkable : breadcrumbView.checkable + onIndexSelected : { + breadcrumbTopLevel._transitionSelect = row; + breadcrumbTopLevel.state = "before_select_breadcrumb"; + } + } + + selectedItemDelegate : CollectionDelegate { + indentation : _top.indentation + height : itemHeight + dragParent : dragOverlay + selectedDelegate : true + checkModel : breadcrumbComponentFactory.qmlSelectedItemCheckModel() + showUnread : breadcrumbView.showUnread + showCheckbox : breadcrumbView.showCheckboxes + checkable : breadcrumbView.checkable + + MouseArea { + anchors.fill : _top.clickToBulkAction ? parent : undefined + onClicked : selectedClicked(); + } + } + + childItemsDelegate : CollectionDelegate { + indentation : _top.indentation + height : itemHeight + dragParent : dragOverlay + fullClickArea : true + showChildIndicator : true + checkModel : breadcrumbComponentFactory.qmlChildCheckModel() + showUnread : breadcrumbView.showUnread + showCheckbox : breadcrumbView.showCheckboxes + checkable : breadcrumbView.checkable + onIndexSelected : { + breadcrumbTopLevel._transitionSelect = row; + breadcrumbTopLevel.state = "before_select_child"; + } + } + } +} diff --git a/kdepim-runtime/qml/akonadi/CMakeLists.txt b/kdepim-runtime/qml/akonadi/CMakeLists.txt new file mode 100644 index 00000000..1bcbf61e --- /dev/null +++ b/kdepim-runtime/qml/akonadi/CMakeLists.txt @@ -0,0 +1,16 @@ +if (QT_QTDECLARATIVE_FOUND) +install(FILES + qmldir + collectionview.qml + AkonadiBreadcrumbNavigationView.qml + CollectionDelegate.qml + border_dot.png + check.png + sliderbackground.png + transparentplus.png + DESTINATION ${PLUGIN_INSTALL_DIR}/imports/org/kde/akonadi +) + +add_subdirectory( tests ) + +endif () diff --git a/kdepim-runtime/qml/akonadi/CollectionDelegate.qml b/kdepim-runtime/qml/akonadi/CollectionDelegate.qml new file mode 100644 index 00000000..01062ede --- /dev/null +++ b/kdepim-runtime/qml/akonadi/CollectionDelegate.qml @@ -0,0 +1,217 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 +import org.kde 4.5 +import org.kde.akonadi 4.5 + +Item { + id: wrapper + clip: true + + property bool fullClickArea : false + property bool showChildIndicator : false + property bool selectedDelegate : false + property bool topItem : false + property bool showUnread : false + property bool showCheckbox : false + property bool checkable : false + property bool uncheckable : false + property bool alternatingRowColors : false + property int indentation : 0 + property real dragCheckThreshold : 0.5 + + property alias dragParent : dragTarget.parent + + property variant checkModel + + signal indexSelected(int row) + + x : indentation + width : ListView.view.width - indentation + + Rectangle { + // This is the same as anchors.fill : parent, but ParentAnimation only works + // if positional layouting is used instead of anchor layouting. + x : 0 + y : 0 + width : wrapper.width + height : wrapper.height + id : nestedItem + color : ( alternatingRowColors && model.index % 2 == 0 ) ? "#33ffffff" : "#00000000" + + Behavior on x { + id : dragFinishedBehavior + SequentialAnimation { + NumberAnimation { + easing.type: "OutQuad" + easing.amplitude: 100 + duration: 800 + } + ScriptAction { + script : { + nestedItem.parent = wrapper + nestedItem.y = 0 + dragFinishedBehavior.enabled = false + nestedItem.x = 0 + dragFinishedBehavior.enabled = true + } + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if ( fullClickArea ) + { + if (topItem) + { + indexSelected(model.index); + return; + } + + if (showChildIndicator) + { + nestedItem.state = "before_select_child"; + } else if (!selectedDelegate) + nestedItem.state = "before_select_breadcrumb"; + indexSelected(model.index); + } + } + drag.target : (checkable || uncheckable) ? nestedItem : undefined + drag.axis : Drag.XAxis + drag.minimumX : uncheckable ? -nestedItem.width : wrapper.indentation + drag.maximumX : checkable ? nestedItem.width : wrapper.indentation + drag.onActiveChanged : { + if (!drag.active) { + if ((checkable && nestedItem.x > nestedItem.width * dragCheckThreshold) + || (uncheckable && nestedItem.x < nestedItem.width * dragCheckThreshold)) + { + // 8 is QItemSelectionModel::Toggle + checkModel.select(model.index, 8); + } + nestedItem.x = wrapper.indentation + } else { + var point = mapToItem(dragParent, nestedItem.x, nestedItem.y) + nestedItem.y = point.y + dragFinishedBehavior.enabled = false; + nestedItem.x = point.x + wrapper.indentation + dragFinishedBehavior.enabled = true; + nestedItem.parent = dragParent + // Using the state directly does not seem to work. +// nestedItem.state = "dragging" + } + } + } + Row { + id: topLayout + x: 10; y: 10; + height: 48 + width: parent.width + spacing: 10 + + Image { + id : checkbox + y : -10 + source : "check.png" + visible : wrapper.showCheckbox && model.checkOn; + } + + //Image { + // id: collectionIcon + // http://lists.trolltech.com/pipermail/qt-qml/2010-July/000668.html + // pixmap: KDE.iconToPixmap( model.decoration, height ); + // width: 48; height: 48 +// } + + Column { + height : parent.height + Text { + width: wrapper.width - 48 - 50 + text : model.display + color: (model.foreground != undefined ? model.foreground : "black") + //### requires a newer QML version + //wrapMode: "WrapAnywhere" // Needs the anchors.fill to work properly + } + + Text { + text : wrapper.showUnread && model.unreadCount > 0 ? KDE.i18n( "Unread: %1", model.unreadCount ) : "" + color: "#0C55BB" + font.pixelSize: 16 + } + Rectangle { + id: progressBar + color: "black" + width: parent.width + height: 7 + visible: (model.collectionSyncProgress != undefined && model.collectionSyncProgress != 0 && model.collectionSyncProgress != 100) + Rectangle { + color: "lightsteelblue" + x: 1 + y: 1 + height: 5 + width: ((parent.width * (model.collectionSyncProgress == 100 ? 0 : model.collectionSyncProgress)) / 100) + } + } + } + } + + Image { + width : height + anchors.right : nestedItem.right + anchors.rightMargin : 5 + anchors.verticalCenter : nestedItem.verticalCenter + opacity : ( showChildIndicator && breadcrumbComponentFactory.childCollectionHasChildren( model.index ) ) ? 1 : 0 + source: "transparentplus.png" + } + + states : [ + State { + name : "dragging" + ParentChange { id : dragTarget; target : nestedItem } + }, + State { + name : "before_select_child" + ParentChange { target : nestedItem; parent : selectedItemPlaceHolder; } + PropertyChanges { target : nestedItem; x : indentation; y : 0 } + }, + State { + name : "before_select_breadcrumb" + ParentChange { target : nestedItem; parent : selectedItemPlaceHolder; } + PropertyChanges { target : nestedItem; x : indentation; y : 0 } + } + ] + transitions : [ + Transition { + ParentAnimation { + target : nestedItem + NumberAnimation { + properties: "x,y"; + duration : 500 + easing.type: Easing.OutQuad; + } + } + } + ] + } +} + diff --git a/kdepim-runtime/qml/akonadi/border_dot.png b/kdepim-runtime/qml/akonadi/border_dot.png new file mode 100644 index 0000000000000000000000000000000000000000..55b20f7c8bcf43d0066b9332b6ea9b04b7de195d GIT binary patch literal 251 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 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/akonadi/collectionview.qml b/kdepim-runtime/qml/akonadi/collectionview.qml new file mode 100644 index 00000000..e5b39e4b --- /dev/null +++ b/kdepim-runtime/qml/akonadi/collectionview.qml @@ -0,0 +1,100 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 +import org.kde 4.5 + +/** Akonadi Collection List View + @param model: The collection model to display, ETM-based, filtered to only contain a flat collection list. + @param currentIndex: Index of the currently selected row. + */ +Item { + id: collectionListViewTopLevel + property alias model: collectionListView.model + property alias currentIndex: collectionListView.currentIndex + signal collectionSelected + + SystemPalette { id: palette; colorGroup: "Active" } + Component { + id: collectionViewDelegate + + Item { + id: wrapper + width: collectionListView.width + height : 68 + + Rectangle { + id: background + opacity: 0.25 + x: 1; y: 2; width: parent.width - 2; height: parent.height - 4 + border.color: palette.mid + radius: 5 + } + MouseArea { + anchors.fill: parent + onClicked: { + wrapper.ListView.view.currentIndex = model.index; + collectionListViewTopLevel.collectionSelected() + } + } + + Row { + id: topLayout + x: 10; y: 10; + height: collectionIcon.height; + width: parent.width + spacing: 10 + + Image { + id: collectionIcon + pixmap: KDE.iconToPixmap( model.decoration, height ); + width: 48; height: 48 + } + + Column { + height: collectionIcon.height + width: background.width - collectionIcon.width - 20 + spacing: 5 + Text { + text : model.display + } + } + } + } + } + + Component { + id: highlight + Rectangle { + color: palette.highlight + radius: 5 + } + } + + ListView + { + id: collectionListView + anchors.fill: parent + delegate : collectionViewDelegate + highlight: highlight + highlightFollowsCurrentItem: true + focus: true + clip: true + } +} diff --git a/kdepim-runtime/qml/akonadi/qmldir b/kdepim-runtime/qml/akonadi/qmldir new file mode 100644 index 00000000..1953a355 --- /dev/null +++ b/kdepim-runtime/qml/akonadi/qmldir @@ -0,0 +1,3 @@ +CollectionView 4.5 collectionview.qml +CollectionDelegate 4.5 CollectionDelegate.qml +AkonadiBreadcrumbNavigationView 4.5 AkonadiBreadcrumbNavigationView.qml diff --git a/kdepim-runtime/qml/akonadi/sliderbackground.png b/kdepim-runtime/qml/akonadi/sliderbackground.png new file mode 100644 index 0000000000000000000000000000000000000000..accd8de7900d7dd0c3e175f74d5bb707e5013e65 GIT binary patch literal 26391 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 + + 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. +*/ + +import Qt 4.7 +import org.kde 4.5 +import org.kde.akonadi 4.5 + +Rectangle { + color: white + height: 480 + width: 800 + + CollectionView { + id: listView + anchors.fill: parent + model: collectionModel + } + Binding { + target: application + property: "collectionRow" + value: listView.currentIndex + } +} diff --git a/kdepim-runtime/qml/akonadi/tests/qmltest.cpp b/kdepim-runtime/qml/akonadi/tests/qmltest.cpp new file mode 100644 index 00000000..5c3c3a36 --- /dev/null +++ b/kdepim-runtime/qml/akonadi/tests/qmltest.cpp @@ -0,0 +1,111 @@ +/* +* This file is part of Akonadi +* +* Copyright 2010 Stephen Kelly +* +* 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, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301 USA +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +// #include + +#include +#include +#include +#include +#include +#include +#include +#include + + +class QmlTestWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY( int collectionRow READ selectedCollectionRow WRITE collectionRowSelected ) + +public: + QmlTestWidget( QWidget *parent = 0 ); + + public: + int selectedCollectionRow() const { return 0; } + void collectionRowSelected( int row ); + +}; + + + +QmlTestWidget::QmlTestWidget(QWidget* parent) + : QWidget(parent) +{ + + QHBoxLayout *mainLayout = new QHBoxLayout( this ); + + Akonadi::ChangeRecorder *changeRecorder = new Akonadi::ChangeRecorder(); + changeRecorder->setCollectionMonitored( Akonadi::Collection::root() ); + + Akonadi::EntityTreeModel *etm = new Akonadi::EntityTreeModel( changeRecorder ); + + Akonadi::EntityMimeTypeFilterModel *collectionFilter = new Akonadi::EntityMimeTypeFilterModel(); + collectionFilter->setHeaderGroup( Akonadi::EntityTreeModel::CollectionTreeHeaders ); + collectionFilter->setSourceModel( etm ); + collectionFilter->addMimeTypeInclusionFilter( Akonadi::Collection::mimeType() ); + +#if 0 + KDescendantsProxyModel *flatProxy = new KDescendantsProxyModel( this ); + flatProxy->setSourceModel( collectionFilter ); + flatProxy->setAncestorSeparator( QLatin1String(" / ") ); + flatProxy->setDisplayAncestorData( true ); +#endif + + QDeclarativeView *view = new QDeclarativeView( this ); + mainLayout->addWidget( view ); + + view->engine()->rootContext()->setContextProperty( QLatin1String("collectionModel"), QVariant::fromValue( static_cast( collectionFilter ) ) ); + view->engine()->rootContext()->setContextProperty( QLatin1String("application"), QVariant::fromValue( static_cast( this ) ) ); + view->setSource( QUrl( QLatin1String("collectionviewtest.qml") ) ); // TODO make this a command line argument so this test can be used for other qml components as well +} + +void QmlTestWidget::collectionRowSelected(int row) +{ + kDebug() << row; +} + +int main( int argc, char **argv ) +{ + const QByteArray& ba = QByteArray( "akonadi_qml" ); + const KLocalizedString name = ki18n( "Akonadi QML Test" ); + KAboutData aboutData( ba, ba, name, ba, name ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KApplication app; + + QmlTestWidget testWidget; + testWidget.show(); + + return app.exec(); +} + +#include "qmltest.moc" diff --git a/kdepim-runtime/qml/akonadi/transparentplus.png b/kdepim-runtime/qml/akonadi/transparentplus.png new file mode 100644 index 0000000000000000000000000000000000000000..72f25e2624a8b13ed174d01c23ee65c7d261c121 GIT binary patch literal 852 zcmV-a1FQUrP)MzCV_|S* zE^l&Yo9;Xs0008hNklrPK;qFaT2R}H#u}40Nz-)4W!Pp*qk_+{13U9R zyf5?aj!<-RRJuts&K8ziRk`@YrGpOb=&&`~wL2U!5GdAY)>qy(UO)Np4{gE{C*udB zScLI3Tog?j)Tq2tllL2c)Q%51XKlx;&65tySqc=W=i63Urpm8!`Od?S9oqQe^vR@f zh!e%-LNO-M+}tOrd0Lr8^?h>s85$GKiCk_XEmAnxo>bz-(<=*TR`#W_i4h?zjENH; zAAALn0Aoha`5q%j4jc4AG$cMPKzLd&IlP$jV@5mMa3Rx|A*+u8Ee5!tMS~s$ZeOdw zBam>)P8T*x0zrUiuR_)v>B1>nYpHxnCXwwdD##YR`E#1I%;dp@WIlmK?}BKTXq%s0 ze0G+c)%A5leaF7fj+9M5)!6jm73t61nsa*t)*-H7s(o!kZ69xKpndzT>+p3^iHpreDR!6hH?R+BF(H6 zB%4~x{Cb~>VjXHA{07&kyujD$qcPoVkXUTN@Z!-8m^X%^Y$Xd?T%*K_i_dnln10s` zBA8yh)|}$N;7otamjo@E{80GVIE1vO^!|=7P7x$9KURv=B}RKlBQzmm_mQi2Tc^JTm9Jq_W{RrMM^G)p#?AK$oaSFYjFw zrs`_;`4uR&e`aWj;^w;~#Q4hKkG5EY~nWzFYDO%2dkz40000 + + 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. +*/ + +import Qt 4.7 + +Item { + id: breadcrumbTopLevel + clip : true + + property variant breadcrumbComponentFactory + + property alias topDelegate : topButton.sourceComponent + property alias breadcrumbDelegate : breadcrumbsView.delegate + property alias selectedItemDelegate : selectedItemView.delegate + property alias childItemsDelegate : childItemsView.delegate + property alias multipleSelectionText : multipleSelectionMessage.text + + property int itemHeight : height / 7 + property int _transitionSelect : -1 + + property bool hasChildren : childItemsView.count > 0 + property bool hasSelection : selectedItemView.count > 0 + property bool hasBreadcrumbs : breadcrumbsView.count > 0 + + property alias numBreadcrumbs : breadcrumbsView.count + property alias numSelected : selectedItemView.count + + property variant breadcrumbSelectionModel : breadcrumbComponentFactory.qmlBreadcrumbSelectionModel(); + property variant selectedItemSelectionModel : breadcrumbComponentFactory.qmlSelectionModel(); + property variant childSelectionModel : breadcrumbComponentFactory.qmlChildSelectionModel(); + + signal childCollectionSelected(int row) + signal breadcrumbCollectionSelected(int row) + signal homeClicked() + + SystemPalette { id: palette; colorGroup: "Active" } + + Loader { + id : topButton + height : itemHeight + + anchors.top : parent.top + + anchors.left : parent.left + anchors.right : parent.right + + Image { + id : topRightDivider + source : "dividing-line.png" + anchors.top : parent.top + anchors.right : parent.right + anchors.bottom : parent.bottom + anchors.bottomMargin : breadcrumbTopLevel.hasBreadcrumbs ? 0 : 8 + fillMode : Image.TileVertically + opacity : breadcrumbTopLevel.hasSelection ? 1 : 0 + } + } + + ListView { + id : breadcrumbsView + model : breadcrumbComponentFactory.qmlBreadcrumbsModel(); + interactive : false + height : breadcrumbsView.count > 0 ? itemHeight : 0 + + property int selectedIndex : -1 + anchors.top : topButton.bottom + anchors.left : parent.left + anchors.right : parent.right + highlightFollowsCurrentItem : true + highlightRangeMode : ListView.StrictlyEnforceRange + preferredHighlightBegin : 0 + preferredHighlightEnd : height + onCountChanged : { + positionViewAtIndex(count - 1, ListView.Beginning) + } + Component.onCompleted : { + positionViewAtIndex(count - 1, ListView.Beginning) + } + } + + Item { + id : breadcrumbPlaceHolder + height : breadcrumbTopLevel.hasBreadcrumbs ? itemHeight : 0 + anchors.top : topButton.bottom + anchors.left : parent.left + anchors.right : parent.right + } + Image { + id : breadcrumbRightDivider + source : "dividing-line.png" + anchors.top : breadcrumbPlaceHolder.top + anchors.right : breadcrumbPlaceHolder.right + height : breadcrumbTopLevel.hasBreadcrumbs ? (itemHeight -8) : 0 + fillMode : Image.TileVertically + opacity : breadcrumbTopLevel.hasBreadcrumbs ? 1 : 0 + } + + ListView { + id : selectedItemView + interactive : false + + model : breadcrumbComponentFactory.qmlSelectedItemModel(); + height : itemHeight * selectedItemView.count + anchors.top : breadcrumbsView.bottom + anchors.left : parent.left + anchors.right : parent.right + + onCountChanged : { + if (selectedItemView.count > 1) + { + breadcrumbsView.visible = false; + breadcrumbPlaceHolder.visible = false; + selectedItemView.visible = false; + selectedItemPlaceHolder.visible = false; + childItemsView.visible = false; + favinfoOverlay.visible = true; + } + else + { + breadcrumbsView.visible = true; + breadcrumbPlaceHolder.visible = true; + selectedItemView.visible = true; + selectedItemPlaceHolder.visible = true; + childItemsView.visible = true; + favinfoOverlay.visible = false; + } + } + } + + Item { + id : selectedItemPlaceHolder + height : selectedItemView.count > 0 ? itemHeight : 0 + anchors.top : breadcrumbPlaceHolder.bottom + anchors.left : parent.left + anchors.right : parent.right + Item { + id : selectedPlaceHolderImages + anchors.fill : parent + opacity : (selectedItemView.count > 0) ? 1 : 0 + Image { + source : "dividing-line-horizontal.png" + fillMode : Image.TileHorizontally + anchors.top : parent.top + anchors.topMargin : -3 + anchors.right : topLine.left + anchors.left : parent.left + } + Image { + id : topLine + source : "list-line-top.png" + anchors.right : parent.right + anchors.top : parent.top + anchors.topMargin : -8 + } + } + } + + ListView { + id : childItemsView + clip : true + model : breadcrumbComponentFactory.qmlChildItemsModel(); + property bool shouldBeFlickable + + anchors.top : selectedItemPlaceHolder.bottom + anchors.bottom : breadcrumbTopLevel.bottom + anchors.left : parent.left + anchors.right : parent.right + + shouldBeFlickable : childItemsView.height < (itemHeight * childItemsView.count) + interactive : shouldBeFlickable + } + + Item { + id : childItemsViewPlaceHolder + anchors.top : selectedItemPlaceHolder.bottom + anchors.bottom : breadcrumbTopLevel.bottom + anchors.left : parent.left + anchors.right : parent.right + + + Image { + source : "dividing-line-horizontal.png" + fillMode : Image.TileHorizontally + anchors.right : parent.right + anchors.left : parent.left + anchors.top : parent.top + visible : selectedItemView.count > 1 ? false : true + } + + Image { + source : "dividing-line.png" + anchors.top : parent.top + anchors.right : parent.right + anchors.bottom : parent.bottom + fillMode : Image.TileVertically + } + Image { + source : "scrollable-top.png" + anchors.top : parent.top + anchors.right : parent.right + anchors.left : parent.left + fillMode : Image.TileHorizontally + opacity : childItemsView.shouldBeFlickable ? 1 : 0 + visible : selectedItemView.count > 1 ? false : true + } + Image { + source : "scrollable-bottom.png" + anchors.bottom : parent.bottom + anchors.right : parent.right + anchors.left : parent.left + fillMode : Image.TileHorizontally + opacity : childItemsView.shouldBeFlickable ? 1 : 0 + visible : selectedItemView.count > 1 ? false : true + } + } + + Item { + id : favinfoOverlay + anchors.top : topButton.bottom + anchors.bottom : parent.bottom + anchors.left : parent.left + anchors.right : parent.right + visible : false + + Text { + id : multipleSelectionMessage + font.italic : true + horizontalAlignment : Text.AlignHCenter + anchors.horizontalCenter : parent.horizontalCenter + + height : 30 + x : 20 + y : 50 + } + + Image { + source : "dividing-line-horizontal.png" + fillMode : Image.TileHorizontally + anchors.top : parent.top + anchors.right : parent.right + anchors.left : parent.left + } + + Image { + source : "dividing-line.png" + fillMode : Image.TileVertically + anchors.top : parent.top + anchors.right : parent.right + anchors.bottom : parent.bottom + } + + } + + function completeHomeSelection() { + selectedItemSelectionModel.clearSelection(); + homeClicked() + // TODO: Remove: + breadcrumbCollectionSelected(breadcrumbTopLevel._transitionSelect); + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "after_select_breadcrumb"; + breadcrumbTopLevel.state = ""; + } + + function completeChildSelection() { + childSelectionModel.select(breadcrumbTopLevel._transitionSelect, 3) + // TODO: Remove: + childCollectionSelected(breadcrumbTopLevel._transitionSelect); + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "after_select_child"; + breadcrumbTopLevel.state = ""; + } + + function completeBreadcrumbSelection() { + breadcrumbSelectionModel.select(breadcrumbTopLevel._transitionSelect, 3) + // TODO: Remove: + breadcrumbCollectionSelected(breadcrumbTopLevel._transitionSelect); + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "after_select_breadcrumb"; + breadcrumbTopLevel.state = ""; + } + + states : [ + State { + name : "before_select_home" + PropertyChanges { + target : breadcrumbsView + opacity : 0 + height : 0 + } + PropertyChanges { + target : breadcrumbPlaceHolder + opacity : 0 + height : 0 + } + PropertyChanges { + target : selectedItemView + opacity : 0 + height : 0 + } + PropertyChanges { + target : selectedItemPlaceHolder + opacity : 0 + height : 0 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "before_select_child" + PropertyChanges { + target : topRightDivider + anchors.bottomMargin : 0 + opacity : 1 + } + PropertyChanges { + target : breadcrumbsView + height : itemHeight + anchors.topMargin : -itemHeight + opacity : 0 + } + PropertyChanges { + target : breadcrumbRightDivider + anchors.topMargin : -8 + height : 67 + opacity : 0 // { 1 } // selectedItemView.count > 0 ? 1 : 0 + } + PropertyChanges { + target : selectedItemPlaceHolder + anchors.topMargin : (breadcrumbsView.count == 0 && selectedItemView.count > 0) ? (itemHeight) : (breadcrumbsView.count == 0) ? 8 : 0 + height : itemHeight + opacity : 1 + } + + PropertyChanges { + target : selectedPlaceHolderImages + opacity : 1 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "after_select_child" + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "before_select_breadcrumb" + PropertyChanges { + target : breadcrumbsView + height : { if (breadcrumbTopLevel._transitionSelect >= 0) itemHeight * ( breadcrumbTopLevel._transitionSelect + 1 ) } + anchors.bottomMargin : -itemHeight + opacity : 0.5 + } + PropertyChanges { + target : selectedItemView + anchors.topMargin : (application.selectedCollectionRow() + breadcrumbsView.count) * itemHeight; + opacity : 0 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "after_select_breadcrumb" + PropertyChanges { + target : breadcrumbsView + contentY : breadcrumbsView.count > 1 ? itemHeight : 0 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + } + ] + + transitions : [ + Transition { + from : "*" + to : "before_select_home" + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + target : breadcrumbPlaceHolder + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : breadcrumbsView + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : selectedItemView + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : selectedItemPlaceHolder + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : childItemsView + duration: 500 + easing.type: "OutQuad" + properties : "height" + } + } + ScriptAction { + script: { completeHomeSelection(); } + } + } + }, + Transition { + from : "*" + to : "before_select_child" + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: topRightDivider + properties: "opacity,anchors.bottomMargin" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: breadcrumbsView + properties: "height,anchors.topMargin,opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: breadcrumbRightDivider + properties: "height" + } + PropertyAnimation { + target : selectedItemPlaceHolder + duration: 500 + easing.type: "OutQuad" + properties : "anchors.topMargin,height,opacity" + } + PropertyAnimation { + target : selectedPlaceHolderImages + duration: 500 + easing.type: "OutQuad" + properties : "opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsView + properties: "opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsDelegate + properties: "itemBackground" + } + } + ScriptAction { + script: { completeChildSelection(); } + } + } + }, + Transition { + from : "after_select_child" + to : "" + NumberAnimation { + target: childItemsView + properties: "opacity" + } + NumberAnimation { + target: selectedItemView + properties: "opacity" + } + }, + Transition { + from : "*" + to : "before_select_breadcrumb" + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: breadcrumbsView + properties: "height,opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: selectedItemView + properties: "opacity,anchors.topMargin" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsView + properties: "opacity" + } + } + ScriptAction { + script: { completeBreadcrumbSelection(); } + } + } + }, + Transition { + from : "after_select_breadcrumb" + to : "" + NumberAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsView + properties: "opacity" + } + } + ] +} diff --git a/kdepim-runtime/qml/kde/CMakeLists.txt b/kdepim-runtime/qml/kde/CMakeLists.txt new file mode 100644 index 00000000..de5e10a4 --- /dev/null +++ b/kdepim-runtime/qml/kde/CMakeLists.txt @@ -0,0 +1,45 @@ +if (QT_QTDECLARATIVE_FOUND) + +if (KDEQMLPLUGIN_STATIC) + set(LIBRARY_TYPE "STATIC") +else() + set(LIBRARY_TYPE) +endif () + +kde4_add_plugin( kdeqmlplugin ${LIBRARY_TYPE} WITH_PREFIX + kdeintegration.cpp + kdeintegrationplugin.cpp +) +target_link_libraries(kdeqmlplugin ${KDE4_KDEUI_LIBS} ${QT_QTDECLARATIVE_LIBRARY} ${QT_QTSCRIPT_LIBRARY}) + +if (KDEQMLPLUGIN_STATIC) + install( FILES qmldir_without_kdeqmlplugin DESTINATION ${PLUGIN_INSTALL_DIR}/imports/org/kde RENAME qmldir) +else() + install( FILES qmldir DESTINATION ${PLUGIN_INSTALL_DIR}/imports/org/kde ) +endif () + +install( FILES + BreadcrumbNavigationView.qml + SlideoutPanel.qml + SlideoutPanelContainer.qml + Dialog.qml + Flap.qml + Flap2.qml + dividing-line.png + dividing-line-horizontal.png + list-line-top.png + scrollable-bottom.png + scrollable-top.png + flap-collapsed-top.png + flap-collapsed-mid.png + flap-collapsed-bottom.png + flap-expanded-top.png + flap-expanded-mid.png + flap-expanded-bottom.png + DESTINATION ${PLUGIN_INSTALL_DIR}/imports/org/kde ) + +install( TARGETS kdeqmlplugin DESTINATION ${PLUGIN_INSTALL_DIR}/imports/org/kde ) + +#add_subdirectory(tests) + +endif () diff --git a/kdepim-runtime/qml/kde/Dialog.qml b/kdepim-runtime/qml/kde/Dialog.qml new file mode 100644 index 00000000..a4f80e1f --- /dev/null +++ b/kdepim-runtime/qml/kde/Dialog.qml @@ -0,0 +1,74 @@ +/* + Copyright (C) 2010 Anselmo Lacerda Silveira de Melo + + 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. +*/ + +import Qt 4.7 + +Item { + id : _topLevel + + property alias content : expandFlap.contentArea + + property int contentWidth : expandFlap.width - 80 + + property int expandedPosition : 0 + property int expandedHeight : height - expandedPosition + + property real expandThreshold : 3/4 + + signal extensionChanged(real extension) + + function changeExtension(extension) + { + + } + +// signal expanded(variant obj) +// signal collapsed(variant obj) + + z: 100 + x : 0 + y : 0 + + function collapse() { + expandFlap.changeExtension(0.0) + } + + function expand() { + expandFlap.changeExtension(1.0) + } + + Flap2 { + id : expandFlap + x : -width + y : expandedPosition + leftBound : 0 + rightBound : 80 + contentWidth + height : expandedHeight + + contentWidth : _topLevel.contentWidth + + topBackgroundImage : "flap-expanded-top.png" + midBackgroundImage : "flap-expanded-mid.png" + bottomBackgroundImage : "flap-expanded-bottom.png" + + onExtensionChanged : { + expandFlap.changeExtension(extension) + } + } +} diff --git a/kdepim-runtime/qml/kde/Flap.qml b/kdepim-runtime/qml/kde/Flap.qml new file mode 100644 index 00000000..5e18ba4c --- /dev/null +++ b/kdepim-runtime/qml/kde/Flap.qml @@ -0,0 +1,161 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + id : flap_toplevel + property real leftBound + property real rightBound + property real threshold : 0.5 + property bool expander : false + + property alias topBackgroundImage : collapsed_top.source + property alias midBackgroundImage : collapsed_mid.source + property alias bottomBackgroundImage : collapsed_bottom.source + property alias contentArea : _contentArea.data + property string titleText + property string titleIcon + property int imageWidth : 36 + property alias contentWidth : _contentArea.width + + color : "#00000000" + + width : draggedItem.width + + opacity : expander ? ( draggedItem.x - rightBound) / (leftBound - rightBound) : ( draggedItem.x - leftBound ) / (rightBound - leftBound) + + signal extensionChanged(real extension) + + function changeExtension(extension) + { + if (draggedItem.x != leftBound && draggedItem.x != rightBound) + return; + + draggedItem.x = ( extension * (rightBound - leftBound) ) + leftBound + } + + Item { + id : draggedItem + x : leftBound + y : 0 + + width : collapsed_top.width + height : flap_toplevel.height + + onXChanged : { + if (x != leftBound && x != rightBound) + return; + + extensionChanged( ( x - leftBound ) / ( rightBound - leftBound ) ) + } + + // Bug here: http://bugreports.qt.nokia.com/browse/QTBUG-12295 + Behavior on x { + NumberAnimation { + easing.type: "OutQuad" + easing.amplitude: 100 + duration: 300 + } + } + + Image { + id : collapsed_top + anchors.top : parent.top + anchors.left : parent.left + } + Image { + id : collapsed_mid + anchors.top : collapsed_top.bottom + anchors.left : parent.left + fillMode : Image.TileVertically + anchors.bottom : collapsed_bottom.top + } + Image { + id : collapsed_bottom + anchors.bottom : parent.bottom + anchors.left : parent.left + } + Image { + id: titleImage + width: imageWidth - 4 + height: (titleIcon == '' ? 0 : width) + source: titleIcon + anchors.bottom : parent.bottom + anchors.right: parent.right + anchors.margins: 6 + } + + Text { + id: titleLabel + z : 100 + anchors.verticalCenter : parent.verticalCenter + anchors.horizontalCenter : parent.right + anchors.horizontalCenterOffset : -imageWidth / 2 + anchors.verticalCenterOffset : - titleImage.height / 2 + transformOrigin : Item.Center + rotation: -90 + + text: titleText + horizontalAlignment: "AlignHCenter" + verticalAlignment: "AlignVCenter" + } + + MouseArea { + id: mrDrag + + property bool active : drag.active + + anchors.top : collapsed_top.top + anchors.left : collapsed_top.left + anchors.bottom : collapsed_bottom.bottom + anchors.right : collapsed_bottom.right + + onReleased: { + if (draggedItem.x > leftBound + ( ( rightBound - leftBound ) * threshold ) ) + { + draggedItem.x = rightBound + } else { + draggedItem.x = leftBound + } + } + drag.target: parent; + drag.axis: "XAxis" + drag.minimumX: leftBound + drag.maximumX: rightBound + drag.filterChildren : true + + // This should be a mouse area so we don't block the + // the mouse events that should go to it's children + MouseArea { + id : _contentArea + anchors.top : parent.top + anchors.bottom : parent.bottom + anchors.right : parent.right + width : contentWidth + anchors.leftMargin : 20 + anchors.topMargin : 20 + anchors.bottomMargin : 20 + anchors.rightMargin : 30 + } + } + } +} diff --git a/kdepim-runtime/qml/kde/Flap2.qml b/kdepim-runtime/qml/kde/Flap2.qml new file mode 100644 index 00000000..b577de83 --- /dev/null +++ b/kdepim-runtime/qml/kde/Flap2.qml @@ -0,0 +1,110 @@ +/* + Copyright (C) 2010 Anselmo Lacerda Silveira de Melo + + 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. +*/ + +import Qt 4.7 + +Rectangle { + id : flap_toplevel + property real leftBound + property real rightBound + + property alias topBackgroundImage : topImage.source + property alias midBackgroundImage : midImage.source + property alias bottomBackgroundImage : bottomImage.source + + property alias contentArea : _contentArea.data + property alias contentWidth : _contentArea.width + + color : "#00000000" + + width : draggedItem.width + + opacity : ( draggedItem.x - leftBound ) / (rightBound - leftBound) + + signal extensionChanged(real extension) + + function changeExtension(extension) + { + if (draggedItem.x != leftBound && draggedItem.x != rightBound) + return; + + draggedItem.x = ( extension * (rightBound - leftBound) ) + leftBound + } + + Item { + id : draggedItem + x : leftBound + y : 0 + + width : topImage.width + height : flap_toplevel.height + + onXChanged : { + if (x != leftBound && x != rightBound) + return; + + extensionChanged( ( x - leftBound ) / ( rightBound - leftBound ) ) + } + + Behavior on x { + PropertyAnimation { + duration: 300 + } + } + + Image { + id : topImage + anchors { + top : parent.top + left : parent.left + } + } + Image { + id : midImage + anchors { + top : topImage.bottom + left : parent.left + bottom : bottomImage.top + } + fillMode : Image.TileVertically + } + Image { + id : bottomImage + anchors { + bottom : parent.bottom + left : parent.left + } + } + + Item { + id : _contentArea + width : contentWidth + anchors { + top : parent.top + bottom : parent.bottom + right : parent.right + + leftMargin : 20 + topMargin : 20 + bottomMargin : 20 + rightMargin : 30 + } + } + } +} diff --git a/kdepim-runtime/qml/kde/SlideoutPanel.qml b/kdepim-runtime/qml/kde/SlideoutPanel.qml new file mode 100644 index 00000000..e05d4923 --- /dev/null +++ b/kdepim-runtime/qml/kde/SlideoutPanel.qml @@ -0,0 +1,137 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 + +/** + * A container for arbitrary content that can be expanded via dragging from a screen edge. + * @param titleText Tab label in collapsed state + * @param tilteIcon Tab icon path in collapsed state + * @param handlePosition offset from the top for the tab handle in collapsed state + * @param handleWidth Tab height in collapsed state + * @param handleHeight Tab width in collapsed state + * @param dragThreshold Drag distance needed before expanding + * @param radius tab and panel corner radius + * @param contentWidth width of the content area in expanded state + * @signal expanded emitted when the panel is expanded + * @slot collapse collapses the panel + */ +Item { + + id : _topLevel + property alias titleText : collapseFlap.titleText + property alias titleIcon : collapseFlap.titleIcon + + property alias collapsedContent : collapseFlap.contentArea + property alias expandedContent : expandFlap.contentArea + + + // Compat. Remove these. + property alias content : expandFlap.contentArea + property int handlePosition : 0 + property int handleHeight : 160 + property int contentWidth : expandFlap.width - 40 - collapsedWidth //: expandFlap.contentWidth + + property int collapsedPosition : handlePosition + property int expandedPosition : 0 + + property int collapsedWidth : 36 + + property int collapsedHeight : handleHeight + property int expandedHeight : height - expandedPosition + + property real collapseThreshold : 1/4 + property real expandThreshold : 3/4 + + property bool noCollapse : false + + signal extensionChanged(real extension) + + function changeExtension(extension) + { + + } + + signal expanded(variant obj) + signal collapsed(variant obj) + + z: 100 + x : 0 + y : 0 + + function collapse() { + collapseFlap.changeExtension(0.0) + } + + function expand() { + collapseFlap.changeExtension(1.0) + } + + Flap { + id : collapseFlap + x : collapsedWidth - width + y : collapsedPosition + threshold : collapseThreshold + + leftBound : 0 + rightBound : width - collapsedWidth + + height : collapsedHeight + topBackgroundImage : "flap-collapsed-top.png" + midBackgroundImage : "flap-collapsed-mid.png" + bottomBackgroundImage : "flap-collapsed-bottom.png" + expander : true + + onExtensionChanged : { + if (extension == 1.0) + { + _topLevel.noCollapse = true; + _topLevel.z = 99; + expanded(parent); + _topLevel.noCollapse = false; + } + else if (extension == 0.0) { + _topLevel.z = 100; + collapsed(parent); + } + + expandFlap.changeExtension(extension) + } + } + + Flap { + id : expandFlap + x : -width + y : expandedPosition + leftBound : 0 + rightBound : 40 + contentWidth + collapsedWidth + height : expandedHeight + threshold : expandThreshold + + contentWidth : _topLevel.contentWidth // width - 40 - collapsedWidth + + topBackgroundImage : "flap-expanded-top.png" + midBackgroundImage : "flap-expanded-mid.png" + bottomBackgroundImage : "flap-expanded-bottom.png" + + onExtensionChanged : { + collapseFlap.changeExtension(extension) + } + } +} diff --git a/kdepim-runtime/qml/kde/SlideoutPanelContainer.qml b/kdepim-runtime/qml/kde/SlideoutPanelContainer.qml new file mode 100644 index 00000000..6756a2a9 --- /dev/null +++ b/kdepim-runtime/qml/kde/SlideoutPanelContainer.qml @@ -0,0 +1,70 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 + +/** + * Container for a set of SlideoutPanels. + * It ensures that only one of them is expanded at any given time. + */ +Item { + id: _slideoutPanelContainer + + /** Margin of the panels regarding the container on the three sides where it is not docked to the edge. */ + property int margin: 20 + + /** Collapse all panels. */ + function collapseOthers(obj) + { + for ( var i = 0; i < children.length; ++i ) { + // Does not work: + // if (children[i] != obj) + if (!children[i].noCollapse) + children[i].collapse(); + } + } + + Component.onCompleted: + { + for ( var i = 0; i < children.length; ++i ) { + var panel = children[i]; + panel.expanded.connect( this, collapseOthers ); + panel.anchors.fill = _slideoutPanelContainer; + panel.anchors.rightMargin = margin; + panel.anchors.topMargin = margin; + panel.anchors.bottomMargin = margin; + /* + if ( i >= 1 ) { + var prevPanel = children[i - 1]; + panel.collapsedPosition = prevPanel.collapsedPosition + prevPanel.collapsedHeight + } */ + } + /* + // limit the height of the last panel to the available space + if ( children.length > 0 ) { + var lastPanel = children[ children.length - 1 ]; + if ( children.length > 1 ) { + var prevPanel = children[ children.length - 2 ]; + lastPanel.collapsedHeight = Math.min( lastPanel.collapsedHeight, height - prevPanel.collapsedPosition - prevPanel.collapsedHeight - lastPanel.anchors.topMargin - lastPanel.anchors.bottomMargin ); + } else { + lastPanel.collapsedHeight = Math.min( lastPanel.collapsedHeight, height - lastPanel.anchors.topMargin - lastPanel.anchors.bottomMargin ); + } + } */ + } +} diff --git a/kdepim-runtime/qml/kde/dividing-line-horizontal.png b/kdepim-runtime/qml/kde/dividing-line-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0de8c2f77b4730f60150c0d663269bd14f0b6b GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^AT|>R8<0#)^t}M2Sc;uILpXq-h9ji|$mcBZh%9Dc z;PVCIIgIzd%?Sqz>Uz33hH%XEJ-U&XL4k*5WA%}LrEN)SeQF9^rvt?eqJHuIc)Lbx g62l(GE4v%mw=)P${rupEDo`VXr>mdKI;Vst07ft-dH?_b literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/dividing-line.png b/kdepim-runtime/qml/kde/dividing-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9e80239b83318c8f04857670438e8a84481fbf67 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^OhC-V!3HGfJ?-}dQY^(zo*^7SP{WbZ0pxQQctjQh z)n5l;MkkHg6+l7B64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1TyJY5_^ zIA(^P*vNapfQMycwYzhMWB%)zB`cYZ$R2dJy}Os2!-3Ir64SN%9cgwe?ip+8%WvSD UzpIMj08lrBr>mdKI;Vst0IOs)f&c&j literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/flap-collapsed-bottom.png b/kdepim-runtime/qml/kde/flap-collapsed-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..3bd5b8938f345d02f73a347a190cdb74224e0ca6 GIT binary patch literal 1626 zcmV-g2BrClP)IIs)F~vr*AGJ<>DCUkf-X+F_Oxu>vbJv;`6Fu&--O# z4*mta zkQ-us@MYQ$B=UNi7Av3Jg4GMQWe_<&o-?szgf zHFC7a;RXsUmE@;_ToeWBy6*LhRKR<`P(HOEtEvh`QS{1|Mm@pi-gFKvV|jTWD!bHI z_g3DTwKz{5&3oT7xG^T_rZQ6|ov#78s@RuhxpeHEb9nUVQ9M3A{vN;-K#e?O=K$qd z1V93XMpR+Bp3&=^OG>I@G#a0|mjXauz-=}oz-n-=qv3O7<)tvsqAYu=gUS+PO!F8- z%9nCBP18GQk(Xv!?MG=qey*|qZ`f~dZ{z6b=)04XlOF(F1DHWGww4M;M!vBsp+d}N zv)&v6lOG_49D@7NaB4sWlk$)c!QuK4Lh8HJ8Q2qQ3y}ASD!2E(XE*5wS&f#3{dhcvF=l#jaPS3y-vC@;p0VScsjI502ZlEE3S0;QWm%>^ zj^#@?SHLtwDezjZy!X9KqTg%p#y*u!q}fhOmda99RXV`YSkAfRB$@@Os{*UQH%)`0 zD0(M+K672uG=s0D^1ho6T$bg6Bd!Ye#A6xQmt~2^j~~bV{rxX5FE3xu9eWDEW8Q`U zfMxbm!MGv&K?Pb#g=6%RXnf5~ETxQgDoJ9LYV;E*^3vW?ji_>KMpQ#lSACb7JCu&) zMzk6OKM9NBW_-^3AI(ttnmpnZv>_wqS?jKn%X#|FGj{^bhPpP_O?l-G zr!o)e^iTKqp}VkH1?=m!+@JWonP^ z7xab5H30S>O87Oc>0c<$uYj&+zCrhgrYSlHzn!~(BrP4}gU z?hxqbSDjrR^FKQcWoab?;ImIE>YCPce~NOo16KsP-s3Jt2Egz!c1>$qE=B#kk25R% Y4ZnL@a7Q(3;{X5v07*qoM6N<$f{ls;6951J literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/flap-collapsed-mid.png b/kdepim-runtime/qml/kde/flap-collapsed-mid.png new file mode 100644 index 0000000000000000000000000000000000000000..bb2893f114446f4ed7d2c60ca02fe1a6af380cb6 GIT binary patch literal 1012 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 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/flap-expanded-bottom.png b/kdepim-runtime/qml/kde/flap-expanded-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a38ca2a10a3bdeacf0a2dfe720038e7f2f0be6 GIT binary patch literal 2170 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 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/flap-expanded-top.png b/kdepim-runtime/qml/kde/flap-expanded-top.png new file mode 100644 index 0000000000000000000000000000000000000000..5d34c3802d8fade5ba63557d6badc798bd01ded2 GIT binary patch literal 37640 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 z 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 TTQde=fImf9HJLIg)8PLDhrAK- literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/kdeintegration.cpp b/kdepim-runtime/qml/kde/kdeintegration.cpp new file mode 100644 index 00000000..b45a262b --- /dev/null +++ b/kdepim-runtime/qml/kde/kdeintegration.cpp @@ -0,0 +1,306 @@ +/* + Copyright (c) 2010 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 "kdeintegration.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +static QString translate( const KLocalizedString &msg, + const QScriptContext *context, const int start, bool plural = false ) +{ + KLocalizedString string( msg ); + const int numArgs = context->argumentCount(); + + for (int i = start; i < numArgs; ++i) { + const QVariant arg = context->argument(i).toVariant(); + + // numbers provided from javascript seem to arrive always as double, which does not work for plural handling + if ( i == start && plural ) { + string = string.subs( arg.toInt() ); + continue; + } + + switch ( arg.type() ) { + case QVariant::Char: + string = string.subs( arg.toChar() ); + break; + case QVariant::Int: + string = string.subs( arg.toInt() ); + break; + case QVariant::UInt: + string = string.subs( arg.toUInt() ); + break; + case QVariant::LongLong: + string = string.subs( arg.toLongLong() ); + break; + case QVariant::ULongLong: + string = string.subs( arg.toULongLong() ); + break; + case QVariant::Double: + string = string.subs( arg.toDouble() ); + break; + case QVariant::String: + string = string.subs( arg.toString() ); + break; + default: + kWarning() << "Unknown i18n argument type:" << arg; + } + + } + + return string.toString(); +} + +KDEIntegration::KDEIntegration( QObject *parent ) : QObject( parent ) +{ +} + +QScriptContext *KDEIntegration::getContext( const QScriptValue &v ) +{ + QScriptEngine *engine = v.engine(); + + if (!engine) { + return 0; + } + + QScriptContext *context = engine->currentContext(); + if (!context) { + return 0; + } + + return context; +} + +QString KDEIntegration::i18n( const QScriptValue &array ) +{ + QScriptContext *context = getContext(array); + + if (!context) { + kWarning() << "No context !"; + return QString(); + } + + if (context->argumentCount() < 1) { + // ## TODO: (new str): context->throwError(i18n("i18n() takes at least one argument")); + return QString(); + } + + KLocalizedString message = ki18n(context->argument(0).toString().toUtf8()); + return translate(message, context, 1); +} + + +QString KDEIntegration::i18nc( const QScriptValue &array ) +{ + QScriptContext *context = getContext(array); + + if (!context) { + kWarning() << "No context !"; + return QString(); + } + + if (context->argumentCount() < 2) { + kWarning() << "i18nc() takes at least two arguments"; + //### TODO (new str): context->throwError(i18n("i18nc() takes at least two arguments")); + return QString(); + } + + KLocalizedString message = ki18nc(context->argument(0).toString().toUtf8(), + context->argument(1).toString().toUtf8()); + + return translate(message, context, 2); +} + +QString KDEIntegration::i18np( const QScriptValue &array ) +{ + QScriptContext *context = getContext(array); + + if (!context) { + kWarning() << "No context !"; + return QString(); + } + + if (context->argumentCount() < 2) { + kWarning() << "i18np() takes at least two arguments"; + //### TODO (new str): context->throwError(i18n("i18np() takes at least two arguments")); + return QString(); + } + + KLocalizedString message = ki18np(context->argument(0).toString().toUtf8(), + context->argument(1).toString().toUtf8()); + + return translate(message, context, 2, true); +} + +QString KDEIntegration::i18ncp( const QScriptValue &array ) +{ + QScriptContext *context = getContext(array); + + if (!context) { + kWarning() << "No context !"; + return QString(); + } + + if (context->argumentCount() < 3) { + kWarning() << "i18ncp() takes at least three arguments"; + //### TODO (new str): context->throwError(i18n("i18ncp() takes at least three arguments")); + return QString(); + } + + KLocalizedString message = ki18ncp(context->argument(0).toString().toUtf8(), + context->argument(1).toString().toUtf8(), + context->argument(2).toString().toUtf8()); + + return translate(message, context, 3, true); +} + +QString KDEIntegration::iconPath( const QString &iconName, int iconSize ) +{ + return KIconLoader::global()->iconPath( iconName, -iconSize ); // yes, the minus there is correct... +} + +QPixmap KDEIntegration::iconToPixmap(const QIcon& icon, int size ) +{ + if ( icon.isNull() ) + return QPixmap(); + return icon.pixmap( size ); +} + +QString KDEIntegration::locate(const QString& type, const QString& filename) +{ + return KStandardDirs::locate( type.toLatin1(), filename ); +} + +qreal KDEIntegration::mm2px(qreal mm) +{ +#ifdef _WIN32 +//TODO: Cache value + HMONITOR mon = MonitorFromWindow(QApplication::desktop()->winId(), MONITOR_DEFAULTTONEAREST); + MONITORINFOEX moninfo; + moninfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(mon, &moninfo); + + long monitorWidthInPixel = moninfo.rcMonitor.right - moninfo.rcMonitor.left; + long monitorHeightinPixel = moninfo.rcMonitor.bottom - moninfo.rcMonitor.top; + + DISPLAY_DEVICE dd; + dd.cb = sizeof(DISPLAY_DEVICE); + EnumDisplayDevices(moninfo.szDevice, 0, &dd, 0); + + const QString deviceID = QString::fromUtf16( ( const unsigned short *)dd.DeviceID); + + QRegExp rx(QLatin1String("^MONITOR\\\\(\\w+)\\\\")); + + if (rx.indexIn(deviceID) != -1) { + const QString devID = rx.cap(1); + + const QString baseRegKey = + QLatin1String("SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\") + devID; + + HKEY registryHandle; + LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + reinterpret_cast(baseRegKey.utf16()), + 0, + KEY_READ, + ®istryHandle); + if (res != ERROR_SUCCESS) { + RegCloseKey(registryHandle); + return mm * QApplication::desktop()->logicalDpiX() / 25.4; + } + + DWORD keyLength = 0; + res = RegQueryInfoKey(registryHandle, 0, 0, 0, 0, &keyLength, 0, 0, 0, 0, 0, 0); + if (res != ERROR_SUCCESS) { + RegCloseKey(registryHandle); + return mm * QApplication::desktop()->logicalDpiX() / 25.4; + } + + //Utf16 so 2 Bytes per Character + QByteArray buf(keyLength * 2, 0); + keyLength++; //keyLength including the terminating 0, which is already in a QByteArray + + res = RegEnumKeyEx(registryHandle, + 0, + reinterpret_cast(buf.data()), + &keyLength, + 0, + 0, + 0, + 0); + if (res != ERROR_SUCCESS) { + RegCloseKey(registryHandle); + return mm * QApplication::desktop()->logicalDpiX() / 25.4; + } + RegCloseKey(registryHandle); + + const QString guidString = QString::fromWCharArray(reinterpret_cast(buf.data())); + + const QString edidRegString = QLatin1String("SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\") + + devID + QLatin1String("\\") + guidString + QLatin1String("\\Device Parameters"); + RegOpenKeyEx(HKEY_LOCAL_MACHINE, reinterpret_cast(edidRegString.utf16()), + 0, KEY_READ, ®istryHandle); + + DWORD dataSize; + res = RegQueryValueEx(registryHandle, L"EDID", 0, 0, 0, &dataSize); + if (res != ERROR_SUCCESS) { + RegCloseKey(registryHandle); + return mm * QApplication::desktop()->logicalDpiX() / 25.4; + } + + QByteArray data(dataSize, 0); + res = RegQueryValueEx(registryHandle, L"EDID", 0, 0, + reinterpret_cast(data.data()), &dataSize); + RegCloseKey(registryHandle); + + const int monitorWidthInCM = data.at(21); + //int monitorHeightInCM = data.at(22); + + return (monitorWidthInPixel * mm) / (monitorWidthInCM * 10.0); + //int pixelPerMM = monitorHeightinPixel / (monitorHeightInCM * 10.0); + } +#endif + +#ifdef Q_WS_MAEMO_5 + // N900 (which is the only thing actually running Maemo5) reports 96 dpi while its screen actually has 267 dpi + return mm * 267 / 25.4; +#elif defined(MEEGO_EDITION_HARMATTAN) + // N9[50] (which is the only thing actually running Maemo6) reports 96 dpi as well while its screen actually has 251 dpi + return mm * 251 / 25.4; +#endif + return mm * QApplication::desktop()->logicalDpiX() / 25.4; +} + +#include "moc_kdeintegration.cpp" + diff --git a/kdepim-runtime/qml/kde/kdeintegration.h b/kdepim-runtime/qml/kde/kdeintegration.h new file mode 100644 index 00000000..4cd5d042 --- /dev/null +++ b/kdepim-runtime/qml/kde/kdeintegration.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2010 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 KDEINTEGRATION_H +#define KDEINTEGRATION_H + +#include +#include +#include + +class QIcon; +class QPixmap; +class QScriptValue; +class QScriptContext; + +class KDEIntegration : public QObject +{ + Q_OBJECT + + public: + explicit KDEIntegration(QObject* parent = 0); + + public slots: + QString i18n( const QScriptValue &array ); + QString i18nc( const QScriptValue &array ); + QString i18np( const QScriptValue &array ); + QString i18ncp( const QScriptValue &array ); + + QString iconPath( const QString &iconName, int size ); + QPixmap iconToPixmap( const QIcon &icon, int size ); + + QString locate( const QString &type, const QString &filename ); + + /// convert millimeters into pixels + qreal mm2px( qreal mm ); + + private: + QScriptContext *getContext( const QScriptValue &v ); +}; + +#endif + diff --git a/kdepim-runtime/qml/kde/kdeintegrationplugin.cpp b/kdepim-runtime/qml/kde/kdeintegrationplugin.cpp new file mode 100644 index 00000000..c1360b4b --- /dev/null +++ b/kdepim-runtime/qml/kde/kdeintegrationplugin.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2010 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 "kdeintegrationplugin.h" +#include "kdeintegration.h" + +#include +#include +#include +#include + +KDEIntegrationPlugin::KDEIntegrationPlugin(QObject* parent) : QDeclarativeExtensionPlugin( parent ) +{ + qDebug() << Q_FUNC_INFO; + kDebug(); +} + +void KDEIntegrationPlugin::registerTypes(const char* uri) +{ + kDebug() << uri; +// qmlRegisterType( uri, 4, 5, "KDE" ); +} + +void KDEIntegrationPlugin::initializeEngine(QDeclarativeEngine *engine, const char* uri) +{ + kDebug() << engine << uri; + engine->rootContext()->setContextProperty( QLatin1String("KDE"), new KDEIntegration( engine ) ); +} + + +Q_EXPORT_PLUGIN2( kdeintegrationplugin, KDEIntegrationPlugin ) diff --git a/kdepim-runtime/qml/kde/kdeintegrationplugin.h b/kdepim-runtime/qml/kde/kdeintegrationplugin.h new file mode 100644 index 00000000..627ab1fa --- /dev/null +++ b/kdepim-runtime/qml/kde/kdeintegrationplugin.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2010 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 KDEDECLARATIVEEXTENSIONPLUGIN_H +#define KDEDECLARATIVEEXTENSIONPLUGIN_H + +#include +#include + +class KDE_EXPORT KDEIntegrationPlugin : public QDeclarativeExtensionPlugin +{ + Q_OBJECT + public: + explicit KDEIntegrationPlugin(QObject* parent = 0); + virtual void registerTypes(const char* uri); + virtual void initializeEngine(QDeclarativeEngine* engine, const char* uri); +}; + +#endif diff --git a/kdepim-runtime/qml/kde/list-line-top.png b/kdepim-runtime/qml/kde/list-line-top.png new file mode 100644 index 0000000000000000000000000000000000000000..d83e032c72a6ab5df4f57be0ab3c25875a4d8971 GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^M}e4~gAGXTkm&RWQY^(zo*^7SP{WbZ0pxQQctjQh z)n5l;MkkHg6+l7B64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1T+dAc}; zM6|xWzLB@tfv5GMeFxW4zk>=skD6R16qOCwbh%xeLT)~g;_}|v#wE*JBxQ8i^-%ol zXQuxC`({d>i=8|3s^-$~7JcqBt$T{=7j+uyHHht;Y%zWIOi>4MryGqYryq*F71^e&1W9b*5PQ{kLF&$wpaSTrlcxFDf loPLy1!+>#bN6!Ak;vJEmtx>CXhXP&9;OXk;vd$@?2>|<}Y{38k literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/qmldir b/kdepim-runtime/qml/kde/qmldir new file mode 100644 index 00000000..e9ea2cc6 --- /dev/null +++ b/kdepim-runtime/qml/kde/qmldir @@ -0,0 +1,8 @@ +plugin kdeqmlplugin +BreadcrumbNavigationView 4.5 BreadcrumbNavigationView.qml +CollectionDelegate 4.5 CollectionDelegate.qml +SlideoutPanel 4.5 SlideoutPanel.qml +SlideoutPanelContainer 4.5 SlideoutPanelContainer.qml +Flap 4.5 Flap.qml +Flap2 4.5 Flap2.qml +Dialog 4.5 Dialog.qml diff --git a/kdepim-runtime/qml/kde/qmldir_without_kdeqmlplugin b/kdepim-runtime/qml/kde/qmldir_without_kdeqmlplugin new file mode 100644 index 00000000..dfe96970 --- /dev/null +++ b/kdepim-runtime/qml/kde/qmldir_without_kdeqmlplugin @@ -0,0 +1,7 @@ +BreadcrumbNavigationView 4.5 BreadcrumbNavigationView.qml +CollectionDelegate 4.5 CollectionDelegate.qml +SlideoutPanel 4.5 SlideoutPanel.qml +SlideoutPanelContainer 4.5 SlideoutPanelContainer.qml +Flap 4.5 Flap.qml +Flap2 4.5 Flap2.qml +Dialog 4.5 Dialog.qml diff --git a/kdepim-runtime/qml/kde/scrollable-bottom.png b/kdepim-runtime/qml/kde/scrollable-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..9a68baff0f9a0eb652b707b48503586dec3fded8 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^M}Sz4gAGU)GjCJ^QY^(zo*^7SP{WbZ0pxQQctjQh z)n5l;MkkHg6+l7B64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1Sxdb&7< zRLpsM$2#wb0gr>D+G(}#`ya<0xUk+}LK|y-g1B)*wds+~st4`wzc**@=;*L{{>N@F zM@9FZ_qQD!PHw&S{H5!G=!(=PwdZrV7**H54&u<*r?}wc)@e=-Ck@+RL~);!Lm)>+ z(fhRm0Y&Fl3Ix0{a$GEM!D3~bo8khXgb0(=Zl4QJ?nNsu*s+VTceON-eY^kEf)tlO nwm{psnHhT<8`NGiDm{{CHt@co^TTfg(D4kOu6{1-oD!M<1+jBN literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/scrollable-top.png b/kdepim-runtime/qml/kde/scrollable-top.png new file mode 100644 index 0000000000000000000000000000000000000000..e23287b86aea3b5096e14a97eb567d08e46e8a75 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^M}SzKgAGXTh)-$hiV*-Rk%I^Z6N%noMvfta(MWfO=Mr5!^E|wtqs0lLDxP*5hgSJs{v1H z?{hImpR*KY+Lp6clO^YzK){pIST4rdy22v=FB_?-y%u?{1M~%hr>mdKI;Vst0A9Or A?f?J) literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/MyItem.qml b/kdepim-runtime/qml/kde/tests/MyItem.qml new file mode 100644 index 00000000..e4540ffe --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/MyItem.qml @@ -0,0 +1,41 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 50 + height : 50 + + color : "blue" + + signal triggered(variant triggeredObject) + + MouseArea { + anchors.fill : parent + onClicked : { + parent.color = "green" + console.log("CLICK TRIGGERED " + parent) + triggered(parent) + } + } + +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/SliderComponent.qml b/kdepim-runtime/qml/kde/tests/SliderComponent.qml new file mode 100644 index 00000000..ec58c60d --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/SliderComponent.qml @@ -0,0 +1,111 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Item { + id : _topLevel + property real leftBound + property real rightBound + property real threshold : 0.5 + property bool expander + + Rectangle { + id : left + width : 10 + height : _topLevel.height + x : leftBound - width - 2 + y : _topLevel.y + color : "red" + } + + opacity : expander ? ( someRect.x - rightBound) / (leftBound - rightBound) : ( someRect.x - leftBound ) / (rightBound - leftBound) + + signal extensionChanged(real extension) + + function changeExtension(extension) + { + console.log("FOO " + someRect.x + " " + leftBound + " " + rightBound) + if (someRect.x != leftBound && someRect.x != rightBound) + return; + console.log("Changed") + someRect.x = ( extension * (rightBound - leftBound) ) + leftBound + } + + Rectangle { + id : someRect + x : leftBound + y : _topLevel.y + color : "lightsteelblue" + width : 100 + height : 100 + + onXChanged : { + if (x != leftBound && x != rightBound) + return; + + extensionChanged( ( x - leftBound ) / ( rightBound - leftBound ) ) + } + + Behavior on x { + NumberAnimation { + easing.type: "OutQuad" + easing.amplitude: 100 + duration: 300 + } + } + + MouseArea { + id: mrDrag + //property alias active : drag.active + anchors.fill: parent + onReleased: { + if (someRect.x > leftBound + ( ( rightBound - leftBound ) * threshold ) ) + { + someRect.x = rightBound + } else { + someRect.x = leftBound + } + } + drag.target: parent; + drag.axis: "XAxis" + drag.minimumX: leftBound + drag.maximumX: rightBound + } + } + + Rectangle { + id : right + width : 10 + height : _topLevel.height + x : rightBound + someRect.width + 2 + y : _topLevel.y + color : "red" + } + + Rectangle { + width : 1 + height : 10 + x : leftBound + ( ( rightBound - leftBound ) * threshold ) + y : _topLevel.y + _topLevel.height + color : "red" + } +} diff --git a/kdepim-runtime/qml/kde/tests/SomeComponent.qml b/kdepim-runtime/qml/kde/tests/SomeComponent.qml new file mode 100644 index 00000000..37006c70 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/SomeComponent.qml @@ -0,0 +1,47 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + x : 30 + y : 30 + width : 30 + height : 30 + id : blueRect + color : "blue" + + function mySlot() + { + console.log("Invoked"); + } + + function makeConnections() + { + for ( var i = 0; i < children.length; ++i ) { + children[i].triggered.disconnect(this, mySlot) + children[i].triggered.connect(this, mySlot) + } + } + + onChildrenChanged : makeConnections() + Component.onCompleted : makeConnections() +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/actionstest.qml b/kdepim-runtime/qml/kde/tests/actionstest.qml new file mode 100644 index 00000000..fff429de --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/actionstest.qml @@ -0,0 +1,103 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 as QML +import org.kde.pim.mobileui 4.5 as KPIM + +QML.Rectangle { + width : 800 + height : 500 + + KPIM.ActionMenuContainer { + x : 100 + y : 100 + width : 600 + height : 300 + id :myTree + actionItemHeight : 70 + actionItemWidth : 200 + actionItemSpacing : 2 + + KPIM.FakeAction { name : "quit" } + + KPIM.ActionList { + name : "file" + KPIM.ActionList { + name : "new" + KPIM.FakeAction { name : "new event" } + KPIM.FakeAction { name : "new alarm" } + KPIM.FakeAction { name : "new birthday" } + + } + KPIM.FakeAction { name : "import" } + KPIM.FakeAction { name : "export" } + } + + KPIM.ActionList { + name : "edit" + KPIM.FakeAction { name : "copy" } + KPIM.FakeAction { name : "move" } + KPIM.FakeAction { name : "cut" } + + } + + KPIM.ReorderList { + name : "reorder" + + delegate : QML.Component { + QML.Text { height : 20; text : model.index } + } + model : 15 + + } + + KPIM.ActionList { + name : "view" + KPIM.FakeAction { name : "month_view" } + KPIM.FakeAction { name : "day_view" } + KPIM.FakeAction { name : "week_view" } + KPIM.FakeAction { name : "hour_view" } + + } + + /* + KPIM.ActionList { + name : "view" + KPIM.ActionListItem { name : "month" } + KPIM.ActionListItem { name : "week" } + KPIM.ActionListItem { name : "day" } + } */ + /* + ActionList { + name : "view" + Action { "A" } + ActionList { + name : "B" + Action { "B1" } + Action { "B2" } + } + Action { + Action { "C" } + } + } */ + } + +} diff --git a/kdepim-runtime/qml/kde/tests/behaviouronx.qml b/kdepim-runtime/qml/kde/tests/behaviouronx.qml new file mode 100644 index 00000000..7400a13f --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/behaviouronx.qml @@ -0,0 +1,36 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 800 + height : 480 + + SliderComponent { + id : one + y : 30 + x : 50 + height : 100 + leftBound : 30 + rightBound : 130 + } +} diff --git a/kdepim-runtime/qml/kde/tests/bindinggroupedproperties.qml b/kdepim-runtime/qml/kde/tests/bindinggroupedproperties.qml new file mode 100644 index 00000000..8790df3b --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/bindinggroupedproperties.qml @@ -0,0 +1,30 @@ +import Qt 4.7 + +Item { + width : 400 + height : 400 + Rectangle { + id : rectY + color : "yellow" + width : maY.containsMouse ? 200 : 100 + height : 100 + MouseArea { + id : maY + hoverEnabled : true + anchors.fill : parent + } + } + Rectangle { + id : rectR + color : "red" + width : 50 + height : 50 + //anchors.left : rectY.right + } + Binding { + target : rectR + property : "anchors.left" + value : rectY.right + } +} + diff --git a/kdepim-runtime/qml/kde/tests/collapsibilesections.qml b/kdepim-runtime/qml/kde/tests/collapsibilesections.qml new file mode 100644 index 00000000..b0d77e5f --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/collapsibilesections.qml @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This example shows how a ListView can be separated into sections using +// the ListView.section attached property. + +import Qt 4.7 + +//! [0] +Rectangle { + id: container + width: 200 + height: 250 + + ListModel { + id: animalsModel + ListElement { name: "Parrot"; size: "Small" } + ListElement { name: "Guinea pig"; size: "Small" } + ListElement { name: "Dog"; size: "Medium" } + ListElement { name: "Cat"; size: "Medium" } + ListElement { name: "Elephant"; size: "Large" } + } + + // The delegate for each section header + Component { + id: sectionHeading + Rectangle { + width: container.width + height: childrenRect.height + color: "lightsteelblue" + + Text { + text: section + font.bold: true + } + MouseArea { + anchors.fill : parent + onClicked: + { + if (myList.expanded.indexOf(section) != -1) { + myList.expanded = myList.expanded.replace(section + ",", "") + } + else { + myList.expanded += (section + ",") + } + } + } + } + } + Component { + id : itemDelegate + + Text { + clip :true + height : ListView.view.expanded.indexOf(model.size) > -1 ? 30 : 0 + text: name + } + } + ListView { + id : myList + property string expanded + + onExpandedChanged : + console.log("expanded" + expanded) + + anchors.fill: parent + model: animalsModel + delegate: itemDelegate + + section.property: "size" + section.criteria: ViewSection.FullString + section.delegate: sectionHeading + } +} +//! [0] + diff --git a/kdepim-runtime/qml/kde/tests/disconnect.qml b/kdepim-runtime/qml/kde/tests/disconnect.qml new file mode 100644 index 00000000..8a4f02bd --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/disconnect.qml @@ -0,0 +1,51 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 200 + height : 200 + + SomeComponent { + x : 30 + y : 30 + width : 30 + height : 30 + id : blueRect + color : "blue" + + Rectangle { + x : 130 + y : 30 + width : 30 + height : 30 + color : "red" + + signal triggered() + + MouseArea { + anchors.fill : parent + onClicked : parent.triggered() + } + } + } +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/dragitems-0.qml b/kdepim-runtime/qml/kde/tests/dragitems-0.qml new file mode 100644 index 00000000..ef6fdb79 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/dragitems-0.qml @@ -0,0 +1,77 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 400 + height : 400 + + Rectangle { + id : dropRect + x : 130 + y : 30 + width : 90 + height : 150 + color : "green" + } + Rectangle { + x : 30 + y : 30 + width : 90 + height : 150 + id : blueRect + color : "blue" + + Rectangle { + x : 10 + y : 30 + width : 70 + height : 90 + color : "yellow" + + Rectangle { + id : placeholder + x : 20 + y : 30 + width : 30 + height : 30 +// clip : true + Rectangle { + id : targetRect + width : parent.width + height : parent.height + color : "lightsteelblue" + z : 10000 + + MouseArea { + anchors.fill : parent + + drag.target : targetRect + drag.axis : Drag.XAxis + drag.minimumX : 0 + drag.maximumX : 100 + } + } + } + } + } +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/elementmodel.qml b/kdepim-runtime/qml/kde/tests/elementmodel.qml new file mode 100644 index 00000000..c46b7b96 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/elementmodel.qml @@ -0,0 +1,92 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 500 + height : 500 + + VisualItemModel { + id : items + Rectangle { + id : top1 + width : 100 //ListView.view.width + height : 30 + color : "red" + MouseArea { + anchors.fill : parent + onClicked : { + myList.currentIndex = top1.VisualItemModel.index + } + } + } + Rectangle { + id : top2 + width : 100 //ListView.view.width + height : 30 + color : "green" + MouseArea { + anchors.fill : parent + onClicked : { + myList.currentIndex = top2.VisualItemModel.index + } + } + } + Rectangle { + id : top3 + width : 100 //ListView.view.width + height : 30 + color : "blue" + MouseArea { + anchors.fill : parent + onClicked : { + myList.currentIndex = top3.VisualItemModel.index + } + } + } + } + + Component { + id: highlightBar + Rectangle { + x : -10 + width: 120; height: 30 + color: "#66FFFF88" + y: myList.currentItem.y; + Behavior on y { NumberAnimation { duration : 100; easing.type : OutQuad } } + } + } + + ListView { + id : myList + x : 30 + y : 30 + width : 100 + height : 100 + model : items + focus : true + + delegate : Text { text : model.index; height : 30; width : ListView.view.width } + + highlight : highlightBar + } +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/i18n.qml b/kdepim-runtime/qml/kde/tests/i18n.qml new file mode 100644 index 00000000..36dd98dc --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/i18n.qml @@ -0,0 +1,51 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 +import org.kde 4.5 + +Rectangle { + width: 100; height: 100; color: "blue" + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + console.log( "*** begin i18n test ***" ); + console.log( KDE.i18n( "single message" ) ); + console.log( KDE.i18n( "single message with arguments %1 %2", "bar", "foo" ) ); + console.log( KDE.i18n( "single message with arguments multiline %1 %2", + "bar", + "foo" ) ); + console.log( KDE.i18nc( "context", "message with context and \n to break our script" ) ); + console.log( KDE.i18nc( "context", "message with context and arguments %1 %2 %3", 5, 3.1415, "string" ) ); + console.log( KDE.i18np( "singular", "%1 plural", 1 ) ); + console.log( KDE.i18np( "singular", "%1 plural", 2 ) ); + console.log( KDE.i18np( "singular with arg %2", "%1 plural with arg %2", 1, "bla" ) ); + console.log( KDE.i18np( "singular with arg %2", "%1 plural with arg %2", 2, "bla" ) ); + console.log( KDE.i18ncp( "context", "singular", "%1 plural", 1 ) ); + console.log( KDE.i18ncp( "context", "singular", "%1 plural", 2 ) ); + console.log( KDE.i18ncp( "context", "singular with arg %2", "%1 plural with arg %2", 1, "bla" ) ); + console.log( KDE.i18ncp( "context", "singular with arg %2", "%1 plural with arg %2", 2, "bla" ) ); + console.log( "*** end i18n test ***" ); + } + } + Text { + text: KDE.i18n( "Click to run test!" ); + } + } diff --git a/kdepim-runtime/qml/kde/tests/icons.qml b/kdepim-runtime/qml/kde/tests/icons.qml new file mode 100644 index 00000000..a82217fb --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/icons.qml @@ -0,0 +1,40 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 +import org.kde 4.5 + +Image { + width: 256; height: 256; + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + console.log( "*** begin icon path test ***" ); + console.log( "icon 4: " + KDE.iconPath( "akonadi", 4 ) ); + console.log( "icon 15: " + KDE.iconPath( "akonadi", 15 ) ); + console.log( "icon 32: " + KDE.iconPath( "akonadi", 32 ) ); + console.log( "icon 65: " + KDE.iconPath( "akonadi", 65 ) ); + console.log( "icon 1000: " + KDE.iconPath( "akonadi", 1000 ) ); + console.log( "*** end icon path test ***" ); + } + } + + source: KDE.iconPath( "akonadi", 256 ); + } diff --git a/kdepim-runtime/qml/kde/tests/kstandarddirs.qml b/kdepim-runtime/qml/kde/tests/kstandarddirs.qml new file mode 100644 index 00000000..81f22793 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/kstandarddirs.qml @@ -0,0 +1,33 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 +import org.kde 4.5 + +Rectangle { + width: 256; height: 256; + MouseArea { + anchors.fill: parent + onClicked: { + console.log( "*** begin kstandarddirs test ***" ); + console.log( "share/apps/mobileui/top.png: " + KDE.locate( "data", "mobileui/top.png" ) ); + console.log( "*** end kstandarddirs test ***" ); + } + } +} diff --git a/kdepim-runtime/qml/kde/tests/mm2px.qml b/kdepim-runtime/qml/kde/tests/mm2px.qml new file mode 100644 index 00000000..e4aa42b8 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/mm2px.qml @@ -0,0 +1,35 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 +import org.kde 4.5 + +Rectangle { + width: KDE.mm2px( 100 ) + height: KDE.mm2px( 100 ) + color: "lightsteelblue" + Rectangle { + width: KDE.mm2px( 40 ) + height: KDE.mm2px( 40 ) + x: KDE.mm2px( 20 ) + y: KDE.mm2px( 10 ) + color: "yellow" + } +} + diff --git a/kdepim-runtime/qml/kde/tests/propagateevent.qml b/kdepim-runtime/qml/kde/tests/propagateevent.qml new file mode 100644 index 00000000..98cf51f6 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/propagateevent.qml @@ -0,0 +1,125 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 800 + height : 480 + + Flickable { + x : 400 + y : 0 + width : 100 + height : 350 + clip : true + flickableDirection: Flickable.VerticalFlick + contentHeight: myColumn.height; + + Column { + id : myColumn + Text { height : 20; width : 100; text : "a" } + Text { height : 20; width : 100; text : "b" } + Text { height : 20; width : 100; text : "c" } + Text { height : 20; width : 100; text : "d" } + Text { height : 20; width : 100; text : "e" } + Text { height : 20; width : 100; text : "f" } + Text { height : 20; width : 100; text : "g" } + Text { height : 20; width : 100; text : "h" } + Text { height : 20; width : 100; text : "i" } + Text { height : 20; width : 100; text : "j" } + Text { height : 20; width : 100; text : "k" } + Text { height : 20; width : 100; text : "l" } + Text { height : 20; width : 100; text : "m" } + Text { height : 20; width : 100; text : "n" } + Text { height : 20; width : 100; text : "o" } + Text { height : 20; width : 100; text : "p" } + Text { height : 20; width : 100; text : "q" } + Text { height : 20; width : 100; text : "r" } + Text { height : 20; width : 100; text : "s" } + Text { height : 20; width : 100; text : "t" } + Text { height : 20; width : 100; text : "u" } + Text { height : 20; width : 100; text : "v" } + Text { height : 20; width : 100; text : "w" } + Text { height : 20; width : 100; text : "x" } + Text { height : 20; width : 100; text : "y" } + Text { height : 20; width : 100; text : "z" } + } + } + + Rectangle { + x : 30 + y : 30 + color : "lightsteelblue" + width : 300 + height : 300 + + MouseArea { + id: mrDrag + anchors.fill: parent + + onClicked : { + mouse.accepted = false; + return; + } + + onReleased: { + if (!drag.active) + { + mouse.accepted = false; + return; + } + } + + Rectangle { + color : "yellow" + x : 50 + y : 50 + z : 1000 + width : 100 + height : 100 + + + MouseArea { + anchors.fill : parent + onClicked : { + console.log("Clicked"); + } + onPressed : { + console.log("P") + mouse.accepted = true + } + onPressAndHold : { + console.log("P AND H") + mouse.accepted = false + } + } + } + + drag.target: parent; + drag.axis: "XAxis" + drag.filterChildren : true + drag.minimumX: 30 + drag.maximumX: 430 + } + } + +} diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/CMakeLists.txt b/kdepim-runtime/qml/kde/tests/qml_moves/CMakeLists.txt new file mode 100644 index 00000000..813c3ec8 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/CMakeLists.txt @@ -0,0 +1,42 @@ +project(qml_moves) + +cmake_minimum_required(VERSION 2.6) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) + +# Not depending on KDE here for easier portability. +find_package(Qt4 REQUIRED) + +include_directories( + ${QT_INCLUDES} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTDECLARATIVE_INCLUDE_DIR} + ${PROJECT_BINARY_DIR} +) + +set(app_SRCS + dynamictreemodel.cpp + mainwindow.cpp + main.cpp +) + +set(app_qml_SRCS + mainview.qml +) + +foreach( qml_src ${app_qml_SRCS}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/${qml_src}" "${CMAKE_BINARY_DIR}/${qml_src}") +endforeach() + +qt4_automoc( app_MOC_SRCS ${app_SRCS} ) + +add_executable(qml_moves + ${app_SRCS} +) + +target_link_libraries( + qml_moves + ${QT_QTCORE_LIBRARIES} + ${QT_QTGUI_LIBRARIES} + ${QT_QTDECLARATIVE_LIBRARIES} +) diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.cpp b/kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.cpp new file mode 100644 index 00000000..5537bb74 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.cpp @@ -0,0 +1,1009 @@ +/* + Copyright (c) 2009 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 "dynamictreemodel.h" + +#include +#include +#include +#include + +#include + +#include + +// If DUMPTREE is defined, ModelInsertCommand dumps the tree of what it is inserting. +// #define DUMPTREE +#ifdef DUMPTREE +#include +#endif + +DynamicTreeModel::DynamicTreeModel(QObject *parent) + : QAbstractItemModel(parent), + nextId(1) +{ +} + +QModelIndex DynamicTreeModel::index(int row, int column, const QModelIndex &parent) const +{ +// if (column != 0) +// return QModelIndex(); + + if ( column < 0 || row < 0 ) + return QModelIndex(); + + QList > childIdColumns = m_childItems.value(parent.internalId()); + + const qint64 grandParent = findParentId(parent.internalId()); + if (grandParent >= 0) { + QList > parentTable = m_childItems.value(grandParent); + Q_ASSERT(parent.column() < parentTable.size()); + QList parentSiblings = parentTable.at(parent.column()); + Q_ASSERT(parent.row() < parentSiblings.size()); + } + + if (childIdColumns.size() == 0) + return QModelIndex(); + + if (column >= childIdColumns.size()) + return QModelIndex(); + + QList rowIds = childIdColumns.at(column); + + if ( row >= rowIds.size()) + return QModelIndex(); + + qint64 id = rowIds.at(row); + + return createIndex(row, column, reinterpret_cast(id)); + +} + +qint64 DynamicTreeModel::findParentId(qint64 searchId) const +{ + if (searchId <= 0) + return -1; + + QHashIterator > > i(m_childItems); + while (i.hasNext()) + { + i.next(); + QListIterator > j(i.value()); + while (j.hasNext()) + { + QList l = j.next(); + if (l.contains(searchId)) + { + return i.key(); + } + } + } + return -1; +} + +QModelIndex DynamicTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + qint64 searchId = index.internalId(); + qint64 parentId = findParentId(searchId); + // Will never happen for valid index, but what the hey... + if (parentId <= 0) + return QModelIndex(); + + qint64 grandParentId = findParentId(parentId); + if (grandParentId < 0) + grandParentId = 0; + + int column = 0; + QList childList = m_childItems.value(grandParentId).at(column); + + int row = childList.indexOf(parentId); + + return createIndex(row, column, reinterpret_cast(parentId)); + +} + +int DynamicTreeModel::rowCount(const QModelIndex &index ) const +{ + QList > cols = m_childItems.value(index.internalId()); + + if (cols.size() == 0 ) + return 0; + + if (index.column() > 0) + return 0; + + return cols.at(0).size(); +} + +int DynamicTreeModel::columnCount(const QModelIndex &index ) const +{ +// Q_UNUSED(index); + return m_childItems.value(index.internalId()).size(); +} + +QVariant DynamicTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if ( DynamicTreeModelId == role ) + return index.internalId(); + + if (Qt::DisplayRole == role || Qt::EditRole == role) + { + return m_items.value(index.internalId()); + } + return QVariant(); +} + + +bool DynamicTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole) + { + m_items[index.internalId()] = value.toString(); + dataChanged(index, index); + return true; + } + + return QAbstractItemModel::setData(index, value, role); +} + +void DynamicTreeModel::clear() +{ + beginResetModel(); + m_items.clear(); + m_childItems.clear(); + nextId = 1; + endResetModel(); +} + + +bool DynamicTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int _column, const QModelIndex& parent) +{ + Q_UNUSED(action); + Q_UNUSED(_column); + QByteArray encoded = data->data(mimeTypes().first()); + + QHash > movedItems; + bool ok; + qint64 id; + int _row; + static const int column = 0; + QHash > >::const_iterator it; + foreach(const QByteArray &ba, encoded.split('\0')) + { + id = ba.toInt(&ok); + if (!ok) + qDebug() << ba; + Q_ASSERT(ok); + + _row = -1; + for (it = m_childItems.constBegin(); it != m_childItems.constEnd(); ++it) + { + _row = it.value().first().indexOf(id); + if (_row < 0) + continue; + movedItems[createIndex(_row, column, reinterpret_cast(id)).parent()].append(_row); + break; + } + Q_ASSERT(_row >= 0); + if (_row < 0) + return false; + } + + const int destRow = row < 0 ? 0 : row; + const QList destPath = indexToPath(parent); + + QList srcPath; + QModelIndex srcParent; + QHash >::iterator src_parent_it = movedItems.begin(); + int startRow = 0; + int endRow = 0; + int nextMovedRow = 0; + + QList rowsMoved; + QList::iterator src_row_it; + QList::iterator rows_moved_end; + QList moveCommands; + + for ( ; src_parent_it != movedItems.end(); ++src_parent_it) + { + srcParent = src_parent_it.key(); + srcPath = indexToPath(srcParent); + + rowsMoved = src_parent_it.value(); + qSort(rowsMoved); + src_row_it = rowsMoved.begin(); + rows_moved_end = rowsMoved.end(); + startRow = *src_row_it; + endRow = startRow; + ++src_row_it; + + if (src_row_it == rows_moved_end) + { + moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); + continue; + } + + for ( ; src_row_it != rows_moved_end; ++src_row_it) + { + nextMovedRow = *src_row_it; + + if (nextMovedRow == endRow + 1) + { + ++endRow; + } else { + Q_ASSERT(nextMovedRow > endRow + 1); + moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); + startRow = nextMovedRow; + endRow = nextMovedRow; + + if ((src_row_it + 1) == rows_moved_end) + moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); + + } + } + } + + QPersistentModelIndex destParent = parent; + QPersistentModelIndex destRowIndex = index(destRow, column, parent); + + ModelMoveCommand *firstCommand = moveCommands.takeFirst(); + firstCommand->setDestAncestors(indexToPath(parent)); + firstCommand->setDestRow(destRow); + firstCommand->doCommand(); + + if (!destRowIndex.isValid()) + destRowIndex = index(destRow, column, parent); + + int offset = firstCommand->endRow() - firstCommand->startRow() + 1; + foreach(ModelMoveCommand *moveCommand, moveCommands) + { + moveCommand->setDestAncestors(indexToPath(destParent)); + moveCommand->setDestRow(destRowIndex.row() + offset); + moveCommand->doCommand(); + offset = moveCommand->endRow() - moveCommand->startRow() + 1; + } + + return false; +} + +ModelMoveCommand* DynamicTreeModel::getMoveCommand(const QList &srcPath, int startRow, int endRow) +{ + ModelMoveCommand *moveCommand = new ModelMoveCommand(this, this); + moveCommand->setAncestorRowNumbers(srcPath); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + return moveCommand; +} + +Qt::ItemFlags DynamicTreeModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (index.isValid()) + return flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; + return flags; +} + +Qt::DropActions DynamicTreeModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +QStringList DynamicTreeModel::mimeTypes() const +{ + QStringList types; + types << QLatin1String("application/x-dynamictreemodel-itemlist"); + return types; +} + +QMimeData* DynamicTreeModel::mimeData(const QModelIndexList& indexes) const +{ + QMimeData *data = new QMimeData(); + QByteArray itemData; + QModelIndexList::const_iterator it = indexes.begin(); + const QModelIndexList::const_iterator end = indexes.end(); + while(it != end) + { + itemData.append(QByteArray::number(it->internalId())); + ++it; + if (it != end) + itemData.append('\0'); + } + data->setData(mimeTypes().first(), itemData); + return data; +} + +QList DynamicTreeModel::indexToPath(const QModelIndex &_idx) const +{ + QList list; + QModelIndex idx = _idx; + while (idx.isValid()) + { + list.prepend(idx.row()); + idx = idx.parent(); + } + return list; +} + +QModelIndexList DynamicTreeModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const +{ + if (role != DynamicTreeModelId) + return QAbstractItemModel::match(start, role, value, hits, flags); + + qint64 id = value.toLongLong(); + + QHash > >::const_iterator it = m_childItems.constBegin(); + const QHash > >::const_iterator end = m_childItems.constEnd(); + + QList > items; + QList >::const_iterator itemIt; + QList >::const_iterator itemEnd; + int foundIndexRow; + for ( ; it != end; ++it) + { + items = it.value(); + itemEnd = items.constEnd(); + for (itemIt = items.constBegin(); itemIt != itemEnd; ++itemIt) + { + foundIndexRow = itemIt->indexOf(id); + if (foundIndexRow != -1) + { + static const int column = 0; + return QModelIndexList() << createIndex(foundIndexRow, column, reinterpret_cast(id)); + } + } + } + return QModelIndexList(); +} + +ModelChangeCommand::ModelChangeCommand( DynamicTreeModel *model, QObject *parent ) + : QObject(parent), m_model(model), m_startRow(-1), m_endRow(-1), m_numCols(1) +{ + +} + +QModelIndex ModelChangeCommand::findIndex(const QList &rows) const +{ + const int col = 0; + QModelIndex parent = QModelIndex(); + QListIterator i(rows); + while (i.hasNext()) + { + parent = m_model->index(i.next(), col, parent); + Q_ASSERT(parent.isValid()); + } + return parent; +} + +ModelInsertCommand::ModelInsertCommand(DynamicTreeModel *model, QObject *parent ) + : ModelChangeCommand(model, parent) +{ + +} + +QList ModelInsertCommand::tokenize(const QString& treeString) const +{ + QStringList parts = treeString.split("-"); + + QList tokens; + + const QStringList::const_iterator begin = parts.constBegin(); + const QStringList::const_iterator end = parts.constEnd(); + + QStringList::const_iterator it = begin; + ++it; + for (; it != end; ++it) + { + Token token; + if (it->trimmed().isEmpty()) + { + token.type = Token::Branch; + } else { + token.type = Token::Leaf; + token.content = *it; + } + tokens.append(token); + } + return tokens; +} + +void ModelInsertCommand::interpret(const QString& treeString) +{ + m_treeString = treeString; + + QList depths = getDepths(m_treeString); + + int size = 0; + qCount(depths, 0, size); + Q_ASSERT(size != 0); + + m_endRow = m_startRow + size - 1; +} + +QList ModelInsertCommand::getDepths(const QString& treeString) const +{ + int depth = 0; + QList depths; + +#ifdef DUMPTREE + int id = 1; +#endif + + QList tokens = tokenize(treeString); + while(!tokens.isEmpty()) + { + Token token = tokens.takeFirst(); + + if (token.type == Token::Branch) + { + ++depth; + continue; + } + Q_ASSERT(token.type == Token::Leaf); + + depths.append(depth); +#ifdef DUMPTREE + std::cout << "\""; + for (int i = 0; i <= depth; ++i) + std::cout << " -"; + std::cout << " " << id++ << "\"" << std::endl; +#endif + depth = 0; + } + + return depths; +} + +void ModelInsertCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + + if (!m_treeString.isEmpty()) + { + QList depths = getDepths(m_treeString); + + int size = 0; + qCount(depths, 0, size); + Q_ASSERT(size != 0); + m_endRow = m_startRow + size - 1; + } + m_model->beginInsertRows(parent, m_startRow, m_endRow); + if (!m_treeString.isEmpty()) + { + doInsertTree(parent); + } else { + qint64 parentId = parent.internalId(); + + for (int row = m_startRow; row <= m_endRow; row++) + { + for(int col = 0; col < m_numCols; col++ ) + { + if (m_model->m_childItems[parentId].size() <= col) + { + m_model->m_childItems[parentId].append(QList()); + } + qint64 id = m_model->newId(); + QString name = QString::number(id); + + m_model->m_items.insert(id, name); + m_model->m_childItems[parentId][col].insert(row, id); + + } + } + } + m_model->endInsertRows(); +} + +void ModelInsertCommand::doInsertTree(const QModelIndex &fragmentParent) +{ + QList depths = getDepths(m_treeString); + + qint64 fragmentParentIdentifier = fragmentParent.internalId(); + + QList::const_iterator it = depths.constBegin(); + const QList::const_iterator end = depths.constEnd(); + + QList recentParents; + recentParents.append(fragmentParentIdentifier); + + qint64 lastId = 0; + qint64 id; + QString name; + int depth = 0; + int row = m_startRow; + Q_ASSERT(*it == depth); + + QList rows; + rows.append(row); + + for( ; it != end; ++it) + { + if (*it > depth) + { + Q_ASSERT(*it == depth + 1); + fragmentParentIdentifier = lastId; + if (recentParents.size() == *it) + recentParents.append(fragmentParentIdentifier); + else + recentParents[*it] = fragmentParentIdentifier; + + ++depth; + + } else if ( *it < depth ) + { + fragmentParentIdentifier = recentParents.at(*it); + depth = (*it); + } + + if (rows.size() == depth) + rows.append(0); + + id = m_model->newId(); + lastId = id; + for (int column = 0; column < m_numCols; ++column) { + if (m_model->m_childItems[fragmentParentIdentifier].size() <= column) + { + m_model->m_childItems[fragmentParentIdentifier].append(QList()); + } + m_model->m_items.insert(id, QString::number(id)); + m_model->m_childItems[fragmentParentIdentifier][column].insert(rows[depth], id); + if (column != m_numCols - 1) + id = m_model->newId(); + } + rows[depth]++; + } +} + + +ModelInsertAndRemoveQueuedCommand::ModelInsertAndRemoveQueuedCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + qRegisterMetaType("QModelIndex"); +} + +void ModelInsertAndRemoveQueuedCommand::queuedBeginInsertRows(const QModelIndex& parent, int start, int end) +{ + m_model->beginInsertRows(parent, start, end); +} + +void ModelInsertAndRemoveQueuedCommand::queuedEndInsertRows() +{ + m_model->endInsertRows(); +} + +void ModelInsertAndRemoveQueuedCommand::queuedBeginRemoveRows(const QModelIndex& parent, int start, int end) +{ + m_model->beginRemoveRows(parent, start, end); +} + +void ModelInsertAndRemoveQueuedCommand::queuedEndRemoveRows() +{ + m_model->endRemoveRows(); +} + +void ModelInsertAndRemoveQueuedCommand::purgeItem(qint64 parent) +{ + QList > childItemRows = m_model->m_childItems.value(parent); + + if (childItemRows.size() > 0) + { + for (int col = 0; col < m_numCols; col++) + { + QList childItems = childItemRows[col]; + foreach(qint64 item, childItems) + { + purgeItem(item); + m_model->m_childItems[parent][col].removeOne(item); + } + } + } + m_model->m_items.remove(parent); +} + +void ModelInsertAndRemoveQueuedCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + + connect (this, SIGNAL(beginInsertRows(QModelIndex,int,int)), SLOT(queuedBeginInsertRows(QModelIndex,int,int)), Qt::QueuedConnection); + connect (this, SIGNAL(endInsertRows()), SLOT(queuedEndInsertRows()), Qt::QueuedConnection); + connect (this, SIGNAL(beginRemoveRows(QModelIndex,int,int)), SLOT(queuedBeginRemoveRows(QModelIndex,int,int)), Qt::QueuedConnection); + connect (this, SIGNAL(endRemoveRows()), SLOT(queuedEndRemoveRows()), Qt::QueuedConnection); + + emit beginInsertRows(parent, m_startRow, m_endRow); +// m_model->beginInsertRows(parent, m_startRow, m_endRow); + qint64 parentId = parent.internalId(); + for (int row = m_startRow; row <= m_endRow; row++) + { + for(int col = 0; col < m_numCols; col++ ) + { + if (m_model->m_childItems[parentId].size() <= col) + { + m_model->m_childItems[parentId].append(QList()); + } + qint64 id = m_model->newId(); + QString name = QString::number(id); + + m_model->m_items.insert(id, name); + m_model->m_childItems[parentId][col].insert(row, id); + + } + } + + emit endInsertRows(); +// m_model->endInsertRows(); + + emit beginRemoveRows(parent, m_startRow, m_endRow); +// m_model->beginRemoveRows(parent, m_startRow, m_endRow); + for(int col = 0; col < m_numCols; col++ ) + { + QList childItems = m_model->m_childItems.value(parentId).value(col); + for (int row = m_startRow; row <= m_endRow; row++) + { + qint64 item = childItems[row]; + purgeItem(item); + m_model->m_childItems[parentId][col].removeOne(item); + } + } + emit endRemoveRows(); +// m_model->endRemoveRows(); + +} + +ModelRemoveCommand::ModelRemoveCommand(DynamicTreeModel *model, QObject *parent ) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelRemoveCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + m_model->beginRemoveRows(parent, m_startRow, m_endRow); + qint64 parentId = parent.internalId(); + for(int col = 0; col < m_numCols; col++ ) + { + QList childItems = m_model->m_childItems.value(parentId).value(col); + for (int row = m_startRow; row <= m_endRow; row++) + { + qint64 item = childItems[row]; + purgeItem(item); + m_model->m_childItems[parentId][col].removeOne(item); + } + } + m_model->endRemoveRows(); +} + +void ModelRemoveCommand::purgeItem(qint64 parent) +{ + QList > childItemRows = m_model->m_childItems.value(parent); + + if (childItemRows.size() > 0) + { + for (int col = 0; col < m_numCols; col++) + { + QList childItems = childItemRows[col]; + foreach(qint64 item, childItems) + { + purgeItem(item); + m_model->m_childItems[parent][col].removeOne(item); + } + } + } + m_model->m_items.remove(parent); +} + + +ModelDataChangeCommand::ModelDataChangeCommand(DynamicTreeModel *model, QObject *parent) + : ModelChangeCommand(model, parent), m_startColumn(0) +{ + +} + +void ModelDataChangeCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + QModelIndex topLeft = m_model->index(m_startRow, m_startColumn, parent); + QModelIndex bottomRight = m_model->index(m_endRow, m_numCols - 1, parent); + + QList > childItems = m_model->m_childItems[parent.internalId()]; + + + for (int col = m_startColumn; col < m_startColumn + m_numCols; col++) + { + for (int row = m_startRow; row <= m_endRow; row++ ) + { + QString name = QString::number( m_model->newId() ); + m_model->m_items[childItems[col][row]] = name; + } + } + m_model->dataChanged(topLeft, bottomRight); +} + + +ModelMoveCommand::ModelMoveCommand(DynamicTreeModel *model, QObject *parent) +: ModelChangeCommand(model, parent) +{ + +} + +bool ModelMoveCommand::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + return m_model->beginMoveRows(srcParent, srcStart, srcEnd, destParent, destRow); +} + +void ModelMoveCommand::doCommand() +{ + QModelIndex srcParent = findIndex(m_rowNumbers); + QModelIndex destParent = findIndex(m_destRowNumbers); + + if (!emitPreSignal(srcParent, m_startRow, m_endRow, destParent, m_destRow)) + { + return; + } + + for (int column = 0; column < m_numCols; ++column) + { + QList l = m_model->m_childItems.value(srcParent.internalId())[column].mid(m_startRow, m_endRow - m_startRow + 1 ); + + for (int i = m_startRow; i <= m_endRow ; i++) + { + m_model->m_childItems[srcParent.internalId()][column].removeAt(m_startRow); + } + int d; + if (m_destRow < m_startRow) + d = m_destRow; + else + { + if (srcParent == destParent) + d = m_destRow - (m_endRow - m_startRow + 1); + else + d = m_destRow - (m_endRow - m_startRow); + } + + foreach(const qint64 id, l) + { + + if (!m_model->m_childItems.contains(destParent.internalId())) + { + m_model->m_childItems[destParent.internalId()].append(QList()); + } + + m_model->m_childItems[destParent.internalId()][column].insert(d++, id); + } + } + + emitPostSignal(); +} + +void ModelMoveCommand::emitPostSignal() +{ + m_model->endMoveRows(); +} + + +ModelMoveLayoutChangeCommand::ModelMoveLayoutChangeCommand(DynamicTreeModel* model, QObject* parent): ModelMoveCommand(model, parent) +{ + +} + +ModelMoveLayoutChangeCommand::~ModelMoveLayoutChangeCommand() +{ + +} + +bool ModelMoveLayoutChangeCommand::emitPreSignal(const QModelIndex& srcParent, int srcStart, int srcEnd, const QModelIndex& destParent, int destRow) +{ + m_model->layoutAboutToBeChanged(); + + const int column = 0; + + for (int row = srcStart; row <= srcEnd; ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + + if (srcParent != destParent) + { + for (int row = srcEnd + 1; row < m_model->rowCount(srcParent); ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + for (int row = destRow; row < m_model->rowCount(destParent); ++row) + { + m_beforeMoveList << m_model->index(row, column, destParent); + } + } else { + if (destRow < srcStart) + { + for (int row = destRow; row < srcStart; ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + } else { + for (int row = srcStart + (srcEnd - srcStart + 1); row < destRow; ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + } + } + // We assume that the move was legal here. + return true; +} + +void ModelMoveLayoutChangeCommand::emitPostSignal() +{ + int srcStart = m_startRow; + int srcEnd = m_endRow; + int destRow = m_destRow; + + // Moving indexes may affect the m_rowNumbers and m_destRowNumbers. + // Instead of adjusting them programmatically, the test writer must specify them if they change. + + const QList sourceRowNumbers = m_endOfMoveSourceAncestors.isEmpty() ? m_rowNumbers : m_endOfMoveSourceAncestors; + QModelIndex srcParent = findIndex(sourceRowNumbers); + + const QList destRowNumbers = m_endOfMoveDestAncestors.isEmpty() ? m_destRowNumbers : m_endOfMoveDestAncestors; + QModelIndex destParent = findIndex(destRowNumbers); + + const int column = 0; + + QModelIndexList afterMoveList; + + if (srcParent != destParent) + { + for (int row = destRow; row <= (destRow + (srcEnd - srcStart)); ++row) + { + afterMoveList << m_model->index(row, column, destParent); + } + for (int row = srcStart; row < m_model->rowCount(srcParent); ++row) + { + afterMoveList << m_model->index(row, column, srcParent); + } + for (int row = destRow + (srcEnd - srcStart + 1); row < m_model->rowCount(destParent); ++row) + { + afterMoveList << m_model->index(row, column, destParent); + } + } else { + if (destRow < srcStart) + { + for (int row = srcStart; row <= srcEnd; ++row) + { + afterMoveList << m_model->index(destRow + (srcStart - row), column, destParent); + } + } else { + for (int row = srcStart; row <= srcEnd; ++row) + { + afterMoveList << m_model->index(destRow + (srcStart - row - 1), column, destParent); + } + } + if (destRow < srcStart) + { + for (int row = destRow + 1; row <= srcStart; ++row) + { + afterMoveList << m_model->index(row, column, srcParent); + } + } else { + for (int row = srcStart + (srcEnd - srcStart + 1); row < (srcStart + (destRow - srcEnd)); ++row) + { + afterMoveList << m_model->index(row - (srcEnd - srcStart + 1), column, srcParent); + } + } + } + + m_model->changePersistentIndexList(m_beforeMoveList, afterMoveList); + m_beforeMoveList.clear(); + m_model->layoutChanged(); + + +} + +ModelResetCommand::ModelResetCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + +} + +ModelResetCommand::~ModelResetCommand() +{ + +} + +void ModelResetCommand::setInitialTree(const QString& treeString) +{ + m_treeString = treeString; +} + +void ModelResetCommand::doCommand() +{ + m_model->beginResetModel(); + bool blocked = m_model->blockSignals(true); + m_model->clear(); + if (!m_treeString.isEmpty()) + { + ModelInsertCommand *ins = new ModelInsertCommand(m_model); + ins->setStartRow(0); + ins->interpret(m_treeString); + ins->doCommand(); + } + m_model->blockSignals(blocked); + m_model->endResetModel(); +} + +ModelLayoutChangeCommand::ModelLayoutChangeCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + +} + +ModelLayoutChangeCommand::~ModelLayoutChangeCommand() +{ + +} + +void ModelLayoutChangeCommand::setInitialTree(const QString& treeString) +{ + m_treeString = treeString; +} + +void ModelLayoutChangeCommand::setPersistentChanges(const QList< ModelLayoutChangeCommand::PersistentChange >& changes) +{ + m_changes = changes; +} + +void ModelLayoutChangeCommand::doCommand() +{ + m_model->layoutAboutToBeChanged(); + QModelIndexList oldList; + + foreach(const PersistentChange &change, m_changes) + { + const IndexFinder oldFinder(m_model, change.oldPath); + oldList << oldFinder.getIndex(); + } + + bool blocked = m_model->blockSignals(true); + m_model->clear(); + if (!m_treeString.isEmpty()) + { + ModelInsertCommand *ins = new ModelInsertCommand(m_model); + ins->setStartRow(0); + ins->interpret(m_treeString); + ins->doCommand(); + } + + QModelIndexList newList; + foreach(const PersistentChange &change, m_changes) + { + const IndexFinder newFinder(m_model, change.newPath); + newList << newFinder.getIndex(); + } + m_model->changePersistentIndexList(oldList, newList); + + m_model->blockSignals(blocked); + m_model->layoutChanged(); +} + + + diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.h b/kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.h new file mode 100644 index 00000000..54a12719 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/dynamictreemodel.h @@ -0,0 +1,358 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef DYNAMICTREEMODEL_H +#define DYNAMICTREEMODEL_H + +#include + +#include +#include + +#include + +#include "indexfinder.h" + +template class QList; + +class ModelMoveCommand; + +class DynamicTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + enum Roles + { + DynamicTreeModelId = Qt::UserRole, + + LastRole + }; + + explicit DynamicTreeModel(QObject *parent = 0); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &index = QModelIndex()) const; + int columnCount(const QModelIndex &index = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + Qt::ItemFlags flags(const QModelIndex& index) const; + Qt::DropActions supportedDropActions() const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits = 1, + Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const; + + void clear(); + QList indexToPath(const QModelIndex &idx) const; + ModelMoveCommand* getMoveCommand(const QList &srcPath, int startRow, int endRow); + +protected slots: + + /** + Finds the parent id of the string with id @p searchId. + + Returns -1 if not found. + */ + qint64 findParentId(qint64 searchId) const; + +private: + QHash m_items; + QHash > > m_childItems; + qint64 nextId; + qint64 newId() { return nextId++; }; + + QModelIndex m_nextParentIndex; + int m_nextRow; + + int m_depth; + int maxDepth; + + friend class ModelInsertCommand; + friend class ModelInsertWithDescendantsCommand; + friend class ModelRemoveCommand; + friend class ModelDataChangeCommand; + friend class ModelMoveCommand; + friend class ModelMoveLayoutChangeCommand; + friend class ModelResetCommand; + friend class ModelLayoutChangeCommand; +// friend class ModelSortIndexCommand; + friend class ModelSortIndexLayoutChangeCommand; + friend class ModelInsertAndRemoveQueuedCommand; + +}; + + +class ModelChangeCommand : public QObject +{ + Q_OBJECT +public: + + ModelChangeCommand( DynamicTreeModel *model, QObject *parent = 0 ); + + virtual ~ModelChangeCommand() {} + + void setAncestorRowNumbers(const QList &rowNumbers) { m_rowNumbers = rowNumbers; } + QList srcAncestors() const { return m_rowNumbers; } + + QModelIndex findIndex(const QList &rows) const; + + void setStartRow(int row) { m_startRow = row; } + + void setEndRow(int row) { m_endRow = row; } + + void setNumCols(int cols) { m_numCols = cols; } + + virtual void doCommand() = 0; + + QModelIndex parentIndex() const { return findIndex(m_rowNumbers); } + int startRow() const { return m_startRow; } + int endRow() const { return m_endRow; } + +protected: + DynamicTreeModel* m_model; + QList m_rowNumbers; + int m_startRow; + int m_endRow; + int m_numCols; + +}; + +typedef QList ModelChangeCommandList; + +/** + @brief Inserts a sub tree into the dynamictreemodel. + + As an alternative to setStartRow and setEndRow, the interpret command may be used. + + The interpret command is used to set the structure of the subtree. + + For example, + @code + cmd = new ModelInsertCommand(m_model, this); + cmd->interpret( + "- A" + "- B" + "- - C" + "- D" + ); + @endcode + + Will emit an insert for 3 rows, the second of which will have a child row. The interpretation + string may be complex as long as it is valid. The text at the end of each row does not need to be consistent. + There is a define DUMPTREE to make this command print the tree it inserts for better readability. + + @code + cmd->interpret( + "- A" + "- - B" + "- - C" + "- - - C" + "- - C" + "- - - C" + "- - - C" + "- - C" + "- D" + "- - E" + "- - F" + ); + @endcode + + The string is valid if (depth of row (N + 1)) <= ( (depth of row N) + 1). For example, the following is invalid + because the depth of B is 2 and the depth of A is 0. + + @code + cmd->interpret( + "- A" + "- - - B" + "- - C" + @endcode +*/ +class ModelInsertCommand : public ModelChangeCommand +{ + Q_OBJECT + + struct Token + { + enum Type { Branch, Leaf }; + Type type; + QString content; + }; + +public: + + explicit ModelInsertCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelInsertCommand() {} + + void interpret(const QString &treeString); + + virtual void doCommand(); + void doInsertTree(const QModelIndex &parent); + +protected: + QList tokenize(const QString &treeString) const; + + QList getDepths(const QString &treeString) const; + + QString m_treeString; +}; + +class ModelInsertAndRemoveQueuedCommand : public ModelChangeCommand +{ + Q_OBJECT + +public: + + explicit ModelInsertAndRemoveQueuedCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelInsertAndRemoveQueuedCommand() {} + + virtual void doCommand(); + +signals: + void beginInsertRows(const QModelIndex &parent, int start, int end); + void endInsertRows(); + void beginRemoveRows(const QModelIndex &parent, int start, int end); + void endRemoveRows(); + +protected slots: + void queuedBeginInsertRows(const QModelIndex &parent, int start, int end); + void queuedEndInsertRows(); + void queuedBeginRemoveRows(const QModelIndex &parent, int start, int end); + void queuedEndRemoveRows(); + +protected: + void purgeItem(qint64 parent); +}; + +class ModelRemoveCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + explicit ModelRemoveCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelRemoveCommand() {} + + virtual void doCommand(); + + void purgeItem(qint64 parent); +}; + +class ModelDataChangeCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + explicit ModelDataChangeCommand(DynamicTreeModel *model, QObject *parent = 0); + + virtual ~ModelDataChangeCommand() {} + + virtual void doCommand(); + + void setStartColumn(int column) { m_startColumn = column; } + +protected: + int m_startColumn; +}; + +class ModelMoveCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + explicit ModelMoveCommand(DynamicTreeModel *model, QObject *parent); + + virtual ~ModelMoveCommand() {} + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + + virtual void doCommand(); + + virtual void emitPostSignal(); + + void setDestAncestors( const QList &rows ) { m_destRowNumbers = rows; } + QList destAncestors() const { return m_destRowNumbers; } + + void setDestRow(int row) { m_destRow = row; } + +protected: + QList m_destRowNumbers; + int m_destRow; +}; + +class ModelMoveLayoutChangeCommand : public ModelMoveCommand +{ + Q_OBJECT +public: + explicit ModelMoveLayoutChangeCommand(DynamicTreeModel* model, QObject* parent); + virtual ~ModelMoveLayoutChangeCommand(); + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + + virtual void emitPostSignal(); + + void setEndOfMoveSourceAncestors(const QList &rows ) { m_endOfMoveSourceAncestors = rows; } + void setEndOfMoveDestAncestors(const QList &rows ) { m_endOfMoveDestAncestors = rows; } + +private: + QModelIndexList m_beforeMoveList; + QList m_endOfMoveSourceAncestors; + QList m_endOfMoveDestAncestors; + +}; + +class ModelResetCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelResetCommand(DynamicTreeModel* model, QObject* parent = 0); + virtual ~ModelResetCommand(); + + void setInitialTree(const QString &treeString); + + /* reimp */ void doCommand(); +private: + QString m_treeString; +}; + + +class ModelLayoutChangeCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelLayoutChangeCommand(DynamicTreeModel* model, QObject* parent = 0); + virtual ~ModelLayoutChangeCommand(); + + struct PersistentChange + { + QList oldPath; + QList newPath; + }; + + void setPersistentChanges(const QList &changes); + + void setInitialTree(const QString &treeString); + + /* reimp */ void doCommand(); +private: + QString m_treeString; + QList m_changes; +}; + + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/indexfinder.h b/kdepim-runtime/qml/kde/tests/qml_moves/indexfinder.h new file mode 100644 index 00000000..0cdbadcd --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/indexfinder.h @@ -0,0 +1,84 @@ +/* +Copyright (c) 2009 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. +*/ + +#ifndef INDEXFINDER_H +#define INDEXFINDER_H + +#include + +class IndexFinder +{ + public: + IndexFinder(QList rows = QList()) : m_rows(rows), m_model(0) {} + + IndexFinder(const QAbstractItemModel *model, QList rows = QList() ) + : m_rows(rows), m_model(model) + { + Q_ASSERT(model); + } + + QModelIndex getIndex() const + { + if(!m_model) + return QModelIndex(); + static const int col = 0; + QModelIndex parent = QModelIndex(); + QListIterator i(m_rows); + while (i.hasNext()) + { + parent = m_model->index(i.next(), col, parent); + Q_ASSERT(parent.isValid()); + } + return parent; + } + + static IndexFinder indexToIndexFinder(const QModelIndex &_idx) + { + if (!_idx.isValid()) + return IndexFinder(); + + QList list; + QModelIndex idx = _idx; + while (idx.isValid()) + { + list.prepend(idx.row()); + idx = idx.parent(); + } + return IndexFinder(_idx.model(), list); + } + + bool operator==( const IndexFinder &other ) const + { + return (m_rows == other.m_rows && m_model == other.m_model ); + } + + QList rows() const { return m_rows; } + void appendRow(int row) { m_rows.append(row); } + void setRows( const QList &rows ) { m_rows = rows; } + void setModel(QAbstractItemModel *model) { m_model = model; } + + private: + QList m_rows; + const QAbstractItemModel * m_model; +}; + + +Q_DECLARE_METATYPE( IndexFinder ) + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/main.cpp b/kdepim-runtime/qml/kde/tests/qml_moves/main.cpp new file mode 100644 index 00000000..58f87e50 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/main.cpp @@ -0,0 +1,18 @@ + +#include + +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + MainWindow mw; + mw.resize(640, 480); + mw.show(); + + return app.exec(); + +} + + diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/mainview.qml b/kdepim-runtime/qml/kde/tests/qml_moves/mainview.qml new file mode 100644 index 00000000..f07f976b --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/mainview.qml @@ -0,0 +1,45 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + color : "#f6f6f5" + anchors.fill : parent + width : 200 + + ListView { + anchors.fill : parent + width : 100 + model : _model + delegate: Text + { + height : 67 + text : model.display + } + + highlight : Rectangle { + opacity : .25 + width : parent.width + color : "blue" + } + } +} diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.cpp b/kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.cpp new file mode 100644 index 00000000..13245753 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dynamictreemodel.h" + +MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + : QWidget(parent, f) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + QSplitter *splitter = new QSplitter; + layout->addWidget(splitter); + + m_treeModel = new DynamicTreeModel(this); + ModelInsertCommand *insert = new ModelInsertCommand(m_treeModel, this); + insert->setStartRow(0); + insert->interpret( + "- 1" + "- 1" + "- 1" + "- 1" + "- 1" + ); + insert->doCommand(); + + QTreeView *view = new QTreeView(splitter); + view->setModel(m_treeModel); + + m_declarativeView = new QDeclarativeView(splitter); + + QDeclarativeContext *context = m_declarativeView->engine()->rootContext(); + + context->setContextProperty( "_model", m_treeModel ); + + context->setContextProperty( "application", QVariant::fromValue( static_cast( this ) ) ); + + m_declarativeView->setResizeMode( QDeclarativeView::SizeRootObjectToView ); + m_declarativeView->setSource( QUrl( "./mainview.qml" ) ); + + splitter->setSizes(QList() << 1 << 1); + QTimer::singleShot(2000, this, SLOT(doMove())); + +} + +void MainWindow::doMove() +{ + qDebug() << "MOV"; + ModelMoveCommand *command = new ModelMoveCommand(m_treeModel, this); + command->setStartRow(0); + command->setEndRow(0); + command->setDestRow(2); + command->doCommand(); +} + diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.h b/kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.h new file mode 100644 index 00000000..bb157285 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/mainwindow.h @@ -0,0 +1,34 @@ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include + +class QDeclarativeView; +class QTreeView; + +#include "dynamictreemodel.h" + +Q_DECLARE_METATYPE(QModelIndex) + +class KBreadcrumbNavigationFactory; + +class MainWindow : public QWidget +{ + Q_OBJECT +public: + MainWindow(QWidget* parent = 0, Qt::WindowFlags f = 0); + +public slots: + void doMove(); + +private: + QTreeView *m_treeView; + DynamicTreeModel *m_treeModel; + QDeclarativeView *m_declarativeView; + KBreadcrumbNavigationFactory *m_bnf; +}; + +#endif + diff --git a/kdepim-runtime/qml/kde/tests/qml_moves/qml_moves.pro b/kdepim-runtime/qml/kde/tests/qml_moves/qml_moves.pro new file mode 100644 index 00000000..736576b9 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qml_moves/qml_moves.pro @@ -0,0 +1,14 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Fri Dec 3 13:51:12 2010 +###################################################################### + +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += . + +QT += declarative + +# Input +HEADERS += dynamictreemodel.h indexfinder.h mainwindow.h +SOURCES += dynamictreemodel.cpp main.cpp mainwindow.cpp diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/001-change-opacity b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/001-change-opacity new file mode 100644 index 00000000..16049467 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/001-change-opacity @@ -0,0 +1,13 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +index 78b8bf1..44d789c 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +@@ -320,7 +320,7 @@ Item { + target : breadcrumbRightDivider + anchors.topMargin : -8 + height : {console.log(itemHeight); 67} +- opacity : 0 ++ opacity : 1 + } + PropertyChanges { + target : selectedItemPlaceHolder diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/002-change-opacity-with-braces b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/002-change-opacity-with-braces new file mode 100644 index 00000000..08f2da8e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/002-change-opacity-with-braces @@ -0,0 +1,13 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +index 78b8bf1..bc69a93 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +@@ -320,7 +320,7 @@ Item { + target : breadcrumbRightDivider + anchors.topMargin : -8 + height : {console.log(itemHeight); 67} +- opacity : 0 ++ opacity : { 1 } + } + PropertyChanges { + target : selectedItemPlaceHolder diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/003-change-opacity-properly b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/003-change-opacity-properly new file mode 100644 index 00000000..3af72938 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/003-change-opacity-properly @@ -0,0 +1,13 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +index 78b8bf1..9bb900e 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +@@ -320,7 +320,7 @@ Item { + target : breadcrumbRightDivider + anchors.topMargin : -8 + height : {console.log(itemHeight); 67} +- opacity : 0 ++ opacity : selectedItemView.count > 0 ? 1 : 0 + } + PropertyChanges { + target : selectedItemPlaceHolder diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/004-add-second-breadcrumbitem b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/004-add-second-breadcrumbitem new file mode 100644 index 00000000..5dfb4794 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/004-add-second-breadcrumbitem @@ -0,0 +1,26 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +index 78b8bf1..ab27d9d 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +@@ -80,7 +80,7 @@ Item { + + ListView { + id : breadcrumbsView +- interactive : false ++ //interactive : false + height : breadcrumbsView.count > 0 ? itemHeight : 0 + + clip : true; +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp +index ae44e36..0f363a9 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp +@@ -133,7 +133,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + QDeclarativeContext *context = m_declarativeView->engine()->rootContext(); + + m_bnf = new KBreadcrumbNavigationFactory(this); +- m_bnf->setBreadcrumbDepth(1); ++ m_bnf->setBreadcrumbDepth(2); + m_bnf->createBreadcrumbContext( m_treeModel, this ); + + widget->treeView()->setSelectionModel( m_bnf->selectionModel() ); diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/005-try-to-make-bottom-item-always-visible b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/005-try-to-make-bottom-item-always-visible new file mode 100644 index 00000000..83231332 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/005-try-to-make-bottom-item-always-visible @@ -0,0 +1,42 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +index 1978b72..f4f23ae 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +@@ -57,7 +57,7 @@ Item { + + ListView { + id : topButton +- interactive : false ++// interactive : false + height : itemHeight + + anchors.top : parent.top +@@ -92,6 +92,15 @@ Item { + highlightRangeMode : ListView.StrictlyEnforceRange + preferredHighlightBegin : 0 + preferredHighlightEnd : height ++ onCountChanged : { ++ console.log("count ###" + count); ++ console.log("BEFORE" + indexAt(0, 0) + " CurrentIndex:" + currentIndex); ++ positionViewAtIndex(count - 1, ListView.Beginning) ++ console.log("AFTER" + indexAt(0, 0) + " CurrentIndex:" + currentIndex); ++ // Select down to GMail > Inbox > Subfolder 4, flick inbox into view (it's below Gmail). Select inbox. Get: ++ // ASSERT: "!isEmpty()" in file ../../include/QtCore/../../../../src/qt/src/corelib/tools/qlist.h, line 269 ++ // Also note that the view does not reposition ++ } + } + + Item { +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp +index ae44e36..0f363a9 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp +@@ -133,7 +133,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + QDeclarativeContext *context = m_declarativeView->engine()->rootContext(); + + m_bnf = new KBreadcrumbNavigationFactory(this); +- m_bnf->setBreadcrumbDepth(1); ++ m_bnf->setBreadcrumbDepth(2); + m_bnf->createBreadcrumbContext( m_treeModel, this ); + + widget->treeView()->setSelectionModel( m_bnf->selectionModel() ); diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/006-using-apply-range-helps-somewhat b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/006-using-apply-range-helps-somewhat new file mode 100644 index 00000000..171b87e0 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/006-using-apply-range-helps-somewhat @@ -0,0 +1,48 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +index 1978b72..14a8042 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml +@@ -57,7 +57,7 @@ Item { + + ListView { + id : topButton +- interactive : false ++// interactive : false + height : itemHeight + + anchors.top : parent.top +@@ -89,9 +89,20 @@ Item { + anchors.left : parent.left + anchors.right : parent.right + highlightFollowsCurrentItem : true +- highlightRangeMode : ListView.StrictlyEnforceRange ++ // If ApplyRange is used, it works when navigating down the tree, but when navigating up the ++ // breadcrumbs, the first item in the list is shown instead of the second. ++ highlightRangeMode : ListView.ApplyRange + preferredHighlightBegin : 0 + preferredHighlightEnd : height ++ onCountChanged : { ++ console.log("count ###" + count); ++ console.log("BEFORE" + indexAt(0, 0) + " CurrentIndex:" + currentIndex); ++ positionViewAtIndex(count - 1, ListView.Beginning) ++ console.log("AFTER" + indexAt(0, 0) + " CurrentIndex:" + currentIndex); ++ // Select down to GMail > Inbox > Subfolder 4, flick inbox into view (it's below Gmail). Select inbox. Get: ++ // ASSERT: "!isEmpty()" in file ../../include/QtCore/../../../../src/qt/src/corelib/tools/qlist.h, line 269 ++ // Also note that the view does not reposition ++ } + } + + Item { +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp +index ae44e36..0f363a9 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp +@@ -133,7 +133,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + QDeclarativeContext *context = m_declarativeView->engine()->rootContext(); + + m_bnf = new KBreadcrumbNavigationFactory(this); +- m_bnf->setBreadcrumbDepth(1); ++ m_bnf->setBreadcrumbDepth(2); + m_bnf->createBreadcrumbContext( m_treeModel, this ); + + widget->treeView()->setSelectionModel( m_bnf->selectionModel() ); diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml new file mode 100644 index 00000000..630ac97e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/BreadcrumbNavigationView.qml @@ -0,0 +1,553 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Item { + id: breadcrumbTopLevel + clip : true + + property variant breadcrumbComponentFactory + + property alias topDelegate : topButton.delegate + property alias breadcrumbDelegate : breadcrumbsView.delegate + property alias selectedItemDelegate : selectedItemView.delegate + property alias childItemsDelegate : childItemsView.delegate + property alias multipleSelectionText : multipleSelectionMessage.text + + property int itemHeight : height / 7 + property int _transitionSelect : -1 + + property bool hasChildren : childItemsView.count > 0 + property bool hasSelection : selectedItemView.count > 0 + property bool hasBreadcrumbs : breadcrumbsView.count > 0 + + property alias numBreadcrumbs : breadcrumbsView.count + property alias numSelected : selectedItemView.count + + property variant breadcrumbSelectionModel : breadcrumbComponentFactory.qmlBreadcrumbSelectionModel(); + property variant selectedItemSelectionModel : breadcrumbComponentFactory.qmlSelectionModel(); + property variant childSelectionModel : breadcrumbComponentFactory.qmlChildSelectionModel(); + + signal childCollectionSelected(int row) + signal breadcrumbCollectionSelected(int row) + + SystemPalette { id: palette; colorGroup: "Active" } + ListModel { + id : topModel + ListElement { display : "Home" } + } + + ListView { + id : topButton + interactive : false + height : itemHeight + + anchors.top : parent.top + + anchors.left : parent.left + anchors.right : parent.right + model : topModel + + Image { + id : topRightDivider + source : "dividing-line.png" + anchors.top : parent.top + anchors.right : parent.right + anchors.bottom : parent.bottom + anchors.bottomMargin : breadcrumbTopLevel.hasBreadcrumbs ? 0 : 8 + fillMode : Image.TileVertically + opacity : breadcrumbTopLevel.hasSelection ? 1 : 0 + } + } + + ListView { + id : breadcrumbsView + model : breadcrumbComponentFactory.qmlBreadcrumbsModel(); + interactive : false + height : breadcrumbsView.count > 0 ? itemHeight : 0 + + clip : true; + property int selectedIndex : -1 + anchors.top : topButton.bottom + anchors.left : parent.left + anchors.right : parent.right + highlightFollowsCurrentItem : true + highlightRangeMode : ListView.StrictlyEnforceRange + preferredHighlightBegin : 0 + preferredHighlightEnd : height + onCountChanged : { + positionViewAtIndex(count - 1, ListView.Beginning) + } + Component.onCompleted : { + positionViewAtIndex(count - 1, ListView.Beginning) + } + } + + Item { + id : breadcrumbPlaceHolder + height : breadcrumbTopLevel.hasBreadcrumbs ? itemHeight : 0 + anchors.top : topButton.bottom + anchors.left : parent.left + anchors.right : parent.right + } + Image { + id : breadcrumbRightDivider + source : "dividing-line.png" + anchors.top : breadcrumbPlaceHolder.top + anchors.right : breadcrumbPlaceHolder.right + height : breadcrumbTopLevel.hasBreadcrumbs ? (itemHeight -8) : 0 + fillMode : Image.TileVertically + opacity : breadcrumbTopLevel.hasBreadcrumbs ? 1 : 0 + } + + ListView { + id : selectedItemView + interactive : false + + model : breadcrumbComponentFactory.qmlSelectedItemModel(); + height : itemHeight * selectedItemView.count + anchors.top : breadcrumbsView.bottom + anchors.left : parent.left + anchors.right : parent.right + + onCountChanged : { + if (selectedItemView.count > 1) + { + breadcrumbsView.visible = false; + breadcrumbPlaceHolder.visible = false; + selectedItemView.visible = false; + selectedItemPlaceHolder.visible = false; + childItemsView.visible = false; + favinfoOverlay.visible = true; + } + else + { + breadcrumbsView.visible = true; + breadcrumbPlaceHolder.visible = true; + selectedItemView.visible = true; + selectedItemPlaceHolder.visible = true; + childItemsView.visible = true; + favinfoOverlay.visible = false; + } + } + } + + Item { + id : selectedItemPlaceHolder + height : selectedItemView.count > 0 ? itemHeight : 0 + anchors.top : breadcrumbPlaceHolder.bottom + anchors.left : parent.left + anchors.right : parent.right + Item { + id : selectedPlaceHolderImages + anchors.fill : parent + opacity : (selectedItemView.count > 0) ? 1 : 0 + Image { + source : "dividing-line-horizontal.png" + fillMode : Image.TileHorizontally + anchors.top : parent.top + anchors.topMargin : -3 + anchors.right : topLine.left + anchors.left : parent.left + } + Image { + id : topLine + source : "list-line-top.png" + anchors.right : parent.right + anchors.top : parent.top + anchors.topMargin : -8 + } + } + } + + ListView { + id : childItemsView + model : breadcrumbComponentFactory.qmlChildItemsModel(); + property bool shouldBeFlickable + + clip : true + anchors.top : selectedItemPlaceHolder.bottom + anchors.bottom : breadcrumbTopLevel.bottom + anchors.left : parent.left + anchors.right : parent.right + + shouldBeFlickable : childItemsView.height < (itemHeight * childItemsView.count) + interactive : shouldBeFlickable + } + + Item { + id : childItemsViewPlaceHolder + anchors.top : selectedItemPlaceHolder.bottom + anchors.bottom : breadcrumbTopLevel.bottom + anchors.left : parent.left + anchors.right : parent.right + + + Image { + source : "dividing-line-horizontal.png" + fillMode : Image.TileHorizontally + anchors.right : parent.right + anchors.left : parent.left + anchors.top : parent.top + } + + Image { + source : "dividing-line.png" + anchors.top : parent.top + anchors.right : parent.right + anchors.bottom : parent.bottom + fillMode : Image.TileVertically + } + Image { + source : "scrollable-top.png" + anchors.top : parent.top + anchors.right : parent.right + anchors.left : parent.left + fillMode : Image.TileHorizontally + opacity : childItemsView.shouldBeFlickable ? 1 : 0 + } + Image { + source : "scrollable-bottom.png" + anchors.bottom : parent.bottom + anchors.right : parent.right + anchors.left : parent.left + fillMode : Image.TileHorizontally + opacity : childItemsView.shouldBeFlickable ? 1 : 0 + } + } + + Item { + id : favinfoOverlay + anchors.top : topButton.bottom + anchors.bottom : parent.bottom + anchors.left : parent.left + anchors.right : parent.right + visible : false + + Text { + id : multipleSelectionMessage + font.italic : true + horizontalAlignment : Text.AlignHCenter + anchors.horizontalCenter : parent.horizontalCenter + + height : 30 + x : 20 + y : 50 + } + + Image { + source : "dividing-line-horizontal.png" + fillMode : Image.TileHorizontally + anchors.top : parent.top + anchors.right : parent.right + anchors.left : parent.left + } + + Image { + source : "dividing-line.png" + fillMode : Image.TileVertically + anchors.top : parent.top + anchors.right : parent.right + anchors.bottom : parent.bottom + } + + } + + function completeHomeSelection() { + selectedItemSelectionModel.clearSelection(); + // TODO: Remove: + breadcrumbCollectionSelected(breadcrumbTopLevel._transitionSelect); + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "after_select_breadcrumb"; + breadcrumbTopLevel.state = ""; + } + + function completeChildSelection() { + childSelectionModel.select(breadcrumbTopLevel._transitionSelect, 3) + // TODO: Remove: + childCollectionSelected(breadcrumbTopLevel._transitionSelect); + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "after_select_child"; + breadcrumbTopLevel.state = ""; + } + + function completeBreadcrumbSelection() { + breadcrumbSelectionModel.select(breadcrumbTopLevel._transitionSelect, 3) + // TODO: Remove: + breadcrumbCollectionSelected(breadcrumbTopLevel._transitionSelect); + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "after_select_breadcrumb"; + breadcrumbTopLevel.state = ""; + } + + states : [ + State { + name : "before_select_home" + PropertyChanges { + target : breadcrumbsView + opacity : 0 + height : 0 + } + PropertyChanges { + target : breadcrumbPlaceHolder + opacity : 0 + height : 0 + } + PropertyChanges { + target : selectedItemView + opacity : 0 + height : 0 + } + PropertyChanges { + target : selectedItemPlaceHolder + opacity : 0 + height : 0 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "before_select_child" + PropertyChanges { + target : topRightDivider + anchors.bottomMargin : 0 + opacity : 1 + } + PropertyChanges { + target : breadcrumbsView + height : itemHeight + anchors.topMargin : -itemHeight + opacity : 0 + } + PropertyChanges { + target : breadcrumbRightDivider + anchors.topMargin : -8 + height : {console.log(itemHeight); 67} + opacity : 0 // { 1 } // selectedItemView.count > 0 ? 1 : 0 + } + PropertyChanges { + target : selectedItemPlaceHolder + anchors.topMargin : (breadcrumbsView.count == 0 && selectedItemView.count > 0) ? (itemHeight) : (breadcrumbsView.count == 0) ? 8 : 0 + height : itemHeight + opacity : 1 + } + + PropertyChanges { + target : selectedPlaceHolderImages + opacity : 1 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "after_select_child" + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "before_select_breadcrumb" + PropertyChanges { + target : breadcrumbsView + height : { if (breadcrumbTopLevel._transitionSelect >= 0) itemHeight * ( breadcrumbTopLevel._transitionSelect + 1 ) } + anchors.bottomMargin : -itemHeight + opacity : 0.5 + } + PropertyChanges { + target : selectedItemView + anchors.topMargin : (application.selectedCollectionRow() + breadcrumbsView.count) * itemHeight; + opacity : 0 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + }, + State { + name : "after_select_breadcrumb" + PropertyChanges { + target : breadcrumbsView + contentY : breadcrumbsView.count > 1 ? itemHeight : 0 + } + PropertyChanges { + target : childItemsView + opacity : 0 + } + } + ] + + transitions : [ + Transition { + from : "*" + to : "before_select_home" + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + target : breadcrumbPlaceHolder + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : breadcrumbsView + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : selectedItemView + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : selectedItemPlaceHolder + duration: 500 + easing.type: "OutQuad" + properties : "opacity,height" + } + PropertyAnimation { + target : childItemsView + duration: 500 + easing.type: "OutQuad" + properties : "height" + } + } + ScriptAction { + script: { completeHomeSelection(); } + } + } + }, + Transition { + from : "*" + to : "before_select_child" + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: topRightDivider + properties: "opacity,anchors.bottomMargin" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: breadcrumbsView + properties: "height,anchors.topMargin,opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: breadcrumbRightDivider + properties: "height" + } + PropertyAnimation { + target : selectedItemPlaceHolder + duration: 500 + easing.type: "OutQuad" + properties : "anchors.topMargin,height,opacity" + } + PropertyAnimation { + target : selectedPlaceHolderImages + duration: 500 + easing.type: "OutQuad" + properties : "opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsView + properties: "opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsDelegate + properties: "itemBackground" + } + } + ScriptAction { + script: { completeChildSelection(); } + } + } + }, + Transition { + from : "after_select_child" + to : "" + NumberAnimation { + target: childItemsView + properties: "opacity" + } + NumberAnimation { + target: selectedItemView + properties: "opacity" + } + ScriptAction { + script : {console.log("### " + selectedItemView.count + " " + selectedItemView.currentIndex); } + } + }, + Transition { + from : "*" + to : "before_select_breadcrumb" + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: breadcrumbsView + properties: "height,opacity" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: selectedItemView + properties: "opacity,anchors.topMargin" + } + PropertyAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsView + properties: "opacity" + } + } + ScriptAction { + script: { completeBreadcrumbSelection(); } + } + } + }, + Transition { + from : "after_select_breadcrumb" + to : "" + NumberAnimation { + duration: 500 + easing.type: "OutQuad" + target: childItemsView + properties: "opacity" + } + } + ] +} diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/CMakeLists.txt b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/CMakeLists.txt new file mode 100644 index 00000000..9daceb1f --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/CMakeLists.txt @@ -0,0 +1,61 @@ +project(qmlbreadcrumbnavigation) + +cmake_minimum_required(VERSION 2.6) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) + +# Not depending on KDE here for easier portability. +find_package(Qt4 REQUIRED) + +include_directories( + ${QT_INCLUDES} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTSQL_INCLUDE_DIR} + ${QT_QTDECLARATIVE_INCLUDE_DIR} + ${PROJECT_BINARY_DIR} +) + +set(app_SRCS + breadcrumbnavigation.cpp + breadcrumbnavigationcontext.cpp + checkableitemproxymodel.cpp + dynamictreemodel.cpp + dynamictreewidget.cpp + qmllistselectionmodel.cpp + kresettingproxymodel.cpp + mainwindow.cpp + main.cpp + kselectionproxymodel.cpp + kbreadcrumbselectionmodel.cpp + kmodelindexproxymapper.cpp + kproxyitemselectionmodel.cpp +) + +set(app_qml_SRCS + mainview.qml + BreadcrumbNavigationView.qml + ListDelegate.qml +) + +FILE(GLOB _images "*png") +foreach( _image ${_images}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${_image}" "${CMAKE_BINARY_DIR}") +endforeach() + + +foreach( qml_src ${app_qml_SRCS}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/${qml_src}" "${CMAKE_BINARY_DIR}/${qml_src}") +endforeach() + +qt4_automoc( app_MOC_SRCS ${app_SRCS} ) + +add_executable(qml_nav + ${app_SRCS} +) + +target_link_libraries( + qml_nav + ${QT_QTCORE_LIBRARIES} + ${QT_QTGUI_LIBRARIES} + ${QT_QTDECLARATIVE_LIBRARIES} +) diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/ListDelegate.qml b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/ListDelegate.qml new file mode 100644 index 00000000..e36b3b6d --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/ListDelegate.qml @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 Klarälvdalens Datakonsult AB, + * a KDAB Group company, info@kdab.net, + * author 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. + */ + +import Qt 4.7 + +Rectangle { + property alias text : _text.text; + property bool clickable : false + property bool isChild : false + property bool isSelected : false + property bool topItem : false + + property variant checkModel + + signal indexSelected(int row) + + color : "#00000000" + height: itemHeight; + width : ListView.view.width + + Item { + // This is the same as anchors.fill : parent, but ParentAnimation only works + // if positional layouting is used instead of anchor layouting. + x : 0 + y : 0 + width : parent.width + height : parent.height + id : nestedItem + + Text { + id : _text + anchors.verticalCenter : parent.verticalCenter; + anchors.horizontalCenter : parent.horizontalCenter; + text : model.display + } + + Image { + width : height + anchors.right : parent.right + anchors.rightMargin : 5 + anchors.verticalCenter : parent.verticalCenter + opacity : ( isChild && application.childCollectionHasChildren( model.index ) ) ? 1 : 0 + source: "transparentplus.png" + } + + MouseArea { + anchors.fill : parent + onClicked: { + if ( clickable ) + { + if (topItem) + { + indexSelected(model.index); + return; + } + + if (isChild) + { + nestedItem.state = "before_select_child"; + } else if (!isSelected) + nestedItem.state = "before_select_breadcrumb"; + indexSelected(model.index); + } + } + Rectangle { + width : height + height : 10 + anchors.left : parent.left + anchors.leftMargin : 5 + anchors.verticalCenter : parent.verticalCenter + color: model.checkOn ? "blue" : "white" + MouseArea { + anchors.fill : parent + onClicked : { + // 8 is QItemSelectionModel::Toggle + checkModel.select(model.index, 8); + } + } + } + } + + states : [ + State { + name : "before_select_child" + ParentChange { target : nestedItem; parent : selectedItemPlaceHolder; } + PropertyChanges { target : nestedItem; x : 0; y : 0 } + }, + State { + name : "before_select_breadcrumb" + ParentChange { target : nestedItem; parent : selectedItemPlaceHolder; } + PropertyChanges { target : nestedItem; x : 0; y : 0 } + } + ] + transitions : [ + Transition { + ParentAnimation { + target : nestedItem + NumberAnimation { + properties: "x,y"; + duration : 500 + easing.type: Easing.OutQuad; + } + } + } + ] + } +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/backgroundtile.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/backgroundtile.png new file mode 100644 index 0000000000000000000000000000000000000000..93382ec0bb105431aa17feb30b92d45a5be114a8 GIT binary patch literal 2139 zcmV-h2&DIkP)eSaefwW^{L9a%BK_cXuvnZfkR6VQ^(G zZ*pgw?mQX*00-MiL_t(o!|j&Qj^sLWL{9`$s+t+E|Nk2ohK+^qo$ivz3_nO+GrL$A z2K?X`1NA_#DUum^BG~%3ilTJlI@c#B_FkvTpW4or*(N+vqPFw(e2^nlwrzI&WYu)+ z**eNUdYh?J5B%@jdTjICZ28COKWA3A9DhDPhEna*Ilqnd_#3rpr#|U_O--N2ZgKw9 zUx%C|#v47e792&ghDf)Mtj=J|qXyra!wdQBd3J?b9#ZFXtV4DtU2CjZqBfV0_8Lc@ zv(N9t>Td`B^AA=n^`pn5i6b{_JU-foGE}_2ZTgSd%lg=ffBMv>zj&9ov+}8#X)~X#~g*V#(Y4m%;(<{G~I03huq7QUO$e{rL8!`&mS{Ow!-rdSi_9o z`g!slx1MLe_GWpmwIAb?v%SyGvv(LiyZd7-M`nxmFum*yFYgvMS*7WYNuMNUFKtWd zRCk>ryYi=P3lBtMwx|C{k8ya*$M|tJJn9(xjFe?Bj#_N}H&*ABC0C9|_A%Y~13l(B)~vEt%b%Nm)^H#B zzQ?1SM<fU^igw~v6AsHTl3QJj7_A#JWA3Moh|MbM-~ff zoc(AsaIYy?=4-{> zSY3PYva@0vm%6u+wd`|hq&4`cF-}WfwbybHni-GPrpgTg2MJ+-vflvuBl z{Yb+2?m#=p>pk{eH+EZ8~=RW)$X7`IV{i&tmn@XfmVgmeQ6rv?XPZ z=@rk_W#QAS;!2tbbb6bbjp(yZvt=sYJtti{Um3OQRP;Q@s+kCXdbyB$tPNsV`%gga zWb_c~Wvuy_XRC@Oj^|3tQrJJBZJg2j1Al_NwWFGp?0vDX=H$(*bN^5eYM3 zpjNC5waQTH$Se{=7R0GyG&Jt66|yNW$r3gIrgnWK_N>us8;3>J^q3J@t}sg-6;)B% z-NG}QT5WK#GM1Z|MOXudPYC--UM`!oI_(KgZ$rC_gp-yl%aoTBAEA1=*Ra(So${-! zUfBn7DO+HXy&x40uXTl6)404M9A|p1grq4mIwxYK6huUK0gaM@L29C4d8JwycaLO4 zkwNZ*Bo4--Lo7{_1gtA%N5?%0&Kj}ZTERWs0V5PEZXjv5LWk=OXpae-ij1bN>j=pa zagrt^qZFLDSq>s20?!aP9ZJ!{GI3RxjG>U?WU)ETJX>Zu3m2!5B|6kHMs3lK3W+tM zq}s#{!fF6r|H4Hx3$UP#BitdO2eJ`0NLGba4T((L#)>KmONrWQ!wPLLhZqW10?G=N zNFaxblptO9h?I`1k%(YfN`S}}2|$eU?CFB5-fn2{L0E9Sj-An}%_Yv1DiUHCh7y^pI<#Dds z?rnuZL1}P^0Fi=wbgzX-rE4vra&N_Z-~VQ)U&Fw)x9@7;eFVfr`SsaV8hGF$HTG6oqHcA9>N+bxaXp}r zdXxIkJ1@T(h;K>ZT?LY_A|UZC0>YP9F3Txz`OXjc7k{j@^>wc|O zpzcn*i@%k83tac9zd-zg;Ku8sP4#7dRJS+tt0071@0&UAUgXB?!YCI$)}45!zFN_g zZvCY9Y`)s9U+um_L0>56t5^4R@6w@5sD;gYVbC*S=THcbS*xZ_JcbuC;d( zeDQmG@GA3)kYMRH(G@Fqa&gC}Zc1`fqA#lR7Q)xlg~)|+r2EzTGWy-j8@adr?-xjZ zgL_E)P!2%>h0{>|MDW+m4&kzod&^K-%LCyk?$YP-C z>mbbNq%pe!C@5Lt8c`CQpH@mmtT}V`<;yxP*Ihqi(?4K z_2ggYAH0yM)Or~FLDT*De;yylYh}CV_|C1I>%&`a8EG83WtCB(k%1wHfrQb_Ek-9J z4GbF;lm(=t3;iikwp?Cta{ScqAzI_xZ+y6Am^W{3h>h qDttJQ&HUlzuKBKh#ybpUSQ)ZUNlU1`yYU|A3I + + 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 "breadcrumbnavigation.h" + +#include + +KBreadcrumbNavigationProxyModel::KBreadcrumbNavigationProxyModel(QItemSelectionModel* selectionModel, QObject* parent) + : KSelectionProxyModel(selectionModel, parent) +{ + +} + +QVariant KBreadcrumbNavigationProxyModel::data(const QModelIndex& index, int role) const +{ + if (rowCount() > 2 && index.row() == 0 && role == Qt::DisplayRole) + { + QModelIndex sourceIndex = mapToSource(index); + QStringList dataList; + while (sourceIndex.isValid()) + { + dataList.prepend(sourceIndex.data().toString()); + sourceIndex = sourceIndex.parent(); + } + return dataList.join(" > "); + } + return KSelectionProxyModel::data(index, role); +} + +void KBreadcrumbNavigationProxyModel::setShowHiddenAscendantData(bool showHiddenAscendantData) +{ + m_showHiddenAscendantData = showHiddenAscendantData; +} + +bool KBreadcrumbNavigationProxyModel::showHiddenAscendantData() const +{ + return m_showHiddenAscendantData; +} + +KNavigatingProxyModel::KNavigatingProxyModel(KForwardingItemSelectionModel* selectionModel, QObject* parent) + : KSelectionProxyModel(selectionModel, parent), m_selectionModel(selectionModel) +{ +} + +void KNavigatingProxyModel::silentSelect(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command) +{ + disconnect( m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(navigationSelectionChanged(QItemSelection,QItemSelection)) ); + m_selectionModel->select( selection, command); + connect( m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(navigationSelectionChanged(QItemSelection,QItemSelection)) ); +} + +void KNavigatingProxyModel::setSourceModel(QAbstractItemModel* sourceModel) +{ + connect( m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(navigationSelectionChanged(QItemSelection,QItemSelection)) ); + connect( m_selectionModel, SIGNAL(resetNavigation()), SLOT(updateNavigation()) ); + + disconnect(sourceModel, SIGNAL(modelReset()), this, SLOT(updateNavigation())); + disconnect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(_sourceRowsInserted(QModelIndex,int,int))); + disconnect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(_sourceRowsRemoved(QModelIndex,int,int))); + + KSelectionProxyModel::setSourceModel(sourceModel); + updateNavigation(); + + connect(sourceModel, SIGNAL(modelReset()), SLOT(updateNavigation())); + connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(_sourceRowsInserted(QModelIndex,int,int))); + connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(_sourceRowsRemoved(QModelIndex,int,int))); +} + +void KNavigatingProxyModel::_sourceRowsInserted(const QModelIndex& parent, int start, int end) +{ + if (filterBehavior() != ExactSelection || parent.isValid()) + return; + QItemSelection sel( sourceModel()->index(start, 0, parent), + sourceModel()->index(end, sourceModel()->columnCount(parent) - 1, parent)); + + silentSelect(sel, QItemSelectionModel::Select); +} + +void KNavigatingProxyModel::_sourceRowsRemoved(const QModelIndex& parent, int start, int end) +{ + if (filterBehavior() != ExactSelection || parent.isValid()) + return; + + m_selectionModel->select(QItemSelection( sourceModel()->index(start, 0, parent), + sourceModel()->index(end, sourceModel()->columnCount(parent), parent)), QItemSelectionModel::Deselect); + + if ( m_selectionModel->selection().isEmpty() ) + updateNavigation(); +} + +void KNavigatingProxyModel::navigationSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + updateNavigation(); +} + +void KNavigatingProxyModel::updateNavigation() +{ + beginResetModel(); + if (!sourceModel()) + { + setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); + endResetModel(); + return; + } + + if (m_selectionModel->selection().isEmpty()) + { + setFilterBehavior(KSelectionProxyModel::ExactSelection); + QModelIndex top = sourceModel()->index(0, 0); + QModelIndex bottom = sourceModel()->index(sourceModel()->rowCount() - 1, 0); + silentSelect(QItemSelection(top, bottom), QItemSelectionModel::Select); + } else if (filterBehavior() != KSelectionProxyModel::ChildrenOfExactSelection) { + setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); + } + endResetModel(); +} + +KForwardingItemSelectionModel::KForwardingItemSelectionModel(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QObject *parent) + : QItemSelectionModel(model, parent), m_selectionModel(selectionModel), m_direction(Forward) +{ + Q_ASSERT(model == selectionModel->model()); + connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(navigationSelectionChanged(QItemSelection,QItemSelection))); +} + +KForwardingItemSelectionModel::KForwardingItemSelectionModel(QAbstractItemModel* model, QItemSelectionModel* selectionModel, Direction direction, QObject *parent) + : QItemSelectionModel(model, parent), m_selectionModel(selectionModel), m_direction(direction) +{ + Q_ASSERT(model == selectionModel->model()); + if (m_direction == Forward) + connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(navigationSelectionChanged(QItemSelection,QItemSelection))); +} + +void KForwardingItemSelectionModel::select(const QModelIndex& index, QItemSelectionModel::SelectionFlags command) +{ + if (m_direction == Reverse) + m_selectionModel->select(index, command); + else + QItemSelectionModel::select(index, command); +} + +void KForwardingItemSelectionModel::select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command) +{ + if (m_direction == Reverse) + m_selectionModel->select(selection, command); + else + QItemSelectionModel::select(selection, command); +} + +void KForwardingItemSelectionModel::navigationSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + // ### The KNavigatingProxyModel selects all top level items if there is 'no selection'. + // This slot is called when we newly get a selection, so we can unselect all. + if (selectedRows().size() == model()->rowCount()) + select(selected, ClearAndSelect); + else + select(selected, Select); + + select(deselected, Deselect); + if (selected == selection()) + resetNavigation(); +} + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigation.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigation.h new file mode 100644 index 00000000..f50d6011 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigation.h @@ -0,0 +1,100 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef BREADCRUMBNAVIGATION_H +#define BREADCRUMBNAVIGATION_H + +#include + +#include "kselectionproxymodel.h" + +// Copied from kdeui/tests/proxymodeltestapp/breadcrumbnavigationwidget +// A version of these might be somewhere stable in the future. + +class KBreadcrumbNavigationProxyModel : public KSelectionProxyModel +{ + Q_OBJECT +public: + KBreadcrumbNavigationProxyModel(QItemSelectionModel* selectionModel, QObject* parent = 0); + + void setShowHiddenAscendantData(bool showHiddenAscendantData); + bool showHiddenAscendantData() const; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + +private: + bool m_showHiddenAscendantData; + +}; + +class KForwardingItemSelectionModel; + +class KNavigatingProxyModel : public KSelectionProxyModel +{ + Q_OBJECT +public: + KNavigatingProxyModel(KForwardingItemSelectionModel* selectionModel, QObject* parent = 0); + + virtual void setSourceModel(QAbstractItemModel* sourceModel); + +private slots: + void updateNavigation(); + void navigationSelectionChanged( const QItemSelection &, const QItemSelection & ); + void _sourceRowsInserted( const QModelIndex &parent, int start, int end ); + void _sourceRowsRemoved( const QModelIndex &parent, int start, int end ); + +private: + void silentSelect(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command); + +private: + using KSelectionProxyModel::setFilterBehavior; + + KForwardingItemSelectionModel *m_selectionModel; + +}; + +class KForwardingItemSelectionModel : public QItemSelectionModel +{ + Q_OBJECT +public: + enum Direction + { + Forward, + Reverse + }; + KForwardingItemSelectionModel(QAbstractItemModel* model, QItemSelectionModel *selectionModel, QObject *parent = 0); + KForwardingItemSelectionModel(QAbstractItemModel* model, QItemSelectionModel *selectionModel, Direction direction, QObject *parent = 0); + + virtual void select(const QModelIndex& index, SelectionFlags command); + virtual void select(const QItemSelection& selection, SelectionFlags command); + +signals: + void resetNavigation(); + +private slots: + void navigationSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + +private: + QItemSelectionModel *m_selectionModel; + Direction m_direction; +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.cpp new file mode 100644 index 00000000..a2b1ab25 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.cpp @@ -0,0 +1,397 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "breadcrumbnavigationcontext.h" + +#include +#include + +#include "kselectionproxymodel.h" +#include "kbreadcrumbselectionmodel.h" +#include "kproxyitemselectionmodel.h" +#include "kmodelindexproxymapper.h" + +#include "breadcrumbnavigation.h" +#include "qmllistselectionmodel.h" +#include "checkableitemproxymodel.h" + +class QMLCheckableItemProxyModel : public CheckableItemProxyModel +{ +public: + enum MoreRoles { + CheckOn = Qt::UserRole + 3000 + }; + QMLCheckableItemProxyModel (QObject* parent = 0) + : CheckableItemProxyModel(parent) + { + } + + virtual void setSourceModel(QAbstractItemModel* sourceModel) + { + CheckableItemProxyModel::setSourceModel(sourceModel); + + QHash roles = roleNames(); + roles.insert( CheckOn, "checkOn" ); + setRoleNames(roles); + } + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const + { + if ( role == CheckOn ) + return (index.data(Qt::CheckStateRole) == Qt::Checked); + return CheckableItemProxyModel::data(index, role); + } + +}; + +class KBreadcrumbNavigationFactoryPrivate +{ + KBreadcrumbNavigationFactoryPrivate(KBreadcrumbNavigationFactory *qq) + : q_ptr(qq), + m_breadcrumbSelectionModel(0), + m_selectionModel(0), + m_childItemsSelectionModel(0), + m_breadcrumbModel(0), + m_selectedItemModel(0), + m_unfilteredChildItemsModel(0), + m_childItemsModel(0), + m_breadcrumbDepth(-1), + m_modelIndexProxyMapper(0), + m_checkModel(0), + m_qmlBreadcrumbSelectionModel(0), + m_qmlSelectedItemSelectionModel(0), + m_qmlChildSelectionModel(0), + m_qmlBreadcrumbCheckModel(0), + m_qmlSelectedItemCheckModel(0), + m_qmlChildCheckModel(0), + m_checkedItemsModel(0), + m_checkedItemsCheckModel(0), + m_qmlCheckedItemsCheckModel(0) + + { + + } + Q_DECLARE_PUBLIC(KBreadcrumbNavigationFactory) + KBreadcrumbNavigationFactory * const q_ptr; + + QItemSelectionModel *m_breadcrumbSelectionModel; + QItemSelectionModel *m_selectionModel; + QItemSelectionModel *m_childItemsSelectionModel; + + QMLListSelectionModel *m_qmlBreadcrumbSelectionModel; + QMLListSelectionModel *m_qmlSelectedItemSelectionModel; + QMLListSelectionModel *m_qmlChildSelectionModel; + + QAbstractItemModel *m_breadcrumbModel; + QAbstractItemModel *m_selectedItemModel; + QAbstractItemModel *m_unfilteredChildItemsModel; + QAbstractItemModel *m_childItemsModel; + int m_breadcrumbDepth; + KModelIndexProxyMapper *m_modelIndexProxyMapper; + + QItemSelectionModel *m_checkModel; + + QMLListSelectionModel *m_qmlBreadcrumbCheckModel; + QMLListSelectionModel *m_qmlSelectedItemCheckModel; + QMLListSelectionModel *m_qmlChildCheckModel; + + KSelectionProxyModel *m_checkedItemsModel; + QItemSelectionModel *m_checkedItemsCheckModel; + QMLListSelectionModel *m_qmlCheckedItemsCheckModel; + +}; + +KBreadcrumbNavigationFactory::KBreadcrumbNavigationFactory(QObject* parent) + : QObject(parent), d_ptr(new KBreadcrumbNavigationFactoryPrivate(this)) +{ + +} + +void KBreadcrumbNavigationFactory::createCheckableBreadcrumbContext(QAbstractItemModel* model, QObject* parent) +{ + Q_D(KBreadcrumbNavigationFactory); + + d->m_checkModel = new QItemSelectionModel( model, parent); + + QMLCheckableItemProxyModel *checkableProxy = new QMLCheckableItemProxyModel(this); + checkableProxy->setSourceModel( model ); + checkableProxy->setSelectionModel( d->m_checkModel ); + + createBreadcrumbContext(checkableProxy, parent); + + KLinkItemSelectionModel *breadcrumbLinkSelectionModel = new KLinkItemSelectionModel( d->m_breadcrumbModel, d->m_checkModel, parent); + KLinkItemSelectionModel *childLinkSelectionModel = new KLinkItemSelectionModel(d->m_childItemsModel, d->m_checkModel, parent); + KLinkItemSelectionModel *selectedItemLinkSelectionModel = new KLinkItemSelectionModel( d->m_selectedItemModel, d->m_checkModel, parent); + + d->m_qmlBreadcrumbCheckModel = new QMLListSelectionModel( breadcrumbLinkSelectionModel, parent); + d->m_qmlSelectedItemCheckModel = new QMLListSelectionModel( selectedItemLinkSelectionModel, parent); + d->m_qmlChildCheckModel = new QMLListSelectionModel( childLinkSelectionModel, parent); + + d->m_checkedItemsModel = new KSelectionProxyModel( d->m_checkModel, parent ); + d->m_checkedItemsModel->setFilterBehavior( KSelectionProxyModel::ExactSelection ); + d->m_checkedItemsModel->setSourceModel( checkableProxy ); + + d->m_checkedItemsCheckModel = new KLinkItemSelectionModel( d->m_checkedItemsModel, d->m_checkModel, parent); + + d->m_qmlCheckedItemsCheckModel = new QMLListSelectionModel( d->m_checkedItemsCheckModel, parent); + +} + +void KBreadcrumbNavigationFactory::createBreadcrumbContext(QAbstractItemModel *model, QObject* parent) +{ + Q_D(KBreadcrumbNavigationFactory); + + d->m_selectionModel = new QItemSelectionModel( model, parent ); + + KSelectionProxyModel *currentCollectionSelectionModel = new KSelectionProxyModel( d->m_selectionModel, parent ); + currentCollectionSelectionModel->setFilterBehavior( KSelectionProxyModel::ExactSelection ); + currentCollectionSelectionModel->setSourceModel( model ); + d->m_selectedItemModel = currentCollectionSelectionModel; + + KBreadcrumbSelectionModel *breadcrumbCollectionSelection + = new KBreadcrumbSelectionModel( d->m_selectionModel, KBreadcrumbSelectionModel::MakeBreadcrumbSelectionInOther, parent ); + breadcrumbCollectionSelection->setActualSelectionIncluded(false); + breadcrumbCollectionSelection->setBreadcrumbLength( d->m_breadcrumbDepth ); + + KBreadcrumbNavigationProxyModel *breadcrumbNavigationModel + = new KBreadcrumbNavigationProxyModel( breadcrumbCollectionSelection, parent ); + breadcrumbNavigationModel->setSourceModel( model ); + breadcrumbNavigationModel->setFilterBehavior( KSelectionProxyModel::ExactSelection ); + d->m_breadcrumbModel = getBreadcrumbNavigationModel(breadcrumbNavigationModel); + + KLinkItemSelectionModel *proxyBreadcrumbCollectionSelection + = new KLinkItemSelectionModel( d->m_breadcrumbModel, d->m_selectionModel, parent ); + + d->m_breadcrumbSelectionModel = new KForwardingItemSelectionModel( d->m_breadcrumbModel, + proxyBreadcrumbCollectionSelection, + KForwardingItemSelectionModel::Reverse, + parent ); + + // Breadcrumbs done. (phew!) + + KForwardingItemSelectionModel *oneway = new KForwardingItemSelectionModel( model, d->m_selectionModel, parent ); + + KNavigatingProxyModel *navigatingProxyModel = new KNavigatingProxyModel( oneway, parent ); + navigatingProxyModel->setSourceModel( model ); + d->m_unfilteredChildItemsModel = navigatingProxyModel; + + d->m_childItemsModel = getChildItemsModel(d->m_unfilteredChildItemsModel); + + d->m_childItemsSelectionModel = new KLinkItemSelectionModel( d->m_childItemsModel, d->m_selectionModel, parent ); + + d->m_modelIndexProxyMapper = new KModelIndexProxyMapper(model, d->m_childItemsModel, parent); + + // Navigation stuff for QML: + + d->m_qmlBreadcrumbSelectionModel = new QMLListSelectionModel( d->m_breadcrumbSelectionModel, parent ); + d->m_qmlSelectedItemSelectionModel = new QMLListSelectionModel( d->m_selectionModel, parent ); + d->m_qmlChildSelectionModel = new QMLListSelectionModel( d->m_childItemsSelectionModel, parent ); +} + +QItemSelectionModel* KBreadcrumbNavigationFactory::selectionModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_selectionModel; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::selectedItemModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_selectedItemModel; +} + +int KBreadcrumbNavigationFactory::breadcrumbDepth() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_breadcrumbDepth; +} + +void KBreadcrumbNavigationFactory::setBreadcrumbDepth(int depth) +{ + Q_D(KBreadcrumbNavigationFactory); + d->m_breadcrumbDepth = depth; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::breadcrumbItemModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_breadcrumbModel; +} + +QItemSelectionModel* KBreadcrumbNavigationFactory::breadcrumbSelectionModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_breadcrumbSelectionModel; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::childItemModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_childItemsModel; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::unfilteredChildItemModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_unfilteredChildItemsModel; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::getBreadcrumbNavigationModel(QAbstractItemModel* model) +{ + return model; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::getChildItemsModel(QAbstractItemModel* model) +{ + return model; +} + +QItemSelectionModel* KBreadcrumbNavigationFactory::childSelectionModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_childItemsSelectionModel; +} + +void KBreadcrumbNavigationFactory::selectBreadcrumb(int row) +{ + Q_D(KBreadcrumbNavigationFactory); + if ( row < 0 ) + { + d->m_selectionModel->clearSelection(); + return; + } + QModelIndex index = d->m_breadcrumbModel->index( row, 0 ); + d->m_breadcrumbSelectionModel->select( QItemSelection(index, index), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); +} + +void KBreadcrumbNavigationFactory::selectChild(int row) +{ + Q_D(KBreadcrumbNavigationFactory); + if ( row < 0 ) + { + d->m_selectionModel->clearSelection(); + return; + } + const QModelIndex index = d->m_childItemsModel->index( row, 0 ); + d->m_childItemsSelectionModel->select( QItemSelection(index, index), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); +} + +bool KBreadcrumbNavigationFactory::childCollectionHasChildren(int row) +{ + if ( row < 0 ) + return false; + + Q_D(KBreadcrumbNavigationFactory); + + static const int column = 0; + const QModelIndex idx = d->m_modelIndexProxyMapper->mapRightToLeft(d->m_childItemsModel->index(row, column)); + if (!idx.isValid()) + return false; + + return idx.model()->rowCount( idx ) > 0; +} + +QObject* KBreadcrumbNavigationFactory::qmlBreadcrumbSelectionModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlBreadcrumbSelectionModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlBreadcrumbsModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_breadcrumbModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlChildItemsModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_childItemsModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlChildSelectionModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlChildSelectionModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlSelectedItemModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_selectedItemModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlSelectionModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlSelectedItemSelectionModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlBreadcrumbCheckModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlBreadcrumbCheckModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlChildCheckModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlChildCheckModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlSelectedItemCheckModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlSelectedItemCheckModel; +} + +QItemSelectionModel* KBreadcrumbNavigationFactory::checkedItemsCheckModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_checkedItemsCheckModel; +} + +QAbstractItemModel* KBreadcrumbNavigationFactory::checkedItemsModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_checkedItemsModel; +} + +QItemSelectionModel* KBreadcrumbNavigationFactory::checkModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_checkModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlCheckedItemsCheckModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_qmlCheckedItemsCheckModel; +} + +QObject* KBreadcrumbNavigationFactory::qmlCheckedItemsModel() const +{ + Q_D(const KBreadcrumbNavigationFactory); + return d->m_checkedItemsModel; +} + + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.h new file mode 100644 index 00000000..5d61f3bf --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/breadcrumbnavigationcontext.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef BREADCRUMBNAVIGATIONFACTORY_H +#define BREADCRUMBNAVIGATIONFACTORY_H + +#include + +class QAbstractItemModel; +class QItemSelectionModel; +class QDeclarativeContext; + + +class KBreadcrumbNavigationFactoryPrivate; + +class KBreadcrumbNavigationFactory : public QObject +{ + Q_OBJECT +public: + KBreadcrumbNavigationFactory(QObject* parent = 0); + + void createBreadcrumbContext(QAbstractItemModel *model, QObject* parent = 0); + void createCheckableBreadcrumbContext(QAbstractItemModel *model, QObject* parent = 0); + + void setBreadcrumbDepth(int depth); + int breadcrumbDepth() const; + + QItemSelectionModel *breadcrumbSelectionModel() const; + QItemSelectionModel *selectionModel() const; + QItemSelectionModel *childSelectionModel() const; + + QAbstractItemModel *breadcrumbItemModel() const; + QAbstractItemModel *selectedItemModel() const; + QAbstractItemModel *unfilteredChildItemModel() const; + QAbstractItemModel *childItemModel() const; + + QItemSelectionModel *checkModel() const; + QAbstractItemModel *checkedItemsModel() const; + QItemSelectionModel *checkedItemsCheckModel() const; + +public slots: + QObject* qmlBreadcrumbSelectionModel() const; + QObject* qmlSelectionModel() const; + QObject* qmlChildSelectionModel() const; + + QObject* qmlBreadcrumbCheckModel() const; + QObject* qmlSelectedItemCheckModel() const; + QObject* qmlChildCheckModel() const; + QObject* qmlCheckedItemsModel() const; + QObject* qmlCheckedItemsCheckModel() const; + + QObject* qmlBreadcrumbsModel() const; + QObject* qmlSelectedItemModel() const; + QObject* qmlChildItemsModel() const; + + void selectBreadcrumb( int row ); + void selectChild( int row ); + + bool childCollectionHasChildren( int row ); + +protected: + virtual QAbstractItemModel* getBreadcrumbNavigationModel(QAbstractItemModel *model); + virtual QAbstractItemModel* getChildItemsModel(QAbstractItemModel *model); + +private: + Q_DECLARE_PRIVATE(KBreadcrumbNavigationFactory) + KBreadcrumbNavigationFactoryPrivate * const d_ptr; + +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.cpp new file mode 100644 index 00000000..f61485ad --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.cpp @@ -0,0 +1,122 @@ +/* + This file is part of KDE. + + Copyright (c) 2010 Stephen Kelly + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + USA. +*/ + + +#include "checkableitemproxymodel.h" + +#include +#include + +class CheckableItemProxyModelPrivate +{ + Q_DECLARE_PUBLIC(CheckableItemProxyModel) + CheckableItemProxyModel *q_ptr; + + CheckableItemProxyModelPrivate(CheckableItemProxyModel *checkableModel) + : q_ptr(checkableModel), + m_itemSelectionModel(0) + { + + } + + QItemSelectionModel *m_itemSelectionModel; + + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + +}; + +CheckableItemProxyModel::CheckableItemProxyModel(QObject* parent) + : QSortFilterProxyModel(parent), d_ptr(new CheckableItemProxyModelPrivate(this)) +{ + +} + +void CheckableItemProxyModel::setSelectionModel(QItemSelectionModel* itemSelectionModel) +{ + Q_D(CheckableItemProxyModel); + d->m_itemSelectionModel = itemSelectionModel; + Q_ASSERT(sourceModel() ? d->m_itemSelectionModel->model() == sourceModel() : true); + connect(itemSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged(QItemSelection,QItemSelection))); +} + +Qt::ItemFlags CheckableItemProxyModel::flags(const QModelIndex& index) const +{ + return QSortFilterProxyModel::flags(index) | Qt::ItemIsUserCheckable; +} + +QVariant CheckableItemProxyModel::data(const QModelIndex& index, int role) const +{ + Q_D(const CheckableItemProxyModel); + + if (role == Qt::CheckStateRole) + { + if (!d->m_itemSelectionModel) + return Qt::Unchecked; + + return d->m_itemSelectionModel->selection().contains(mapToSource(index)) ? Qt::Checked : Qt::Unchecked; + } + return QSortFilterProxyModel::data(index, role); +} + +bool CheckableItemProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + Q_D(CheckableItemProxyModel); + if (role == Qt::CheckStateRole) + { + if (!d->m_itemSelectionModel) + return false; + + Qt::CheckState state = static_cast(value.toInt()); + const QModelIndex srcIndex = mapToSource(index); + bool result = select(QItemSelection(srcIndex, srcIndex), state == Qt::Checked ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); + qDebug() << "DC"; + emit dataChanged(srcIndex, srcIndex); + return result; + } + return QSortFilterProxyModel::setData(index, value, role); +} + +void CheckableItemProxyModel::setSourceModel(QAbstractItemModel* sourceModel) +{ + QSortFilterProxyModel::setSourceModel(sourceModel); + Q_ASSERT(d_ptr->m_itemSelectionModel ? d_ptr->m_itemSelectionModel->model() == sourceModel : true); +} + +void CheckableItemProxyModelPrivate::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + qDebug()<< "SEL CH" << selected << deselected; + Q_Q(CheckableItemProxyModel); + foreach (const QItemSelectionRange &range, q->mapSelectionFromSource(selected)) + q->dataChanged(range.topLeft(), range.bottomRight()); + foreach (const QItemSelectionRange &range, q->mapSelectionFromSource(deselected)) + q->dataChanged(range.topLeft(), range.bottomRight()); +} + +bool CheckableItemProxyModel::select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command) +{ + Q_D(CheckableItemProxyModel); + d->m_itemSelectionModel->select(selection, command); + return true; +} + + +#include "moc_checkableitemproxymodel.cpp" + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.h new file mode 100644 index 00000000..49dbc675 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/checkableitemproxymodel.h @@ -0,0 +1,60 @@ +/* + This file is part of Akonadi. + + Copyright (c) 2010 Stephen Kelly + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + USA. +*/ + +#ifndef CHECKABLEITEMPROXYMODEL_H +#define CHECKABLEITEMPROXYMODEL_H + +#include + +#include + +class QItemSelectionModel; + +class CheckableItemProxyModelPrivate; + +class CheckableItemProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + CheckableItemProxyModel(QObject* parent = 0); + + void setSelectionModel(QItemSelectionModel *itemSelectionModel); + + /* reimp */ Qt::ItemFlags flags(const QModelIndex& index) const; + + /* reimp */ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + /* reimp */ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + + /* reimp */ void setSourceModel(QAbstractItemModel* sourceModel); + +protected: + virtual bool select( const QItemSelection &selection, QItemSelectionModel::SelectionFlags command ); + +private: + Q_DECLARE_PRIVATE(CheckableItemProxyModel) + CheckableItemProxyModelPrivate * const d_ptr; + + Q_PRIVATE_SLOT(d_func(), void selectionChanged(const QItemSelection &, const QItemSelection &) ) +}; + +#endif + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/FindQt4.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/FindQt4.cmake new file mode 100644 index 00000000..0fef4275 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/FindQt4.cmake @@ -0,0 +1,1256 @@ +# - Find QT 4 +# This module can be used to find Qt4. +# The most important issue is that the Qt4 qmake is available via the system path. +# This qmake is then used to detect basically everything else. +# This module defines a number of key variables and macros. +# The variable QT_USE_FILE is set which is the path to a CMake file that can be included +# to compile Qt 4 applications and libraries. It sets up the compilation +# environment for include directories, preprocessor defines and populates a +# QT_LIBRARIES variable. +# +# Typical usage could be something like: +# find_package(Qt4 4.4.3 COMPONENTS QtCore QtGui QtXml REQUIRED ) +# include(${QT_USE_FILE}) +# add_executable(myexe main.cpp) +# target_link_libraries(myexe ${QT_LIBRARIES}) +# +# The minimum required version can be specified using the standard find_package()-syntax +# (see example above). +# For compatibility with older versions of FindQt4.cmake it is also possible to +# set the variable QT_MIN_VERSION to the minimum required version of Qt4 before the +# find_package(Qt4) command. +# If both are used, the version used in the find_package() command overrides the +# one from QT_MIN_VERSION. +# +# When using the components argument, QT_USE_QT* variables are automatically set +# for the QT_USE_FILE to pick up. If one wishes to manually set them, the +# available ones to set include: +# QT_DONT_USE_QTCORE +# QT_DONT_USE_QTGUI +# QT_USE_QT3SUPPORT +# QT_USE_QTASSISTANT +# QT_USE_QAXCONTAINER +# QT_USE_QAXSERVER +# QT_USE_QTDESIGNER +# QT_USE_QTMOTIF +# QT_USE_QTMAIN +# QT_USE_QTMULTIMEDIA +# QT_USE_QTNETWORK +# QT_USE_QTNSPLUGIN +# QT_USE_QTOPENGL +# QT_USE_QTSQL +# QT_USE_QTXML +# QT_USE_QTSVG +# QT_USE_QTTEST +# QT_USE_QTUITOOLS +# QT_USE_QTDBUS +# QT_USE_QTSCRIPT +# QT_USE_QTASSISTANTCLIENT +# QT_USE_QTHELP +# QT_USE_QTWEBKIT +# QT_USE_QTXMLPATTERNS +# QT_USE_PHONON +# QT_USE_QTSCRIPTTOOLS +# QT_USE_QTDECLARATIVE +# +# QT_USE_IMPORTED_TARGETS +# If this variable is set to TRUE, FindQt4.cmake will create imported +# library targets for the various Qt libraries and set the +# library variables like QT_QTCORE_LIBRARY to point at these imported +# targets instead of the library file on disk. This provides much better +# handling of the release and debug versions of the Qt libraries and is +# also always backwards compatible, except for the case that dependencies +# of libraries are exported, these will then also list the names of the +# imported targets as dependency and not the file location on disk. This +# is much more flexible, but requires that FindQt4.cmake is executed before +# such an exported dependency file is processed. +# +# There are also some files that need processing by some Qt tools such as moc +# and uic. Listed below are macros that may be used to process those files. +# +# macro QT4_WRAP_CPP(outfiles inputfile ... OPTIONS ...) +# create moc code from a list of files containing Qt class with +# the Q_OBJECT declaration. Per-direcotry preprocessor definitions +# are also added. Options may be given to moc, such as those found +# when executing "moc -help". +# +# macro QT4_WRAP_UI(outfiles inputfile ... OPTIONS ...) +# create code from a list of Qt designer ui files. +# Options may be given to uic, such as those found +# when executing "uic -help" +# +# macro QT4_ADD_RESOURCES(outfiles inputfile ... OPTIONS ...) +# create code from a list of Qt resource files. +# Options may be given to rcc, such as those found +# when executing "rcc -help" +# +# macro QT4_GENERATE_MOC(inputfile outputfile ) +# creates a rule to run moc on infile and create outfile. +# Use this if for some reason QT4_WRAP_CPP() isn't appropriate, e.g. +# because you need a custom filename for the moc file or something similar. +# +# macro QT4_AUTOMOC(sourcefile1 sourcefile2 ... ) +# This macro is still experimental. +# It can be used to have moc automatically handled. +# So if you have the files foo.h and foo.cpp, and in foo.h a +# a class uses the Q_OBJECT macro, moc has to run on it. If you don't +# want to use QT4_WRAP_CPP() (which is reliable and mature), you can insert +# #include "foo.moc" +# in foo.cpp and then give foo.cpp as argument to QT4_AUTOMOC(). This will the +# scan all listed files at cmake-time for such included moc files and if it finds +# them cause a rule to be generated to run moc at build time on the +# accompanying header file foo.h. +# If a source file has the SKIP_AUTOMOC property set it will be ignored by this macro. +# +# macro QT4_ADD_DBUS_INTERFACE(outfiles interface basename) +# create a the interface header and implementation files with the +# given basename from the given interface xml file and add it to +# the list of sources. +# To disable generating a namespace header, set the source file property +# NO_NAMESPACE to TRUE on the interface file. +# To include a header in the interface header, set the source file property +# INCLUDE to the name of the header. +# To specify a class name to use, set the source file property CLASSNAME +# to the name of the class. +# +# macro QT4_ADD_DBUS_INTERFACES(outfiles inputfile ... ) +# create the interface header and implementation files +# for all listed interface xml files +# the name will be automatically determined from the name of the xml file +# To disable generating namespace headers, set the source file property +# NO_NAMESPACE to TRUE for these inputfiles. +# To include a header in the interface header, set the source file property +# INCLUDE to the name of the header. +# To specify a class name to use, set the source file property CLASSNAME +# to the name of the class. +# +# macro QT4_ADD_DBUS_ADAPTOR(outfiles xmlfile parentheader parentclassname [basename] [classname]) +# create a dbus adaptor (header and implementation file) from the xml file +# describing the interface, and add it to the list of sources. The adaptor +# forwards the calls to a parent class, defined in parentheader and named +# parentclassname. The name of the generated files will be +# adaptor.{cpp,h} where basename defaults to the basename of the xml file. +# If is provided, then it will be used as the classname of the +# adaptor itself. +# +# macro QT4_GENERATE_DBUS_INTERFACE( header [interfacename] OPTIONS ...) +# generate the xml interface file from the given header. +# If the optional argument interfacename is omitted, the name of the +# interface file is constructed from the basename of the header with +# the suffix .xml appended. +# Options may be given to qdbuscpp2xml, such as those found when executing "qdbuscpp2xml --help" +# +# macro QT4_CREATE_TRANSLATION( qm_files directories ... sources ... +# ts_files ... OPTIONS ...) +# out: qm_files +# in: directories sources ts_files +# options: flags to pass to lupdate, such as -extensions to specify +# extensions for a directory scan. +# generates commands to create .ts (vie lupdate) and .qm +# (via lrelease) - files from directories and/or sources. The ts files are +# created and/or updated in the source tree (unless given with full paths). +# The qm files are generated in the build tree. +# Updating the translations can be done by adding the qm_files +# to the source list of your library/executable, so they are +# always updated, or by adding a custom target to control when +# they get updated/generated. +# +# macro QT4_ADD_TRANSLATION( qm_files ts_files ... ) +# out: qm_files +# in: ts_files +# generates commands to create .qm from .ts - files. The generated +# filenames can be found in qm_files. The ts_files +# must exists and are not updated in any way. +# +# +# Below is a detailed list of variables that FindQt4.cmake sets. +# QT_FOUND If false, don't try to use Qt. +# QT4_FOUND If false, don't try to use Qt 4. +# +# QT_VERSION_MAJOR The major version of Qt found. +# QT_VERSION_MINOR The minor version of Qt found. +# QT_VERSION_PATCH The patch version of Qt found. +# +# QT_EDITION Set to the edition of Qt (i.e. DesktopLight) +# QT_EDITION_DESKTOPLIGHT True if QT_EDITION == DesktopLight +# QT_QTCORE_FOUND True if QtCore was found. +# QT_QTGUI_FOUND True if QtGui was found. +# QT_QT3SUPPORT_FOUND True if Qt3Support was found. +# QT_QTASSISTANT_FOUND True if QtAssistant was found. +# QT_QTASSISTANTCLIENT_FOUND True if QtAssistantClient was found. +# QT_QAXCONTAINER_FOUND True if QAxContainer was found (Windows only). +# QT_QAXSERVER_FOUND True if QAxServer was found (Windows only). +# QT_QTDBUS_FOUND True if QtDBus was found. +# QT_QTDESIGNER_FOUND True if QtDesigner was found. +# QT_QTDESIGNERCOMPONENTS True if QtDesignerComponents was found. +# QT_QTHELP_FOUND True if QtHelp was found. +# QT_QTMOTIF_FOUND True if QtMotif was found. +# QT_QTMULTIMEDIA_FOUND True if QtMultimedia was found (since Qt 4.6.0). +# QT_QTNETWORK_FOUND True if QtNetwork was found. +# QT_QTNSPLUGIN_FOUND True if QtNsPlugin was found. +# QT_QTOPENGL_FOUND True if QtOpenGL was found. +# QT_QTSQL_FOUND True if QtSql was found. +# QT_QTSVG_FOUND True if QtSvg was found. +# QT_QTSCRIPT_FOUND True if QtScript was found. +# QT_QTSCRIPTTOOLS_FOUND True if QtScriptTools was found. +# QT_QTTEST_FOUND True if QtTest was found. +# QT_QTUITOOLS_FOUND True if QtUiTools was found. +# QT_QTWEBKIT_FOUND True if QtWebKit was found. +# QT_QTXML_FOUND True if QtXml was found. +# QT_QTXMLPATTERNS_FOUND True if QtXmlPatterns was found. +# QT_PHONON_FOUND True if phonon was found. +# QT_QTDECLARATIVE_FOUND True if QtDeclarative was found. +# +# QT_MAC_USE_COCOA For Mac OS X, its whether Cocoa or Carbon is used. +# In general, this should not be used, but its useful +# when having platform specific code. +# +# QT_DEFINITIONS Definitions to use when compiling code that uses Qt. +# You do not need to use this if you include QT_USE_FILE. +# The QT_USE_FILE will also define QT_DEBUG and QT_NO_DEBUG +# to fit your current build type. Those are not contained +# in QT_DEFINITIONS. +# +# QT_INCLUDES List of paths to all include directories of +# Qt4 QT_INCLUDE_DIR and QT_QTCORE_INCLUDE_DIR are +# always in this variable even if NOTFOUND, +# all other INCLUDE_DIRS are +# only added if they are found. +# You do not need to use this if you include QT_USE_FILE. +# +# +# Include directories for the Qt modules are listed here. +# You do not need to use these variables if you include QT_USE_FILE. +# +# QT_INCLUDE_DIR Path to "include" of Qt4 +# QT_QT_INCLUDE_DIR Path to "include/Qt" +# QT_QT3SUPPORT_INCLUDE_DIR Path to "include/Qt3Support" +# QT_QTASSISTANT_INCLUDE_DIR Path to "include/QtAssistant" +# QT_QTASSISTANTCLIENT_INCLUDE_DIR Path to "include/QtAssistant" +# QT_QAXCONTAINER_INCLUDE_DIR Path to "include/ActiveQt" (Windows only) +# QT_QAXSERVER_INCLUDE_DIR Path to "include/ActiveQt" (Windows only) +# QT_QTCORE_INCLUDE_DIR Path to "include/QtCore" +# QT_QTDBUS_INCLUDE_DIR Path to "include/QtDBus" +# QT_QTDESIGNER_INCLUDE_DIR Path to "include/QtDesigner" +# QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR Path to "include/QtDesigner" +# QT_QTGUI_INCLUDE_DIR Path to "include/QtGui" +# QT_QTHELP_INCLUDE_DIR Path to "include/QtHelp" +# QT_QTMOTIF_INCLUDE_DIR Path to "include/QtMotif" +# QT_QTMULTIMEDIA_INCLUDE_DIR Path to "include/QtMultimedia" +# QT_QTNETWORK_INCLUDE_DIR Path to "include/QtNetwork" +# QT_QTNSPLUGIN_INCLUDE_DIR Path to "include/QtNsPlugin" +# QT_QTOPENGL_INCLUDE_DIR Path to "include/QtOpenGL" +# QT_QTSCRIPT_INCLUDE_DIR Path to "include/QtScript" +# QT_QTSQL_INCLUDE_DIR Path to "include/QtSql" +# QT_QTSVG_INCLUDE_DIR Path to "include/QtSvg" +# QT_QTTEST_INCLUDE_DIR Path to "include/QtTest" +# QT_QTWEBKIT_INCLUDE_DIR Path to "include/QtWebKit" +# QT_QTXML_INCLUDE_DIR Path to "include/QtXml" +# QT_QTXMLPATTERNS_INCLUDE_DIR Path to "include/QtXmlPatterns" +# QT_PHONON_INCLUDE_DIR Path to "include/phonon" +# QT_QTSCRIPTTOOLS_INCLUDE_DIR Path to "include/QtScriptTools" +# QT_QTDECLARATIVE_INCLUDE_DIR Path to "include/QtDeclarative" +# +# QT_BINARY_DIR Path to "bin" of Qt4 +# QT_LIBRARY_DIR Path to "lib" of Qt4 +# QT_PLUGINS_DIR Path to "plugins" for Qt4 +# QT_TRANSLATIONS_DIR Path to "translations" of Qt4 +# QT_DOC_DIR Path to "doc" of Qt4 +# QT_MKSPECS_DIR Path to "mkspecs" of Qt4 +# +# +# For every library of Qt, a QT_QTFOO_LIBRARY variable is defined, with the full path to the library. +# +# So there are the following variables: +# The Qt3Support library: QT_QT3SUPPORT_LIBRARY +# +# The QtAssistant library: QT_QTASSISTANT_LIBRARY +# +# The QtAssistantClient library: QT_QTASSISTANTCLIENT_LIBRARY +# +# The QAxServer library: QT_QAXSERVER_LIBRARY +# +# The QAxContainer library: QT_QAXCONTAINER_LIBRARY +# +# The QtCore library: QT_QTCORE_LIBRARY +# +# The QtDBus library: QT_QTDBUS_LIBRARY +# +# The QtDesigner library: QT_QTDESIGNER_LIBRARY +# +# The QtDesignerComponents library: QT_QTDESIGNERCOMPONENTS_LIBRARY +# +# The QtGui library: QT_QTGUI_LIBRARY +# +# The QtHelp library: QT_QTHELP_LIBRARY +# +# The QtMotif library: QT_QTMOTIF_LIBRARY +# +# The QtMultimedia library: QT_QTMULTIMEDIA_LIBRARY +# +# The QtNetwork library: QT_QTNETWORK_LIBRARY +# +# The QtNsPLugin library: QT_QTNSPLUGIN_LIBRARY +# +# The QtOpenGL library: QT_QTOPENGL_LIBRARY +# +# The QtScript library: QT_QTSCRIPT_LIBRARY +# +# The QtScriptTools library: QT_QTSCRIPTTOOLS_LIBRARY +# +# The QtSql library: QT_QTSQL_LIBRARY +# +# The QtSvg library: QT_QTSVG_LIBRARY +# +# The QtTest library: QT_QTTEST_LIBRARY +# +# The QtUiTools library: QT_QTUITOOLS_LIBRARY +# +# The QtWebKit library: QT_QTWEBKIT_LIBRARY +# +# The QtXml library: QT_QTXML_LIBRARY +# +# The QtXmlPatterns library: QT_QTXMLPATTERNS_LIBRARY +# +# The qtmain library for Windows QT_QTMAIN_LIBRARY +# +# The Phonon library: QT_PHONON_LIBRARY +# +# The QtDeclarative library: QT_QTDECLARATIVE_LIBRARY +# +# also defined, but NOT for general use are +# QT_MOC_EXECUTABLE Where to find the moc tool. +# QT_UIC_EXECUTABLE Where to find the uic tool. +# QT_UIC3_EXECUTABLE Where to find the uic3 tool. +# QT_RCC_EXECUTABLE Where to find the rcc tool +# QT_DBUSCPP2XML_EXECUTABLE Where to find the qdbuscpp2xml tool. +# QT_DBUSXML2CPP_EXECUTABLE Where to find the qdbusxml2cpp tool. +# QT_LUPDATE_EXECUTABLE Where to find the lupdate tool. +# QT_LRELEASE_EXECUTABLE Where to find the lrelease tool. +# QT_QCOLLECTIONGENERATOR_EXECUTABLE Where to find the qcollectiongenerator tool. +# QT_DESIGNER_EXECUTABLE Where to find the Qt designer tool. +# QT_LINGUIST_EXECUTABLE Where to find the Qt linguist tool. +# +# +# These are around for backwards compatibility +# they will be set +# QT_WRAP_CPP Set true if QT_MOC_EXECUTABLE is found +# QT_WRAP_UI Set true if QT_UIC_EXECUTABLE is found +# +# These variables do _NOT_ have any effect anymore (compared to FindQt.cmake) +# QT_MT_REQUIRED Qt4 is now always multithreaded +# +# These variables are set to "" Because Qt structure changed +# (They make no sense in Qt4) +# QT_QT_LIBRARY Qt-Library is now split + +# Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +# See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + +# Use FIND_PACKAGE( Qt4 COMPONENTS ... ) to enable modules +IF( Qt4_FIND_COMPONENTS ) + FOREACH( component ${Qt4_FIND_COMPONENTS} ) + STRING( TOUPPER ${component} _COMPONENT ) + SET( QT_USE_${_COMPONENT} 1 ) + ENDFOREACH( component ) + + # To make sure we don't use QtCore or QtGui when not in COMPONENTS + IF(NOT QT_USE_QTCORE) + SET( QT_DONT_USE_QTCORE 1 ) + ENDIF(NOT QT_USE_QTCORE) + + IF(NOT QT_USE_QTGUI) + SET( QT_DONT_USE_QTGUI 1 ) + ENDIF(NOT QT_USE_QTGUI) + +ENDIF( Qt4_FIND_COMPONENTS ) + +# If Qt3 has already been found, fail. +IF(QT_QT_LIBRARY) + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Qt3 and Qt4 cannot be used together in one project. If switching to Qt4, the CMakeCache.txt needs to be cleaned.") + ELSE(Qt4_FIND_REQUIRED) + IF(NOT Qt4_FIND_QUIETLY) + MESSAGE( STATUS "Qt3 and Qt4 cannot be used together in one project. If switching to Qt4, the CMakeCache.txt needs to be cleaned.") + ENDIF(NOT Qt4_FIND_QUIETLY) + RETURN() + ENDIF(Qt4_FIND_REQUIRED) +ENDIF(QT_QT_LIBRARY) + + +IF (QT4_QMAKE_FOUND AND Qt4::QtCore) + # Check already done in this cmake run, nothing more to do + RETURN() +ENDIF (QT4_QMAKE_FOUND AND Qt4::QtCore) + +# check that QT_NO_DEBUG is defined for release configurations +MACRO(QT_CHECK_FLAG_EXISTS FLAG VAR DOC) + IF(NOT ${VAR} MATCHES "${FLAG}") + SET(${VAR} "${${VAR}} ${FLAG}" + CACHE STRING "Flags used by the compiler during ${DOC} builds." FORCE) + ENDIF(NOT ${VAR} MATCHES "${FLAG}") +ENDMACRO(QT_CHECK_FLAG_EXISTS FLAG VAR) + +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_RELWITHDEBINFO "Release with Debug Info") +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_RELEASE "release") +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_MINSIZEREL "release minsize") + +INCLUDE(MacroPushRequiredVars) +INCLUDE(CheckSymbolExists) +INCLUDE(MacroAddFileDependencies) + +SET(QT_USE_FILE ${CMAKE_ROOT}/Modules/UseQt4.cmake) + +SET( QT_DEFINITIONS "") + +SET(QT4_INSTALLED_VERSION_TOO_OLD FALSE) + +# macro for asking qmake to process pro files +MACRO(QT_QUERY_QMAKE outvar invar) + IF(QT_QMAKE_EXECUTABLE) + FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake/tmp.pro + "message(CMAKE_MESSAGE<$$${invar}>)") + + # Invoke qmake with the tmp.pro program to get the desired + # information. Use the same variable for both stdout and stderr + # to make sure we get the output on all platforms. + EXECUTE_PROCESS(COMMAND ${QT_QMAKE_EXECUTABLE} + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake + OUTPUT_VARIABLE _qmake_query_output + RESULT_VARIABLE _qmake_result + ERROR_VARIABLE _qmake_query_output ) + + FILE(REMOVE_RECURSE + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake") + + IF(_qmake_result) + MESSAGE(WARNING " querying qmake for ${invar}. qmake reported:\n${_qmake_query_output}") + ELSE(_qmake_result) + STRING(REGEX REPLACE ".*CMAKE_MESSAGE<([^>]*).*" "\\1" ${outvar} "${_qmake_query_output}") + ENDIF(_qmake_result) + + ENDIF(QT_QMAKE_EXECUTABLE) +ENDMACRO(QT_QUERY_QMAKE) + +GET_FILENAME_COMPONENT(qt_install_version "[HKEY_CURRENT_USER\\Software\\trolltech\\Versions;DefaultQtVersion]" NAME) +# check for qmake +# Debian uses qmake-qt4 +# macports' Qt uses qmake-mac +FIND_PROGRAM(QT_QMAKE_EXECUTABLE NAMES qmake qmake4 qmake-qt4 qmake-mac PATHS + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Qt3Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\${qt_install_version};InstallDir]/bin" + $ENV{QTDIR}/bin +) + +IF (QT_QMAKE_EXECUTABLE) + + IF(QT_QMAKE_EXECUTABLE_LAST) + STRING(COMPARE NOTEQUAL "${QT_QMAKE_EXECUTABLE_LAST}" "${QT_QMAKE_EXECUTABLE}" QT_QMAKE_CHANGED) + ENDIF(QT_QMAKE_EXECUTABLE_LAST) + + SET(QT_QMAKE_EXECUTABLE_LAST "${QT_QMAKE_EXECUTABLE}" CACHE INTERNAL "" FORCE) + + SET(QT4_QMAKE_FOUND FALSE) + + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} ARGS "-query QT_VERSION" OUTPUT_VARIABLE QTVERSION) + + # check for qt3 qmake and then try and find qmake4 or qmake-qt4 in the path + IF("${QTVERSION}" MATCHES "Unknown") + SET(QT_QMAKE_EXECUTABLE NOTFOUND CACHE FILEPATH "" FORCE) + FIND_PROGRAM(QT_QMAKE_EXECUTABLE NAMES qmake4 qmake-qt4 PATHS + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Qt3Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\4.0.0;InstallDir]/bin" + $ENV{QTDIR}/bin + ) + IF(QT_QMAKE_EXECUTABLE) + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_VERSION" OUTPUT_VARIABLE QTVERSION) + ENDIF(QT_QMAKE_EXECUTABLE) + ENDIF("${QTVERSION}" MATCHES "Unknown") + + # check that we found the Qt4 qmake, Qt3 qmake output won't match here + STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" qt_version_tmp "${QTVERSION}") + IF (qt_version_tmp) + + # we need at least version 4.0.0 + IF (NOT QT_MIN_VERSION) + SET(QT_MIN_VERSION "4.0.0") + ENDIF (NOT QT_MIN_VERSION) + + #now parse the parts of the user given version string into variables + STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" req_qt_major_vers "${QT_MIN_VERSION}") + IF (NOT req_qt_major_vers) + MESSAGE( FATAL_ERROR "Invalid Qt version string given: \"${QT_MIN_VERSION}\", expected e.g. \"4.0.1\"") + ENDIF (NOT req_qt_major_vers) + + # now parse the parts of the user given version string into variables + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" req_qt_major_vers "${QT_MIN_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+" "\\1" req_qt_minor_vers "${QT_MIN_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" req_qt_patch_vers "${QT_MIN_VERSION}") + + # Suppport finding at least a particular version, for instance FIND_PACKAGE( Qt4 4.4.3 ) + # This implementation is a hack to avoid duplicating code and make sure we stay + # source-compatible with CMake 2.6.x + IF( Qt4_FIND_VERSION ) + SET( QT_MIN_VERSION ${Qt4_FIND_VERSION} ) + SET( req_qt_major_vers ${Qt4_FIND_VERSION_MAJOR} ) + SET( req_qt_minor_vers ${Qt4_FIND_VERSION_MINOR} ) + SET( req_qt_patch_vers ${Qt4_FIND_VERSION_PATCH} ) + ENDIF( Qt4_FIND_VERSION ) + + IF (NOT req_qt_major_vers EQUAL 4) + MESSAGE( FATAL_ERROR "Invalid Qt version string given: \"${QT_MIN_VERSION}\", major version 4 is required, e.g. \"4.0.1\"") + ENDIF (NOT req_qt_major_vers EQUAL 4) + + # and now the version string given by qmake + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" QT_VERSION_MAJOR "${QTVERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+.*" "\\1" QT_VERSION_MINOR "${QTVERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" QT_VERSION_PATCH "${QTVERSION}") + + # compute an overall version number which can be compared at once + MATH(EXPR req_vers "${req_qt_major_vers}*10000 + ${req_qt_minor_vers}*100 + ${req_qt_patch_vers}") + MATH(EXPR found_vers "${QT_VERSION_MAJOR}*10000 + ${QT_VERSION_MINOR}*100 + ${QT_VERSION_PATCH}") + + # Support finding *exactly* a particular version, for instance FIND_PACKAGE( Qt4 4.4.3 EXACT ) + IF( Qt4_FIND_VERSION_EXACT ) + IF(found_vers EQUAL req_vers) + SET( QT4_QMAKE_FOUND TRUE ) + ELSE(found_vers EQUAL req_vers) + SET( QT4_QMAKE_FOUND FALSE ) + IF (found_vers LESS req_vers) + SET(QT4_INSTALLED_VERSION_TOO_OLD TRUE) + ELSE (found_vers LESS req_vers) + SET(QT4_INSTALLED_VERSION_TOO_NEW TRUE) + ENDIF (found_vers LESS req_vers) + ENDIF(found_vers EQUAL req_vers) + ELSE( Qt4_FIND_VERSION_EXACT ) + IF (found_vers LESS req_vers) + SET(QT4_QMAKE_FOUND FALSE) + SET(QT4_INSTALLED_VERSION_TOO_OLD TRUE) + ELSE (found_vers LESS req_vers) + SET(QT4_QMAKE_FOUND TRUE) + ENDIF (found_vers LESS req_vers) + ENDIF( Qt4_FIND_VERSION_EXACT ) + ENDIF (qt_version_tmp) + +ENDIF (QT_QMAKE_EXECUTABLE) + +IF (QT4_QMAKE_FOUND) + + if (WIN32) + # get qt install dir + get_filename_component(_DIR ${QT_QMAKE_EXECUTABLE} PATH ) + get_filename_component(QT_INSTALL_DIR ${_DIR} PATH ) + endif (WIN32) + + # ask qmake for the library dir + # Set QT_LIBRARY_DIR + IF (NOT QT_LIBRARY_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_LIBS" + OUTPUT_VARIABLE QT_LIBRARY_DIR_TMP ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${QT_LIBRARY_DIR_TMP}" QT_LIBRARY_DIR_TMP) + IF(EXISTS "${QT_LIBRARY_DIR_TMP}") + SET(QT_LIBRARY_DIR ${QT_LIBRARY_DIR_TMP} CACHE PATH "Qt library dir" FORCE) + ELSE(EXISTS "${QT_LIBRARY_DIR_TMP}") + MESSAGE("Warning: QT_QMAKE_EXECUTABLE reported QT_INSTALL_LIBS as ${QT_LIBRARY_DIR_TMP}") + MESSAGE("Warning: ${QT_LIBRARY_DIR_TMP} does NOT exist, Qt must NOT be installed correctly.") + ENDIF(EXISTS "${QT_LIBRARY_DIR_TMP}") + ENDIF(NOT QT_LIBRARY_DIR OR QT_QMAKE_CHANGED) + + IF (APPLE) + IF (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + SET(QT_USE_FRAMEWORKS ON + CACHE BOOL "Set to ON if Qt build uses frameworks." FORCE) + ELSE (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + SET(QT_USE_FRAMEWORKS OFF + CACHE BOOL "Set to ON if Qt build uses frameworks." FORCE) + ENDIF (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + + MARK_AS_ADVANCED(QT_USE_FRAMEWORKS) + ENDIF (APPLE) + + # ask qmake for the binary dir + IF (QT_LIBRARY_DIR AND NOT QT_BINARY_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_BINS" + OUTPUT_VARIABLE qt_bins ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_bins}" qt_bins) + SET(QT_BINARY_DIR ${qt_bins} CACHE INTERNAL "" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_BINARY_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the include dir + IF (QT_LIBRARY_DIR AND NOT QT_HEADERS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_HEADERS" + OUTPUT_VARIABLE qt_headers ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_headers}" qt_headers) + SET(QT_HEADERS_DIR ${qt_headers} CACHE INTERNAL "" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_HEADERS_DIR OR QT_QMAKE_CHANGED) + + + # ask qmake for the documentation directory + IF (QT_LIBRARY_DIR AND NOT QT_DOC_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_DOCS" + OUTPUT_VARIABLE qt_doc_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_doc_dir}" qt_doc_dir) + SET(QT_DOC_DIR ${qt_doc_dir} CACHE PATH "The location of the Qt docs" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_DOC_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the mkspecs directory + IF (QT_LIBRARY_DIR AND NOT QT_MKSPECS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QMAKE_MKSPECS" + OUTPUT_VARIABLE qt_mkspecs_dirs ) + # do not replace : on windows as it might be a drive letter + # and windows should already use ; as a separator + IF(UNIX) + STRING(REPLACE ":" ";" qt_mkspecs_dirs "${qt_mkspecs_dirs}") + ENDIF(UNIX) + SET(QT_MKSPECS_DIR NOTFOUND) + FIND_PATH(QT_MKSPECS_DIR qconfig.pri PATHS ${qt_mkspecs_dirs} + DOC "The location of the Qt mkspecs containing qconfig.pri" + NO_DEFAULT_PATH ) + ENDIF (QT_LIBRARY_DIR AND NOT QT_MKSPECS_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the plugins directory + IF (QT_LIBRARY_DIR AND NOT QT_PLUGINS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_PLUGINS" + OUTPUT_VARIABLE qt_plugins_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_plugins_dir}" qt_plugins_dir) + SET(QT_PLUGINS_DIR ${qt_plugins_dir} CACHE PATH "The location of the Qt plugins" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_PLUGINS_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the translations directory + IF (QT_LIBRARY_DIR AND NOT QT_TRANSLATIONS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_TRANSLATIONS" + OUTPUT_VARIABLE qt_translations_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_translations_dir}" qt_translations_dir) + SET(QT_TRANSLATIONS_DIR ${qt_translations_dir} CACHE PATH "The location of the Qt translations" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_TRANSLATIONS_DIR OR QT_QMAKE_CHANGED) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED( QT_LIBRARY_DIR QT_DOC_DIR QT_MKSPECS_DIR + QT_PLUGINS_DIR QT_TRANSLATIONS_DIR) + + + ############################################# + # + # Find out what window system we're using + # + ############################################# + # Save required includes and required_flags variables + MACRO_PUSH_REQUIRED_VARS() + # Add QT_INCLUDE_DIR to CMAKE_REQUIRED_INCLUDES + SET(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${QT_HEADERS_DIR}") + # On Mac OS X when Qt has framework support, also add the framework path + IF( QT_USE_FRAMEWORKS ) + SET(CMAKE_REQUIRED_FLAGS "-F${QT_LIBRARY_DIR} ") + ENDIF( QT_USE_FRAMEWORKS ) + # Check for Window system symbols (note: only one should end up being set) + CHECK_SYMBOL_EXISTS(Q_WS_X11 "QtCore/qglobal.h" Q_WS_X11) + CHECK_SYMBOL_EXISTS(Q_WS_WIN "QtCore/qglobal.h" Q_WS_WIN) + CHECK_SYMBOL_EXISTS(Q_WS_QWS "QtCore/qglobal.h" Q_WS_QWS) + CHECK_SYMBOL_EXISTS(Q_WS_MAC "QtCore/qglobal.h" Q_WS_MAC) + IF(Q_WS_MAC) + IF(QT_QMAKE_CHANGED) + SET(QT_MAC_USE_COCOA "" CACHE BOOL "Use Cocoa on Mac" FORCE) + ENDIF(QT_QMAKE_CHANGED) + CHECK_SYMBOL_EXISTS(QT_MAC_USE_COCOA "QtCore/qconfig.h" QT_MAC_USE_COCOA) + ENDIF(Q_WS_MAC) + + IF (QT_QTCOPY_REQUIRED) + CHECK_SYMBOL_EXISTS(QT_IS_QTCOPY "QtCore/qglobal.h" QT_KDE_QT_COPY) + IF (NOT QT_IS_QTCOPY) + MESSAGE(FATAL_ERROR "qt-copy is required, but hasn't been found") + ENDIF (NOT QT_IS_QTCOPY) + ENDIF (QT_QTCOPY_REQUIRED) + + # Restore CMAKE_REQUIRED_INCLUDES+CMAKE_REQUIRED_FLAGS variables + MACRO_POP_REQUIRED_VARS() + # + ############################################# + + + + ######################################## + # + # Setting the INCLUDE-Variables + # + ######################################## + + SET(QT_MODULES QtCore QtGui Qt3Support QtSvg QtScript QtTest QtUiTools + QtHelp QtWebKit QtXmlPatterns QtNetwork QtMultimedia + QtNsPlugin QtOpenGL QtSql QtXml QtDesigner QtDBus QtScriptTools QtDeclarative) + + IF(Q_WS_X11) + SET(QT_MODULES ${QT_MODULES} QtMotif) + ENDIF(Q_WS_X11) + + IF(QT_QMAKE_CHANGED) + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + SET(QT_${_upper_qt_module}_INCLUDE_DIR NOTFOUND) + SET(QT_${_upper_qt_module}_LIBRARY_RELEASE NOTFOUND) + SET(QT_${_upper_qt_module}_LIBRARY_DEBUG NOTFOUND) + ENDFOREACH(QT_MODULE) + SET(QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR NOTFOUND) + SET(QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTDESIGNERCOMPONENTS_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTASSISTANTCLIENT_INCLUDE_DIR NOTFOUND) + SET(QT_QTASSISTANTCLIENT_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTASSISTANTCLIENT_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTASSISTANT_INCLUDE_DIR NOTFOUND) + SET(QT_QTASSISTANT_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTASSISTANT_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTCLUCENE_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTCLUCENE_LIBRARY_DEBUG NOTFOUND) + SET(QT_QAXCONTAINER_INCLUDE_DIR NOTFOUND) + SET(QT_QAXCONTAINER_LIBRARY_RELEASE NOTFOUND) + SET(QT_QAXCONTAINER_LIBRARY_DEBUG NOTFOUND) + SET(QT_QAXSERVER_INCLUDE_DIR NOTFOUND) + SET(QT_QAXSERVER_LIBRARY_RELEASE NOTFOUND) + SET(QT_QAXSERVER_LIBRARY_DEBUG NOTFOUND) + IF(WIN32) + SET(QT_QTMAIN_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTMAIN_LIBRARY_RELEASE NOTFOUND) + ENDIF(WIN32) + SET(QT_PHONON_INCLUDE_DIR NOTFOUND) + ENDIF(QT_QMAKE_CHANGED) + + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + FIND_PATH(QT_${_upper_qt_module}_INCLUDE_DIR ${QT_MODULE} + PATHS + ${QT_HEADERS_DIR}/${QT_MODULE} + ${QT_LIBRARY_DIR}/${QT_MODULE}.framework/Headers + NO_DEFAULT_PATH + ) + ENDFOREACH(QT_MODULE) + + IF(WIN32) + SET(QT_MODULES ${QT_MODULES} QAxContainer QAxServer) + # Set QT_AXCONTAINER_INCLUDE_DIR and QT_AXSERVER_INCLUDE_DIR + FIND_PATH(QT_QAXCONTAINER_INCLUDE_DIR ActiveQt + PATHS + ${QT_HEADERS_DIR}/ActiveQt + NO_DEFAULT_PATH + ) + FIND_PATH(QT_QAXSERVER_INCLUDE_DIR ActiveQt + PATHS + ${QT_HEADERS_DIR}/ActiveQt + NO_DEFAULT_PATH + ) + ENDIF(WIN32) + + # Set QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR + FIND_PATH(QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR QDesignerComponents + PATHS + ${QT_HEADERS_DIR}/QtDesigner + ${QT_LIBRARY_DIR}/QtDesigner.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QTASSISTANT_INCLUDE_DIR + FIND_PATH(QT_QTASSISTANT_INCLUDE_DIR QtAssistant + PATHS + ${QT_HEADERS_DIR}/QtAssistant + ${QT_LIBRARY_DIR}/QtAssistant.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QTASSISTANTCLIENT_INCLUDE_DIR + FIND_PATH(QT_QTASSISTANTCLIENT_INCLUDE_DIR QAssistantClient + PATHS + ${QT_HEADERS_DIR}/QtAssistant + ${QT_LIBRARY_DIR}/QtAssistant.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QT_INCLUDE_DIR + FIND_PATH(QT_QT_INCLUDE_DIR qglobal.h + PATHS + ${QT_HEADERS_DIR}/Qt + ${QT_LIBRARY_DIR}/QtCore.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_PHONON_INCLUDE_DIR + # Qt >= 4.5.3 (or kde-qt-4.5.2 which has the fix too) : Phonon/ClassName is inside include/phonon + # With previous versions of Qt, this could not work; upgrade Qt or use a standalone phonon + FIND_PATH(QT_PHONON_INCLUDE_DIR Phonon + PATHS + ${QT_HEADERS_DIR}/phonon + NO_DEFAULT_PATH + ) + SET(QT_MODULES ${QT_MODULES} phonon) + + # Set QT_INCLUDE_DIR by removine "/QtCore" in the string ${QT_QTCORE_INCLUDE_DIR} + IF( QT_QTCORE_INCLUDE_DIR AND NOT QT_INCLUDE_DIR) + IF (QT_USE_FRAMEWORKS) + SET(QT_INCLUDE_DIR ${QT_HEADERS_DIR}) + ELSE (QT_USE_FRAMEWORKS) + STRING( REGEX REPLACE "/QtCore$" "" qt4_include_dir ${QT_QTCORE_INCLUDE_DIR}) + SET( QT_INCLUDE_DIR ${qt4_include_dir} CACHE PATH "") + ENDIF (QT_USE_FRAMEWORKS) + ENDIF( QT_QTCORE_INCLUDE_DIR AND NOT QT_INCLUDE_DIR) + + IF( NOT QT_INCLUDE_DIR) + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could NOT find QtCore header") + ENDIF(Qt4_FIND_REQUIRED) + ENDIF( NOT QT_INCLUDE_DIR) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED( QT_INCLUDE_DIR QT_QT_INCLUDE_DIR) + + # Set QT_INCLUDES + SET( QT_INCLUDES ${QT_QT_INCLUDE_DIR} ${QT_MKSPECS_DIR}/default ${QT_INCLUDE_DIR} ) + + + ####################################### + # + # Qt configuration + # + ####################################### + IF(EXISTS "${QT_MKSPECS_DIR}/qconfig.pri") + FILE(READ ${QT_MKSPECS_DIR}/qconfig.pri _qconfig_FILE_contents) + STRING(REGEX MATCH "QT_CONFIG[^\n]+" QT_QCONFIG "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "CONFIG[^\n]+" QT_CONFIG "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "EDITION[^\n]+" QT_EDITION "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "QT_LIBINFIX[^\n]+" _qconfig_qt_libinfix "${_qconfig_FILE_contents}") + STRING(REGEX REPLACE "QT_LIBINFIX *= *([^\n]*)" "\\1" QT_LIBINFIX "${_qconfig_qt_libinfix}") + ENDIF(EXISTS "${QT_MKSPECS_DIR}/qconfig.pri") + IF("${QT_EDITION}" MATCHES "DesktopLight") + SET(QT_EDITION_DESKTOPLIGHT 1) + ENDIF("${QT_EDITION}" MATCHES "DesktopLight") + + ######################################## + # + # Setting the LIBRARY-Variables + # + ######################################## + + # find the libraries + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + FIND_LIBRARY(QT_${_upper_qt_module}_LIBRARY_RELEASE + NAMES ${QT_MODULE}${QT_LIBINFIX} ${QT_MODULE}${QT_LIBINFIX}4 + PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH + ) + FIND_LIBRARY(QT_${_upper_qt_module}_LIBRARY_DEBUG + NAMES ${QT_MODULE}${QT_LIBINFIX}_debug ${QT_MODULE}${QT_LIBINFIX}d ${QT_MODULE}${QT_LIBINFIX}d4 + PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH + ) + ENDFOREACH(QT_MODULE) + + # QtUiTools not with other frameworks with binary installation (in /usr/lib) + IF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTUITOOLS_LIBRARY_RELEASE) + FIND_LIBRARY(QT_QTUITOOLS_LIBRARY_RELEASE NAMES QtUiTools${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR}) + ENDIF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTUITOOLS_LIBRARY_RELEASE) + + IF( NOT QT_QTCORE_LIBRARY_DEBUG AND NOT QT_QTCORE_LIBRARY_RELEASE ) + + # try dropping a hint if trying to use Visual Studio with Qt built by mingw + IF(QT_LIBRARY_DIR AND MSVC) + IF(EXISTS ${QT_LIBRARY_DIR}/libqtmain.a) + MESSAGE( FATAL_ERROR "It appears you're trying to use Visual Studio with Qt built by mingw") + ENDIF(EXISTS ${QT_LIBRARY_DIR}/libqtmain.a) + ENDIF(QT_LIBRARY_DIR AND MSVC) + + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could NOT find QtCore. Check ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log for more details.") + ENDIF(Qt4_FIND_REQUIRED) + ENDIF( NOT QT_QTCORE_LIBRARY_DEBUG AND NOT QT_QTCORE_LIBRARY_RELEASE ) + + # Set QT_QTDESIGNERCOMPONENTS_LIBRARY + FIND_LIBRARY(QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE NAMES QtDesignerComponents${QT_LIBINFIX} QtDesignerComponents${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTDESIGNERCOMPONENTS_LIBRARY_DEBUG NAMES QtDesignerComponents${QT_LIBINFIX}_debug QtDesignerComponents${QT_LIBINFIX}d QtDesignerComponents${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTMAIN_LIBRARY + IF(WIN32) + FIND_LIBRARY(QT_QTMAIN_LIBRARY_RELEASE NAMES qtmain${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR} + NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTMAIN_LIBRARY_DEBUG NAMES qtmain${QT_LIBINFIX}d PATHS ${QT_LIBRARY_DIR} + NO_DEFAULT_PATH) + ENDIF(WIN32) + + # Set QT_QTASSISTANTCLIENT_LIBRARY + FIND_LIBRARY(QT_QTASSISTANTCLIENT_LIBRARY_RELEASE NAMES QtAssistantClient${QT_LIBINFIX} QtAssistantClient${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTASSISTANTCLIENT_LIBRARY_DEBUG NAMES QtAssistantClient${QT_LIBINFIX}_debug QtAssistantClient${QT_LIBINFIX}d QtAssistantClient${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTASSISTANT_LIBRARY + FIND_LIBRARY(QT_QTASSISTANT_LIBRARY_RELEASE NAMES QtAssistantClient${QT_LIBINFIX} QtAssistantClient${QT_LIBINFIX}4 QtAssistant${QT_LIBINFIX} QtAssistant${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTASSISTANT_LIBRARY_DEBUG NAMES QtAssistantClient${QT_LIBINFIX}_debug QtAssistantClient${QT_LIBINFIX}d QtAssistantClient${QT_LIBINFIX}d4 QtAssistant${QT_LIBINFIX}_debug QtAssistant${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTHELP_LIBRARY + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_RELEASE NAMES QtCLucene${QT_LIBINFIX} QtCLucene${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_DEBUG NAMES QtCLucene${QT_LIBINFIX}_debug QtCLucene${QT_LIBINFIX}d QtCLucene${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + # QtCLucene not with other frameworks with binary installation (in /usr/lib) + IF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTCLUCENE_LIBRARY_RELEASE) + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_RELEASE NAMES QtCLucene${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR}) + ENDIF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTCLUCENE_LIBRARY_RELEASE) + + ############################################ + # + # Check the existence of the libraries. + # + ############################################ + + # On OSX when Qt is found as framework, never use the imported targets for now, since + # in this case the handling of the framework directory currently does not work correctly. + IF(QT_USE_FRAMEWORKS) + SET(QT_USE_IMPORTED_TARGETS FALSE) + ENDIF(QT_USE_FRAMEWORKS) + + + MACRO (_QT4_ADJUST_LIB_VARS _camelCaseBasename) + + STRING(TOUPPER "${_camelCaseBasename}" basename) + + # The name of the imported targets, i.e. the prefix "Qt4::" must not change, + # since it is stored in EXPORT-files as name of a required library. If the name would change + # here, this would lead to the imported Qt4-library targets not being resolved by cmake anymore. + IF (QT_${basename}_LIBRARY_RELEASE OR QT_${basename}_LIBRARY_DEBUG) + + IF(NOT TARGET Qt4::${_camelCaseBasename}) + ADD_LIBRARY(Qt4::${_camelCaseBasename} UNKNOWN IMPORTED ) + + IF (QT_${basename}_LIBRARY_RELEASE) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} PROPERTY IMPORTED_LOCATION_RELEASE "${QT_${basename}_LIBRARY_RELEASE}" ) + ENDIF (QT_${basename}_LIBRARY_RELEASE) + + IF (QT_${basename}_LIBRARY_DEBUG) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} PROPERTY IMPORTED_LOCATION_DEBUG "${QT_${basename}_LIBRARY_DEBUG}" ) + ENDIF (QT_${basename}_LIBRARY_DEBUG) + ENDIF(NOT TARGET Qt4::${_camelCaseBasename}) + + # If QT_USE_IMPORTED_TARGETS is enabled, the QT_QTFOO_LIBRARY variables are set to point at these + # imported targets. This works better in general, and is also in almost all cases fully + # backward compatible. The only issue is when a project A which had this enabled then exports its + # libraries via export or EXPORT_LIBRARY_DEPENDENCIES(). In this case the libraries from project + # A will depend on the imported Qt targets, and the names of these imported targets will be stored + # in the dependency files on disk. This means when a project B then uses project A, these imported + # targets must be created again, otherwise e.g. "Qt4__QtCore" will be interpreted as name of a + # library file on disk, and not as a target, and linking will fail: + IF(QT_USE_IMPORTED_TARGETS) + SET(QT_${basename}_LIBRARY Qt4::${_camelCaseBasename} ) + SET(QT_${basename}_LIBRARIES Qt4::${_camelCaseBasename} ) + ELSE(QT_USE_IMPORTED_TARGETS) + + # if the release- as well as the debug-version of the library have been found: + IF (QT_${basename}_LIBRARY_DEBUG AND QT_${basename}_LIBRARY_RELEASE) + # if the generator supports configuration types then set + # optimized and debug libraries, or if the CMAKE_BUILD_TYPE has a value + IF (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + SET(QT_${basename}_LIBRARY optimized ${QT_${basename}_LIBRARY_RELEASE} debug ${QT_${basename}_LIBRARY_DEBUG}) + ELSE(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + # if there are no configuration types and CMAKE_BUILD_TYPE has no value + # then just use the release libraries + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_RELEASE} ) + ENDIF(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + SET(QT_${basename}_LIBRARIES optimized ${QT_${basename}_LIBRARY_RELEASE} debug ${QT_${basename}_LIBRARY_DEBUG}) + ENDIF (QT_${basename}_LIBRARY_DEBUG AND QT_${basename}_LIBRARY_RELEASE) + + # if only the release version was found, set the debug variable also to the release version + IF (QT_${basename}_LIBRARY_RELEASE AND NOT QT_${basename}_LIBRARY_DEBUG) + SET(QT_${basename}_LIBRARY_DEBUG ${QT_${basename}_LIBRARY_RELEASE}) + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_RELEASE}) + SET(QT_${basename}_LIBRARIES ${QT_${basename}_LIBRARY_RELEASE}) + ENDIF (QT_${basename}_LIBRARY_RELEASE AND NOT QT_${basename}_LIBRARY_DEBUG) + + # if only the debug version was found, set the release variable also to the debug version + IF (QT_${basename}_LIBRARY_DEBUG AND NOT QT_${basename}_LIBRARY_RELEASE) + SET(QT_${basename}_LIBRARY_RELEASE ${QT_${basename}_LIBRARY_DEBUG}) + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_DEBUG}) + SET(QT_${basename}_LIBRARIES ${QT_${basename}_LIBRARY_DEBUG}) + ENDIF (QT_${basename}_LIBRARY_DEBUG AND NOT QT_${basename}_LIBRARY_RELEASE) + + # put the value in the cache: + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY} CACHE STRING "The Qt ${basename} library" FORCE) + + ENDIF(QT_USE_IMPORTED_TARGETS) + +#message(STATUS "QT_${basename}_LIBRARY: ${QT_${basename}_LIBRARY}") + + SET(QT_${basename}_FOUND 1) + + ENDIF (QT_${basename}_LIBRARY_RELEASE OR QT_${basename}_LIBRARY_DEBUG) + + IF (QT_${basename}_INCLUDE_DIR) + #add the include directory to QT_INCLUDES + SET(QT_INCLUDES "${QT_${basename}_INCLUDE_DIR}" ${QT_INCLUDES}) + ENDIF (QT_${basename}_INCLUDE_DIR) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED(QT_${basename}_LIBRARY QT_${basename}_LIBRARY_RELEASE QT_${basename}_LIBRARY_DEBUG QT_${basename}_INCLUDE_DIR) + ENDMACRO (_QT4_ADJUST_LIB_VARS) + + + # Set QT_xyz_LIBRARY variable and add + # library include path to QT_INCLUDES + _QT4_ADJUST_LIB_VARS(QtCore) + _QT4_ADJUST_LIB_VARS(QtGui) + _QT4_ADJUST_LIB_VARS(Qt3Support) + _QT4_ADJUST_LIB_VARS(QtAssistant) + _QT4_ADJUST_LIB_VARS(QtAssistantClient) + _QT4_ADJUST_LIB_VARS(QtCLucene) + _QT4_ADJUST_LIB_VARS(QtDBus) + _QT4_ADJUST_LIB_VARS(QtDeclarative) + _QT4_ADJUST_LIB_VARS(QtDesigner) + _QT4_ADJUST_LIB_VARS(QtDesignerComponents) + _QT4_ADJUST_LIB_VARS(QtHelp) + _QT4_ADJUST_LIB_VARS(QtMultimedia) + _QT4_ADJUST_LIB_VARS(QtNetwork) + _QT4_ADJUST_LIB_VARS(QtNsPlugin) + _QT4_ADJUST_LIB_VARS(QtOpenGL) + _QT4_ADJUST_LIB_VARS(QtScript) + _QT4_ADJUST_LIB_VARS(QtScriptTools) + _QT4_ADJUST_LIB_VARS(QtSql) + _QT4_ADJUST_LIB_VARS(QtSvg) + _QT4_ADJUST_LIB_VARS(QtTest) + _QT4_ADJUST_LIB_VARS(QtUiTools) + _QT4_ADJUST_LIB_VARS(QtWebKit) + _QT4_ADJUST_LIB_VARS(QtXml) + _QT4_ADJUST_LIB_VARS(QtXmlPatterns) + _QT4_ADJUST_LIB_VARS(phonon) + + # platform dependent libraries + IF(Q_WS_X11) + _QT4_ADJUST_LIB_VARS(QtMotif) + ENDIF(Q_WS_X11) + IF(WIN32) + _QT4_ADJUST_LIB_VARS(qtmain) + _QT4_ADJUST_LIB_VARS(QAxServer) + _QT4_ADJUST_LIB_VARS(QAxContainer) + ENDIF(WIN32) + + # If Qt is installed as a framework, we need to add QT_QTCORE_LIBRARY here (which + # is the framework directory in that case), since this will make the cmake include_directories() + # command recognize that we need the framework flag with the respective directory (-F) + IF(QT_USE_FRAMEWORKS) + SET(QT_INCLUDES ${QT_INCLUDES} ${QT_QTCORE_LIBRARY} ) + SET(QT_INCLUDE_DIR ${QT_INCLUDE_DIR} ${QT_QTCORE_LIBRARY} ) + ENDIF(QT_USE_FRAMEWORKS) + + + + ####################################### + # + # Check the executables of Qt + # ( moc, uic, rcc ) + # + ####################################### + + + IF(QT_QMAKE_CHANGED) + SET(QT_UIC_EXECUTABLE NOTFOUND) + SET(QT_MOC_EXECUTABLE NOTFOUND) + SET(QT_UIC3_EXECUTABLE NOTFOUND) + SET(QT_RCC_EXECUTABLE NOTFOUND) + SET(QT_DBUSCPP2XML_EXECUTABLE NOTFOUND) + SET(QT_DBUSXML2CPP_EXECUTABLE NOTFOUND) + SET(QT_LUPDATE_EXECUTABLE NOTFOUND) + SET(QT_LRELEASE_EXECUTABLE NOTFOUND) + SET(QT_QCOLLECTIONGENERATOR_EXECUTABLE NOTFOUND) + SET(QT_DESIGNER_EXECUTABLE NOTFOUND) + SET(QT_LINGUIST_EXECUTABLE NOTFOUND) + ENDIF(QT_QMAKE_CHANGED) + + FIND_PROGRAM(QT_MOC_EXECUTABLE + NAMES moc-qt4 moc + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_UIC_EXECUTABLE + NAMES uic-qt4 uic + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_UIC3_EXECUTABLE + NAMES uic3 + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_RCC_EXECUTABLE + NAMES rcc + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DBUSCPP2XML_EXECUTABLE + NAMES qdbuscpp2xml + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DBUSXML2CPP_EXECUTABLE + NAMES qdbusxml2cpp + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LUPDATE_EXECUTABLE + NAMES lupdate-qt4 lupdate + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LRELEASE_EXECUTABLE + NAMES lrelease-qt4 lrelease + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_QCOLLECTIONGENERATOR_EXECUTABLE + NAMES qcollectiongenerator-qt4 qcollectiongenerator + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DESIGNER_EXECUTABLE + NAMES designer-qt4 designer + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LINGUIST_EXECUTABLE + NAMES linguist-qt4 linguist + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + IF (QT_MOC_EXECUTABLE) + SET(QT_WRAP_CPP "YES") + ENDIF (QT_MOC_EXECUTABLE) + + IF (QT_UIC_EXECUTABLE) + SET(QT_WRAP_UI "YES") + ENDIF (QT_UIC_EXECUTABLE) + + + + MARK_AS_ADVANCED( QT_UIC_EXECUTABLE QT_UIC3_EXECUTABLE QT_MOC_EXECUTABLE + QT_RCC_EXECUTABLE QT_DBUSXML2CPP_EXECUTABLE QT_DBUSCPP2XML_EXECUTABLE + QT_LUPDATE_EXECUTABLE QT_LRELEASE_EXECUTABLE QT_QCOLLECTIONGENERATOR_EXECUTABLE + QT_DESIGNER_EXECUTABLE QT_LINGUIST_EXECUTABLE) + + + # get the directory of the current file, used later on in the file + GET_FILENAME_COMPONENT( _qt4_current_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) + + ###################################### + # + # Macros for building Qt files + # + ###################################### + + INCLUDE("${_qt4_current_dir}/Qt4Macros.cmake") + + + ###################################### + # + # decide if Qt got found + # + ###################################### + + # if the includes,libraries,moc,uic and rcc are found then we have it + IF( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + SET( QT4_FOUND "YES" ) + IF( NOT Qt4_FIND_QUIETLY) + MESSAGE(STATUS "Found Qt-Version ${QTVERSION} (using ${QT_QMAKE_EXECUTABLE})") + ENDIF( NOT Qt4_FIND_QUIETLY) + ELSE( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + SET( QT4_FOUND "NO") + SET(QT_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}-NOTFOUND" CACHE FILEPATH "Invalid qmake found" FORCE) + IF( Qt4_FIND_REQUIRED) + IF ( NOT QT_LIBRARY_DIR ) + MESSAGE(STATUS "Qt libraries NOT found!") + ENDIF(NOT QT_LIBRARY_DIR ) + IF ( NOT QT_INCLUDE_DIR ) + MESSAGE(STATUS "Qt includes NOT found!") + ENDIF( NOT QT_INCLUDE_DIR ) + IF ( NOT QT_MOC_EXECUTABLE ) + MESSAGE(STATUS "Qt's moc NOT found!") + ENDIF( NOT QT_MOC_EXECUTABLE ) + IF ( NOT QT_UIC_EXECUTABLE ) + MESSAGE(STATUS "Qt's uic NOT found!") + ENDIF( NOT QT_UIC_EXECUTABLE ) + IF ( NOT QT_RCC_EXECUTABLE ) + MESSAGE(STATUS "Qt's rcc NOT found!") + ENDIF( NOT QT_RCC_EXECUTABLE ) + MESSAGE( FATAL_ERROR "Qt libraries, includes, moc, uic or/and rcc NOT found!") + ENDIF( Qt4_FIND_REQUIRED) + ENDIF( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + + SET(QT_FOUND ${QT4_FOUND}) + + + ############################################### + # + # configuration/system dependent settings + # + ############################################### + + INCLUDE("${_qt4_current_dir}/Qt4ConfigDependentSettings.cmake") + + + ####################################### + # + # compatibility settings + # + ####################################### + # Backwards compatibility for CMake1.4 and 1.2 + SET (QT_MOC_EXE ${QT_MOC_EXECUTABLE} ) + SET (QT_UIC_EXE ${QT_UIC_EXECUTABLE} ) + + SET( QT_QT_LIBRARY "") + +ELSE(QT4_QMAKE_FOUND) + + SET(QT_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}-NOTFOUND" CACHE FILEPATH "Invalid qmake found" FORCE) + + # The code below is overly complex to make sure we do not break compatibility with CMake 2.6.x + # For CMake 2.8, it should be simplified by getting rid of QT4_INSTALLED_VERSION_TOO_OLD and + # QT4_INSTALLED_VERSION_TOO_NEW + IF(Qt4_FIND_REQUIRED) + IF(QT4_INSTALLED_VERSION_TOO_OLD) + IF( Qt4_FIND_VERSION_EXACT ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too old, version ${QT_MIN_VERSION} is required") + ELSE( Qt4_FIND_VERSION_EXACT ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too old, at least version ${QT_MIN_VERSION} is required") + ENDIF( Qt4_FIND_VERSION_EXACT ) + ELSE(QT4_INSTALLED_VERSION_TOO_OLD) + IF( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too new, version ${QT_MIN_VERSION} is required") + ELSE( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + MESSAGE( FATAL_ERROR "Qt qmake not found!") + ENDIF( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + ENDIF(QT4_INSTALLED_VERSION_TOO_OLD) + ELSE(Qt4_FIND_REQUIRED) + IF(QT4_INSTALLED_VERSION_TOO_OLD AND NOT Qt4_FIND_QUIETLY) + MESSAGE(STATUS "The installed Qt version ${QTVERSION} is too old, at least version ${QT_MIN_VERSION} is required") + ENDIF(QT4_INSTALLED_VERSION_TOO_OLD AND NOT Qt4_FIND_QUIETLY) + ENDIF(Qt4_FIND_REQUIRED) + +ENDIF (QT4_QMAKE_FOUND) + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/MacroPushRequiredVars.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/MacroPushRequiredVars.cmake new file mode 100644 index 00000000..650b566e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/MacroPushRequiredVars.cmake @@ -0,0 +1,47 @@ +# this module defines two macros: +# MACRO_PUSH_REQUIRED_VARS() +# and +# MACRO_POP_REQUIRED_VARS() +# use these if you call cmake macros which use +# any of the CMAKE_REQUIRED_XXX variables +# +# Usage: +# MACRO_PUSH_REQUIRED_VARS() +# SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -DSOME_MORE_DEF) +# CHECK_FUNCTION_EXISTS(...) +# MACRO_POP_REQUIRED_VARS() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(MACRO_PUSH_REQUIRED_VARS) + + IF(NOT DEFINED _PUSH_REQUIRED_VARS_COUNTER) + SET(_PUSH_REQUIRED_VARS_COUNTER 0) + ENDIF(NOT DEFINED _PUSH_REQUIRED_VARS_COUNTER) + + MATH(EXPR _PUSH_REQUIRED_VARS_COUNTER "${_PUSH_REQUIRED_VARS_COUNTER}+1") + + SET(_CMAKE_REQUIRED_INCLUDES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_INCLUDES}) + SET(_CMAKE_REQUIRED_DEFINITIONS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_DEFINITIONS}) + SET(_CMAKE_REQUIRED_LIBRARIES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_LIBRARIES}) + SET(_CMAKE_REQUIRED_FLAGS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_FLAGS}) +ENDMACRO(MACRO_PUSH_REQUIRED_VARS) + +MACRO(MACRO_POP_REQUIRED_VARS) + +# don't pop more than we pushed + IF("${_PUSH_REQUIRED_VARS_COUNTER}" GREATER "0") + + SET(CMAKE_REQUIRED_INCLUDES ${_CMAKE_REQUIRED_INCLUDES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_DEFINITIONS ${_CMAKE_REQUIRED_DEFINITIONS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_LIBRARIES ${_CMAKE_REQUIRED_LIBRARIES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_FLAGS ${_CMAKE_REQUIRED_FLAGS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + + MATH(EXPR _PUSH_REQUIRED_VARS_COUNTER "${_PUSH_REQUIRED_VARS_COUNTER}-1") + ENDIF("${_PUSH_REQUIRED_VARS_COUNTER}" GREATER "0") + +ENDMACRO(MACRO_POP_REQUIRED_VARS) + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4ConfigDependentSettings.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4ConfigDependentSettings.cmake new file mode 100644 index 00000000..b5462e7b --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4ConfigDependentSettings.cmake @@ -0,0 +1,384 @@ +# This file is included by FindQt4.cmake, don't include it directly. + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + + +############################################### +# +# configuration/system dependent settings +# +############################################### + +# this check for X11 and threads may not be necessary, since it is not +# contained in the cmake version of FindQt4.cmake: + +# for unix add X11 stuff +IF(UNIX) + # on OS X X11 may not be required + IF (Q_WS_X11) + FIND_PACKAGE(X11 REQUIRED) + ENDIF (Q_WS_X11) + FIND_PACKAGE(Threads) + SET(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +ENDIF(UNIX) + + +# find dependencies for some Qt modules +# when doing builds against a static Qt, they are required +# when doing builds against a shared Qt, they are not required +# if a user needs the dependencies, and they couldn't be found, they can set +# the variables themselves. + +SET(QT_QTGUI_LIB_DEPENDENCIES "") +SET(QT_QTCORE_LIB_DEPENDENCIES "") +SET(QT_QTNETWORK_LIB_DEPENDENCIES "") +SET(QT_QTOPENGL_LIB_DEPENDENCIES "") +SET(QT_QTDBUS_LIB_DEPENDENCIES "") +SET(QT_QTHELP_LIB_DEPENDENCIES ${QT_QTCLUCENE_LIBRARY}) + + +IF(WIN32) + # On Windows, qconfig.pri has "static" for static library builds + IF(QT_CONFIG MATCHES "static") + SET(QT_IS_STATIC 1) + ENDIF(QT_CONFIG MATCHES "static") +ELSE(WIN32) + # On other platforms, check file extension to know if its static + IF(QT_QTCORE_LIBRARY_RELEASE) + GET_FILENAME_COMPONENT(qtcore_lib_ext "${QT_QTCORE_LIBRARY_RELEASE}" EXT) + IF("${qtcore_lib_ext}" STREQUAL "${CMAKE_STATIC_LIBRARY_SUFFIX}") + SET(QT_IS_STATIC 1) + ENDIF("${qtcore_lib_ext}" STREQUAL "${CMAKE_STATIC_LIBRARY_SUFFIX}") + ENDIF(QT_QTCORE_LIBRARY_RELEASE) + IF(QT_QTCORE_LIBRARY_DEBUG) + GET_FILENAME_COMPONENT(qtcore_lib_ext "${QT_QTCORE_LIBRARY_DEBUG}" EXT) + IF(${qtcore_lib_ext} STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET(QT_IS_STATIC 1) + ENDIF(${qtcore_lib_ext} STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + ENDIF(QT_QTCORE_LIBRARY_DEBUG) +ENDIF(WIN32) + +# build using shared Qt needs -DQT_DLL on Windows +IF(WIN32 AND NOT QT_IS_STATIC) + SET(QT_DEFINITIONS ${QT_DEFINITIONS} -DQT_DLL) +ENDIF(WIN32 AND NOT QT_IS_STATIC) + + +# QtOpenGL dependencies +QT_QUERY_QMAKE(QMAKE_LIBS_OPENGL "QMAKE_LIBS_OPENGL") +IF(Q_WS_MAC) +# On the Mac OpenGL is probably frameworks and QMAKE_LIBS_OPENGL can be e.g. "-framework OpenGL -framework AGL". +# The separate_arguments() call in the other branch makes "-framework;-OpenGL;-framework;-lAGL" appear in the +# linker command. So we need to protect the "-framework foo" as non-separatable strings. +# We do this by replacing the space after "-framework" with an underscore, then calling separate_arguments(), +# and then we replace the underscores again with spaces. So we get proper linker commands. Alex + STRING(REGEX REPLACE "-framework +" "-framework_" QMAKE_LIBS_OPENGL "${QMAKE_LIBS_OPENGL}") + SEPARATE_ARGUMENTS(QMAKE_LIBS_OPENGL) + STRING(REGEX REPLACE "-framework_" "-framework " QMAKE_LIBS_OPENGL "${QMAKE_LIBS_OPENGL}") +ELSE(Q_WS_MAC) + SEPARATE_ARGUMENTS(QMAKE_LIBS_OPENGL) +ENDIF(Q_WS_MAC) +SET (QT_QTOPENGL_LIB_DEPENDENCIES ${QT_QTOPENGL_LIB_DEPENDENCIES} ${QMAKE_LIBS_OPENGL}) + + +## system png +IF(QT_QCONFIG MATCHES "system-png") + FIND_LIBRARY(QT_PNG_LIBRARY NAMES png) + MARK_AS_ADVANCED(QT_PNG_LIBRARY) + IF(QT_PNG_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_PNG_LIBRARY}) + ENDIF(QT_PNG_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-png") + + +# for X11, get X11 library directory +IF(Q_WS_X11) + QT_QUERY_QMAKE(QMAKE_LIBDIR_X11 "QMAKE_LIBDIR_X11") +ENDIF(Q_WS_X11) + + +## X11 SM +IF(QT_QCONFIG MATCHES "x11sm") + # ask qmake where the x11 libs are + FIND_LIBRARY(QT_X11_SM_LIBRARY NAMES SM PATHS ${QMAKE_LIBDIR_X11}) + FIND_LIBRARY(QT_X11_ICE_LIBRARY NAMES ICE PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_X11_SM_LIBRARY) + MARK_AS_ADVANCED(QT_X11_ICE_LIBRARY) + IF(QT_X11_SM_LIBRARY AND QT_X11_ICE_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_X11_SM_LIBRARY} ${QT_X11_ICE_LIBRARY}) + ENDIF(QT_X11_SM_LIBRARY AND QT_X11_ICE_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "x11sm") + + +## Xi +IF(QT_QCONFIG MATCHES "tablet") + FIND_LIBRARY(QT_XI_LIBRARY NAMES Xi PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XI_LIBRARY) + IF(QT_XI_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XI_LIBRARY}) + ENDIF(QT_XI_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "tablet") + + +## Xrender +IF(QT_QCONFIG MATCHES "xrender") + FIND_LIBRARY(QT_XRENDER_LIBRARY NAMES Xrender PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XRENDER_LIBRARY) + IF(QT_XRENDER_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XRENDER_LIBRARY}) + ENDIF(QT_XRENDER_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xrender") + + +## Xrandr +IF(QT_QCONFIG MATCHES "xrandr") + FIND_LIBRARY(QT_XRANDR_LIBRARY NAMES Xrandr PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XRANDR_LIBRARY) + IF(QT_XRANDR_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XRANDR_LIBRARY}) + ENDIF(QT_XRANDR_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xrandr") + + +## Xcursor +IF(QT_QCONFIG MATCHES "xcursor") + FIND_LIBRARY(QT_XCURSOR_LIBRARY NAMES Xcursor PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XCURSOR_LIBRARY) + IF(QT_XCURSOR_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XCURSOR_LIBRARY}) + ENDIF(QT_XCURSOR_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xcursor") + + +## Xinerama +IF(QT_QCONFIG MATCHES "xinerama") + FIND_LIBRARY(QT_XINERAMA_LIBRARY NAMES Xinerama PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XINERAMA_LIBRARY) + IF(QT_XINERAMA_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XINERAMA_LIBRARY}) + ENDIF(QT_XINERAMA_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xinerama") + + +## Xfixes +IF(QT_QCONFIG MATCHES "xfixes") + FIND_LIBRARY(QT_XFIXES_LIBRARY NAMES Xfixes PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XFIXES_LIBRARY) + IF(QT_XFIXES_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XFIXES_LIBRARY}) + ENDIF(QT_XFIXES_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xfixes") + + +## system-freetype +IF(QT_QCONFIG MATCHES "system-freetype") + FIND_LIBRARY(QT_FREETYPE_LIBRARY NAMES freetype) + MARK_AS_ADVANCED(QT_FREETYPE_LIBRARY) + IF(QT_FREETYPE_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_FREETYPE_LIBRARY}) + ENDIF(QT_FREETYPE_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-freetype") + + +## fontconfig +IF(QT_QCONFIG MATCHES "fontconfig") + FIND_LIBRARY(QT_FONTCONFIG_LIBRARY NAMES fontconfig) + MARK_AS_ADVANCED(QT_FONTCONFIG_LIBRARY) + IF(QT_FONTCONFIG_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_FONTCONFIG_LIBRARY}) + ENDIF(QT_FONTCONFIG_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "fontconfig") + + +## system-zlib +IF(QT_QCONFIG MATCHES "system-zlib") + FIND_LIBRARY(QT_ZLIB_LIBRARY NAMES z) + MARK_AS_ADVANCED(QT_ZLIB_LIBRARY) + IF(QT_ZLIB_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_ZLIB_LIBRARY}) + ENDIF(QT_ZLIB_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-zlib") + + +## openssl +IF(NOT Q_WS_WIN) + SET(_QT_NEED_OPENSSL 0) + IF(QT_VERSION_MINOR LESS 4 AND QT_QCONFIG MATCHES "openssl") + SET(_QT_NEED_OPENSSL 1) + ENDIF(QT_VERSION_MINOR LESS 4 AND QT_QCONFIG MATCHES "openssl") + IF(QT_VERSION_MINOR GREATER 3 AND QT_QCONFIG MATCHES "openssl-linked") + SET(_QT_NEED_OPENSSL 1) + ENDIF(QT_VERSION_MINOR GREATER 3 AND QT_QCONFIG MATCHES "openssl-linked") + IF(_QT_NEED_OPENSSL) + FIND_PACKAGE(OpenSSL) + IF(OPENSSL_LIBRARIES) + SET(QT_QTNETWORK_LIB_DEPENDENCIES ${QT_QTNETWORK_LIB_DEPENDENCIES} ${OPENSSL_LIBRARIES}) + ENDIF(OPENSSL_LIBRARIES) + ENDIF(_QT_NEED_OPENSSL) +ENDIF(NOT Q_WS_WIN) + + +## dbus +IF(QT_QCONFIG MATCHES "dbus") + + # if the dbus library isn't found, we'll assume its not required to build + # shared Qt on Linux doesn't require it + IF(NOT QT_DBUS_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L dbus-1 + OUTPUT_VARIABLE _dbus_query_output + RESULT_VARIABLE _dbus_result + ERROR_VARIABLE _dbus_query_output ) + + IF(_dbus_result MATCHES 0) + STRING(REPLACE "-L" "" _dbus_query_output "${_dbus_query_output}") + SEPARATE_ARGUMENTS(_dbus_query_output) + ELSE(_dbus_result MATCHES 0) + SET(_dbus_query_output) + ENDIF(_dbus_result MATCHES 0) + + FIND_LIBRARY(QT_DBUS_LIBRARY NAMES dbus-1 PATHS ${_dbus_query_output} ) + + IF(QT_DBUS_LIBRARY) + SET(QT_QTDBUS_LIB_DEPENDENCIES ${QT_QTDBUS_LIB_DEPENDENCIES} ${QT_DBUS_LIBRARY}) + ENDIF(QT_DBUS_LIBRARY) + + MARK_AS_ADVANCED(QT_DBUS_LIBRARY) + ENDIF(NOT QT_DBUS_LIBRARY) + +ENDIF(QT_QCONFIG MATCHES "dbus") + + +## glib +IF(QT_QCONFIG MATCHES "glib") + + # if the glib libraries aren't found, we'll assume its not required to build + # shared Qt on Linux doesn't require it + + # Qt 4.2.0+ uses glib-2.0 + IF(NOT QT_GLIB_LIBRARY OR NOT QT_GTHREAD_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L glib-2.0 gthread-2.0 + OUTPUT_VARIABLE _glib_query_output + RESULT_VARIABLE _glib_result + ERROR_VARIABLE _glib_query_output ) + + IF(_glib_result MATCHES 0) + STRING(REPLACE "-L" "" _glib_query_output "${_glib_query_output}") + SEPARATE_ARGUMENTS(_glib_query_output) + ELSE(_glib_result MATCHES 0) + SET(_glib_query_output) + ENDIF(_glib_result MATCHES 0) + + FIND_LIBRARY(QT_GLIB_LIBRARY NAMES glib-2.0 PATHS ${_glib_query_output} ) + FIND_LIBRARY(QT_GTHREAD_LIBRARY NAMES gthread-2.0 PATHS ${_glib_query_output} ) + + MARK_AS_ADVANCED(QT_GLIB_LIBRARY) + MARK_AS_ADVANCED(QT_GTHREAD_LIBRARY) + ENDIF(NOT QT_GLIB_LIBRARY OR NOT QT_GTHREAD_LIBRARY) + + IF(QT_GLIB_LIBRARY AND QT_GTHREAD_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} + ${QT_GTHREAD_LIBRARY} ${QT_GLIB_LIBRARY}) + ENDIF(QT_GLIB_LIBRARY AND QT_GTHREAD_LIBRARY) + + + # Qt 4.5+ also links to gobject-2.0 + IF(QT_VERSION_MINOR GREATER 4) + IF(NOT QT_GOBJECT_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L gobject-2.0 + OUTPUT_VARIABLE _glib_query_output + RESULT_VARIABLE _glib_result + ERROR_VARIABLE _glib_query_output ) + + IF(_glib_result MATCHES 0) + STRING(REPLACE "-L" "" _glib_query_output "${_glib_query_output}") + SEPARATE_ARGUMENTS(_glib_query_output) + ELSE(_glib_result MATCHES 0) + SET(_glib_query_output) + ENDIF(_glib_result MATCHES 0) + + FIND_LIBRARY(QT_GOBJECT_LIBRARY NAMES gobject-2.0 PATHS ${_glib_query_output} ) + + MARK_AS_ADVANCED(QT_GOBJECT_LIBRARY) + ENDIF(NOT QT_GOBJECT_LIBRARY) + + IF(QT_GOBJECT_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} + ${QT_GOBJECT_LIBRARY}) + ENDIF(QT_GOBJECT_LIBRARY) + ENDIF(QT_VERSION_MINOR GREATER 4) + +ENDIF(QT_QCONFIG MATCHES "glib") + + +## clock-monotonic, just see if we need to link with rt +IF(QT_QCONFIG MATCHES "clock-monotonic") + SET(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) + SET(CMAKE_REQUIRED_LIBRARIES rt) + CHECK_SYMBOL_EXISTS(_POSIX_TIMERS "unistd.h;time.h" QT_POSIX_TIMERS) + SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_SAVE}) + IF(QT_POSIX_TIMERS) + FIND_LIBRARY(QT_RT_LIBRARY NAMES rt) + MARK_AS_ADVANCED(QT_RT_LIBRARY) + IF(QT_RT_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_RT_LIBRARY}) + ENDIF(QT_RT_LIBRARY) + ENDIF(QT_POSIX_TIMERS) +ENDIF(QT_QCONFIG MATCHES "clock-monotonic") + + +IF(Q_WS_X11) + # X11 libraries Qt absolutely depends on + QT_QUERY_QMAKE(QT_LIBS_X11 "QMAKE_LIBS_X11") + SEPARATE_ARGUMENTS(QT_LIBS_X11) + FOREACH(QT_X11_LIB ${QT_LIBS_X11}) + STRING(REGEX REPLACE "-l" "" QT_X11_LIB "${QT_X11_LIB}") + SET(QT_TMP_STR "QT_X11_${QT_X11_LIB}_LIBRARY") + FIND_LIBRARY(${QT_TMP_STR} NAMES "${QT_X11_LIB}" PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(${QT_TMP_STR}) + IF(${QT_TMP_STR}) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${${QT_TMP_STR}}) + ENDIF(${QT_TMP_STR}) + ENDFOREACH(QT_X11_LIB) + + QT_QUERY_QMAKE(QT_LIBS_THREAD "QMAKE_LIBS_THREAD") + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_LIBS_THREAD}) + + QT_QUERY_QMAKE(QMAKE_LIBS_DYNLOAD "QMAKE_LIBS_DYNLOAD") + SET (QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QMAKE_LIBS_DYNLOAD}) + +ENDIF(Q_WS_X11) + + +IF(Q_WS_WIN) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ws2_32) +ENDIF(Q_WS_WIN) + + +IF(Q_WS_MAC) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework Carbon") + + # Qt 4.0, 4.1, 4.2 use QuickTime + IF(QT_VERSION_MINOR LESS 3) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework QuickTime") + ENDIF(QT_VERSION_MINOR LESS 3) + + # Qt 4.2+ use AppKit + IF(QT_VERSION_MINOR GREATER 1) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework AppKit") + ENDIF(QT_VERSION_MINOR GREATER 1) + + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} "-framework ApplicationServices") +ENDIF(Q_WS_MAC) + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4Macros.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4Macros.cmake new file mode 100644 index 00000000..a5142511 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/cmake/Qt4Macros.cmake @@ -0,0 +1,414 @@ +# This file is included by FindQt4.cmake, don't include it directly. + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + + +###################################### +# +# Macros for building Qt files +# +###################################### + + +MACRO (QT4_EXTRACT_OPTIONS _qt4_files _qt4_options) + SET(${_qt4_files}) + SET(${_qt4_options}) + SET(_QT4_DOING_OPTIONS FALSE) + FOREACH(_currentArg ${ARGN}) + IF ("${_currentArg}" STREQUAL "OPTIONS") + SET(_QT4_DOING_OPTIONS TRUE) + ELSE ("${_currentArg}" STREQUAL "OPTIONS") + IF(_QT4_DOING_OPTIONS) + LIST(APPEND ${_qt4_options} "${_currentArg}") + ELSE(_QT4_DOING_OPTIONS) + LIST(APPEND ${_qt4_files} "${_currentArg}") + ENDIF(_QT4_DOING_OPTIONS) + ENDIF ("${_currentArg}" STREQUAL "OPTIONS") + ENDFOREACH(_currentArg) +ENDMACRO (QT4_EXTRACT_OPTIONS) + + +# macro used to create the names of output files preserving relative dirs +MACRO (QT4_MAKE_OUTPUT_FILE infile prefix ext outfile ) + STRING(LENGTH ${CMAKE_CURRENT_BINARY_DIR} _binlength) + STRING(LENGTH ${infile} _infileLength) + SET(_checkinfile ${CMAKE_CURRENT_SOURCE_DIR}) + IF(_infileLength GREATER _binlength) + STRING(SUBSTRING "${infile}" 0 ${_binlength} _checkinfile) + IF(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${infile}) + ELSE(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${infile}) + ENDIF(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + ELSE(_infileLength GREATER _binlength) + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${infile}) + ENDIF(_infileLength GREATER _binlength) + IF(WIN32 AND rel MATCHES "^[a-zA-Z]:") # absolute path + STRING(REGEX REPLACE "^([a-zA-Z]):(.*)$" "\\1_\\2" rel "${rel}") + ENDIF(WIN32 AND rel MATCHES "^[a-zA-Z]:") + SET(_outfile "${CMAKE_CURRENT_BINARY_DIR}/${rel}") + STRING(REPLACE ".." "__" _outfile ${_outfile}) + GET_FILENAME_COMPONENT(outpath ${_outfile} PATH) + GET_FILENAME_COMPONENT(_outfile ${_outfile} NAME_WE) + FILE(MAKE_DIRECTORY ${outpath}) + SET(${outfile} ${outpath}/${prefix}${_outfile}.${ext}) +ENDMACRO (QT4_MAKE_OUTPUT_FILE ) + + +MACRO (QT4_GET_MOC_FLAGS _moc_flags) + SET(${_moc_flags}) + GET_DIRECTORY_PROPERTY(_inc_DIRS INCLUDE_DIRECTORIES) + + FOREACH(_current ${_inc_DIRS}) + IF("${_current}" MATCHES ".framework/?$") + STRING(REGEX REPLACE "/[^/]+.framework" "" framework_path "${_current}") + SET(${_moc_flags} ${${_moc_flags}} "-F${framework_path}") + ELSE("${_current}" MATCHES ".framework/?$") + SET(${_moc_flags} ${${_moc_flags}} "-I${_current}") + ENDIF("${_current}" MATCHES ".framework/?$") + ENDFOREACH(_current ${_inc_DIRS}) + + GET_DIRECTORY_PROPERTY(_defines COMPILE_DEFINITIONS) + FOREACH(_current ${_defines}) + SET(${_moc_flags} ${${_moc_flags}} "-D${_current}") + ENDFOREACH(_current ${_defines}) + + IF(Q_WS_WIN) + SET(${_moc_flags} ${${_moc_flags}} -DWIN32) + ENDIF(Q_WS_WIN) + +ENDMACRO(QT4_GET_MOC_FLAGS) + + +# helper macro to set up a moc rule +MACRO (QT4_CREATE_MOC_COMMAND infile outfile moc_flags moc_options) + # For Windows, create a parameters file to work around command line length limit + IF (WIN32) + # Pass the parameters in a file. Set the working directory to + # be that containing the parameters file and reference it by + # just the file name. This is necessary because the moc tool on + # MinGW builds does not seem to handle spaces in the path to the + # file given with the @ syntax. + GET_FILENAME_COMPONENT(_moc_outfile_name "${outfile}" NAME) + GET_FILENAME_COMPONENT(_moc_outfile_dir "${outfile}" PATH) + IF(_moc_outfile_dir) + SET(_moc_working_dir WORKING_DIRECTORY ${_moc_outfile_dir}) + ENDIF(_moc_outfile_dir) + SET (_moc_parameters_file ${outfile}_parameters) + SET (_moc_parameters ${moc_flags} ${moc_options} -o "${outfile}" "${infile}") + FILE (REMOVE ${_moc_parameters_file}) + FOREACH(arg ${_moc_parameters}) + FILE (APPEND ${_moc_parameters_file} "${arg}\n") + ENDFOREACH(arg) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_MOC_EXECUTABLE} @${_moc_outfile_name}_parameters + DEPENDS ${infile} + ${_moc_working_dir} + VERBATIM) + ELSE (WIN32) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_MOC_EXECUTABLE} + ARGS ${moc_flags} ${moc_options} -o ${outfile} ${infile} + DEPENDS ${infile}) + ENDIF (WIN32) +ENDMACRO (QT4_CREATE_MOC_COMMAND) + + +MACRO (QT4_GENERATE_MOC infile outfile ) +# get include dirs and flags + QT4_GET_MOC_FLAGS(moc_flags) + GET_FILENAME_COMPONENT(abs_infile ${infile} ABSOLUTE) + QT4_CREATE_MOC_COMMAND(${abs_infile} ${outfile} "${moc_flags}" "") + SET_SOURCE_FILES_PROPERTIES(${outfile} PROPERTIES SKIP_AUTOMOC TRUE) # do not run automoc on this file + + MACRO_ADD_FILE_DEPENDENCIES(${abs_infile} ${outfile}) +ENDMACRO (QT4_GENERATE_MOC) + + +# QT4_WRAP_CPP(outfiles inputfile ... ) + +MACRO (QT4_WRAP_CPP outfiles ) + # get include dirs + QT4_GET_MOC_FLAGS(moc_flags) + QT4_EXTRACT_OPTIONS(moc_files moc_options ${ARGN}) + + FOREACH (it ${moc_files}) + GET_FILENAME_COMPONENT(it ${it} ABSOLUTE) + QT4_MAKE_OUTPUT_FILE(${it} moc_ cxx outfile) + QT4_CREATE_MOC_COMMAND(${it} ${outfile} "${moc_flags}" "${moc_options}") + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH(it) + +ENDMACRO (QT4_WRAP_CPP) + + +# QT4_WRAP_UI(outfiles inputfile ... ) + +MACRO (QT4_WRAP_UI outfiles ) + QT4_EXTRACT_OPTIONS(ui_files ui_options ${ARGN}) + + FOREACH (it ${ui_files}) + GET_FILENAME_COMPONENT(outfile ${it} NAME_WE) + GET_FILENAME_COMPONENT(infile ${it} ABSOLUTE) + SET(outfile ${CMAKE_CURRENT_BINARY_DIR}/ui_${outfile}.h) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_UIC_EXECUTABLE} + ARGS ${ui_options} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile}) + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH (it) + +ENDMACRO (QT4_WRAP_UI) + + +# QT4_ADD_RESOURCES(outfiles inputfile ... ) + +MACRO (QT4_ADD_RESOURCES outfiles ) + QT4_EXTRACT_OPTIONS(rcc_files rcc_options ${ARGN}) + + FOREACH (it ${rcc_files}) + GET_FILENAME_COMPONENT(outfilename ${it} NAME_WE) + GET_FILENAME_COMPONENT(infile ${it} ABSOLUTE) + GET_FILENAME_COMPONENT(rc_path ${infile} PATH) + SET(outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${outfilename}.cxx) + # parse file for dependencies + # all files are absolute paths or relative to the location of the qrc file + FILE(READ "${infile}" _RC_FILE_CONTENTS) + STRING(REGEX MATCHALL "]*>" "" _RC_FILE "${_RC_FILE}") + STRING(REGEX MATCH "^/|([A-Za-z]:/)" _ABS_PATH_INDICATOR "${_RC_FILE}") + IF(NOT _ABS_PATH_INDICATOR) + SET(_RC_FILE "${rc_path}/${_RC_FILE}") + ENDIF(NOT _ABS_PATH_INDICATOR) + SET(_RC_DEPENDS ${_RC_DEPENDS} "${_RC_FILE}") + ENDFOREACH(_RC_FILE) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_RCC_EXECUTABLE} + ARGS ${rcc_options} -name ${outfilename} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile} + DEPENDS ${_RC_DEPENDS}) + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH (it) + +ENDMACRO (QT4_ADD_RESOURCES) + + +MACRO(QT4_ADD_DBUS_INTERFACE _sources _interface _basename) + GET_FILENAME_COMPONENT(_infile ${_interface} ABSOLUTE) + SET(_header ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.h) + SET(_impl ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.cpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.moc) + + GET_SOURCE_FILE_PROPERTY(_nonamespace ${_interface} NO_NAMESPACE) + IF ( _nonamespace ) + SET(_params -N -m) + ELSE ( _nonamespace ) + SET(_params -m) + ENDIF ( _nonamespace ) + + GET_SOURCE_FILE_PROPERTY(_classname ${_interface} CLASSNAME) + IF ( _classname ) + SET(_params ${_params} -c ${_classname}) + ENDIF ( _classname ) + + GET_SOURCE_FILE_PROPERTY(_include ${_interface} INCLUDE) + IF ( _include ) + SET(_params ${_params} -i ${_include}) + ENDIF ( _include ) + + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} ${_params} -p ${_basename} ${_infile} + DEPENDS ${_infile}) + + SET_SOURCE_FILES_PROPERTIES(${_impl} PROPERTIES SKIP_AUTOMOC TRUE) + + QT4_GENERATE_MOC(${_header} ${_moc}) + + SET(${_sources} ${${_sources}} ${_impl} ${_header} ${_moc}) + MACRO_ADD_FILE_DEPENDENCIES(${_impl} ${_moc}) + +ENDMACRO(QT4_ADD_DBUS_INTERFACE) + + +MACRO(QT4_ADD_DBUS_INTERFACES _sources) + FOREACH (_current_FILE ${ARGN}) + GET_FILENAME_COMPONENT(_infile ${_current_FILE} ABSOLUTE) + # get the part before the ".xml" suffix + STRING(REGEX REPLACE "(.*[/\\.])?([^\\.]+)\\.xml" "\\2" _basename ${_current_FILE}) + STRING(TOLOWER ${_basename} _basename) + QT4_ADD_DBUS_INTERFACE(${_sources} ${_infile} ${_basename}interface) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_ADD_DBUS_INTERFACES) + + +MACRO(QT4_GENERATE_DBUS_INTERFACE _header) # _customName OPTIONS -some -options ) + QT4_EXTRACT_OPTIONS(_customName _qt4_dbus_options ${ARGN}) + + GET_FILENAME_COMPONENT(_in_file ${_header} ABSOLUTE) + GET_FILENAME_COMPONENT(_basename ${_header} NAME_WE) + + IF (_customName) + SET(_target ${CMAKE_CURRENT_BINARY_DIR}/${_customName}) + ELSE (_customName) + SET(_target ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.xml) + ENDIF (_customName) + + ADD_CUSTOM_COMMAND(OUTPUT ${_target} + COMMAND ${QT_DBUSCPP2XML_EXECUTABLE} ${_qt4_dbus_options} ${_in_file} -o ${_target} + DEPENDS ${_in_file} + ) +ENDMACRO(QT4_GENERATE_DBUS_INTERFACE) + + +MACRO(QT4_ADD_DBUS_ADAPTOR _sources _xml_file _include _parentClass) # _optionalBasename _optionalClassName) + GET_FILENAME_COMPONENT(_infile ${_xml_file} ABSOLUTE) + + SET(_optionalBasename "${ARGV4}") + IF (_optionalBasename) + SET(_basename ${_optionalBasename} ) + ELSE (_optionalBasename) + STRING(REGEX REPLACE "(.*[/\\.])?([^\\.]+)\\.xml" "\\2adaptor" _basename ${_infile}) + STRING(TOLOWER ${_basename} _basename) + ENDIF (_optionalBasename) + + SET(_optionalClassName "${ARGV5}") + SET(_header ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.h) + SET(_impl ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.cpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.moc) + + IF(_optionalClassName) + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} -m -a ${_basename} -c ${_optionalClassName} -i ${_include} -l ${_parentClass} ${_infile} + DEPENDS ${_infile} + ) + ELSE(_optionalClassName) + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} -m -a ${_basename} -i ${_include} -l ${_parentClass} ${_infile} + DEPENDS ${_infile} + ) + ENDIF(_optionalClassName) + + QT4_GENERATE_MOC(${_header} ${_moc}) + SET_SOURCE_FILES_PROPERTIES(${_impl} PROPERTIES SKIP_AUTOMOC TRUE) + MACRO_ADD_FILE_DEPENDENCIES(${_impl} ${_moc}) + + SET(${_sources} ${${_sources}} ${_impl} ${_header} ${_moc}) +ENDMACRO(QT4_ADD_DBUS_ADAPTOR) + + +MACRO(QT4_AUTOMOC) + QT4_GET_MOC_FLAGS(_moc_INCS) + + SET(_matching_FILES ) + FOREACH (_current_FILE ${ARGN}) + + GET_FILENAME_COMPONENT(_abs_FILE ${_current_FILE} ABSOLUTE) + # if "SKIP_AUTOMOC" is set to true, we will not handle this file here. + # This is required to make uic work correctly: + # we need to add generated .cpp files to the sources (to compile them), + # but we cannot let automoc handle them, as the .cpp files don't exist yet when + # cmake is run for the very first time on them -> however the .cpp files might + # exist at a later run. at that time we need to skip them, so that we don't add two + # different rules for the same moc file + GET_SOURCE_FILE_PROPERTY(_skip ${_abs_FILE} SKIP_AUTOMOC) + + IF ( NOT _skip AND EXISTS ${_abs_FILE} ) + + FILE(READ ${_abs_FILE} _contents) + + GET_FILENAME_COMPONENT(_abs_PATH ${_abs_FILE} PATH) + + STRING(REGEX MATCHALL "# *include +[^ ]+\\.moc[\">]" _match "${_contents}") + IF(_match) + FOREACH (_current_MOC_INC ${_match}) + STRING(REGEX MATCH "[^ <\"]+\\.moc" _current_MOC "${_current_MOC_INC}") + + GET_FILENAME_COMPONENT(_basename ${_current_MOC} NAME_WE) + IF(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_header ${_abs_PATH}/${_basename}.hpp) + ELSE(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_header ${_abs_PATH}/${_basename}.h) + ENDIF(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_current_MOC}) + QT4_CREATE_MOC_COMMAND(${_header} ${_moc} "${_moc_INCS}" "") + MACRO_ADD_FILE_DEPENDENCIES(${_abs_FILE} ${_moc}) + ENDFOREACH (_current_MOC_INC) + ENDIF(_match) + ENDIF ( NOT _skip AND EXISTS ${_abs_FILE} ) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_AUTOMOC) + + +MACRO(QT4_CREATE_TRANSLATION _qm_files) + QT4_EXTRACT_OPTIONS(_lupdate_files _lupdate_options ${ARGN}) + SET(_my_sources) + SET(_my_dirs) + SET(_my_tsfiles) + SET(_ts_pro) + FOREACH (_file ${_lupdate_files}) + GET_FILENAME_COMPONENT(_ext ${_file} EXT) + GET_FILENAME_COMPONENT(_abs_FILE ${_file} ABSOLUTE) + IF(_ext MATCHES "ts") + LIST(APPEND _my_tsfiles ${_abs_FILE}) + ELSE(_ext MATCHES "ts") + IF(NOT _ext) + LIST(APPEND _my_dirs ${_abs_FILE}) + ELSE(NOT _ext) + LIST(APPEND _my_sources ${_abs_FILE}) + ENDIF(NOT _ext) + ENDIF(_ext MATCHES "ts") + ENDFOREACH(_file) + FOREACH(_ts_file ${_my_tsfiles}) + IF(_my_sources) + # make a .pro file to call lupdate on, so we don't make our commands too + # long for some systems + GET_FILENAME_COMPONENT(_ts_name ${_ts_file} NAME_WE) + SET(_ts_pro ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lupdate.pro) + SET(_pro_srcs) + FOREACH(_pro_src ${_my_sources}) + SET(_pro_srcs "${_pro_srcs} \"${_pro_src}\"") + ENDFOREACH(_pro_src ${_my_sources}) + FILE(WRITE ${_ts_pro} "SOURCES = ${_pro_srcs}") + ENDIF(_my_sources) + ADD_CUSTOM_COMMAND(OUTPUT ${_ts_file} + COMMAND ${QT_LUPDATE_EXECUTABLE} + ARGS ${_lupdate_options} ${_ts_pro} ${_my_dirs} -ts ${_ts_file} + DEPENDS ${_my_sources} ${_ts_pro}) + ENDFOREACH(_ts_file) + QT4_ADD_TRANSLATION(${_qm_files} ${_my_tsfiles}) +ENDMACRO(QT4_CREATE_TRANSLATION) + + +MACRO(QT4_ADD_TRANSLATION _qm_files) + FOREACH (_current_FILE ${ARGN}) + GET_FILENAME_COMPONENT(_abs_FILE ${_current_FILE} ABSOLUTE) + GET_FILENAME_COMPONENT(qm ${_abs_FILE} NAME_WE) + GET_SOURCE_FILE_PROPERTY(output_location ${_abs_FILE} OUTPUT_LOCATION) + IF(output_location) + FILE(MAKE_DIRECTORY "${output_location}") + SET(qm "${output_location}/${qm}.qm") + ELSE(output_location) + SET(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm") + ENDIF(output_location) + + ADD_CUSTOM_COMMAND(OUTPUT ${qm} + COMMAND ${QT_LRELEASE_EXECUTABLE} + ARGS ${_abs_FILE} -qm ${qm} + DEPENDS ${_abs_FILE} + ) + SET(${_qm_files} ${${_qm_files}} ${qm}) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_ADD_TRANSLATION) diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/currentindicator.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/currentindicator.png new file mode 100644 index 0000000000000000000000000000000000000000..69410e200146d6490d7f0d482fb419e2905b49d9 GIT binary patch literal 902 zcmV;119|+3P)MzCV_|S* zE^l&Yo9;Xs00098Nkl3%S#(U90zb8MW}izQWPu{MCe7qi#@c5V2C0Vtwmdn zG}ZQCZ4+aR8(%RAiFy2HH(6t%=KVC;n0WD0sMzAge?Sm~_NZr1Y7fEb%qp!$qkQ?n zFu&i2fgN^AaFLiwUp_RI7VF;FkW@)pJBPaFdhBnY$KEy9IV5S7RNcd5Ew4>-_z0gt z2`W;f@CgQ?$0Tbh%G#^D74P8-Ffb1mu!Dn?g{&F)3JT~}v{&E4qz!$B5zvuA9()jn z1SCmC$a)d1Fip;XYmhb+FllAKUIj+r2}oXM?cXX2ZZN?Z4C>upwn6#@k$RY1ew05$YBUouI>h{1$b*$5XE6D!Q+euN9K7Bp(;s}Px@ zX$D-bB`@b^18l%qB&6f_c-{OwQZ$Kd>>Y>P%YPQI_5OH0PFCG7GNU@kx>lyNxa7X* zhFE`$GmneRH6KSSOZt>%@NP6Nn@M}Tapaj7S=dHyhT~VKGnyB7pl6e5)*p5zAz>5Q zrj>r)kP8JT*VIpMN3)N#VMi#yC(#;gT1B=)SPJ4O$lJmbfzj%l=)(gIZ;b`q2}JL5 z=Ys7`QdWLlj$>GLGm6i~J|EwV#-iZYTU>+={>lga|rrx%u&lN_C( zlZSGVDA2~6|l zNl$c1E*NtOi1cV2$0N9mGMf5>DM4iGAfPDk4C!LbV_Y&($@owt%BRsf=}~wvh3l{U zIQfok^99R78bucEQO2#Q)mm(cusg$M$v%bK=6uKXzq|ZIxuq8w9biLCC~C;q;z5r< z5Y0Ml=}(lnyznilIamH-a*izIAWL9$G3bdbrnwx@Y&d>1=Sp02+sjpk+fMaA3}lE> zltWpx0W?1ir+VB*`bFQ#TC6&=AEAAA*ZSSEYu#s$(3u^paP8gCD@`4i;bE-CGo*&e cy6)!8U$g)Qi{|1U&;S4c07*qoM6N<$f-C2f$p8QV literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dividing-line-horizontal.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dividing-line-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..f8472ddd3474082152a3b7334b12b451cbb535dd GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^AT|>R3y?gW(Hafn6gzo_aBzgaaRUi*7I;J!GcfS^ z0`VNid*9}S0|k{mT^vI=t|$MgZ)@b_kZk-Rf4JV@-+3eE6X^`eYzp!njZF;CySdZD TtSkC~${9Re{an^LB{Ts5r-nbtE2^2edhH!9%zHtKyau#?*76VmZ z2Vq7hjoB4ILCF%=h?3y^w370~qEv?R@^Zb*yzJuS#DY}4{G#;P?`))iiqt(_978y+ zC;zEWJ612o=ElaRaPY*5LkCzA4ILu?I~@7{-{9K*|2zzL=W=#(R{sOJgTd3)&t;uc GLK6U;H8PX{ literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.cpp new file mode 100644 index 00000000..16ed25b2 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.cpp @@ -0,0 +1,966 @@ +/* + Copyright (c) 2009 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 "dynamictreemodel.h" + +#include +#include +#include +#include + +#include + +#include + +// If DUMPTREE is defined, ModelInsertCommand dumps the tree of what it is inserting. +// #define DUMPTREE +#ifdef DUMPTREE +#include +#endif + +DynamicTreeModel::DynamicTreeModel(QObject *parent) + : QAbstractItemModel(parent), + nextId(1) +{ +} + +QModelIndex DynamicTreeModel::index(int row, int column, const QModelIndex &parent) const +{ +// if (column != 0) +// return QModelIndex(); + + if ( column < 0 || row < 0 ) + return QModelIndex(); + + QList > childIdColumns = m_childItems.value(parent.internalId()); + + + if (childIdColumns.size() == 0) + return QModelIndex(); + + if (column >= childIdColumns.size()) + return QModelIndex(); + + QList rowIds = childIdColumns.at(column); + + if ( row >= rowIds.size()) + return QModelIndex(); + + qint64 id = rowIds.at(row); + + return createIndex(row, column, reinterpret_cast(id)); + +} + +qint64 DynamicTreeModel::findParentId(qint64 searchId) const +{ + if (searchId <= 0) + return -1; + + QHashIterator > > i(m_childItems); + while (i.hasNext()) + { + i.next(); + QListIterator > j(i.value()); + while (j.hasNext()) + { + QList l = j.next(); + if (l.contains(searchId)) + { + return i.key(); + } + } + } + return -1; +} + +QModelIndex DynamicTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + qint64 searchId = index.internalId(); + qint64 parentId = findParentId(searchId); + // Will never happen for valid index, but what the hey... + if (parentId <= 0) + return QModelIndex(); + + qint64 grandParentId = findParentId(parentId); + if (grandParentId < 0) + grandParentId = 0; + + int column = 0; + QList childList = m_childItems.value(grandParentId).at(column); + + int row = childList.indexOf(parentId); + + return createIndex(row, column, reinterpret_cast(parentId)); + +} + +int DynamicTreeModel::rowCount(const QModelIndex &index ) const +{ + QList > cols = m_childItems.value(index.internalId()); + + if (cols.size() == 0 ) + return 0; + + if (index.column() > 0) + return 0; + + return cols.at(0).size(); +} + +int DynamicTreeModel::columnCount(const QModelIndex &index ) const +{ +// Q_UNUSED(index); + return m_childItems.value(index.internalId()).size(); +} + +static const char * const accounts[] = { + "GMail", + "Hotmail", + "KDE", + "Work", + "Squash", + "Cycling", + "Climbing", + "Mailing Lists" +}; + +QVariant DynamicTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if ( DynamicTreeModelId == role ) + return index.internalId(); + + if (Qt::DisplayRole == role || Qt::EditRole == role) + { + if (!index.parent().isValid()) + { + if ( index.row() > ( sizeof accounts / sizeof *accounts ) - 1) + return "Account" + m_items.value(index.internalId()); + return QString(accounts[index.row()]); + } + if (index.row() == 1 && !index.parent().parent().isValid()) + return "Inbox"; + + return "Subfolder" + m_items.value(index.internalId()); + } + return QVariant(); +} + + +bool DynamicTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole) + { + m_items[index.internalId()] = value.toString(); + dataChanged(index, index); + return true; + } + + return QAbstractItemModel::setData(index, value, role); +} + +void DynamicTreeModel::clear() +{ + beginResetModel(); + m_items.clear(); + m_childItems.clear(); + nextId = 1; + endResetModel(); +} + + +bool DynamicTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int _column, const QModelIndex& parent) +{ + Q_UNUSED(action); + Q_UNUSED(_column); + QByteArray encoded = data->data(mimeTypes().first()); + + QHash > movedItems; + bool ok; + qint64 id; + int _row; + static const int column = 0; + QHash > >::const_iterator it; + foreach(const QByteArray &ba, encoded.split('\0')) + { + id = ba.toInt(&ok); + Q_ASSERT(ok); + + _row = -1; + for (it = m_childItems.constBegin(); it != m_childItems.constEnd(); ++it) + { + _row = it.value().first().indexOf(id); + if (_row < 0) + continue; + movedItems[createIndex(_row, column, reinterpret_cast(id)).parent()].append(_row); + break; + } + Q_ASSERT(_row >= 0); + if (_row < 0) + return false; + } + + const int destRow = row < 0 ? 0 : row; + const QList destPath = indexToPath(parent); + + QList srcPath; + QModelIndex srcParent; + QHash >::iterator src_parent_it = movedItems.begin(); + int startRow = 0; + int endRow = 0; + int nextMovedRow = 0; + + QList rowsMoved; + QList::iterator src_row_it; + QList::iterator rows_moved_end; + QList moveCommands; + + for ( ; src_parent_it != movedItems.end(); ++src_parent_it) + { + srcParent = src_parent_it.key(); + srcPath = indexToPath(srcParent); + + rowsMoved = src_parent_it.value(); + qSort(rowsMoved); + src_row_it = rowsMoved.begin(); + rows_moved_end = rowsMoved.end(); + startRow = *src_row_it; + endRow = startRow; + ++src_row_it; + + if (src_row_it == rows_moved_end) + { + moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); + continue; + } + + for ( ; src_row_it != rows_moved_end; ++src_row_it) + { + nextMovedRow = *src_row_it; + + if (nextMovedRow == endRow + 1) + { + ++endRow; + } else { + Q_ASSERT(nextMovedRow > endRow + 1); + moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); + startRow = nextMovedRow; + endRow = nextMovedRow; + + if ((src_row_it + 1) == rows_moved_end) + moveCommands.prepend(getMoveCommand(srcPath, startRow, endRow)); + + } + } + } + + QPersistentModelIndex destParent = parent; + QPersistentModelIndex destRowIndex = index(destRow, column, parent); + + ModelMoveCommand *firstCommand = moveCommands.takeFirst(); + firstCommand->setDestAncestors(indexToPath(parent)); + firstCommand->setDestRow(destRow); + firstCommand->doCommand(); + + if (!destRowIndex.isValid()) + destRowIndex = index(destRow, column, parent); + + Q_ASSERT(destRowIndex.isValid()); + int offset = firstCommand->endRow() - firstCommand->startRow() + 1; + foreach(ModelMoveCommand *moveCommand, moveCommands) + { + moveCommand->setDestAncestors(indexToPath(destParent)); + moveCommand->setDestRow(destRowIndex.row() + offset); + moveCommand->doCommand(); + offset = moveCommand->endRow() - moveCommand->startRow() + 1; + } + + return false; +} + +ModelMoveCommand* DynamicTreeModel::getMoveCommand(const QList &srcPath, int startRow, int endRow) +{ + ModelMoveCommand *moveCommand = new ModelMoveCommand(this, this); + moveCommand->setAncestorRowNumbers(srcPath); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + return moveCommand; +} + +Qt::ItemFlags DynamicTreeModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (index.isValid()) + return flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; + return flags; +} + +Qt::DropActions DynamicTreeModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +QStringList DynamicTreeModel::mimeTypes() const +{ + QStringList types; + types << QLatin1String("application/x-dynamictreemodel-itemlist"); + return types; +} + +QMimeData* DynamicTreeModel::mimeData(const QModelIndexList& indexes) const +{ + QMimeData *data = new QMimeData(); + QByteArray itemData; + QModelIndexList::const_iterator it = indexes.begin(); + const QModelIndexList::const_iterator end = indexes.end(); + while(it != end) + { + itemData.append(QByteArray::number(it->internalId())); + ++it; + if (it != end) + itemData.append('\0'); + } + data->setData(mimeTypes().first(), itemData); + return data; +} + +QList DynamicTreeModel::indexToPath(const QModelIndex &_idx) const +{ + QList list; + QModelIndex idx = _idx; + while (idx.isValid()) + { + list.prepend(idx.row()); + idx = idx.parent(); + } + return list; +} + +QModelIndexList DynamicTreeModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const +{ + if (role != DynamicTreeModelId) + return QAbstractItemModel::match(start, role, value, hits, flags); + + qint64 id = value.toLongLong(); + + QHash > >::const_iterator it = m_childItems.constBegin(); + const QHash > >::const_iterator end = m_childItems.constEnd(); + + QList > items; + QList >::const_iterator itemIt; + QList >::const_iterator itemEnd; + int foundIndexRow; + for ( ; it != end; ++it) + { + items = it.value(); + itemEnd = items.constEnd(); + for (itemIt = items.constBegin(); itemIt != itemEnd; ++itemIt) + { + foundIndexRow = itemIt->indexOf(id); + if (foundIndexRow != -1) + { + static const int column = 0; + return QModelIndexList() << createIndex(foundIndexRow, column, reinterpret_cast(id)); + } + } + } + return QModelIndexList(); +} + +ModelChangeCommand::ModelChangeCommand( DynamicTreeModel *model, QObject *parent ) + : QObject(parent), m_model(model), m_startRow(-1), m_endRow(-1), m_numCols(1) +{ + +} + +QModelIndex ModelChangeCommand::findIndex(const QList &rows) const +{ + const int col = 0; + QModelIndex parent = QModelIndex(); + QListIterator i(rows); + while (i.hasNext()) + { + parent = m_model->index(i.next(), col, parent); + Q_ASSERT(parent.isValid()); + } + return parent; +} + +ModelInsertCommand::ModelInsertCommand(DynamicTreeModel *model, QObject *parent ) + : ModelChangeCommand(model, parent) +{ + +} + +QList ModelInsertCommand::tokenize(const QString& treeString) const +{ + QStringList parts = treeString.split("-"); + + QList tokens; + + const QStringList::const_iterator begin = parts.constBegin(); + const QStringList::const_iterator end = parts.constEnd(); + + QStringList::const_iterator it = begin; + ++it; + for (; it != end; ++it) + { + Token token; + if (it->trimmed().isEmpty()) + { + token.type = Token::Branch; + } else { + token.type = Token::Leaf; + token.content = *it; + } + tokens.append(token); + } + return tokens; +} + +void ModelInsertCommand::interpret(const QString& treeString) +{ + m_treeString = treeString; + + QList depths = getDepths(m_treeString); + + int size = 0; + qCount(depths, 0, size); + Q_ASSERT(size != 0); + + m_endRow = m_startRow + size - 1; +} + +QList ModelInsertCommand::getDepths(const QString& treeString) const +{ + int depth = 0; + QList depths; + +#ifdef DUMPTREE + int id = 1; +#endif + + QList tokens = tokenize(treeString); + while(!tokens.isEmpty()) + { + Token token = tokens.takeFirst(); + + if (token.type == Token::Branch) + { + ++depth; + continue; + } + Q_ASSERT(token.type == Token::Leaf); + + depths.append(depth); +#ifdef DUMPTREE + std::cout << "\""; + for (int i = 0; i <= depth; ++i) + std::cout << " -"; + std::cout << " " << id++ << "\"" << std::endl; +#endif + depth = 0; + } + + return depths; +} + +void ModelInsertCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + + if (!m_treeString.isEmpty()) + { + QList depths = getDepths(m_treeString); + + int size = 0; + qCount(depths, 0, size); + Q_ASSERT(size != 0); + m_endRow = m_startRow + size - 1; + } + m_model->beginInsertRows(parent, m_startRow, m_endRow); + if (!m_treeString.isEmpty()) + { + doInsertTree(parent); + } else { + qint64 parentId = parent.internalId(); + + for (int row = m_startRow; row <= m_endRow; row++) + { + for(int col = 0; col < m_numCols; col++ ) + { + if (m_model->m_childItems[parentId].size() <= col) + { + m_model->m_childItems[parentId].append(QList()); + } + qint64 id = m_model->newId(); + QString name = QString::number(id); + + m_model->m_items.insert(id, name); + m_model->m_childItems[parentId][col].insert(row, id); + + } + } + } + m_model->endInsertRows(); +} + +void ModelInsertCommand::doInsertTree(const QModelIndex &fragmentParent) +{ + static const int column = 0; + + QList depths = getDepths(m_treeString); + + qint64 fragmentParentIdentifier = fragmentParent.internalId(); + if (m_model->m_childItems[fragmentParentIdentifier].size() <= column) + m_model->m_childItems[fragmentParentIdentifier].append(QList()); + + QList::const_iterator it = depths.constBegin(); + const QList::const_iterator end = depths.constEnd(); + + QList recentParents; + recentParents.append(fragmentParentIdentifier); + + qint64 lastId = 0; + qint64 id; + QString name; + int depth = 0; + int row = m_startRow; + Q_ASSERT(*it == depth); + + QList rows; + rows.append(row); + + for( ; it != end; ++it) + { + id = m_model->newId(); + if (*it > depth) + { + Q_ASSERT(*it == depth + 1); + fragmentParentIdentifier = lastId; + if (recentParents.size() == *it) + recentParents.append(fragmentParentIdentifier); + else + recentParents[*it] = fragmentParentIdentifier; + + ++depth; + + } else if ( *it < depth ) + { + fragmentParentIdentifier = recentParents.at(*it); + depth = (*it); + } + + if (m_model->m_childItems[fragmentParentIdentifier].size() <= column) + { + m_model->m_childItems[fragmentParentIdentifier].append(QList()); + } + if (rows.size() == depth) + rows.append(0); + + m_model->m_items.insert(id, QString::number(id)); + m_model->m_childItems[fragmentParentIdentifier][column].insert(rows[depth]++, id); + lastId = id; + } +} + + +ModelInsertAndRemoveQueuedCommand::ModelInsertAndRemoveQueuedCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + qRegisterMetaType("QModelIndex"); +} + +void ModelInsertAndRemoveQueuedCommand::queuedBeginInsertRows(const QModelIndex& parent, int start, int end) +{ + m_model->beginInsertRows(parent, start, end); +} + +void ModelInsertAndRemoveQueuedCommand::queuedEndInsertRows() +{ + m_model->endInsertRows(); +} + +void ModelInsertAndRemoveQueuedCommand::queuedBeginRemoveRows(const QModelIndex& parent, int start, int end) +{ + m_model->beginRemoveRows(parent, start, end); +} + +void ModelInsertAndRemoveQueuedCommand::queuedEndRemoveRows() +{ + m_model->endRemoveRows(); +} + +void ModelInsertAndRemoveQueuedCommand::purgeItem(qint64 parent) +{ + QList > childItemRows = m_model->m_childItems.value(parent); + + if (childItemRows.size() > 0) + { + for (int col = 0; col < m_numCols; col++) + { + QList childItems = childItemRows[col]; + foreach(qint64 item, childItems) + { + purgeItem(item); + m_model->m_childItems[parent][col].removeOne(item); + } + } + } + m_model->m_items.remove(parent); +} + +void ModelInsertAndRemoveQueuedCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + + connect (this, SIGNAL(beginInsertRows(QModelIndex,int,int)), SLOT(queuedBeginInsertRows(QModelIndex,int,int)), Qt::QueuedConnection); + connect (this, SIGNAL(endInsertRows()), SLOT(queuedEndInsertRows()), Qt::QueuedConnection); + connect (this, SIGNAL(beginRemoveRows(QModelIndex,int,int)), SLOT(queuedBeginRemoveRows(QModelIndex,int,int)), Qt::QueuedConnection); + connect (this, SIGNAL(endRemoveRows()), SLOT(queuedEndRemoveRows()), Qt::QueuedConnection); + + emit beginInsertRows(parent, m_startRow, m_endRow); +// m_model->beginInsertRows(parent, m_startRow, m_endRow); + qint64 parentId = parent.internalId(); + for (int row = m_startRow; row <= m_endRow; row++) + { + for(int col = 0; col < m_numCols; col++ ) + { + if (m_model->m_childItems[parentId].size() <= col) + { + m_model->m_childItems[parentId].append(QList()); + } + qint64 id = m_model->newId(); + QString name = QString::number(id); + + m_model->m_items.insert(id, name); + m_model->m_childItems[parentId][col].insert(row, id); + + } + } + + emit endInsertRows(); +// m_model->endInsertRows(); + + emit beginRemoveRows(parent, m_startRow, m_endRow); +// m_model->beginRemoveRows(parent, m_startRow, m_endRow); + for(int col = 0; col < m_numCols; col++ ) + { + QList childItems = m_model->m_childItems.value(parentId).value(col); + for (int row = m_startRow; row <= m_endRow; row++) + { + qint64 item = childItems[row]; + purgeItem(item); + m_model->m_childItems[parentId][col].removeOne(item); + } + } + emit endRemoveRows(); +// m_model->endRemoveRows(); + +} + +ModelRemoveCommand::ModelRemoveCommand(DynamicTreeModel *model, QObject *parent ) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelRemoveCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + m_model->beginRemoveRows(parent, m_startRow, m_endRow); + qint64 parentId = parent.internalId(); + for(int col = 0; col < m_numCols; col++ ) + { + QList childItems = m_model->m_childItems.value(parentId).value(col); + for (int row = m_startRow; row <= m_endRow; row++) + { + qint64 item = childItems[row]; + purgeItem(item); + m_model->m_childItems[parentId][col].removeOne(item); + } + } + m_model->endRemoveRows(); +} + +void ModelRemoveCommand::purgeItem(qint64 parent) +{ + QList > childItemRows = m_model->m_childItems.value(parent); + + if (childItemRows.size() > 0) + { + for (int col = 0; col < m_numCols; col++) + { + QList childItems = childItemRows[col]; + foreach(qint64 item, childItems) + { + purgeItem(item); + m_model->m_childItems[parent][col].removeOne(item); + } + } + } + m_model->m_items.remove(parent); +} + + +ModelDataChangeCommand::ModelDataChangeCommand(DynamicTreeModel *model, QObject *parent) + : ModelChangeCommand(model, parent), m_startColumn(0) +{ + +} + +void ModelDataChangeCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + QModelIndex topLeft = m_model->index(m_startRow, m_startColumn, parent); + QModelIndex bottomRight = m_model->index(m_endRow, m_numCols - 1, parent); + + QList > childItems = m_model->m_childItems[parent.internalId()]; + + + for (int col = m_startColumn; col < m_startColumn + m_numCols; col++) + { + for (int row = m_startRow; row <= m_endRow; row++ ) + { + QString name = QString::number( m_model->newId() ); + m_model->m_items[childItems[col][row]] = name; + } + } + m_model->dataChanged(topLeft, bottomRight); +} + + +ModelMoveCommand::ModelMoveCommand(DynamicTreeModel *model, QObject *parent) +: ModelChangeCommand(model, parent) +{ + +} + +bool ModelMoveCommand::emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + return m_model->beginMoveRows(srcParent, srcStart, srcEnd, destParent, destRow); +} + +void ModelMoveCommand::doCommand() +{ + QModelIndex srcParent = findIndex(m_rowNumbers); + QModelIndex destParent = findIndex(m_destRowNumbers); + + if (!emitPreSignal(srcParent, m_startRow, m_endRow, destParent, m_destRow)) + { + return; + } + + for (int column = 0; column < m_numCols; ++column) + { + QList l = m_model->m_childItems.value(srcParent.internalId())[column].mid(m_startRow, m_endRow - m_startRow + 1 ); + + for (int i = m_startRow; i <= m_endRow ; i++) + { + m_model->m_childItems[srcParent.internalId()][column].removeAt(m_startRow); + } + int d; + if (m_destRow < m_startRow) + d = m_destRow; + else + { + if (srcParent == destParent) + d = m_destRow - (m_endRow - m_startRow + 1); + else + d = m_destRow - (m_endRow - m_startRow); + } + + foreach(const qint64 id, l) + { + + if (!m_model->m_childItems.contains(destParent.internalId())) + { + m_model->m_childItems[destParent.internalId()].append(QList()); + } + + m_model->m_childItems[destParent.internalId()][column].insert(d++, id); + } + } + + emitPostSignal(); +} + +void ModelMoveCommand::emitPostSignal() +{ + m_model->endMoveRows(); +} + + +ModelMoveLayoutChangeCommand::ModelMoveLayoutChangeCommand(DynamicTreeModel* model, QObject* parent): ModelMoveCommand(model, parent) +{ + +} + +ModelMoveLayoutChangeCommand::~ModelMoveLayoutChangeCommand() +{ + +} + +bool ModelMoveLayoutChangeCommand::emitPreSignal(const QModelIndex& srcParent, int srcStart, int srcEnd, const QModelIndex& destParent, int destRow) +{ + m_model->layoutAboutToBeChanged(); + + const int column = 0; + + for (int row = srcStart; row <= srcEnd; ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + + if (srcParent != destParent) + { + for (int row = srcEnd + 1; row < m_model->rowCount(srcParent); ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + for (int row = destRow; row < m_model->rowCount(destParent); ++row) + { + m_beforeMoveList << m_model->index(row, column, destParent); + } + } else { + if (destRow < srcStart) + { + for (int row = destRow; row < srcStart; ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + } else { + for (int row = srcStart + (srcEnd - srcStart + 1); row < destRow; ++row) + { + m_beforeMoveList << m_model->index(row, column, srcParent); + } + } + } + // We assume that the move was legal here. + return true; +} + +void ModelMoveLayoutChangeCommand::emitPostSignal() +{ + int srcStart = m_startRow; + int srcEnd = m_endRow; + int destRow = m_destRow; + + // Moving indexes may affect the m_rowNumbers and m_destRowNumbers. + // Instead of adjusting them programmatically, the test writer must specify them if they change. + + const QList sourceRowNumbers = m_endOfMoveSourceAncestors.isEmpty() ? m_rowNumbers : m_endOfMoveSourceAncestors; + QModelIndex srcParent = findIndex(sourceRowNumbers); + + const QList destRowNumbers = m_endOfMoveDestAncestors.isEmpty() ? m_destRowNumbers : m_endOfMoveDestAncestors; + QModelIndex destParent = findIndex(destRowNumbers); + + const int column = 0; + + QModelIndexList afterMoveList; + + if (srcParent != destParent) + { + for (int row = destRow; row <= (destRow + (srcEnd - srcStart)); ++row) + { + afterMoveList << m_model->index(row, column, destParent); + } + for (int row = srcStart; row < m_model->rowCount(srcParent); ++row) + { + afterMoveList << m_model->index(row, column, srcParent); + } + for (int row = destRow + (srcEnd - srcStart + 1); row < m_model->rowCount(destParent); ++row) + { + afterMoveList << m_model->index(row, column, destParent); + } + } else { + if (destRow < srcStart) + { + for (int row = srcStart; row <= srcEnd; ++row) + { + afterMoveList << m_model->index(destRow + (srcStart - row), column, destParent); + } + } else { + for (int row = srcStart; row <= srcEnd; ++row) + { + afterMoveList << m_model->index(destRow + (srcStart - row - 1), column, destParent); + } + } + if (destRow < srcStart) + { + for (int row = destRow + 1; row <= srcStart; ++row) + { + afterMoveList << m_model->index(row, column, srcParent); + } + } else { + for (int row = srcStart + (srcEnd - srcStart + 1); row < (srcStart + (destRow - srcEnd)); ++row) + { + afterMoveList << m_model->index(row - (srcEnd - srcStart + 1), column, srcParent); + } + } + } + + m_model->changePersistentIndexList(m_beforeMoveList, afterMoveList); + m_beforeMoveList.clear(); + m_model->layoutChanged(); + + +} + +ModelResetCommand::ModelResetCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + +} + +ModelResetCommand::~ModelResetCommand() +{ + +} + +void ModelResetCommand::setInitialTree(const QString& treeString) +{ + m_treeString = treeString; +} + +void ModelResetCommand::doCommand() +{ + m_model->beginResetModel(); + bool blocked = m_model->blockSignals(true); + m_model->clear(); + if (!m_treeString.isEmpty()) + { + ModelInsertCommand *ins = new ModelInsertCommand(m_model); + ins->setStartRow(0); + ins->interpret(m_treeString); + ins->doCommand(); + } + m_model->blockSignals(blocked); + m_model->endResetModel(); +} + + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.h new file mode 100644 index 00000000..96e902c7 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreemodel.h @@ -0,0 +1,334 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef DYNAMICTREEMODEL_H +#define DYNAMICTREEMODEL_H + +#include + +#include +#include + +#include + +#include "indexfinder.h" + +template class QList; + +class ModelMoveCommand; + +class DynamicTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + enum Roles + { + DynamicTreeModelId = Qt::UserRole, + + LastRole + }; + + explicit DynamicTreeModel(QObject *parent = 0); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &index = QModelIndex()) const; + int columnCount(const QModelIndex &index = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + Qt::ItemFlags flags(const QModelIndex& index) const; + Qt::DropActions supportedDropActions() const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits = 1, + Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const; + + + void clear(); + QList indexToPath(const QModelIndex &idx) const; + ModelMoveCommand* getMoveCommand(const QList &srcPath, int startRow, int endRow); + +protected slots: + + /** + Finds the parent id of the string with id @p searchId. + + Returns -1 if not found. + */ + qint64 findParentId(qint64 searchId) const; + +private: + QHash m_items; + QHash > > m_childItems; + qint64 nextId; + qint64 newId() { return nextId++; }; + + QModelIndex m_nextParentIndex; + int m_nextRow; + + int m_depth; + int maxDepth; + + friend class ModelInsertCommand; + friend class ModelInsertWithDescendantsCommand; + friend class ModelRemoveCommand; + friend class ModelDataChangeCommand; + friend class ModelMoveCommand; + friend class ModelMoveLayoutChangeCommand; + friend class ModelResetCommand; +// friend class ModelSortIndexCommand; + friend class ModelSortIndexLayoutChangeCommand; + friend class ModelInsertAndRemoveQueuedCommand; + +}; + + +class ModelChangeCommand : public QObject +{ + Q_OBJECT +public: + + ModelChangeCommand( DynamicTreeModel *model, QObject *parent = 0 ); + + virtual ~ModelChangeCommand() {} + + void setAncestorRowNumbers(const QList &rowNumbers) { m_rowNumbers = rowNumbers; } + QList srcAncestors() const { return m_rowNumbers; } + + QModelIndex findIndex(const QList &rows) const; + + void setStartRow(int row) { m_startRow = row; } + + void setEndRow(int row) { m_endRow = row; } + + void setNumCols(int cols) { m_numCols = cols; } + + virtual void doCommand() = 0; + + QModelIndex parentIndex() const { return findIndex(m_rowNumbers); } + int startRow() const { return m_startRow; } + int endRow() const { return m_endRow; } + +protected: + DynamicTreeModel* m_model; + QList m_rowNumbers; + int m_startRow; + int m_endRow; + int m_numCols; + +}; + +typedef QList ModelChangeCommandList; + +/** + @brief Inserts a sub tree into the dynamictreemodel. + + As an alternative to setStartRow and setEndRow, the interpret command may be used. + + The interpret command is used to set the structure of the subtree. + + For example, + @code + cmd = new ModelInsertCommand(m_model, this); + cmd->interpret( + "- A" + "- B" + "- - C" + "- D" + ); + @endcode + + Will emit an insert for 3 rows, the second of which will have a child row. The interpretation + string may be complex as long as it is valid. The text at the end of each row does not need to be consistent. + There is a define DUMPTREE to make this command print the tree it inserts for better readability. + + @code + cmd->interpret( + "- A" + "- - B" + "- - C" + "- - - C" + "- - C" + "- - - C" + "- - - C" + "- - C" + "- D" + "- - E" + "- - F" + ); + @endcode + + The string is valid if (depth of row (N + 1)) <= ( (depth of row N) + 1). For example, the following is invalid + because the depth of B is 2 and the depth of A is 0. + + @code + cmd->interpret( + "- A" + "- - - B" + "- - C" + @endcode +*/ +class ModelInsertCommand : public ModelChangeCommand +{ + Q_OBJECT + + struct Token + { + enum Type { Branch, Leaf }; + Type type; + QString content; + }; + +public: + + explicit ModelInsertCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelInsertCommand() {} + + void interpret(const QString &treeString); + + virtual void doCommand(); + void doInsertTree(const QModelIndex &parent); + +protected: + QList tokenize(const QString &treeString) const; + + QList getDepths(const QString &treeString) const; + + QString m_treeString; +}; + +class ModelInsertAndRemoveQueuedCommand : public ModelChangeCommand +{ + Q_OBJECT + +public: + + explicit ModelInsertAndRemoveQueuedCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelInsertAndRemoveQueuedCommand() {} + + virtual void doCommand(); + +signals: + void beginInsertRows(const QModelIndex &parent, int start, int end); + void endInsertRows(); + void beginRemoveRows(const QModelIndex &parent, int start, int end); + void endRemoveRows(); + +protected slots: + void queuedBeginInsertRows(const QModelIndex &parent, int start, int end); + void queuedEndInsertRows(); + void queuedBeginRemoveRows(const QModelIndex &parent, int start, int end); + void queuedEndRemoveRows(); + +protected: + void purgeItem(qint64 parent); +}; + +class ModelRemoveCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + explicit ModelRemoveCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelRemoveCommand() {} + + virtual void doCommand(); + + void purgeItem(qint64 parent); +}; + +class ModelDataChangeCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + explicit ModelDataChangeCommand(DynamicTreeModel *model, QObject *parent = 0); + + virtual ~ModelDataChangeCommand() {} + + virtual void doCommand(); + + void setStartColumn(int column) { m_startColumn = column; } + +protected: + int m_startColumn; +}; + +class ModelMoveCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + explicit ModelMoveCommand(DynamicTreeModel *model, QObject *parent); + + virtual ~ModelMoveCommand() {} + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + + virtual void doCommand(); + + virtual void emitPostSignal(); + + void setDestAncestors( const QList &rows ) { m_destRowNumbers = rows; } + QList destAncestors() const { return m_destRowNumbers; } + + void setDestRow(int row) { m_destRow = row; } + +protected: + QList m_destRowNumbers; + int m_destRow; +}; + +class ModelMoveLayoutChangeCommand : public ModelMoveCommand +{ + Q_OBJECT +public: + explicit ModelMoveLayoutChangeCommand(DynamicTreeModel* model, QObject* parent); + virtual ~ModelMoveLayoutChangeCommand(); + + virtual bool emitPreSignal(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow); + + virtual void emitPostSignal(); + + void setEndOfMoveSourceAncestors(const QList &rows ) { m_endOfMoveSourceAncestors = rows; } + void setEndOfMoveDestAncestors(const QList &rows ) { m_endOfMoveDestAncestors = rows; } + +private: + QModelIndexList m_beforeMoveList; + QList m_endOfMoveSourceAncestors; + QList m_endOfMoveDestAncestors; + +}; + +class ModelResetCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelResetCommand(DynamicTreeModel* model, QObject* parent = 0); + virtual ~ModelResetCommand(); + + void setInitialTree(const QString &treeString); + + /* reimp */ void doCommand(); +private: + QString m_treeString; +}; + + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.cpp new file mode 100644 index 00000000..fc40a972 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.cpp @@ -0,0 +1,398 @@ +/* + Copyright (c) 2009 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 "dynamictreewidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "dynamictreemodel.h" + +static const char * const treePredefinesNames[] = { + "Flat List", + "Straight Line Tree", + "Dragon Teeth 1", + "Dragon Teeth 2", + "Random Tree 1" +}; + +static const char * const treePredefinesContent[] = { + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1" + " - 1", + + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - - - - - 1" + " - - - - - - 1" + " - - - - - - - 1" + " - - - - - - - - 1" + " - - - - - - - - - 1" + " - - - - - - - - - - 1" + " - - - - - - - - - - - 1" + " - - - - - - - - - - - - 1" + " - - - - - - - - - - - - - 1" + " - - - - - - - - - - - - - - 1" + " - - - - - - - - - - - - - - - 1" + " - - - - - - - - - - - - - - - - 1", + + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - 1" + " - - 1" + " - - - 1" + " - - - - 1", + + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - - - - - 1" + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - - - - - 1" + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - - - - - 1" + " - 1" + " - - 1" + " - - - 1" + " - - - - 1" + " - - - - - 1", + + " - 1" + " - 2" + " - - 3" + " - - - 4" + " - 5" + " - 6" + " - 7" + " - - 8" + " - - - 9" + " - - - 10" + " - - - - 11" + " - - - 12" + " - - - - 13" + " - 14" + " - 15" +}; + +static const char * const insertSubTreePredefinesNames[] = { + "Flat List", + "Straight Line Tree", + "Dragon Teeth 1", + "Dragon Teeth 2", + "Random Tree 1" +}; + +static const char * const insertSubTreePredefinesContent[] = { + " - 1\n" + " - 1\n" + " - 1\n" + " - 1\n", + + " - 1\n" + " - - 1\n" + " - - - 1\n" + " - - - - 1\n", + + " - 1\n" + " - - 1\n" + " - 1\n" + " - - 1\n", + + " - 1\n" + " - - 1\n" + " - - - 1\n" + " - 1\n" + " - - 1\n" + " - - - 1\n", + + " - 1\n" + " - 2\n" + " - - 3\n" + " - - - 4\n" + " - 5\n" +}; + +DynamicTreeWidget::DynamicTreeWidget(DynamicTreeModel *rootModel, QWidget* parent, Qt::WindowFlags f) + : QWidget(parent, f), m_dynamicTreeModel(rootModel) +{ + QTabWidget *tabWidget = new QTabWidget(this); + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->addWidget(tabWidget); + + QWidget *editContainer = new QWidget(tabWidget); + QVBoxLayout *editLayout = new QVBoxLayout(editContainer); + + m_treePredefines = new QComboBox(editContainer); + for (uint i = 0; i < sizeof treePredefinesNames / sizeof *treePredefinesNames; ++i) + m_treePredefines->addItem(*(treePredefinesNames + i), *(treePredefinesContent + i)); + editLayout->addWidget(m_treePredefines); + connect(m_treePredefines, SIGNAL(currentIndexChanged(int)), SLOT(setTreePredefine(int))); + + m_textEdit = new QPlainTextEdit(editContainer); + editLayout->addWidget(m_textEdit); + + QWidget *viewContainer = new QWidget(tabWidget); + + QVBoxLayout *viewLayout = new QVBoxLayout(viewContainer); + + m_treeView = new QTreeView(tabWidget); + m_treeView->setModel(rootModel); + m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_treeView->setDragDropMode(QAbstractItemView::InternalMove); + m_treeView->setDragEnabled(true); + m_treeView->setAcceptDrops(true); + m_treeView->setDropIndicatorShown(true); + m_treeView->viewport()->installEventFilter(this); + +// QPushButton *m_removeButton = new QPushButton("Remove", tabWidget); + +// connect(m_removeButton, SIGNAL(clicked(bool)), SLOT(removeSelected())); + +// m_insertSubTreePredefines = new QComboBox(this); +// for (uint i = 0; i < sizeof insertSubTreePredefinesNames / sizeof *insertSubTreePredefinesNames; ++i) +// m_insertSubTreePredefines->addItem(*(insertSubTreePredefinesNames + i), *(insertSubTreePredefinesContent + i)); +// editLayout->addWidget(m_insertSubTreePredefines); +// connect(m_insertSubTreePredefines, SIGNAL(currentIndexChanged(int)), SLOT(setInsertSubTreePredefine(int))); + +// m_insertPatternTextEdit = new QPlainTextEdit(tabWidget); +// m_insertPatternTextEdit->setMaximumHeight(100); + +// m_insertChildren = new QRadioButton("Insert Children", tabWidget); +// m_insertSiblingsAbove = new QRadioButton("Insert Siblings Above", tabWidget); +// m_insertSiblingsBelow = new QRadioButton("Insert Siblings Below", tabWidget); + +// m_insertChildren->setChecked(true); + +// QPushButton *m_insertButton = new QPushButton("Insert", tabWidget); + +// connect(m_insertButton, SIGNAL(clicked(bool)), SLOT(insertSelected())); + +// QPushButton *m_resetButton = new QPushButton("Reset", tabWidget); + +// connect(m_resetButton, SIGNAL(clicked(bool)), SLOT(resetModel())); + + viewLayout->addWidget(m_treeView); + +// viewLayout->addWidget(m_removeButton); + +// viewLayout->addWidget(m_insertSubTreePredefines); +// viewLayout->addWidget(m_insertPatternTextEdit); +// viewLayout->addWidget(m_insertChildren); +// viewLayout->addWidget(m_insertSiblingsAbove); +// viewLayout->addWidget(m_insertSiblingsBelow); +// viewLayout->addWidget(m_insertButton); +// viewLayout->addWidget(m_resetButton); + + tabWidget->addTab(editContainer, "Edit"); + tabWidget->addTab(viewContainer, "View"); + + tabWidget->setCurrentIndex(ViewTab); + + connect(tabWidget, SIGNAL(currentChanged(int)), SLOT(currentChanged(int))); + stringToModel( + " - 1" + " - 2" + " - - 3" + " - - 4" + " - - 5" + " - 6" + " - 7" + " - - 8" + " - - - 9" + " - - - 10" + " - - 11" + " - - 12" + " - 13" + " - 14" + " - 15" + " - - 16" + " - - - 17" + " - - - 18" + " - 19" + " - 20" + " - 21" + ); +} + +void DynamicTreeWidget::setInitialTree(const QString &treeString) +{ + stringToModel(treeString); +} + +void DynamicTreeWidget::currentChanged(int index) +{ + switch(index) + { + case EditTab: + m_textEdit->setPlainText(modelTreeToString(0, QModelIndex())); + break; + case ViewTab: + if (m_textEdit->document()->isModified()) + stringToModel(m_textEdit->toPlainText()); + m_textEdit->document()->setModified(false); + break; + } +} + +void DynamicTreeWidget::stringToModel(const QString &treeString) +{ + if (treeString.isEmpty()) + return; + + m_dynamicTreeModel->clear(); + ModelInsertCommand *command = new ModelInsertCommand(m_dynamicTreeModel); + command->setStartRow(0); + command->interpret(treeString); + command->doCommand(); + m_treeView->expandAll(); +} + +QString DynamicTreeWidget::modelTreeToString(int depth, const QModelIndex &parent) +{ + QString result; + QModelIndex idx; + static const int column = 0; + QString prefix; + + for (int i = 0; i <= depth; ++i) + prefix.append(" -"); + + for (int row = 0; row < m_dynamicTreeModel->rowCount(parent); ++row) + { + idx = m_dynamicTreeModel->index(row, column, parent); + result.append(prefix + " " + idx.data().toString() + "\n"); + if (m_dynamicTreeModel->hasChildren(idx)) + result.append(modelTreeToString(depth+1, idx)); + } + return result; +} + +void DynamicTreeWidget::removeSelected() +{ + QModelIndex parent; + ModelRemoveCommand *removeCommand = new ModelRemoveCommand(m_dynamicTreeModel, this); + QItemSelection selection = m_treeView->selectionModel()->selection(); + while (!selection.isEmpty()) + { + const QItemSelectionRange &range = selection.takeFirst(); // The selection model will take care of updating persistent indexes. + Q_ASSERT(range.isValid()); +// kDebug() << range.parent() << range.top() << range.bottom(); + removeCommand->setAncestorRowNumbers(m_dynamicTreeModel->indexToPath(range.parent())); + removeCommand->setStartRow(range.top()); + removeCommand->setEndRow(range.bottom()); + +// kDebug() << m_dynamicTreeModel->indexToPath(range.parent()); + + removeCommand->doCommand(); + } +} + +void DynamicTreeWidget::insertSelected() +{ + const QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows(); + + if (selectedRows.size() != 1) + return; + + const QModelIndex selectedRow = selectedRows.first(); + + ModelInsertCommand *ins = new ModelInsertCommand(m_dynamicTreeModel, this); + if (m_insertChildren->isChecked()) + { + ins->setAncestorRowNumbers(m_dynamicTreeModel->indexToPath(selectedRow)); + ins->setStartRow(0); + } else if (m_insertSiblingsAbove->isChecked()) + { + ins->setAncestorRowNumbers(m_dynamicTreeModel->indexToPath(selectedRow.parent())); + ins->setStartRow(selectedRow.row()); + } else { + Q_ASSERT(m_insertSiblingsBelow->isChecked()); + ins->setAncestorRowNumbers(m_dynamicTreeModel->indexToPath(selectedRow.parent())); + ins->setStartRow(selectedRow.row() + 1); + } + ins->interpret(m_insertPatternTextEdit->toPlainText()); + ins->doCommand(); +} + +void DynamicTreeWidget::resetModel() +{ + ModelResetCommand *resetCommand = new ModelResetCommand(m_dynamicTreeModel, this); + + resetCommand->setInitialTree(m_insertPatternTextEdit->toPlainText().trimmed()); + resetCommand->doCommand(); +} + +void DynamicTreeWidget::setTreePredefine(int index) +{ + stringToModel(m_treePredefines->itemData(index).toString()); + m_textEdit->setPlainText(modelTreeToString(0, QModelIndex())); +} + +void DynamicTreeWidget::setInsertSubTreePredefine(int index) +{ + m_insertPatternTextEdit->setPlainText(m_insertSubTreePredefines->itemData(index).toString()); +} + +bool DynamicTreeWidget::eventFilter(QObject* o, QEvent* e) +{ + + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseButtonRelease) + return true; + return QObject::eventFilter( o, e); +} + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.h new file mode 100644 index 00000000..edac1654 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/dynamictreewidget.h @@ -0,0 +1,81 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef DYNAMICTREEWIDGET_H +#define DYNAMICTREEWIDGET_H + +#include + +class QModelIndex; + +class QComboBox; +class QPlainTextEdit; +class QTreeView; +class QRadioButton; + +class DynamicTreeModel; + +class DynamicTreeWidget : public QWidget +{ + Q_OBJECT +public: + DynamicTreeWidget(DynamicTreeModel *rootModel, QWidget* parent = 0, Qt::WindowFlags f = 0); + + void setInitialTree(const QString &treeString); + + DynamicTreeModel *model() const { return m_dynamicTreeModel; } + QTreeView *treeView() const { return m_treeView; } + QPlainTextEdit *textEdit() const { return m_textEdit; } + +private slots: + void currentChanged(int index); + void setTreePredefine(int index); + void setInsertSubTreePredefine(int index); + + void removeSelected(); + void insertSelected(); + void resetModel(); + +protected: + virtual bool eventFilter(QObject* , QEvent* ); + +private: + void stringToModel(const QString &treeString); + QString modelTreeToString(int depth, const QModelIndex &parent); + +private: + enum Tab { + EditTab, + ViewTab + }; + + QString m_initialString; + DynamicTreeModel *m_dynamicTreeModel; + QTreeView *m_treeView; + QPlainTextEdit *m_textEdit; + + QPlainTextEdit *m_insertPatternTextEdit; + QRadioButton *m_insertChildren; + QRadioButton *m_insertSiblingsAbove; + QRadioButton *m_insertSiblingsBelow; + QComboBox *m_insertSubTreePredefines; + QComboBox *m_treePredefines; +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/fuzz.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/fuzz.png new file mode 100644 index 0000000000000000000000000000000000000000..2da72e7742ef364c7f21946cbb598e48facb7d24 GIT binary patch literal 62501 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

yU97Ory>RMo29xUAAFp6?W1&cT} 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

l-VfAg}1fOLZ7OcQ>+Nl$%5mcj<5re0!)H#@ zBDlU!0T)mmKjCbYfDu=id1w@!g|0z+w)K*uEhA8pk{-<@5*D?ATh_QiQ`GPP6Ja_r>D}U}(E}YOW?F%1{SrqA`QOUj6 zxwH?G^9{8xra*+}q=Vu^K_Qec$z=?2P;+hckGFyK_N@O*RJbr)vjT!n)EH+J20(~WNDqJ`8BvVaqkF%_X%Kk9^r zkHWoR(PH4Z9X_V>^k0I1#)OKo$eQ}~+zW4i+k3;`f8$&9ohPHIthE&O@K7cZWgCSZ zJNBDV$YXq(b_SfVo$x5;A+~LhZ_My@4mUup3Adb>y}JAB{z)(02f_i|nBZ^cKToEA zA;D6JdnCt4Jn__;P|5Ij)ujuOcD1KIPI8CavjfM8WNVifZE#R(Khnu5e+IeMme34> zbLBPHhT|xBp5eyohh=AE1^oT+^s_I7&;G@4p3_{gG58%{@4EB%qv7)13`}(x8XBQ2 z@x2`qJ*zkh=Sj@YE5e-f=W>dT!1}5RNUlHL%nk98dp;W8{+@RcTf7D%m`Su92P28D z@QY8&2Z7Q#pPQjiAuc#>IfZxJhzU;54xb1btMjukoMb3ncF9Gd8GN|3xCk7%igtp+ z?rL>yPQk&qZfQppTZ|>w0)?=OY(-|$p3Un}=QMn9^jMm7qcgQY!`IUbNJ%F!B&r8wQQL%ZNYir=@*{MIWjG%w@nA= zp$8UjQ;j{HZ6J2Nx-#hmMc96?=Ht2dA3jC!PuYa5R#^tlj^H*en7@o7T^a7U*q%~|M%b==G1=Tsw+?Zj$ei(Xdo?88Lgx+!@N)5MH(X>S)rIr}eT+m%-_HG$j5{D+B^E9n{j^J-aRHEVkW*Kw&I@Pjg2D z;O(t11i~vFH4wh{)W#%*bO>D8)w=CS51UTaxK)t&4N#gqtpRTKx-2_0(|h17oDlHn z-@o&{zx0DH^w2Mp>stEDZ+!jR*>j_#?-=a6H4Kcbe0gmD@WK1TAcS_MQYf@UHE1QG zixdeyddHz&*-F29GBl$EdEk2w1OtMed-i$6u`}@2s{{M4;NM({V2|lgq2S|#xjSfWNge{x8gP280+OYhf#!=e(1w@bdLyJg2~oc*n1oxx+C7B?mxLjdug#k_5ZE&djo}{G>ew(8(Rb?=y%jidZpO{` zHSUpk#mzAsKJ}kJm9DtzSI#*p;LtCZ>+^s4xwP`3@2B$eQfZ`|#6%~@qOfJxPPo&r z3u~Ww8pNSBlrY2*u0s%z@SXMpoY)M3H7r0zG5B45)fH^#I?~QPyVBIjQ_`(B-;fTp zG^VAOUYuB2r`?SQQ_nz7WKDR@rL`NjCR~@&yz^^`q|h<{SZY9pa{C)@&F)TPM+M1J zENc2??ip!k|BX^*4y_L@#=+LGeAy-825blL1X0zU(_HWR8V`kprO)M^-)EO|9+n>FxeTzx zIB(W$df^dS_SCd{M_7LCatvD9Lo);4Oz`M+IGtAEgtTPgJOeH>WVm_jj&Rl0S3~c7 zD%=7zxcWyw(pX)ZCmX;%#Jnx^)+H=QPQmq<3u-bZ)G#PM^ziD?4sYY;>z4=296~Gj z^B2GHxxxooDCqzD^`Q^`hqx2uq7I0y7*$6zg=BjnzFb{5HO!y4Fq~nXz8xmtMGNQW zkf8|(P}yJ516PCzAdYu-#D7_IN!1>VSv%aPd^O+gp{)c{` z_?Hv)wWp)=Baw9`qzpz^|?7zq= zeCQOMjjfnUPQ@f~Fud;O8)fsUiXocg@o+ie??l$I4&TKIZFnc}-S@s#_|OUk{hPb4 zzy4M6GMtsxZ`f*zZ0JAU5h`k7#KFV9?WXI)H@~wgoKrNKE#14qSO4~}Lp_!SJrMI% z&XnM?lwxt14*q?lvn}8osX$jcHc%Xn`*_#2!_6q8?=~0kT+ozypuZke*di@=5inM$wTKjf_MVDQ6W%%0HzfN)Yhk>D!p>@BEzp`*15pSRP#K#LCM4_MySK<2k>wf__ Wglm-~9DZ{E0000 + +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 INDEXFINDER_H +#define INDEXFINDER_H + +#include + +class IndexFinder +{ + public: + IndexFinder(QList rows = QList()) : m_rows(rows), m_model(0) {} + + IndexFinder(QAbstractItemModel *model, QList rows = QList() ) + : m_rows(rows), m_model(model) + { + Q_ASSERT(model); + } + + QModelIndex getIndex() + { + if(!m_model) + return QModelIndex(); + static const int col = 0; + QModelIndex parent = QModelIndex(); + QListIterator i(m_rows); + while (i.hasNext()) + { + parent = m_model->index(i.next(), col, parent); + Q_ASSERT(parent.isValid()); + } + return parent; + } + + bool operator==( const IndexFinder &other ) const + { + return (m_rows == other.m_rows && m_model == other.m_model ); + } + + QList rows() const { return m_rows; } + void appendRow(int row) { m_rows.append(row); } + void setRows( const QList &rows ) { m_rows = rows; } + void setModel(QAbstractItemModel *model) { m_model = model; } + + private: + QList m_rows; + QAbstractItemModel *m_model; +}; + + +Q_DECLARE_METATYPE( IndexFinder ) + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbihash_p.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbihash_p.h new file mode 100644 index 00000000..58d9222a --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbihash_p.h @@ -0,0 +1,401 @@ +/* + + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + + +#ifndef KBIHASH_P_H +#define KBIHASH_P_H + +#include +#include + +template class KBiHash; +template QDebug operator<<(QDebug out, const KBiHash &biHash); +template QDataStream &operator<<(QDataStream &out, const KBiHash &bihash); +template QDataStream &operator>>(QDataStream &in, KBiHash &biHash); + +/** + * @brief KBiHash provides a bi-directional hash container + * + * @note This class is designed to make mapping easier in proxy model implementations. + * + * @todo Figure out whether to discard this and use boost::bimap instead, submit it Qt or keep it here and make more direct use of QHashNode. + */ +template +class KBiHash +{ +public: + typedef T left_type; + typedef U right_type; + + template + class _iterator : public QHash::iterator + { + public: + explicit inline _iterator(void *data) : QHash::iterator(data) {} + /* implicit */ _iterator(const typename QHash::iterator it) + // Using internals here because I was too lazy to write my own iterator. + : QHash::iterator(reinterpret_cast(static_cast *>(it))) { } + + inline const W &value() const { + return QHash::iterator::value(); + } + inline const W &operator*() const { + return QHash::iterator::operator*(); + } + inline const W *operator->() const { + return QHash::iterator::operator->(); + } + + private: +#ifndef Q_CC_MSVC + using QHash::iterator::operator*; + using QHash::iterator::operator->; + using QHash::iterator::value; +#endif + }; + + typedef _iterator left_iterator; + typedef typename QHash::const_iterator left_const_iterator; + typedef _iterator right_iterator; + typedef typename QHash::const_iterator right_const_iterator; + + inline KBiHash() {} + inline KBiHash(const KBiHash &other) { + *this = other; + } + + static KBiHash fromHash(const QHash &hash) { + KBiHash biHash; + typename QHash::const_iterator it = hash.constBegin(); + const typename QHash::const_iterator end = hash.constEnd(); + for ( ; it != end; ++it) + biHash.insert(it.key(), it.value()); + return biHash; + } + + const KBiHash &operator=(const KBiHash &other) { + _leftToRight = other._leftToRight; _rightToLeft = other._rightToLeft; return *this; + } + + inline bool removeLeft(T t) { + const U u = _leftToRight.take(t); + return _rightToLeft.remove(u) != 0; + } + + inline bool removeRight(U u) { + const T t = _rightToLeft.take(u); + return _leftToRight.remove(t) != 0; + } + + inline U takeLeft(T t) { + const U u = _leftToRight.take(t); + _rightToLeft.remove(u); + return u; + } + + inline T takeRight(U u) { + const T t = _rightToLeft.take(u); + _leftToRight.remove(t); + return t; + } + + inline T rightToLeft(U u) const { + return _rightToLeft.value(u); + } + + inline U leftToRight(T t) const { + return _leftToRight.value(t); + } + + inline bool leftContains(T t) const { + return _leftToRight.contains(t); + } + + inline bool rightContains(U u) const { + return _rightToLeft.contains(u); + } + + inline int size() const { + return _leftToRight.size(); + } + + inline int count() const { + return _leftToRight.count(); + } + + inline int capacity() const { + return _leftToRight.capacity(); + } + + void reserve(int size) { + _leftToRight.reserve(size); _rightToLeft.reserve(size); + } + + inline void squeeze() { + _leftToRight.squeeze(); _rightToLeft.squeeze(); + } + + inline void detach() { + _leftToRight.detach(); _rightToLeft.detach(); + } + + inline bool isDetached() const { + return _leftToRight.isDetached(); + } + + inline void setSharable(bool sharable) { + _leftToRight.setSharable(sharable); _rightToLeft.setSharable(sharable); + } + + inline bool isSharedWith(const KBiHash &other) const { + return _leftToRight.isSharedWith(other._leftToRight) && _rightToLeft.isSharedWith(other._leftToRight); + } + + void clear() { + _leftToRight.clear(); _rightToLeft.clear(); + } + + QList leftValues() const { + return _leftToRight.keys(); + } + + QList rightValues() const { + return _rightToLeft.keys(); + } + + right_iterator eraseRight(right_iterator it) { + Q_ASSERT(it != rightEnd()); + _leftToRight.remove(it.value()); + return _rightToLeft.erase(it); + } + + left_iterator eraseLeft(left_iterator it) { + Q_ASSERT(it != leftEnd()); + _rightToLeft.remove(it.value()); + return _leftToRight.erase(it); + } + + left_iterator findLeft(T t) { + return _leftToRight.find(t); + } + + left_const_iterator findLeft(T t) const { + return _leftToRight.find(t); + } + + left_const_iterator constFindLeft(T t) const { + return _leftToRight.constFind(t); + } + + right_iterator findRight(U u) { + return _rightToLeft.find(u); + } + + right_const_iterator findRight(U u) const { + return _rightToLeft.find(u); + } + + right_const_iterator constFindRight(U u) const { + return _rightToLeft.find(u); + } + + left_iterator insert(T t, U u) { + // biHash.insert(5, 7); // creates 5->7 in _leftToRight and 7->5 in _rightToLeft + // biHash.insert(5, 9); // replaces 5->7 with 5->9 in _leftToRight and inserts 9->5 in _rightToLeft. + // The 7->5 in _rightToLeft would be dangling, so we remove it before insertion. + + // This means we need to hash u and t up to twice each. Could probably be done better using QHashNode. + + if (_leftToRight.contains(t)) + _rightToLeft.remove(_leftToRight.take(t)); + if (_rightToLeft.contains(u)) + _leftToRight.remove(_rightToLeft.take(u)); + + _rightToLeft.insert(u, t); + return _leftToRight.insert(t, u); + } + + KBiHash &intersect(const KBiHash &other) { + typename KBiHash::left_iterator it = leftBegin(); + while (it != leftEnd()) { + if (!other.leftContains(it.key())) + it = eraseLeft(it); + else + ++it; + } + return *this; + } + + KBiHash &subtract(const KBiHash &other) { + typename KBiHash::left_iterator it = leftBegin(); + while (it != leftEnd()) { + if (other._leftToRight.contains(it.key())) + it = eraseLeft(it); + else + ++it; + } + return *this; + } + + KBiHash &unite(const KBiHash &other) { + typename QHash::const_iterator it = other._leftToRight.constBegin(); + const typename QHash::const_iterator end = other._leftToRight.constEnd(); + while (it != end) { + const T key = it.key(); + if (!_leftToRight.contains(key)) + insert(key, it.value()); + ++it; + } + return *this; + } + + void updateRight(left_iterator it, U u) { + Q_ASSERT(it != leftEnd()); + const T key = it.key(); + _rightToLeft.remove(_leftToRight.value(key)); + _leftToRight[key] = u; + _rightToLeft[u] = key; + } + + void updateLeft(right_iterator it, T t) { + Q_ASSERT(it != rightEnd()); + const U key = it.key(); + _leftToRight.remove(_rightToLeft.value(key)); + _rightToLeft[key] = t; + _leftToRight[t] = key; + } + + inline bool isEmpty() const { + return _leftToRight.isEmpty(); + } + + const U operator[](const T &t) const { + return _leftToRight.operator[](t); + } + + bool operator==(const KBiHash &other) { + return _leftToRight.operator == (other._leftToRight); + } + + bool operator!=(const KBiHash &other) { + return _leftToRight.operator != (other._leftToRight); + } + + left_iterator toLeftIterator(right_iterator it) const { + Q_ASSERT(it != rightEnd()); + return _leftToRight.find(it.value()); + } + + right_iterator toRightIterator(left_iterator it) const { + Q_ASSERT(it != leftEnd()); + return _rightToLeft.find(it.value()); + } + + inline left_iterator leftBegin() { + return _leftToRight.begin(); + } + + inline left_iterator leftEnd() { + return _leftToRight.end(); + } + + inline left_const_iterator leftBegin() const { + return _leftToRight.begin(); + } + + inline left_const_iterator leftEnd() const { + return _leftToRight.end(); + } + + inline left_const_iterator leftConstBegin() const { + return _leftToRight.constBegin(); + } + + inline left_const_iterator leftConstEnd() const { + return _leftToRight.constEnd(); + } + + inline right_iterator rightBegin() { + return _rightToLeft.begin(); + } + + inline right_iterator rightEnd() { + return _rightToLeft.end(); + } + + inline right_const_iterator rightBegin() const { + return _rightToLeft.begin(); + } + + inline right_const_iterator rightEnd() const { + return _rightToLeft.end(); + } + inline right_const_iterator rightConstBegin() const { + return _rightToLeft.constBegin(); + } + + inline right_const_iterator rightConstEnd() const { + return _rightToLeft.constEnd(); + } + + friend QDataStream &operator<< (QDataStream &out, const KBiHash &bihash); + friend QDataStream &operator>> (QDataStream &in, KBiHash &biHash); + friend QDebug operator<< (QDebug out, const KBiHash &biHash); +private: + QHash _leftToRight; + QHash _rightToLeft; +}; + +template +QDataStream &operator<<(QDataStream &out, const KBiHash &biHash) +{ + return out << biHash._leftToRight; +} + +template +QDataStream &operator>>(QDataStream &in, KBiHash &biHash) +{ + QHash leftToRight; + in >> leftToRight; + typename QHash::const_iterator it = leftToRight.constBegin(); + const typename QHash::const_iterator end = leftToRight.constEnd(); + for (; it != end; ++it) + biHash.insert(it.key(), it.value()); + + return in; +} + +template +QDebug operator<<(QDebug out, const KBiHash &biHash) +{ + typename KBiHash::left_const_iterator it = biHash.leftConstBegin(); + + const typename KBiHash::left_const_iterator end = biHash.leftConstEnd(); + out << "KBiHash("; + for (; it != end; ++it) + out << "(" << it.key() << "<=>" << it.value() << ")"; + + out << ")"; + return out; +} + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.cpp new file mode 100644 index 00000000..f0d68847 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.cpp @@ -0,0 +1,210 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "kbreadcrumbselectionmodel.h" + + +class KBreadcrumbSelectionModelPrivate +{ + Q_DECLARE_PUBLIC(KBreadcrumbSelectionModel) + KBreadcrumbSelectionModel * const q_ptr; +public: + KBreadcrumbSelectionModelPrivate(KBreadcrumbSelectionModel *breadcrumbSelector, QItemSelectionModel *selectionModel, KBreadcrumbSelectionModel::BreadcrumbTarget direction) + : q_ptr(breadcrumbSelector), + m_includeActualSelection(true), + m_selectionDepth(-1), + m_showHiddenAscendantData(false), + m_selectionModel(selectionModel), + m_direction(direction), + m_ignoreCurrentChanged(false) + { + + } + + /** + Returns a selection containing the breadcrumbs for @p index + */ + QItemSelection getBreadcrumbSelection(const QModelIndex &index); + + /** + Returns a selection containing the breadcrumbs for @p selection + */ + QItemSelection getBreadcrumbSelection(const QItemSelection &selection); + +// void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + + bool m_includeActualSelection; + int m_selectionDepth; + bool m_showHiddenAscendantData; + QItemSelectionModel *m_selectionModel; + KBreadcrumbSelectionModel::BreadcrumbTarget m_direction; + bool m_ignoreCurrentChanged; +}; + +KBreadcrumbSelectionModel::KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, QObject* parent) + : QItemSelectionModel(const_cast(selectionModel->model()), parent), + d_ptr(new KBreadcrumbSelectionModelPrivate(this, selectionModel, MakeBreadcrumbSelectionInSelf)) +{ +} + +KBreadcrumbSelectionModel::KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, BreadcrumbTarget direction, QObject* parent) + : QItemSelectionModel(const_cast(selectionModel->model()), parent), + d_ptr(new KBreadcrumbSelectionModelPrivate(this, selectionModel, direction)) +{ + if ( direction != MakeBreadcrumbSelectionInSelf) + connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(sourceSelectionChanged(QItemSelection,QItemSelection))); +} + +KBreadcrumbSelectionModel::~KBreadcrumbSelectionModel() +{ + delete d_ptr; +} + +bool KBreadcrumbSelectionModel::isActualSelectionIncluded() const +{ + Q_D(const KBreadcrumbSelectionModel); + return d->m_includeActualSelection; +} + +void KBreadcrumbSelectionModel::setActualSelectionIncluded(bool includeActualSelection) +{ + Q_D(KBreadcrumbSelectionModel); + d->m_includeActualSelection = includeActualSelection; +} + +int KBreadcrumbSelectionModel::breadcrumbLength() const +{ + Q_D(const KBreadcrumbSelectionModel); + return d->m_selectionDepth; +} + +void KBreadcrumbSelectionModel::setBreadcrumbLength(int breadcrumbLength) +{ + Q_D(KBreadcrumbSelectionModel); + d->m_selectionDepth = breadcrumbLength; +} + +QItemSelection KBreadcrumbSelectionModelPrivate::getBreadcrumbSelection(const QModelIndex& index) +{ + QItemSelection breadcrumbSelection; + + if (m_includeActualSelection) + breadcrumbSelection.append(QItemSelectionRange(index)); + + QModelIndex parent = index.parent(); + int sumBreadcrumbs = 0; + bool includeAll = m_selectionDepth < 0; + while (parent.isValid() && (includeAll || sumBreadcrumbs < m_selectionDepth)) { + breadcrumbSelection.append(QItemSelectionRange(parent)); + parent = parent.parent(); + } + return breadcrumbSelection; +} + +QItemSelection KBreadcrumbSelectionModelPrivate::getBreadcrumbSelection(const QItemSelection& selection) +{ + QItemSelection breadcrumbSelection; + + if (m_includeActualSelection) + breadcrumbSelection = selection; + + QItemSelection::const_iterator it = selection.constBegin(); + const QItemSelection::const_iterator end = selection.constEnd(); + + for ( ; it != end; ++it) + { + QModelIndex parent = it->parent(); + int sumBreadcrumbs = 0; + bool includeAll = m_selectionDepth < 0; + while (parent.isValid() && (includeAll || sumBreadcrumbs < m_selectionDepth)) + { + breadcrumbSelection.append(QItemSelectionRange(parent)); + parent = parent.parent(); + ++sumBreadcrumbs; + } + } + return breadcrumbSelection; +} + +void KBreadcrumbSelectionModel::sourceSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + Q_D(KBreadcrumbSelectionModel); + QItemSelection deselectedCrumbs = d->getBreadcrumbSelection(deselected); + QItemSelection selectedCrumbs = d->getBreadcrumbSelection(selected); + + QItemSelection removed = deselectedCrumbs; + foreach(const QItemSelectionRange &range, selectedCrumbs) + { + removed.removeAll(range); + } + + QItemSelection added = selectedCrumbs; + foreach(const QItemSelectionRange &range, deselectedCrumbs) + { + added.removeAll(range); + } + + if (!removed.isEmpty()) + { + QItemSelectionModel::select(removed, QItemSelectionModel::Deselect); + } + if (!added.isEmpty()) + { + QItemSelectionModel::select(added, QItemSelectionModel::Select); + } +} + +void KBreadcrumbSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + Q_D(KBreadcrumbSelectionModel); + // When an item is removed, the current index is set to the top index in the model. + // That causes a selectionChanged signal with a selection which we do not want. + if ( d->m_ignoreCurrentChanged ) + { + d->m_ignoreCurrentChanged = false; + return; + } + if ( d->m_direction == MakeBreadcrumbSelectionInOther ) + { + d->m_selectionModel->select(d->getBreadcrumbSelection(index), command); + QItemSelectionModel::select(index, command); + } else { + d->m_selectionModel->select(index, command); + QItemSelectionModel::select(d->getBreadcrumbSelection(index), command); + } +} + +void KBreadcrumbSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) +{ + Q_D(KBreadcrumbSelectionModel); + QItemSelection bcc = d->getBreadcrumbSelection(selection); + if ( d->m_direction == MakeBreadcrumbSelectionInOther ) + { + d->m_selectionModel->select(selection, command); + QItemSelectionModel::select(bcc, command); + } else { + d->m_selectionModel->select(bcc, command); + QItemSelectionModel::select(selection, command); + } +} + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.h new file mode 100644 index 00000000..052b8dd6 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kbreadcrumbselectionmodel.h @@ -0,0 +1,162 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef KBREADCRUMBSPROXYMODEL_H +#define KBREADCRUMBSPROXYMODEL_H + +#include +#include + +class KBreadcrumbSelectionModelPrivate; + +/** + @class KBreadcrumbSelectionModel kbreadcrumbselectionmodel.h + + @brief Selects the parents of selected items to create breadcrumbs + + For example, if the tree is + @verbatim + - A + - B + - - C + - - D + - - - E + - - - - F + @endverbatim + + and E is selected, the selection can contain + + @verbatim + - B + - D + @endverbatim + + or + + @verbatim + - B + - D + - E + @endverbatim + + if isActualSelectionIncluded is true. + + The depth of the selection may also be set. For example if the breadcrumbLength is 1: + + @verbatim + - D + - E + @endverbatim + + And if breadcrumbLength is 2: + + @verbatim + - B + - D + - E + @endverbatim + + A KBreadcrumbsProxyModel with a breadcrumbLength of 0 and including the actual selection is + the same as a KSelectionProxyModel in the KSelectionProxyModel::ExactSelection configuration. + + @code + view1->setModel(rootModel); + + QItemSelectionModel *breadcrumbSelectionModel = new QItemSelectionModel(rootModel, this); + + KBreadcrumbSelectionModel *breadcrumbProxySelector = new KBreadcrumbSelectionModel(breadcrumbSelectionModel, rootModel, this); + + view1->setSelectionModel(breadcrumbProxySelector); + + KSelectionProxyModel *breadcrumbSelectionProxyModel = new KSelectionProxyModel( breadcrumbSelectionModel, this); + breadcrumbSelectionProxyModel->setSourceModel( rootModel ); + breadcrumbSelectionProxyModel->setFilterBehavior( KSelectionProxyModel::ExactSelection ); + + view2->setModel(breadcrumbSelectionProxyModel); + @endcode + + @image html kbreadcrumbselectionmodel.png "KBreadcrumbSelectionModel in several configurations" + + This can work in two directions. One option is for a single selection in the KBreadcrumbSelectionModel to invoke + the breadcrumb selection in its constructor argument. + + The other is for a selection in the itemselectionmodel in the constructor argument to cause a breadcrumb selection + in @p this. + + @since 4.5 + +*/ +class KBreadcrumbSelectionModel : public QItemSelectionModel +{ + Q_OBJECT +public: + enum BreadcrumbTarget + { + MakeBreadcrumbSelectionInOther, + MakeBreadcrumbSelectionInSelf + }; + + explicit KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, QObject* parent = 0); + KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, BreadcrumbTarget target, QObject* parent = 0); + virtual ~KBreadcrumbSelectionModel(); + + /** + Returns whether the actual selection in included in the proxy. + + The default is true. + */ + bool isActualSelectionIncluded() const; + + /** + Set whether the actual selection in included in the proxy to @p isActualSelectionIncluded. + */ + void setActualSelectionIncluded(bool isActualSelectionIncluded); + + /** + Returns the depth that the breadcrumb selection should go to. + */ + int breadcrumbLength() const; + + /** + Sets the depth that the breadcrumb selection should go to. + + If the @p breadcrumbLength is -1, all breadcrumbs are selected. + The default is -1 + */ + void setBreadcrumbLength(int breadcrumbLength); + + /* reimp */ void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + + /* reimp */ void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command); + +private slots: + void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + +protected: + KBreadcrumbSelectionModelPrivate * const d_ptr; +private: + //@cond PRIVATE + Q_DECLARE_PRIVATE(KBreadcrumbSelectionModel) + //@cond PRIVATE +}; + + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.cpp new file mode 100644 index 00000000..c4649388 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.cpp @@ -0,0 +1,247 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "kmodelindexproxymapper.h" + +#include +#include +#include +#include + +#include + +class KModelIndexProxyMapperPrivate +{ + KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq) + : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel) + { + createProxyChain(); + } + + void createProxyChain(); + bool assertValid(); + + Q_DECLARE_PUBLIC(KModelIndexProxyMapper) + KModelIndexProxyMapper * const q_ptr; + + QList > m_proxyChainUp; + QList > m_proxyChainDown; + + QWeakPointer m_leftModel; + QWeakPointer m_rightModel; +}; + + +/* + + The idea here is that this selection model and proxySelectionModel might be in different parts of the + proxy chain. We need to build up to two chains of proxy models to create mappings between them. + + Example 1: + + Root model + | + / \ + Proxy 1 Proxy 3 + | | + Proxy 2 Proxy 4 + + Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other. + + Example 2: + + Root model + | + Proxy 1 + | + Proxy 2 + / \ + Proxy 3 Proxy 6 + | | + Proxy 4 Proxy 7 + | + Proxy 5 + + We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is + already in the first chain. + + Stephen Kelly, 30 March 2010. +*/ + +void KModelIndexProxyMapperPrivate::createProxyChain() +{ + QWeakPointer targetModel = m_rightModel; + + if (!targetModel) + return; + + if (m_leftModel == targetModel) + return; + + QList > proxyChainDown; + QWeakPointer selectionTargetProxyModel = qobject_cast(targetModel.data()); + while( selectionTargetProxyModel ) + { + proxyChainDown.prepend( selectionTargetProxyModel ); + + selectionTargetProxyModel = qobject_cast(selectionTargetProxyModel.data()->sourceModel()); + + if (selectionTargetProxyModel.data() == m_leftModel.data()) + { + m_proxyChainDown = proxyChainDown; + return; + } + } + + QWeakPointer sourceModel = m_leftModel; + QWeakPointer sourceProxyModel = qobject_cast(sourceModel.data()); + + while(sourceProxyModel) + { + m_proxyChainUp.append(sourceProxyModel); + + sourceProxyModel = qobject_cast(sourceProxyModel.data()->sourceModel()); + + const int targetIndex = proxyChainDown.indexOf(sourceProxyModel); + + if (targetIndex != -1) + { + m_proxyChainDown = proxyChainDown.mid(targetIndex, proxyChainDown.size()); + return; + } + } + m_proxyChainDown = proxyChainDown; + Q_ASSERT(assertValid()); +} + +bool KModelIndexProxyMapperPrivate::assertValid() +{ + if ( m_proxyChainDown.isEmpty()) + { + Q_ASSERT( !m_proxyChainUp.isEmpty() ); + Q_ASSERT( m_proxyChainUp.last().data()->sourceModel() == m_rightModel.data() ); + } + else if ( m_proxyChainUp.isEmpty()) + { + Q_ASSERT( !m_proxyChainDown.isEmpty() ); + Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_leftModel.data() ); + } else { + Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_proxyChainUp.last().data()->sourceModel() ); + } + return true; +} + +KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel* leftModel, const QAbstractItemModel* rightModel, QObject* parent) + : QObject(parent), d_ptr( new KModelIndexProxyMapperPrivate(leftModel, rightModel, this) ) +{ + +} + +QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex& index) const +{ + const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index)); + if (selection.isEmpty()) + return QModelIndex(); + + return selection.indexes().first(); +} + +QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex& index) const +{ + const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index)); + if (selection.isEmpty()) + return QModelIndex(); + + return selection.indexes().first(); +} + +QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection& selection) const +{ + Q_D(const KModelIndexProxyMapper); + + if (selection.isEmpty()) + return QItemSelection(); + + Q_ASSERT(selection.first().model() == d->m_leftModel.data()); + + QItemSelection seekSelection = selection; + QListIterator > iUp(d->m_proxyChainUp); + + while (iUp.hasNext()) + { + const QWeakPointer proxy = iUp.next(); + if (!proxy.data()) + return QItemSelection(); + seekSelection = proxy.data()->mapSelectionToSource(seekSelection); + } + + QListIterator > iDown(d->m_proxyChainDown); + + while (iDown.hasNext()) + { + const QWeakPointer proxy = iDown.next(); + if (!proxy.data()) + return QItemSelection(); + seekSelection = proxy.data()->mapSelectionFromSource(seekSelection); + } + + Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel.data() ) || true ); + return seekSelection; +} + +QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection& selection) const +{ + Q_D(const KModelIndexProxyMapper); + + if (selection.isEmpty()) + return QItemSelection(); + + if (selection.first().model() != d->m_rightModel.data()) + qDebug() << selection.first().model() << d->m_rightModel.data(); + Q_ASSERT(selection.first().model() == d->m_rightModel.data()); + + QItemSelection seekSelection = selection; + QListIterator > iDown(d->m_proxyChainDown); + + iDown.toBack(); + while (iDown.hasPrevious()) + { + const QWeakPointer proxy = iDown.previous(); + if (!proxy.data()) + return QItemSelection(); + seekSelection = proxy.data()->mapSelectionToSource(seekSelection); + } + + QListIterator > iUp(d->m_proxyChainUp); + + iUp.toBack(); + while (iUp.hasPrevious()) + { + const QWeakPointer proxy = iUp.previous(); + if (!proxy.data()) + return QItemSelection(); + seekSelection = proxy.data()->mapSelectionFromSource(seekSelection); + } + + Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel.data() ) || true ); + return seekSelection; +} + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.h new file mode 100644 index 00000000..a4f35288 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kmodelindexproxymapper.h @@ -0,0 +1,113 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef KMODELINDEXPROXYMAPPER_H +#define KMODELINDEXPROXYMAPPER_H + +#include + +class QAbstractItemModel; +class QModelIndex; +class QItemSelection; +class KModelIndexProxyMapperPrivate; + +/** + * @brief This class facilitates easy mapping of indexes and selections through proxy models. + * + * In a complex system of proxy models there can be a need to map indexes and selections between them, + * and sometimes to do so without knowledge of the path from one model to another. + * + * For example, + * + * @verbatim + * Root model + * | + * / \ + * Proxy 1 Proxy 3 + * | | + * Proxy 2 Proxy 4 + * @endverbatim + * + * If there is a need to map indexes between proxy 2 and proxy 4, a KModelIndexProxyMapper can be created + * to facilitate mapping of indexes between them. + * + * @code + * m_indexMapper = new KModelIndexProxyMapper(proxy2, proxy4, this); + * + * ... + * + * const QModelIndex proxy4Index = m_mapLeftToRight(proxy2->index(0, 0)); + * Q_ASSERT(proxy4Index.model() == proxy4); + * @endcode + * + * Note that the aim is to achieve black box connections so that there is no need for application code to + * know the structure of proxy models in the path between left and right and attempt to manually map them. + * + * @verbatim + * Root model + * | + * --------------- + * | Black Box | + * --------------- + * | | + * Proxy 2 Proxy 4 + * @endverbatim + * + * @author Stephen Kelly + * + */ +class KModelIndexProxyMapper : public QObject +{ + Q_OBJECT +public: + /** + * Constructor + */ + KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject* parent = 0); + + /** + * Maps the @p index from the left model to the right model. + */ + QModelIndex mapLeftToRight(const QModelIndex &index) const; + + /** + * Maps the @p index from the right model to the left model. + */ + QModelIndex mapRightToLeft(const QModelIndex &index) const; + + /** + * Maps the @p selection from the left model to the right model. + */ + QItemSelection mapSelectionLeftToRight(const QItemSelection &selection) const; + + /** + * Maps the @p selection from the right model to the left model. + */ + QItemSelection mapSelectionRightToLeft(const QItemSelection &selection) const; + +private: + //@cond PRIVATE + Q_DECLARE_PRIVATE(KModelIndexProxyMapper) + KModelIndexProxyMapperPrivate * const d_ptr; + //@endcond +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.cpp new file mode 100644 index 00000000..6d176fb9 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.cpp @@ -0,0 +1,256 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "kproxyitemselectionmodel.h" + +#include + +class KLinkItemSelectionModelPrivate +{ +public: + KLinkItemSelectionModelPrivate(KLinkItemSelectionModel *proxySelectionModel, QAbstractItemModel *model, + QItemSelectionModel *selectionModel) + : q_ptr( proxySelectionModel ), m_model(model), m_linkedItemSelectionModel(selectionModel) + { + createProxyChain(); + } + + void createProxyChain(); + + bool assertValid(); + + Q_DECLARE_PUBLIC(KLinkItemSelectionModel) + KLinkItemSelectionModel * const q_ptr; + + QList m_proxyChainUp; + QList m_proxyChainDown; + + QAbstractItemModel *m_model; + QItemSelectionModel *m_linkedItemSelectionModel; + bool m_ignoreCurrentChanged; +}; + +/* + + The idea here is that this selection model and proxySelectionModel might be in different parts of the + proxy chain. We need to build up to two chains of proxy models to create mappings between them. + + Example 1: + + Root model + | + / \ + Proxy 1 Proxy 3 + | | + Proxy 2 Proxy 4 + + Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other. + + Example 2: + + Root model + | + Proxy 1 + | + Proxy 2 + / \ + Proxy 3 Proxy 6 + | | + Proxy 4 Proxy 7 + | + Proxy 5 + + We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is + already in the first chain. + + Stephen Kelly, 30 March 2010. +*/ + +void KLinkItemSelectionModelPrivate::createProxyChain() +{ + const QAbstractItemModel *selectionTargetModel = m_linkedItemSelectionModel->model(); + + if (m_model == selectionTargetModel) + return; + + QList proxyChainDown; + + const QAbstractProxyModel *selectionTargetProxyModel = qobject_cast( selectionTargetModel ); + + while( selectionTargetProxyModel ) + { + proxyChainDown.prepend( selectionTargetProxyModel ); + + selectionTargetProxyModel = qobject_cast(selectionTargetProxyModel->sourceModel()); + + if ( selectionTargetProxyModel == m_model ) + { + m_proxyChainDown = proxyChainDown; + return; + } + } + + const QAbstractItemModel *sourceModel = m_model; + const QAbstractProxyModel *sourceProxyModel = qobject_cast( sourceModel ); + + while( sourceProxyModel ) + { + m_proxyChainUp.append( sourceProxyModel ); + + sourceProxyModel = qobject_cast(sourceProxyModel->sourceModel()); + + int targetIndex = proxyChainDown.indexOf(sourceProxyModel ); + + if ( targetIndex != -1 ) + { + m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size()); + return; + } + } + m_proxyChainDown = proxyChainDown; + Q_ASSERT(assertValid()); +} + +bool KLinkItemSelectionModelPrivate::assertValid() +{ + qDebug() << m_proxyChainDown << m_proxyChainUp; + if ( m_proxyChainDown.isEmpty()) + { + Q_ASSERT( !m_proxyChainUp.isEmpty() ); + Q_ASSERT( m_proxyChainUp.last()->sourceModel() == m_linkedItemSelectionModel->model() ); + } + else if ( m_proxyChainUp.isEmpty()) + { + Q_ASSERT( !m_proxyChainDown.isEmpty() ); + Q_ASSERT( m_proxyChainDown.first()->sourceModel() == m_model ); + } else { + Q_ASSERT( m_proxyChainDown.first()->sourceModel() == m_proxyChainUp.last()->sourceModel() ); + } + return true; +} + +KLinkItemSelectionModel::KLinkItemSelectionModel( QAbstractItemModel *model, QItemSelectionModel *proxySelector, QObject *parent) + : QItemSelectionModel(model, parent), + d_ptr(new KLinkItemSelectionModelPrivate(this, model, proxySelector)) +{ + connect(proxySelector, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(sourceSelectionChanged(QItemSelection,QItemSelection))); +} + +QItemSelection KLinkItemSelectionModel::mapSelectionFromSource(const QModelIndex& sourceIndex) const +{ + return mapSelectionFromSource(QItemSelection(sourceIndex, sourceIndex)); +} + +QItemSelection KLinkItemSelectionModel::mapSelectionFromSource(const QItemSelection& sourceSelection) const +{ + Q_D(const KLinkItemSelectionModel); + + QItemSelection seekSelection = sourceSelection; + QListIterator iUp(d->m_proxyChainUp); + + while (iUp.hasNext()) + { + const QAbstractProxyModel *proxy = iUp.next(); + seekSelection = proxy->mapSelectionToSource(seekSelection); + } + + QListIterator iDown(d->m_proxyChainDown); + + while (iDown.hasNext()) + { + const QAbstractProxyModel *proxy = iDown.next(); + seekSelection = proxy->mapSelectionFromSource(seekSelection); + } + + Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_linkedItemSelectionModel->model() ) || true ); + return seekSelection; +} + +QItemSelection KLinkItemSelectionModel::mapSelectionToSource(const QModelIndex& sourceIndex) const +{ + return mapSelectionToSource(QItemSelection(sourceIndex, sourceIndex)); +} + +QItemSelection KLinkItemSelectionModel::mapSelectionToSource(const QItemSelection& proxySelection) const +{ + Q_D(const KLinkItemSelectionModel); + + QItemSelection seekSelection = proxySelection; + QListIterator iDown(d->m_proxyChainDown); + + iDown.toBack(); + while (iDown.hasPrevious()) + { + const QAbstractProxyModel *proxy = iDown.previous(); + seekSelection = proxy->mapSelectionToSource(seekSelection); + } + + QListIterator iUp(d->m_proxyChainUp); + + iUp.toBack(); + while (iUp.hasPrevious()) + { + const QAbstractProxyModel *proxy = iUp.previous(); + seekSelection = proxy->mapSelectionFromSource(seekSelection); + } + + Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_model ) || true ); + return seekSelection; +} + +void KLinkItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + Q_D(KLinkItemSelectionModel); + // When an item is removed, the current index is set to the top index in the model. + // That causes a selectionChanged signal with a selection which we do not want. + if ( d->m_ignoreCurrentChanged ) + { + return; + } + QItemSelectionModel::select(index, command); + if (index.isValid()) + d->m_linkedItemSelectionModel->select(mapSelectionFromSource(index), command); + else + { + d->m_linkedItemSelectionModel->clearSelection(); + } +} + +void KLinkItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) +{ + Q_D(KLinkItemSelectionModel); + d->m_ignoreCurrentChanged = true; + QItemSelectionModel::select(selection, command); + d->m_linkedItemSelectionModel->select(mapSelectionFromSource(selection), command); + d->m_ignoreCurrentChanged = false; +} + +void KLinkItemSelectionModel::sourceSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + QItemSelection mappedDeselection = mapSelectionToSource(deselected); + QItemSelection mappedSelection = mapSelectionToSource(selected); + + QItemSelectionModel::select(mappedDeselection, Deselect); + QItemSelectionModel::select(mappedSelection, Select); +} + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.h new file mode 100644 index 00000000..d1560c24 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kproxyitemselectionmodel.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef KPROXYITEMSELECTIONMODEL_H +#define KPROXYITEMSELECTIONMODEL_H + +#include +#include + +class KLinkItemSelectionModelPrivate; + +/** + @brief Makes it possible to share a selection in multiple views which do not have the same source model + + Although multiple views can share the same QItemSelectionModel, the views then need to have the same source model. + + If there is a proxy model between the model and one of the views, or different proxy models in each, this class makes + it possible to share the selection between the views. + + @image html kproxyitemselectionmodel-simple.png "Sharing a QItemSelectionModel between views on the same model is trivial" + @image html kproxyitemselectionmodel-error.png "If a proxy model is used, it is no longer possible to share the QItemSelectionModel directly" + @image html kproxyitemselectionmodel-solution.png "A KProxyItemSelectionModel can be used to map the selection through the proxy model" + @image html kproxyitemselectionmodel-complex.png "Arbitrarily complex proxy configurations on the same root model can be used" + + @since 4.5 + +*/ +class KLinkItemSelectionModel : public QItemSelectionModel +{ + Q_OBJECT +public: + /** + Constructor. + */ + KLinkItemSelectionModel( QAbstractItemModel *targetModel, QItemSelectionModel *proxySelector, QObject *parent = 0); + + /* reimp */ void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + /* reimp */ void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command); + +private: + /** + Maps the @p sourceIndex though the proxy models to a selection in the proxySelector + */ + QItemSelection mapSelectionFromSource( const QModelIndex &sourceIndex ) const; + QItemSelection mapSelectionFromSource( const QItemSelection &sourceSelection ) const; + QItemSelection mapSelectionToSource( const QModelIndex &sourceIndex ) const; + QItemSelection mapSelectionToSource( const QItemSelection &sourceSelection ) const; + +private slots: + void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + +protected: + KLinkItemSelectionModelPrivate * const d_ptr; + +private: + Q_DECLARE_PRIVATE(KLinkItemSelectionModel) +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.cpp new file mode 100644 index 00000000..479d2d4c --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.cpp @@ -0,0 +1,54 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "kresettingproxymodel.h" + +KResettingProxyModel::KResettingProxyModel(QObject* parent) + : QSortFilterProxyModel(parent) +{ + +} + +void KResettingProxyModel::setSourceModel(QAbstractItemModel* sourceModel) +{ + connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(slotBeginReset())); + connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotEndReset())); + connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotBeginReset())); + connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotEndReset())); + + QSortFilterProxyModel::setSourceModel(sourceModel); + disconnect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + disconnect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + +} + +void KResettingProxyModel::slotBeginReset() +{ + QMetaObject::invokeMethod(this, "_q_sourceAboutToBeReset", Qt::DirectConnection); +} + +void KResettingProxyModel::slotEndReset() +{ + QMetaObject::invokeMethod(this, "_q_sourceReset", Qt::DirectConnection); +} + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.h new file mode 100644 index 00000000..6429ab4e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kresettingproxymodel.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef KRESETTINGPROXYMODEL +#define KRESETTINGPROXYMODEL + +#include + +/** + * This class is a workaround for buggy handling of insert/remove signals in QML: + * http://bugreports.qt.nokia.com/browse/QTBUG-11644 + */ +class KResettingProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + KResettingProxyModel(QObject* parent = 0); + + virtual void setSourceModel(QAbstractItemModel* sourceModel); + +private slots: + void slotBeginReset(); + void slotEndReset(); + +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.cpp new file mode 100644 index 00000000..755e187e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.cpp @@ -0,0 +1,2314 @@ +/* + Copyright (c) 2009 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 "kselectionproxymodel.h" + +#include +#include +#include + +/** + Return true if @p idx is a descendant of one of the indexes in @p list. + Note that this returns false if @p list contains @p idx. +*/ +template +bool isDescendantOf(const QList &list, const QModelIndex &idx) +{ + if (!idx.isValid()) + return false; + + if (list.contains(idx)) + return false; + + QModelIndex parent = idx.parent(); + while (parent.isValid()) { + if (list.contains(parent)) + return true; + parent = parent.parent(); + } + return false; +} + +static bool isDescendantOf(const QModelIndex &ancestor, const QModelIndex &descendant) +{ + if (!descendant.isValid()) + return false; + + if (ancestor == descendant) + return false; + + QModelIndex parent = descendant.parent(); + while (parent.isValid()) { + if (parent == ancestor) + return true; + + parent = parent.parent(); + } + return false; +} + +static bool isDescendantOf(const QItemSelection &selection, const QModelIndex &descendant) +{ + if (!descendant.isValid()) + return false; + + if (selection.contains(descendant)) + return false; + + QModelIndex parent = descendant.parent(); + while (parent.isValid()) { + if (selection.contains(parent)) + return true; + + parent = parent.parent(); + } + return false; +} + +static bool isDescendantOf(const QItemSelectionRange &range, const QModelIndex &descendant) +{ + if (!descendant.isValid()) + return false; + + if (range.contains(descendant)) + return false; + + QModelIndex parent = descendant.parent(); + while (parent.isValid()) { + if (range.contains(parent)) + return true; + + parent = parent.parent(); + } + return false; +} + +static int _getRootListRow(const QList &rootAncestors, const QModelIndex &index) +{ + QModelIndex commonParent = index; + QModelIndex youngestAncestor; + + int firstCommonParent = -1; + int bestParentRow = -1; + while (commonParent.isValid()) { + youngestAncestor = commonParent; + commonParent = commonParent.parent(); + + for (int i = 0; i < rootAncestors.size(); ++i) { + const QModelIndexList ancestorList = rootAncestors.at(i); + + const int parentRow = ancestorList.indexOf(commonParent); + + if (parentRow < 0) + continue; + + if (parentRow > bestParentRow) { + firstCommonParent = i; + bestParentRow = parentRow; + } + } + + if (firstCommonParent >= 0) + break; + } + + // If @p list is non-empty, the invalid QModelIndex() will at least be found in ancestorList. + Q_ASSERT(firstCommonParent >= 0); + + const QModelIndexList firstAnsList = rootAncestors.at(firstCommonParent); + + const QModelIndex eldestSibling = firstAnsList.value(bestParentRow + 1); + + if (eldestSibling.isValid()) { + // firstCommonParent is a sibling of one of the ancestors of @p index. + // It is the first index to share a common parent with one of the ancestors of @p index. + if (eldestSibling.row() >= youngestAncestor.row()) + return firstCommonParent; + } + + int siblingOffset = 1; + + // The same commonParent might be common to several root indexes. + // If this is the last in the list, it's the only match. We instruct the model + // to insert the new index after it ( + siblingOffset). + if (rootAncestors.size() <= firstCommonParent + siblingOffset) { + return firstCommonParent + siblingOffset; + } + + // A + // - B + // - C + // - D + // - E + // F + // + // F is selected, then C then D. When inserting D into the model, the commonParent is B (the parent of C). + // The next existing sibling of B is F (in the proxy model). bestParentRow will then refer to an index on + // the level of a child of F (which doesn't exist - Boom!). If it doesn't exist, then we've already found + // the place to insert D + QModelIndexList ansList = rootAncestors.at(firstCommonParent + siblingOffset); + if (ansList.size() <= bestParentRow) { + return firstCommonParent + siblingOffset; + } + + QModelIndex nextParent = ansList.at(bestParentRow); + while (nextParent == commonParent) { + if (ansList.size() < bestParentRow + 1) + // If the list is longer, it means that at the end of it is a descendant of the new index. + // We insert the ancestors items first in that case. + break; + + const QModelIndex nextSibling = ansList.value(bestParentRow + 1); + + if (!nextSibling.isValid()) { + continue; + } + + if (youngestAncestor.row() <= nextSibling.row()) { + break; + } + + siblingOffset++; + + if (rootAncestors.size() <= firstCommonParent + siblingOffset) + break; + + ansList = rootAncestors.at(firstCommonParent + siblingOffset); + + // In the scenario above, E is selected after D, causing this loop to be entered, + // and requiring a similar result if the next sibling in the proxy model does not have children. + if (ansList.size() <= bestParentRow) { + break; + } + + nextParent = ansList.at(bestParentRow); + } + + return firstCommonParent + siblingOffset; +} + +/** + Determines the correct location to insert @p index into @p list. +*/ +template +static int getRootListRow(const QList &list, const QModelIndex &index) +{ + if (!index.isValid()) + return -1; + + if (list.isEmpty()) + return 0; + + // What's going on? + // Consider a tree like + // + // A + // - B + // - - C + // - - - D + // - E + // - F + // - - G + // - - - H + // - I + // - - J + // - K + // + // If D, E and J are already selected, and H is newly selected, we need to put H between E and J in the proxy model. + // To figure that out, we create a list for each already selected index of its ancestors. Then, + // we climb the ancestors of H until we reach an index with siblings which have a descendant + // selected (F above has siblings B, E and I which have descendants which are already selected). + // Those child indexes are traversed to find the right sibling to put F beside. + // + // i.e., new items are inserted in the expected location. + + QList rootAncestors; + foreach(const QModelIndex &root, list) { + QModelIndexList ancestors; + ancestors << root; + QModelIndex parent = root.parent(); + while (parent.isValid()) { + ancestors.prepend(parent); + parent = parent.parent(); + } + ancestors.prepend(QModelIndex()); + rootAncestors << ancestors; + } + return _getRootListRow(rootAncestors, index); +} + +/** + Returns a selection in which no descendants of selected indexes are also themselves selected. + For example, + @code + A + - B + C + D + @endcode + If A, B and D are selected in @p selection, the returned selection contains only A and D. +*/ +static QItemSelection getRootRanges(const QItemSelection &_selection) +{ + QItemSelection rootSelection; + QItemSelection selection = _selection; + QList::iterator it = selection.begin(); + while (it != selection.end()) { + if (!it->topLeft().parent().isValid()) + { + rootSelection.append(*it); + it = selection.erase(it); + } else + ++it; + } + + it = selection.begin(); + const QList::iterator end = selection.end(); + while ( it != end ) { + const QItemSelectionRange range = *it; + it = selection.erase(it); + + if (isDescendantOf(rootSelection, range.topLeft()) || isDescendantOf(selection, range.topLeft())) + continue; + + rootSelection << range; + } + return rootSelection; +} + +template +ForwardIterator kMaxElement(ForwardIterator it, ForwardIterator end) +{ + ForwardIterator result = it; + for ( ; it != end; ++it) + { + if (*result < *it) + result = it; + } + return result; +} +/** + */ +struct RangeLessThan +{ + bool operator()(const QItemSelectionRange &left, const QItemSelectionRange &right) + { + if (right.model() == left.model()) { + // parent has to be calculated, so we only do so once. + const QModelIndex topLeftParent = left.parent(); + const QModelIndex otherTopLeftParent = right.parent(); + if (topLeftParent == otherTopLeftParent) { + if (right.top() == left.top()) { + if (right.left() == left.left()) { + if (right.bottom() == left.bottom()) { + return left.right() < right.right(); + } + return left.bottom() < right.bottom(); + } + return left.left() < right.left(); + } + return left.top() < right.top(); + } + return topLeftParent < otherTopLeftParent; + } + return left.model() < right.model(); + } +}; + +static QItemSelection stableNormalizeSelection(QItemSelection selection) +{ + if (selection.size() <= 1) + return selection; + + QList::iterator it = selection.begin(); + + Q_ASSERT(it != selection.end()); + QList::iterator scout = it + 1; + while (scout != selection.end()) { + Q_ASSERT(it != selection.end()); + int bottom = it->bottom(); + while (scout != selection.end() && it->parent() == scout->parent() && bottom + 1 == scout->top()) { + bottom = scout->bottom(); + scout = selection.erase(scout); + } + if (bottom != it->bottom()) { + const QModelIndex topLeft = it->topLeft(); + *it = QItemSelectionRange(topLeft, topLeft.sibling(bottom, it->right())); + } + Q_ASSERT(it != scout); + if (scout == selection.end()) + break; + it = scout; + ++scout; + } + return selection; +} + +static QItemSelection normalizeSelection(QItemSelection selection) +{ + if (selection.size() <= 1) + return selection; + + RangeLessThan lt; + qSort(selection.begin(), selection.end(), lt); + return stableNormalizeSelection(selection); +} + + +KSelectionProxyModelPrivate::KSelectionProxyModelPrivate(KSelectionProxyModel* model, QItemSelectionModel* selectionModel) + : q_ptr(model), + m_startWithChildTrees(false), + m_omitChildren(false), + m_omitDescendants(false), + m_includeAllSelected(false), + m_rowsInserted(false), + m_rowsRemoved(false), + m_rowsMoved(false), + m_resetting(false), + m_ignoreNextLayoutAboutToBeChanged(false), + m_ignoreNextLayoutChanged(false), + m_selectionModel(selectionModel), + m_nextId(1) +{ + // QItemSelectionModel doesn't clear its selection when its model is reset so we do it manually here. + // Fixed in Qt 4.7: http://qt.gitorious.org/qt/qt/merge_requests/639 + QObject::connect(selectionModel->model(), SIGNAL(modelAboutToBeReset()), selectionModel, SLOT(clear())); +} + +void KSelectionProxyModelPrivate::emitContinuousRanges(const QModelIndex &sourceFirst, const QModelIndex &sourceLast, + const QModelIndex &proxyFirst, const QModelIndex &proxyLast) +{ + Q_Q(KSelectionProxyModel); + + const int proxyRangeSize = proxyLast.row() - proxyFirst.row(); + const int sourceRangeSize = sourceLast.row() - sourceFirst.row(); + + if (proxyRangeSize == sourceRangeSize) { + emit q->dataChanged(proxyFirst, proxyLast); + return; + } + + + // TODO: Loop to skip descendant ranges. +// int lastRow; +// +// const QModelIndex sourceHalfWay = sourceFirst.sibling(sourceFirst.row() + (sourceRangeSize / 2)); +// const QModelIndex proxyHalfWay = proxyFirst.sibling(proxyFirst.row() + (proxyRangeSize / 2)); +// const QModelIndex mappedSourceHalfway = q->mapToSource(proxyHalfWay); +// +// const int halfProxyRange = mappedSourceHalfway.row() - proxyFirst.row(); +// const int halfSourceRange = sourceHalfWay.row() - sourceFirst.row(); +// +// if (proxyRangeSize == sourceRangeSize) +// { +// emit q->dataChanged(proxyFirst, proxyLast.sibling(proxyFirst.row() + proxyRangeSize, proxyLast.column())); +// return; +// } + + emit q->dataChanged(proxyFirst, proxyLast); +} + +void KSelectionProxyModelPrivate::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_Q(KSelectionProxyModel); + + const QModelIndex proxyTopLeft = q->mapFromSource(topLeft); + const QModelIndex proxyBottomRight = q->mapFromSource(bottomRight); + + const QModelIndex proxyRangeParent = proxyTopLeft.parent(); + + if (!m_omitChildren && m_omitDescendants && m_startWithChildTrees && m_includeAllSelected) { + // ChildrenOfExactSelection + if (proxyTopLeft.isValid()) + emitContinuousRanges(topLeft, bottomRight, proxyTopLeft, proxyBottomRight); + return; + } + + if ((m_omitChildren && !m_startWithChildTrees && m_includeAllSelected) + || (!proxyRangeParent.isValid() && !m_startWithChildTrees)) { + // Exact selection and SubTreeRoots and SubTrees in top level + // Emit continuous ranges. + QList changedRows; + for (int row = topLeft.row(); row < bottomRight.row(); ++row) { + const QModelIndex index = q->sourceModel()->index(row, topLeft.column(), topLeft.parent()); + const int idx = m_rootIndexList.indexOf(index); + if (idx != -1) { + changedRows.append(idx); + } + } + if (changedRows.isEmpty()) + return; + int first = changedRows.first(); + int previous = first; + QList::const_iterator it = changedRows.constBegin(); + const QList::const_iterator end = changedRows.constEnd(); + for ( ; it != end; ++it) { + if (*it == previous + 1) { + ++previous; + } else { + const QModelIndex _top = q->index(first, topLeft.column()); + const QModelIndex _bottom = q->index(previous, bottomRight.column()); + emit q->dataChanged(_top, _bottom); + previous = first = *it; + } + } + if (first != previous) { + const QModelIndex _top = q->index(first, topLeft.column()); + const QModelIndex _bottom = q->index(previous, bottomRight.column()); + emit q->dataChanged(_top, _bottom); + } + return; + } + if (proxyRangeParent.isValid()) { + if (m_omitChildren && !m_startWithChildTrees && !m_includeAllSelected) + // SubTreeRoots + return; + if (!proxyTopLeft.isValid()) + return; + // SubTrees and SubTreesWithoutRoots + emit q->dataChanged(proxyTopLeft, proxyBottomRight); + return; + } + + if (m_startWithChildTrees && !m_omitChildren && !m_includeAllSelected && !m_omitDescendants) { + // SubTreesWithoutRoots + if (proxyTopLeft.isValid()) + emit q->dataChanged(proxyTopLeft, proxyBottomRight); + return; + } +} + +void KSelectionProxyModelPrivate::sourceLayoutAboutToBeChanged() +{ + Q_Q(KSelectionProxyModel); + + if (m_ignoreNextLayoutAboutToBeChanged) { + m_ignoreNextLayoutAboutToBeChanged = false; + return; + } + + if (!m_selectionModel->hasSelection()) + return; + + emit q->layoutAboutToBeChanged(); + + QPersistentModelIndex srcPersistentIndex; + foreach(const QPersistentModelIndex &proxyPersistentIndex, q->persistentIndexList()) { + m_proxyIndexes << proxyPersistentIndex; + Q_ASSERT(proxyPersistentIndex.isValid()); + srcPersistentIndex = q->mapToSource(proxyPersistentIndex); + Q_ASSERT(srcPersistentIndex.isValid()); + m_layoutChangePersistentIndexes << srcPersistentIndex; + } + + SourceProxyIndexMapping::left_const_iterator parentIt = m_mappedParents.leftConstBegin(); + const SourceProxyIndexMapping::left_const_iterator parentEnd = m_mappedParents.leftConstEnd(); + for ( ; parentIt != parentEnd; ++parentIt) { + m_sourcePersistentParents << parentIt.key(); + m_weakSourceParents << parentIt.key(); + m_sourceGrandParents << parentIt.key().parent(); + } + SourceProxyIndexMapping::left_const_iterator firstChildIt = m_mappedFirstChildren.leftConstBegin(); + const SourceProxyIndexMapping::left_const_iterator firstChildEnd = m_mappedFirstChildren.leftConstEnd(); + for ( ; firstChildIt != firstChildEnd; ++firstChildIt) { + m_sourcePersistentFirstChilds << firstChildIt.key(); + m_weakSourceFirstChilds << firstChildIt.key(); + m_sourceFirstChildParents << firstChildIt.key().parent(); + } + QList::const_iterator rootIt = m_rootIndexList.constBegin(); + const QList::const_iterator rootEnd = m_rootIndexList.constEnd(); + for ( ; rootIt != rootEnd; ++rootIt) { + m_weakRootIndexes << *rootIt; + m_weakRootParents << rootIt->parent(); + } +} + +void KSelectionProxyModelPrivate::refreshFirstChildMappings(QList &mappings) +{ + Q_Q(KSelectionProxyModel); + + QList::const_iterator it = mappings.constBegin(); + const QList::const_iterator end = mappings.constEnd(); + + for ( ; it != end; ++it) { + if (!it->isValid()) { + m_mappedFirstChildren.removeLeft(*it); + continue; + } + if (!it->parent().isValid()) + continue; + + Q_ASSERT(m_mappedFirstChildren.leftContains(*it)); + m_mappedFirstChildren.removeLeft(*it); + + int _newProxyRow = 0; + const int rootListRow = m_rootIndexList.indexOf(it->parent()); + for (int i = 0; i < rootListRow; ++i) + _newProxyRow += q->sourceModel()->rowCount(m_rootIndexList.at(i)); + + createFirstChildMapping(it->parent(), _newProxyRow); + } +} + +void KSelectionProxyModelPrivate::refreshParentMappings(QList &mappings) +{ + Q_Q(KSelectionProxyModel); + + const int lastSize = mappings.size(); + + QList::iterator it = mappings.begin(); + + while (it != mappings.end()) { + if (!it->isValid()) { + m_parentIds.removeRight(m_mappedParents.takeLeft(*it)); + it = mappings.erase(it); + continue; + } + if (it->parent().isValid() || m_mappedParents.leftContains(it->parent()) || m_rootIndexList.contains(*it)) { + const qint64 id = m_parentIds.takeRight(m_mappedParents.takeLeft(*it)); + const QModelIndex newProxyIndex = q->mapFromSource(*it); + m_mappedParents.insert(*it, newProxyIndex); + m_parentIds.insert(id, newProxyIndex); + + it = mappings.erase(it); + } else + ++it; + } + if (!mappings.isEmpty()) { + if (lastSize == mappings.size()) { + Q_ASSERT(!"Something went very wrong"); + return; + } + refreshParentMappings(mappings); + } +} + +void KSelectionProxyModelPrivate::sourceLayoutChanged() +{ + Q_Q(KSelectionProxyModel); + + if (m_ignoreNextLayoutChanged) { + m_ignoreNextLayoutChanged = false; + return; + } + + if (!m_selectionModel->hasSelection()) { + return; + } + + QList parentMappingsToUpdate; + for (int i = 0; i < m_sourcePersistentParents.size(); ++i) { + const QPersistentModelIndex persistentIndex = m_sourcePersistentParents.at(i); + const QModelIndex nonPersistentIndex = m_weakSourceParents.at(i); + if (persistentIndex.row() == nonPersistentIndex.row() + && persistentIndex.column() == nonPersistentIndex.column() + && persistentIndex.parent() == m_sourceGrandParents.at(i)) + continue; + + parentMappingsToUpdate << persistentIndex; + } + if (!parentMappingsToUpdate.isEmpty()) + refreshParentMappings(parentMappingsToUpdate); + + QList changedRoots; + QList::iterator rootIt = m_rootIndexList.begin(); + const QList::iterator rootEnd = m_rootIndexList.end(); + int rootPosition = 0; + while (rootIt != rootEnd) { + const QPersistentModelIndex persistentIndex = *rootIt; + const QModelIndex nonPersistentIndex = m_weakRootIndexes.at(rootPosition); + const QModelIndex weakParent = m_weakRootParents.at(rootPosition); + ++rootPosition; + if (persistentIndex.row() == nonPersistentIndex.row() + && persistentIndex.column() == nonPersistentIndex.column() + && persistentIndex.parent() == weakParent) { + ++rootIt; + continue; + } + + if (persistentIndex.isValid()) + changedRoots << persistentIndex; + + rootIt = m_rootIndexList.erase(rootIt); + } + foreach(const QPersistentModelIndex &idx, changedRoots) { + int row = getRootListRow(m_rootIndexList, idx); + m_rootIndexList.insert(row, idx); + } + + QList firstChildsToUpdate; + for (int i = 0; i < m_sourcePersistentFirstChilds.size(); ++i) { + const QPersistentModelIndex persistentIndex = m_sourcePersistentFirstChilds.at(i); + const QModelIndex nonPersistentIndex = m_weakSourceFirstChilds.at(i); + if (persistentIndex.row() == nonPersistentIndex.row() + && persistentIndex.column() == nonPersistentIndex.column() + && persistentIndex.parent() == m_sourceFirstChildParents.at(i)) + continue; + + firstChildsToUpdate << persistentIndex; + } + if (!firstChildsToUpdate.isEmpty()) + refreshFirstChildMappings(firstChildsToUpdate); + + for (int i = 0; i < m_proxyIndexes.size(); ++i) { + // Need to update m_mappedParents and m_mappedFirstChildren and m_parentIds before trying mapFromSource. + q->changePersistentIndex(m_proxyIndexes.at(i), q->mapFromSource(m_layoutChangePersistentIndexes.at(i))); + } + + m_layoutChangePersistentIndexes.clear(); + m_proxyIndexes.clear(); + + m_weakSourceParents.clear(); + m_weakSourceFirstChilds.clear(); + m_weakRootIndexes.clear(); + m_weakRootParents.clear(); + m_sourceFirstChildParents.clear(); + m_sourceGrandParents.clear(); + m_sourcePersistentParents.clear(); + m_sourcePersistentFirstChilds.clear(); + + emit q->layoutChanged(); +} + +void KSelectionProxyModelPrivate::resetInternalData() +{ + m_rootIndexList.clear(); + m_layoutChangePersistentIndexes.clear(); + m_proxyIndexes.clear(); + m_weakSourceParents.clear(); + m_weakSourceFirstChilds.clear(); + m_weakRootIndexes.clear(); + m_weakRootParents.clear(); + m_sourceFirstChildParents.clear(); + m_sourceGrandParents.clear(); + m_sourcePersistentParents.clear(); + m_sourcePersistentFirstChilds.clear(); + m_mappedParents.clear(); + m_parentIds.clear(); + m_mappedFirstChildren.clear(); + m_nextId = 1; +} + +void KSelectionProxyModelPrivate::sourceModelDestroyed() +{ + Q_Q(KSelectionProxyModel); + // There is very little we can do here. + resetInternalData(); + m_resetting = false; + q->endResetModel(); +} + +void KSelectionProxyModelPrivate::sourceModelAboutToBeReset() +{ + Q_Q(KSelectionProxyModel); + + // Deselecting an index in the selectionModel will cause it to + // be removed from m_rootIndexList, so we don't need to clear + // the list here manually. + // We also don't need to notify that an index is about to be removed. + m_selectionModel->clearSelection(); + + q->beginResetModel(); + m_resetting = true; +} + +void KSelectionProxyModelPrivate::sourceModelReset() +{ + Q_Q(KSelectionProxyModel); + + // No need to try to refill this. When the model is reset it doesn't have a meaningful selection anymore, + // but when it gets one we'll be notified anyway. + resetInternalData(); + m_resetting = false; + q->endResetModel(); +} + +int KSelectionProxyModelPrivate::getProxyInitialRow(const QModelIndex &parent) const +{ + Q_ASSERT(m_rootIndexList.contains(parent)); + + // The difficulty here is that parent and parent.parent() might both be in the m_rootIndexList. + + // - A + // - B + // - - C + // - - D + // - - - E + + // Consider that B and D are selected. The proxy model is: + + // - C + // - D + // - E + + // Then D gets a new child at 0. In that case we require adding F between D and E. + + // Consider instead that D gets removed. Then @p parent will be B. + + + Q_Q(const KSelectionProxyModel); + int parentPosition = m_rootIndexList.indexOf(parent); + + QModelIndex parentAbove; + + // If parentPosition == 0, then parent.parent() is not also in the model. (ordering is preserved) + while (parentPosition > 0) { + parentPosition--; + + parentAbove = m_rootIndexList.at(parentPosition); + Q_ASSERT(parentAbove.isValid()); + + int rows = q->sourceModel()->rowCount(parentAbove); + if (rows > 0) { + QModelIndex sourceIndexAbove = q->sourceModel()->index(rows - 1, 0, parentAbove); + Q_ASSERT(sourceIndexAbove.isValid()); + QModelIndex proxyChildAbove = q->mapFromSource(sourceIndexAbove); + Q_ASSERT(proxyChildAbove.isValid()); + return proxyChildAbove.row() + 1; + } + } + return 0; +} + +void KSelectionProxyModelPrivate::updateFirstChildMapping(const QModelIndex& parent, int offset) +{ + Q_Q(KSelectionProxyModel); + + static const int column = 0; + static const int row = 0; + + const QPersistentModelIndex srcIndex = q->sourceModel()->index(row, column, parent); + + const QPersistentModelIndex previousFirstChild = q->sourceModel()->index(offset, column, parent); + + SourceProxyIndexMapping::left_iterator it = m_mappedFirstChildren.findLeft(previousFirstChild); + if (it == m_mappedFirstChildren.leftEnd()) + return; + + Q_ASSERT(srcIndex.isValid()); + const QModelIndex proxyIndex = it.value(); + Q_ASSERT(proxyIndex.isValid()); + Q_ASSERT(proxyIndex.model() == q); + + m_mappedFirstChildren.eraseLeft(it); + + // The proxy index in the mapping has already been updated by the offset in updateInternalTopIndexes + // so we restore it by applying the reverse. + const QModelIndex newProxyIndex = q->createIndex(proxyIndex.row() - offset, proxyIndex.column()); + m_mappedFirstChildren.insert(srcIndex, newProxyIndex); +} + +QPair< int, int > KSelectionProxyModelPrivate::beginInsertRows(const QModelIndex& parent, int start, int end) const +{ + Q_Q(const KSelectionProxyModel); + + const QModelIndex proxyParent = q->mapFromSource(parent); + + if (!m_startWithChildTrees) { + // SubTrees + if (proxyParent.isValid()) + return qMakePair(start, end); + return qMakePair(-1, -1); + } + + if (!m_includeAllSelected && proxyParent.isValid()) { + // SubTreesWithoutRoots deeper than topLevel + return qMakePair(start, end); + } + + if (!m_rootIndexList.contains(parent)) + return qMakePair(-1, -1); + + const int proxyStartRow = getProxyInitialRow(parent) + start; + return qMakePair(proxyStartRow, proxyStartRow + (end - start)); +} + +void KSelectionProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) +{ + Q_Q(KSelectionProxyModel); + + if (!m_selectionModel->hasSelection()) + return; + + if (m_omitChildren) + // ExactSelection and SubTreeRoots + return; + + // topLevel insertions can be ignored because topLevel items would need to be selected to affect the proxy. + if (!parent.isValid()) + return; + + QPair pair = beginInsertRows(parent, start, end); + if (pair.first == -1) + return; + + const QModelIndex proxyParent = m_startWithChildTrees ? QModelIndex() : q->mapFromSource(parent); + + m_rowsInserted = true; + q->beginInsertRows(proxyParent, pair.first, pair.second); +} + +void KSelectionProxyModelPrivate::endInsertRows(const QModelIndex& parent, int start, int end) +{ + Q_Q(const KSelectionProxyModel); + const QModelIndex proxyParent = q->mapFromSource(parent); + const int offset = end - start + 1; + + const bool isNewParent = (q->sourceModel()->rowCount(parent) == offset); + + if (m_startWithChildTrees && m_rootIndexList.contains(parent)) { + const int proxyInitialRow = getProxyInitialRow(parent); + Q_ASSERT(proxyInitialRow >= 0); + const int proxyStartRow = proxyInitialRow + start; + + updateInternalTopIndexes(proxyStartRow, offset); + if (isNewParent) + createFirstChildMapping(parent, proxyStartRow); + else if (start == 0) + // We already have a first child mapping, but what we have mapped is not the first child anymore + // so we need to update it. + updateFirstChildMapping(parent, end + 1); + } else { + Q_ASSERT(proxyParent.isValid()); + if (!isNewParent) + updateInternalIndexes(proxyParent, start, offset); + else + createParentMappings(parent.parent(), parent.row(), parent.row()); + } + createParentMappings(parent, start, end); +} + +void KSelectionProxyModelPrivate::sourceRowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_Q(KSelectionProxyModel); + + if (!m_rowsInserted) + return; + m_rowsInserted = false; + endInsertRows(parent, start, end); + q->endInsertRows(); +} + +QPair KSelectionProxyModelPrivate::beginRemoveRows(const QModelIndex& parent, int start, int end) const +{ + Q_Q(const KSelectionProxyModel); + QPair pair = qMakePair(start, end); + + if (m_omitChildren && !m_startWithChildTrees && !m_includeAllSelected) { + // SubTreeRoots + if (m_rootIndexList.contains(parent) || isDescendantOf(m_rootIndexList, parent)) { + return qMakePair(-1, -1); + } + } + + const QModelIndex proxyParent = mapParentFromSource(parent); + + if (!m_includeAllSelected && !m_omitChildren) { + // SubTrees and SubTreesWithoutRoots + if (proxyParent.isValid()) { + return pair; + } + if (m_startWithChildTrees && m_rootIndexList.contains(parent)) { + // SubTreesWithoutRoots topLevel + const int proxyStartRow = getProxyInitialRow(parent) + start; + return qMakePair(proxyStartRow, proxyStartRow + (end - start)); + } + } + + if (m_includeAllSelected && m_startWithChildTrees) { + // ChildrenOfExactSelection + int position = m_rootIndexList.indexOf(parent); + if (position != -1) { + const int proxyStartRow = getProxyInitialRow(parent) + start; + int proxyEndRow = proxyStartRow + (end - start); + ++position; + while (m_rootIndexList.size() < position) { + const QModelIndex idx = m_rootIndexList.at(position); + if (isDescendantOf(parent, idx)) + proxyEndRow += q->sourceModel()->rowCount(idx); + else + break; + } + return qMakePair(proxyStartRow, proxyEndRow); + } + } + + QList::const_iterator rootIt = m_rootIndexList.constBegin(); + const QList::const_iterator rootEnd = m_rootIndexList.constEnd(); + int rootPosition = 0; + int rootStartRemove = -1; + int rootEndRemove = -1; + int siblingCount = 0; + + for ( ; rootIt != rootEnd; ++rootIt, ++rootPosition) { + if (m_omitChildren && m_includeAllSelected) { + // ExactSelection + if (parent == rootIt->parent() && rootIt->row() <= end && rootIt->row() >= start) { + if (rootStartRemove == -1) + rootStartRemove = rootPosition; + ++rootEndRemove; + } else { + if (rootStartRemove != -1) + break; + } + } else { + if (isDescendantOf(parent, *rootIt)) { + if (rootStartRemove == -1) + rootStartRemove = rootPosition; + ++rootEndRemove; + if (m_startWithChildTrees) + siblingCount += q->sourceModel()->rowCount(*rootIt); + } else { + if (rootStartRemove != -1) + break; + } + } + } + if (rootStartRemove != -1) { + return qMakePair(siblingCount + rootStartRemove, siblingCount + rootEndRemove); + } + + return qMakePair(-1, -1); +} + +void KSelectionProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_Q(KSelectionProxyModel); + + if (!m_selectionModel->hasSelection()) + return; + + QPair pair = beginRemoveRows(parent, start, end); + if (pair.first == -1) + return; + + const QModelIndex proxyParent = mapParentFromSource(parent); + + m_rowsRemoved = true; + m_proxyRemoveRows = pair; + q->beginRemoveRows(proxyParent, pair.first, pair.second); +} + +void KSelectionProxyModelPrivate::endRemoveRows(const QModelIndex &sourceParent, int proxyStart, int proxyEnd) +{ + const QModelIndex proxyParent = mapParentFromSource(sourceParent); + + if (proxyParent.isValid()) + updateInternalIndexes(proxyParent, proxyEnd + 1, -1*(proxyEnd - proxyStart + 1)); + else + updateInternalTopIndexes(proxyEnd + 1, -1*(proxyEnd - proxyStart + 1)); + + { + SourceProxyIndexMapping::right_iterator it = m_mappedParents.rightBegin(); + + while (it != m_mappedParents.rightEnd()) { + if (!it.value().isValid()) { + m_parentIds.removeRight(it.key()); + it = m_mappedParents.eraseRight(it); + } else + ++it; + } + } + + { + // Depending on what is selected at the time, a single removal in the source could invalidate + // many mapped first child items at once. + + // - A + // - B + // - - C + // - - D + // - - - E + // - - - F + // - - - - G + // - - - - H + + // If D and F are selected, the proxy contains E, F, G, H. If B is then deleted E to H will + // be removed, including both first child mappings at E and G. + + SourceProxyIndexMapping::right_iterator it = m_mappedFirstChildren.rightBegin(); + + while (it != m_mappedFirstChildren.rightEnd()) { + if (!it.value().isValid()) + it = m_mappedFirstChildren.eraseRight(it); + else + ++it; + } + } + + QList::iterator rootIt = m_rootIndexList.begin(); + while (rootIt != m_rootIndexList.end()) { + if (!rootIt->isValid()) + rootIt = m_rootIndexList.erase(rootIt); + else + ++rootIt; + } +} + +void KSelectionProxyModelPrivate::sourceRowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_Q(KSelectionProxyModel); + Q_UNUSED(end) + + if (!m_selectionModel->hasSelection()) + return; + + if (!m_rowsRemoved) + return; + m_rowsRemoved = false; + + Q_ASSERT(m_proxyRemoveRows.first >= 0); + Q_ASSERT(m_proxyRemoveRows.second >= 0); + endRemoveRows(parent, m_proxyRemoveRows.first, m_proxyRemoveRows.second); + if (m_startWithChildTrees && start == 0 && q->sourceModel()->hasChildren(parent)) + // The private endRemoveRows call might remove the first child mapping for parent, so + // we create it again in that case. + createFirstChildMapping(parent, m_proxyRemoveRows.first); + + m_proxyRemoveRows = qMakePair(-1, -1); + q->endRemoveRows(); +} + +void KSelectionProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + Q_Q(KSelectionProxyModel); + + // layout{,AboutToBe}Changed signals are emitted by QAIM for backward compatibility reasons when rows are moved. + // Processing them is expensive so we do our best to avoid them. + m_ignoreNextLayoutAboutToBeChanged = true; + + if (!m_selectionModel->hasSelection()) + return; + + const QPair removePair = beginRemoveRows(srcParent, srcStart, srcEnd); + const bool notifyRemove = removePair.first != -1; + + const QModelIndex proxyDestination = q->mapFromSource(destParent); + + // Notify insert if + // - The destParent is valid or m_startWithChildTrees and destParent is in rootIndexList + // - Not m_startWithChildTrees and we are moving elements of rootIndexList + + if (!m_startWithChildTrees) { +// if (m_rootIndexList.contains()) + { + + + } + } + +#if 0 + if (m_omitChildren && !m_startWithChildTrees && m_includeAllSelected) { + // ExactSelection + int row = srcStart; + int proxyStart = -1; + static const int column = 0; + for ( ; row < srcEnd; ++row) { + const QModelIndex idx = q->sourceModel()->index(row, column, srcParent); + proxyStart = m_rootIndexList.indexOf(idx); + if (proxyStart != -1) { + break; + } + } + if (proxyStart == -1) + return; + const int rangeStart = row; + row = srcEnd; + int proxyEnd = proxyStart; + for ( ; row > rangeStart; --row) { + const QModelIndex idx = q->sourceModel()->index(row, column, srcParent); + if (m_rootIndexList.contains(idx) || isDescendantOf(srcParent, idx)) { + proxyEnd = m_rootIndexList.indexOf(idx); + break; + } + } + // Figure out if destination is actually a change. + } +#endif + + + bool notifyDestination = proxyDestination.isValid(); + + if (proxyDestination.isValid() && (m_omitChildren || (m_startWithChildTrees && m_omitDescendants))) + notifyDestination = false; + + if (m_includeAllSelected && !m_startWithChildTrees) { + int destMoveRow = -1; + + QList::const_iterator it = m_rootIndexList.constBegin(); + const QList::const_iterator end = m_rootIndexList.constEnd(); + + int startMoveRow = -1; + int endMoveRow = -1; + int count = 0; + int row; + for ( ; it != end; ++it, ++count) { + row = it->row(); + if ((row >= srcStart && row <= srcEnd) && q->sourceModel()->parent(*it) == srcParent) { + if (startMoveRow == -1) { + startMoveRow = count; + endMoveRow = count; + + if (destMoveRow == -1) { + if (destParent.isValid()) + destMoveRow = getRootListRow(m_rootIndexList, destParent); + else + destMoveRow = it->row(); + } + } else + ++endMoveRow; + } else { + if (row < destRow && q->sourceModel()->parent(*it) == destParent) { + if (destMoveRow == -1) + if (destParent.isValid()) + destMoveRow = getRootListRow(m_rootIndexList, destParent); + else + destMoveRow = it->row(); + else + ++destMoveRow; + } + } + } + if (startMoveRow != -1 && (destMoveRow < startMoveRow || destMoveRow > endMoveRow)) { + Q_ASSERT(destMoveRow != -1); + q->beginMoveRows(QModelIndex(), startMoveRow, endMoveRow, QModelIndex(), destMoveRow); + m_rowsMoved = true; + } + return; + } + + const bool srcInModel = (!m_startWithChildTrees && isInModel(srcParent)) || (m_startWithChildTrees && m_rootIndexList.contains(srcParent)); + const bool destInModel = (!m_startWithChildTrees && isInModel(destParent)) || (m_startWithChildTrees && m_rootIndexList.contains(destParent)); + + if (srcInModel) { + if (destInModel) { + // The easy case. + const bool allowMove = q->beginMoveRows(q->mapFromSource(srcParent), srcStart, srcEnd, q->mapFromSource(destParent), destRow); + Q_UNUSED(allowMove); // prevent warning in release builds. + Q_ASSERT(allowMove); + m_rowsMoved = true; + } else { + // source is in the proxy, but dest isn't. + // Emit a remove + q->beginRemoveRows(srcParent, srcStart, srcEnd); + } + } else { + if (destInModel) { + // dest is in proxy, but source is not. + // Emit an insert + q->beginInsertRows(destParent, destRow, destRow + (srcEnd - srcStart)); + } + } +} + +void KSelectionProxyModelPrivate::sourceRowsMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destRow) +{ + Q_Q(KSelectionProxyModel); + + m_ignoreNextLayoutChanged = true; + + if (!m_selectionModel->hasSelection()) + return; + + if (m_rowsMoved) { + m_rowsMoved = false; + endRemoveRows(srcParent, srcStart, srcEnd); + endInsertRows(destParent, destRow, destRow + (srcEnd - srcStart)); + q->endMoveRows(); + return; + } + + if (m_rowsRemoved) + q->endRemoveRows(); + + if (m_rowsInserted) + q->endInsertRows(); +} + +void KSelectionProxyModelPrivate::removeSelectionFromProxy(const QItemSelection &selection) +{ + Q_Q(KSelectionProxyModel); + if (selection.isEmpty()) + return; + + q->rootSelectionAboutToBeRemoved(selection); + + foreach(const QItemSelectionRange range, selection) + removeRangeFromProxy(range); +} + +void KSelectionProxyModelPrivate::removeRangeFromProxy(const QItemSelectionRange &range) +{ + Q_Q(KSelectionProxyModel); + + const QModelIndex sourceTopLeft = range.topLeft(); + const QModelIndex proxyTopLeft = q->mapFromSource(sourceTopLeft); + const QModelIndex sourceBottomLeft = range.bottomRight().sibling(range.bottom(), 0); + const QModelIndex proxyBottomLeft = q->mapFromSource(sourceBottomLeft); + const QModelIndex proxyParent = proxyTopLeft.parent(); + const QModelIndex sourceParent = sourceTopLeft.parent(); + + if (m_startWithChildTrees) { + Q_ASSERT(sourceTopLeft.isValid()); + Q_ASSERT(sourceBottomLeft.isValid()); + const int startRootIdx = m_rootIndexList.indexOf(sourceTopLeft); + int endRootIdx = m_rootIndexList.indexOf(sourceBottomLeft); + QItemSelection extraRanges; + if (m_includeAllSelected) { + // It can happen that indexes of descendants get in between indexes which make up a range. + // We handle only the first contiguous block here and handle the rest later. + int idx = startRootIdx; + const int bottomIdx = endRootIdx; + const int rootListSize = m_rootIndexList.size(); + int next = idx + 1; + while (next <= bottomIdx) + { + if (next < rootListSize && m_rootIndexList.at(next).parent() == sourceParent) { + idx = next; + ++next; + } else + break; + } + endRootIdx = idx; + ++idx; + while (idx <= bottomIdx) + { + const QModelIndex index= m_rootIndexList.at(idx); + if (m_rootIndexList.at(idx).parent() == sourceParent) + extraRanges << QItemSelectionRange(index, index); + ++idx; + } + } + Q_ASSERT(endRootIdx != -1); + int childrenCount = q->sourceModel()->rowCount(sourceTopLeft); + for (int rootIdx = startRootIdx + 1; rootIdx <= endRootIdx; ++rootIdx) + { + childrenCount += q->sourceModel()->rowCount(m_rootIndexList.at(rootIdx)); + } + if (childrenCount == 0) + { + for (int rootIdx = startRootIdx; rootIdx <= endRootIdx; ++rootIdx) + { + const QModelIndex idx = m_rootIndexList.at(startRootIdx); + q->rootIndexAboutToBeRemoved(idx); + m_rootIndexList.removeOne(idx); + } + return; + } + if (!m_includeAllSelected) + { + ++endRootIdx; + for ( ; endRootIdx < m_rootIndexList.size(); ++endRootIdx) { + const QModelIndex idx = m_rootIndexList.at(endRootIdx); + if (isDescendantOf(sourceBottomLeft, idx)) + childrenCount += q->sourceModel()->rowCount(idx); + else + break; + } + --endRootIdx; + } + const int proxyStart = getTargetRow(startRootIdx); + int proxyEnd = proxyStart + childrenCount - 1; + q->beginRemoveRows(QModelIndex(), proxyStart, proxyEnd); + + for (int rootIdx = startRootIdx; rootIdx <= endRootIdx; ++rootIdx) + { + q->rootIndexAboutToBeRemoved(m_rootIndexList.at(startRootIdx)); + } + + removeParentMappings(QModelIndex(), proxyStart, proxyEnd); + removeFirstChildMappings(proxyStart, proxyEnd); + int numRemovedChildren = 0; + for (int rootIdx = startRootIdx; rootIdx <= endRootIdx; ++rootIdx) + { + const QModelIndex idx = m_rootIndexList.at(startRootIdx); + const int childCount = q->sourceModel()->rowCount(idx); + m_rootIndexList.removeAt(startRootIdx); + numRemovedChildren += childCount; + } + updateInternalTopIndexes(proxyEnd + 1, -1 * numRemovedChildren); + q->endRemoveRows(); + if (m_includeAllSelected) { + removeSelectionFromProxy(normalizeSelection(extraRanges)); + } + } else { + if (!proxyTopLeft.isValid()) + return; + const int height = range.height(); + q->beginRemoveRows(proxyParent, proxyTopLeft.row(), proxyTopLeft.row() + height - 1); + + // TODO: Do this conditionally if the signal is connected to anything. + for (int i = 0; i < height; ++i) + { + const QModelIndex idx = sourceTopLeft.sibling(range.top() + i, sourceTopLeft.column()); + q->rootIndexAboutToBeRemoved(idx); + } + + removeParentMappings(proxyParent, proxyTopLeft.row(), proxyTopLeft.row() + height - 1); + updateInternalIndexes(proxyParent, proxyTopLeft.row() + height, -1 * height); + + for (int i = 0; i < height; ++i) + { + const QModelIndex idx = sourceTopLeft.sibling(range.top() + i, sourceTopLeft.column()); + Q_ASSERT(idx.isValid()); + const bool b = m_rootIndexList.removeOne(idx); + Q_UNUSED(b) + Q_ASSERT(b); + } + + q->endRemoveRows(); + } +} + +void KSelectionProxyModelPrivate::selectionChanged(const QItemSelection &_selected, const QItemSelection &_deselected) +{ + Q_Q(KSelectionProxyModel); + + if (!q->sourceModel() || (_selected.isEmpty() && _deselected.isEmpty())) + return; + + // Any deselected indexes in the m_rootIndexList are removed. Then, any + // indexes in the selected range which are not a descendant of one of the already selected indexes + // are inserted into the model. + // + // All ranges from the selection model need to be split into individual rows. Ranges which are contiguous in + // the selection model may not be contiguous in the source model if there's a sort filter proxy model in the chain. + // + // Some descendants of deselected indexes may still be selected. The ranges in m_selectionModel->selection() + // are examined. If any of the ranges are descendants of one of the + // indexes in deselected, they are added to the ranges to be inserted into the model. + // + // The new indexes are inserted in sorted order. + + const QItemSelection selected = m_indexMapper->mapSelectionRightToLeft(_selected); + const QItemSelection deselected = m_indexMapper->mapSelectionRightToLeft(_deselected); + +#if QT_VERSION < 0x040800 + // The QItemSelectionModel sometimes doesn't remove deselected items from its selection + // Fixed in Qt 4.8 : http://qt.gitorious.org/qt/qt/merge_requests/2403 + QItemSelection reportedSelection = m_selectionModel->selection(); + reportedSelection.merge(deselected, QItemSelectionModel::Deselect); + QItemSelection fullSelection = m_indexMapper->mapSelectionRightToLeft(reportedSelection); +#else + QItemSelection fullSelection = m_indexMapper->mapSelectionRightToLeft(m_selectionModel->selection()); +#endif + + QItemSelection newRootRanges; + QItemSelection removedRootRanges; + if (!m_includeAllSelected) { + newRootRanges = getRootRanges(selected); + + QItemSelection existingSelection = fullSelection; + // What was selected before the selection was made. + existingSelection.merge(selected, QItemSelectionModel::Deselect); + + // This is similar to m_rootRanges, but that m_rootRanges at this point still contains the roots + // of deselected and existingRootRanges does not. + + const QItemSelection existingRootRanges = getRootRanges(existingSelection); + { + QMutableListIterator i(newRootRanges); + while (i.hasNext()) { + const QItemSelectionRange range = i.next(); + const QModelIndex topLeft = range.topLeft(); + if (isDescendantOf(existingRootRanges, topLeft)) { + i.remove(); + } + } + } + + QItemSelection exposedSelection; + { + QItemSelection deselectedRootRanges = getRootRanges(deselected); + QListIterator i(deselectedRootRanges); + while (i.hasNext()) { + const QItemSelectionRange range = i.next(); + const QModelIndex topLeft = range.topLeft(); + // Consider this: + // + // - A + // - - B + // - - - C + // - - - - D + // + // B and D were selected, then B was deselected and C was selected in one go. + if (!isDescendantOf(existingRootRanges, topLeft)) { + // B is topLeft and fullSelection contains D. + // B is not a descendant of D. + + // range is not a descendant of the selection, but maybe the selection is a descendant of range. + // no need to check selected here. That's already in newRootRanges. + // existingRootRanges and newRootRanges do not overlap. + foreach (const QItemSelectionRange &selectedRange, existingRootRanges) { + const QModelIndex selectedRangeTopLeft = selectedRange.topLeft(); + // existingSelection (and selectedRangeTopLeft) is D. + // D is a descendant of B, so when B was removed, D might have been exposed as a root. + if (isDescendantOf(range, selectedRangeTopLeft) + // But D is also a descendant of part of the new selection C, which is already set to be a new root + // so D would not be added to exposedSelection because C is in newRootRanges. + && !isDescendantOf(newRootRanges, selectedRangeTopLeft)) + exposedSelection.append(selectedRange); + } + removedRootRanges << range; + } + } + } + + QItemSelection obscuredRanges; + { + QListIterator i(existingRootRanges); + while (i.hasNext()) { + const QItemSelectionRange range = i.next(); + if (isDescendantOf(newRootRanges, range.topLeft())) + obscuredRanges << range; + } + } + removedRootRanges << getRootRanges(obscuredRanges); + newRootRanges << getRootRanges(exposedSelection); + } else { + removedRootRanges = deselected; + newRootRanges = selected; + } + + removeSelectionFromProxy(removedRootRanges); + + if (!m_selectionModel->hasSelection()) + { + Q_ASSERT(m_rootIndexList.isEmpty()); + Q_ASSERT(m_mappedFirstChildren.isEmpty()); + Q_ASSERT(m_mappedParents.isEmpty()); + Q_ASSERT(m_parentIds.isEmpty()); + } + + insertSelectionIntoProxy(newRootRanges); +} + +int KSelectionProxyModelPrivate::getTargetRow(int rootListRow) +{ + Q_Q(KSelectionProxyModel); + if (!m_startWithChildTrees) + return rootListRow; + + --rootListRow; + while (rootListRow >= 0) { + const QModelIndex idx = m_rootIndexList.at(rootListRow); + Q_ASSERT(idx.isValid()); + const int rowCount = q->sourceModel()->rowCount(idx); + if (rowCount > 0) { + static const int column = 0; + const QModelIndex srcIdx = q->sourceModel()->index(rowCount - 1, column, idx); + const QModelIndex proxyLastChild = q->mapFromSource(srcIdx); + return proxyLastChild.row() + 1; + } + --rootListRow; + } + return 0; +} + +void KSelectionProxyModelPrivate::insertSelectionIntoProxy(const QItemSelection &selection) +{ + Q_Q(KSelectionProxyModel); + + if (selection.isEmpty()) + return; + + foreach(const QModelIndex &newIndex, selection.indexes()) { + if (newIndex.column() > 0) + continue; + if (m_startWithChildTrees) { + const int rootListRow = getRootListRow(m_rootIndexList, newIndex); + Q_ASSERT(q->sourceModel() == newIndex.model()); + const int rowCount = q->sourceModel()->rowCount(newIndex); + const int startRow = getTargetRow(rootListRow); + + if (rowCount == 0) { + // Even if the newindex doesn't have any children to put into the model yet, + // We still need to make sure it's future children are inserted into the model. + m_rootIndexList.insert(rootListRow, newIndex); + if (!m_resetting) + emit q->rootIndexAdded(newIndex); + continue; + } + if (!m_resetting) + q->beginInsertRows(QModelIndex(), startRow, startRow + rowCount - 1); + Q_ASSERT(newIndex.isValid()); + m_rootIndexList.insert(rootListRow, newIndex); + emit q->rootIndexAdded(newIndex); + + int _start = 0; + for (int i = 0; i < rootListRow; ++i) + _start += q->sourceModel()->rowCount(m_rootIndexList.at(i)); + + updateInternalTopIndexes(_start, rowCount); + createFirstChildMapping(newIndex, _start); + createParentMappings(newIndex, 0, rowCount - 1); + + if (!m_resetting) { + q->endInsertRows(); + } + + } else { + const int row = getRootListRow(m_rootIndexList, newIndex); + if (!m_resetting) + q->beginInsertRows(QModelIndex(), row, row); + + Q_ASSERT(newIndex.isValid()); + m_rootIndexList.insert(row, newIndex); + emit q->rootIndexAdded(newIndex); + Q_ASSERT(m_rootIndexList.size() > row); + updateInternalIndexes(QModelIndex(), row, 1); + createParentMappings(newIndex.parent(), newIndex.row(), newIndex.row()); + + if (!m_resetting) { + q->endInsertRows(); + } + } + } + q->rootSelectionAdded(selection); +} + +bool KSelectionProxyModelPrivate::isInModel(const QModelIndex &sourceIndex) const +{ + if (m_rootIndexList.contains(sourceIndex)) { + if (m_startWithChildTrees) + return false; + return true; + } + + QModelIndex seekIndex = sourceIndex; + while (seekIndex.isValid()) { + if (m_rootIndexList.contains(seekIndex)) { + return true; + } + + seekIndex = seekIndex.parent(); + } + return false; +} + +KSelectionProxyModel::KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent) + : QAbstractProxyModel(parent), d_ptr(new KSelectionProxyModelPrivate(this, selectionModel)) +{ +} + +KSelectionProxyModel::~KSelectionProxyModel() +{ + delete d_ptr; +} + +void KSelectionProxyModel::setFilterBehavior(FilterBehavior behavior) +{ + Q_D(KSelectionProxyModel); + + beginResetModel(); + + d->m_filterBehavior = behavior; + + switch (behavior) { + case SubTrees: { + d->m_omitChildren = false; + d->m_omitDescendants = false; + d->m_startWithChildTrees = false; + d->m_includeAllSelected = false; + break; + } + case SubTreeRoots: { + d->m_omitChildren = true; + d->m_startWithChildTrees = false; + d->m_includeAllSelected = false; + break; + } + case SubTreesWithoutRoots: { + d->m_omitChildren = false; + d->m_omitDescendants = false; + d->m_startWithChildTrees = true; + d->m_includeAllSelected = false; + break; + } + case ExactSelection: { + d->m_omitChildren = true; + d->m_startWithChildTrees = false; + d->m_includeAllSelected = true; + break; + } + case ChildrenOfExactSelection: { + d->m_omitChildren = false; + d->m_omitDescendants = true; + d->m_startWithChildTrees = true; + d->m_includeAllSelected = true; + break; + } + } + d->resetInternalData(); + d->selectionChanged(d->m_selectionModel->selection(), QItemSelection()); + + endResetModel(); +} + +KSelectionProxyModel::FilterBehavior KSelectionProxyModel::filterBehavior() const +{ + Q_D(const KSelectionProxyModel); + return (KSelectionProxyModel::FilterBehavior)d->m_filterBehavior; +} + +void KSelectionProxyModel::setSourceModel(QAbstractItemModel *_sourceModel) +{ + Q_D(KSelectionProxyModel); + if (_sourceModel == sourceModel()) + return; + + connect(d->m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(selectionChanged(QItemSelection,QItemSelection))); + + beginResetModel(); + d->m_resetting = true; + + if (_sourceModel) { + disconnect(_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(sourceRowsInserted(QModelIndex,int,int))); + disconnect(_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); +// disconnect(_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), +// this, SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); +// disconnect(_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), +// this, SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); + disconnect(_sourceModel, SIGNAL(modelAboutToBeReset()), + this, SLOT(sourceModelAboutToBeReset())); + disconnect(_sourceModel, SIGNAL(modelReset()), + this, SLOT(sourceModelReset())); + disconnect(_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); + disconnect(_sourceModel, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(sourceLayoutAboutToBeChanged())); + disconnect(_sourceModel, SIGNAL(layoutChanged()), + this, SLOT(sourceLayoutChanged())); + disconnect(_sourceModel, SIGNAL(destroyed()), + this, SLOT(sourceModelDestroyed())); + } + + // Must be called before QAbstractProxyModel::setSourceModel because it emits some signals. + d->resetInternalData(); + QAbstractProxyModel::setSourceModel(_sourceModel); + if (_sourceModel) { + d->m_indexMapper = new KModelIndexProxyMapper(_sourceModel, d->m_selectionModel->model(), this); + if (d->m_selectionModel->hasSelection()) + d->selectionChanged(d->m_selectionModel->selection(), QItemSelection()); + + connect(_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int))); + connect(_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + SLOT(sourceRowsInserted(QModelIndex,int,int))); + connect(_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + connect(_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + SLOT(sourceRowsRemoved(QModelIndex,int,int))); +// connect(_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), +// SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); +// connect(_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), +// SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); + connect(_sourceModel, SIGNAL(modelAboutToBeReset()), + SLOT(sourceModelAboutToBeReset())); + connect(_sourceModel, SIGNAL(modelReset()), + SLOT(sourceModelReset())); + connect(_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + SLOT(sourceDataChanged(QModelIndex,QModelIndex))); + connect(_sourceModel, SIGNAL(layoutAboutToBeChanged()), + SLOT(sourceLayoutAboutToBeChanged())); + connect(_sourceModel, SIGNAL(layoutChanged()), + SLOT(sourceLayoutChanged())); + connect(_sourceModel, SIGNAL(destroyed()), + SLOT(sourceModelDestroyed())); + } + + d->m_resetting = false; + endResetModel(); +} + +void KSelectionProxyModelPrivate::updateInternalTopIndexes(int start, int offset) +{ + Q_Q(KSelectionProxyModel); + + updateInternalIndexes(QModelIndex(), start, offset); + + SourceProxyIndexMapping::left_iterator firstChildIt = m_mappedFirstChildren.leftBegin(); + + for ( ; firstChildIt != m_mappedFirstChildren.leftEnd(); ++firstChildIt) { + const QModelIndex proxyIndex = firstChildIt.value(); + Q_ASSERT(proxyIndex.isValid()); + + if (proxyIndex.row() < start) + continue; + + const QModelIndex newProxyIndex = q->createIndex(proxyIndex.row() + offset, proxyIndex.column(), proxyIndex.internalPointer()); + + Q_ASSERT(newProxyIndex.isValid()); + m_mappedFirstChildren.updateRight(firstChildIt, newProxyIndex); + } +} + +void KSelectionProxyModelPrivate::updateInternalIndexes(const QModelIndex &parent, int start, int offset) +{ + Q_Q(KSelectionProxyModel); + + Q_ASSERT(start + offset >= 0); + + if (m_omitChildren || (m_omitDescendants && m_startWithChildTrees)) + return; + + SourceProxyIndexMapping::left_iterator mappedParentIt = m_mappedParents.leftBegin(); + + QHash updatedParentIds; + QHash updatedParents; + + for ( ; mappedParentIt != m_mappedParents.leftEnd(); ++mappedParentIt) { + const QModelIndex proxyIndex = mappedParentIt.value(); + Q_ASSERT(proxyIndex.isValid()); + + if (proxyIndex.row() < start) + continue; + + const QModelIndex proxyParent = proxyIndex.parent(); + + if (parent.isValid()) { + if (proxyParent != parent) + continue; + } else { + if (proxyParent.isValid()) + continue; + } + Q_ASSERT(m_parentIds.rightContains(proxyIndex)); + const qint64 key = m_parentIds.rightToLeft(proxyIndex); + + const QModelIndex newIndex = q->createIndex(proxyIndex.row() + offset, proxyIndex.column(), proxyIndex.internalPointer()); + + Q_ASSERT(newIndex.isValid()); + + updatedParentIds.insert(key, newIndex); + updatedParents.insert(mappedParentIt.key(), newIndex); + } + + { + QHash::const_iterator it = updatedParents.constBegin(); + const QHash::const_iterator end = updatedParents.constEnd(); + for ( ; it != end; ++it) + m_mappedParents.insert(it.key(), it.value()); + } + + { + QHash::const_iterator it = updatedParentIds.constBegin(); + const QHash::const_iterator end = updatedParentIds.constEnd(); + for ( ; it != end; ++it) + m_parentIds.insert(it.key(), it.value()); + } +} + +bool KSelectionProxyModelPrivate::parentAlreadyMapped(const QModelIndex &parent) const +{ + return m_mappedParents.leftContains(parent); +} + +bool KSelectionProxyModelPrivate::firstChildAlreadyMapped(const QModelIndex &firstChild) const +{ + return m_mappedFirstChildren.leftContains(firstChild); +} + +void KSelectionProxyModelPrivate::createFirstChildMapping(const QModelIndex& parent, int proxyRow) const +{ + Q_Q(const KSelectionProxyModel); + + static const int column = 0; + static const int row = 0; + + const QPersistentModelIndex srcIndex = q->sourceModel()->index(row, column, parent); + + if (firstChildAlreadyMapped(srcIndex)) + return; + + Q_ASSERT(srcIndex.isValid()); + m_mappedFirstChildren.insert(srcIndex, q->createIndex(proxyRow, column)); +} + +void KSelectionProxyModelPrivate::createParentMappings(const QModelIndex &parent, int start, int end) const +{ + if (m_omitChildren || (m_omitDescendants && m_startWithChildTrees)) + return; + + Q_Q(const KSelectionProxyModel); + + static const int column = 0; + + for (int row = start; row <= end; ++row) { + const QModelIndex srcIndex = q->sourceModel()->index(row, column, parent); + Q_ASSERT(srcIndex.isValid()); + if (!q->sourceModel()->hasChildren(srcIndex) || parentAlreadyMapped(srcIndex)) + continue; + + const QModelIndex proxyIndex = q->mapFromSource(srcIndex); + if (!proxyIndex.isValid()) + return; // If one of them is not mapped, its siblings won't be either + + const qint64 newId = m_nextId++; + m_parentIds.insert(newId, proxyIndex); + Q_ASSERT(srcIndex.isValid()); + m_mappedParents.insert(QPersistentModelIndex(srcIndex), proxyIndex); + } +} + +void KSelectionProxyModelPrivate::removeFirstChildMappings(int start, int end) +{ + SourceProxyIndexMapping::left_iterator it = m_mappedFirstChildren.leftBegin(); + + while (it != m_mappedFirstChildren.leftEnd()) { + const int row = it.value().row(); + if (row >= start && row <= end /*&& !parentAlreadyMapped(it->first)*/) { + it = m_mappedFirstChildren.eraseLeft(it); + } else + ++it; + } +} + +void KSelectionProxyModelPrivate::removeParentMappings(const QModelIndex &parent, int start, int end) +{ + Q_Q(KSelectionProxyModel); + + Q_ASSERT(parent.isValid() ? parent.model() == q : true); + + SourceProxyIndexMapping::right_iterator it = m_mappedParents.rightBegin(); + SourceProxyIndexMapping::right_iterator endIt = m_mappedParents.rightEnd(); + + typedef QPair Pair; + + QList pairs; + + QModelIndexList list; + + const bool flatList = m_omitChildren || (m_startWithChildTrees && m_omitDescendants); + + while (it != endIt) { + if (it.key().row() >= start && it.key().row() <= end) + { + const QModelIndex sourceParent = it.value(); + const QModelIndex proxyGrandParent = mapParentFromSource(sourceParent.parent()); + if (proxyGrandParent == parent) + { + if (!flatList) + // Due to recursive calls, we could have several iterators on the container + // when erase is called. That's safe accoring to the QHash::iterator docs though. + removeParentMappings(it.key(), 0, q->sourceModel()->rowCount(it.value()) - 1); + + m_parentIds.removeRight(it.key()); + it = m_mappedParents.eraseRight(it); + } else + ++it; + } else + ++it; + } +} + +QModelIndex KSelectionProxyModel::mapToSource(const QModelIndex &proxyIndex) const +{ + Q_D(const KSelectionProxyModel); + + if (!proxyIndex.isValid() || !sourceModel() || d->m_rootIndexList.isEmpty()) + return QModelIndex(); + + Q_ASSERT(proxyIndex.internalId() >= 0); + + if (proxyIndex.internalId() == 0) { + const int column = proxyIndex.column(); + if (!d->m_startWithChildTrees) + { + const QModelIndex idx = d->m_rootIndexList.at(proxyIndex.row()); + return idx.sibling(idx.row(), column); + } + + if (d->m_mappedFirstChildren.isEmpty()) + return QModelIndex(); + + SourceProxyIndexMapping::left_const_iterator it = d->m_mappedFirstChildren.leftConstBegin(); + const SourceProxyIndexMapping::left_const_iterator end = d->m_mappedFirstChildren.leftConstEnd(); + + SourceProxyIndexMapping::left_const_iterator result = end; + for ( ; it != end; ++it) { + if (it.value().row() <= proxyIndex.row()) { + if (result == end || it.value().row() > result.value().row()) + result = it; + } + } + + const QModelIndex proxyFirstChild = result.value(); + const QModelIndex sourceFirstChild = result.key(); + Q_ASSERT(proxyFirstChild.isValid()); + Q_ASSERT(proxyFirstChild.internalPointer() == 0); + Q_ASSERT(sourceFirstChild.isValid()); + return sourceFirstChild.sibling(proxyIndex.row() - proxyFirstChild.row(), column); + } + + const QModelIndex proxyParent = d->m_parentIds.leftToRight(proxyIndex.internalId()); + Q_ASSERT(proxyParent.isValid()); + const QModelIndex sourceParent = d->mapParentToSource(proxyParent); + Q_ASSERT(sourceParent.isValid()); + return sourceModel()->index(proxyIndex.row(), proxyIndex.column(), sourceParent); +} + +QModelIndex KSelectionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + Q_D(const KSelectionProxyModel); + + if (!sourceModel() || !sourceIndex.isValid() || d->m_rootIndexList.isEmpty()) + return QModelIndex(); + + QModelIndex ancestor = sourceIndex.parent(); + QModelIndexList ancestorList; + while (ancestor.isValid()) + { + if (!d->parentAlreadyMapped(ancestor)) + ancestorList.prepend(ancestor); + else { + break; + } + ancestor = ancestor.parent(); + } + if (!ancestor.isValid()) + ancestorList.clear(); + + // sourceIndex can be mapped to the proxy. We just need to create mappings for its ancestors first. + for(int i = 0; i < ancestorList.size(); ++i) + { + const QModelIndex existingAncestor = d->mapParentFromSource(ancestor); + Q_ASSERT(existingAncestor.isValid()); + qint64 ansId = d->m_parentIds.rightToLeft(existingAncestor); + const QModelIndex newSourceParent = ancestorList.at(i); + const QModelIndex newProxyParent = createIndex(newSourceParent.row(), newSourceParent.column(), reinterpret_cast(ansId)); + + const qint64 newId = d->m_nextId++; + d->m_parentIds.insert(newId, newProxyParent); + d->m_mappedParents.insert(QPersistentModelIndex(newSourceParent), newProxyParent); + ancestor = newSourceParent; + } + + + const QModelIndex maybeMapped = d->mapParentFromSource(sourceIndex); + if (maybeMapped.isValid()) { +// Q_ASSERT((!d->m_startWithChildTrees && d->m_rootIndexList.contains(maybeMapped)) ? maybeMapped.row() < 0 : true ); + return maybeMapped; + } + + const int row = d->m_rootIndexList.indexOf(sourceIndex); + const QModelIndex sourceParent = sourceIndex.parent(); + if (row != -1) { + if (!d->m_startWithChildTrees) { + Q_ASSERT(d->m_rootIndexList.size() > row); + return createIndex(row, sourceIndex.column()); + } + if (!d->m_rootIndexList.contains(sourceParent)) + return QModelIndex(); + + const QModelIndex firstChild = sourceModel()->index(0, 0, sourceParent); + const QModelIndex firstProxyChild = d->mapRootFirstChildFromSource(firstChild); + + return createIndex(firstProxyChild.row() + sourceIndex.row(), sourceIndex.column()); + } + + const QModelIndex proxyParent = d->mapParentFromSource(sourceParent); + if (proxyParent.isValid()) { + const qint64 parentId = d->m_parentIds.rightToLeft(proxyParent); + static const int column = 0; + return createIndex(sourceIndex.row(), column, reinterpret_cast(parentId)); + } + + // Need rules for breaking the index -> parent -> mapToSource -> mapFromSource cylce. + + if (!d->m_startWithChildTrees) + return QModelIndex(); + + const QModelIndex firstChild = sourceModel()->index(0, 0, sourceParent); + const QModelIndex firstProxyChild = d->mapRootFirstChildFromSource(firstChild); + + if (!firstProxyChild.isValid()) + return QModelIndex(); + + return createIndex(firstProxyChild.row() + sourceIndex.row(), sourceIndex.column()); +} + +int KSelectionProxyModel::rowCount(const QModelIndex &index) const +{ + Q_D(const KSelectionProxyModel); + + if (!sourceModel() || index.column() > 0 || d->m_rootIndexList.isEmpty()) + return 0; + + if (!index.isValid()) { + if (!d->m_startWithChildTrees) + return d->m_rootIndexList.size(); + + SourceProxyIndexMapping::left_const_iterator begin = d->m_mappedFirstChildren.leftConstBegin(); + const SourceProxyIndexMapping::left_const_iterator end = d->m_mappedFirstChildren.leftConstEnd(); + if (begin == end) + return 0; + const SourceProxyIndexMapping::left_const_iterator result = kMaxElement(begin, end); + Q_ASSERT(result != end); + const QModelIndex proxyFirstChild = *result; + Q_ASSERT(proxyFirstChild.isValid()); + const QModelIndex sourceFirstChild = result.key(); + Q_ASSERT(sourceFirstChild.isValid()); + const QModelIndex sourceParent = sourceFirstChild.parent(); + Q_ASSERT(sourceParent.isValid()); + return sourceModel()->rowCount(sourceParent) + proxyFirstChild.row(); + } + + // index is valid + if (d->m_omitChildren || (d->m_startWithChildTrees && d->m_omitDescendants)) + return 0; + + QModelIndex sourceParent = d->mapParentToSource(index); + + if (!sourceParent.isValid() && sourceModel()->hasChildren(sourceParent)) { + sourceParent = mapToSource(index.parent()); + d->createParentMappings(sourceParent, 0, sourceModel()->rowCount(sourceParent) - 1); + sourceParent = d->mapParentToSource(index); + } + + if (!sourceParent.isValid()) + return 0; + + return sourceModel()->rowCount(sourceParent); +} + +QModelIndex KSelectionProxyModelPrivate::mapParentToSource(const QModelIndex &proxyParent) const +{ + return m_mappedParents.rightToLeft(proxyParent); +} + +QModelIndex KSelectionProxyModelPrivate::mapParentFromSource(const QModelIndex &sourceParent) const +{ + return m_mappedParents.leftToRight(sourceParent); +} + +QModelIndex KSelectionProxyModelPrivate::mapRootFirstChildToSource(const QModelIndex &proxyChild) const +{ + return m_mappedFirstChildren.rightToLeft(proxyChild); +} + +QModelIndex KSelectionProxyModelPrivate::mapRootFirstChildFromSource(const QModelIndex &sourceChild) const +{ + return m_mappedFirstChildren.leftToRight(sourceChild); +} + +static bool indexIsValid(bool startWithChildTrees, int row, const QList &rootIndexList, const SourceProxyIndexMapping &mappedFirstChildren) +{ + if (!startWithChildTrees) { + Q_ASSERT(rootIndexList.size() > row); + } else { + + Q_ASSERT(!mappedFirstChildren.isEmpty()); + SourceProxyIndexMapping::left_const_iterator it = mappedFirstChildren.leftConstBegin(); + const SourceProxyIndexMapping::left_const_iterator end = mappedFirstChildren.leftConstEnd(); + + SourceProxyIndexMapping::left_const_iterator result = end; + for ( ; it != end; ++it) + { + if (it->row() <= row) + { + if (result == end || result.value().row() < it.value().row()) + result = it; + } + } + Q_ASSERT(result != end); + const QModelIndex proxyFirstChild = result.value(); + const QModelIndex sourceFirstChild = result.key(); + Q_ASSERT(proxyFirstChild.isValid()); + Q_ASSERT(proxyFirstChild.internalPointer() == 0); + Q_ASSERT(sourceFirstChild.isValid()); + Q_ASSERT(sourceFirstChild.parent().isValid()); + Q_ASSERT(row <= proxyFirstChild.row() + sourceFirstChild.model()->rowCount(sourceFirstChild.parent())); + } + return true; +} + +QModelIndex KSelectionProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const KSelectionProxyModel); + + if (!sourceModel() || d->m_rootIndexList.isEmpty() || !hasIndex(row, column, parent)) + return QModelIndex(); + + if (!parent.isValid()) { + Q_ASSERT(indexIsValid(d->m_startWithChildTrees, row, d->m_rootIndexList, d->m_mappedFirstChildren)); + return createIndex(row, column); + } else { + Q_ASSERT(d->m_startWithChildTrees ? true : d->m_parentIds.rightContains(parent)); + + const qint64 parentId = d->m_parentIds.rightToLeft(parent); + return createIndex(row, column, reinterpret_cast(parentId)); + } +} + +QModelIndex KSelectionProxyModel::parent(const QModelIndex &index) const +{ + Q_D(const KSelectionProxyModel); + + if (!sourceModel() || !index.isValid() || d->m_rootIndexList.isEmpty()) + return QModelIndex(); + + const qint64 parentId = index.internalId(); + + return d->m_parentIds.leftToRight(parentId); +} + +Qt::ItemFlags KSelectionProxyModel::flags(const QModelIndex &index) const +{ + if (!index.isValid() || !sourceModel()) + return QAbstractProxyModel::flags(index); + + const QModelIndex srcIndex = mapToSource(index); + Q_ASSERT(srcIndex.isValid()); + return sourceModel()->flags(srcIndex); +} + +QVariant KSelectionProxyModel::data(const QModelIndex & index, int role) const +{ + if (!sourceModel()) + return QVariant(); + + if (index.isValid()) { + const QModelIndex idx = mapToSource(index); + return idx.data(role); + } + return sourceModel()->data(index, role); +} + +QVariant KSelectionProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (!sourceModel()) + return QVariant(); + return sourceModel()->headerData(section, orientation, role); +} + +QMimeData* KSelectionProxyModel::mimeData(const QModelIndexList & indexes) const +{ + if (!sourceModel()) + return QAbstractProxyModel::mimeData(indexes); + QModelIndexList sourceIndexes; + foreach(const QModelIndex& index, indexes) + sourceIndexes << mapToSource(index); + return sourceModel()->mimeData(sourceIndexes); +} + +QStringList KSelectionProxyModel::mimeTypes() const +{ + if (!sourceModel()) + return QAbstractProxyModel::mimeTypes(); + return sourceModel()->mimeTypes(); +} + +Qt::DropActions KSelectionProxyModel::supportedDropActions() const +{ + if (!sourceModel()) + return QAbstractProxyModel::supportedDropActions(); + return sourceModel()->supportedDropActions(); +} + +bool KSelectionProxyModel::hasChildren(const QModelIndex & parent) const +{ + Q_D(const KSelectionProxyModel); + + if (d->m_rootIndexList.isEmpty() || !sourceModel()) + return false; + + if (parent.isValid()) { + if (d->m_omitChildren || (d->m_startWithChildTrees && d->m_omitDescendants)) + return false; + return sourceModel()->hasChildren(mapToSource(parent)); + } + + if (!d->m_startWithChildTrees) + return true; + + return !d->m_mappedFirstChildren.isEmpty(); +} + +int KSelectionProxyModel::columnCount(const QModelIndex &index) const +{ + Q_D(const KSelectionProxyModel); + if (!sourceModel() || index.column() > 0 || d->m_rootIndexList.isEmpty()) + return 0; + return sourceModel()->columnCount(mapToSource(index)); +} + +QItemSelectionModel* KSelectionProxyModel::selectionModel() const +{ + Q_D(const KSelectionProxyModel); + return d->m_selectionModel; +} + +bool KSelectionProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + Q_D(const KSelectionProxyModel); + if (!sourceModel() || d->m_rootIndexList.isEmpty()) + return false; + + if ((row == -1) && (column == -1)) + return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + + int source_destination_row = -1; + int source_destination_column = -1; + QModelIndex source_parent; + + if (row == rowCount(parent)) { + source_parent = mapToSource(parent); + source_destination_row = sourceModel()->rowCount(source_parent); + } else { + const QModelIndex proxy_index = index(row, column, parent); + const QModelIndex source_index = mapToSource(proxy_index); + source_destination_row = source_index.row(); + source_destination_column = source_index.column(); + source_parent = source_index.parent(); + } + return sourceModel()->dropMimeData(data, action, source_destination_row, + source_destination_column, source_parent); +} + +QList KSelectionProxyModel::sourceRootIndexes() const +{ + Q_D(const KSelectionProxyModel); + return d->m_rootIndexList; +} + +QModelIndexList KSelectionProxyModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const +{ + if (role < Qt::UserRole) + return QAbstractProxyModel::match(start, role, value, hits, flags); + + QModelIndexList list; + QModelIndex proxyIndex; + foreach(const QModelIndex &idx, sourceModel()->match(mapToSource(start), role, value, hits, flags)) { + proxyIndex = mapFromSource(idx); + if (proxyIndex.isValid()) + list << proxyIndex; + + } + return list; +} + +#include "moc_kselectionproxymodel.cpp" diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.h new file mode 100644 index 00000000..0776d18e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/kselectionproxymodel.h @@ -0,0 +1,501 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef KSELECTIONPROXYMODEL_H +#define KSELECTIONPROXYMODEL_H + +#include +#include + + +#include "kmodelindexproxymapper.h" +#include "kbihash_p.h" + +typedef KBiHash SourceProxyIndexMapping; +typedef KBiHash ParentMapping; + +#define KDO(object) kDebug() << #object << object +#define SON(object) object->setObjectName(#object) + +class QItemSelectionModel; + +class KSelectionProxyModel; + +class KSelectionProxyModelPrivate +{ +public: + KSelectionProxyModelPrivate(KSelectionProxyModel *model, QItemSelectionModel *selectionModel); + + Q_DECLARE_PUBLIC(KSelectionProxyModel) + KSelectionProxyModel * const q_ptr; + + // A unique id is generated for each parent. It is used for the internalId of its children in the proxy + // This is used to store a unique id for QModelIndexes in the proxy which have children. + // If an index newly gets children it is added to this hash. If its last child is removed it is removed from this map. + // If this map contains an index, that index hasChildren(). This hash is populated when new rows are inserted in the + // source model, or a new selection is made. + mutable ParentMapping m_parentIds; + // This mapping maps indexes with children in the source to indexes with children in the proxy. + // The order of indexes in this list is not relevant. + mutable SourceProxyIndexMapping m_mappedParents; + + /** + Keeping Persistent indexes from this model in this model breaks in certain situations + such as after source insert, but before calling endInsertRows in this model. In such a state, + the persistent indexes are not updated, but the methods assume they are already uptodate. + + Instead of using persistentindexes for proxy indexes in m_mappedParents, we maintain them ourselves with this method. + + m_mappedParents and m_parentIds are affected. + + @p parent and @p start refer to the proxy model. Any rows >= @p start will be updated. + @p offset is the amount that affected indexes will be changed. + */ + void updateInternalIndexes(const QModelIndex &parent, int start, int offset); + + /** + * Updates stored indexes in the proxy. Any proxy row >= @p start is changed by @p offset. + * + * This is only called to update indexes in the top level of the proxy. Most commonly that is + * + * m_mappedParents, m_parentIds and m_mappedFirstChildren are affected. + */ + void updateInternalTopIndexes(int start, int offset); + + void updateFirstChildMapping(const QModelIndex& parent, int offset); + + /** + Creates mappings in m_parentIds and m_mappedParents between the source and the proxy. + + This is not recursive + */ + void createParentMappings(const QModelIndex &parent, int start, int end) const; + void createFirstChildMapping(const QModelIndex &parent, int proxyRow) const; + bool firstChildAlreadyMapped(const QModelIndex &firstChild) const; + bool parentAlreadyMapped(const QModelIndex &parent) const; + void removeFirstChildMappings(int start, int end); + void removeParentMappings(const QModelIndex &parent, int start, int end); + + /** + Given a QModelIndex in the proxy, return the corresponding QModelIndex in the source. + + This method works only if the index has children in the proxy model which already has a mapping from the source. + + This means that if the proxy is a flat list, this method will always return QModelIndex(). Additionally, it means that m_mappedParents is not populated automatically and must be populated manually. + + No new mapping is created by this method. + */ + QModelIndex mapParentToSource(const QModelIndex &proxyParent) const; + + /** + Given a QModelIndex in the source model, return the corresponding QModelIndex in the proxy. + + This method works only if the index has children in the proxy model which already has a mapping from the source. + + No new mapping is created by this method. + */ + QModelIndex mapParentFromSource(const QModelIndex &sourceParent) const; + QModelIndex mapRootFirstChildToSource(const QModelIndex &proxyParent) const; + QModelIndex mapRootFirstChildFromSource(const QModelIndex &sourceParent) const; + + // Only populated if m_startWithChildTrees. + + mutable SourceProxyIndexMapping m_mappedFirstChildren; + + // Source list is the selection in the source model. + QList m_rootIndexList; + + KModelIndexProxyMapper *m_indexMapper; + + QPair beginRemoveRows(const QModelIndex &parent, int start, int end) const; + QPair beginInsertRows(const QModelIndex &parent, int start, int end) const; + void endRemoveRows(const QModelIndex &sourceParent, int proxyStart, int proxyEnd); + void endInsertRows(const QModelIndex &parent, int start, int end); + + void sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void sourceRowsInserted(const QModelIndex &parent, int start, int end); + void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + void sourceRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destRow); + void sourceRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destRow); + void sourceModelAboutToBeReset(); + void sourceModelReset(); + void sourceLayoutAboutToBeChanged(); + void sourceLayoutChanged(); + void emitContinuousRanges(const QModelIndex &sourceFirst, const QModelIndex &sourceLast, + const QModelIndex &proxyFirst, const QModelIndex &proxyLast); + void sourceDataChanged(const QModelIndex &topLeft , const QModelIndex &bottomRight); + + void refreshParentMappings(QList &mappings); + void refreshFirstChildMappings(QList &mappings); + + void removeSelectionFromProxy(const QItemSelection &selection); + void removeRangeFromProxy(const QItemSelectionRange &range); + + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void sourceModelDestroyed(); + + void resetInternalData(); + + /** + When items are inserted or removed in the m_startWithChildTrees configuration, + this method helps find the startRow for use emitting the signals from the proxy. + */ + int getProxyInitialRow(const QModelIndex &parent) const; + + /** + If m_startWithChildTrees is true, this method returns the row in the proxy model to insert newIndex + items. + + This is a special case because the items above rootListRow in the list are not in the model, but + their children are. Those children must be counted. + + If m_startWithChildTrees is false, this method returns @p rootListRow. + */ + int getTargetRow(int rootListRow); + + /** + Inserts the indexes in @p list into the proxy model. + */ + void insertSelectionIntoProxy(const QItemSelection& selection); + + /** + Returns true if @p sourceIndex or one of its ascendants is already part of the proxy model. + */ + bool isInModel(const QModelIndex &sourceIndex) const; + + bool m_startWithChildTrees; + bool m_omitChildren; + bool m_omitDescendants; + bool m_includeAllSelected; + bool m_rowsInserted; + bool m_rowsRemoved; + QPair m_proxyRemoveRows; + bool m_rowsMoved; + bool m_resetting; + bool m_ignoreNextLayoutAboutToBeChanged; + bool m_ignoreNextLayoutChanged; + QItemSelectionModel * const m_selectionModel; + mutable qint64 m_nextId; + + int m_filterBehavior; + + QList m_layoutChangePersistentIndexes; + QModelIndexList m_proxyIndexes; + + QModelIndexList m_weakSourceParents; + QModelIndexList m_weakSourceFirstChilds; + QModelIndexList m_weakRootIndexes; + QModelIndexList m_weakRootParents; + QList m_sourceFirstChildParents; + QList m_sourceGrandParents; + QList m_sourcePersistentParents; + QList m_sourcePersistentFirstChilds; +}; + + +/** + @brief A Proxy Model which presents a subset of its source model to observers. + + The KSelectionProxyModel is most useful as a convenience for displaying the selection in one view in + another view. The selectionModel of the initial view is used to create a proxied model which is filtered + based on the configuration of this class. + + For example, when a user clicks a mail folder in one view in an email application, the contained emails + should be displayed in another view. + + This takes away the need for the developer to handle the selection between the views, including all the + mapToSource, mapFromSource and setRootIndex calls. + + @code + MyModel *sourceModel = new MyModel(this); + QTreeView *leftView = new QTreeView(this); + leftView->setModel(sourceModel); + + KSelectionProxyModel *selectionProxy = new KSelectionProxyModel(leftView->selectionModel(), this); + selectionProxy->setSourceModel(sourceModel); + + QTreeView *rightView = new QTreeView(this); + rightView->setModel(selectionProxy); + @endcode + + \image html selectionproxymodelsimpleselection.png "A Selection in one view creating a model for use with another view." + + The KSelectionProxyModel can handle complex selections. + + \image html selectionproxymodelmultipleselection.png "Non-contiguous selection creating a new simple model in a second view." + + The contents of the secondary view depends on the selection in the primary view, and the configuration of the proxy model. + See KSelectionProxyModel::setFilterBehavior for the different possible configurations. + + For example, if the filterBehavior is SubTrees, selecting another item in an already selected subtree has no effect. + + \image html selectionproxymodelmultipleselection-withdescendant.png "Selecting an item and its descendant." + + See the test application in KDE/kdelibs/kdeui/tests/proxymodeltestapp to try out the valid configurations. + + \image html kselectionproxymodel-testapp.png "KSelectionProxyModel test application" + + Obviously, the KSelectionProxyModel may be used in a view, or further processed with other proxy models. + See KAddressBook and AkonadiConsole in kdepim for examples which use a further KDescendantsProxyModel + and QSortFilterProxyModel on top of a KSelectionProxyModel. + + Additionally, this class can be used to programmatically choose some items from the source model to display in the view. For example, + this is how the Favourite Folder View in KMail works, and is also used in unit testing. + + See also: http://doc.trolltech.com/4.5/model-view-proxy-models.html + + @since 4.4 + @author Stephen Kelly + +*/ +class KSelectionProxyModel : public QAbstractProxyModel +{ + Q_OBJECT +public: + /** + ctor. + + @p selectionModel The selection model used to filter what is presented by the proxy. + */ + + explicit KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent = 0); + + /** + dtor + */ + virtual ~KSelectionProxyModel(); + + /** + reimp. + */ + virtual void setSourceModel(QAbstractItemModel * sourceModel); + + QItemSelectionModel *selectionModel() const; + + enum FilterBehavior { + SubTrees, + SubTreeRoots, + SubTreesWithoutRoots, + ExactSelection, + ChildrenOfExactSelection + }; + Q_ENUMS(FilterBehavior) + + /** + Set the filter behaviors of this model. + The filter behaviors of the model govern the content of the model based on the selection of the contained QItemSelectionModel. + + See kdeui/proxymodeltestapp to try out the different proxy model behaviors. + + The most useful behaviors are SubTrees, ExactSelection and ChildrenOfExactSelection. + + The default behavior is SubTrees. This means that this proxy model will contain the roots of the items in the source model. + Any descendants which are also selected have no additional effect. + For example if the source model is like: + + @verbatim + (root) + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + @endverbatim + + And A, B, C and D are selected, the proxy will contain: + + @verbatim + (root) + - A + - B + - C + - D + - E + - F + - G + @endverbatim + + That is, selecting 'D' or 'C' if 'B' is also selected has no effect. If 'B' is de-selected, then 'C' amd 'D' become top-level items: + + @verbatim + (root) + - A + - C + - D + - E + - F + - G + @endverbatim + + This is the behavior used by KJots when rendering books. + + If the behavior is set to SubTreeRoots, then the children of selected indexes are not part of the model. If 'A', 'B' and 'D' are selected, + + @verbatim + (root) + - A + - B + @endverbatim + + Note that although 'D' is selected, it is not part of the proxy model, because its parent 'B' is already selected. + + SubTreesWithoutRoots has the effect of not making the selected items part of the model, but making their children part of the model instead. If 'A', 'B' and 'I' are selected: + + @verbatim + (root) + - C + - D + - E + - F + - G + - J + - K + - L + @endverbatim + + Note that 'A' has no children, so selecting it has no outward effect on the model. + + ChildrenOfExactSelection causes the proxy model to contain the children of the selected indexes,but further descendants are omitted. + Additionally, if descendants of an already selected index are selected, their children are part of the proxy model. + For example, if 'A', 'B', 'D' and 'I' are selected: + + @verbatim + (root) + - C + - D + - E + - G + - J + - K + - L + @endverbatim + + This would be useful for example if showing containers (for example maildirs) in one view and their items in another. Sub-maildirs would still appear in the proxy, but + could be filtered out using a QSortfilterProxyModel. + + The ExactSelection behavior causes the selected items to be part of the proxy model, even if their ancestors are already selected, but children of selected items are not included. + + Again, if 'A', 'B', 'D' and 'I' are selected: + + @verbatim + (root) + - A + - B + - D + - I + @endverbatim + + This is the behavior used by the Favourite Folder View in KMail. + + */ + void setFilterBehavior(FilterBehavior behavior); + FilterBehavior filterBehavior() const; + + QModelIndex mapFromSource(const QModelIndex & sourceIndex) const; + QModelIndex mapToSource(const QModelIndex & proxyIndex) const; + + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + virtual QMimeData* mimeData(const QModelIndexList & indexes) const; + virtual QStringList mimeTypes() const; + virtual Qt::DropActions supportedDropActions() const; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + + virtual bool hasChildren(const QModelIndex & parent = QModelIndex()) const; + virtual QModelIndex index(int, int, const QModelIndex& = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex&) const; + virtual int columnCount(const QModelIndex& = QModelIndex()) const; + + virtual QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits = 1, + Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const; + +Q_SIGNALS: +#if !defined(Q_MOC_RUN) +private: // Don't allow subclasses to emit these signals. +#endif + + /** + @internal + Emitted before @p removeRootIndex, an index in the sourceModel is removed from + the root selected indexes. This may be unrelated to rows removed from the model, + depending on configuration. + */ + void rootIndexAboutToBeRemoved(const QModelIndex &removeRootIndex); + + /** + @internal + Emitted when @p newIndex, an index in the sourceModel is added to the root selected + indexes. This may be unrelated to rows inserted to the model, + depending on configuration. + */ + void rootIndexAdded(const QModelIndex &newIndex); + + /** + @internal + Emitted before @p selection, a selection in the sourceModel, is removed from + the root selection. + */ + void rootSelectionAboutToBeRemoved(const QItemSelection &selection); + + /** + @internal + Emitted after @p selection, a selection in the sourceModel, is added to + the root selection. + */ + void rootSelectionAdded(const QItemSelection &selection); + +protected: + QList sourceRootIndexes() const; + +private: + Q_DECLARE_PRIVATE(KSelectionProxyModel) + //@cond PRIVATE + KSelectionProxyModelPrivate *d_ptr; + + Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &, int, int)) + Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &, int, int)) + Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int)) + Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &, int, int)) + Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)) + Q_PRIVATE_SLOT(d_func(), void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)) + Q_PRIVATE_SLOT(d_func(), void sourceModelAboutToBeReset()) + Q_PRIVATE_SLOT(d_func(), void sourceModelReset()) + Q_PRIVATE_SLOT(d_func(), void sourceLayoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void sourceLayoutChanged()) + Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &, const QModelIndex &)) + Q_PRIVATE_SLOT(d_func(), void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected)) + Q_PRIVATE_SLOT(d_func(), void sourceModelDestroyed()) + + + //@endcond + +}; + +#endif diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/list-line-top.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/list-line-top.png new file mode 100644 index 0000000000000000000000000000000000000000..c93253b397772c22d9cfebc9dbefec3ed9f4cef2 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^M}e4~gBeKnAFHedQq09po*^6@9Je3(KLBz$3p^r= zfy%FgFr$;k>oc5!lIL8@MUQTpt6Hc~)EmH|E? zuK)l4|M&0T>eZ_;$vu1a00r;gzyIXP6QBYhShHr$*RNlJ44}B>mzw!NLj+5L{DK*n zxTS0Z%Gx&^I(q8-?Wa#)EcEak7A$j#6J0pVv56i~ne2pLC-F^NS#B&zw z_jcJ_k9M1@U0k`P^~&?k4@+)lXfuW1T6dFK{!*pX*Y&SYUjiDz;OXk;vd$@?2>^=R Bd@29{ literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/main.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/main.cpp new file mode 100644 index 00000000..58f87e50 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/main.cpp @@ -0,0 +1,18 @@ + +#include + +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + MainWindow mw; + mw.resize(640, 480); + mw.show(); + + return app.exec(); + +} + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainview.qml b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainview.qml new file mode 100644 index 00000000..ab0dd2a5 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainview.qml @@ -0,0 +1,98 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + color : "#f6f6f5" + anchors.fill : parent + + Image { + id : backgroundImage + width : parent.width + fillMode : Image.TileHorizontally + source : "backgroundtile.png" + } + + BreadcrumbNavigationView { + id : breadcrumbNavView + SystemPalette { id: palette; colorGroup: "Active" } + + breadcrumbComponentFactory : _breadcrumbNavigationFactory + + anchors.top : parent.top; + anchors.bottom : parent.bottom; + anchors.left : parent.left; + width : 200 + +// anchors.rightMargin : 20 + + topDelegate : ListDelegate + { + clickable : true + topItem : true + onIndexSelected : { + breadcrumbTopLevel._transitionSelect = -1; + breadcrumbTopLevel.state = "before_select_home"; + } + } + + breadcrumbDelegate : ListDelegate + { + clickable : true + checkModel : breadcrumbComponentFactory.qmlBreadcrumbCheckModel() + onIndexSelected : { + breadcrumbTopLevel._transitionSelect = row; + breadcrumbTopLevel.state = "before_select_breadcrumb"; + } + } + + selectedItemDelegate : ListDelegate + { + isSelected : true + checkModel : breadcrumbComponentFactory.qmlSelectedItemCheckModel() + } + + childItemsDelegate : ListDelegate + { + clickable : true + isChild : true + checkModel : breadcrumbComponentFactory.qmlChildCheckModel() + onIndexSelected : { + breadcrumbTopLevel._transitionSelect = row; + breadcrumbTopLevel.state = "before_select_child"; + } + } + } + + ListView { + anchors.top : breadcrumbNavView.top; + anchors.bottom : breadcrumbNavView.bottom; + anchors.left : breadcrumbNavView.right; + width : 100 + model : _breadcrumbNavigationFactory.qmlCheckedItemsModel(); + delegate: ListDelegate + { + height : 67 + checkModel : _breadcrumbNavigationFactory.qmlCheckedItemsCheckModel(); + } + } +} diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp new file mode 100644 index 00000000..29f6998a --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.cpp @@ -0,0 +1,170 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "dynamictreemodel.h" +#include "dynamictreewidget.h" + +#include "breadcrumbnavigationcontext.h" +#include +#include +#include "kresettingproxymodel.h" +#include "qmllistselectionmodel.h" +#include "kselectionproxymodel.h" +#include "checkableitemproxymodel.h" + +MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + : QWidget(parent, f) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + QSplitter *splitter = new QSplitter; + layout->addWidget(splitter); + + m_treeModel = new DynamicTreeModel(this); + + DynamicTreeWidget *widget = new DynamicTreeWidget(m_treeModel, splitter); + + QString initialTree; + if (qApp->arguments().size() == 2) + { + QString filename = qApp->arguments().at(1); + QFile f(filename); + if (f.open(QFile::ReadOnly)) + { + initialTree = f.readAll(); + } + f.close(); + } + if (initialTree.isEmpty()){ + initialTree = + "- 1" + "- - 1" + "- - 1" + "- - - 1" + "- - - - 1" + "- - - 1" + "- - - 1" + "- - - - 1" + "- - - - 1" + "- - - - - 1" + "- - - - - - 1" + "- - - - - 1" + "- - - - - -1" + "- - - - - - 1" + "- - - - - - - 1" + "- - - - - - - 1" + "- - - - - - 1" + "- - - - - 1" + "- - - - - 1" + "- - - - 1" + "- - - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- - - - 1" + "- - - - 1" + "- - - - - 1" + "- - - - - - 1" + "- - - - - 1" + "- - - - - -1" + "- - - - - - 1" + "- - - - - - - 1" + "- - - - - - - 1" + "- - - - - - 1" + "- - - - - 1" + "- - - - - 1" + "- - - - 1" + "- - - - 1" + "- - - 1" + "- - - 1" + "- - - 1" + "- 1" + "- - 1" + "- - 1" + "- - 1" + "- - - 1" + "- - - 1" + "- - 1" + "- 1" + "- - 1" + "- - - 1" + "- - - 1" + "- - 1" + "- - 1" + "- - 1" + "- 1"; + } + + widget->setInitialTree(initialTree); + m_declarativeView = new QDeclarativeView(splitter); + + QDeclarativeContext *context = m_declarativeView->engine()->rootContext(); + + m_bnf = new KBreadcrumbNavigationFactory(this); + m_bnf->setBreadcrumbDepth(1); + m_bnf->createCheckableBreadcrumbContext( m_treeModel, this ); + + context->setContextProperty( "_breadcrumbNavigationFactory", m_bnf ); + +// widget->treeView()->setSelectionModel( m_bnf->selectionModel() ); + + context->setContextProperty( "application", QVariant::fromValue( static_cast( this ) ) ); + + m_declarativeView->setResizeMode( QDeclarativeView::SizeRootObjectToView ); + m_declarativeView->setSource( QUrl( "./mainview.qml" ) ); + + splitter->setSizes(QList() << 1 << 1); + +} + +bool MainWindow::childCollectionHasChildren( int row ) +{ + return m_bnf->childCollectionHasChildren(row); +} + +int MainWindow::selectedCollectionRow() +{ + const QModelIndexList list = m_bnf->selectionModel()->selectedRows(); + if (list.size() != 1) + return -1; + return list.first().row(); +} + + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.h new file mode 100644 index 00000000..de2c45d1 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/mainwindow.h @@ -0,0 +1,36 @@ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include + +class QDeclarativeView; +class QTreeView; + +#include "dynamictreemodel.h" + +Q_DECLARE_METATYPE(QModelIndex) + +class KBreadcrumbNavigationFactory; + +class MainWindow : public QWidget +{ + Q_OBJECT +public: + MainWindow(QWidget* parent = 0, Qt::WindowFlags f = 0); + +public slots: + /** Returns whether or not the child collection at row @param row has children. */ + bool childCollectionHasChildren( int row ); + int selectedCollectionRow(); + +private: + QTreeView *m_treeView; + DynamicTreeModel *m_treeModel; + QDeclarativeView *m_declarativeView; + KBreadcrumbNavigationFactory *m_bnf; +}; + +#endif + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.cpp new file mode 100644 index 00000000..dbc794e6 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.cpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "qmllistselectionmodel.h" + +#include + +QMLListSelectionModel::QMLListSelectionModel(QItemSelectionModel *selectionModel, QObject* parent) + : QObject(parent), m_selectionModel(selectionModel) +{ + +} + +QMLListSelectionModel::QMLListSelectionModel(QAbstractItemModel* model, QObject* parent) + : QObject(parent), m_selectionModel(new QItemSelectionModel(model, this)) +{ + +} + +QItemSelectionModel* QMLListSelectionModel::selectionModel() const +{ + return m_selectionModel; +} + +QList< int > QMLListSelectionModel::selection() const +{ + QList< int > list; + const QModelIndexList indexes = m_selectionModel->selectedRows(); + foreach (const QModelIndex &index, indexes) + list << index.row(); + return list; +} + +void QMLListSelectionModel::select(int row, int command) +{ + qDebug() << row << command; + Q_ASSERT(row >= 0); + static const int column = 0; + const QModelIndex idx = m_selectionModel->model()->index(row, column); + Q_ASSERT(idx.isValid()); + qDebug() << idx << idx.data(); + QItemSelection sel(idx, idx); + QItemSelectionModel::SelectionFlags flags = static_cast(command); + m_selectionModel->select(sel, flags); + emit selectionChanged(); +} + +void QMLListSelectionModel::clearSelection() +{ + m_selectionModel->clearSelection(); + emit selectionChanged(); +} + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.h new file mode 100644 index 00000000..bba66790 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/qmllistselectionmodel.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef QMLLISTSELECTIONMODEL_H +#define QMLLISTSELECTIONMODEL_H + +#include + +class QMLListSelectionModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList selection READ selection NOTIFY selectionChanged) +public: + enum SelectionFlag { + NoUpdate = 0x0000, + Clear = 0x0001, + Select = 0x0002, + Deselect = 0x0004, + Toggle = 0x0008, + Current = 0x0010, + Rows = 0x0020, + Columns = 0x0040, + SelectCurrent = Select | Current, + ToggleCurrent = Toggle | Current, + ClearAndSelect = Clear | Select + }; + //Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag) + + explicit QMLListSelectionModel(QItemSelectionModel *selectionModel, QObject* parent = 0); + explicit QMLListSelectionModel(QAbstractItemModel *model, QObject* parent = 0); + + QItemSelectionModel* selectionModel() const; + + QList selection() const; + + +public slots: + void clearSelection(); + void select(int row, int command); + +signals: + void selectionChanged(); + +private: + QItemSelectionModel * const m_selectionModel; +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/scrollable-bottom.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/scrollable-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..60013f2f65509d032e2ab7ce0d0a6eaad82b5079 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^M}Sz4gAGU)GjCJ^QY^(zo*^7SP{WbZ0pxQQctjQh z)n5l;MkkHg6+l7B64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U%db&7< zRNQ)d$C~S~frzW4YNhYD{qFe=FTy#Rdbu2#)?GUL=3!CJ$sdI>?kW$2l6%sb^bDVy za6~NH`pv7sUCrX{qHC)JHn^ONR6JnK@<*=4>44Buk=BM>0WQYd8&^9uI33vjVy`w! zj(9N_qu)#i#RI;k8Z01ak=j%rdq$JxM+tMyF2?Q69~K)$oHN`boFI@OP+-jQUSLBF Z1G9nmjrCUVa)1tH@O1TaS?83{1OOGGVN3u3 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/scrollable-top.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/scrollable-top.png new file mode 100644 index 0000000000000000000000000000000000000000..251f3845279d3a31b366df01aada9f04aacee470 GIT binary patch literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^M}SzKgAGXTh)-$gqjF>*2H&3y3u@EoUx=~)u4ny!iqs%HdqyjT}2$}~qYLFBWI fHp>m8gD;pb8>y&exLec%oy*|q>gTe~DWM4fE5l}R literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/selected_bottom.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/selected_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..4194f3781fbaeeb734b24ee545e50fe88e059b96 GIT binary patch literal 2529 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 r{f+j#_!rv#hdP9O1m+dg=oMN_o3V8RQ-Q< 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 XOMKZLx5v9q00000NkvXXu0mjfoctT8 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/001-assert.patch b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/001-assert.patch new file mode 100644 index 00000000..49a6e907 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/001-assert.patch @@ -0,0 +1,29 @@ +diff --git a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp +index 168c2ca..33a4e6b 100644 +--- a/runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp ++++ b/runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp +@@ -44,8 +44,8 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + m_model = new QStandardItemModel(this); + treeView->setModel(m_model); + +- appendRow(); +- appendRow(); ++// appendRow(); ++// appendRow(); + + m_declarativeView = new QDeclarativeView(splitter); + +@@ -58,8 +58,12 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + + splitter->setSizes(QList() << 1 << 1); + ++ QTimer::singleShot(0, this, SLOT(appendRow())); ++ QTimer::singleShot(1, this, SLOT(appendRow())); ++ QTimer::singleShot(2, this, SLOT(removeTopRow())); ++ + // QTimer::singleShot(4000, this, SLOT(removeBottomRow())); +- QTimer::singleShot(8000, this, SLOT(prependNewRow())); ++// QTimer::singleShot(8000, this, SLOT(prependNewRow())); + + } + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/CMakeLists.txt b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/CMakeLists.txt new file mode 100644 index 00000000..a856acb9 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/CMakeLists.txt @@ -0,0 +1,42 @@ +project(qmlbreadcrumbnavigation) + +cmake_minimum_required(VERSION 2.6) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) + +# Not depending on KDE here for easier portability. +find_package(Qt4 REQUIRED) + +include_directories( + ${QT_INCLUDES} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTSQL_INCLUDE_DIR} + ${QT_QTDECLARATIVE_INCLUDE_DIR} + ${PROJECT_BINARY_DIR} +) + +set(app_SRCS + mainwindow.cpp + main.cpp +) + +set(app_qml_SRCS + mainview.qml +) + +foreach( qml_src ${app_qml_SRCS}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/${qml_src}" "${CMAKE_BINARY_DIR}/${qml_src}") +endforeach() + +qt4_automoc( app_MOC_SRCS ${app_SRCS} ) + +add_executable(qml_nav + ${app_SRCS} +) + +target_link_libraries( + qml_nav + ${QT_QTCORE_LIBRARIES} + ${QT_QTGUI_LIBRARIES} + ${QT_QTDECLARATIVE_LIBRARIES} +) diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/FindQt4.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/FindQt4.cmake new file mode 100644 index 00000000..0fef4275 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/FindQt4.cmake @@ -0,0 +1,1256 @@ +# - Find QT 4 +# This module can be used to find Qt4. +# The most important issue is that the Qt4 qmake is available via the system path. +# This qmake is then used to detect basically everything else. +# This module defines a number of key variables and macros. +# The variable QT_USE_FILE is set which is the path to a CMake file that can be included +# to compile Qt 4 applications and libraries. It sets up the compilation +# environment for include directories, preprocessor defines and populates a +# QT_LIBRARIES variable. +# +# Typical usage could be something like: +# find_package(Qt4 4.4.3 COMPONENTS QtCore QtGui QtXml REQUIRED ) +# include(${QT_USE_FILE}) +# add_executable(myexe main.cpp) +# target_link_libraries(myexe ${QT_LIBRARIES}) +# +# The minimum required version can be specified using the standard find_package()-syntax +# (see example above). +# For compatibility with older versions of FindQt4.cmake it is also possible to +# set the variable QT_MIN_VERSION to the minimum required version of Qt4 before the +# find_package(Qt4) command. +# If both are used, the version used in the find_package() command overrides the +# one from QT_MIN_VERSION. +# +# When using the components argument, QT_USE_QT* variables are automatically set +# for the QT_USE_FILE to pick up. If one wishes to manually set them, the +# available ones to set include: +# QT_DONT_USE_QTCORE +# QT_DONT_USE_QTGUI +# QT_USE_QT3SUPPORT +# QT_USE_QTASSISTANT +# QT_USE_QAXCONTAINER +# QT_USE_QAXSERVER +# QT_USE_QTDESIGNER +# QT_USE_QTMOTIF +# QT_USE_QTMAIN +# QT_USE_QTMULTIMEDIA +# QT_USE_QTNETWORK +# QT_USE_QTNSPLUGIN +# QT_USE_QTOPENGL +# QT_USE_QTSQL +# QT_USE_QTXML +# QT_USE_QTSVG +# QT_USE_QTTEST +# QT_USE_QTUITOOLS +# QT_USE_QTDBUS +# QT_USE_QTSCRIPT +# QT_USE_QTASSISTANTCLIENT +# QT_USE_QTHELP +# QT_USE_QTWEBKIT +# QT_USE_QTXMLPATTERNS +# QT_USE_PHONON +# QT_USE_QTSCRIPTTOOLS +# QT_USE_QTDECLARATIVE +# +# QT_USE_IMPORTED_TARGETS +# If this variable is set to TRUE, FindQt4.cmake will create imported +# library targets for the various Qt libraries and set the +# library variables like QT_QTCORE_LIBRARY to point at these imported +# targets instead of the library file on disk. This provides much better +# handling of the release and debug versions of the Qt libraries and is +# also always backwards compatible, except for the case that dependencies +# of libraries are exported, these will then also list the names of the +# imported targets as dependency and not the file location on disk. This +# is much more flexible, but requires that FindQt4.cmake is executed before +# such an exported dependency file is processed. +# +# There are also some files that need processing by some Qt tools such as moc +# and uic. Listed below are macros that may be used to process those files. +# +# macro QT4_WRAP_CPP(outfiles inputfile ... OPTIONS ...) +# create moc code from a list of files containing Qt class with +# the Q_OBJECT declaration. Per-direcotry preprocessor definitions +# are also added. Options may be given to moc, such as those found +# when executing "moc -help". +# +# macro QT4_WRAP_UI(outfiles inputfile ... OPTIONS ...) +# create code from a list of Qt designer ui files. +# Options may be given to uic, such as those found +# when executing "uic -help" +# +# macro QT4_ADD_RESOURCES(outfiles inputfile ... OPTIONS ...) +# create code from a list of Qt resource files. +# Options may be given to rcc, such as those found +# when executing "rcc -help" +# +# macro QT4_GENERATE_MOC(inputfile outputfile ) +# creates a rule to run moc on infile and create outfile. +# Use this if for some reason QT4_WRAP_CPP() isn't appropriate, e.g. +# because you need a custom filename for the moc file or something similar. +# +# macro QT4_AUTOMOC(sourcefile1 sourcefile2 ... ) +# This macro is still experimental. +# It can be used to have moc automatically handled. +# So if you have the files foo.h and foo.cpp, and in foo.h a +# a class uses the Q_OBJECT macro, moc has to run on it. If you don't +# want to use QT4_WRAP_CPP() (which is reliable and mature), you can insert +# #include "foo.moc" +# in foo.cpp and then give foo.cpp as argument to QT4_AUTOMOC(). This will the +# scan all listed files at cmake-time for such included moc files and if it finds +# them cause a rule to be generated to run moc at build time on the +# accompanying header file foo.h. +# If a source file has the SKIP_AUTOMOC property set it will be ignored by this macro. +# +# macro QT4_ADD_DBUS_INTERFACE(outfiles interface basename) +# create a the interface header and implementation files with the +# given basename from the given interface xml file and add it to +# the list of sources. +# To disable generating a namespace header, set the source file property +# NO_NAMESPACE to TRUE on the interface file. +# To include a header in the interface header, set the source file property +# INCLUDE to the name of the header. +# To specify a class name to use, set the source file property CLASSNAME +# to the name of the class. +# +# macro QT4_ADD_DBUS_INTERFACES(outfiles inputfile ... ) +# create the interface header and implementation files +# for all listed interface xml files +# the name will be automatically determined from the name of the xml file +# To disable generating namespace headers, set the source file property +# NO_NAMESPACE to TRUE for these inputfiles. +# To include a header in the interface header, set the source file property +# INCLUDE to the name of the header. +# To specify a class name to use, set the source file property CLASSNAME +# to the name of the class. +# +# macro QT4_ADD_DBUS_ADAPTOR(outfiles xmlfile parentheader parentclassname [basename] [classname]) +# create a dbus adaptor (header and implementation file) from the xml file +# describing the interface, and add it to the list of sources. The adaptor +# forwards the calls to a parent class, defined in parentheader and named +# parentclassname. The name of the generated files will be +# adaptor.{cpp,h} where basename defaults to the basename of the xml file. +# If is provided, then it will be used as the classname of the +# adaptor itself. +# +# macro QT4_GENERATE_DBUS_INTERFACE( header [interfacename] OPTIONS ...) +# generate the xml interface file from the given header. +# If the optional argument interfacename is omitted, the name of the +# interface file is constructed from the basename of the header with +# the suffix .xml appended. +# Options may be given to qdbuscpp2xml, such as those found when executing "qdbuscpp2xml --help" +# +# macro QT4_CREATE_TRANSLATION( qm_files directories ... sources ... +# ts_files ... OPTIONS ...) +# out: qm_files +# in: directories sources ts_files +# options: flags to pass to lupdate, such as -extensions to specify +# extensions for a directory scan. +# generates commands to create .ts (vie lupdate) and .qm +# (via lrelease) - files from directories and/or sources. The ts files are +# created and/or updated in the source tree (unless given with full paths). +# The qm files are generated in the build tree. +# Updating the translations can be done by adding the qm_files +# to the source list of your library/executable, so they are +# always updated, or by adding a custom target to control when +# they get updated/generated. +# +# macro QT4_ADD_TRANSLATION( qm_files ts_files ... ) +# out: qm_files +# in: ts_files +# generates commands to create .qm from .ts - files. The generated +# filenames can be found in qm_files. The ts_files +# must exists and are not updated in any way. +# +# +# Below is a detailed list of variables that FindQt4.cmake sets. +# QT_FOUND If false, don't try to use Qt. +# QT4_FOUND If false, don't try to use Qt 4. +# +# QT_VERSION_MAJOR The major version of Qt found. +# QT_VERSION_MINOR The minor version of Qt found. +# QT_VERSION_PATCH The patch version of Qt found. +# +# QT_EDITION Set to the edition of Qt (i.e. DesktopLight) +# QT_EDITION_DESKTOPLIGHT True if QT_EDITION == DesktopLight +# QT_QTCORE_FOUND True if QtCore was found. +# QT_QTGUI_FOUND True if QtGui was found. +# QT_QT3SUPPORT_FOUND True if Qt3Support was found. +# QT_QTASSISTANT_FOUND True if QtAssistant was found. +# QT_QTASSISTANTCLIENT_FOUND True if QtAssistantClient was found. +# QT_QAXCONTAINER_FOUND True if QAxContainer was found (Windows only). +# QT_QAXSERVER_FOUND True if QAxServer was found (Windows only). +# QT_QTDBUS_FOUND True if QtDBus was found. +# QT_QTDESIGNER_FOUND True if QtDesigner was found. +# QT_QTDESIGNERCOMPONENTS True if QtDesignerComponents was found. +# QT_QTHELP_FOUND True if QtHelp was found. +# QT_QTMOTIF_FOUND True if QtMotif was found. +# QT_QTMULTIMEDIA_FOUND True if QtMultimedia was found (since Qt 4.6.0). +# QT_QTNETWORK_FOUND True if QtNetwork was found. +# QT_QTNSPLUGIN_FOUND True if QtNsPlugin was found. +# QT_QTOPENGL_FOUND True if QtOpenGL was found. +# QT_QTSQL_FOUND True if QtSql was found. +# QT_QTSVG_FOUND True if QtSvg was found. +# QT_QTSCRIPT_FOUND True if QtScript was found. +# QT_QTSCRIPTTOOLS_FOUND True if QtScriptTools was found. +# QT_QTTEST_FOUND True if QtTest was found. +# QT_QTUITOOLS_FOUND True if QtUiTools was found. +# QT_QTWEBKIT_FOUND True if QtWebKit was found. +# QT_QTXML_FOUND True if QtXml was found. +# QT_QTXMLPATTERNS_FOUND True if QtXmlPatterns was found. +# QT_PHONON_FOUND True if phonon was found. +# QT_QTDECLARATIVE_FOUND True if QtDeclarative was found. +# +# QT_MAC_USE_COCOA For Mac OS X, its whether Cocoa or Carbon is used. +# In general, this should not be used, but its useful +# when having platform specific code. +# +# QT_DEFINITIONS Definitions to use when compiling code that uses Qt. +# You do not need to use this if you include QT_USE_FILE. +# The QT_USE_FILE will also define QT_DEBUG and QT_NO_DEBUG +# to fit your current build type. Those are not contained +# in QT_DEFINITIONS. +# +# QT_INCLUDES List of paths to all include directories of +# Qt4 QT_INCLUDE_DIR and QT_QTCORE_INCLUDE_DIR are +# always in this variable even if NOTFOUND, +# all other INCLUDE_DIRS are +# only added if they are found. +# You do not need to use this if you include QT_USE_FILE. +# +# +# Include directories for the Qt modules are listed here. +# You do not need to use these variables if you include QT_USE_FILE. +# +# QT_INCLUDE_DIR Path to "include" of Qt4 +# QT_QT_INCLUDE_DIR Path to "include/Qt" +# QT_QT3SUPPORT_INCLUDE_DIR Path to "include/Qt3Support" +# QT_QTASSISTANT_INCLUDE_DIR Path to "include/QtAssistant" +# QT_QTASSISTANTCLIENT_INCLUDE_DIR Path to "include/QtAssistant" +# QT_QAXCONTAINER_INCLUDE_DIR Path to "include/ActiveQt" (Windows only) +# QT_QAXSERVER_INCLUDE_DIR Path to "include/ActiveQt" (Windows only) +# QT_QTCORE_INCLUDE_DIR Path to "include/QtCore" +# QT_QTDBUS_INCLUDE_DIR Path to "include/QtDBus" +# QT_QTDESIGNER_INCLUDE_DIR Path to "include/QtDesigner" +# QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR Path to "include/QtDesigner" +# QT_QTGUI_INCLUDE_DIR Path to "include/QtGui" +# QT_QTHELP_INCLUDE_DIR Path to "include/QtHelp" +# QT_QTMOTIF_INCLUDE_DIR Path to "include/QtMotif" +# QT_QTMULTIMEDIA_INCLUDE_DIR Path to "include/QtMultimedia" +# QT_QTNETWORK_INCLUDE_DIR Path to "include/QtNetwork" +# QT_QTNSPLUGIN_INCLUDE_DIR Path to "include/QtNsPlugin" +# QT_QTOPENGL_INCLUDE_DIR Path to "include/QtOpenGL" +# QT_QTSCRIPT_INCLUDE_DIR Path to "include/QtScript" +# QT_QTSQL_INCLUDE_DIR Path to "include/QtSql" +# QT_QTSVG_INCLUDE_DIR Path to "include/QtSvg" +# QT_QTTEST_INCLUDE_DIR Path to "include/QtTest" +# QT_QTWEBKIT_INCLUDE_DIR Path to "include/QtWebKit" +# QT_QTXML_INCLUDE_DIR Path to "include/QtXml" +# QT_QTXMLPATTERNS_INCLUDE_DIR Path to "include/QtXmlPatterns" +# QT_PHONON_INCLUDE_DIR Path to "include/phonon" +# QT_QTSCRIPTTOOLS_INCLUDE_DIR Path to "include/QtScriptTools" +# QT_QTDECLARATIVE_INCLUDE_DIR Path to "include/QtDeclarative" +# +# QT_BINARY_DIR Path to "bin" of Qt4 +# QT_LIBRARY_DIR Path to "lib" of Qt4 +# QT_PLUGINS_DIR Path to "plugins" for Qt4 +# QT_TRANSLATIONS_DIR Path to "translations" of Qt4 +# QT_DOC_DIR Path to "doc" of Qt4 +# QT_MKSPECS_DIR Path to "mkspecs" of Qt4 +# +# +# For every library of Qt, a QT_QTFOO_LIBRARY variable is defined, with the full path to the library. +# +# So there are the following variables: +# The Qt3Support library: QT_QT3SUPPORT_LIBRARY +# +# The QtAssistant library: QT_QTASSISTANT_LIBRARY +# +# The QtAssistantClient library: QT_QTASSISTANTCLIENT_LIBRARY +# +# The QAxServer library: QT_QAXSERVER_LIBRARY +# +# The QAxContainer library: QT_QAXCONTAINER_LIBRARY +# +# The QtCore library: QT_QTCORE_LIBRARY +# +# The QtDBus library: QT_QTDBUS_LIBRARY +# +# The QtDesigner library: QT_QTDESIGNER_LIBRARY +# +# The QtDesignerComponents library: QT_QTDESIGNERCOMPONENTS_LIBRARY +# +# The QtGui library: QT_QTGUI_LIBRARY +# +# The QtHelp library: QT_QTHELP_LIBRARY +# +# The QtMotif library: QT_QTMOTIF_LIBRARY +# +# The QtMultimedia library: QT_QTMULTIMEDIA_LIBRARY +# +# The QtNetwork library: QT_QTNETWORK_LIBRARY +# +# The QtNsPLugin library: QT_QTNSPLUGIN_LIBRARY +# +# The QtOpenGL library: QT_QTOPENGL_LIBRARY +# +# The QtScript library: QT_QTSCRIPT_LIBRARY +# +# The QtScriptTools library: QT_QTSCRIPTTOOLS_LIBRARY +# +# The QtSql library: QT_QTSQL_LIBRARY +# +# The QtSvg library: QT_QTSVG_LIBRARY +# +# The QtTest library: QT_QTTEST_LIBRARY +# +# The QtUiTools library: QT_QTUITOOLS_LIBRARY +# +# The QtWebKit library: QT_QTWEBKIT_LIBRARY +# +# The QtXml library: QT_QTXML_LIBRARY +# +# The QtXmlPatterns library: QT_QTXMLPATTERNS_LIBRARY +# +# The qtmain library for Windows QT_QTMAIN_LIBRARY +# +# The Phonon library: QT_PHONON_LIBRARY +# +# The QtDeclarative library: QT_QTDECLARATIVE_LIBRARY +# +# also defined, but NOT for general use are +# QT_MOC_EXECUTABLE Where to find the moc tool. +# QT_UIC_EXECUTABLE Where to find the uic tool. +# QT_UIC3_EXECUTABLE Where to find the uic3 tool. +# QT_RCC_EXECUTABLE Where to find the rcc tool +# QT_DBUSCPP2XML_EXECUTABLE Where to find the qdbuscpp2xml tool. +# QT_DBUSXML2CPP_EXECUTABLE Where to find the qdbusxml2cpp tool. +# QT_LUPDATE_EXECUTABLE Where to find the lupdate tool. +# QT_LRELEASE_EXECUTABLE Where to find the lrelease tool. +# QT_QCOLLECTIONGENERATOR_EXECUTABLE Where to find the qcollectiongenerator tool. +# QT_DESIGNER_EXECUTABLE Where to find the Qt designer tool. +# QT_LINGUIST_EXECUTABLE Where to find the Qt linguist tool. +# +# +# These are around for backwards compatibility +# they will be set +# QT_WRAP_CPP Set true if QT_MOC_EXECUTABLE is found +# QT_WRAP_UI Set true if QT_UIC_EXECUTABLE is found +# +# These variables do _NOT_ have any effect anymore (compared to FindQt.cmake) +# QT_MT_REQUIRED Qt4 is now always multithreaded +# +# These variables are set to "" Because Qt structure changed +# (They make no sense in Qt4) +# QT_QT_LIBRARY Qt-Library is now split + +# Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +# See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + +# Use FIND_PACKAGE( Qt4 COMPONENTS ... ) to enable modules +IF( Qt4_FIND_COMPONENTS ) + FOREACH( component ${Qt4_FIND_COMPONENTS} ) + STRING( TOUPPER ${component} _COMPONENT ) + SET( QT_USE_${_COMPONENT} 1 ) + ENDFOREACH( component ) + + # To make sure we don't use QtCore or QtGui when not in COMPONENTS + IF(NOT QT_USE_QTCORE) + SET( QT_DONT_USE_QTCORE 1 ) + ENDIF(NOT QT_USE_QTCORE) + + IF(NOT QT_USE_QTGUI) + SET( QT_DONT_USE_QTGUI 1 ) + ENDIF(NOT QT_USE_QTGUI) + +ENDIF( Qt4_FIND_COMPONENTS ) + +# If Qt3 has already been found, fail. +IF(QT_QT_LIBRARY) + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Qt3 and Qt4 cannot be used together in one project. If switching to Qt4, the CMakeCache.txt needs to be cleaned.") + ELSE(Qt4_FIND_REQUIRED) + IF(NOT Qt4_FIND_QUIETLY) + MESSAGE( STATUS "Qt3 and Qt4 cannot be used together in one project. If switching to Qt4, the CMakeCache.txt needs to be cleaned.") + ENDIF(NOT Qt4_FIND_QUIETLY) + RETURN() + ENDIF(Qt4_FIND_REQUIRED) +ENDIF(QT_QT_LIBRARY) + + +IF (QT4_QMAKE_FOUND AND Qt4::QtCore) + # Check already done in this cmake run, nothing more to do + RETURN() +ENDIF (QT4_QMAKE_FOUND AND Qt4::QtCore) + +# check that QT_NO_DEBUG is defined for release configurations +MACRO(QT_CHECK_FLAG_EXISTS FLAG VAR DOC) + IF(NOT ${VAR} MATCHES "${FLAG}") + SET(${VAR} "${${VAR}} ${FLAG}" + CACHE STRING "Flags used by the compiler during ${DOC} builds." FORCE) + ENDIF(NOT ${VAR} MATCHES "${FLAG}") +ENDMACRO(QT_CHECK_FLAG_EXISTS FLAG VAR) + +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_RELWITHDEBINFO "Release with Debug Info") +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_RELEASE "release") +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_MINSIZEREL "release minsize") + +INCLUDE(MacroPushRequiredVars) +INCLUDE(CheckSymbolExists) +INCLUDE(MacroAddFileDependencies) + +SET(QT_USE_FILE ${CMAKE_ROOT}/Modules/UseQt4.cmake) + +SET( QT_DEFINITIONS "") + +SET(QT4_INSTALLED_VERSION_TOO_OLD FALSE) + +# macro for asking qmake to process pro files +MACRO(QT_QUERY_QMAKE outvar invar) + IF(QT_QMAKE_EXECUTABLE) + FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake/tmp.pro + "message(CMAKE_MESSAGE<$$${invar}>)") + + # Invoke qmake with the tmp.pro program to get the desired + # information. Use the same variable for both stdout and stderr + # to make sure we get the output on all platforms. + EXECUTE_PROCESS(COMMAND ${QT_QMAKE_EXECUTABLE} + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake + OUTPUT_VARIABLE _qmake_query_output + RESULT_VARIABLE _qmake_result + ERROR_VARIABLE _qmake_query_output ) + + FILE(REMOVE_RECURSE + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake") + + IF(_qmake_result) + MESSAGE(WARNING " querying qmake for ${invar}. qmake reported:\n${_qmake_query_output}") + ELSE(_qmake_result) + STRING(REGEX REPLACE ".*CMAKE_MESSAGE<([^>]*).*" "\\1" ${outvar} "${_qmake_query_output}") + ENDIF(_qmake_result) + + ENDIF(QT_QMAKE_EXECUTABLE) +ENDMACRO(QT_QUERY_QMAKE) + +GET_FILENAME_COMPONENT(qt_install_version "[HKEY_CURRENT_USER\\Software\\trolltech\\Versions;DefaultQtVersion]" NAME) +# check for qmake +# Debian uses qmake-qt4 +# macports' Qt uses qmake-mac +FIND_PROGRAM(QT_QMAKE_EXECUTABLE NAMES qmake qmake4 qmake-qt4 qmake-mac PATHS + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Qt3Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\${qt_install_version};InstallDir]/bin" + $ENV{QTDIR}/bin +) + +IF (QT_QMAKE_EXECUTABLE) + + IF(QT_QMAKE_EXECUTABLE_LAST) + STRING(COMPARE NOTEQUAL "${QT_QMAKE_EXECUTABLE_LAST}" "${QT_QMAKE_EXECUTABLE}" QT_QMAKE_CHANGED) + ENDIF(QT_QMAKE_EXECUTABLE_LAST) + + SET(QT_QMAKE_EXECUTABLE_LAST "${QT_QMAKE_EXECUTABLE}" CACHE INTERNAL "" FORCE) + + SET(QT4_QMAKE_FOUND FALSE) + + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} ARGS "-query QT_VERSION" OUTPUT_VARIABLE QTVERSION) + + # check for qt3 qmake and then try and find qmake4 or qmake-qt4 in the path + IF("${QTVERSION}" MATCHES "Unknown") + SET(QT_QMAKE_EXECUTABLE NOTFOUND CACHE FILEPATH "" FORCE) + FIND_PROGRAM(QT_QMAKE_EXECUTABLE NAMES qmake4 qmake-qt4 PATHS + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Qt3Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\4.0.0;InstallDir]/bin" + $ENV{QTDIR}/bin + ) + IF(QT_QMAKE_EXECUTABLE) + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_VERSION" OUTPUT_VARIABLE QTVERSION) + ENDIF(QT_QMAKE_EXECUTABLE) + ENDIF("${QTVERSION}" MATCHES "Unknown") + + # check that we found the Qt4 qmake, Qt3 qmake output won't match here + STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" qt_version_tmp "${QTVERSION}") + IF (qt_version_tmp) + + # we need at least version 4.0.0 + IF (NOT QT_MIN_VERSION) + SET(QT_MIN_VERSION "4.0.0") + ENDIF (NOT QT_MIN_VERSION) + + #now parse the parts of the user given version string into variables + STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" req_qt_major_vers "${QT_MIN_VERSION}") + IF (NOT req_qt_major_vers) + MESSAGE( FATAL_ERROR "Invalid Qt version string given: \"${QT_MIN_VERSION}\", expected e.g. \"4.0.1\"") + ENDIF (NOT req_qt_major_vers) + + # now parse the parts of the user given version string into variables + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" req_qt_major_vers "${QT_MIN_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+" "\\1" req_qt_minor_vers "${QT_MIN_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" req_qt_patch_vers "${QT_MIN_VERSION}") + + # Suppport finding at least a particular version, for instance FIND_PACKAGE( Qt4 4.4.3 ) + # This implementation is a hack to avoid duplicating code and make sure we stay + # source-compatible with CMake 2.6.x + IF( Qt4_FIND_VERSION ) + SET( QT_MIN_VERSION ${Qt4_FIND_VERSION} ) + SET( req_qt_major_vers ${Qt4_FIND_VERSION_MAJOR} ) + SET( req_qt_minor_vers ${Qt4_FIND_VERSION_MINOR} ) + SET( req_qt_patch_vers ${Qt4_FIND_VERSION_PATCH} ) + ENDIF( Qt4_FIND_VERSION ) + + IF (NOT req_qt_major_vers EQUAL 4) + MESSAGE( FATAL_ERROR "Invalid Qt version string given: \"${QT_MIN_VERSION}\", major version 4 is required, e.g. \"4.0.1\"") + ENDIF (NOT req_qt_major_vers EQUAL 4) + + # and now the version string given by qmake + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" QT_VERSION_MAJOR "${QTVERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+.*" "\\1" QT_VERSION_MINOR "${QTVERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" QT_VERSION_PATCH "${QTVERSION}") + + # compute an overall version number which can be compared at once + MATH(EXPR req_vers "${req_qt_major_vers}*10000 + ${req_qt_minor_vers}*100 + ${req_qt_patch_vers}") + MATH(EXPR found_vers "${QT_VERSION_MAJOR}*10000 + ${QT_VERSION_MINOR}*100 + ${QT_VERSION_PATCH}") + + # Support finding *exactly* a particular version, for instance FIND_PACKAGE( Qt4 4.4.3 EXACT ) + IF( Qt4_FIND_VERSION_EXACT ) + IF(found_vers EQUAL req_vers) + SET( QT4_QMAKE_FOUND TRUE ) + ELSE(found_vers EQUAL req_vers) + SET( QT4_QMAKE_FOUND FALSE ) + IF (found_vers LESS req_vers) + SET(QT4_INSTALLED_VERSION_TOO_OLD TRUE) + ELSE (found_vers LESS req_vers) + SET(QT4_INSTALLED_VERSION_TOO_NEW TRUE) + ENDIF (found_vers LESS req_vers) + ENDIF(found_vers EQUAL req_vers) + ELSE( Qt4_FIND_VERSION_EXACT ) + IF (found_vers LESS req_vers) + SET(QT4_QMAKE_FOUND FALSE) + SET(QT4_INSTALLED_VERSION_TOO_OLD TRUE) + ELSE (found_vers LESS req_vers) + SET(QT4_QMAKE_FOUND TRUE) + ENDIF (found_vers LESS req_vers) + ENDIF( Qt4_FIND_VERSION_EXACT ) + ENDIF (qt_version_tmp) + +ENDIF (QT_QMAKE_EXECUTABLE) + +IF (QT4_QMAKE_FOUND) + + if (WIN32) + # get qt install dir + get_filename_component(_DIR ${QT_QMAKE_EXECUTABLE} PATH ) + get_filename_component(QT_INSTALL_DIR ${_DIR} PATH ) + endif (WIN32) + + # ask qmake for the library dir + # Set QT_LIBRARY_DIR + IF (NOT QT_LIBRARY_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_LIBS" + OUTPUT_VARIABLE QT_LIBRARY_DIR_TMP ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${QT_LIBRARY_DIR_TMP}" QT_LIBRARY_DIR_TMP) + IF(EXISTS "${QT_LIBRARY_DIR_TMP}") + SET(QT_LIBRARY_DIR ${QT_LIBRARY_DIR_TMP} CACHE PATH "Qt library dir" FORCE) + ELSE(EXISTS "${QT_LIBRARY_DIR_TMP}") + MESSAGE("Warning: QT_QMAKE_EXECUTABLE reported QT_INSTALL_LIBS as ${QT_LIBRARY_DIR_TMP}") + MESSAGE("Warning: ${QT_LIBRARY_DIR_TMP} does NOT exist, Qt must NOT be installed correctly.") + ENDIF(EXISTS "${QT_LIBRARY_DIR_TMP}") + ENDIF(NOT QT_LIBRARY_DIR OR QT_QMAKE_CHANGED) + + IF (APPLE) + IF (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + SET(QT_USE_FRAMEWORKS ON + CACHE BOOL "Set to ON if Qt build uses frameworks." FORCE) + ELSE (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + SET(QT_USE_FRAMEWORKS OFF + CACHE BOOL "Set to ON if Qt build uses frameworks." FORCE) + ENDIF (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + + MARK_AS_ADVANCED(QT_USE_FRAMEWORKS) + ENDIF (APPLE) + + # ask qmake for the binary dir + IF (QT_LIBRARY_DIR AND NOT QT_BINARY_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_BINS" + OUTPUT_VARIABLE qt_bins ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_bins}" qt_bins) + SET(QT_BINARY_DIR ${qt_bins} CACHE INTERNAL "" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_BINARY_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the include dir + IF (QT_LIBRARY_DIR AND NOT QT_HEADERS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_HEADERS" + OUTPUT_VARIABLE qt_headers ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_headers}" qt_headers) + SET(QT_HEADERS_DIR ${qt_headers} CACHE INTERNAL "" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_HEADERS_DIR OR QT_QMAKE_CHANGED) + + + # ask qmake for the documentation directory + IF (QT_LIBRARY_DIR AND NOT QT_DOC_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_DOCS" + OUTPUT_VARIABLE qt_doc_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_doc_dir}" qt_doc_dir) + SET(QT_DOC_DIR ${qt_doc_dir} CACHE PATH "The location of the Qt docs" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_DOC_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the mkspecs directory + IF (QT_LIBRARY_DIR AND NOT QT_MKSPECS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QMAKE_MKSPECS" + OUTPUT_VARIABLE qt_mkspecs_dirs ) + # do not replace : on windows as it might be a drive letter + # and windows should already use ; as a separator + IF(UNIX) + STRING(REPLACE ":" ";" qt_mkspecs_dirs "${qt_mkspecs_dirs}") + ENDIF(UNIX) + SET(QT_MKSPECS_DIR NOTFOUND) + FIND_PATH(QT_MKSPECS_DIR qconfig.pri PATHS ${qt_mkspecs_dirs} + DOC "The location of the Qt mkspecs containing qconfig.pri" + NO_DEFAULT_PATH ) + ENDIF (QT_LIBRARY_DIR AND NOT QT_MKSPECS_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the plugins directory + IF (QT_LIBRARY_DIR AND NOT QT_PLUGINS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_PLUGINS" + OUTPUT_VARIABLE qt_plugins_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_plugins_dir}" qt_plugins_dir) + SET(QT_PLUGINS_DIR ${qt_plugins_dir} CACHE PATH "The location of the Qt plugins" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_PLUGINS_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the translations directory + IF (QT_LIBRARY_DIR AND NOT QT_TRANSLATIONS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_TRANSLATIONS" + OUTPUT_VARIABLE qt_translations_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_translations_dir}" qt_translations_dir) + SET(QT_TRANSLATIONS_DIR ${qt_translations_dir} CACHE PATH "The location of the Qt translations" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_TRANSLATIONS_DIR OR QT_QMAKE_CHANGED) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED( QT_LIBRARY_DIR QT_DOC_DIR QT_MKSPECS_DIR + QT_PLUGINS_DIR QT_TRANSLATIONS_DIR) + + + ############################################# + # + # Find out what window system we're using + # + ############################################# + # Save required includes and required_flags variables + MACRO_PUSH_REQUIRED_VARS() + # Add QT_INCLUDE_DIR to CMAKE_REQUIRED_INCLUDES + SET(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${QT_HEADERS_DIR}") + # On Mac OS X when Qt has framework support, also add the framework path + IF( QT_USE_FRAMEWORKS ) + SET(CMAKE_REQUIRED_FLAGS "-F${QT_LIBRARY_DIR} ") + ENDIF( QT_USE_FRAMEWORKS ) + # Check for Window system symbols (note: only one should end up being set) + CHECK_SYMBOL_EXISTS(Q_WS_X11 "QtCore/qglobal.h" Q_WS_X11) + CHECK_SYMBOL_EXISTS(Q_WS_WIN "QtCore/qglobal.h" Q_WS_WIN) + CHECK_SYMBOL_EXISTS(Q_WS_QWS "QtCore/qglobal.h" Q_WS_QWS) + CHECK_SYMBOL_EXISTS(Q_WS_MAC "QtCore/qglobal.h" Q_WS_MAC) + IF(Q_WS_MAC) + IF(QT_QMAKE_CHANGED) + SET(QT_MAC_USE_COCOA "" CACHE BOOL "Use Cocoa on Mac" FORCE) + ENDIF(QT_QMAKE_CHANGED) + CHECK_SYMBOL_EXISTS(QT_MAC_USE_COCOA "QtCore/qconfig.h" QT_MAC_USE_COCOA) + ENDIF(Q_WS_MAC) + + IF (QT_QTCOPY_REQUIRED) + CHECK_SYMBOL_EXISTS(QT_IS_QTCOPY "QtCore/qglobal.h" QT_KDE_QT_COPY) + IF (NOT QT_IS_QTCOPY) + MESSAGE(FATAL_ERROR "qt-copy is required, but hasn't been found") + ENDIF (NOT QT_IS_QTCOPY) + ENDIF (QT_QTCOPY_REQUIRED) + + # Restore CMAKE_REQUIRED_INCLUDES+CMAKE_REQUIRED_FLAGS variables + MACRO_POP_REQUIRED_VARS() + # + ############################################# + + + + ######################################## + # + # Setting the INCLUDE-Variables + # + ######################################## + + SET(QT_MODULES QtCore QtGui Qt3Support QtSvg QtScript QtTest QtUiTools + QtHelp QtWebKit QtXmlPatterns QtNetwork QtMultimedia + QtNsPlugin QtOpenGL QtSql QtXml QtDesigner QtDBus QtScriptTools QtDeclarative) + + IF(Q_WS_X11) + SET(QT_MODULES ${QT_MODULES} QtMotif) + ENDIF(Q_WS_X11) + + IF(QT_QMAKE_CHANGED) + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + SET(QT_${_upper_qt_module}_INCLUDE_DIR NOTFOUND) + SET(QT_${_upper_qt_module}_LIBRARY_RELEASE NOTFOUND) + SET(QT_${_upper_qt_module}_LIBRARY_DEBUG NOTFOUND) + ENDFOREACH(QT_MODULE) + SET(QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR NOTFOUND) + SET(QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTDESIGNERCOMPONENTS_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTASSISTANTCLIENT_INCLUDE_DIR NOTFOUND) + SET(QT_QTASSISTANTCLIENT_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTASSISTANTCLIENT_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTASSISTANT_INCLUDE_DIR NOTFOUND) + SET(QT_QTASSISTANT_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTASSISTANT_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTCLUCENE_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTCLUCENE_LIBRARY_DEBUG NOTFOUND) + SET(QT_QAXCONTAINER_INCLUDE_DIR NOTFOUND) + SET(QT_QAXCONTAINER_LIBRARY_RELEASE NOTFOUND) + SET(QT_QAXCONTAINER_LIBRARY_DEBUG NOTFOUND) + SET(QT_QAXSERVER_INCLUDE_DIR NOTFOUND) + SET(QT_QAXSERVER_LIBRARY_RELEASE NOTFOUND) + SET(QT_QAXSERVER_LIBRARY_DEBUG NOTFOUND) + IF(WIN32) + SET(QT_QTMAIN_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTMAIN_LIBRARY_RELEASE NOTFOUND) + ENDIF(WIN32) + SET(QT_PHONON_INCLUDE_DIR NOTFOUND) + ENDIF(QT_QMAKE_CHANGED) + + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + FIND_PATH(QT_${_upper_qt_module}_INCLUDE_DIR ${QT_MODULE} + PATHS + ${QT_HEADERS_DIR}/${QT_MODULE} + ${QT_LIBRARY_DIR}/${QT_MODULE}.framework/Headers + NO_DEFAULT_PATH + ) + ENDFOREACH(QT_MODULE) + + IF(WIN32) + SET(QT_MODULES ${QT_MODULES} QAxContainer QAxServer) + # Set QT_AXCONTAINER_INCLUDE_DIR and QT_AXSERVER_INCLUDE_DIR + FIND_PATH(QT_QAXCONTAINER_INCLUDE_DIR ActiveQt + PATHS + ${QT_HEADERS_DIR}/ActiveQt + NO_DEFAULT_PATH + ) + FIND_PATH(QT_QAXSERVER_INCLUDE_DIR ActiveQt + PATHS + ${QT_HEADERS_DIR}/ActiveQt + NO_DEFAULT_PATH + ) + ENDIF(WIN32) + + # Set QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR + FIND_PATH(QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR QDesignerComponents + PATHS + ${QT_HEADERS_DIR}/QtDesigner + ${QT_LIBRARY_DIR}/QtDesigner.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QTASSISTANT_INCLUDE_DIR + FIND_PATH(QT_QTASSISTANT_INCLUDE_DIR QtAssistant + PATHS + ${QT_HEADERS_DIR}/QtAssistant + ${QT_LIBRARY_DIR}/QtAssistant.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QTASSISTANTCLIENT_INCLUDE_DIR + FIND_PATH(QT_QTASSISTANTCLIENT_INCLUDE_DIR QAssistantClient + PATHS + ${QT_HEADERS_DIR}/QtAssistant + ${QT_LIBRARY_DIR}/QtAssistant.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QT_INCLUDE_DIR + FIND_PATH(QT_QT_INCLUDE_DIR qglobal.h + PATHS + ${QT_HEADERS_DIR}/Qt + ${QT_LIBRARY_DIR}/QtCore.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_PHONON_INCLUDE_DIR + # Qt >= 4.5.3 (or kde-qt-4.5.2 which has the fix too) : Phonon/ClassName is inside include/phonon + # With previous versions of Qt, this could not work; upgrade Qt or use a standalone phonon + FIND_PATH(QT_PHONON_INCLUDE_DIR Phonon + PATHS + ${QT_HEADERS_DIR}/phonon + NO_DEFAULT_PATH + ) + SET(QT_MODULES ${QT_MODULES} phonon) + + # Set QT_INCLUDE_DIR by removine "/QtCore" in the string ${QT_QTCORE_INCLUDE_DIR} + IF( QT_QTCORE_INCLUDE_DIR AND NOT QT_INCLUDE_DIR) + IF (QT_USE_FRAMEWORKS) + SET(QT_INCLUDE_DIR ${QT_HEADERS_DIR}) + ELSE (QT_USE_FRAMEWORKS) + STRING( REGEX REPLACE "/QtCore$" "" qt4_include_dir ${QT_QTCORE_INCLUDE_DIR}) + SET( QT_INCLUDE_DIR ${qt4_include_dir} CACHE PATH "") + ENDIF (QT_USE_FRAMEWORKS) + ENDIF( QT_QTCORE_INCLUDE_DIR AND NOT QT_INCLUDE_DIR) + + IF( NOT QT_INCLUDE_DIR) + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could NOT find QtCore header") + ENDIF(Qt4_FIND_REQUIRED) + ENDIF( NOT QT_INCLUDE_DIR) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED( QT_INCLUDE_DIR QT_QT_INCLUDE_DIR) + + # Set QT_INCLUDES + SET( QT_INCLUDES ${QT_QT_INCLUDE_DIR} ${QT_MKSPECS_DIR}/default ${QT_INCLUDE_DIR} ) + + + ####################################### + # + # Qt configuration + # + ####################################### + IF(EXISTS "${QT_MKSPECS_DIR}/qconfig.pri") + FILE(READ ${QT_MKSPECS_DIR}/qconfig.pri _qconfig_FILE_contents) + STRING(REGEX MATCH "QT_CONFIG[^\n]+" QT_QCONFIG "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "CONFIG[^\n]+" QT_CONFIG "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "EDITION[^\n]+" QT_EDITION "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "QT_LIBINFIX[^\n]+" _qconfig_qt_libinfix "${_qconfig_FILE_contents}") + STRING(REGEX REPLACE "QT_LIBINFIX *= *([^\n]*)" "\\1" QT_LIBINFIX "${_qconfig_qt_libinfix}") + ENDIF(EXISTS "${QT_MKSPECS_DIR}/qconfig.pri") + IF("${QT_EDITION}" MATCHES "DesktopLight") + SET(QT_EDITION_DESKTOPLIGHT 1) + ENDIF("${QT_EDITION}" MATCHES "DesktopLight") + + ######################################## + # + # Setting the LIBRARY-Variables + # + ######################################## + + # find the libraries + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + FIND_LIBRARY(QT_${_upper_qt_module}_LIBRARY_RELEASE + NAMES ${QT_MODULE}${QT_LIBINFIX} ${QT_MODULE}${QT_LIBINFIX}4 + PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH + ) + FIND_LIBRARY(QT_${_upper_qt_module}_LIBRARY_DEBUG + NAMES ${QT_MODULE}${QT_LIBINFIX}_debug ${QT_MODULE}${QT_LIBINFIX}d ${QT_MODULE}${QT_LIBINFIX}d4 + PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH + ) + ENDFOREACH(QT_MODULE) + + # QtUiTools not with other frameworks with binary installation (in /usr/lib) + IF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTUITOOLS_LIBRARY_RELEASE) + FIND_LIBRARY(QT_QTUITOOLS_LIBRARY_RELEASE NAMES QtUiTools${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR}) + ENDIF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTUITOOLS_LIBRARY_RELEASE) + + IF( NOT QT_QTCORE_LIBRARY_DEBUG AND NOT QT_QTCORE_LIBRARY_RELEASE ) + + # try dropping a hint if trying to use Visual Studio with Qt built by mingw + IF(QT_LIBRARY_DIR AND MSVC) + IF(EXISTS ${QT_LIBRARY_DIR}/libqtmain.a) + MESSAGE( FATAL_ERROR "It appears you're trying to use Visual Studio with Qt built by mingw") + ENDIF(EXISTS ${QT_LIBRARY_DIR}/libqtmain.a) + ENDIF(QT_LIBRARY_DIR AND MSVC) + + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could NOT find QtCore. Check ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log for more details.") + ENDIF(Qt4_FIND_REQUIRED) + ENDIF( NOT QT_QTCORE_LIBRARY_DEBUG AND NOT QT_QTCORE_LIBRARY_RELEASE ) + + # Set QT_QTDESIGNERCOMPONENTS_LIBRARY + FIND_LIBRARY(QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE NAMES QtDesignerComponents${QT_LIBINFIX} QtDesignerComponents${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTDESIGNERCOMPONENTS_LIBRARY_DEBUG NAMES QtDesignerComponents${QT_LIBINFIX}_debug QtDesignerComponents${QT_LIBINFIX}d QtDesignerComponents${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTMAIN_LIBRARY + IF(WIN32) + FIND_LIBRARY(QT_QTMAIN_LIBRARY_RELEASE NAMES qtmain${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR} + NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTMAIN_LIBRARY_DEBUG NAMES qtmain${QT_LIBINFIX}d PATHS ${QT_LIBRARY_DIR} + NO_DEFAULT_PATH) + ENDIF(WIN32) + + # Set QT_QTASSISTANTCLIENT_LIBRARY + FIND_LIBRARY(QT_QTASSISTANTCLIENT_LIBRARY_RELEASE NAMES QtAssistantClient${QT_LIBINFIX} QtAssistantClient${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTASSISTANTCLIENT_LIBRARY_DEBUG NAMES QtAssistantClient${QT_LIBINFIX}_debug QtAssistantClient${QT_LIBINFIX}d QtAssistantClient${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTASSISTANT_LIBRARY + FIND_LIBRARY(QT_QTASSISTANT_LIBRARY_RELEASE NAMES QtAssistantClient${QT_LIBINFIX} QtAssistantClient${QT_LIBINFIX}4 QtAssistant${QT_LIBINFIX} QtAssistant${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTASSISTANT_LIBRARY_DEBUG NAMES QtAssistantClient${QT_LIBINFIX}_debug QtAssistantClient${QT_LIBINFIX}d QtAssistantClient${QT_LIBINFIX}d4 QtAssistant${QT_LIBINFIX}_debug QtAssistant${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTHELP_LIBRARY + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_RELEASE NAMES QtCLucene${QT_LIBINFIX} QtCLucene${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_DEBUG NAMES QtCLucene${QT_LIBINFIX}_debug QtCLucene${QT_LIBINFIX}d QtCLucene${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + # QtCLucene not with other frameworks with binary installation (in /usr/lib) + IF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTCLUCENE_LIBRARY_RELEASE) + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_RELEASE NAMES QtCLucene${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR}) + ENDIF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTCLUCENE_LIBRARY_RELEASE) + + ############################################ + # + # Check the existence of the libraries. + # + ############################################ + + # On OSX when Qt is found as framework, never use the imported targets for now, since + # in this case the handling of the framework directory currently does not work correctly. + IF(QT_USE_FRAMEWORKS) + SET(QT_USE_IMPORTED_TARGETS FALSE) + ENDIF(QT_USE_FRAMEWORKS) + + + MACRO (_QT4_ADJUST_LIB_VARS _camelCaseBasename) + + STRING(TOUPPER "${_camelCaseBasename}" basename) + + # The name of the imported targets, i.e. the prefix "Qt4::" must not change, + # since it is stored in EXPORT-files as name of a required library. If the name would change + # here, this would lead to the imported Qt4-library targets not being resolved by cmake anymore. + IF (QT_${basename}_LIBRARY_RELEASE OR QT_${basename}_LIBRARY_DEBUG) + + IF(NOT TARGET Qt4::${_camelCaseBasename}) + ADD_LIBRARY(Qt4::${_camelCaseBasename} UNKNOWN IMPORTED ) + + IF (QT_${basename}_LIBRARY_RELEASE) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} PROPERTY IMPORTED_LOCATION_RELEASE "${QT_${basename}_LIBRARY_RELEASE}" ) + ENDIF (QT_${basename}_LIBRARY_RELEASE) + + IF (QT_${basename}_LIBRARY_DEBUG) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} PROPERTY IMPORTED_LOCATION_DEBUG "${QT_${basename}_LIBRARY_DEBUG}" ) + ENDIF (QT_${basename}_LIBRARY_DEBUG) + ENDIF(NOT TARGET Qt4::${_camelCaseBasename}) + + # If QT_USE_IMPORTED_TARGETS is enabled, the QT_QTFOO_LIBRARY variables are set to point at these + # imported targets. This works better in general, and is also in almost all cases fully + # backward compatible. The only issue is when a project A which had this enabled then exports its + # libraries via export or EXPORT_LIBRARY_DEPENDENCIES(). In this case the libraries from project + # A will depend on the imported Qt targets, and the names of these imported targets will be stored + # in the dependency files on disk. This means when a project B then uses project A, these imported + # targets must be created again, otherwise e.g. "Qt4__QtCore" will be interpreted as name of a + # library file on disk, and not as a target, and linking will fail: + IF(QT_USE_IMPORTED_TARGETS) + SET(QT_${basename}_LIBRARY Qt4::${_camelCaseBasename} ) + SET(QT_${basename}_LIBRARIES Qt4::${_camelCaseBasename} ) + ELSE(QT_USE_IMPORTED_TARGETS) + + # if the release- as well as the debug-version of the library have been found: + IF (QT_${basename}_LIBRARY_DEBUG AND QT_${basename}_LIBRARY_RELEASE) + # if the generator supports configuration types then set + # optimized and debug libraries, or if the CMAKE_BUILD_TYPE has a value + IF (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + SET(QT_${basename}_LIBRARY optimized ${QT_${basename}_LIBRARY_RELEASE} debug ${QT_${basename}_LIBRARY_DEBUG}) + ELSE(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + # if there are no configuration types and CMAKE_BUILD_TYPE has no value + # then just use the release libraries + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_RELEASE} ) + ENDIF(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + SET(QT_${basename}_LIBRARIES optimized ${QT_${basename}_LIBRARY_RELEASE} debug ${QT_${basename}_LIBRARY_DEBUG}) + ENDIF (QT_${basename}_LIBRARY_DEBUG AND QT_${basename}_LIBRARY_RELEASE) + + # if only the release version was found, set the debug variable also to the release version + IF (QT_${basename}_LIBRARY_RELEASE AND NOT QT_${basename}_LIBRARY_DEBUG) + SET(QT_${basename}_LIBRARY_DEBUG ${QT_${basename}_LIBRARY_RELEASE}) + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_RELEASE}) + SET(QT_${basename}_LIBRARIES ${QT_${basename}_LIBRARY_RELEASE}) + ENDIF (QT_${basename}_LIBRARY_RELEASE AND NOT QT_${basename}_LIBRARY_DEBUG) + + # if only the debug version was found, set the release variable also to the debug version + IF (QT_${basename}_LIBRARY_DEBUG AND NOT QT_${basename}_LIBRARY_RELEASE) + SET(QT_${basename}_LIBRARY_RELEASE ${QT_${basename}_LIBRARY_DEBUG}) + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_DEBUG}) + SET(QT_${basename}_LIBRARIES ${QT_${basename}_LIBRARY_DEBUG}) + ENDIF (QT_${basename}_LIBRARY_DEBUG AND NOT QT_${basename}_LIBRARY_RELEASE) + + # put the value in the cache: + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY} CACHE STRING "The Qt ${basename} library" FORCE) + + ENDIF(QT_USE_IMPORTED_TARGETS) + +#message(STATUS "QT_${basename}_LIBRARY: ${QT_${basename}_LIBRARY}") + + SET(QT_${basename}_FOUND 1) + + ENDIF (QT_${basename}_LIBRARY_RELEASE OR QT_${basename}_LIBRARY_DEBUG) + + IF (QT_${basename}_INCLUDE_DIR) + #add the include directory to QT_INCLUDES + SET(QT_INCLUDES "${QT_${basename}_INCLUDE_DIR}" ${QT_INCLUDES}) + ENDIF (QT_${basename}_INCLUDE_DIR) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED(QT_${basename}_LIBRARY QT_${basename}_LIBRARY_RELEASE QT_${basename}_LIBRARY_DEBUG QT_${basename}_INCLUDE_DIR) + ENDMACRO (_QT4_ADJUST_LIB_VARS) + + + # Set QT_xyz_LIBRARY variable and add + # library include path to QT_INCLUDES + _QT4_ADJUST_LIB_VARS(QtCore) + _QT4_ADJUST_LIB_VARS(QtGui) + _QT4_ADJUST_LIB_VARS(Qt3Support) + _QT4_ADJUST_LIB_VARS(QtAssistant) + _QT4_ADJUST_LIB_VARS(QtAssistantClient) + _QT4_ADJUST_LIB_VARS(QtCLucene) + _QT4_ADJUST_LIB_VARS(QtDBus) + _QT4_ADJUST_LIB_VARS(QtDeclarative) + _QT4_ADJUST_LIB_VARS(QtDesigner) + _QT4_ADJUST_LIB_VARS(QtDesignerComponents) + _QT4_ADJUST_LIB_VARS(QtHelp) + _QT4_ADJUST_LIB_VARS(QtMultimedia) + _QT4_ADJUST_LIB_VARS(QtNetwork) + _QT4_ADJUST_LIB_VARS(QtNsPlugin) + _QT4_ADJUST_LIB_VARS(QtOpenGL) + _QT4_ADJUST_LIB_VARS(QtScript) + _QT4_ADJUST_LIB_VARS(QtScriptTools) + _QT4_ADJUST_LIB_VARS(QtSql) + _QT4_ADJUST_LIB_VARS(QtSvg) + _QT4_ADJUST_LIB_VARS(QtTest) + _QT4_ADJUST_LIB_VARS(QtUiTools) + _QT4_ADJUST_LIB_VARS(QtWebKit) + _QT4_ADJUST_LIB_VARS(QtXml) + _QT4_ADJUST_LIB_VARS(QtXmlPatterns) + _QT4_ADJUST_LIB_VARS(phonon) + + # platform dependent libraries + IF(Q_WS_X11) + _QT4_ADJUST_LIB_VARS(QtMotif) + ENDIF(Q_WS_X11) + IF(WIN32) + _QT4_ADJUST_LIB_VARS(qtmain) + _QT4_ADJUST_LIB_VARS(QAxServer) + _QT4_ADJUST_LIB_VARS(QAxContainer) + ENDIF(WIN32) + + # If Qt is installed as a framework, we need to add QT_QTCORE_LIBRARY here (which + # is the framework directory in that case), since this will make the cmake include_directories() + # command recognize that we need the framework flag with the respective directory (-F) + IF(QT_USE_FRAMEWORKS) + SET(QT_INCLUDES ${QT_INCLUDES} ${QT_QTCORE_LIBRARY} ) + SET(QT_INCLUDE_DIR ${QT_INCLUDE_DIR} ${QT_QTCORE_LIBRARY} ) + ENDIF(QT_USE_FRAMEWORKS) + + + + ####################################### + # + # Check the executables of Qt + # ( moc, uic, rcc ) + # + ####################################### + + + IF(QT_QMAKE_CHANGED) + SET(QT_UIC_EXECUTABLE NOTFOUND) + SET(QT_MOC_EXECUTABLE NOTFOUND) + SET(QT_UIC3_EXECUTABLE NOTFOUND) + SET(QT_RCC_EXECUTABLE NOTFOUND) + SET(QT_DBUSCPP2XML_EXECUTABLE NOTFOUND) + SET(QT_DBUSXML2CPP_EXECUTABLE NOTFOUND) + SET(QT_LUPDATE_EXECUTABLE NOTFOUND) + SET(QT_LRELEASE_EXECUTABLE NOTFOUND) + SET(QT_QCOLLECTIONGENERATOR_EXECUTABLE NOTFOUND) + SET(QT_DESIGNER_EXECUTABLE NOTFOUND) + SET(QT_LINGUIST_EXECUTABLE NOTFOUND) + ENDIF(QT_QMAKE_CHANGED) + + FIND_PROGRAM(QT_MOC_EXECUTABLE + NAMES moc-qt4 moc + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_UIC_EXECUTABLE + NAMES uic-qt4 uic + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_UIC3_EXECUTABLE + NAMES uic3 + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_RCC_EXECUTABLE + NAMES rcc + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DBUSCPP2XML_EXECUTABLE + NAMES qdbuscpp2xml + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DBUSXML2CPP_EXECUTABLE + NAMES qdbusxml2cpp + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LUPDATE_EXECUTABLE + NAMES lupdate-qt4 lupdate + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LRELEASE_EXECUTABLE + NAMES lrelease-qt4 lrelease + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_QCOLLECTIONGENERATOR_EXECUTABLE + NAMES qcollectiongenerator-qt4 qcollectiongenerator + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DESIGNER_EXECUTABLE + NAMES designer-qt4 designer + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LINGUIST_EXECUTABLE + NAMES linguist-qt4 linguist + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + IF (QT_MOC_EXECUTABLE) + SET(QT_WRAP_CPP "YES") + ENDIF (QT_MOC_EXECUTABLE) + + IF (QT_UIC_EXECUTABLE) + SET(QT_WRAP_UI "YES") + ENDIF (QT_UIC_EXECUTABLE) + + + + MARK_AS_ADVANCED( QT_UIC_EXECUTABLE QT_UIC3_EXECUTABLE QT_MOC_EXECUTABLE + QT_RCC_EXECUTABLE QT_DBUSXML2CPP_EXECUTABLE QT_DBUSCPP2XML_EXECUTABLE + QT_LUPDATE_EXECUTABLE QT_LRELEASE_EXECUTABLE QT_QCOLLECTIONGENERATOR_EXECUTABLE + QT_DESIGNER_EXECUTABLE QT_LINGUIST_EXECUTABLE) + + + # get the directory of the current file, used later on in the file + GET_FILENAME_COMPONENT( _qt4_current_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) + + ###################################### + # + # Macros for building Qt files + # + ###################################### + + INCLUDE("${_qt4_current_dir}/Qt4Macros.cmake") + + + ###################################### + # + # decide if Qt got found + # + ###################################### + + # if the includes,libraries,moc,uic and rcc are found then we have it + IF( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + SET( QT4_FOUND "YES" ) + IF( NOT Qt4_FIND_QUIETLY) + MESSAGE(STATUS "Found Qt-Version ${QTVERSION} (using ${QT_QMAKE_EXECUTABLE})") + ENDIF( NOT Qt4_FIND_QUIETLY) + ELSE( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + SET( QT4_FOUND "NO") + SET(QT_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}-NOTFOUND" CACHE FILEPATH "Invalid qmake found" FORCE) + IF( Qt4_FIND_REQUIRED) + IF ( NOT QT_LIBRARY_DIR ) + MESSAGE(STATUS "Qt libraries NOT found!") + ENDIF(NOT QT_LIBRARY_DIR ) + IF ( NOT QT_INCLUDE_DIR ) + MESSAGE(STATUS "Qt includes NOT found!") + ENDIF( NOT QT_INCLUDE_DIR ) + IF ( NOT QT_MOC_EXECUTABLE ) + MESSAGE(STATUS "Qt's moc NOT found!") + ENDIF( NOT QT_MOC_EXECUTABLE ) + IF ( NOT QT_UIC_EXECUTABLE ) + MESSAGE(STATUS "Qt's uic NOT found!") + ENDIF( NOT QT_UIC_EXECUTABLE ) + IF ( NOT QT_RCC_EXECUTABLE ) + MESSAGE(STATUS "Qt's rcc NOT found!") + ENDIF( NOT QT_RCC_EXECUTABLE ) + MESSAGE( FATAL_ERROR "Qt libraries, includes, moc, uic or/and rcc NOT found!") + ENDIF( Qt4_FIND_REQUIRED) + ENDIF( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + + SET(QT_FOUND ${QT4_FOUND}) + + + ############################################### + # + # configuration/system dependent settings + # + ############################################### + + INCLUDE("${_qt4_current_dir}/Qt4ConfigDependentSettings.cmake") + + + ####################################### + # + # compatibility settings + # + ####################################### + # Backwards compatibility for CMake1.4 and 1.2 + SET (QT_MOC_EXE ${QT_MOC_EXECUTABLE} ) + SET (QT_UIC_EXE ${QT_UIC_EXECUTABLE} ) + + SET( QT_QT_LIBRARY "") + +ELSE(QT4_QMAKE_FOUND) + + SET(QT_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}-NOTFOUND" CACHE FILEPATH "Invalid qmake found" FORCE) + + # The code below is overly complex to make sure we do not break compatibility with CMake 2.6.x + # For CMake 2.8, it should be simplified by getting rid of QT4_INSTALLED_VERSION_TOO_OLD and + # QT4_INSTALLED_VERSION_TOO_NEW + IF(Qt4_FIND_REQUIRED) + IF(QT4_INSTALLED_VERSION_TOO_OLD) + IF( Qt4_FIND_VERSION_EXACT ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too old, version ${QT_MIN_VERSION} is required") + ELSE( Qt4_FIND_VERSION_EXACT ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too old, at least version ${QT_MIN_VERSION} is required") + ENDIF( Qt4_FIND_VERSION_EXACT ) + ELSE(QT4_INSTALLED_VERSION_TOO_OLD) + IF( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too new, version ${QT_MIN_VERSION} is required") + ELSE( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + MESSAGE( FATAL_ERROR "Qt qmake not found!") + ENDIF( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + ENDIF(QT4_INSTALLED_VERSION_TOO_OLD) + ELSE(Qt4_FIND_REQUIRED) + IF(QT4_INSTALLED_VERSION_TOO_OLD AND NOT Qt4_FIND_QUIETLY) + MESSAGE(STATUS "The installed Qt version ${QTVERSION} is too old, at least version ${QT_MIN_VERSION} is required") + ENDIF(QT4_INSTALLED_VERSION_TOO_OLD AND NOT Qt4_FIND_QUIETLY) + ENDIF(Qt4_FIND_REQUIRED) + +ENDIF (QT4_QMAKE_FOUND) + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/MacroPushRequiredVars.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/MacroPushRequiredVars.cmake new file mode 100644 index 00000000..650b566e --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/MacroPushRequiredVars.cmake @@ -0,0 +1,47 @@ +# this module defines two macros: +# MACRO_PUSH_REQUIRED_VARS() +# and +# MACRO_POP_REQUIRED_VARS() +# use these if you call cmake macros which use +# any of the CMAKE_REQUIRED_XXX variables +# +# Usage: +# MACRO_PUSH_REQUIRED_VARS() +# SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -DSOME_MORE_DEF) +# CHECK_FUNCTION_EXISTS(...) +# MACRO_POP_REQUIRED_VARS() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(MACRO_PUSH_REQUIRED_VARS) + + IF(NOT DEFINED _PUSH_REQUIRED_VARS_COUNTER) + SET(_PUSH_REQUIRED_VARS_COUNTER 0) + ENDIF(NOT DEFINED _PUSH_REQUIRED_VARS_COUNTER) + + MATH(EXPR _PUSH_REQUIRED_VARS_COUNTER "${_PUSH_REQUIRED_VARS_COUNTER}+1") + + SET(_CMAKE_REQUIRED_INCLUDES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_INCLUDES}) + SET(_CMAKE_REQUIRED_DEFINITIONS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_DEFINITIONS}) + SET(_CMAKE_REQUIRED_LIBRARIES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_LIBRARIES}) + SET(_CMAKE_REQUIRED_FLAGS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_FLAGS}) +ENDMACRO(MACRO_PUSH_REQUIRED_VARS) + +MACRO(MACRO_POP_REQUIRED_VARS) + +# don't pop more than we pushed + IF("${_PUSH_REQUIRED_VARS_COUNTER}" GREATER "0") + + SET(CMAKE_REQUIRED_INCLUDES ${_CMAKE_REQUIRED_INCLUDES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_DEFINITIONS ${_CMAKE_REQUIRED_DEFINITIONS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_LIBRARIES ${_CMAKE_REQUIRED_LIBRARIES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_FLAGS ${_CMAKE_REQUIRED_FLAGS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + + MATH(EXPR _PUSH_REQUIRED_VARS_COUNTER "${_PUSH_REQUIRED_VARS_COUNTER}-1") + ENDIF("${_PUSH_REQUIRED_VARS_COUNTER}" GREATER "0") + +ENDMACRO(MACRO_POP_REQUIRED_VARS) + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4ConfigDependentSettings.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4ConfigDependentSettings.cmake new file mode 100644 index 00000000..b5462e7b --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4ConfigDependentSettings.cmake @@ -0,0 +1,384 @@ +# This file is included by FindQt4.cmake, don't include it directly. + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + + +############################################### +# +# configuration/system dependent settings +# +############################################### + +# this check for X11 and threads may not be necessary, since it is not +# contained in the cmake version of FindQt4.cmake: + +# for unix add X11 stuff +IF(UNIX) + # on OS X X11 may not be required + IF (Q_WS_X11) + FIND_PACKAGE(X11 REQUIRED) + ENDIF (Q_WS_X11) + FIND_PACKAGE(Threads) + SET(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +ENDIF(UNIX) + + +# find dependencies for some Qt modules +# when doing builds against a static Qt, they are required +# when doing builds against a shared Qt, they are not required +# if a user needs the dependencies, and they couldn't be found, they can set +# the variables themselves. + +SET(QT_QTGUI_LIB_DEPENDENCIES "") +SET(QT_QTCORE_LIB_DEPENDENCIES "") +SET(QT_QTNETWORK_LIB_DEPENDENCIES "") +SET(QT_QTOPENGL_LIB_DEPENDENCIES "") +SET(QT_QTDBUS_LIB_DEPENDENCIES "") +SET(QT_QTHELP_LIB_DEPENDENCIES ${QT_QTCLUCENE_LIBRARY}) + + +IF(WIN32) + # On Windows, qconfig.pri has "static" for static library builds + IF(QT_CONFIG MATCHES "static") + SET(QT_IS_STATIC 1) + ENDIF(QT_CONFIG MATCHES "static") +ELSE(WIN32) + # On other platforms, check file extension to know if its static + IF(QT_QTCORE_LIBRARY_RELEASE) + GET_FILENAME_COMPONENT(qtcore_lib_ext "${QT_QTCORE_LIBRARY_RELEASE}" EXT) + IF("${qtcore_lib_ext}" STREQUAL "${CMAKE_STATIC_LIBRARY_SUFFIX}") + SET(QT_IS_STATIC 1) + ENDIF("${qtcore_lib_ext}" STREQUAL "${CMAKE_STATIC_LIBRARY_SUFFIX}") + ENDIF(QT_QTCORE_LIBRARY_RELEASE) + IF(QT_QTCORE_LIBRARY_DEBUG) + GET_FILENAME_COMPONENT(qtcore_lib_ext "${QT_QTCORE_LIBRARY_DEBUG}" EXT) + IF(${qtcore_lib_ext} STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET(QT_IS_STATIC 1) + ENDIF(${qtcore_lib_ext} STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + ENDIF(QT_QTCORE_LIBRARY_DEBUG) +ENDIF(WIN32) + +# build using shared Qt needs -DQT_DLL on Windows +IF(WIN32 AND NOT QT_IS_STATIC) + SET(QT_DEFINITIONS ${QT_DEFINITIONS} -DQT_DLL) +ENDIF(WIN32 AND NOT QT_IS_STATIC) + + +# QtOpenGL dependencies +QT_QUERY_QMAKE(QMAKE_LIBS_OPENGL "QMAKE_LIBS_OPENGL") +IF(Q_WS_MAC) +# On the Mac OpenGL is probably frameworks and QMAKE_LIBS_OPENGL can be e.g. "-framework OpenGL -framework AGL". +# The separate_arguments() call in the other branch makes "-framework;-OpenGL;-framework;-lAGL" appear in the +# linker command. So we need to protect the "-framework foo" as non-separatable strings. +# We do this by replacing the space after "-framework" with an underscore, then calling separate_arguments(), +# and then we replace the underscores again with spaces. So we get proper linker commands. Alex + STRING(REGEX REPLACE "-framework +" "-framework_" QMAKE_LIBS_OPENGL "${QMAKE_LIBS_OPENGL}") + SEPARATE_ARGUMENTS(QMAKE_LIBS_OPENGL) + STRING(REGEX REPLACE "-framework_" "-framework " QMAKE_LIBS_OPENGL "${QMAKE_LIBS_OPENGL}") +ELSE(Q_WS_MAC) + SEPARATE_ARGUMENTS(QMAKE_LIBS_OPENGL) +ENDIF(Q_WS_MAC) +SET (QT_QTOPENGL_LIB_DEPENDENCIES ${QT_QTOPENGL_LIB_DEPENDENCIES} ${QMAKE_LIBS_OPENGL}) + + +## system png +IF(QT_QCONFIG MATCHES "system-png") + FIND_LIBRARY(QT_PNG_LIBRARY NAMES png) + MARK_AS_ADVANCED(QT_PNG_LIBRARY) + IF(QT_PNG_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_PNG_LIBRARY}) + ENDIF(QT_PNG_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-png") + + +# for X11, get X11 library directory +IF(Q_WS_X11) + QT_QUERY_QMAKE(QMAKE_LIBDIR_X11 "QMAKE_LIBDIR_X11") +ENDIF(Q_WS_X11) + + +## X11 SM +IF(QT_QCONFIG MATCHES "x11sm") + # ask qmake where the x11 libs are + FIND_LIBRARY(QT_X11_SM_LIBRARY NAMES SM PATHS ${QMAKE_LIBDIR_X11}) + FIND_LIBRARY(QT_X11_ICE_LIBRARY NAMES ICE PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_X11_SM_LIBRARY) + MARK_AS_ADVANCED(QT_X11_ICE_LIBRARY) + IF(QT_X11_SM_LIBRARY AND QT_X11_ICE_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_X11_SM_LIBRARY} ${QT_X11_ICE_LIBRARY}) + ENDIF(QT_X11_SM_LIBRARY AND QT_X11_ICE_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "x11sm") + + +## Xi +IF(QT_QCONFIG MATCHES "tablet") + FIND_LIBRARY(QT_XI_LIBRARY NAMES Xi PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XI_LIBRARY) + IF(QT_XI_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XI_LIBRARY}) + ENDIF(QT_XI_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "tablet") + + +## Xrender +IF(QT_QCONFIG MATCHES "xrender") + FIND_LIBRARY(QT_XRENDER_LIBRARY NAMES Xrender PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XRENDER_LIBRARY) + IF(QT_XRENDER_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XRENDER_LIBRARY}) + ENDIF(QT_XRENDER_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xrender") + + +## Xrandr +IF(QT_QCONFIG MATCHES "xrandr") + FIND_LIBRARY(QT_XRANDR_LIBRARY NAMES Xrandr PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XRANDR_LIBRARY) + IF(QT_XRANDR_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XRANDR_LIBRARY}) + ENDIF(QT_XRANDR_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xrandr") + + +## Xcursor +IF(QT_QCONFIG MATCHES "xcursor") + FIND_LIBRARY(QT_XCURSOR_LIBRARY NAMES Xcursor PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XCURSOR_LIBRARY) + IF(QT_XCURSOR_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XCURSOR_LIBRARY}) + ENDIF(QT_XCURSOR_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xcursor") + + +## Xinerama +IF(QT_QCONFIG MATCHES "xinerama") + FIND_LIBRARY(QT_XINERAMA_LIBRARY NAMES Xinerama PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XINERAMA_LIBRARY) + IF(QT_XINERAMA_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XINERAMA_LIBRARY}) + ENDIF(QT_XINERAMA_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xinerama") + + +## Xfixes +IF(QT_QCONFIG MATCHES "xfixes") + FIND_LIBRARY(QT_XFIXES_LIBRARY NAMES Xfixes PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XFIXES_LIBRARY) + IF(QT_XFIXES_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XFIXES_LIBRARY}) + ENDIF(QT_XFIXES_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xfixes") + + +## system-freetype +IF(QT_QCONFIG MATCHES "system-freetype") + FIND_LIBRARY(QT_FREETYPE_LIBRARY NAMES freetype) + MARK_AS_ADVANCED(QT_FREETYPE_LIBRARY) + IF(QT_FREETYPE_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_FREETYPE_LIBRARY}) + ENDIF(QT_FREETYPE_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-freetype") + + +## fontconfig +IF(QT_QCONFIG MATCHES "fontconfig") + FIND_LIBRARY(QT_FONTCONFIG_LIBRARY NAMES fontconfig) + MARK_AS_ADVANCED(QT_FONTCONFIG_LIBRARY) + IF(QT_FONTCONFIG_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_FONTCONFIG_LIBRARY}) + ENDIF(QT_FONTCONFIG_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "fontconfig") + + +## system-zlib +IF(QT_QCONFIG MATCHES "system-zlib") + FIND_LIBRARY(QT_ZLIB_LIBRARY NAMES z) + MARK_AS_ADVANCED(QT_ZLIB_LIBRARY) + IF(QT_ZLIB_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_ZLIB_LIBRARY}) + ENDIF(QT_ZLIB_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-zlib") + + +## openssl +IF(NOT Q_WS_WIN) + SET(_QT_NEED_OPENSSL 0) + IF(QT_VERSION_MINOR LESS 4 AND QT_QCONFIG MATCHES "openssl") + SET(_QT_NEED_OPENSSL 1) + ENDIF(QT_VERSION_MINOR LESS 4 AND QT_QCONFIG MATCHES "openssl") + IF(QT_VERSION_MINOR GREATER 3 AND QT_QCONFIG MATCHES "openssl-linked") + SET(_QT_NEED_OPENSSL 1) + ENDIF(QT_VERSION_MINOR GREATER 3 AND QT_QCONFIG MATCHES "openssl-linked") + IF(_QT_NEED_OPENSSL) + FIND_PACKAGE(OpenSSL) + IF(OPENSSL_LIBRARIES) + SET(QT_QTNETWORK_LIB_DEPENDENCIES ${QT_QTNETWORK_LIB_DEPENDENCIES} ${OPENSSL_LIBRARIES}) + ENDIF(OPENSSL_LIBRARIES) + ENDIF(_QT_NEED_OPENSSL) +ENDIF(NOT Q_WS_WIN) + + +## dbus +IF(QT_QCONFIG MATCHES "dbus") + + # if the dbus library isn't found, we'll assume its not required to build + # shared Qt on Linux doesn't require it + IF(NOT QT_DBUS_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L dbus-1 + OUTPUT_VARIABLE _dbus_query_output + RESULT_VARIABLE _dbus_result + ERROR_VARIABLE _dbus_query_output ) + + IF(_dbus_result MATCHES 0) + STRING(REPLACE "-L" "" _dbus_query_output "${_dbus_query_output}") + SEPARATE_ARGUMENTS(_dbus_query_output) + ELSE(_dbus_result MATCHES 0) + SET(_dbus_query_output) + ENDIF(_dbus_result MATCHES 0) + + FIND_LIBRARY(QT_DBUS_LIBRARY NAMES dbus-1 PATHS ${_dbus_query_output} ) + + IF(QT_DBUS_LIBRARY) + SET(QT_QTDBUS_LIB_DEPENDENCIES ${QT_QTDBUS_LIB_DEPENDENCIES} ${QT_DBUS_LIBRARY}) + ENDIF(QT_DBUS_LIBRARY) + + MARK_AS_ADVANCED(QT_DBUS_LIBRARY) + ENDIF(NOT QT_DBUS_LIBRARY) + +ENDIF(QT_QCONFIG MATCHES "dbus") + + +## glib +IF(QT_QCONFIG MATCHES "glib") + + # if the glib libraries aren't found, we'll assume its not required to build + # shared Qt on Linux doesn't require it + + # Qt 4.2.0+ uses glib-2.0 + IF(NOT QT_GLIB_LIBRARY OR NOT QT_GTHREAD_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L glib-2.0 gthread-2.0 + OUTPUT_VARIABLE _glib_query_output + RESULT_VARIABLE _glib_result + ERROR_VARIABLE _glib_query_output ) + + IF(_glib_result MATCHES 0) + STRING(REPLACE "-L" "" _glib_query_output "${_glib_query_output}") + SEPARATE_ARGUMENTS(_glib_query_output) + ELSE(_glib_result MATCHES 0) + SET(_glib_query_output) + ENDIF(_glib_result MATCHES 0) + + FIND_LIBRARY(QT_GLIB_LIBRARY NAMES glib-2.0 PATHS ${_glib_query_output} ) + FIND_LIBRARY(QT_GTHREAD_LIBRARY NAMES gthread-2.0 PATHS ${_glib_query_output} ) + + MARK_AS_ADVANCED(QT_GLIB_LIBRARY) + MARK_AS_ADVANCED(QT_GTHREAD_LIBRARY) + ENDIF(NOT QT_GLIB_LIBRARY OR NOT QT_GTHREAD_LIBRARY) + + IF(QT_GLIB_LIBRARY AND QT_GTHREAD_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} + ${QT_GTHREAD_LIBRARY} ${QT_GLIB_LIBRARY}) + ENDIF(QT_GLIB_LIBRARY AND QT_GTHREAD_LIBRARY) + + + # Qt 4.5+ also links to gobject-2.0 + IF(QT_VERSION_MINOR GREATER 4) + IF(NOT QT_GOBJECT_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L gobject-2.0 + OUTPUT_VARIABLE _glib_query_output + RESULT_VARIABLE _glib_result + ERROR_VARIABLE _glib_query_output ) + + IF(_glib_result MATCHES 0) + STRING(REPLACE "-L" "" _glib_query_output "${_glib_query_output}") + SEPARATE_ARGUMENTS(_glib_query_output) + ELSE(_glib_result MATCHES 0) + SET(_glib_query_output) + ENDIF(_glib_result MATCHES 0) + + FIND_LIBRARY(QT_GOBJECT_LIBRARY NAMES gobject-2.0 PATHS ${_glib_query_output} ) + + MARK_AS_ADVANCED(QT_GOBJECT_LIBRARY) + ENDIF(NOT QT_GOBJECT_LIBRARY) + + IF(QT_GOBJECT_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} + ${QT_GOBJECT_LIBRARY}) + ENDIF(QT_GOBJECT_LIBRARY) + ENDIF(QT_VERSION_MINOR GREATER 4) + +ENDIF(QT_QCONFIG MATCHES "glib") + + +## clock-monotonic, just see if we need to link with rt +IF(QT_QCONFIG MATCHES "clock-monotonic") + SET(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) + SET(CMAKE_REQUIRED_LIBRARIES rt) + CHECK_SYMBOL_EXISTS(_POSIX_TIMERS "unistd.h;time.h" QT_POSIX_TIMERS) + SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_SAVE}) + IF(QT_POSIX_TIMERS) + FIND_LIBRARY(QT_RT_LIBRARY NAMES rt) + MARK_AS_ADVANCED(QT_RT_LIBRARY) + IF(QT_RT_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_RT_LIBRARY}) + ENDIF(QT_RT_LIBRARY) + ENDIF(QT_POSIX_TIMERS) +ENDIF(QT_QCONFIG MATCHES "clock-monotonic") + + +IF(Q_WS_X11) + # X11 libraries Qt absolutely depends on + QT_QUERY_QMAKE(QT_LIBS_X11 "QMAKE_LIBS_X11") + SEPARATE_ARGUMENTS(QT_LIBS_X11) + FOREACH(QT_X11_LIB ${QT_LIBS_X11}) + STRING(REGEX REPLACE "-l" "" QT_X11_LIB "${QT_X11_LIB}") + SET(QT_TMP_STR "QT_X11_${QT_X11_LIB}_LIBRARY") + FIND_LIBRARY(${QT_TMP_STR} NAMES "${QT_X11_LIB}" PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(${QT_TMP_STR}) + IF(${QT_TMP_STR}) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${${QT_TMP_STR}}) + ENDIF(${QT_TMP_STR}) + ENDFOREACH(QT_X11_LIB) + + QT_QUERY_QMAKE(QT_LIBS_THREAD "QMAKE_LIBS_THREAD") + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_LIBS_THREAD}) + + QT_QUERY_QMAKE(QMAKE_LIBS_DYNLOAD "QMAKE_LIBS_DYNLOAD") + SET (QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QMAKE_LIBS_DYNLOAD}) + +ENDIF(Q_WS_X11) + + +IF(Q_WS_WIN) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ws2_32) +ENDIF(Q_WS_WIN) + + +IF(Q_WS_MAC) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework Carbon") + + # Qt 4.0, 4.1, 4.2 use QuickTime + IF(QT_VERSION_MINOR LESS 3) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework QuickTime") + ENDIF(QT_VERSION_MINOR LESS 3) + + # Qt 4.2+ use AppKit + IF(QT_VERSION_MINOR GREATER 1) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework AppKit") + ENDIF(QT_VERSION_MINOR GREATER 1) + + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} "-framework ApplicationServices") +ENDIF(Q_WS_MAC) + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4Macros.cmake b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4Macros.cmake new file mode 100644 index 00000000..a5142511 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/cmake/Qt4Macros.cmake @@ -0,0 +1,414 @@ +# This file is included by FindQt4.cmake, don't include it directly. + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + + +###################################### +# +# Macros for building Qt files +# +###################################### + + +MACRO (QT4_EXTRACT_OPTIONS _qt4_files _qt4_options) + SET(${_qt4_files}) + SET(${_qt4_options}) + SET(_QT4_DOING_OPTIONS FALSE) + FOREACH(_currentArg ${ARGN}) + IF ("${_currentArg}" STREQUAL "OPTIONS") + SET(_QT4_DOING_OPTIONS TRUE) + ELSE ("${_currentArg}" STREQUAL "OPTIONS") + IF(_QT4_DOING_OPTIONS) + LIST(APPEND ${_qt4_options} "${_currentArg}") + ELSE(_QT4_DOING_OPTIONS) + LIST(APPEND ${_qt4_files} "${_currentArg}") + ENDIF(_QT4_DOING_OPTIONS) + ENDIF ("${_currentArg}" STREQUAL "OPTIONS") + ENDFOREACH(_currentArg) +ENDMACRO (QT4_EXTRACT_OPTIONS) + + +# macro used to create the names of output files preserving relative dirs +MACRO (QT4_MAKE_OUTPUT_FILE infile prefix ext outfile ) + STRING(LENGTH ${CMAKE_CURRENT_BINARY_DIR} _binlength) + STRING(LENGTH ${infile} _infileLength) + SET(_checkinfile ${CMAKE_CURRENT_SOURCE_DIR}) + IF(_infileLength GREATER _binlength) + STRING(SUBSTRING "${infile}" 0 ${_binlength} _checkinfile) + IF(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${infile}) + ELSE(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${infile}) + ENDIF(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + ELSE(_infileLength GREATER _binlength) + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${infile}) + ENDIF(_infileLength GREATER _binlength) + IF(WIN32 AND rel MATCHES "^[a-zA-Z]:") # absolute path + STRING(REGEX REPLACE "^([a-zA-Z]):(.*)$" "\\1_\\2" rel "${rel}") + ENDIF(WIN32 AND rel MATCHES "^[a-zA-Z]:") + SET(_outfile "${CMAKE_CURRENT_BINARY_DIR}/${rel}") + STRING(REPLACE ".." "__" _outfile ${_outfile}) + GET_FILENAME_COMPONENT(outpath ${_outfile} PATH) + GET_FILENAME_COMPONENT(_outfile ${_outfile} NAME_WE) + FILE(MAKE_DIRECTORY ${outpath}) + SET(${outfile} ${outpath}/${prefix}${_outfile}.${ext}) +ENDMACRO (QT4_MAKE_OUTPUT_FILE ) + + +MACRO (QT4_GET_MOC_FLAGS _moc_flags) + SET(${_moc_flags}) + GET_DIRECTORY_PROPERTY(_inc_DIRS INCLUDE_DIRECTORIES) + + FOREACH(_current ${_inc_DIRS}) + IF("${_current}" MATCHES ".framework/?$") + STRING(REGEX REPLACE "/[^/]+.framework" "" framework_path "${_current}") + SET(${_moc_flags} ${${_moc_flags}} "-F${framework_path}") + ELSE("${_current}" MATCHES ".framework/?$") + SET(${_moc_flags} ${${_moc_flags}} "-I${_current}") + ENDIF("${_current}" MATCHES ".framework/?$") + ENDFOREACH(_current ${_inc_DIRS}) + + GET_DIRECTORY_PROPERTY(_defines COMPILE_DEFINITIONS) + FOREACH(_current ${_defines}) + SET(${_moc_flags} ${${_moc_flags}} "-D${_current}") + ENDFOREACH(_current ${_defines}) + + IF(Q_WS_WIN) + SET(${_moc_flags} ${${_moc_flags}} -DWIN32) + ENDIF(Q_WS_WIN) + +ENDMACRO(QT4_GET_MOC_FLAGS) + + +# helper macro to set up a moc rule +MACRO (QT4_CREATE_MOC_COMMAND infile outfile moc_flags moc_options) + # For Windows, create a parameters file to work around command line length limit + IF (WIN32) + # Pass the parameters in a file. Set the working directory to + # be that containing the parameters file and reference it by + # just the file name. This is necessary because the moc tool on + # MinGW builds does not seem to handle spaces in the path to the + # file given with the @ syntax. + GET_FILENAME_COMPONENT(_moc_outfile_name "${outfile}" NAME) + GET_FILENAME_COMPONENT(_moc_outfile_dir "${outfile}" PATH) + IF(_moc_outfile_dir) + SET(_moc_working_dir WORKING_DIRECTORY ${_moc_outfile_dir}) + ENDIF(_moc_outfile_dir) + SET (_moc_parameters_file ${outfile}_parameters) + SET (_moc_parameters ${moc_flags} ${moc_options} -o "${outfile}" "${infile}") + FILE (REMOVE ${_moc_parameters_file}) + FOREACH(arg ${_moc_parameters}) + FILE (APPEND ${_moc_parameters_file} "${arg}\n") + ENDFOREACH(arg) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_MOC_EXECUTABLE} @${_moc_outfile_name}_parameters + DEPENDS ${infile} + ${_moc_working_dir} + VERBATIM) + ELSE (WIN32) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_MOC_EXECUTABLE} + ARGS ${moc_flags} ${moc_options} -o ${outfile} ${infile} + DEPENDS ${infile}) + ENDIF (WIN32) +ENDMACRO (QT4_CREATE_MOC_COMMAND) + + +MACRO (QT4_GENERATE_MOC infile outfile ) +# get include dirs and flags + QT4_GET_MOC_FLAGS(moc_flags) + GET_FILENAME_COMPONENT(abs_infile ${infile} ABSOLUTE) + QT4_CREATE_MOC_COMMAND(${abs_infile} ${outfile} "${moc_flags}" "") + SET_SOURCE_FILES_PROPERTIES(${outfile} PROPERTIES SKIP_AUTOMOC TRUE) # do not run automoc on this file + + MACRO_ADD_FILE_DEPENDENCIES(${abs_infile} ${outfile}) +ENDMACRO (QT4_GENERATE_MOC) + + +# QT4_WRAP_CPP(outfiles inputfile ... ) + +MACRO (QT4_WRAP_CPP outfiles ) + # get include dirs + QT4_GET_MOC_FLAGS(moc_flags) + QT4_EXTRACT_OPTIONS(moc_files moc_options ${ARGN}) + + FOREACH (it ${moc_files}) + GET_FILENAME_COMPONENT(it ${it} ABSOLUTE) + QT4_MAKE_OUTPUT_FILE(${it} moc_ cxx outfile) + QT4_CREATE_MOC_COMMAND(${it} ${outfile} "${moc_flags}" "${moc_options}") + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH(it) + +ENDMACRO (QT4_WRAP_CPP) + + +# QT4_WRAP_UI(outfiles inputfile ... ) + +MACRO (QT4_WRAP_UI outfiles ) + QT4_EXTRACT_OPTIONS(ui_files ui_options ${ARGN}) + + FOREACH (it ${ui_files}) + GET_FILENAME_COMPONENT(outfile ${it} NAME_WE) + GET_FILENAME_COMPONENT(infile ${it} ABSOLUTE) + SET(outfile ${CMAKE_CURRENT_BINARY_DIR}/ui_${outfile}.h) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_UIC_EXECUTABLE} + ARGS ${ui_options} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile}) + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH (it) + +ENDMACRO (QT4_WRAP_UI) + + +# QT4_ADD_RESOURCES(outfiles inputfile ... ) + +MACRO (QT4_ADD_RESOURCES outfiles ) + QT4_EXTRACT_OPTIONS(rcc_files rcc_options ${ARGN}) + + FOREACH (it ${rcc_files}) + GET_FILENAME_COMPONENT(outfilename ${it} NAME_WE) + GET_FILENAME_COMPONENT(infile ${it} ABSOLUTE) + GET_FILENAME_COMPONENT(rc_path ${infile} PATH) + SET(outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${outfilename}.cxx) + # parse file for dependencies + # all files are absolute paths or relative to the location of the qrc file + FILE(READ "${infile}" _RC_FILE_CONTENTS) + STRING(REGEX MATCHALL "]*>" "" _RC_FILE "${_RC_FILE}") + STRING(REGEX MATCH "^/|([A-Za-z]:/)" _ABS_PATH_INDICATOR "${_RC_FILE}") + IF(NOT _ABS_PATH_INDICATOR) + SET(_RC_FILE "${rc_path}/${_RC_FILE}") + ENDIF(NOT _ABS_PATH_INDICATOR) + SET(_RC_DEPENDS ${_RC_DEPENDS} "${_RC_FILE}") + ENDFOREACH(_RC_FILE) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_RCC_EXECUTABLE} + ARGS ${rcc_options} -name ${outfilename} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile} + DEPENDS ${_RC_DEPENDS}) + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH (it) + +ENDMACRO (QT4_ADD_RESOURCES) + + +MACRO(QT4_ADD_DBUS_INTERFACE _sources _interface _basename) + GET_FILENAME_COMPONENT(_infile ${_interface} ABSOLUTE) + SET(_header ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.h) + SET(_impl ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.cpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.moc) + + GET_SOURCE_FILE_PROPERTY(_nonamespace ${_interface} NO_NAMESPACE) + IF ( _nonamespace ) + SET(_params -N -m) + ELSE ( _nonamespace ) + SET(_params -m) + ENDIF ( _nonamespace ) + + GET_SOURCE_FILE_PROPERTY(_classname ${_interface} CLASSNAME) + IF ( _classname ) + SET(_params ${_params} -c ${_classname}) + ENDIF ( _classname ) + + GET_SOURCE_FILE_PROPERTY(_include ${_interface} INCLUDE) + IF ( _include ) + SET(_params ${_params} -i ${_include}) + ENDIF ( _include ) + + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} ${_params} -p ${_basename} ${_infile} + DEPENDS ${_infile}) + + SET_SOURCE_FILES_PROPERTIES(${_impl} PROPERTIES SKIP_AUTOMOC TRUE) + + QT4_GENERATE_MOC(${_header} ${_moc}) + + SET(${_sources} ${${_sources}} ${_impl} ${_header} ${_moc}) + MACRO_ADD_FILE_DEPENDENCIES(${_impl} ${_moc}) + +ENDMACRO(QT4_ADD_DBUS_INTERFACE) + + +MACRO(QT4_ADD_DBUS_INTERFACES _sources) + FOREACH (_current_FILE ${ARGN}) + GET_FILENAME_COMPONENT(_infile ${_current_FILE} ABSOLUTE) + # get the part before the ".xml" suffix + STRING(REGEX REPLACE "(.*[/\\.])?([^\\.]+)\\.xml" "\\2" _basename ${_current_FILE}) + STRING(TOLOWER ${_basename} _basename) + QT4_ADD_DBUS_INTERFACE(${_sources} ${_infile} ${_basename}interface) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_ADD_DBUS_INTERFACES) + + +MACRO(QT4_GENERATE_DBUS_INTERFACE _header) # _customName OPTIONS -some -options ) + QT4_EXTRACT_OPTIONS(_customName _qt4_dbus_options ${ARGN}) + + GET_FILENAME_COMPONENT(_in_file ${_header} ABSOLUTE) + GET_FILENAME_COMPONENT(_basename ${_header} NAME_WE) + + IF (_customName) + SET(_target ${CMAKE_CURRENT_BINARY_DIR}/${_customName}) + ELSE (_customName) + SET(_target ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.xml) + ENDIF (_customName) + + ADD_CUSTOM_COMMAND(OUTPUT ${_target} + COMMAND ${QT_DBUSCPP2XML_EXECUTABLE} ${_qt4_dbus_options} ${_in_file} -o ${_target} + DEPENDS ${_in_file} + ) +ENDMACRO(QT4_GENERATE_DBUS_INTERFACE) + + +MACRO(QT4_ADD_DBUS_ADAPTOR _sources _xml_file _include _parentClass) # _optionalBasename _optionalClassName) + GET_FILENAME_COMPONENT(_infile ${_xml_file} ABSOLUTE) + + SET(_optionalBasename "${ARGV4}") + IF (_optionalBasename) + SET(_basename ${_optionalBasename} ) + ELSE (_optionalBasename) + STRING(REGEX REPLACE "(.*[/\\.])?([^\\.]+)\\.xml" "\\2adaptor" _basename ${_infile}) + STRING(TOLOWER ${_basename} _basename) + ENDIF (_optionalBasename) + + SET(_optionalClassName "${ARGV5}") + SET(_header ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.h) + SET(_impl ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.cpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.moc) + + IF(_optionalClassName) + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} -m -a ${_basename} -c ${_optionalClassName} -i ${_include} -l ${_parentClass} ${_infile} + DEPENDS ${_infile} + ) + ELSE(_optionalClassName) + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} -m -a ${_basename} -i ${_include} -l ${_parentClass} ${_infile} + DEPENDS ${_infile} + ) + ENDIF(_optionalClassName) + + QT4_GENERATE_MOC(${_header} ${_moc}) + SET_SOURCE_FILES_PROPERTIES(${_impl} PROPERTIES SKIP_AUTOMOC TRUE) + MACRO_ADD_FILE_DEPENDENCIES(${_impl} ${_moc}) + + SET(${_sources} ${${_sources}} ${_impl} ${_header} ${_moc}) +ENDMACRO(QT4_ADD_DBUS_ADAPTOR) + + +MACRO(QT4_AUTOMOC) + QT4_GET_MOC_FLAGS(_moc_INCS) + + SET(_matching_FILES ) + FOREACH (_current_FILE ${ARGN}) + + GET_FILENAME_COMPONENT(_abs_FILE ${_current_FILE} ABSOLUTE) + # if "SKIP_AUTOMOC" is set to true, we will not handle this file here. + # This is required to make uic work correctly: + # we need to add generated .cpp files to the sources (to compile them), + # but we cannot let automoc handle them, as the .cpp files don't exist yet when + # cmake is run for the very first time on them -> however the .cpp files might + # exist at a later run. at that time we need to skip them, so that we don't add two + # different rules for the same moc file + GET_SOURCE_FILE_PROPERTY(_skip ${_abs_FILE} SKIP_AUTOMOC) + + IF ( NOT _skip AND EXISTS ${_abs_FILE} ) + + FILE(READ ${_abs_FILE} _contents) + + GET_FILENAME_COMPONENT(_abs_PATH ${_abs_FILE} PATH) + + STRING(REGEX MATCHALL "# *include +[^ ]+\\.moc[\">]" _match "${_contents}") + IF(_match) + FOREACH (_current_MOC_INC ${_match}) + STRING(REGEX MATCH "[^ <\"]+\\.moc" _current_MOC "${_current_MOC_INC}") + + GET_FILENAME_COMPONENT(_basename ${_current_MOC} NAME_WE) + IF(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_header ${_abs_PATH}/${_basename}.hpp) + ELSE(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_header ${_abs_PATH}/${_basename}.h) + ENDIF(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_current_MOC}) + QT4_CREATE_MOC_COMMAND(${_header} ${_moc} "${_moc_INCS}" "") + MACRO_ADD_FILE_DEPENDENCIES(${_abs_FILE} ${_moc}) + ENDFOREACH (_current_MOC_INC) + ENDIF(_match) + ENDIF ( NOT _skip AND EXISTS ${_abs_FILE} ) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_AUTOMOC) + + +MACRO(QT4_CREATE_TRANSLATION _qm_files) + QT4_EXTRACT_OPTIONS(_lupdate_files _lupdate_options ${ARGN}) + SET(_my_sources) + SET(_my_dirs) + SET(_my_tsfiles) + SET(_ts_pro) + FOREACH (_file ${_lupdate_files}) + GET_FILENAME_COMPONENT(_ext ${_file} EXT) + GET_FILENAME_COMPONENT(_abs_FILE ${_file} ABSOLUTE) + IF(_ext MATCHES "ts") + LIST(APPEND _my_tsfiles ${_abs_FILE}) + ELSE(_ext MATCHES "ts") + IF(NOT _ext) + LIST(APPEND _my_dirs ${_abs_FILE}) + ELSE(NOT _ext) + LIST(APPEND _my_sources ${_abs_FILE}) + ENDIF(NOT _ext) + ENDIF(_ext MATCHES "ts") + ENDFOREACH(_file) + FOREACH(_ts_file ${_my_tsfiles}) + IF(_my_sources) + # make a .pro file to call lupdate on, so we don't make our commands too + # long for some systems + GET_FILENAME_COMPONENT(_ts_name ${_ts_file} NAME_WE) + SET(_ts_pro ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lupdate.pro) + SET(_pro_srcs) + FOREACH(_pro_src ${_my_sources}) + SET(_pro_srcs "${_pro_srcs} \"${_pro_src}\"") + ENDFOREACH(_pro_src ${_my_sources}) + FILE(WRITE ${_ts_pro} "SOURCES = ${_pro_srcs}") + ENDIF(_my_sources) + ADD_CUSTOM_COMMAND(OUTPUT ${_ts_file} + COMMAND ${QT_LUPDATE_EXECUTABLE} + ARGS ${_lupdate_options} ${_ts_pro} ${_my_dirs} -ts ${_ts_file} + DEPENDS ${_my_sources} ${_ts_pro}) + ENDFOREACH(_ts_file) + QT4_ADD_TRANSLATION(${_qm_files} ${_my_tsfiles}) +ENDMACRO(QT4_CREATE_TRANSLATION) + + +MACRO(QT4_ADD_TRANSLATION _qm_files) + FOREACH (_current_FILE ${ARGN}) + GET_FILENAME_COMPONENT(_abs_FILE ${_current_FILE} ABSOLUTE) + GET_FILENAME_COMPONENT(qm ${_abs_FILE} NAME_WE) + GET_SOURCE_FILE_PROPERTY(output_location ${_abs_FILE} OUTPUT_LOCATION) + IF(output_location) + FILE(MAKE_DIRECTORY "${output_location}") + SET(qm "${output_location}/${qm}.qm") + ELSE(output_location) + SET(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm") + ENDIF(output_location) + + ADD_CUSTOM_COMMAND(OUTPUT ${qm} + COMMAND ${QT_LRELEASE_EXECUTABLE} + ARGS ${_abs_FILE} -qm ${qm} + DEPENDS ${_abs_FILE} + ) + SET(${_qm_files} ${${_qm_files}} ${qm}) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_ADD_TRANSLATION) diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/main.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/main.cpp new file mode 100644 index 00000000..58f87e50 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/main.cpp @@ -0,0 +1,18 @@ + +#include + +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + MainWindow mw; + mw.resize(640, 480); + mw.show(); + + return app.exec(); + +} + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainview.qml b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainview.qml new file mode 100644 index 00000000..ba7a17d5 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainview.qml @@ -0,0 +1,51 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + color : "#f6f6f5" + anchors.fill : parent + property int itemHeight : 30 + + ListView { + height : itemHeight + anchors.centerIn : parent + model : myModel + width : 200 + clip : true + delegate : Component { + Text{ + height : itemHeight; + text : model.display + } + } + onCountChanged : { + console.log("%%" + count) + positionViewAtIndex(count - 1, ListView.Beginning) + } + Component.onCompleted : { + console.log("Completed"); + console.log("COUNT" + count) + positionViewAtIndex(count - 1, ListView.Beginning) + } + } +} diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp new file mode 100644 index 00000000..d469f9da --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.cpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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 "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f ) + : QWidget(parent, f) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + QSplitter *splitter = new QSplitter; + layout->addWidget(splitter); + + QTreeView *treeView = new QTreeView(splitter); + + m_model = new QStandardItemModel(this); + treeView->setModel(m_model); + + appendRow(); + appendRow(); + + m_declarativeView = new QDeclarativeView(splitter); + + QDeclarativeContext *context = m_declarativeView->engine()->rootContext(); + + context->setContextProperty( "myModel", QVariant::fromValue( static_cast( m_model ) ) ); + + m_declarativeView->setResizeMode( QDeclarativeView::SizeRootObjectToView ); + m_declarativeView->setSource( QUrl( "./mainview.qml" ) ); + + splitter->setSizes(QList() << 1 << 1); + +// QTimer::singleShot(4000, this, SLOT(removeBottomRow())); + QTimer::singleShot(8000, this, SLOT(prependNewRow())); + +} + +static int sCount = 1; + +void MainWindow::appendRow() +{ + QStandardItem *item = new QStandardItem(QString::fromLatin1("Item %1").arg(sCount++)); + m_model->appendRow(item); +} + +void MainWindow::prependNewRow() +{ + QStandardItem *item = new QStandardItem(QString::fromLatin1("Item %1").arg(sCount++)); + m_model->insertRow(0, item); +} + +void MainWindow::removeTopRow() +{ + m_model->removeRow(0); +} + +void MainWindow::removeBottomRow() +{ + m_model->removeRow(1); +} + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.h b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.h new file mode 100644 index 00000000..2d440ff6 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/simple/mainwindow.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include + +class QDeclarativeView; +class QTreeView; +class QStandardItemModel; + +class KBreadcrumbNavigationFactory; + +class MainWindow : public QWidget +{ + Q_OBJECT +public: + MainWindow(QWidget* parent = 0, Qt::WindowFlags f = 0); + +public slots: + void appendRow(); + void removeTopRow(); + void removeBottomRow(); + void prependNewRow(); + +private: + QTreeView *m_treeView; + QDeclarativeView *m_declarativeView; + QStandardItemModel *m_model; +}; + +#endif + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/slideout-panel-background.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/slideout-panel-background.png new file mode 100644 index 0000000000000000000000000000000000000000..5e762dd38031c7a34154ade5e85a8c9e03e021c5 GIT binary patch literal 27307 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 ND~mJc^`>On{{n(;%E15t literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/slideout-panel-handle.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/slideout-panel-handle.png new file mode 100644 index 0000000000000000000000000000000000000000..e90f939d54dea46ce50201d165b3d7a1c43c0f74 GIT binary patch literal 1442 zcmYLJdo2W>tNM5BY zkBc?QtL9Ob5;Lz8ClMB7TyO5`+-~14S*pa}_YG3kn)kycqDrXknNUK|g;1L3 zWnU1DMnhc;44`^@2K%CdE>rH$n5%+7+tlrCtcc+Q!a?2%H_Gl`L`z|SxVIa8)b5@J zk=a|;Ul*bhoM$GIuA4+6f|{-)(szoa>t*wwquk(y*V7<(eC&E6+b|PjmGwsxf_F1C zI#sS=!$sL>G-hA)p3TmiCPhaw$<;kL?DXxo1&mdE-1}3}`lKz)gj=4@*a1PG(#^MN zPa(y9dL&tT>B|c8N1UE({tP#}HTzZM%@xrKrWkDA;%*o?yHnT?E+Am9>Z?24{@7NZ zOg}e{*ebWs!61HP& zV^n98zvAT)Ag)QSyA~Q6y1ai~=?aauR9joSiNRpLXxO;xsmH1!Y^X=%-pXEXG6i zhD`VkH#_p)n(Ha`2FTDhsC* zW}4H%(S>Th6<^1<@hfQ$q|oUr=`0qj z@vUhsi}!3nSY1&eBB*(2HP5{AnPtB7Cy|Oc98N^h`DnC)@GpsgMx(K5&PxI93>I!0 zhr?~?iAEirtlEkfFQZadsTF_IYJ`Z#x}k+*}gCm;|&jUFM%-tpv@6+z37 zkph8+V8mUqqSC6uO1Zfe(hMPi0YCw`t9`fW=S2L^5KB#39}ql3K48YMU~#Zg=+C%# zZR@J=)CR)Lc8&Lbg;&Clzck6 zV~1sBGhFJK0K|o4AVIqj4+LZ;$PI|`lGz$9@5w}tc*(#KBW2JgE=1F)2`=@`0V06E z%Pnxlm}HnU5Thj>Tq(tb760$r!(A|TYT>pLlqg3l=ZNNhNL(j8R?Or+1lWTwED~Hk z-{J^@PuIAn_WCcntIH5(ZfHqQkhGQ09{!EJ8qNpzKjFb+KX7xqCLE&7FQT$;C&MO| z!X6X1AG+5;Q8e-2ETl46FiOqfC6$!6+4MJ`-HQeCSs7Z=hYVF={eTgBpgv1zX``V_2`%^6zN zm9JRJP_8)(Lrpu@m-*9+9w$pa6!y@v^0L;RMCm#zj^Xp2mzU-a;5cq_#|z#kHj_mt z=EK5w0X%u02eq5guY!Cnj>@~oax+nrJ03My>15IHC1R?4^~5Y;9zZSdg;VjZUBI;F zX|M1a*RMq&ljX1GU1F~n?W2Kgv-|XrZZ+PJAVBWm=3-y#8T*71`d&+m*S}{nvXVRc z$Nr?t0pfkr$HcZVYVbk)%#m1oznz(99CAE_6VhW31Zi(BrK~h&;?E^-IL2hB4$cE! oI@K=(+ob0A)^3^Uia%{^^F8QqP+&MNlKfMUy{(hYQ)_bEzlqkp$N&HU literal 0 HcmV?d00001 diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/transparentplus.png b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/transparentplus.png new file mode 100644 index 0000000000000000000000000000000000000000..72f25e2624a8b13ed174d01c23ee65c7d261c121 GIT binary patch literal 852 zcmV-a1FQUrP)MzCV_|S* zE^l&Yo9;Xs0008hNklrPK;qFaT2R}H#u}40Nz-)4W!Pp*qk_+{13U9R zyf5?aj!<-RRJuts&K8ziRk`@YrGpOb=&&`~wL2U!5GdAY)>qy(UO)Np4{gE{C*udB zScLI3Tog?j)Tq2tllL2c)Q%51XKlx;&65tySqc=W=i63Urpm8!`Od?S9oqQe^vR@f zh!e%-LNO-M+}tOrd0Lr8^?h>s85$GKiCk_XEmAnxo>bz-(<=*TR`#W_i4h?zjENH; zAAALn0Aoha`5q%j4jc4AG$cMPKzLd&IlP$jV@5mMa3Rx|A*+u8Ee5!tMS~s$ZeOdw zBam>)P8T*x0zrUiuR_)v>B1>nYpHxnCXwwdD##YR`E#1I%;dp@WIlmK?}BKTXq%s0 ze0G+c)%A5leaF7fj+9M5)!6jm73t61nsa*t)*-H7s(o!kZ69xKpndzT>+p3^iHpreDR!6hH?R+BF(H6 zB%4~x{Cb~>VjXHA{07&kyujD$qcPoVkXUTN@Z!-8m^X%^Y$Xd?T%*K_i_dnln10s` zBA8yh)|}$N;7otamjo@E{80GVIE1vO^!|=7P7x$9KURv=B}RKlBQzmm_mQi2Tc^JTm9Jq_W{RrMM^G)p#?AK$oaSFYjFw zrs`_;`4uR&e`aWj;^w;~#Q4hKkG5EY~nWzFYDO%2dkz40000 + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/tree_example b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/tree_example new file mode 100644 index 00000000..1964f8d9 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qmlbreadcrumbnavigation/tree_example @@ -0,0 +1,20 @@ +- 1 +- - 1 +- - 1 +- 1 +- - 1 +- - 1 +- - - 1 +- - - - 1 +- - - 1 +- - - - 1 +- - - - 1 +- - - - - 1 +- - - - - 1 +- - - - - 1 +- - - - 1 +- - - 1 +- - - 1 +- - - - 1 +- - - - 1 + diff --git a/kdepim-runtime/qml/kde/tests/qobject_sender.qml b/kdepim-runtime/qml/kde/tests/qobject_sender.qml new file mode 100644 index 00000000..539e8b82 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/qobject_sender.qml @@ -0,0 +1,50 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 500 + height : 500 + + function resetOthersColors(clickedObject) + { + console.log("CLICKED OBJECT " + clickedObject) + for (var i = 0; i < children.length; ++i) + { + console.log("CHILD OBJECT " + children[i]) + if (children[i] == clickedObject) + continue; + children[i].color = "blue" + } + } + + MyItem { x: 100; y : 20 } + MyItem { x: 100; y : 80 } + MyItem { x: 100; y : 140 } + + Component.onCompleted : { + for (var i = 0; i < children.length; ++i) + { + children[i].triggered.connect(this, resetOthersColors) + } + } +} \ No newline at end of file diff --git a/kdepim-runtime/qml/kde/tests/slideoutpaneltest.qml b/kdepim-runtime/qml/kde/tests/slideoutpaneltest.qml new file mode 100644 index 00000000..13397c9b --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/slideoutpaneltest.qml @@ -0,0 +1,89 @@ +/* + Copyright (c) 2010 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. +*/ + +import Qt 4.7 +import org.kde 4.5 + +Rectangle { + width: 800 + height: 480 + + color : "#00000000" + + SlideoutPanelContainer { + id: panelContainer + anchors.fill: parent + + SlideoutPanel { + id: folderPanel + titleText: KDE.i18n( "Folders" ) + handleHeight: 150 + content: [ + Rectangle { + color: "blue" + anchors.fill: parent + + Rectangle { + color : "yellow" + x : 300 + y : 100 + width : 100 + height : 100 + MouseArea { + anchors.fill : parent + onClicked : { + console.log("Clicked!"); + } + } + } + } + ] + } + + SlideoutPanel { + id: actionPanel + titleText: KDE.i18n( "Actions" ) + titleIcon: KDE.iconPath( "akonadi", 48 ); + handleHeight: 150 + collapsedPosition : 150 + expandedPosition : 25 + contentWidth: 200 + content: [ + Rectangle { + color: "red" + anchors.fill: parent + } + ] + } + + SlideoutPanel { + id: attachmentPanel + titleIcon: KDE.iconPath( "mail-attachment", 48 ); + collapsedPosition : 300 + expandedPosition : 50 + contentWidth: 100 + content: [ + Rectangle { + color: "green" + anchors.fill: parent + } + ] + } + } +} diff --git a/kdepim-runtime/qml/kde/tests/synced_sliders.qml b/kdepim-runtime/qml/kde/tests/synced_sliders.qml new file mode 100644 index 00000000..4e9b2faa --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/synced_sliders.qml @@ -0,0 +1,58 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 800 + height : 480 + + property real syncedExtension + + SliderComponent { + id : one + y : 30 + x : 50 + height : 100 + leftBound : 30 + rightBound : 130 + threshold : 0.2 + expander : true + + onExtensionChanged : { + two.changeExtension(extension) + } + } + SliderComponent { + id : two + y : 130 + x : 50 + + height : 100 + leftBound : 30 + rightBound : 430 + threshold : 0.7 + onExtensionChanged : { + one.changeExtension(extension) + } + } + +} diff --git a/kdepim-runtime/qml/kde/tests/transformorigin.qml b/kdepim-runtime/qml/kde/tests/transformorigin.qml new file mode 100644 index 00000000..1f200d09 --- /dev/null +++ b/kdepim-runtime/qml/kde/tests/transformorigin.qml @@ -0,0 +1,148 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company, info@kdab.net, + author 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. +*/ + +import Qt 4.7 + +Rectangle { + width : 800 + height : 480 + + Grid { + x : 30 + y : 30 + columns : 3 + spacing : 30 + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.TopLeft + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.Top + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.TopRight + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.Left + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.Center + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.Right + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.BottomLeft + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + + Text { + height : 20 + text : "Something" + rotation : 30 + transformOrigin : Item.Bottom + } + } + Rectangle { + height : children[0].height + width : children[0].width + border.color: "lightsteelblue" + border.width : 5 + + Text { + height : 20 + text : "Something" + rotation : -90 + transformOrigin : Item.BottomRight + } + } + } +} diff --git a/kdepim-runtime/qml/kde/transparentplus.svg b/kdepim-runtime/qml/kde/transparentplus.svg new file mode 100644 index 00000000..c121e62b --- /dev/null +++ b/kdepim-runtime/qml/kde/transparentplus.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/kdepim-runtime/resources/.krazy b/kdepim-runtime/resources/.krazy new file mode 100644 index 00000000..0b16e7f7 --- /dev/null +++ b/kdepim-runtime/resources/.krazy @@ -0,0 +1 @@ +SKIP /tests/ diff --git a/kdepim-runtime/resources/CMakeLists.txt b/kdepim-runtime/resources/CMakeLists.txt new file mode 100644 index 00000000..9cfc5b07 --- /dev/null +++ b/kdepim-runtime/resources/CMakeLists.txt @@ -0,0 +1,80 @@ + +#add_definitions( -DQT_NO_CAST_FROM_ASCII ) +#add_definitions( -DQT_NO_CAST_TO_ASCII ) + +include_directories( + ${kdepim-runtime_SOURCE_DIR}/libkdepim-copy + ${CMAKE_CURRENT_SOURCE_DIR}/shared + ${CMAKE_CURRENT_BINARY_DIR}/shared +) + +set( AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresourcebase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresourceconfigdialogbase.cpp +) + +if (KDEPIM_MOBILE_UI) +set( AKONADI_SINGLEFILERESOURCE_SHARED_UI + ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresourceconfigdialog_mobile.ui + ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresourceconfigdialog.ui +) +else () +set( AKONADI_SINGLEFILERESOURCE_SHARED_UI + ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresourceconfigdialog_desktop.ui + ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresourceconfigdialog.ui +) +endif () + +set( AKONADI_COLLECTIONATTRIBUTES_SHARED_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/shared/collectionannotationsattribute.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shared/collectionflagsattribute.cpp +) + +set( AKONADI_IMAPATTRIBUTES_SHARED_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/shared/imapaclattribute.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shared/imapquotaattribute.cpp +) + +add_subdirectory( akonotes ) +add_subdirectory( kalarm ) +add_subdirectory( contacts ) +macro_optional_add_subdirectory( dav ) +add_subdirectory( ical ) +add_subdirectory( imap ) +if (KDEPIMLIBS_KRESOURCES_LIBS) + add_subdirectory( kabc ) + add_subdirectory( kcal ) +endif() +macro_optional_add_subdirectory( kdeaccounts ) +if (Libkolab_FOUND AND Libkolabxml_FOUND) + macro_optional_add_subdirectory( kolabproxy ) + add_subdirectory( kolab ) +endif() + +macro_optional_add_subdirectory( localbookmarks ) + +add_subdirectory( maildir ) + +macro_optional_add_subdirectory( openxchange ) +add_subdirectory( pop3 ) + +if( LibKGAPI2_FOUND ) + add_subdirectory( google ) + # Disabled in KDE 4.14 - too many issues for stable release + #add_subdirectory( gmail ) +endif() + +if( LibKFbAPI_FOUND ) + add_subdirectory( facebook ) +endif() + +add_subdirectory( shared ) +add_subdirectory( birthdays ) +add_subdirectory( mixedmaildir ) +add_subdirectory( mailtransport_dummy ) +add_subdirectory( mbox ) +add_subdirectory( nntp ) +add_subdirectory( vcarddir ) +add_subdirectory( icaldir ) +add_subdirectory( vcard ) +add_subdirectory( folderarchivesettings ) diff --git a/kdepim-runtime/resources/Info.plist.template b/kdepim-runtime/resources/Info.plist.template new file mode 100644 index 00000000..c39ddb95 --- /dev/null +++ b/kdepim-runtime/resources/Info.plist.template @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + LSUIElement + 1 + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + diff --git a/kdepim-runtime/resources/Mainpage.dox b/kdepim-runtime/resources/Mainpage.dox new file mode 100644 index 00000000..d035670e --- /dev/null +++ b/kdepim-runtime/resources/Mainpage.dox @@ -0,0 +1,2 @@ +// DOXYGEN_NAME=Akonadi Resources +// DOXYGEN_ENABLE=YES diff --git a/kdepim-runtime/resources/akonotes/CMakeLists.txt b/kdepim-runtime/resources/akonotes/CMakeLists.txt new file mode 100644 index 00000000..2fdeafe8 --- /dev/null +++ b/kdepim-runtime/resources/akonotes/CMakeLists.txt @@ -0,0 +1,42 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../maildir + ${CMAKE_CURRENT_BINARY_DIR}/../maildir + ${CMAKE_CURRENT_SOURCE_DIR}/../maildir/libmaildir + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( akonotesresource_SRCS + ../maildir/maildirresource.cpp + ../maildir/configdialog.cpp + ../maildir/retrieveitemsjob.cpp + akonotesresource.cpp + + main.cpp +) + + +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/../maildir/maildirresource.kcfg org.kde.Akonadi.Maildir.Settings) + +kde4_add_ui_files(akonotesresource_SRCS ../maildir/settings.ui) + +kde4_add_kcfg_files(akonotesresource_SRCS ../maildir/settings.kcfgc) + +qt4_add_dbus_adaptor(akonotesresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Maildir.Settings.xml settings.h Akonadi_Maildir_Resource::MaildirSettings maildirsettingsadaptor MaildirSettingsAdaptor +) + +install( FILES akonotesresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_plugin(akonadi_akonotes_resource ${akonotesresource_SRCS}) + +target_link_libraries(akonadi_akonotes_resource ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} maildir ${QT_QTDBUS_LIBRARY} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS} folderarchivesettings) + +install(TARGETS akonadi_akonotes_resource DESTINATION ${PLUGIN_INSTALL_DIR}) + diff --git a/kdepim-runtime/resources/akonotes/Messages.sh b/kdepim-runtime/resources/akonotes/Messages.sh new file mode 100644 index 00000000..1d9220f8 --- /dev/null +++ b/kdepim-runtime/resources/akonotes/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/akonadi_akonotes_resource.pot diff --git a/kdepim-runtime/resources/akonotes/akonotesresource.cpp b/kdepim-runtime/resources/akonotes/akonotesresource.cpp new file mode 100644 index 00000000..11b047d8 --- /dev/null +++ b/kdepim-runtime/resources/akonotes/akonotesresource.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "akonotesresource.h" + +#include +#include + +AkonotesResource::AkonotesResource( const QString &id ) + : MaildirResource( id ) +{ + KGlobal::locale()->insertCatalog( "akonadi_maildir_resource" ); +} + +AkonotesResource::~AkonotesResource() +{ +} + +QString AkonotesResource::itemMimeType() const +{ + return QLatin1String( "text/x-vnd.akonadi.note" ); +} + +void AkonotesResource::configure( WId windowId ) +{ + MaildirResource::configure( windowId ); + synchronize(); // heavy to do it in the MaildirResource method, which already has sync on demand working properly +} + +QString AkonotesResource::defaultResourceType() +{ + return QLatin1String("notes"); +} + diff --git a/kdepim-runtime/resources/akonotes/akonotesresource.desktop b/kdepim-runtime/resources/akonotes/akonotesresource.desktop new file mode 100644 index 00000000..d3f949ae --- /dev/null +++ b/kdepim-runtime/resources/akonotes/akonotesresource.desktop @@ -0,0 +1,97 @@ +[Desktop Entry] +Name=AkoNotes +Name[bg]=AkoNotes +Name[bs]=AkoNotes +Name[ca]=AkoNotes +Name[ca@valencia]=AkoNotes +Name[cs]=AkoNotes +Name[da]=AkoNotes +Name[de]=AkoNotes +Name[el]=AkoNotes +Name[en_GB]=AkoNotes +Name[es]=AkoNotes +Name[et]=AkoNotes +Name[fi]=AkoNotes +Name[fr]=AkoNotes +Name[ga]=AkoNotes +Name[gl]=AkoNotes +Name[hu]=AkoNotes +Name[ia]=AkoNotes +Name[it]=AkoNotes +Name[ja]=AkoNotes +Name[kk]=AkoNotes +Name[km]=AkoNotes +Name[ko]=AkoNotes +Name[lt]=AkoNotes +Name[lv]=AkoNotes +Name[nb]=AkoNotes +Name[nds]=Akonotes +Name[nl]=AkoNotes +Name[pl]=AkoNotes +Name[pt]=AkoNotes +Name[pt_BR]=AkoNotes +Name[ro]=AkoNote +Name[ru]=AkoNotes +Name[sk]=AkoNotes +Name[sl]=AkoNotes +Name[sr]=Ðко‑белешке +Name[sr@ijekavian]=Ðко‑биљешке +Name[sr@ijekavianlatin]=Ako‑biljeÅ¡ke +Name[sr@latin]=Ako‑beleÅ¡ke +Name[sv]=Ako-anteckningar +Name[tr]=AkoNot +Name[uk]=Ðотатки +Name[x-test]=xxAkoNotesxx +Name[zh_CN]=AkoNotes +Name[zh_TW]=AkoNotes +Comment=Loads a notes hierarchy from a local maildir folder +Comment[bs]=UÄitava hijerarhiju napomena iz loklnog maildir direktorija +Comment[ca]=Carrega una jerarquia de notes des d'una carpeta pel directori de correu local +Comment[ca@valencia]=Carrega una jerarquia de notes des d'una carpeta pel directori de correu local +Comment[da]=Indlæser et hierarki af noter fra en lokal maildir-mappe +Comment[de]=Laden einer Notizenhierarchie aus einem lokalen Maildir-Ordner +Comment[el]=ΦόÏτωση ιεÏαÏχίας σημειώσεων από έναν τοπικό φάκελο maildir +Comment[en_GB]=Loads a notes hierarchy from a local maildir folder +Comment[es]=Carga una jerarquía de notas desde una carpeta de directorio de correo local +Comment[et]=Märkmete hierarhia laadimine kohalikust maildir-kaustast +Comment[fi]=Lataa muistiinpanohierarkian paikallisesta maildir-kansiosta +Comment[fr]=Charge une arborescence de notes d'un dossier au format « maildir x +Comment[ga]=Luchtaíonn sé seo ordlathas nótaí ó fhillteán logánta maildir +Comment[gl]=Carga unha xerarquía de notas desde un cartafol de correo local +Comment[hu]=Jegyzethierarchia betöltése egy helyi maildir mappából +Comment[ia]=Lege un hierarchia de notas de un dossier local de Maildir +Comment[it]=Carica una gerarchia di note da una cartella locale maildir +Comment[ja]=ローカル㮠maildir フォルダã‹ã‚‰ notes ã®éšŽå±¤ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=Жергілікті maildir қапшығынан жазбалар иерархиÑÑын жүктеп алады +Comment[km]=ផ្ទុក​ឋានានុក្រម​ចំណាំ​ពី​ážáž maildir មូលដ្ឋាន +Comment[ko]=로컬 maildir í´ë”ì—ì„œ 노트 계층 구조를 불러옴 +Comment[lt]=įkelia užrašų hierarchijÄ… iÅ¡ vietinio maildir aplanko +Comment[lv]=IelÄdÄ“ piezÄ«mju hierarhiju no lokÄlas maildir mapes +Comment[nb]=Laster et notat-hierarki fra en lokal maildir-mappe +Comment[nds]=Notizenstruktuur ut en lokaal Nettpostorner laden +Comment[nl]=Laadt hiërarchie van notities van een lokale maildir-map +Comment[pl]=Wczytuje hierarchiÄ™ notatek z lokalnego katalogu maildir +Comment[pt]=Carrega uma hierarquia de notas de uma pasta Maildir local +Comment[pt_BR]=Carrega uma hierarquia de notas de uma pasta maildir local +Comment[ro]=ÃŽncarcă o ierarhie de notiÈ›e dintr-un dosar maildir local +Comment[ru]=Загрузка иерархии примечаний из локальной папки Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð¹ +Comment[sk]=NaÄíta hierarchiu poznámok z miestneho prieÄinka maildir +Comment[sl]=Naloži hierarhijo notic iz krajevne poÅ¡tne mape MailDir +Comment[sr]=Учитава хијерархију белешки из локалне мејлдир фаÑцикле +Comment[sr@ijekavian]=Учитава хијерархију биљешки из локалне мејлдир фаÑцикле +Comment[sr@ijekavianlatin]=UÄitava hijerarhiju biljeÅ¡ki iz lokalne maildir fascikle +Comment[sr@latin]=UÄitava hijerarhiju beleÅ¡ki iz lokalne maildir fascikle +Comment[sv]=Laddar en anteckningshierarki frÃ¥n en lokal maildir-katalog +Comment[tr]=Yerel bir maildir dizininden not hiyerarÅŸisini yükler +Comment[uk]=Завантажує ієрархію нотаток з локальної теки maildir +Comment[x-test]=xxLoads a notes hierarchy from a local maildir folderxx +Comment[zh_CN]=从本地 maildir 文件夹中载入层次型便笺 +Comment[zh_TW]=從本地 Maildir æ ¼å¼çš„ç›®éŒ„ä¸­è¼‰å…¥çµ„ç¹”ä¾¿æ¢ +Type=AkonadiResource +Exec=akonadi_akonotes_resource +Icon=view-pim-notes + +X-Akonadi-MimeTypes=text/x-vnd.akonadi.note +X-Akonadi-Capabilities=Resource,Notes +X-Akonadi-Identifier=akonadi_akonotes_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/akonotes/akonotesresource.h b/kdepim-runtime/resources/akonotes/akonotesresource.h new file mode 100644 index 00000000..03e494a7 --- /dev/null +++ b/kdepim-runtime/resources/akonotes/akonotesresource.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2007 Till Adam + + 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 __AKONOTES_RESOURCE_H__ +#define __AKONOTES_RESOURCE_H__ + +#include "maildirresource.h" + +class AkonotesResource : public MaildirResource +{ + Q_OBJECT + + public: + explicit AkonotesResource( const QString &id ); + ~AkonotesResource(); + + virtual QString defaultResourceType(); +public Q_SLOTS: + virtual void configure( WId windowId ); + + protected: + virtual QString itemMimeType() const; +}; + +#endif diff --git a/kdepim-runtime/resources/akonotes/main.cpp b/kdepim-runtime/resources/akonotes/main.cpp new file mode 100644 index 00000000..3aafdf42 --- /dev/null +++ b/kdepim-runtime/resources/akonotes/main.cpp @@ -0,0 +1,24 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "akonotesresource.h" + +#include + +AKONADI_AGENT_FACTORY( AkonotesResource, akonadi_akonotes_resource ) diff --git a/kdepim-runtime/resources/birthdays/CMakeLists.txt b/kdepim-runtime/resources/birthdays/CMakeLists.txt new file mode 100644 index 00000000..29538d33 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/CMakeLists.txt @@ -0,0 +1,35 @@ +include_directories( ${Boost_INCLUDE_DIR} ) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +set( birthdayresource_srcs + birthdaysresource.cpp + configdialog.cpp +) + +kde4_add_kcfg_files( birthdayresource_srcs settings.kcfgc ) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/birthdaysresource.kcfg org.kde.Akonadi.Birthdays.Settings) + +kde4_add_ui_files(birthdayresource_srcs configdialog.ui) + +qt4_add_dbus_adaptor(birthdayresource_srcs + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Birthdays.Settings.xml settings.h Settings +) +kde4_add_executable(akonadi_birthdays_resource ${birthdayresource_srcs}) + +if (Q_WS_MAC) + set_target_properties(akonadi_birthdays_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_birthdays_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.Birthdays") + set_target_properties(akonadi_birthdays_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Birthdays Resource") +endif () + +target_link_libraries(akonadi_birthdays_resource + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDEPIMLIBS_KPIMUTILS_LIBS} +) + +install( TARGETS akonadi_birthdays_resource ${INSTALL_TARGETS_DEFAULT_ARGS} ) +install( FILES birthdaysresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) diff --git a/kdepim-runtime/resources/birthdays/Messages.sh b/kdepim-runtime/resources/birthdays/Messages.sh new file mode 100644 index 00000000..9a63721e --- /dev/null +++ b/kdepim-runtime/resources/birthdays/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_birthdays_resource.pot diff --git a/kdepim-runtime/resources/birthdays/birthdaysresource.cpp b/kdepim-runtime/resources/birthdays/birthdaysresource.cpp new file mode 100644 index 00000000..a87a9361 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/birthdaysresource.cpp @@ -0,0 +1,343 @@ +/* + Copyright (c) 2003 Cornelius Schumacher + Copyright (c) 2009 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 "birthdaysresource.h" +#include "settings.h" +#include "settingsadaptor.h" +#include "configdialog.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace Akonadi; +using namespace KABC; +using namespace KCalCore; + + +BirthdaysResource::BirthdaysResource(const QString& id) : + ResourceBase( id ) +{ + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + setName( i18n( "Birthdays & Anniversaries" ) ); + + Monitor *monitor = new Monitor( this ); + monitor->setMimeTypeMonitored( Addressee::mimeType() ); + monitor->itemFetchScope().fetchFullPayload(); + connect( monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), + SLOT(contactChanged(Akonadi::Item)) ); + connect( monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), + SLOT(contactChanged(Akonadi::Item)) ); + connect( monitor, SIGNAL(itemRemoved(Akonadi::Item)), + SLOT(contactRemoved(Akonadi::Item)) ); + + connect( this, SIGNAL(reloadConfiguration()), SLOT(doFullSearch()) ); +} + +BirthdaysResource::~BirthdaysResource() +{ +} + +void BirthdaysResource::configure( WId windowId ) +{ + ConfigDialog dlg; + if ( windowId ) + KWindowSystem::setMainWindow( &dlg, windowId ); + if ( dlg.exec() ) { + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + doFullSearch(); + synchronizeCollectionTree(); +} + +void BirthdaysResource::retrieveCollections() +{ + Collection c; + c.setParentCollection( Collection::root() ); + c.setRemoteId( QLatin1String("akonadi_birthdays_resource") ); + c.setName( name() ); + c.setContentMimeTypes( QStringList() << QLatin1String("application/x-vnd.akonadi.calendar.event") ); + c.setRights( Collection::ReadOnly ); + + EntityDisplayAttribute *attribute = c.attribute( Collection::AddIfMissing ); + attribute->setIconName( QLatin1String( "view-calendar-birthday" ) ); + + Collection::List list; + list << c; + collectionsRetrieved( list ); +} + +void BirthdaysResource::retrieveItems(const Akonadi::Collection& collection) +{ + Q_UNUSED( collection ); + itemsRetrievedIncremental( mPendingItems.values(), mDeletedItems.values() ); + mPendingItems.clear(); + mDeletedItems.clear(); +} + +bool BirthdaysResource::retrieveItem(const Akonadi::Item& item, const QSet< QByteArray > &parts) +{ + Q_UNUSED( parts ); + qint64 contactId = item.remoteId().mid( 1 ).toLongLong(); + ItemFetchJob *job = new ItemFetchJob( Item( contactId ), this ); + job->fetchScope().fetchFullPayload(); + connect( job, SIGNAL(result(KJob*)), SLOT(contactRetrieved(KJob*)) ); + return true; +} + +void BirthdaysResource::contactRetrieved(KJob* job) +{ + ItemFetchJob *fj = static_cast( job ); + if ( job->error() ) { + emit error( job->errorText() ); + cancelTask(); + } else if ( fj->items().count() != 1 ) { + cancelTask(); + } else { + KCalCore::Incidence::Ptr ev; + if ( currentItem().remoteId().startsWith( QLatin1Char('b') ) ) + ev = createBirthday( fj->items().first() ); + else if ( currentItem().remoteId().startsWith( QLatin1Char('a') ) ) + ev = createAnniversary( fj->items().first() ); + if ( !ev ) { + cancelTask(); + } else { + Item i( currentItem() ); + i.setPayload( ev ); + itemRetrieved( i ); + } + } +} + +void BirthdaysResource::contactChanged( const Akonadi::Item& item ) +{ + if ( !item.hasPayload() ) + return; + + KABC::Addressee contact = item.payload(); + + if ( Settings::self()->filterOnCategories() ) { + bool hasCategory = false; + const QStringList categories = contact.categories(); + foreach ( const QString &cat, Settings::self()->filterCategories() ) { + if ( categories.contains( cat ) ) { + hasCategory = true; + break; + } + } + + if ( !hasCategory ) + return; + } + + Event::Ptr event = createBirthday( item ); + if ( event ) + addPendingEvent( event, QString::fromLatin1( "b%1" ).arg( item.id() ) ); + + event = createAnniversary( item ); + if ( event ) + addPendingEvent( event, QString::fromLatin1( "a%1" ).arg( item.id() ) ); +} + +void BirthdaysResource::addPendingEvent( const KCalCore::Event::Ptr &event, const QString &remoteId ) +{ + KCalCore::Incidence::Ptr evptr( event ); + Item i( KCalCore::Event::eventMimeType() ); + i.setRemoteId( remoteId ); + i.setPayload( evptr ); + mPendingItems[ remoteId ] = i; + synchronize(); +} + + +void BirthdaysResource::contactRemoved( const Akonadi::Item& item ) +{ + Item i( KCalCore::Event::eventMimeType() ); + i.setRemoteId( QString::fromLatin1( "b%1" ).arg( item.id() ) ); + mDeletedItems[ i.remoteId() ] = i; + i.setRemoteId( QString::fromLatin1( "a%1" ).arg( item.id() ) ); + mDeletedItems[ i.remoteId() ] = i; + synchronize(); +} + + +void BirthdaysResource::doFullSearch() +{ + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this ); + connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(listContacts(Akonadi::Collection::List)) ); +} + +void BirthdaysResource::listContacts(const Akonadi::Collection::List &cols) +{ + MimeTypeChecker contactFilter; + contactFilter.addWantedMimeType( Addressee::mimeType() ); + foreach ( const Collection &col, cols ) { + if ( !contactFilter.isWantedCollection( col ) ) + continue; + ItemFetchJob *job = new ItemFetchJob( col, this ); + job->fetchScope().fetchFullPayload(); + connect( job, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(createEvents(Akonadi::Item::List)) ); + } +} + +void BirthdaysResource::createEvents(const Akonadi::Item::List &items) +{ + foreach ( const Item &item, items ) + contactChanged( item ); +} + +KCalCore::Event::Ptr BirthdaysResource::createBirthday(const Akonadi::Item& contactItem) +{ + if ( !contactItem.hasPayload() ) + return KCalCore::Event::Ptr(); + KABC::Addressee contact = contactItem.payload(); + + const QString name = contact.realName().isEmpty() ? contact.nickName() : contact.realName(); + if ( name.isEmpty() ) { + kDebug() << "contact " << contact.uid() << contactItem.id() << " has no name, skipping."; + return KCalCore::Event::Ptr(); + } + + const QDate birthdate = contact.birthday().date(); + if ( birthdate.isValid() ) { + const QString summary = i18n( "%1's birthday", name ); + + Event::Ptr ev = createEvent( birthdate ); + ev->setUid( contact.uid() + QLatin1String("_KABC_Birthday") ); + + ev->setCustomProperty( "KABC", "BIRTHDAY", QLatin1String("YES") ); + ev->setCustomProperty( "KABC", "UID-1", contact.uid() ); + ev->setCustomProperty( "KABC", "NAME-1", name ); + ev->setCustomProperty( "KABC", "EMAIL-1", contact.fullEmail() ); + ev->setSummary( summary ); + + ev->setCategories( i18n( "Birthday" ) ); + return ev; + } + return KCalCore::Event::Ptr(); +} + +KCalCore::Event::Ptr BirthdaysResource::createAnniversary(const Akonadi::Item& contactItem) +{ + if ( !contactItem.hasPayload() ) + return KCalCore::Event::Ptr(); + KABC::Addressee contact = contactItem.payload(); + + const QString name = contact.realName().isEmpty() ? contact.nickName() : contact.realName(); + if ( name.isEmpty() ) { + kDebug() << "contact " << contact.uid() << contactItem.id() << " has no name, skipping."; + return KCalCore::Event::Ptr(); + } + + const QString anniversary_string = contact.custom( QLatin1String("KADDRESSBOOK"), QLatin1String("X-Anniversary") ); + if ( anniversary_string.isEmpty() ) + return KCalCore::Event::Ptr(); + const QDate anniversary = QDate::fromString( anniversary_string, Qt::ISODate ); + if ( anniversary.isValid() ) { + const QString spouseName = contact.custom( QLatin1String("KADDRESSBOOK"), QLatin1String("X-SpousesName") ); + + QString summary; + if ( !spouseName.isEmpty() ) { + QString tname, temail; + KPIMUtils::extractEmailAddressAndName( spouseName, temail, tname ); + tname = KPIMUtils::quoteNameIfNecessary( tname ); + if ( ( tname[0] == QLatin1Char('"') ) && ( tname[tname.length() - 1] == QLatin1Char('"') ) ) { + tname.remove( 0, 1 ); + tname.truncate( tname.length() - 1 ); + } + tname.remove( QLatin1Char('\\') ); // remove escape chars + KABC::Addressee spouse; + spouse.setNameFromString( tname ); + QString name_2 = spouse.nickName(); + if ( name_2.isEmpty() ) { + name_2 = spouse.realName(); + } + summary = i18nc( "insert names of both spouses", + "%1's & %2's anniversary", name, name_2 ); + } else { + summary = i18nc( "only one spouse in addressbook, insert the name", + "%1's anniversary", name ); + } + + Event::Ptr event = createEvent( anniversary ); + event->setUid( contact.uid() + QLatin1String("_KABC_Anniversary") ); + event->setSummary( summary ); + + event->setCustomProperty( "KABC", "UID-1", contact.uid() ); + event->setCustomProperty( "KABC", "NAME-1", name ); + event->setCustomProperty( "KABC", "EMAIL-1", contact.fullEmail() ); + event->setCustomProperty( "KABC", "ANNIVERSARY", QLatin1String("YES") ); + // insert category + event->setCategories( i18n( "Anniversary" ) ); + return event; + } + return KCalCore::Event::Ptr(); +} + +KCalCore::Event::Ptr BirthdaysResource::createEvent(const QDate& date) +{ + Event::Ptr event( new Event() ); + event->setDtStart( KDateTime( date, KDateTime::ClockTime ) ); + event->setDtEnd( KDateTime( date, KDateTime::ClockTime ) ); + event->setHasEndDate( true ); + event->setAllDay( true ); + event->setTransparency( Event::Transparent ); + + // Set the recurrence + Recurrence *recurrence = event->recurrence(); + recurrence->setStartDateTime( KDateTime( date, KDateTime::ClockTime ) ); + recurrence->setYearly( 1 ); + if ( date.month() == 2 && date.day() == 29 ) + recurrence->addYearlyDay( 60 ); + + // Set the alarm + event->clearAlarms(); + if ( Settings::self()->enableAlarm() ) { + Alarm::Ptr alarm = event->newAlarm(); + alarm->setType( Alarm::Display ); + alarm->setText( event->summary() ); + alarm->setTime( KDateTime( date, KDateTime::ClockTime ) ); + // N days before + alarm->setStartOffset( Duration( -Settings::self()->alarmDays(), Duration::Days ) ); + alarm->setEnabled( true ); + } + + return event; +} + + +AKONADI_RESOURCE_MAIN( BirthdaysResource ) + diff --git a/kdepim-runtime/resources/birthdays/birthdaysresource.desktop b/kdepim-runtime/resources/birthdays/birthdaysresource.desktop new file mode 100644 index 00000000..14e6c902 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/birthdaysresource.desktop @@ -0,0 +1,99 @@ +[Desktop Entry] +Name=Birthdays & Anniversaries +Name[ar]=أعياد الميلاد Ùˆ الذكريات +Name[bs]=RoÄ‘endani i godiÅ¡njice +Name[ca]=Dates de naixement i aniversaris +Name[ca@valencia]=Dates de naixement i aniversaris +Name[cs]=Narozeniny a výroÄí +Name[da]=Fødselsdage og Ã¥rsdage +Name[de]=Geburtstage und Jahrestage +Name[el]=Γενέθλια & επέτειοι +Name[en_GB]=Birthdays & Anniversaries +Name[es]=Cumpleaños y aniversarios +Name[et]=Sünni- ja aastapäevad +Name[fi]=Syntymä- ja vuosipäivät +Name[fr]=Anniversaires et fêtes +Name[gl]=Cumpreanos e aniversarios +Name[hu]=Születésnapok és évfordulók +Name[ia]=Dies natal e anniversarios +Name[it]=Compleanni e anniversari +Name[ja]=誕生日ã¨è¨˜å¿µæ—¥ +Name[kk]=Тұған күн мен жылдықтар +Name[km]=បុណ្យ​ážáž½áž” និង​បុណ្យ​ážáž½áž”​កំណើហ+Name[ko]=ìƒì¼ê³¼ 기ë…ì¼ +Name[lt]=Gimtadieniai ir sukaktys +Name[lv]=DzimÅ¡anas un svÄ“tku dienas +Name[nb]=Vis fødselsdager og bryllupsdager +Name[nds]=Geboorts- un Johrdaag +Name[nl]=Verjaardagen & trouwdagen +Name[nn]=Fødselsdagar og bryllaupsdagar +Name[pa]=ਜਨਮਦਿਨ ਅਤੇ ਵਰà©à¨¹à©‡à¨—ੰਢ +Name[pl]=Urodziny i rocznice +Name[pt]=Datas de Nascimento & Aniversários +Name[pt_BR]=Aniversários e aniversários de casamento +Name[ro]=Zile de naÈ™tere È™i aniversări +Name[ru]=Праздники +Name[sk]=Narodeniny a výroÄia +Name[sl]=Rojstni dnevi in obletnice +Name[sq]=Ditëlindje & Përvjetorë +Name[sr]=Рођендани и годишњице +Name[sr@ijekavian]=Рођендани и годишњице +Name[sr@ijekavianlatin]=RoÄ‘endani i godiÅ¡njice +Name[sr@latin]=RoÄ‘endani i godiÅ¡njice +Name[sv]=Födelsedagar och Ã¥rsdagar +Name[tr]=DoÄŸum günleri & Yıl dönümleri +Name[ug]=تۇغۇلغان ÙƒÛˆÙ† Û‹Û• خاتىرە كۈنلەر +Name[uk]=Дні Ð½Ð°Ñ€Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ñ– річниці веÑіль +Name[x-test]=xxBirthdays & Anniversariesxx +Name[zh_CN]=生日和纪念日 +Name[zh_TW]=生日與紀念日 +Comment=Provides access to birthday and anniversary dates of contacts in your address book as calendar events +Comment[ar]=يوÙر الحصول على موعد الذكرى السنوية لميلاد أو ذكرى لقائمة دÙتر العناوين الخاص بك حسب الجدول الزمني للأحداث +Comment[bs]=Omogućava pristup datumima roÄ‘endana i godiÅ¡njica u vaÅ¡em adresaru kao dogaÄ‘ajima na kalendaru +Comment[ca]=Proporciona accés a les dates de naixement i els aniversaris dels contactes a la vostra llibreta d'adreces com a esdeveniments de calendari +Comment[ca@valencia]=Proporciona l'accés a les dates de naixement i els aniversaris dels contactes de la llibreta d'adreces com a esdeveniments de calendari +Comment[da]=Giver adgang til datoer for fødsels- og Ã¥rsdage for kontakter i din adressebog som kalenderbegivenheder +Comment[de]=Ermöglicht den Zugriff auf Geburtstage und Jahrestage von Kontakten aus dem KDE-Adressbuch in Form von Kalendereinträgen. +Comment[el]=ΠÏοσφέÏει Ï€Ïόσβαση σε γενέθλια και επετείους επαφών του βιβλίου διευθÏνσεών σας ως γεγονότα ημεÏολογίου +Comment[en_GB]=Provides access to birthday and anniversary dates of contacts in your address book as calendar events +Comment[es]=Proporciona acceso a las fechas de cumpleaños y aniversarios de los contactos en la libreta de direcciones como eventos de calendario +Comment[et]=Võimaldab kasutada KDE aadressiraamatusse kalendrisündmustena salvestatud kontaktide sünni- ja aastapäevi +Comment[fi]=Noutaa yhteystietojesi syntymä- ja vuosipäivät osoitekirjastasi kalenteritapahtumina +Comment[fr]=Fournit l'accès aux dates d'anniversaires et de fêtes des contacts du carnet d'adresses KDE comme des évènements de l'agenda +Comment[gl]=Fornece acceso ás datas de cumpreanos e aniversarios dos contactos do caderno de enderezos como actividades do calendario +Comment[hu]=Hozzáférést biztosít a névjegyekben tárolt születésnapokhoz és évfordulókhoz a címjegyzékekben (naptári eseményként) +Comment[ia]=Provide accesso a datos de dies natal e anniversarios de contactos in tu adressario como eventos in le calendario +Comment[it]=Consente l'accesso alle date di nascita ed agli anniversari dei contatti della rubrica di KDE come eventi di calendario. +Comment[ja]=アドレス帳ã«ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã‚¤ãƒ™ãƒ³ãƒˆã¨ã—ã¦ä¿å­˜ã•ã‚Œã¦ã„る誕生日ã¨è¨˜å¿µæ—¥ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’æä¾›ã—ã¾ã™ +Comment[kk]=ÐдреÑтік кітапшаңыздағы түған күн және жылдықтар мәліметіне күнтізбе оқиғалар ретінде қатынау мүмкіндігін береді +Comment[km]=ផ្ដល់​សិទ្ធិ​ចូល​ដំណើរការ​ទៅកាន់​កាលបរិច្ឆáŸáž‘​ážáŸ’ងៃ​ážáž½áž”​កំណើážâ€‹áž“ៃ​ទំនាក់ទំនង​នៅ​ក្នុង​សៀវភៅ​អាសយដ្ឋាន KDE ជា​ព្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸâ€‹áž”្រážáž·áž‘áž·áž“ +Comment[ko]=주소ë¡ì— 등ë¡ëœ ìƒì¼ê³¼ 기ë…ì¼ì„ 달력ì—ì„œ 접근할 수 있ë„ë¡ í•¨ +Comment[lt]=Suteikia prieigÄ… prie kontaktų gimimo dienų ir sukakÄių datų, įraÅ¡ytų adresų knygelÄ—je, kaip prie kalendoriaus įvykių +Comment[lv]=NodroÅ¡ina piekļuvi KDE adreÅ¡u grÄmatas kontaktu dzimÅ¡anas dienu datumiem kÄ kalendÄra notikumiem +Comment[nb]=Gir tilgang til bursdager og bryllupsdager for kontakter i adresseboka, som kalenderhendelser +Comment[nds]=Stellt Togriep op Geboorts- un Johrdaag vun Kontakten binnen Dien Adressbook as Kalenner-Begeevnissen praat +Comment[nl]=Geeft toegang tot verjaar- en trouwdagdata van contactpersonen in uw adresboek als agenda-gebeurtenissen +Comment[nn]=Gjev tilgang til fødselsdagane og bryllaupsdagane til kontaktar i adresseboka som kalenderhendingar +Comment[pl]=Zapewnia dostÄ™p do dat urodzin i rocznic z wizytówek w książce adresowej jako zdarzeÅ„ kalendarza +Comment[pt]=Oferece o acesso às datas de nascimento e aniversário dos contactos no livro de endereços como eventos do calendário +Comment[pt_BR]=Fornece acesso às datas de aniversário e de casamento de contatos do seu livro de endereços como eventos de calendário +Comment[ru]=ДоÑтуп к информации о днÑÑ… Ñ€Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¸Ð· адреÑной книги KDE как к ÑобытиÑм ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ +Comment[sk]=Poskytuje prístup k narodeninám a výroÄiam kontaktov vo vaÅ¡om adresári ako udalosti kalendára +Comment[sl]=OmogoÄa dostop do rojstnih dni in obletnic stikov, shranjenih v vaÅ¡em imeniku. Na voljo so kot koledarski dogodki. +Comment[sr]=Омогућава приÑтуп датумима рођендана и годишњица контаката у вашем адреÑару као календарÑким догађајима +Comment[sr@ijekavian]=Омогућава приÑтуп датумима рођендана и годишњица контаката у вашем адреÑару као календарÑким догађајима +Comment[sr@ijekavianlatin]=Omogućava pristup datumima roÄ‘endana i godiÅ¡njica kontakata u vaÅ¡em adresaru kao kalendarskim dogaÄ‘ajima +Comment[sr@latin]=Omogućava pristup datumima roÄ‘endana i godiÅ¡njica kontakata u vaÅ¡em adresaru kao kalendarskim dogaÄ‘ajima +Comment[sv]=Ger tillgÃ¥ng till datum för födelsedagar och Ã¥rsdagar för kontakter i KDE:s adressbok som kalenderhändelser +Comment[tr]=Adres defterindeki kiÅŸilerin doÄŸum günleri ve yıl dönümlerine takvim olayları olarak eriÅŸmeyi saÄŸlar +Comment[uk]="Ðадає доÑтуп до дат днів Ð½Ð°Ñ€Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ñ– річниць веÑіль, Ñкі зберігаютьÑÑ Ñƒ вашій адреÑній книзі Ñк події календарÑ" +Comment[x-test]=xxProvides access to birthday and anniversary dates of contacts in your address book as calendar eventsxx +Comment[zh_CN]=æ供对存储在 KDE 地å€ç°¿æ–‡ä»¶å¤¹ä¸­çš„è”系人生日和纪念日的访问支æŒï¼Œå¹¶å°†å…¶ä½œä¸ºæ—¥åŽ†ä¸­çš„事件 +Comment[zh_TW]=æ供存å–儲存通訊錄中的生日與紀念日日期,åšç‚ºè¡Œäº‹æ›†çš„事件 +Type=AkonadiResource +Exec=akonadi_birthdays_resource +Icon=view-calendar-birthday + +X-Akonadi-MimeTypes=text/calendar,application/x-vnd.akonadi.calendar.event +X-Akonadi-Capabilities=Resource,Unique +X-Akonadi-Identifier=akonadi_birthdays_resource diff --git a/kdepim-runtime/resources/birthdays/birthdaysresource.h b/kdepim-runtime/resources/birthdays/birthdaysresource.h new file mode 100644 index 00000000..9c61643a --- /dev/null +++ b/kdepim-runtime/resources/birthdays/birthdaysresource.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2003 Cornelius Schumacher + Copyright (c) 2009 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 BIRTHDAYSRESOURCE_H +#define BIRTHDAYSRESOURCE_H + +#include + +#include + +#include + +class QDate; + +class BirthdaysResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + BirthdaysResource( const QString &id ); + ~BirthdaysResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &collection ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + private: + void addPendingEvent( const KCalCore::Event::Ptr &event, const QString &remoteId ); + + KCalCore::Event::Ptr createBirthday( const Akonadi::Item &contactItem ); + KCalCore::Event::Ptr createAnniversary( const Akonadi::Item &contactItem ); + KCalCore::Event::Ptr createEvent( const QDate &date ); + + private slots: + void doFullSearch(); + void listContacts( const Akonadi::Collection::List &cols ); + void createEvents( const Akonadi::Item::List &items ); + + void contactChanged( const Akonadi::Item &item ); + void contactRemoved( const Akonadi::Item &item ); + + void contactRetrieved( KJob *job ); + private: + QHash mPendingItems; + QHash mDeletedItems; +}; + +#endif diff --git a/kdepim-runtime/resources/birthdays/birthdaysresource.kcfg b/kdepim-runtime/resources/birthdays/birthdaysresource.kcfg new file mode 100644 index 00000000..ae2f2178 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/birthdaysresource.kcfg @@ -0,0 +1,25 @@ + + + + + + + + false + + + + + + + true + + + 1 + + + diff --git a/kdepim-runtime/resources/birthdays/configdialog.cpp b/kdepim-runtime/resources/birthdays/configdialog.cpp new file mode 100644 index 00000000..389bf980 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/configdialog.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2003 Cornelius Schumacher + Copyright (c) 2009 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 "configdialog.h" +#include "settings.h" + +#include + +ConfigDialog::ConfigDialog(QWidget* parent) + : KDialog( parent ) +{ + ui.setupUi( mainWidget() ); + setWindowIcon( KIcon( QLatin1String("view-calendar-birthday") ) ); + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); + ui.kcfg_AlarmDays->setSuffix( ki18np( " day", " days" ) ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); +} + +void ConfigDialog::save() +{ + mManager->updateSettings(); + Settings::self()->writeConfig(); +} + diff --git a/kdepim-runtime/resources/birthdays/configdialog.h b/kdepim-runtime/resources/birthdays/configdialog.h new file mode 100644 index 00000000..9f8b2b8b --- /dev/null +++ b/kdepim-runtime/resources/birthdays/configdialog.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2003 Cornelius Schumacher + Copyright (c) 2009 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include "ui_configdialog.h" + +#include + +class KConfigDialogManager; + +class ConfigDialog : public KDialog +{ + Q_OBJECT + public: + ConfigDialog( QWidget *parent = 0 ); + ~ConfigDialog() {}; + + private slots: + void save(); + + private: + Ui::ConfigDialog ui; + KConfigDialogManager* mManager; +}; + +#endif diff --git a/kdepim-runtime/resources/birthdays/configdialog.ui b/kdepim-runtime/resources/birthdays/configdialog.ui new file mode 100644 index 00000000..de44cfa4 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/configdialog.ui @@ -0,0 +1,162 @@ + + ConfigDialog + + + + 0 + 0 + 434 + 364 + + + + + + + Reminder + + + false + + + + + + Set &reminder + + + + + + + false + + + R&emind prior to event: + + + kcfg_AlarmDays + + + + + + + false + + + + + + 355 + + + + + + + + + + Filter + + + + + + &Filter by categories + + + + + + + false + + + KEditListWidget::Add|KEditListWidget::Remove + + + + + + + + + + Qt::Vertical + + + + 20 + 18 + + + + + + + + + KEditListWidget + QGroupBox +
keditlistwidget.h
+
+ + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + + + kcfg_EnableAlarm + toggled(bool) + label + setEnabled(bool) + + + 97 + 54 + + + 104 + 75 + + + + + kcfg_EnableAlarm + toggled(bool) + kcfg_AlarmDays + setEnabled(bool) + + + 169 + 51 + + + 292 + 85 + + + + + kcfg_FilterOnCategories + toggled(bool) + kcfg_FilterCategories + setEnabled(bool) + + + 29 + 151 + + + 81 + 199 + + + + +
diff --git a/kdepim-runtime/resources/birthdays/settings.kcfgc b/kdepim-runtime/resources/birthdays/settings.kcfgc new file mode 100644 index 00000000..12374c33 --- /dev/null +++ b/kdepim-runtime/resources/birthdays/settings.kcfgc @@ -0,0 +1,7 @@ +File=birthdaysresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +GlobalEnums=true diff --git a/kdepim-runtime/resources/contacts/CMakeLists.txt b/kdepim-runtime/resources/contacts/CMakeLists.txt new file mode 100644 index 00000000..f1733dbb --- /dev/null +++ b/kdepim-runtime/resources/contacts/CMakeLists.txt @@ -0,0 +1,36 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +add_subdirectory( wizard ) + +########### next target ############### + +set( contactsresource_SRCS + contactsresource.cpp + settingsdialog.cpp +) + +kde4_add_ui_files(contactsresource_SRCS settingsdialog.ui) +kde4_add_kcfg_files(contactsresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/contactsresource.kcfg org.kde.Akonadi.Contacts.Settings) +qt4_add_dbus_adaptor(contactsresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Contacts.Settings.xml settings.h Akonadi_Contacts_Resource::ContactsResourceSettings contactsresourcesettingsadaptor ContactsResourceSettingsAdaptor +) + +install( FILES contactsresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_plugin(akonadi_contacts_resource ${contactsresource_SRCS}) + +target_link_libraries(akonadi_contacts_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDE4_KIO_LIBS} +) + +install(TARGETS akonadi_contacts_resource DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/contacts/Messages.sh b/kdepim-runtime/resources/contacts/Messages.sh new file mode 100644 index 00000000..07eba8d4 --- /dev/null +++ b/kdepim-runtime/resources/contacts/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_contacts_resource.pot diff --git a/kdepim-runtime/resources/contacts/contactsresource.cpp b/kdepim-runtime/resources/contacts/contactsresource.cpp new file mode 100644 index 00000000..2d3eb2ef --- /dev/null +++ b/kdepim-runtime/resources/contacts/contactsresource.cpp @@ -0,0 +1,534 @@ +/* + Copyright (c) 2009 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. +*/ + +#include "contactsresource.h" + +#include "settings.h" +#include "contactsresourcesettingsadaptor.h" +#include "settingsdialog.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; +using namespace Akonadi_Contacts_Resource; + +ContactsResource::ContactsResource( const QString &id ) + : ResourceBase( id ), + mSettings( new ContactsResourceSettings( componentData().config() ) ) +{ + // setup the resource + new ContactsResourceSettingsAdaptor( mSettings ); + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + mSettings, QDBusConnection::ExportAdaptors ); + + changeRecorder()->fetchCollection( true ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + + setHierarchicalRemoteIdentifiersEnabled( true ); + + mSupportedMimeTypes << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType() << Collection::mimeType(); + + if ( name().startsWith( QLatin1String( "akonadi_contacts_resource" ) ) ) + setName( i18n( "Personal Contacts" ) ); + + // Make sure we have a valid directory (XDG dirs want this very much). + initializeDirectory(mSettings->path()); + + if ( mSettings->isConfigured() ) + synchronize(); +} + +ContactsResource::~ContactsResource() +{ + delete mSettings; +} + +void ContactsResource::aboutToQuit() +{ +} + +void ContactsResource::configure( WId windowId ) +{ + QPointer dlg = new SettingsDialog( mSettings, windowId ); + if ( dlg->exec() ) { + mSettings->setIsConfigured( true ); + mSettings->writeConfig(); + + clearCache(); + initializeDirectory( baseDirectoryPath() ); + + synchronize(); + + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + delete dlg; +} + +Collection::List ContactsResource::createCollectionsForDirectory( const QDir &parentDirectory, const Collection &parentCollection ) const +{ + Collection::List collections; + + QDir dir( parentDirectory ); + dir.setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable ); + const QFileInfoList entries = dir.entryInfoList(); + + foreach ( const QFileInfo &entry, entries ) { + QDir subdir( entry.absoluteFilePath() ); + + Collection collection; + collection.setParentCollection( parentCollection ); + collection.setRemoteId( entry.fileName() ); + collection.setName( entry.fileName() ); + collection.setContentMimeTypes( mSupportedMimeTypes ); + collection.setRights( supportedRights( false ) ); + + collections << collection; + collections << createCollectionsForDirectory( subdir, collection ); + } + + return collections; +} + +void ContactsResource::retrieveCollections() +{ + // create the resource collection + Collection resourceCollection; + resourceCollection.setParentCollection( Collection::root() ); + resourceCollection.setRemoteId( baseDirectoryPath() ); + resourceCollection.setName( name() ); + resourceCollection.setContentMimeTypes( mSupportedMimeTypes ); + resourceCollection.setRights( supportedRights( true ) ); + + const QDir baseDir( baseDirectoryPath() ); + + Collection::List collections = createCollectionsForDirectory( baseDir, resourceCollection ); + collections.append( resourceCollection ); + + collectionsRetrieved( collections ); +} + +void ContactsResource::retrieveItems( const Akonadi::Collection &collection ) +{ + QDir directory( directoryForCollection( collection ) ); + if ( !directory.exists() ) { + cancelTask( i18n( "Directory '%1' does not exists", collection.remoteId() ) ); + return; + } + + directory.setFilter( QDir::Files | QDir::Readable ); + + Item::List items; + + const QFileInfoList entries = directory.entryInfoList(); + + foreach ( const QFileInfo &entry, entries ) { + if ( entry.fileName() == QLatin1String("WARNING_README.txt") ) + continue; + + Item item; + item.setRemoteId( entry.fileName() ); + + if ( entry.fileName().endsWith( QLatin1String( ".vcf" ) ) ) + item.setMimeType( KABC::Addressee::mimeType() ); + else if ( entry.fileName().endsWith( QLatin1String( ".ctg" ) ) ) + item.setMimeType( KABC::ContactGroup::mimeType() ); + else { + cancelTask( i18n( "Found file of unknown format: '%1'", entry.absoluteFilePath() ) ); + return; + } + + items.append( item ); + } + + itemsRetrieved( items ); +} + +bool ContactsResource::retrieveItem( const Akonadi::Item &item, const QSet& ) +{ + const QString filePath = directoryForCollection( item.parentCollection() ) + QDir::separator() + item.remoteId(); + + Item newItem( item ); + + QFile file( filePath ); + if ( !file.open( QIODevice::ReadOnly ) ) { + cancelTask( i18n( "Unable to open file '%1'", filePath ) ); + return false; + } + + if ( filePath.endsWith( QLatin1String( ".vcf" ) ) ) { + KABC::VCardConverter converter; + + const QByteArray content = file.readAll(); + const KABC::Addressee contact = converter.parseVCard( content ); + if ( contact.isEmpty() ) { + cancelTask( i18n( "Found invalid contact in file '%1'", filePath ) ); + return false; + } + + newItem.setPayload( contact ); + } else if ( filePath.endsWith( QLatin1String( ".ctg" ) ) ) { + KABC::ContactGroup group; + QString errorMessage; + + if ( !KABC::ContactGroupTool::convertFromXml( &file, group, &errorMessage ) ) { + cancelTask( i18n( "Found invalid contact group in file '%1': %2", filePath, errorMessage ) ); + return false; + } + + newItem.setPayload( group ); + } else { + cancelTask( i18n( "Found file of unknown format: '%1'", filePath ) ); + return false; + } + + file.close(); + + itemRetrieved( newItem ); + + return true; +} + +void ContactsResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + if ( mSettings->readOnly() ) { + cancelTask( i18n( "Trying to write to a read-only directory: '%1'", collection.remoteId() ) ); + return; + } + + const QString directoryPath = directoryForCollection( collection ); + + Item newItem( item ); + + if ( item.hasPayload() ) { + const KABC::Addressee contact = item.payload(); + + const QString fileName = directoryPath + QDir::separator() + contact.uid() + QLatin1String(".vcf"); + + KABC::VCardConverter converter; + const QByteArray content = converter.createVCard( contact ); + + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) { + cancelTask( i18n( "Unable to write to file '%1': %2", fileName, file.errorString() ) ); + return; + } + + file.write( content ); + file.close(); + + newItem.setRemoteId( contact.uid() + QLatin1String(".vcf") ); + + } else if ( item.hasPayload() ) { + const KABC::ContactGroup group = item.payload(); + + const QString fileName = directoryPath + QDir::separator() + group.id() + QLatin1String(".ctg"); + + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) { + cancelTask( i18n( "Unable to write to file '%1': %2", fileName, file.errorString() ) ); + return; + } + + KABC::ContactGroupTool::convertToXml( group, &file ); + + file.close(); + + newItem.setRemoteId( group.id() + QLatin1String(".ctg") ); + + } else { + kWarning() << "got item without (usable) payload, ignoring it"; + } + + changeCommitted( newItem ); +} + +void ContactsResource::itemChanged( const Akonadi::Item &item, const QSet& ) +{ + if ( mSettings->readOnly() ) { + cancelTask( i18n( "Trying to write to a read-only file: '%1'", item.remoteId() ) ); + return; + } + + Item newItem( item ); + + const QString fileName = directoryForCollection( item.parentCollection() ) + QDir::separator() + item.remoteId(); + + if ( item.hasPayload() ) { + const KABC::Addressee contact = item.payload(); + + KABC::VCardConverter converter; + const QByteArray content = converter.createVCard( contact ); + + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) { + cancelTask( i18n( "Unable to write to file '%1': %2", fileName, file.errorString() ) ); + return; + } + file.write( content ); + file.close(); + + newItem.setRemoteId( item.remoteId() ); + + } else if ( item.hasPayload() ) { + const KABC::ContactGroup group = item.payload(); + + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) { + cancelTask( i18n( "Unable to write to file '%1': %2", fileName, file.errorString() ) ); + return; + } + + KABC::ContactGroupTool::convertToXml( group, &file ); + + file.close(); + + newItem.setRemoteId( item.remoteId() ); + + } else { + cancelTask( i18n( "Received item with unknown payload %1", item.mimeType() ) ); + return; + } + + changeCommitted( newItem ); +} + +void ContactsResource::itemRemoved( const Akonadi::Item &item ) +{ + if ( mSettings->readOnly() ) { + cancelTask( i18n( "Trying to write to a read-only file: '%1'", item.remoteId() ) ); + return; + } + + // If the parent collection has no valid remote id, the parent + // collection will be removed in a second, so stop here and remove + // all items in collectionRemoved(). + if ( item.parentCollection().remoteId().isEmpty() ) { + changeProcessed(); + return; + } + + const QString fileName = directoryForCollection( item.parentCollection() ) + QDir::separator() + item.remoteId(); + + if ( !QFile::remove( fileName ) ) { + cancelTask( i18n( "Unable to remove file '%1'", fileName ) ); + return; + } + + changeProcessed(); +} + +void ContactsResource::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) +{ + if ( mSettings->readOnly() ) { + cancelTask( i18n( "Trying to write to a read-only directory: '%1'", parent.remoteId() ) ); + return; + } + + const QString dirName = directoryForCollection( parent ) + QDir::separator() + collection.name(); + + if ( !QDir::root().mkpath( dirName ) ) { + cancelTask( i18n( "Unable to create folder '%1'.", dirName ) ); + return; + } + + initializeDirectory( dirName ); + + Collection newCollection( collection ); + newCollection.setRemoteId( collection.name() ); + changeCommitted( newCollection ); +} + +void ContactsResource::collectionChanged( const Akonadi::Collection &collection ) +{ + if ( mSettings->readOnly() ) { + cancelTask( i18n( "Trying to write to a read-only directory: '%1'", collection.remoteId() ) ); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + if ( collection.name() != name() ) + setName( collection.name() ); + changeProcessed(); + return; + } + + if ( collection.remoteId() == collection.name() ) { + changeProcessed(); + return; + } + + const QString dirName = directoryForCollection( collection ); + + QFileInfo oldDirectory( dirName ); + if ( !QDir::root().rename( dirName, oldDirectory.absolutePath() + QDir::separator() + collection.name() ) ) { + cancelTask( i18n( "Unable to rename folder '%1'.", collection.name() ) ); + return; + } + + Collection newCollection( collection ); + newCollection.setRemoteId( collection.name() ); + changeCommitted( newCollection ); +} + +/** + * Removes a @p directory recursively. + */ +static bool removeDirectory( const QDir &directory ) +{ + const QFileInfoList infoList = + directory.entryInfoList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); + + foreach ( const QFileInfo &info, infoList ) { + if ( info.isDir() ) { + if ( !removeDirectory( QDir( info.absoluteFilePath() ) ) ) + return false; + } else { + if ( !QFile::remove( info.filePath() ) ) + return false; + } + } + + if ( !QDir::root().rmdir( directory.absolutePath() ) ) + return false; + + return true; +} + +void ContactsResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + if ( mSettings->readOnly() ) { + cancelTask( i18n( "Trying to write to a read-only directory: '%1'", collection.remoteId() ) ); + return; + } + + if ( !removeDirectory( directoryForCollection( collection ) ) ) { + cancelTask( i18n( "Unable to delete folder '%1'.", collection.name() ) ); + return; + } + + changeProcessed(); +} + +void ContactsResource::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ) +{ + const QString sourceFileName = directoryForCollection( collectionSource ) + QDir::separator() + item.remoteId(); + const QString targetFileName = directoryForCollection( collectionDestination ) + QDir::separator() + item.remoteId(); + + if ( QFile::rename( sourceFileName, targetFileName ) ) + changeProcessed(); + else + cancelTask( i18n( "Unable to move file '%1' to '%2', '%2' already exists.", sourceFileName, targetFileName ) ); +} + +void ContactsResource::collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ) +{ + const QString sourceDirectoryName = directoryForCollection( collectionSource ) + QDir::separator() + collection.remoteId(); + const QString targetDirectoryName = directoryForCollection( collectionDestination ) + QDir::separator() + collection.remoteId(); + + if ( QFile::rename( sourceDirectoryName, targetDirectoryName ) ) + changeProcessed(); + else + cancelTask( i18n( "Unable to move directory '%1' to '%2', '%2' already exists.", sourceDirectoryName, targetDirectoryName ) ); +} + +QString ContactsResource::baseDirectoryPath() const +{ + return mSettings->path(); +} + +void ContactsResource::initializeDirectory( const QString &path ) const +{ + QDir dir( path ); + + // if folder does not exists, create it + if ( !dir.exists() ) + QDir::root().mkpath( dir.absolutePath() ); + + // check whether warning file is in place... + QFile file( dir.absolutePath() + QDir::separator() + QLatin1String("WARNING_README.txt") ); + if ( !file.exists() ) { + // ... if not, create it + file.open( QIODevice::WriteOnly ); + file.write( "Important Warning!!!\n\n" + "Don't create or copy vCards inside this folder manually, they are managed by the Akonadi framework!\n" ); + file.close(); + } +} + +Collection::Rights ContactsResource::supportedRights( bool isResourceCollection ) const +{ + Collection::Rights rights = Collection::ReadOnly; + + if ( !mSettings->readOnly() ) { + rights |= Collection::CanChangeItem; + rights |= Collection::CanCreateItem; + rights |= Collection::CanDeleteItem; + rights |= Collection::CanCreateCollection; + rights |= Collection::CanChangeCollection; + + if ( !isResourceCollection ) + rights |= Collection::CanDeleteCollection; + } + + return rights; +} + +QString ContactsResource::directoryForCollection( const Collection& collection ) const +{ + if ( collection.remoteId().isEmpty() ) { + kWarning() << "Got incomplete ancestor chain:" << collection; + return QString(); + } + + if ( collection.parentCollection() == Collection::root() ) { + kWarning( collection.remoteId() != baseDirectoryPath() ) << "RID mismatch, is " << collection.remoteId() + << " expected " << baseDirectoryPath(); + return collection.remoteId(); + } + + const QString parentDirectory = directoryForCollection( collection.parentCollection() ); + if ( parentDirectory.isNull() ) // invalid, != isEmpty() here! + return QString(); + + QString directory = parentDirectory; + if ( !directory.endsWith( QLatin1Char('/') ) ) + directory += QDir::separator() + collection.remoteId(); + else + directory += collection.remoteId(); + + return directory; +} + +AKONADI_AGENT_FACTORY( ContactsResource, akonadi_contacts_resource ) + diff --git a/kdepim-runtime/resources/contacts/contactsresource.desktop b/kdepim-runtime/resources/contacts/contactsresource.desktop new file mode 100644 index 00000000..a94985ed --- /dev/null +++ b/kdepim-runtime/resources/contacts/contactsresource.desktop @@ -0,0 +1,100 @@ +[Desktop Entry] +Name=Personal Contacts +Name[bg]=Лични контакти +Name[bs]=LiÄni kontakti +Name[ca]=Contactes personals +Name[ca@valencia]=Contactes personals +Name[cs]=Osobní kontakty +Name[da]=Personlige kontakter +Name[de]=Persönliche Kontakte +Name[el]=ΠÏοσωπικές επαφές +Name[en_GB]=Personal Contacts +Name[es]=Contactos personales +Name[et]=Isiklikud kontaktid +Name[fi]=Omat yhteystiedot +Name[fr]=Contacts personnels +Name[ga]=Teagmhálacha Pearsanta +Name[gl]=Contactos Persoais +Name[hu]=Személyes névjegyek +Name[ia]=Contactos personal +Name[it]=Contatti personali +Name[ja]=個人ã®é€£çµ¡å…ˆ +Name[kk]=Ð”ÐµÑ€Ð±ÐµÑ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ñ‚Ð°Ñ€ +Name[km]=ទំនាក់ទំនង​ផ្ទាល់ážáŸ’លួន +Name[ko]=ê°œì¸ ì—°ë½ì²˜ +Name[lt]=Asmeniniai kontaktai +Name[lv]=PersonÄ«gie kontakti +Name[nb]=Personlige kontakter +Name[nds]=Persöönlich Kontakten +Name[nl]=Persoonlijke contacten +Name[nn]=Personlege kontaktar +Name[pa]=ਨਿੱਜੀ ਸੰਪਰਕ +Name[pl]=Kontakty osobiste +Name[pt]=Contactos Pessoais +Name[pt_BR]=Contatos pessoais +Name[ro]=Contacte personale +Name[ru]=Личные контакты +Name[sk]=Osobné kontakty +Name[sl]=Osebni stiki +Name[sr]=Лични контакти +Name[sr@ijekavian]=Лични контакти +Name[sr@ijekavianlatin]=LiÄni kontakti +Name[sr@latin]=LiÄni kontakti +Name[sv]=Personliga kontakter +Name[tr]=KiÅŸisel BaÄŸlantılar +Name[ug]=شەخسىي ئالاقەداشلار +Name[uk]=ОÑобиÑÑ‚Ñ– контакти +Name[x-test]=xxPersonal Contactsxx +Name[zh_CN]=个人è”系人 +Name[zh_TW]=個人è¯çµ¡äºº +Comment=The address book with personal contacts +Comment[bs]=Imenik sa liÄnim kontaktima +Comment[ca]=La llibreta d'adreces amb contactes personals +Comment[ca@valencia]=La llibreta d'adreces amb contactes personals +Comment[da]=Adressebog med personlige kontakter +Comment[de]=Das Adressbuch mit persönlichen Kontakten +Comment[el]=Το βιβλίο διευθÏνσεων με τις Ï€Ïοσωπικές επαφές +Comment[en_GB]=The address book with personal contacts +Comment[es]=La libreta de direcciones con contactos personales +Comment[et]=Isiklikke kontakte sisaldav aadressiraamat +Comment[fi]=Henkilökohtaisten yhteystietojen osoitekirja +Comment[fr]=Le carnet d'adresses avec vos contacts personnels +Comment[gl]=O caderno de enderezos cos contactos persoais +Comment[hu]=A személyes névjegyeket tartalmazó címjegyzék +Comment[ia]=Le adressario con contactos personal +Comment[it]=La rubrica con i contatti personali +Comment[ja]=個人ã®é€£çµ¡å…ˆã‚’å«ã‚€ã‚¢ãƒ‰ãƒ¬ã‚¹å¸³ +Comment[kk]=Ð”ÐµÑ€Ð±ÐµÑ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ñ‚Ð°Ñ€Ñ‹ жазатын адреÑтік кітапша +Comment[km]=សៀវភៅ​អាសយដ្ឋាន​ដែល​មាន​ទំនាក់ទំនង​ផ្ទាល់ážáŸ’លួន +Comment[ko]=ê°œì¸ ì—°ë½ì²˜ê°€ 있는 ì£¼ì†Œë¡ +Comment[lt]=Adresų knygelÄ— su asmeniniais kontaktais +Comment[lv]=PersonÄ«go kontaktu adreÅ¡u grÄmata +Comment[nb]=Adresseboka med personlige kontakter. +Comment[nds]=Dat Adressbook mit persöönlich Kontakten +Comment[nl]=Het adresboek met persoonlijke contacten +Comment[pa]=ਨਿੱਜੀ ਸੰਪਰਕਾਂ ਨਾਲ à¨à¨¡à¨°à©ˆà©±à¨¸ ਬà©à©±à¨• +Comment[pl]=Książka adresowa z osobistymi kontaktami +Comment[pt]=O livro de endereços com os contactos pessoais +Comment[pt_BR]=O livro de endereços com contatos pessoais +Comment[ro]=Cartea de adrese cu contacte personale +Comment[ru]=ÐдреÑÐ½Ð°Ñ ÐºÐ½Ð¸Ð³Ð° Ñ Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ контактами +Comment[sk]=Adresár s osobnými kontaktmi +Comment[sl]=Imenik z osebnimi stiki +Comment[sr]=ÐдреÑар Ñа личним контактима +Comment[sr@ijekavian]=ÐдреÑар Ñа личним контактима +Comment[sr@ijekavianlatin]=Adresar sa liÄnim kontaktima +Comment[sr@latin]=Adresar sa liÄnim kontaktima +Comment[sv]=Adressboken med personliga kontakter +Comment[tr]=KiÅŸisel baÄŸlantıları içeren adres defteri +Comment[uk]=ÐдреÑна книга з оÑобиÑтими запиÑами контактів +Comment[x-test]=xxThe address book with personal contactsxx +Comment[zh_CN]=个人è”系地å€ç°¿ +Comment[zh_TW]=個人è¯çµ¡äººçš„通訊錄 +Type=AkonadiResource +Exec=akonadi_contacts_resource +Icon=text-directory + +X-Akonadi-MimeTypes=text/directory,application/x-vnd.kde.contactgroup +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_contacts_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/contacts/contactsresource.h b/kdepim-runtime/resources/contacts/contactsresource.h new file mode 100644 index 00000000..ce92d6d0 --- /dev/null +++ b/kdepim-runtime/resources/contacts/contactsresource.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef CONTACTSRESOURCE_H +#define CONTACTSRESOURCE_H + +#include + +#include +#include +#include +#include + +namespace Akonadi_Contacts_Resource { +class ContactsResourceSettings; +} +class QDir; + +class ContactsResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + ContactsResource( const QString &id ); + ~ContactsResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + virtual void aboutToQuit(); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &collection ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection ); + // do not hide the other variant, use implementation from base class + // which just forwards to the one above + using Akonadi::AgentBase::ObserverV2::collectionChanged; + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + virtual void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + virtual void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + private: + Akonadi::Collection::List createCollectionsForDirectory( const QDir &parentDirectory, + const Akonadi::Collection &parentCollection ) const; + QString baseDirectoryPath() const; + void initializeDirectory( const QString &path ) const; + Akonadi::Collection::Rights supportedRights( bool isResourceCollection ) const; + QString directoryForCollection( const Akonadi::Collection& collection ) const; + + private: + QStringList mSupportedMimeTypes; + Akonadi_Contacts_Resource::ContactsResourceSettings *mSettings; +}; + +#endif diff --git a/kdepim-runtime/resources/contacts/contactsresource.kcfg b/kdepim-runtime/resources/contacts/contactsresource.kcfg new file mode 100644 index 00000000..d580b90d --- /dev/null +++ b/kdepim-runtime/resources/contacts/contactsresource.kcfg @@ -0,0 +1,21 @@ + + + + + + + $HOME/.local/share/contacts/ + + + + false + + + false + + + diff --git a/kdepim-runtime/resources/contacts/settings.kcfgc b/kdepim-runtime/resources/contacts/settings.kcfgc new file mode 100644 index 00000000..7abd8bee --- /dev/null +++ b/kdepim-runtime/resources/contacts/settings.kcfgc @@ -0,0 +1,8 @@ +File=contactsresource.kcfg +ClassName=ContactsResourceSettings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +GlobalEnums=true +Namespace=Akonadi_Contacts_Resource diff --git a/kdepim-runtime/resources/contacts/settingsdialog.cpp b/kdepim-runtime/resources/contacts/settingsdialog.cpp new file mode 100644 index 00000000..a34e7d27 --- /dev/null +++ b/kdepim-runtime/resources/contacts/settingsdialog.cpp @@ -0,0 +1,100 @@ +/* + Copyright (c) 2009 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. +*/ + +#include "settingsdialog.h" +#include "settings.h" + +#include +#include + +#include + +using namespace Akonadi; +using namespace Akonadi_Contacts_Resource; + +SettingsDialog::SettingsDialog( ContactsResourceSettings *settings, WId windowId ) + : KDialog(), + mSettings( settings ) +{ + ui.setupUi( mainWidget() ); + setWindowIcon( KIcon( QLatin1String("text-directory") ) ); + ui.kcfg_Path->setMode( KFile::LocalOnly | KFile::Directory ); + setButtons( Ok | Cancel ); + + if ( windowId ) + KWindowSystem::setMainWindow( this, windowId ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); + + connect( ui.kcfg_Path, SIGNAL(textChanged(QString)), SLOT(validate()) ); + connect( ui.kcfg_ReadOnly, SIGNAL(toggled(bool)), SLOT(validate()) ); + + QTimer::singleShot( 0, this, SLOT(validate()) ); + + ui.kcfg_Path->setUrl( KUrl( mSettings->path() ) ); + mManager = new KConfigDialogManager( this, mSettings ); + mManager->updateWidgets(); + readConfig(); +} + +SettingsDialog::~SettingsDialog() +{ + writeConfig(); +} + +void SettingsDialog::save() +{ + mManager->updateSettings(); + mSettings->setPath( ui.kcfg_Path->url().toLocalFile() ); + mSettings->writeConfig(); +} + +void SettingsDialog::validate() +{ + const KUrl currentUrl = ui.kcfg_Path->url(); + if ( currentUrl.isEmpty() ) { + enableButton( Ok, false ); + return; + } + + const QFileInfo file( currentUrl.toLocalFile() ); + if ( file.exists() && !file.isWritable() ) { + ui.kcfg_ReadOnly->setEnabled( false ); + ui.kcfg_ReadOnly->setChecked( true ); + } else { + ui.kcfg_ReadOnly->setEnabled( true ); + } + enableButton( Ok, true ); +} + +void SettingsDialog::readConfig() +{ + KConfigGroup group( KGlobal::config(), "SettingsDialog" ); + const QSize size = group.readEntry( "Size", QSize(600, 400) ); + if ( size.isValid() ) { + resize( size ); + } +} + +void SettingsDialog::writeConfig() +{ + KConfigGroup group( KGlobal::config(), "SettingsDialog" ); + group.writeEntry( "Size", size() ); + group.sync(); +} diff --git a/kdepim-runtime/resources/contacts/settingsdialog.h b/kdepim-runtime/resources/contacts/settingsdialog.h new file mode 100644 index 00000000..354edef2 --- /dev/null +++ b/kdepim-runtime/resources/contacts/settingsdialog.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef AKONADI_SETTINGSDIALOG_H +#define AKONADI_SETTINGSDIALOG_H + +#include "ui_settingsdialog.h" + +#include + +namespace Akonadi_Contacts_Resource { +class ContactsResourceSettings; +} +class KConfigDialogManager; + +namespace Akonadi { + +class SettingsDialog : public KDialog +{ + Q_OBJECT + public: + explicit SettingsDialog( Akonadi_Contacts_Resource::ContactsResourceSettings* settings, WId windowId ); + ~SettingsDialog(); + + private Q_SLOTS: + void save(); + void validate(); + + private: + void readConfig(); + void writeConfig(); + Ui::SettingsDialog ui; + KConfigDialogManager* mManager; + Akonadi_Contacts_Resource::ContactsResourceSettings *mSettings; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/contacts/settingsdialog.ui b/kdepim-runtime/resources/contacts/settingsdialog.ui new file mode 100644 index 00000000..e538f9a7 --- /dev/null +++ b/kdepim-runtime/resources/contacts/settingsdialog.ui @@ -0,0 +1,87 @@ + + + SettingsDialog + + + + 0 + 0 + 547 + 386 + + + + + + + Directory Name + + + + + + + + &Directory: + + + kcfg_Path + + + + + + + + + + + + Select the directory whose contents should be represented by this resource. If the directory does not exist, it will be created. + + + true + + + + + + + + + + Access Rights + + + + + + Read only + + + + + + + If read-only mode is enabled, no changes will be written to the directory selected above. Read-only mode will be automatically enabled if you do not have write access to the directory. + + + true + + + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/contacts/wizard/CMakeLists.txt b/kdepim-runtime/resources/contacts/wizard/CMakeLists.txt new file mode 100644 index 00000000..d0dbfb80 --- /dev/null +++ b/kdepim-runtime/resources/contacts/wizard/CMakeLists.txt @@ -0,0 +1,5 @@ + +set(CONTACTS_DIRECTORY_DEFAULT_PATH "$HOME/.local/share/contacts/") + +configure_file(contactswizard.es.cmake ${CMAKE_CURRENT_BINARY_DIR}/contactswizard.es) +install ( FILES contactswizard.desktop ${CMAKE_CURRENT_BINARY_DIR}/contactswizard.es contactswizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/contacts/ ) diff --git a/kdepim-runtime/resources/contacts/wizard/Messages.sh b/kdepim-runtime/resources/contacts/wizard/Messages.sh new file mode 100644 index 00000000..7ce2c32e --- /dev/null +++ b/kdepim-runtime/resources/contacts/wizard/Messages.sh @@ -0,0 +1,6 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_contacts.pot +$XGETTEXT -kqsTr *.es.cmake -j -o $podir/accountwizard_contacts.pot +rm rc.cpp + diff --git a/kdepim-runtime/resources/contacts/wizard/contactswizard.desktop b/kdepim-runtime/resources/contacts/wizard/contactswizard.desktop new file mode 100644 index 00000000..a206f0ca --- /dev/null +++ b/kdepim-runtime/resources/contacts/wizard/contactswizard.desktop @@ -0,0 +1,100 @@ +[Desktop Entry] +Name=Personal Contacts +Name[bg]=Лични контакти +Name[bs]=LiÄni kontakti +Name[ca]=Contactes personals +Name[ca@valencia]=Contactes personals +Name[cs]=Osobní kontakty +Name[da]=Personlige kontakter +Name[de]=Persönliche Kontakte +Name[el]=ΠÏοσωπικές επαφές +Name[en_GB]=Personal Contacts +Name[es]=Contactos personales +Name[et]=Isiklikud kontaktid +Name[fi]=Omat yhteystiedot +Name[fr]=Contacts personnels +Name[ga]=Teagmhálacha Pearsanta +Name[gl]=Contactos Persoais +Name[hu]=Személyes névjegyek +Name[ia]=Contactos personal +Name[it]=Contatti personali +Name[ja]=個人ã®é€£çµ¡å…ˆ +Name[kk]=Ð”ÐµÑ€Ð±ÐµÑ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ñ‚Ð°Ñ€ +Name[km]=ទំនាក់ទំនង​ផ្ទាល់ážáŸ’លួន +Name[ko]=ê°œì¸ ì—°ë½ì²˜ +Name[lt]=Asmeniniai kontaktai +Name[lv]=PersonÄ«gie kontakti +Name[nb]=Personlige kontakter +Name[nds]=Persöönlich Kontakten +Name[nl]=Persoonlijke contacten +Name[nn]=Personlege kontaktar +Name[pa]=ਨਿੱਜੀ ਸੰਪਰਕ +Name[pl]=Kontakty osobiste +Name[pt]=Contactos Pessoais +Name[pt_BR]=Contatos pessoais +Name[ro]=Contacte personale +Name[ru]=Личные контакты +Name[sk]=Osobné kontakty +Name[sl]=Osebni stiki +Name[sr]=Лични контакти +Name[sr@ijekavian]=Лични контакти +Name[sr@ijekavianlatin]=LiÄni kontakti +Name[sr@latin]=LiÄni kontakti +Name[sv]=Personliga kontakter +Name[tr]=KiÅŸisel BaÄŸlantılar +Name[ug]=شەخسىي ئالاقەداشلار +Name[uk]=ОÑобиÑÑ‚Ñ– контакти +Name[x-test]=xxPersonal Contactsxx +Name[zh_CN]=个人è”系人 +Name[zh_TW]=個人è¯çµ¡äºº +Comment=The address book with personal contacts +Comment[bs]=Imenik sa liÄnim kontaktima +Comment[ca]=La llibreta d'adreces amb contactes personals +Comment[ca@valencia]=La llibreta d'adreces amb contactes personals +Comment[da]=Adressebog med personlige kontakter +Comment[de]=Das Adressbuch mit persönlichen Kontakten +Comment[el]=Το βιβλίο διευθÏνσεων με τις Ï€Ïοσωπικές επαφές +Comment[en_GB]=The address book with personal contacts +Comment[es]=La libreta de direcciones con contactos personales +Comment[et]=Isiklikke kontakte sisaldav aadressiraamat +Comment[fi]=Henkilökohtaisten yhteystietojen osoitekirja +Comment[fr]=Le carnet d'adresses avec vos contacts personnels +Comment[gl]=O caderno de enderezos cos contactos persoais +Comment[hu]=A személyes névjegyeket tartalmazó címjegyzék +Comment[ia]=Le adressario con contactos personal +Comment[it]=La rubrica con i contatti personali +Comment[ja]=個人ã®é€£çµ¡å…ˆã‚’å«ã‚€ã‚¢ãƒ‰ãƒ¬ã‚¹å¸³ +Comment[kk]=Ð”ÐµÑ€Ð±ÐµÑ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ñ‚Ð°Ñ€Ñ‹ жазатын адреÑтік кітапша +Comment[km]=សៀវភៅ​អាសយដ្ឋាន​ដែល​មាន​ទំនាក់ទំនង​ផ្ទាល់ážáŸ’លួន +Comment[ko]=ê°œì¸ ì—°ë½ì²˜ê°€ 있는 ì£¼ì†Œë¡ +Comment[lt]=Adresų knygelÄ— su asmeniniais kontaktais +Comment[lv]=PersonÄ«go kontaktu adreÅ¡u grÄmata +Comment[nb]=Adresseboka med personlige kontakter. +Comment[nds]=Dat Adressbook mit persöönlich Kontakten +Comment[nl]=Het adresboek met persoonlijke contacten +Comment[pa]=ਨਿੱਜੀ ਸੰਪਰਕਾਂ ਨਾਲ à¨à¨¡à¨°à©ˆà©±à¨¸ ਬà©à©±à¨• +Comment[pl]=Książka adresowa z osobistymi kontaktami +Comment[pt]=O livro de endereços com os contactos pessoais +Comment[pt_BR]=O livro de endereços com contatos pessoais +Comment[ro]=Cartea de adrese cu contacte personale +Comment[ru]=ÐдреÑÐ½Ð°Ñ ÐºÐ½Ð¸Ð³Ð° Ñ Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ контактами +Comment[sk]=Adresár s osobnými kontaktmi +Comment[sl]=Imenik z osebnimi stiki +Comment[sr]=ÐдреÑар Ñа личним контактима +Comment[sr@ijekavian]=ÐдреÑар Ñа личним контактима +Comment[sr@ijekavianlatin]=Adresar sa liÄnim kontaktima +Comment[sr@latin]=Adresar sa liÄnim kontaktima +Comment[sv]=Adressboken med personliga kontakter +Comment[tr]=KiÅŸisel baÄŸlantıları içeren adres defteri +Comment[uk]=ÐдреÑна книга з оÑобиÑтими запиÑами контактів +Comment[x-test]=xxThe address book with personal contactsxx +Comment[zh_CN]=个人è”系地å€ç°¿ +Comment[zh_TW]=個人è¯çµ¡äººçš„通訊錄 +Icon=text-directory + +[Wizard] +Type=text/directory,application/x-vnd.kde.contactgroup +Script=contactswizard.es + +[Translate] +Filename=accountwizard_contacts diff --git a/kdepim-runtime/resources/contacts/wizard/contactswizard.es.cmake b/kdepim-runtime/resources/contacts/wizard/contactswizard.es.cmake new file mode 100644 index 00000000..3121873d --- /dev/null +++ b/kdepim-runtime/resources/contacts/wizard/contactswizard.es.cmake @@ -0,0 +1,44 @@ +/* + Copyright (c) 2010 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. +*/ + +var page = Dialog.addPage( "contactswizard.ui", qsTr("Settings") ); + +page.widget().lineEdit.text = "${CONTACTS_DIRECTORY_DEFAULT_PATH}"; + +function validateInput() +{ + if ( page.widget().lineEdit.text == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var contactsResource = SetupManager.createResource( "akonadi_contacts_resource" ); + contactsResource.setOption( "Path", page.widget().lineEdit.text ); + contactsResource.setOption( "IsConfigured", "true" ); + contactsResource.setName( qsTr("Local Contacts") ); + SetupManager.execute(); +} + +page.widget().lineEdit.textChanged.connect( validateInput ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/contacts/wizard/contactswizard.ui b/kdepim-runtime/resources/contacts/wizard/contactswizard.ui new file mode 100644 index 00000000..b4fd4a29 --- /dev/null +++ b/kdepim-runtime/resources/contacts/wizard/contactswizard.ui @@ -0,0 +1,52 @@ + + + contactsWizard + + + + 0 + 0 + 400 + 300 + + + + + + + + + Filename: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/dav/CMakeLists.txt b/kdepim-runtime/resources/dav/CMakeLists.txt new file mode 100644 index 00000000..7590124d --- /dev/null +++ b/kdepim-runtime/resources/dav/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( resource ) diff --git a/kdepim-runtime/resources/dav/COPYING b/kdepim-runtime/resources/dav/COPYING new file mode 100644 index 00000000..dcfa4c23 --- /dev/null +++ b/kdepim-runtime/resources/dav/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kdepim-runtime/resources/dav/README b/kdepim-runtime/resources/dav/README new file mode 100644 index 00000000..8809f35d --- /dev/null +++ b/kdepim-runtime/resources/dav/README @@ -0,0 +1,50 @@ +==== What's this ? ==== + +This is an Akonadi resource to access DAV-enabled PIM storages. + +Calendars and todos are supported, using either GroupDAV +or CalDAV, and contacts are supported using GroupDAV or +CardDAV. + + +==== Usage ==== + +It should be pretty straightforward. The URL to use should be, if possible, +the principals URL for your server (CalDAV / CardDAV) or the parent +collection of your calendars to allow discovery. + + +==== Tested with / known bugs ==== + +Here is a list of servers tested with this resource with the URLs used. +Feel free to contact the author(s) if you successfully tested a configuration +not listed here. + +* Egroupware (1.6.002) + https://host/groupdav.php + - GroupDAV working. + - CalDAV working. + - CardDAV working. + +* SOGo (version 1.0.4, 1.1.0 and version at http://sogo-demo.inverse.ca/) + https://host/SOGo/dav//Calendar and https://host/SOGo/dav//Contacts + - GroupDAV calendar working, but the timezone data in the ICalendar + generated by KCal seems misinterpreted by SOGo : every event is + shifted by the timezone offset (at least test with TZ Europe/Paris, + feel free to send your results to the author(s). This seems resolved + with the demo version made available by Inverse.ca. + - CalDAV working, with the same bug. + - CardDAV working. + +* Davical (version 0.9.7.6) + https://host/caldav.php/principals/users/ + - CalDAV working. + +* Zimbra Open-Source edition (version 5.0.18), + https://host/principals/users/ + - Caldav mostly working : retrieval of shared calendars that contain a lot + of events fails with a 500 server error. + +* Google calendar + https://www.google.com/calendar/dav//events + - CalDAV working. diff --git a/kdepim-runtime/resources/dav/TODO b/kdepim-runtime/resources/dav/TODO new file mode 100644 index 00000000..51e20515 --- /dev/null +++ b/kdepim-runtime/resources/dav/TODO @@ -0,0 +1,2 @@ +- see how to get 401 responses and ask the user the new password (and update + KWallet). diff --git a/kdepim-runtime/resources/dav/common/davcollection.cpp b/kdepim-runtime/resources/dav/common/davcollection.cpp new file mode 100644 index 00000000..edb9805b --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollection.cpp @@ -0,0 +1,89 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davcollection.h" + +DavCollection::DavCollection() +{ +} + +DavCollection::DavCollection( DavUtils::Protocol protocol, const QString &url, const QString &displayName, ContentTypes contentTypes ) + : mProtocol( protocol ), mUrl( url ), mDisplayName( displayName ), mContentTypes( contentTypes ), mPrivileges( DavUtils::All ) +{ +} + +void DavCollection::setProtocol( DavUtils::Protocol protocol ) +{ + mProtocol = protocol; +} + +DavUtils::Protocol DavCollection::protocol() const +{ + return mProtocol; +} + +void DavCollection::setUrl( const QString &url ) +{ + mUrl = url; +} + +QString DavCollection::url() const +{ + return mUrl; +} + +void DavCollection::setDisplayName( const QString &displayName ) +{ + mDisplayName = displayName; +} + +QString DavCollection::displayName() const +{ + return mDisplayName; +} + +void DavCollection::setColor( const QColor &color ) +{ + mColor = color; +} + +QColor DavCollection::color() const +{ + return mColor; +} + +void DavCollection::setContentTypes( ContentTypes contentTypes ) +{ + mContentTypes = contentTypes; +} + +DavCollection::ContentTypes DavCollection::contentTypes() const +{ + return mContentTypes; +} + +void DavCollection::setPrivileges( DavUtils::Privileges privs ) +{ + mPrivileges = privs; +} + +DavUtils::Privileges DavCollection::privileges() const +{ + return mPrivileges; +} + diff --git a/kdepim-runtime/resources/dav/common/davcollection.h b/kdepim-runtime/resources/dav/common/davcollection.h new file mode 100644 index 00000000..9183745c --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollection.h @@ -0,0 +1,142 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVCOLLECTION_H +#define DAVCOLLECTION_H + +#include "davutils.h" + +#include +#include +#include + +/** + * @short A helper class to store information about DAV collection. + * + * This class is used as container to transfer information about DAV + * collections between the Akonadi resource and the DAV jobs. + */ +class DavCollection +{ + public: + /** + * Defines a list of DAV collection objects. + */ + typedef QList List; + + /** + * Describes the possible content type of the DAV collection. + */ + enum ContentType { + Events = 1, ///< The collection can contain event DAV resources. + Todos = 2, ///< The collection can contain todo DAV resources. + Contacts = 4, ///< The collection can contain contact DAV resources. + FreeBusy = 8, ///< The collection can contain free/busy information. + Journal = 16, ///< The collection can contain journal DAV resources. + Calendar = 32 ///< The collection can contain anything calendar-related. + }; + Q_DECLARE_FLAGS( ContentTypes, ContentType ) + + /** + * Creates an empty DAV collection. + */ + DavCollection(); + + /** + * Creates a new DAV collection. + * + * @param protocol The DAV protocol dialect the collection comes from. + * @param url The url that identifies the collection. + * @param displayName The display name of the collection. + * @param contentTypes The possible content types of the collection. + */ + DavCollection( DavUtils::Protocol protocol, const QString &url, const QString &displayName, ContentTypes contentTypes ); + + /** + * Sets the DAV @p protocol dialect the collection comes from. + */ + void setProtocol( DavUtils::Protocol protocol ); + + /** + * Returns the DAV protocol dialect the collection comes from. + */ + DavUtils::Protocol protocol() const; + + /** + * Sets the @p url that identifies the collection. + */ + void setUrl( const QString &url ); + + /** + * Returns the url that identifies the collection. + */ + QString url() const; + + /** + * Sets the display @p name of the collection. + */ + void setDisplayName( const QString &name ); + + /** + * Returns the display name of the collection. + */ + QString displayName() const; + + /** + * Sets the color for this collection + */ + void setColor( const QColor &color ); + + /** + * Return the color of the collection, or an empty string if + * none was provided by the backend. + */ + QColor color() const; + + /** + * Sets the possible content @p types of the collection. + */ + void setContentTypes( ContentTypes types ); + + /** + * Returns the possible content types of the collection. + */ + ContentTypes contentTypes() const; + + /** + * Sets the privileges on this collection. + */ + void setPrivileges( DavUtils::Privileges privs ); + + /** + * Returns the privileges on this collection. + */ + DavUtils::Privileges privileges() const; + + private: + DavUtils::Protocol mProtocol; + QString mUrl; + QString mDisplayName; + QColor mColor; + ContentTypes mContentTypes; + DavUtils::Privileges mPrivileges; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( DavCollection::ContentTypes ) + +#endif diff --git a/kdepim-runtime/resources/dav/common/davcollectiondeletejob.cpp b/kdepim-runtime/resources/dav/common/davcollectiondeletejob.cpp new file mode 100644 index 00000000..13da6497 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectiondeletejob.cpp @@ -0,0 +1,61 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davcollectiondeletejob.h" + +#include +#include + +DavCollectionDeleteJob::DavCollectionDeleteJob(const DavUtils::DavUrl &url, QObject *parent ) + : KJob( parent ), mUrl( url ) +{ +} + +void DavCollectionDeleteJob::start() +{ + KIO::DeleteJob *job = KIO::del( mUrl.url(), KIO::HideProgressInfo | KIO::DefaultFlags ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); +} + +void DavCollectionDeleteJob::davJobFinished( KJob *job ) +{ + KIO::DeleteJob *deleteJob = qobject_cast( job ); + + if ( deleteJob->error() && deleteJob->error() != KIO::ERR_NO_CONTENT ) { + const int responseCode = deleteJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + deleteJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + QString err; + if ( deleteJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( deleteJob->error(), deleteJob->errorText() ); + else + err = deleteJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request. The collection has not been deleted from the server.\n" + "%1 (%2).", err, responseCode ) ); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davcollectiondeletejob.h b/kdepim-runtime/resources/dav/common/davcollectiondeletejob.h new file mode 100644 index 00000000..352fbf2b --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectiondeletejob.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVCOLLECTIONDELETEJOB_H +#define DAVCOLLECTIONDELETEJOB_H + +#include "davutils.h" + +#include + +/** + * @short A job that deletes a DAV collection. + * + * This job is used to delete a DAV collection at a certain URL. + */ +class DavCollectionDeleteJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new DAV collection delete job. + * + * @param url The dav url of the collection to delete + * @param parent The parent object. + */ + explicit DavCollectionDeleteJob( const DavUtils::DavUrl &url, QObject *parent = 0 ); + + /** + * Starts the job. + */ + void start(); + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + DavUtils::DavUrl mUrl; +}; + +#endif + + + diff --git a/kdepim-runtime/resources/dav/common/davcollectionmodifyjob.cpp b/kdepim-runtime/resources/dav/common/davcollectionmodifyjob.cpp new file mode 100644 index 00000000..8b810fe3 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectionmodifyjob.cpp @@ -0,0 +1,158 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davcollectionmodifyjob.h" +#include "davmanager.h" +#include "davutils.h" + +#include +#include +#include + +DavCollectionModifyJob::DavCollectionModifyJob( const DavUtils::DavUrl &url, QObject *parent ) + : KJob( parent ), mUrl( url ) +{ +} + +void DavCollectionModifyJob::setProperty( const QString &prop, const QString &value, const QString &ns ) +{ + QDomElement propElement; + + if ( ns.isEmpty() ) + propElement = mQuery.createElement( prop ); + else + propElement = mQuery.createElementNS( prop, ns ); + + const QDomText textElement = mQuery.createTextNode( value ); + propElement.appendChild( textElement ); + + mSetProperties << propElement; +} + +void DavCollectionModifyJob::removeProperty( const QString &prop, const QString &ns ) +{ + QDomElement propElement; + + if ( ns.isEmpty() ) + propElement = mQuery.createElement( prop ); + else + propElement = mQuery.createElementNS( prop, ns ); + + mRemoveProperties << propElement; +} + +void DavCollectionModifyJob::start() +{ + if ( mSetProperties.isEmpty() && mRemoveProperties.isEmpty() ) { + setError( UserDefinedError ); // no special meaning, for now at least + setErrorText( i18n( "No properties to change or remove" ) ); + emitResult(); + return; + } + + QDomDocument mQuery; + QDomElement propertyUpdateElement = mQuery.createElementNS( QLatin1String("DAV:"), QLatin1String("propertyupdate") ); + mQuery.appendChild( propertyUpdateElement ); + + if ( !mSetProperties.isEmpty() ) { + QDomElement setElement = mQuery.createElementNS( QLatin1String("DAV:"), QLatin1String("set") ); + propertyUpdateElement.appendChild( setElement ); + + QDomElement propElement = mQuery.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + setElement.appendChild( propElement ); + + foreach ( const QDomElement &element, mSetProperties ) + propElement.appendChild( element ); + } + + if ( !mRemoveProperties.isEmpty() ) { + QDomElement removeElement = mQuery.createElementNS( QLatin1String("DAV:"), QLatin1String("remove") ); + propertyUpdateElement.appendChild( removeElement ); + + QDomElement propElement = mQuery.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + removeElement.appendChild( propElement ); + + foreach ( const QDomElement &element, mSetProperties ) + propElement.appendChild( element ); + } + + KIO::DavJob *job = DavManager::self()->createPropPatchJob( mUrl.url(), mQuery ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +void DavCollectionModifyJob::davJobFinished( KJob *job ) +{ + KIO::DavJob *davJob = qobject_cast( job ); + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx + if ( davJob->error() || ( responseCode >= 400 && responseCode < 600 ) ) { + QString err; + if ( davJob->error() && davJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( davJob->error(), davJob->errorText() ); + else + err = davJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request. The item has not been modified on the server.\n" + "%1 (%2).", err, responseCode ) ); + + emitResult(); + return; + } + + const QDomDocument response = davJob->response(); + QDomElement responseElement = DavUtils::firstChildElementNS( response.documentElement(), QLatin1String("DAV:"), QLatin1String("response") ); + + bool hasError = false; + QString errorText; + + // parse all propstats answers to get the eventual errors + const QDomNodeList propstats = responseElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("propstat") ); + for ( uint i = 0; i < propstats.length(); ++i ) { + const QDomElement propstatElement = propstats.item( i ).toElement(); + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("status") ); + + const QString statusText = statusElement.text(); + if ( statusText.contains( QLatin1String("200") ) ) { + // Nothing special to do here, this indicates the success of the whole request + break; + } else { + // Generic error + hasError = true; + errorText = i18n( "There was an error when modifying the properties" ); + } + } + + if ( hasError ) { + // Trying to get more information about the error + const QDomElement responseDescriptionElement = DavUtils::firstChildElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("responsedescription") ); + if ( !responseDescriptionElement.isNull() ) { + errorText.append( i18n( "\nThe server returned more information:\n" ) ); + errorText.append( responseDescriptionElement.text() ); + } + + setError( UserDefinedError ); + setErrorText( errorText ); + } + + emitResult(); +} diff --git a/kdepim-runtime/resources/dav/common/davcollectionmodifyjob.h b/kdepim-runtime/resources/dav/common/davcollectionmodifyjob.h new file mode 100644 index 00000000..e5a2b20a --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectionmodifyjob.h @@ -0,0 +1,80 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVCOLLECTIONMODIFYJOB_H +#define DAVCOLLECTIONMODIFYJOB_H + +#include "davutils.h" + +#include + +#include + +/** + * @short A job that modifies a DAV collection. + * + * This job is used to modify a property of a DAV collection + * on the DAV server. + */ +class DavCollectionModifyJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new dav collection modify job. + * + * @param url The DAV url that identifies the collection. + * @param parent The parent object. + */ + explicit DavCollectionModifyJob( const DavUtils::DavUrl &url, QObject *parent = 0 ); + + /** + * Sets the property that shall be modified by the job. + * + * @param property The name of the property. + * @param value The value of the property. + * @param ns The XML namespace that shall be used for the property name. + */ + void setProperty( const QString &property, const QString &value, const QString &ns = QString() ); + + /** + * Sets the property that shall be removed by the job. + * + * @param property The name of the property. + * @param ns The XML namespace that shall be used for the property name. + */ + void removeProperty( const QString &property, const QString &ns ); + + /** + * Starts the job. + */ + virtual void start(); + + private Q_SLOTS: + void davJobFinished( KJob *job ); + + private: + DavUtils::DavUrl mUrl; + QDomDocument mQuery; + + QList mSetProperties; + QList mRemoveProperties; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davcollectionsfetchjob.cpp b/kdepim-runtime/resources/dav/common/davcollectionsfetchjob.cpp new file mode 100644 index 00000000..9c63a5b9 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectionsfetchjob.cpp @@ -0,0 +1,307 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davcollectionsfetchjob.h" + +#include "davmanager.h" +#include "davprincipalhomesetsfetchjob.h" +#include "davprotocolbase.h" +#include "davutils.h" + +#include +#include +#include + +#include +#include + +DavCollectionsFetchJob::DavCollectionsFetchJob( const DavUtils::DavUrl &url, QObject *parent ) + : KJob( parent ), mUrl( url ), mSubJobCount( 0 ) +{ +} + +void DavCollectionsFetchJob::start() +{ + if ( DavManager::self()->davProtocol( mUrl.protocol() )->supportsPrincipals() ) { + DavPrincipalHomeSetsFetchJob *job = new DavPrincipalHomeSetsFetchJob( mUrl ); + connect( job, SIGNAL(result(KJob*)), SLOT(principalFetchFinished(KJob*)) ); + job->start(); + } else { + doCollectionsFetch( mUrl.url() ); + } +} + +DavCollection::List DavCollectionsFetchJob::collections() const +{ + return mCollections; +} + +DavUtils::DavUrl DavCollectionsFetchJob::davUrl() const +{ + return mUrl; +} + +void DavCollectionsFetchJob::doCollectionsFetch( const KUrl &url ) +{ + ++mSubJobCount; + + const QDomDocument collectionQuery = DavManager::self()->davProtocol( mUrl.protocol() )->collectionsQuery(); + + KIO::DavJob *job = DavManager::self()->createPropFindJob( url, collectionQuery ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionsFetchFinished(KJob*)) ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); +} + +void DavCollectionsFetchJob::principalFetchFinished( KJob *job ) +{ + if ( job->error() ) { + // This may mean that the URL was not a principal URL. + // Retry as if it were a calendar URL. + kDebug() << job->errorText(); + doCollectionsFetch( mUrl.url() ); + return; + } + + const DavPrincipalHomeSetsFetchJob *davJob = qobject_cast( job ); + + const QStringList homeSets = davJob->homeSets(); + kDebug() << "Found " << homeSets.size() << " homesets"; + kDebug() << homeSets; + + if ( homeSets.isEmpty() ) { + // Same as above, retry as if it were a calendar URL. + doCollectionsFetch( mUrl.url() ); + return; + } + + foreach ( const QString &homeSet, homeSets ) { + KUrl url = mUrl.url(); + + if ( homeSet.startsWith( QLatin1Char('/') ) ) { + // homeSet is only a path, use request url to complete + url.setEncodedPath( homeSet.toLatin1() ); + } else { + // homeSet is a complete url + KUrl tmpUrl( homeSet ); + tmpUrl.setUser( url.user() ); + tmpUrl.setPass( url.pass() ); + url = tmpUrl; + } + + doCollectionsFetch( url ); + } +} + +void DavCollectionsFetchJob::collectionsFetchFinished( KJob *job ) +{ + KIO::DavJob *davJob = qobject_cast( job ); + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx + if ( davJob->error() || ( responseCode >= 400 && responseCode < 600 ) ) { + if ( davJob->url() != mUrl.url() ) { + // Retry as if the initial URL was a calendar URL. + // We can end up here when retrieving a homeset on + // which a PROPFIND resulted in an error + doCollectionsFetch( mUrl.url() ); + --mSubJobCount; + return; + } + + QString err; + if ( davJob->error() && davJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( davJob->error(), davJob->errorText() ); + else + err = davJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", err, responseCode ) ); + } + else { + // For use in the collectionDiscovered() signal + KUrl _jobUrl = mUrl.url(); + _jobUrl.setUser( QString() ); + const QString jobUrl = _jobUrl.prettyUrl(); + + //kDebug() << davJob->response().toString(); + + QByteArray resp( davJob->response().toByteArray() ); + QBuffer buffer( &resp ); + buffer.open( QIODevice::ReadOnly ); + + QXmlQuery xquery; + if ( !xquery.setFocus( &buffer ) ) { + setError( UserDefinedError ); + setErrorText( i18n( "Error setting focus for XQuery" ) ); + } + + xquery.setQuery( DavManager::self()->davProtocol( mUrl.protocol() )->collectionsXQuery() ); + if ( !xquery.isValid() ) { + setError( UserDefinedError ); + setErrorText( i18n( "Invalid XQuery submitted by DAV implementation" ) ); + } + + QString responsesStr; + xquery.evaluateTo( &responsesStr ); + responsesStr.prepend( QLatin1String("") ); + responsesStr.append( QLatin1String("") ); + + QDomDocument document; + if ( !document.setContent( responsesStr, true ) ) { + setError( UserDefinedError ); + setErrorText( i18n( "Invalid responses from backend" ) ); + } + + if ( !error() ) { + /* + * Extract information from a document like the following: + * + * + * + * /caldav.php/test1.user/home/ + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Test1 User + * + * + * + * + * + * + * HTTP/1.1 200 OK + * + * + * + */ + + const QDomElement responsesElement = document.documentElement(); + + QDomElement responseElement = DavUtils::firstChildElementNS( responsesElement, QLatin1String("DAV:"), QLatin1String("response") ); + while ( !responseElement.isNull() ) { + + QDomElement propstatElement; + + // check for the valid propstat, without giving up on first error + { + const QDomNodeList propstats = responseElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("propstat") ); + for ( uint i = 0; i < propstats.length(); ++i ) { + const QDomElement propstatCandidate = propstats.item( i ).toElement(); + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatCandidate, QLatin1String("DAV:"), QLatin1String("status") ); + if ( statusElement.text().contains( QLatin1String("200") ) ) { + propstatElement = propstatCandidate; + } + } + } + + if ( propstatElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + // extract url + const QDomElement hrefElement = DavUtils::firstChildElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("href") ); + if ( hrefElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + QString href = hrefElement.text(); + if ( !href.endsWith( QLatin1Char('/') ) ) + href.append( QLatin1Char('/') ); + + KUrl url = davJob->url(); + url.setUser( QString() ); + if ( href.startsWith( QLatin1Char('/') ) ) { + // href is only a path, use request url to complete + url.setEncodedPath( href.toLatin1() ); + } else { + // href is a complete url + KUrl tmpUrl( href ); + url = tmpUrl; + } + + // don't add this resource if it has already been detected + bool alreadySeen = false; + foreach ( const DavCollection &seen, mCollections ) { + if ( seen.url() == url.prettyUrl() ) + alreadySeen = true; + } + if ( alreadySeen ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + // extract display name + const QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + const QDomElement displaynameElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("displayname") ); + const QString displayName = displaynameElement.text(); + + // extract calendar color if provided + const QDomElement colorElement = DavUtils::firstChildElementNS( propElement, QLatin1String("http://apple.com/ns/ical/"), QLatin1String("calendar-color") ); + QColor color; + if ( !colorElement.isNull() ) { + QString colorValue = colorElement.text(); + if ( QColor::isValidColor( colorValue ) ) + color.setNamedColor( colorValue ); + } + + // extract allowed content types + const DavCollection::ContentTypes contentTypes = DavManager::self()->davProtocol( mUrl.protocol() )->collectionContentTypes( propstatElement ); + + DavCollection collection( mUrl.protocol(), url.prettyUrl(), displayName, contentTypes ); + if ( color.isValid() ) + collection.setColor( color ); + + // extract privileges + const QDomElement currentPrivsElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("current-user-privilege-set") ); + if ( currentPrivsElement.isNull() ) { + // Assume that we have all privileges + collection.setPrivileges( DavUtils::All ); + } else { + DavUtils::Privileges privileges = DavUtils::extractPrivileges( currentPrivsElement ); + collection.setPrivileges( privileges ); + } + + kDebug() << url.prettyUrl() << "PRIVS: " << collection.privileges(); + mCollections << collection; + emit collectionDiscovered( mUrl.protocol(), url.prettyUrl(), jobUrl ); + + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + } + } + } + + if ( --mSubJobCount == 0 ) + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davcollectionsfetchjob.h b/kdepim-runtime/resources/dav/common/davcollectionsfetchjob.h new file mode 100644 index 00000000..60d413b4 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectionsfetchjob.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVCOLLECTIONSFETCHJOB_H +#define DAVCOLLECTIONSFETCHJOB_H + +#include "davcollection.h" +#include "davutils.h" + +#include + +/** + * @short A job that fetches all DAV collection. + * + * This job is used to fetch all DAV collection that are available + * under a certain DAV url. + */ +class DavCollectionsFetchJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new dav collections fetch job. + * + * @param url The DAV url of the DAV collection whose sub collections shall be fetched. + * @param parent The parent object. + */ + explicit DavCollectionsFetchJob( const DavUtils::DavUrl &url, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of fetched DAV collections. + */ + DavCollection::List collections() const; + + /** + * Return the DavUrl used by this job + */ + DavUtils::DavUrl davUrl() const; + + Q_SIGNALS: + /** + * This signal is emitted every time a new collection has been discovered. + * + * @param collectionUrl The URL of the discovered collection + * @param configuredUrl The URL given to the job + */ + void collectionDiscovered( int protocol, const QString &collectionUrl, const QString &configuredUrl ); + + private Q_SLOTS: + void principalFetchFinished( KJob* ); + void collectionsFetchFinished( KJob* ); + + private: + void doCollectionsFetch( const KUrl &url ); + + DavUtils::DavUrl mUrl; + DavCollection::List mCollections; + uint mSubJobCount; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.cpp b/kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.cpp new file mode 100644 index 00000000..560ba7b3 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.cpp @@ -0,0 +1,62 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davcollectionsmultifetchjob.h" + +#include "davcollectionsfetchjob.h" + +DavCollectionsMultiFetchJob::DavCollectionsMultiFetchJob( const DavUtils::DavUrl::List &urls, QObject *parent ) + : KJob( parent ), mUrls( urls ), mSubJobCount( urls.size() ) +{ +} + +void DavCollectionsMultiFetchJob::start() +{ + if ( mUrls.isEmpty() ) + emitResult(); + + foreach ( const DavUtils::DavUrl &url, mUrls ) { + DavCollectionsFetchJob *job = new DavCollectionsFetchJob( url, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); + connect( job, SIGNAL(collectionDiscovered(int,QString,QString)), + SIGNAL(collectionDiscovered(int,QString,QString)) ); + job->start(); + } +} + +DavCollection::List DavCollectionsMultiFetchJob::collections() const +{ + return mCollections; +} + +void DavCollectionsMultiFetchJob::davJobFinished( KJob *job ) +{ + DavCollectionsFetchJob *fetchJob = qobject_cast( job ); + + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + } + else { + mCollections << fetchJob->collections(); + } + + if ( --mSubJobCount == 0 ) + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.h b/kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.h new file mode 100644 index 00000000..8d4f14b5 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davcollectionsmultifetchjob.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVCOLLECTIONSMULTIFETCHJOB_H +#define DAVCOLLECTIONSMULTIFETCHJOB_H + +#include "davcollection.h" +#include "davutils.h" + +#include + +/** + * @short A job that fetches all DAV collection. + * + * This job is used to fetch all DAV collection that are available + * under a certain list of DAV urls. + * + * @note This class just combines multiple calls of DavCollectionsFetchJob + * into one job. + */ +class DavCollectionsMultiFetchJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new dav collections multi fetch job. + * + * @param urls The list of DAV urls whose sub collections shall be fetched. + * @param parent The parent object. + */ + explicit DavCollectionsMultiFetchJob( const DavUtils::DavUrl::List &urls, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of fetched DAV collections. + */ + DavCollection::List collections() const; + + Q_SIGNALS: + /** + * This signal is emitted every time a new collection has been discovered. + * + * @param collectionUrl The URL of the discovered collection + * @param configuredUrl The URL given to the job + */ + void collectionDiscovered( int protocol, const QString &collectionUrl, const QString &configuredUrl ); + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + DavUtils::DavUrl::List mUrls; + DavCollection::List mCollections; + uint mSubJobCount; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitem.cpp b/kdepim-runtime/resources/dav/common/davitem.cpp new file mode 100644 index 00000000..183118a7 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitem.cpp @@ -0,0 +1,93 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitem.h" + +DavItem::DavItem() +{ +} + +DavItem::DavItem( const QString &url, const QString &contentType, const QByteArray &data, const QString &etag ) + : mUrl( url ), mContentType( contentType ), mData( data ), mEtag( etag ) +{ +} + +void DavItem::setUrl( const QString &url ) +{ + mUrl = url; +} + +QString DavItem::url() const +{ + return mUrl; +} + +void DavItem::setContentType( const QString &contentType ) +{ + mContentType = contentType; +} + +QString DavItem::contentType() const +{ + return mContentType; +} + +void DavItem::setData( const QByteArray &data ) +{ + mData = data; +} + +QByteArray DavItem::data() const +{ + return mData; +} + +void DavItem::setEtag( const QString &etag ) +{ + mEtag = etag; +} + +QString DavItem::etag() const +{ + return mEtag; +} + +QDataStream& operator<<( QDataStream &stream, const DavItem &item ) +{ + stream << item.url(); + stream << item.contentType(); + stream << item.data(); + stream << item.etag(); + + return stream; +} + +QDataStream& operator>>( QDataStream &stream, DavItem &item ) +{ + QString url, contentType, etag; + QByteArray data; + + stream >> url; + stream >> contentType; + stream >> data; + stream >> etag; + + item = DavItem( url, contentType, data, etag ); + + return stream; +} diff --git a/kdepim-runtime/resources/dav/common/davitem.h b/kdepim-runtime/resources/dav/common/davitem.h new file mode 100644 index 00000000..77b5d425 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitem.h @@ -0,0 +1,109 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEM_H +#define DAVITEM_H + +#include +#include +#include +#include + +/** + * @short A helper class to store information about DAV resources. + * + * This class is used as container to transfer information about DAV + * resources between the Akonadi resource and the DAV jobs. + * + * @note While the DAV RFC names them DAV resource we call them items + * to comply to Akonadi terminology. + */ +class DavItem +{ + public: + /** + * Defines a list of DAV item objects. + */ + typedef QList List; + + /** + * Creates an empty DAV item. + */ + DavItem(); + + /** + * Creates a new DAV item. + * + * @param url The url that identifies the item. + * @param contentType The content type of the item. + * @param data The actual raw content data of the item. + * @param etag The etag of the item. + */ + DavItem( const QString &url, const QString &contentType, const QByteArray &data, const QString &etag ); + + /** + * Sets the @p url that identifies the item. + */ + void setUrl( const QString &url ); + + /** + * Returns the url that identifies the item. + */ + QString url() const; + + /** + * Sets the content @p type of the item. + */ + void setContentType( const QString &type ); + + /** + * Returns the content type of the item. + */ + QString contentType() const; + + /** + * Sets the raw content @p data of the item. + */ + void setData( const QByteArray &data ); + + /** + * Returns the raw content data of the item. + */ + QByteArray data() const; + + /** + * Sets the @p etag of the item. + */ + void setEtag( const QString &etag ); + + /** + * Returns the etag of the item. + */ + QString etag() const; + + private: + QString mUrl; + QString mContentType; + QByteArray mData; + QString mEtag; +}; + +QDataStream& operator<<( QDataStream &out, const DavItem &item ); +QDataStream& operator>>( QDataStream &in, DavItem &item); + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitemcreatejob.cpp b/kdepim-runtime/resources/dav/common/davitemcreatejob.cpp new file mode 100644 index 00000000..733687b0 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemcreatejob.cpp @@ -0,0 +1,133 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitemcreatejob.h" + +#include "davitemfetchjob.h" +#include "davmanager.h" + +#include +#include +#include + +#include + +DavItemCreateJob::DavItemCreateJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent ) + : DavJobBase( parent ), mUrl( url ), mItem( item ), mRedirectCount( 0 ) +{ +} + +void DavItemCreateJob::start() +{ + QString headers = QLatin1String("Content-Type: "); + headers += mItem.contentType(); + headers += QLatin1String("\r\n"); + headers += QLatin1String("If-None-Match: *"); + + KIO::StoredTransferJob *job = KIO::storedPut( mItem.data(), mUrl.url(), -1, KIO::HideProgressInfo | KIO::DefaultFlags ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + job->addMetaData( QLatin1String("customHTTPHeader"), headers ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + job->setRedirectionHandlingEnabled( false ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); +} + +DavItem DavItemCreateJob::item() const +{ + return mItem; +} + +void DavItemCreateJob::davJobFinished( KJob *job ) +{ + KIO::StoredTransferJob *storedJob = qobject_cast( job ); + const int responseCode = storedJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + storedJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + + if ( storedJob->error() ) { + QString err; + if ( storedJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( storedJob->error(), storedJob->errorText() ); + else + err = storedJob->errorText(); + + setLatestResponseCode( responseCode ); + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request. The item has not been created on the server.\n" + "%1 (%2).", err, responseCode ) ); + + emitResult(); + return; + } + + // The 'Location:' HTTP header is used to indicate the new URL + const QStringList allHeaders = storedJob->queryMetaData( QLatin1String("HTTP-Headers") ).split( QLatin1Char('\n') ); + QString location; + foreach ( const QString &header, allHeaders ) { + if ( header.startsWith( QLatin1String( "location:" ), Qt::CaseInsensitive ) ) + location = header.section( QLatin1Char(' '), 1 ); + } + + KUrl url; + if ( location.isEmpty() ) + url = storedJob->url(); + else if ( location.startsWith( QLatin1Char('/') ) ) { + url = storedJob->url(); + url.setEncodedPath( location.toLatin1() ); + } else + url = location; + + if ( responseCode == 301 || responseCode == 302 || responseCode == 307 || responseCode == 308 ) { + if ( mRedirectCount > 4 ) { + setLatestResponseCode( responseCode ); + setError( UserDefinedError + responseCode ); + emitResult(); + } + else { + KUrl itemUrl( url ); + itemUrl.setUser( mUrl.url().user() ); + itemUrl.setPassword( mUrl.url().password() ); + mUrl.setUrl( itemUrl ); + + ++mRedirectCount; + start(); + } + + return; + } + + url.setUser( QString() ); + mItem.setUrl( url.prettyUrl() ); + + DavItemFetchJob *fetchJob = new DavItemFetchJob( mUrl, mItem ); + connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(itemRefreshed(KJob*)) ); + fetchJob->start(); +} + +void DavItemCreateJob::itemRefreshed( KJob *job ) +{ + if ( !job->error() ) { + DavItemFetchJob *fetchJob = qobject_cast( job ); + mItem.setEtag( fetchJob->item().etag() ); + } + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davitemcreatejob.h b/kdepim-runtime/resources/dav/common/davitemcreatejob.h new file mode 100644 index 00000000..d76ba112 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemcreatejob.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEMCREATEJOB_H +#define DAVITEMCREATEJOB_H + +#include "davitem.h" +#include "davjobbase.h" +#include "davutils.h" + +/** + * @short A job to create a DAV item on the DAV server. + */ +class DavItemCreateJob : public DavJobBase +{ + Q_OBJECT + + public: + /** + * Creates a new dav item create job. + * + * @param url The target url where the item shall be created. + * @param item The item that shall be created. + * @param parent The parent object. + */ + DavItemCreateJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the created DAV item including the correct identifier url + * and current etag information. + */ + DavItem item() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + void itemRefreshed( KJob* ); + + private: + DavUtils::DavUrl mUrl; + DavItem mItem; + int mRedirectCount; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitemdeletejob.cpp b/kdepim-runtime/resources/dav/common/davitemdeletejob.cpp new file mode 100644 index 00000000..e995657a --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemdeletejob.cpp @@ -0,0 +1,67 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitemdeletejob.h" + +#include "davmanager.h" + +#include +#include + +DavItemDeleteJob::DavItemDeleteJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent ) + : DavJobBase( parent ), mUrl( url ), mItem( item ) +{ +} + +void DavItemDeleteJob::start() +{ + KIO::DeleteJob *job = KIO::del( mUrl.url(), KIO::HideProgressInfo | KIO::DefaultFlags ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + job->addMetaData( QLatin1String("customHTTPHeader"), QLatin1String("If-Match: ") + mItem.etag() ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); +} + +void DavItemDeleteJob::davJobFinished( KJob *job ) +{ + KIO::DeleteJob *deleteJob = qobject_cast( job ); + + if ( deleteJob->error() && deleteJob->error() != KIO::ERR_NO_CONTENT ) { + const int responseCode = deleteJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + deleteJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + if ( responseCode != 404 && responseCode != 410 ) { + QString err; + if ( deleteJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( deleteJob->error(), deleteJob->errorText() ); + else + err = deleteJob->errorText(); + + setLatestResponseCode( responseCode ); + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request. The item has not been deleted from the server.\n" + "%1 (%2).", err, responseCode ) ); + } + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davitemdeletejob.h b/kdepim-runtime/resources/dav/common/davitemdeletejob.h new file mode 100644 index 00000000..8c3d11f1 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemdeletejob.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEMDELETEJOB_H +#define DAVITEMDELETEJOB_H + +#include "davitem.h" +#include "davjobbase.h" +#include "davutils.h" + +/** + * @short A job to delete a DAV item on the DAV server. + */ +class DavItemDeleteJob : public DavJobBase +{ + Q_OBJECT + + public: + /** + * Creates a new dav item delete job. + * + * @param url The url of the item that shall be deleted. + * @param item The item that shall be deleted. + * @param parent The parent object. + */ + DavItemDeleteJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + DavUtils::DavUrl mUrl; + DavItem mItem; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitemfetchjob.cpp b/kdepim-runtime/resources/dav/common/davitemfetchjob.cpp new file mode 100644 index 00000000..854113d4 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemfetchjob.cpp @@ -0,0 +1,93 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitemfetchjob.h" + +#include "davmanager.h" + +#include +#include +#include + +#include + +static QString etagFromHeaders( const QString &headers ) +{ + const QStringList allHeaders = headers.split( QLatin1Char('\n') ); + + QString etag; + foreach ( const QString &header, allHeaders ) { + if ( header.startsWith( QLatin1String( "etag:" ), Qt::CaseInsensitive ) ) + etag = header.section( QLatin1Char(' '), 1 ); + } + + return etag; +} + + +DavItemFetchJob::DavItemFetchJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent ) + : KJob( parent ), mUrl( url ), mItem( item ) +{ +} + +void DavItemFetchJob::start() +{ + KIO::StoredTransferJob *job = KIO::storedGet( mUrl.url(), KIO::Reload, KIO::HideProgressInfo | KIO::DefaultFlags ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + // Work around a strange bug in Zimbra (seen at least on CE 5.0.18) : if the user-agent + // contains "Mozilla", some strange debug data is displayed in the shared calendars. + // This kinda mess up the events parsing... + job->addMetaData( QLatin1String("UserAgent"), QLatin1String("KDE DAV groupware client") ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); +} + +DavItem DavItemFetchJob::item() const +{ + return mItem; +} + +void DavItemFetchJob::davJobFinished( KJob *job ) +{ + KIO::StoredTransferJob *storedJob = qobject_cast( job ); + + if ( storedJob->error() ) { + const int responseCode = storedJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + storedJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + QString err; + if ( storedJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( storedJob->error(), storedJob->errorText() ); + else + err = storedJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", err, responseCode ) ); + } else { + mItem.setData( storedJob->data() ); + mItem.setContentType( storedJob->queryMetaData( QLatin1String("content-type") ) ); + mItem.setEtag( etagFromHeaders( storedJob->queryMetaData( QLatin1String("HTTP-Headers") ) ) ); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davitemfetchjob.h b/kdepim-runtime/resources/dav/common/davitemfetchjob.h new file mode 100644 index 00000000..26982d12 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemfetchjob.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEMFETCHJOB_H +#define DAVITEMFETCHJOB_H + +#include "davitem.h" +#include "davutils.h" + +#include + +/** + * @short A job that fetches a DAV item from the DAV server. + */ +class DavItemFetchJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new dav item fetch job. + * + * @param url The DAV url of the item that shall be fetched. + * @param item The item that shall be fetched. + * @param parent The parent object. + */ + DavItemFetchJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the fetched item including current etag information. + */ + DavItem item() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + DavUtils::DavUrl mUrl; + DavItem mItem; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitemmodifyjob.cpp b/kdepim-runtime/resources/dav/common/davitemmodifyjob.cpp new file mode 100644 index 00000000..6bd0775b --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemmodifyjob.cpp @@ -0,0 +1,113 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitemmodifyjob.h" + +#include "davitemfetchjob.h" +#include "davmanager.h" + +#include +#include + +DavItemModifyJob::DavItemModifyJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent ) + : DavJobBase( parent ), mUrl( url ), mItem( item ) +{ +} + +void DavItemModifyJob::start() +{ + QString headers = QLatin1String("Content-Type: "); + headers += mItem.contentType(); + headers += QLatin1String("\r\n"); + headers += QLatin1String("If-Match: ") + mItem.etag(); + + KIO::StoredTransferJob *job = KIO::storedPut( mItem.data(), mUrl.url(), -1, KIO::HideProgressInfo | KIO::DefaultFlags ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + job->addMetaData( QLatin1String("customHTTPHeader"), headers ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); +} + +DavItem DavItemModifyJob::item() const +{ + return mItem; +} + +void DavItemModifyJob::davJobFinished( KJob *job ) +{ + KIO::StoredTransferJob *storedJob = qobject_cast( job ); + + if ( storedJob->error() ) { + const int responseCode = storedJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + storedJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + QString err; + if ( storedJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( storedJob->error(), storedJob->errorText() ); + else + err = storedJob->errorText(); + + setLatestResponseCode( responseCode ); + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request. The item was not modified on the server.\n" + "%1 (%2).", err, responseCode ) ); + + emitResult(); + return; + } + + // The 'Location:' HTTP header is used to indicate the new URL + const QStringList allHeaders = storedJob->queryMetaData( QLatin1String("HTTP-Headers") ).split( QLatin1Char('\n') ); + QString location; + foreach ( const QString &header, allHeaders ) { + if ( header.startsWith( QLatin1String( "location:" ), Qt::CaseInsensitive ) ) + location = header.section( QLatin1Char(' '), 1 ); + } + + KUrl url; + if ( location.isEmpty() ) + url = storedJob->url(); + else if ( location.startsWith( QLatin1Char('/') ) ) { + url = storedJob->url(); + url.setEncodedPath( location.toLatin1() ); + } else + url = location; + + url.setUser( QString() ); + mItem.setUrl( url.prettyUrl() ); + + DavItemFetchJob *fetchJob = new DavItemFetchJob( mUrl, mItem ); + connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(itemRefreshed(KJob*)) ); + fetchJob->start(); +} + +void DavItemModifyJob::itemRefreshed( KJob *job ) +{ + if ( !job->error() ) { + DavItemFetchJob *fetchJob = qobject_cast( job ); + mItem.setEtag( fetchJob->item().etag() ); + } + else { + mItem.setEtag( QString() ); + } + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davitemmodifyjob.h b/kdepim-runtime/resources/dav/common/davitemmodifyjob.h new file mode 100644 index 00000000..cb3f2014 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemmodifyjob.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEMMODIFYJOB_H +#define DAVITEMMODIFYJOB_H + +#include "davitem.h" +#include "davjobbase.h" +#include "davutils.h" + +/** + * @short A job that modifies a DAV item on the DAV server. + */ +class DavItemModifyJob : public DavJobBase +{ + Q_OBJECT + + public: + /** + * Creates a new dav item modify job. + * + * @param url The url of the item that shall be modified. + * @param item The item that shall be modified. + * @param parent The parent object. + */ + DavItemModifyJob( const DavUtils::DavUrl &url, const DavItem &item, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the modified item including the updated etag information. + */ + DavItem item() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + void itemRefreshed( KJob* ); + + private: + DavUtils::DavUrl mUrl; + DavItem mItem; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitemsfetchjob.cpp b/kdepim-runtime/resources/dav/common/davitemsfetchjob.cpp new file mode 100644 index 00000000..1926a864 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemsfetchjob.cpp @@ -0,0 +1,153 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + Based on DavItemsListJob which is copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitemsfetchjob.h" +#include "davmanager.h" +#include "davmultigetprotocol.h" + +#include +#include + +DavItemsFetchJob::DavItemsFetchJob( const DavUtils::DavUrl &collectionUrl, const QStringList &urls, QObject *parent ) + : KJob( parent ), mCollectionUrl( collectionUrl ), mUrls( urls ) +{ +} + +void DavItemsFetchJob::start() +{ + const DavMultigetProtocol *protocol = + dynamic_cast( DavManager::self()->davProtocol( mCollectionUrl.protocol() ) ); + if ( !protocol ) { + setError( UserDefinedError ); + setErrorText( i18n( "Protocol for the collection does not support MULTIGET" ) ); + emitResult(); + return; + } + + const QDomDocument report = protocol->itemsReportQuery( mUrls ); + KIO::DavJob *job = DavManager::self()->createReportJob( mCollectionUrl.url(), report, QLatin1String( "0" ) ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); +} + +DavItem::List DavItemsFetchJob::items() const +{ + return mItems.values(); +} + +DavItem DavItemsFetchJob::item( const QString &url ) const +{ + return mItems.value( url ); +} + +void DavItemsFetchJob::davJobFinished( KJob *job ) +{ + KIO::DavJob *davJob = qobject_cast( job ); + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx + if ( davJob->error() || ( responseCode >= 400 && responseCode < 600 ) ) { + QString err; + if ( davJob->error() && davJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( davJob->error(), davJob->errorText() ); + else + err = davJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", err, responseCode ) ); + + emitResult(); + return; + } + + const DavMultigetProtocol *protocol = + static_cast( DavManager::self()->davProtocol( mCollectionUrl.protocol() ) ); + + const QDomDocument document = davJob->response(); + const QDomElement documentElement = document.documentElement(); + + QDomElement responseElement = DavUtils::firstChildElementNS( documentElement, QLatin1String("DAV:"), QLatin1String("response") ); + + while ( !responseElement.isNull() ) { + QDomElement propstatElement = DavUtils::firstChildElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("propstat") ); + + if ( propstatElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + // Check for errors + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("status") ); + if ( !statusElement.text().contains( QLatin1String("200") ) ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + const QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + + DavItem item; + + // extract path + const QDomElement hrefElement = DavUtils::firstChildElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("href") ); + const QString href = hrefElement.text(); + + KUrl url = davJob->url(); + url.setUser( QString() ); + if ( href.startsWith( QLatin1Char('/') ) ) { + // href is only a path, use request url to complete + url.setEncodedPath( href.toLatin1() ); + } else { + // href is a complete url + KUrl tmpUrl( href ); + url = tmpUrl; + } + + item.setUrl( url.prettyUrl() ); + + // extract etag + const QDomElement getetagElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("getetag") ); + item.setEtag( getetagElement.text() ); + + // extract content + const QDomElement dataElement = DavUtils::firstChildElementNS( propElement, + protocol->responseNamespace(), + protocol->dataTagName() ); + + if ( dataElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + const QByteArray data = dataElement.firstChild().toText().data().toUtf8(); + if ( data.isEmpty() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + item.setData( data ); + + mItems.insert( item.url(), item ); + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + } + + emitResult(); +} diff --git a/kdepim-runtime/resources/dav/common/davitemsfetchjob.h b/kdepim-runtime/resources/dav/common/davitemsfetchjob.h new file mode 100644 index 00000000..5a8390f0 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemsfetchjob.h @@ -0,0 +1,72 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + Based on DavItemsListJob which is copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEMSFETCHJOB_H +#define DAVITEMSFETCHJOB_H + +#include "davitem.h" +#include "davutils.h" + +#include + +#include +#include + +/** + * @short A job that fetches a list of items from a DAV server using a multiget query. + */ +class DavItemsFetchJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new items fetch job. + * + * @param collectionUrl The DAV collection on which to run the query + * @param urls The list of urls to fetch + * @param parent The parent object + */ + DavItemsFetchJob( const DavUtils::DavUrl &collectionUrl, const QStringList &urls, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of fetched items + */ + DavItem::List items() const; + + /** + * Return the item found at @p url + */ + DavItem item( const QString &url ) const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + DavUtils::DavUrl mCollectionUrl; + QStringList mUrls; + QMap mItems; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davitemslistjob.cpp b/kdepim-runtime/resources/dav/common/davitemslistjob.cpp new file mode 100644 index 00000000..99b5adec --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemslistjob.cpp @@ -0,0 +1,203 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davitemslistjob.h" + +#include "davmanager.h" +#include "davprotocolbase.h" +#include "davutils.h" + +#include +#include + +#include +#include + +DavItemsListJob::DavItemsListJob( const DavUtils::DavUrl &url, QObject *parent ) + : KJob( parent ), mUrl( url ), mSubJobCount( 0 ) +{ +} + +void DavItemsListJob::setContentMimeTypes( const QStringList &types ) +{ + mMimeTypes = types; +} + +void DavItemsListJob::start() +{ + const DavProtocolBase *protocol = DavManager::self()->davProtocol( mUrl.protocol() ); + Q_ASSERT( protocol ); + QListIterator it( protocol->itemsQueries() ); + int queryIndex = 0; + + while ( it.hasNext() ) { + const QDomDocument props = it.next(); + const QString mimeType = protocol->mimeTypeForQuery( queryIndex ); + + if ( mMimeTypes.isEmpty() || mMimeTypes.contains( mimeType ) ) { + ++mSubJobCount; + if ( protocol->useReport() ) { + KIO::DavJob *job = DavManager::self()->createReportJob( mUrl.url(), props ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + job->setProperty( "davType", QLatin1String("report") ); + job->setProperty( "itemsMimeType", mimeType ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); + } else { + KIO::DavJob *job = DavManager::self()->createPropFindJob( mUrl.url(), props ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + job->setProperty( "davType", QLatin1String("propFind") ); + job->setProperty( "itemsMimeType", mimeType ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(davJobFinished(KJob*)) ); + } + } + + ++queryIndex; + } +} + +DavItem::List DavItemsListJob::items() const +{ + return mItems; +} + +void DavItemsListJob::davJobFinished( KJob *job ) +{ + KIO::DavJob *davJob = qobject_cast( job ); + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx + if ( davJob->error() || ( responseCode >= 400 && responseCode < 600 ) ) { + QString err; + if ( davJob->error() && davJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( davJob->error(), davJob->errorText() ); + else + err = davJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", err, responseCode ) ); + } + else { + /* + * Extract data from a document like the following: + * + * + * + * /caldav.php/test1.user/home/KOrganizer-166749289.780.ics + * + * + * "b4bbea0278f4f63854c4167a7656024a" + * + * HTTP/1.1 200 OK + * + * + * + * /caldav.php/test1.user/home/KOrganizer-399416366.464.ics + * + * + * "52eb129018398a7da4f435b2bc4c6cd5" + * + * HTTP/1.1 200 OK + * + * + * + */ + + const QString itemsMimeType = job->property( "itemsMimeType" ).toString(); + const QDomDocument document = davJob->response(); + const QDomElement documentElement = document.documentElement(); + + QDomElement responseElement = DavUtils::firstChildElementNS( documentElement, QLatin1String("DAV:"), QLatin1String("response") ); + while ( !responseElement.isNull() ) { + + QDomElement propstatElement; + + // check for the valid propstat, without giving up on first error + { + const QDomNodeList propstats = responseElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("propstat") ); + for ( uint i = 0; i < propstats.length(); ++i ) { + const QDomElement propstatCandidate = propstats.item( i ).toElement(); + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatCandidate, QLatin1String("DAV:"), QLatin1String("status") ); + if ( statusElement.text().contains( QLatin1String("200") ) ) { + propstatElement = propstatCandidate; + } + } + } + + if ( propstatElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + const QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + + // check whether it is a dav collection ... + const QDomElement resourcetypeElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("resourcetype") ); + if ( !responseElement.isNull() ) { + const QDomElement collectionElement = DavUtils::firstChildElementNS( resourcetypeElement, QLatin1String("DAV:"), QLatin1String("collection") ); + if ( !collectionElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + } + + // ... if not it is an item + DavItem item; + item.setContentType( itemsMimeType ); + + // extract path + const QDomElement hrefElement = DavUtils::firstChildElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("href") ); + const QString href = hrefElement.text(); + + KUrl url = davJob->url(); + url.setUser( QString() ); + if ( href.startsWith( QLatin1Char('/') ) ) { + // href is only a path, use request url to complete + url.setEncodedPath( href.toLatin1() ); + } else { + // href is a complete url + KUrl tmpUrl( href ); + url = tmpUrl; + } + + QString itemUrl = url.prettyUrl(); + if ( mSeenUrls.contains( itemUrl ) ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response" )); + continue; + } + + mSeenUrls << itemUrl; + item.setUrl( itemUrl ); + + // extract etag + const QDomElement getetagElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("getetag") ); + + item.setEtag( getetagElement.text() ); + + mItems << item; + + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + } + } + + if ( --mSubJobCount == 0 ) + emitResult(); +} + diff --git a/kdepim-runtime/resources/dav/common/davitemslistjob.h b/kdepim-runtime/resources/dav/common/davitemslistjob.h new file mode 100644 index 00000000..ceed926e --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davitemslistjob.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVITEMSLISTJOB_H +#define DAVITEMSLISTJOB_H + +#include "davitem.h" +#include "davutils.h" + +#include + +#include +#include + +/** + * @short A job that lists all DAV items inside a DAV collection. + */ +class DavItemsListJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new dav items list job. + * + * @param url The url of the DAV collection. + * @param parent The parent object. + */ + explicit DavItemsListJob( const DavUtils::DavUrl &url, QObject *parent = 0 ); + + /** + * Limits the mime types of the items requested. + * + * If no mime type is given then all will be requested. + * + * @param types The list of mime types to include + */ + void setContentMimeTypes( const QStringList &types ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of items including identifier url and etag information. + */ + DavItem::List items() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + DavUtils::DavUrl mUrl; + QStringList mMimeTypes; + DavItem::List mItems; + QSet mSeenUrls; // to prevent events duplication with some servers + uint mSubJobCount; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davjobbase.cpp b/kdepim-runtime/resources/dav/common/davjobbase.cpp new file mode 100644 index 00000000..ab6d6722 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davjobbase.cpp @@ -0,0 +1,84 @@ +/* + Copyright (c) 2014 Gregory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davjobbase.h" + +DavJobBase::DavJobBase( QObject *parent ) + : KJob( parent ), mLatestResponseCode( 0 ) +{ +} + +unsigned int DavJobBase::latestResponseCode() const +{ + return mLatestResponseCode; +} + +bool DavJobBase::canRetryLater() const +{ + bool ret = false; + + // Be explicit and readable by splitting the if/else if clauses + + if ( latestResponseCode() == 0 && error() ) { + // Likely a timeout or a connection failure. + ret = true; + } + else if ( latestResponseCode() == 401 ) { + // Authentication required + ret = true; + } + else if ( latestResponseCode() == 402 ) { + // Payment required + ret = true; + } + else if ( latestResponseCode() == 407 ) { + // Proxy authentication required + ret = true; + } + else if ( latestResponseCode() == 408 ) { + // Request timeout + ret = true; + } + else if ( latestResponseCode() == 423 ) { + // Locked + ret = true; + } + else if ( latestResponseCode() == 429 ) { + // Too many requests + ret = true; + } + else if ( latestResponseCode() >= 501 && latestResponseCode() <= 504 ) { + // Various server-side errors + ret = true; + } + else if ( latestResponseCode() == 507 ) { + // Insufficient storage + ret = true; + } + else if ( latestResponseCode() == 511 ) { + // Network authentication required + ret = true; + } + + return ret; +} + +void DavJobBase::setLatestResponseCode( unsigned int code ) +{ + mLatestResponseCode = code; +} diff --git a/kdepim-runtime/resources/dav/common/davjobbase.h b/kdepim-runtime/resources/dav/common/davjobbase.h new file mode 100644 index 00000000..b437dac0 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davjobbase.h @@ -0,0 +1,78 @@ +/* + Copyright (c) 2014 Gregory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVJOBBASE_H +#define DAVJOBBASE_H + +#include + +/** + * @short base class for the jobs used by the resource. + */ +class DavJobBase : public KJob +{ + Q_OBJECT + + public: + explicit DavJobBase( QObject *parent = 0 ); + + /** + * Get the latest response code. + * + * If no response code has been set then 0 will be returned, but will + * be meaningless unless error() is non-zero. In that case this means + * that the latest error was not at the HTTP level. + */ + unsigned int latestResponseCode() const; + + /** + * Check if the job can be retried later. + * + * This will return true for transient errors, i.e. if the response code + * is either zero and error() is set or if the HTTP response code hints + * at a temporary error. + * + * The HTTP response codes considered retryable are: + * - 401 + * - 402 + * - 407 + * - 408 + * - 423 + * - 429 + * - 501 to 504, inclusive + * - 507 + * - 511 + */ + bool canRetryLater() const; + + protected: + /** + * Sets the latest response code received. + * + * Only really useful in case of error, though success codes can + * also be set. + * + * @param code The code to set, should be a valid HTTP response code or zero. + */ + void setLatestResponseCode( unsigned int code ); + + private: + unsigned int mLatestResponseCode; +}; + +#endif // DAVJOBBASE_H diff --git a/kdepim-runtime/resources/dav/common/davmanager.cpp b/kdepim-runtime/resources/dav/common/davmanager.cpp new file mode 100644 index 00000000..c4645b81 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davmanager.cpp @@ -0,0 +1,120 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davmanager.h" + +#include "caldavprotocol.h" +#include "carddavprotocol.h" +#include "groupdavprotocol.h" + +#include +#include + +#include + +DavManager* DavManager::mSelf = 0; + +DavManager::DavManager() +{ +} + +DavManager::~DavManager() +{ + QMapIterator it( mProtocols ); + while ( it.hasNext() ) { + it.next(); + delete it.value(); + } +} + +DavManager* DavManager::self() +{ + if ( !mSelf ) + mSelf = new DavManager(); + + return mSelf; +} + +KIO::DavJob* DavManager::createPropFindJob( const KUrl &url, const QDomDocument &document, const QString &depth ) const +{ + KIO::DavJob *job = KIO::davPropFind( url, document, depth, KIO::HideProgressInfo | KIO::DefaultFlags ); + + // workaround needed, Depth: header doesn't seem to be correctly added + const QString header = QLatin1String("Content-Type: text/xml\r\nDepth: ") + depth; + job->addMetaData( QLatin1String("customHTTPHeader"), header ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + job->setProperty( "extraDavDepth", QVariant::fromValue( depth ) ); + + return job; +} + +KIO::DavJob* DavManager::createReportJob( const KUrl &url, const QDomDocument &document, const QString &depth ) const +{ + KIO::DavJob *job = KIO::davReport( url, document.toString(), depth, KIO::HideProgressInfo | KIO::DefaultFlags ); + + // workaround needed, Depth: header doesn't seem to be correctly added + const QString header = QLatin1String("Content-Type: text/xml\r\nDepth: ") + depth; + job->addMetaData( QLatin1String("customHTTPHeader"), header ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + job->setProperty( "extraDavDepth", QVariant::fromValue( depth ) ); + + return job; +} + +KIO::DavJob* DavManager::createPropPatchJob( const KUrl &url, const QDomDocument &document ) const +{ + KIO::DavJob *job = KIO::davPropPatch( url, document, KIO::HideProgressInfo | KIO::DefaultFlags ); + const QString header = QLatin1String("Content-Type: text/xml"); + job->addMetaData( QLatin1String("customHTTPHeader"), header ); + job->addMetaData( QLatin1String("cookies"), QLatin1String("none") ); + job->addMetaData( QLatin1String("no-auth-prompt"), QLatin1String("true") ); + return job; +} + +const DavProtocolBase* DavManager::davProtocol( DavUtils::Protocol protocol ) +{ + if ( createProtocol( protocol ) ) + return mProtocols[ protocol ]; + else + return 0; +} + +bool DavManager::createProtocol( DavUtils::Protocol protocol ) +{ + if ( mProtocols.contains( protocol ) ) + return true; + + switch( protocol ) { + case DavUtils::CalDav: + mProtocols.insert( DavUtils::CalDav, new CaldavProtocol() ); + break; + case DavUtils::CardDav: + mProtocols.insert( DavUtils::CardDav, new CarddavProtocol() ); + break; + case DavUtils::GroupDav: + mProtocols.insert( DavUtils::GroupDav, new GroupdavProtocol() ); + break; + default: + kError() << "Unknown protocol: " << static_cast( protocol ); + return false; + } + + return true; +} diff --git a/kdepim-runtime/resources/dav/common/davmanager.h b/kdepim-runtime/resources/dav/common/davmanager.h new file mode 100644 index 00000000..9efa5c78 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davmanager.h @@ -0,0 +1,104 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVMANAGER_H +#define DAVMANAGER_H + +#include "davutils.h" + +#include +#include + +class DavProtocolBase; + +namespace KIO { +class DavJob; +} + +class KUrl; + +class QDomDocument; + +/** + * @short A factory class for handling DAV jobs. + * + * This class provides factory methods to create preconfigured + * low-level DAV jobs and has access to the global DAV protocol dialect + * objects which abstract the access to the various DAV protocol dialects. + */ +class DavManager +{ + public: + /** + * Destroys the DAV manager. + */ + ~DavManager(); + + /** + * Returns the global instance of the DAV manager. + */ + static DavManager* self(); + + /** + * Returns a preconfigured DAV PROPFIND job. + * + * @param url The target url of the job. + * @param document The query XML document. + * @param depth The Depth: value to send in the HTTP request + */ + KIO::DavJob* createPropFindJob( const KUrl &url, const QDomDocument &document, const QString &depth = QLatin1String( "1" ) ) const; + + /** + * Returns a preconfigured DAV REPORT job. + * + * @param url The target url of the job. + * @param document The query XML document. + * @param depth The Depth: value to send in the HTTP request + */ + KIO::DavJob* createReportJob( const KUrl &url, const QDomDocument &document, const QString &depth = QLatin1String( "1" ) ) const; + + /** + * Returns a preconfigured DAV PROPPATCH job. + * + * @param url The target url of the job. + * @param document The query XML document. + */ + KIO::DavJob* createPropPatchJob( const KUrl &url, const QDomDocument &document ) const; + + /** + * Returns the DAV protocol dialect object for the given DAV @p protocol. + */ + const DavProtocolBase* davProtocol( DavUtils::Protocol protocol ); + + private: + /** + * Creates a new DAV manager. + */ + DavManager(); + + /** + * Creates a new protocol. + */ + bool createProtocol( DavUtils::Protocol protocol ); + + typedef QMap protocolsMap; + protocolsMap mProtocols; + static DavManager* mSelf; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davmultigetprotocol.cpp b/kdepim-runtime/resources/dav/common/davmultigetprotocol.cpp new file mode 100644 index 00000000..0d1e3852 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davmultigetprotocol.cpp @@ -0,0 +1,23 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davmultigetprotocol.h" + +DavMultigetProtocol::~DavMultigetProtocol() +{ +} diff --git a/kdepim-runtime/resources/dav/common/davmultigetprotocol.h b/kdepim-runtime/resources/dav/common/davmultigetprotocol.h new file mode 100644 index 00000000..5106ff30 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davmultigetprotocol.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVMULTIGETPROTOCOL_H +#define DAVMULTIGETPROTOCOL_H + +#include "davprotocolbase.h" + +/** + * @short Base class for protocols that implement multiget capabilities + */ +class DavMultigetProtocol : public DavProtocolBase +{ + public: + /** + * Destroys the DAV protocol + */ + virtual ~DavMultigetProtocol(); + + /** + * Returns the XML document that represents a MULTIGET DAV query to + * list all DAV resources with the given @p urls. + */ + virtual QDomDocument itemsReportQuery( const QStringList &urls ) const = 0; + + /** + * Returns the namespace used by protocol-specific elements found in responses. + */ + virtual QString responseNamespace() const = 0; + + /** + * Returns the tag name of data elements found in responses. + */ + virtual QString dataTagName() const = 0; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.cpp b/kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.cpp new file mode 100644 index 00000000..2638371a --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.cpp @@ -0,0 +1,219 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davprincipalhomesetsfetchjob.h" + +#include "davmanager.h" +#include "davprotocolbase.h" + +#include +#include + +DavPrincipalHomeSetsFetchJob::DavPrincipalHomeSetsFetchJob( const DavUtils::DavUrl &url, QObject *parent ) + : KJob( parent ), mUrl( url ) +{ +} + +void DavPrincipalHomeSetsFetchJob::start() +{ + fetchHomeSets( false ); +} + +void DavPrincipalHomeSetsFetchJob::fetchHomeSets( bool homeSetsOnly ) +{ + QDomDocument document; + + QDomElement propfindElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + document.appendChild( propfindElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propfindElement.appendChild( propElement ); + + const QString homeSet = DavManager::self()->davProtocol( mUrl.protocol() )->principalHomeSet(); + const QString homeSetNS = DavManager::self()->davProtocol( mUrl.protocol() )->principalHomeSetNS(); + propElement.appendChild( document.createElementNS( homeSetNS, homeSet ) ); + + if ( !homeSetsOnly ) { + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("current-user-principal" )) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("principal-URL") ) ); + } + + KIO::DavJob *job = DavManager::self()->createPropFindJob( mUrl.url(), document, QLatin1String("0") ); + job->addMetaData( QLatin1String("PropagateHttpHeader"),QLatin1String("true") ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +QStringList DavPrincipalHomeSetsFetchJob::homeSets() const +{ + return mHomeSets; +} + +void DavPrincipalHomeSetsFetchJob::davJobFinished( KJob *job ) +{ + KIO::DavJob *davJob = qobject_cast( job ); + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx + if ( davJob->error() || ( responseCode >= 400 && responseCode < 600 ) ) { + QString err; + if ( davJob->error() && davJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( davJob->error(), davJob->errorText() ); + else + err = davJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", err, responseCode ) ); + + emitResult(); + return; + } + + /* + * Extract information from a document like the following (if no homeset is defined) : + * + * + * + * /dav/ + * + * HTTP/1.1 200 OK + * + * + * /principals/users/gdacoin/ + * + * + * + * + * HTTP/1.1 404 Not Found + * + * + * + * + * + * + * + * + * Or like this (if the homeset is defined): + * + * + * + * + * /principals/users/greg%40kamago.net/ + * + * + * + * /greg%40kamago.net/ + * + * + * HTTP/1.1 200 OK + * + * + * + */ + + const QString homeSet = DavManager::self()->davProtocol( mUrl.protocol() )->principalHomeSet(); + const QString homeSetNS = DavManager::self()->davProtocol( mUrl.protocol() )->principalHomeSetNS(); + QString nextRoundHref; // The content of the href element that will be used if no homeset was found. + // This is either given by current-user-principal or by principal-URL. + + const QDomDocument document = davJob->response(); + const QDomElement multistatusElement = document.documentElement(); + + QDomElement responseElement = DavUtils::firstChildElementNS( multistatusElement, QLatin1String("DAV:"), QLatin1String("response") ); + while ( !responseElement.isNull() ) { + + QDomElement propstatElement; + + // check for the valid propstat, without giving up on first error + { + const QDomNodeList propstats = responseElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("propstat") ); + for ( uint i = 0; i < propstats.length(); ++i ) { + const QDomElement propstatCandidate = propstats.item( i ).toElement(); + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatCandidate, QLatin1String("DAV:"), QLatin1String("status") ); + if ( statusElement.text().contains( QLatin1String("200") ) ) { + propstatElement = propstatCandidate; + } + } + } + + if ( propstatElement.isNull() ) { + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + continue; + } + + // extract home sets + const QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + const QDomElement homeSetElement = DavUtils::firstChildElementNS( propElement, homeSetNS, homeSet ); + + if ( !homeSetElement.isNull() ) { + QDomElement hrefElement = DavUtils::firstChildElementNS( homeSetElement, QLatin1String("DAV:"), QLatin1String("href") ); + + while ( !hrefElement.isNull() ) { + const QString href = hrefElement.text(); + if ( !mHomeSets.contains( href ) ) + mHomeSets << href; + + hrefElement = DavUtils::nextSiblingElementNS( hrefElement, QLatin1String("DAV:"), QLatin1String("href") ); + } + } + else { + // Trying to get the principal url, given either by current-user-principal or principal-URL + QDomElement urlHolder = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("current-user-principal") ); + if ( urlHolder.isNull() ) + urlHolder = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("principal-URL") ); + + if ( !urlHolder.isNull() ) { + // Getting the href that will be used for the next round + QDomElement hrefElement = DavUtils::firstChildElementNS( urlHolder, QLatin1String("DAV:"), QLatin1String("href") ); + if ( !hrefElement.isNull() ) + nextRoundHref = hrefElement.text(); + } + } + + responseElement = DavUtils::nextSiblingElementNS( responseElement, QLatin1String("DAV:"), QLatin1String("response") ); + } + + /* + * Now either we got one or more homesets, or we got an href for the next round + * or nothing can be found by this job. + * If we have homesets, we're done here and can notify the caller. + * Else we must ensure that we have an href for the next round. + */ + if ( !mHomeSets.isEmpty() || nextRoundHref.isEmpty() ) { + emitResult(); + } else { + KUrl nextRoundUrl( mUrl.url() ); + + if ( nextRoundHref.startsWith( QLatin1Char('/') ) ) { + // nextRoundHref is only a path, use request url to complete + nextRoundUrl.setEncodedPath( nextRoundHref.toLatin1() ); + } else { + // href is a complete url + KUrl tmpUrl( nextRoundHref ); + nextRoundUrl = tmpUrl; + nextRoundUrl.setUser( mUrl.url().user() ); + nextRoundUrl.setPass( mUrl.url().pass() ); + } + + mUrl.setUrl( nextRoundUrl ); + // And one more round, fetching only homesets + fetchHomeSets( true ); + } +} diff --git a/kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.h b/kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.h new file mode 100644 index 00000000..9656b312 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davprincipalhomesetsfetchjob.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVPRINCIPALHOMESETSFETCHJOB_H +#define DAVPRINCIPALHOMESETSFETCHJOB_H + +#include "davutils.h" + +#include + +#include + +/** + * @short A job that fetches home sets for a principal. + */ +class DavPrincipalHomeSetsFetchJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new dav principals home sets fetch job. + * + * @param url The DAV url of the DAV principal. + * @param parent The parent object. + */ + explicit DavPrincipalHomeSetsFetchJob( const DavUtils::DavUrl &url, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the found home sets. + */ + QStringList homeSets() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + /** + * Start the fetch process. + * + * There may be two rounds necessary if the first request + * does not returns the home sets, but only the current-user-principal + * or the principal-URL. The bool flag is here to prevent requesting + * those last two on each request, as they are only fetched in + * the first round. + * + * @param fetchHomeSetsOnly If set to true the request will not include + * the current-user-principal and principal-URL props. + */ + void fetchHomeSets( bool fetchHomeSetsOnly ); + + DavUtils::DavUrl mUrl; + QStringList mHomeSets; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davprincipalsearchjob.cpp b/kdepim-runtime/resources/dav/common/davprincipalsearchjob.cpp new file mode 100644 index 00000000..6b9d5f43 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davprincipalsearchjob.cpp @@ -0,0 +1,369 @@ +/* + Copyright (c) 2011 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davprincipalsearchjob.h" + +#include "davmanager.h" + +#include +#include +#include + +DavPrincipalSearchJob::DavPrincipalSearchJob( const DavUtils::DavUrl& url, DavPrincipalSearchJob::FilterType type, + const QString& filter, QObject* parent ) + : KJob( parent ), mUrl( url ), mType( type), mFilter( filter ), mPrincipalPropertySearchSubJobCount( 0 ), + mPrincipalPropertySearchSubJobSuccessful( false ) +{ +} + +void DavPrincipalSearchJob::fetchProperty( const QString& name, const QString& ns ) +{ + QString propNamespace = ns; + if ( propNamespace.isEmpty() ) + propNamespace = QLatin1String("DAV:"); + + mFetchProperties << QPair( propNamespace, name ); +} + +DavUtils::DavUrl DavPrincipalSearchJob::davUrl() const +{ + return mUrl; +} + +void DavPrincipalSearchJob::start() +{ + /* + * The first step is to try to locate the URL that contains the principals. + * This is done with a PROPFIND request and a XML like this: + * + * + * + * + * + * + */ + QDomDocument query; + + QDomElement propfind = query.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + query.appendChild( propfind ); + + QDomElement prop = query.createElementNS( QLatin1String("DAV:"), QLatin1String("prop" )); + propfind.appendChild( prop ); + + QDomElement principalCollectionSet = query.createElementNS( QLatin1String("DAV:"), QLatin1String("principal-collection-set") ); + prop.appendChild( principalCollectionSet ); + + KIO::DavJob *job = DavManager::self()->createPropFindJob( mUrl.url(), query ); + job->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(principalCollectionSetSearchFinished(KJob*)) ); + job->start(); +} + +void DavPrincipalSearchJob::principalCollectionSetSearchFinished( KJob* job ) +{ + KIO::DavJob *davJob = qobject_cast( job ); + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).isEmpty() ? + 0 : + davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx + if ( davJob->error() || ( responseCode >= 400 && responseCode < 600 ) ) { + QString err; + if ( davJob->error() && davJob->error() != KIO::ERR_SLAVE_DEFINED ) + err = KIO::buildErrorString( davJob->error(), davJob->errorText() ); + else + err = davJob->errorText(); + + setError( UserDefinedError + responseCode ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", err, responseCode ) ); + + emitResult(); + return; + } + + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + /* + * Extract information from a document like the following: + * + * + * + * + * http://www.example.com/papers/ + * + * + * + * http://www.example.com/acl/users/ + * http://www.example.com/acl/groups/ + * + * + * HTTP/1.1 200 OK + * + * + * + */ + + QDomDocument document = davJob->response(); + QDomElement documentElement = document.documentElement(); + + QDomElement responseElement = DavUtils::firstChildElementNS( documentElement, QLatin1String("DAV:"), QLatin1String("response") ); + if ( responseElement.isNull() ) { + emitResult(); + return; + } + + // check for the valid propstat, without giving up on first error + QDomElement propstatElement; + { + const QDomNodeList propstats = responseElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("propstat")); + for ( uint i = 0; i < propstats.length(); ++i ) { + const QDomElement propstatCandidate = propstats.item( i ).toElement(); + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatCandidate, QLatin1String("DAV:"), QLatin1String("status") ); + if ( statusElement.text().contains( QLatin1String("200") ) ) { + propstatElement = propstatCandidate; + } + } + } + + if ( propstatElement.isNull() ) { + emitResult(); + return; + } + + QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + if ( propElement.isNull() ) { + emitResult(); + return; + } + + QDomElement principalCollectionSetElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("principal-collection-set") ); + if ( principalCollectionSetElement.isNull() ) { + emitResult(); + return; + } + + QDomNodeList hrefNodes = principalCollectionSetElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("href") ); + for ( int i = 0; i < hrefNodes.size(); ++i ) { + QDomElement hrefElement = hrefNodes.at( i ).toElement(); + QString href = hrefElement.text(); + + KUrl url = mUrl.url(); + if ( href.startsWith( QLatin1Char('/') ) ) { + // href is only a path, use request url to complete + url.setEncodedPath( href.toLatin1() ); + } else { + // href is a complete url + KUrl tmpUrl( href ); + tmpUrl.setUser( url.user() ); + tmpUrl.setPass( url.pass() ); + url = tmpUrl; + } + + QDomDocument principalPropertySearchQuery; + buildReportQuery( principalPropertySearchQuery ); + KIO::DavJob *reportJob = DavManager::self()->createReportJob( url, principalPropertySearchQuery ); + reportJob->addMetaData( QLatin1String("PropagateHttpHeader"), QLatin1String("true") ); + connect( reportJob, SIGNAL(result(KJob*)), this, SLOT(principalPropertySearchFinished(KJob*)) ); + ++mPrincipalPropertySearchSubJobCount; + reportJob->start(); + } +} + +void DavPrincipalSearchJob::principalPropertySearchFinished( KJob* job ) +{ + --mPrincipalPropertySearchSubJobCount; + + if ( job->error() && !mPrincipalPropertySearchSubJobSuccessful ) { + setError( job->error() ); + setErrorText( job->errorText() ); + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const int responseCode = davJob->queryMetaData( QLatin1String("responsecode") ).toInt(); + + if ( responseCode > 499 && responseCode < 600 && !mPrincipalPropertySearchSubJobSuccessful ) { + // Server-side error, unrecoverable + setError( UserDefinedError ); + setErrorText( i18n( "The server encountered an error that prevented it from completing your request" ) ); + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); + return; + } else if ( responseCode > 399 && responseCode < 500 && !mPrincipalPropertySearchSubJobSuccessful ) { + // User-side error + QString extraMessage; + if ( responseCode == 401 ) + extraMessage = i18n( "Invalid username/password" ); + else if ( responseCode == 403 ) + extraMessage = i18n( "Access forbidden" ); + else if ( responseCode == 404 ) + extraMessage = i18n( "Resource not found" ); + else + extraMessage = i18n( "HTTP error" ); + + setError( UserDefinedError ); + setErrorText( i18n( "There was a problem with the request.\n" + "%1 (%2).", extraMessage, responseCode ) ); + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); + return; + } + + if ( !mPrincipalPropertySearchSubJobSuccessful ) { + setError( 0 ); // nope, everything went fine + mPrincipalPropertySearchSubJobSuccessful = true; + } + + /* + * Extract infos from a document like the following: + * + * + * + * http://www.example.com/users/jdoe + * + * + * John Doe + * + * HTTP/1.1 200 OK + * + * + */ + + const QDomDocument document = davJob->response(); + const QDomElement documentElement = document.documentElement(); + + QDomElement responseElement = DavUtils::firstChildElementNS( documentElement, QLatin1String("DAV:"), QLatin1String("response") ); + if ( responseElement.isNull() ) { + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); + return; + } + + // check for the valid propstat, without giving up on first error + QDomElement propstatElement; + { + const QDomNodeList propstats = responseElement.elementsByTagNameNS( QLatin1String("DAV:"), QLatin1String("propstat") ); + for ( uint i = 0; i < propstats.length(); ++i ) { + const QDomElement propstatCandidate = propstats.item( i ).toElement(); + const QDomElement statusElement = DavUtils::firstChildElementNS( propstatCandidate, QLatin1String("DAV:"), QLatin1String("status") ); + if ( statusElement.text().contains( QLatin1String("200") ) ) { + propstatElement = propstatCandidate; + } + } + } + + if ( propstatElement.isNull() ) { + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); + return; + } + + QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop" )); + if ( propElement.isNull() ) { + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); + return; + } + + // All requested properties are now under propElement, so let's find them + typedef QPair PropertyPair; + foreach ( const PropertyPair &fetchProperty, mFetchProperties ) { + QDomNodeList fetchNodes = propElement.elementsByTagNameNS( fetchProperty.first, fetchProperty.second ); + for ( int i = 0; i < fetchNodes.size(); ++i ) { + QDomElement fetchElement = fetchNodes.at( i ).toElement(); + Result result; + result.propertyNamespace = fetchProperty.first; + result.property = fetchProperty.second; + result.value = fetchElement.text(); + mResults << result; + } + } + + if ( mPrincipalPropertySearchSubJobCount == 0 ) + emitResult(); +} + +QList< DavPrincipalSearchJob::Result > DavPrincipalSearchJob::results() const +{ + return mResults; +} + +void DavPrincipalSearchJob::buildReportQuery( QDomDocument& query ) +{ + /* + * Build a document like the following, where XXX will + * be replaced by the properties the user want to fetch: + * + * + * + * + * + * + * + * FILTER + * + * + * XXX + * + * + */ + + QDomElement principalPropertySearch = query.createElementNS( QLatin1String("DAV:"), QLatin1String("principal-property-search") ); + query.appendChild( principalPropertySearch ); + + QDomElement propertySearch = query.createElementNS( QLatin1String("DAV:"), QLatin1String("property-search") ); + principalPropertySearch.appendChild( propertySearch ); + + QDomElement prop = query.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propertySearch.appendChild( prop ); + + if ( mType == DisplayName ) { + QDomElement displayName = query.createElementNS( QLatin1String("DAV:"), QLatin1String("displayname") ); + prop.appendChild( displayName ); + } + else if ( mType == EmailAddress ) { + QDomElement calendarUserAddressSet = query.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-user-address-set") ); + prop.appendChild( calendarUserAddressSet ); + //QDomElement hrefElement = query.createElementNS( "DAV:", "href" ); + //prop.appendChild( hrefElement ); + } + + QDomElement match = query.createElementNS( QLatin1String("DAV:"), QLatin1String("match") ); + propertySearch.appendChild( match ); + + QDomText propFilter = query.createTextNode( mFilter ); + match.appendChild( propFilter ); + + prop = query.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + principalPropertySearch.appendChild( prop ); + + typedef QPair PropertyPair; + foreach ( const PropertyPair &fetchProperty, mFetchProperties ) { + QDomElement elem = query.createElementNS( fetchProperty.first, fetchProperty.second ); + prop.appendChild( elem ); + } +} diff --git a/kdepim-runtime/resources/dav/common/davprincipalsearchjob.h b/kdepim-runtime/resources/dav/common/davprincipalsearchjob.h new file mode 100644 index 00000000..d1466f4f --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davprincipalsearchjob.h @@ -0,0 +1,112 @@ +/* + Copyright (c) 2011 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVPRINCIPALSEARCHJOB_H +#define DAVPRINCIPALSEARCHJOB_H + +#include "davutils.h" + +#include +#include +#include + +#include + +/** + * @short A job that search a DAV principal on a server + * + * This job is used to search a principal on a server + * that implement the dav-property-search REPORT (RFC3744). + * + * The properties to fetch are set with @ref fetchProperty(). + */ +class DavPrincipalSearchJob : public KJob +{ + Q_OBJECT + + public: + /** + * Types of search that are supported by this job. + * DisplayName will match on the DAV displayname property. + * EmailAddress will match on the CalDav calendar-user-address-set property. + */ + enum FilterType { + DisplayName, + EmailAddress + }; + + /** + * Simple struct to hold the search job results + */ + struct Result { + QString propertyNamespace; + QString property; + QString value; + }; + + /** + * Creates a new dav principal search job + * + * @param url The URL to use in the REPORT query. + * @param type The type that the filter will match. + * @param filter The filter that will be used to match the displayname attribute. + * @param parent The parent object. + */ + explicit DavPrincipalSearchJob( const DavUtils::DavUrl &url, FilterType type, const QString &filter, QObject *parent = 0 ); + + /** + * Add a new property to fetch from the server. + * + * @param name The name of the property. + * @param ns The namespace of this property, defaults to 'DAV:'. + */ + void fetchProperty( const QString &name, const QString &ns = QString() ); + + /** + * Starts the job + */ + virtual void start(); + + /** + * Return the DavUrl used by this job + */ + DavUtils::DavUrl davUrl() const; + + /** + * Get the job results. + */ + QList results() const; + + private slots: + void principalCollectionSetSearchFinished( KJob *job ); + void principalPropertySearchFinished( KJob *job ); + + private: + void buildReportQuery( QDomDocument &query ); + + private: + DavUtils::DavUrl mUrl; + FilterType mType; + QString mFilter; + int mPrincipalPropertySearchSubJobCount; + bool mPrincipalPropertySearchSubJobSuccessful; + QList< QPair > mFetchProperties; + QList mResults; +}; + +#endif // DAVPRINCIPALSEARCHJOB_H diff --git a/kdepim-runtime/resources/dav/common/davprotocolbase.cpp b/kdepim-runtime/resources/dav/common/davprotocolbase.cpp new file mode 100644 index 00000000..9cf67919 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davprotocolbase.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davprotocolbase.h" + +DavProtocolBase::~DavProtocolBase() +{ +} + +QString DavProtocolBase::principalHomeSet() const +{ + return QString(); +} + +QString DavProtocolBase::principalHomeSetNS() const +{ + return QString(); +} diff --git a/kdepim-runtime/resources/dav/common/davprotocolbase.h b/kdepim-runtime/resources/dav/common/davprotocolbase.h new file mode 100644 index 00000000..9c79c7d7 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davprotocolbase.h @@ -0,0 +1,121 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVPROTOCOLBASE_H +#define DAVPROTOCOLBASE_H + +#include "davcollection.h" + +#include +#include + + +/** + * @short Base class for various DAV groupware dialects. + * + * This class provides an interface to query the DAV dialect + * specific features and abstract them. + * + * The functionality is implemented in: + * @li CaldavProtocol + * @li CarddavProtocol + * @li GroupdavProtocol + */ +class DavProtocolBase +{ + public: + /** + * Destroys the dav protocol base. + */ + virtual ~DavProtocolBase(); + + /** + * Returns whether the dav protocol dialect supports principal + * queries. If true, it must return the home set it provides + * access to with principalHomeSet() and the home set namespace + * with principalHomeSetNS(); + */ + virtual bool supportsPrincipals() const = 0; + + /** + * Returns whether the dav protocol dialect supports the REPORT + * command to query all resources of a collection. + * If not, PROPFIND command will be used instead. + */ + virtual bool useReport() const = 0; + + /** + * Returns whether the dav protocol dialect supports the MULTIGET command. + * + * If MULTIGET is supported, the content of all dav resources + * can be fetched in ResourceBase::retrieveItems() already and + * there is no need to call ResourceBase::retrieveItem() for every single + * dav resource. + * + * Protocols that have MULTIGET capabilities must inherit from + * DavMultigetProtocol instead of this class. + */ + virtual bool useMultiget() const = 0; + + /** + * Returns the home set that this protocol supports. + */ + virtual QString principalHomeSet() const; + + /** + * Returns the namespace of the home set. + */ + virtual QString principalHomeSetNS() const; + + /** + * Returns the XML document that represents the DAV query to + * list all available DAV collections. + */ + virtual QDomDocument collectionsQuery() const = 0; + + /** + * Returns the XQuery string that filters out the relevant XML elements + * from the result returned by the query that is provided by collectionQuery(). + */ + virtual QString collectionsXQuery() const = 0; + + /** + * Returns a list of XML documents that represent DAV queries to + * list all available DAV resources inside a specific DAV collection. + */ + virtual QList itemsQueries() const = 0; + + /** + * Returns the mime type of items fetched by query at index @p index + * in the list return by @ref itemsQueries(). + */ + virtual QString mimeTypeForQuery( int index ) const = 0; + + /** + * Returns the possible content types for the collection that + * is described by the passed @p propstat element of a PROPFIND result. + */ + virtual DavCollection::ContentTypes collectionContentTypes( const QDomElement &propstat ) const = 0; + + /** + * Returns the mimetype that shall be used for contact DAV resources. + */ + virtual QString contactsMimeType() const = 0; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/common/davutils.cpp b/kdepim-runtime/resources/dav/common/davutils.cpp new file mode 100644 index 00000000..65d6cea8 --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davutils.cpp @@ -0,0 +1,365 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davutils.h" +#include "davitem.h" +#include "davmanager.h" +#include "davprotocolattribute.h" +#include "davprotocolbase.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef QSharedPointer IncidencePtr; + +QDomElement DavUtils::firstChildElementNS( const QDomElement &parent, const QString &namespaceUri, const QString &tagName ) +{ + for ( QDomNode child = parent.firstChild(); !child.isNull(); child = child.nextSibling() ) { + if ( child.isElement() ) { + const QDomElement elt = child.toElement(); + if ( tagName.isEmpty() || ( elt.tagName() == tagName && elt.namespaceURI() == namespaceUri ) ) + return elt; + } + } + + return QDomElement(); +} + +QDomElement DavUtils::nextSiblingElementNS( const QDomElement &element, const QString &namespaceUri, const QString &tagName ) +{ + for ( QDomNode sib = element.nextSibling(); !sib.isNull(); sib = sib.nextSibling() ) { + if ( sib.isElement() ) { + const QDomElement elt = sib.toElement(); + if ( tagName.isEmpty() || ( elt.tagName() == tagName && elt.namespaceURI() == namespaceUri ) ) + return elt; + } + } + + return QDomElement(); +} + +DavUtils::Privileges DavUtils::extractPrivileges( const QDomElement &element ) +{ + Privileges final = None; + QDomElement privElement = firstChildElementNS( element, QLatin1String("DAV:"), QLatin1String("privilege")); + + while ( !privElement.isNull() ) { + QDomElement child = privElement.firstChildElement(); + + while ( !child.isNull() ) { + final |= parsePrivilege( child ); + child = child.nextSiblingElement(); + } + + privElement = DavUtils::nextSiblingElementNS( privElement, QLatin1String("DAV:"), QLatin1String("privilege") ); + } + + return final; +} + +DavUtils::Privileges DavUtils::parsePrivilege( const QDomElement &element ) +{ + Privileges final = None; + + if ( !element.childNodes().isEmpty() ) { + // This is an aggregate privilege, parse each of its children + QDomElement child = element.firstChildElement(); + while ( !child.isNull() ) { + final |= parsePrivilege( child ); + child = child.nextSiblingElement(); + } + } + else { + // This is a normal privilege + const QString privname = element.localName(); + + if ( privname == QLatin1String("read") ) + final |= DavUtils::Read; + else if ( privname == QLatin1String("write") ) + final |= DavUtils::Write; + else if ( privname == QLatin1String("write-properties") ) + final |= DavUtils::WriteProperties; + else if ( privname == QLatin1String("write-content") ) + final |= DavUtils::WriteContent; + else if ( privname == QLatin1String("unlock") ) + final |= DavUtils::Unlock; + else if ( privname == QLatin1String("read-acl") ) + final |= DavUtils::ReadAcl; + else if ( privname == QLatin1String("read-current-user-privilege-set") ) + final |= DavUtils::ReadCurrentUserPrivilegeSet; + else if ( privname == QLatin1String("write-acl") ) + final |= DavUtils::WriteAcl; + else if ( privname == QLatin1String("bind") ) + final |= DavUtils::Bind; + else if ( privname == QLatin1String("unbind") ) + final |= DavUtils::Unbind; + else if ( privname == QLatin1String("all") ) + final |= DavUtils::All; + } + + return final; +} + +DavUtils::DavUrl::DavUrl() + : mProtocol( CalDav ) +{ +} + +DavUtils::DavUrl::DavUrl( const KUrl &url, DavUtils::Protocol protocol ) + : mUrl( url ), mProtocol( protocol ) +{ +} + +void DavUtils::DavUrl::setUrl( const KUrl &url ) +{ + mUrl = url; +} + +KUrl DavUtils::DavUrl::url() const +{ + return mUrl; +} + +void DavUtils::DavUrl::setProtocol( DavUtils::Protocol protocol ) +{ + mProtocol = protocol; +} + +DavUtils::Protocol DavUtils::DavUrl::protocol() const +{ + return mProtocol; +} + +QLatin1String DavUtils::protocolName( DavUtils::Protocol protocol ) +{ + QLatin1String protocolName( "" ); + + switch( protocol ) { + case DavUtils::CalDav: + protocolName = QLatin1String( "CalDav" ); + break; + case DavUtils::CardDav: + protocolName = QLatin1String( "CardDav" ); + break; + case DavUtils::GroupDav: + protocolName = QLatin1String( "GroupDav" ); + break; + } + + return protocolName; +} + +QString DavUtils::translatedProtocolName( DavUtils::Protocol protocol ) +{ + QString protocolName; + + switch( protocol ) { + case DavUtils::CalDav: + protocolName = i18n( "CalDav" ); + break; + case DavUtils::CardDav: + protocolName = i18n( "CardDav" ); + break; + case DavUtils::GroupDav: + protocolName = i18n( "GroupDav" ); + break; + } + + return protocolName; +} + +DavUtils::Protocol DavUtils::protocolByName( const QString &name ) +{ + DavUtils::Protocol protocol = DavUtils::CalDav; + + if ( name == QLatin1String("CalDav") ) { + protocol = DavUtils::CalDav; + } else if ( name == QLatin1String("CardDav") ) { + protocol = DavUtils::CardDav; + } else if ( name == QLatin1String("GroupDav") ) { + protocol = DavUtils::GroupDav; + } else { + kError() << "Unexpected protocol name : " << name; + } + + return protocol; +} + +DavUtils::Protocol DavUtils::protocolByTranslatedName( const QString &name ) +{ + DavUtils::Protocol protocol; + + if ( name == i18n( "CalDav" ) ) { + protocol = DavUtils::CalDav; + } else if ( name == i18n( "CardDav" ) ) { + protocol = DavUtils::CardDav; + } else if ( name == i18n( "GroupDav" ) ) { + protocol = DavUtils::GroupDav; + } + + return protocol; +} + +QString DavUtils::createUniqueId() +{ + qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000; + int r = qrand() % 1000; + QString id = QLatin1String( "R" ) + QString::number( r ); + QString uid = QString::number( time ) + QLatin1String( "." ) + id; + return uid; +} + +DavItem DavUtils::createDavItem( const Akonadi::Item &item, const Akonadi::Collection &collection, const Akonadi::Item::List &dependentItems ) +{ + QByteArray rawData; + QString mimeType; + KUrl url; + DavItem davItem; + const QString basePath = collection.remoteId(); + + if ( item.hasPayload() ) { + const KABC::Addressee contact = item.payload(); + const QString fileName = createUniqueId(); + + url = KUrl( basePath + fileName + QLatin1String(".vcf") ); + + const DavProtocolAttribute *protoAttr = collection.attribute(); + if ( protoAttr ) { + mimeType = + DavManager::self()->davProtocol( + DavUtils::Protocol( protoAttr->davProtocol() ) )->contactsMimeType(); + } else { + mimeType = KABC::Addressee::mimeType(); + } + + KABC::VCardConverter converter; + // rawData is already UTF-8 + rawData = converter.exportVCard( contact, KABC::VCardConverter::v3_0 ); + } else if ( item.hasPayload() ) { + const KCalCore::MemoryCalendar::Ptr calendar( new KCalCore::MemoryCalendar( KDateTime::LocalZone ) ); + calendar->addIncidence( item.payload() ); + foreach ( const Akonadi::Item &dependentItem, dependentItems ) { + calendar->addIncidence( dependentItem.payload() ); + } + + const QString fileName = createUniqueId(); + + url = KUrl( basePath + fileName + QLatin1String(".ics") ); + mimeType = QLatin1String("text/calendar"); + + KCalCore::ICalFormat formatter; + rawData = formatter.toString( calendar, QString() ).toUtf8(); + } + + davItem.setContentType( mimeType ); + davItem.setData( rawData ); + davItem.setUrl( url.prettyUrl() ); + davItem.setEtag( item.remoteRevision() ); + + return davItem; +} + +bool DavUtils::parseDavData( const DavItem &source, Akonadi::Item &target, Akonadi::Item::List &extraItems ) +{ + const QString data = QString::fromUtf8( source.data() ); + + if ( target.mimeType() == KABC::Addressee::mimeType() ) { + KABC::VCardConverter converter; + const KABC::Addressee contact = converter.parseVCard( source.data() ); + + if ( contact.isEmpty() ) + return false; + + target.setPayloadFromData( source.data() ); + } else { + KCalCore::ICalFormat formatter; + const KCalCore::MemoryCalendar::Ptr calendar( new KCalCore::MemoryCalendar( KDateTime::LocalZone ) ); + formatter.fromString( calendar, data ); + KCalCore::Incidence::List incidences = calendar->incidences(); + + if ( incidences.isEmpty() ) + return false; + + // All items must have the same uid in a single object. + // Find the main VEVENT (if that's indeed what we have, + // could be a VTODO or a VJOURNAL but that doesn't matter) + // and then apply the recurrence exceptions + IncidencePtr mainIncidence; + KCalCore::Incidence::List exceptions; + + foreach ( const IncidencePtr &incidence, incidences ) { + if ( incidence->hasRecurrenceId() ) { + kDebug() << "Exception found with ID" << incidence->instanceIdentifier(); + exceptions << incidence; + } + else { + mainIncidence = incidence; + } + } + + if ( !mainIncidence ) + return false; + + foreach ( const IncidencePtr &exception, exceptions ) { + if ( exception->status() == KCalCore::Incidence::StatusCanceled ) { + KDateTime exDateTime = exception->recurrenceId(); + mainIncidence->recurrence()->addExDateTime( exDateTime ); + } + else { + // The exception remote id will contain a fragment pointing to + // its instance identifier to distinguish it from the main + // event. + QString rid = target.remoteId() + QLatin1String( "#" ) + exception->instanceIdentifier(); + kDebug() << "Extra incidence at" << rid; + Akonadi::Item extraItem = target; + extraItem.setRemoteId( rid ); + extraItem.setRemoteRevision( source.etag() ); + extraItem.setMimeType( exception->mimeType() ); + extraItem.setPayload( exception ); + extraItems << extraItem; + } + } + + target.setPayload( mainIncidence ); + // fix mime type for CalDAV collections + target.setMimeType( mainIncidence->mimeType() ); + + /* + foreach ( const IncidencePtr &incidence, incidences ) { + QString rid = item.remoteId() + QLatin1String( "#" ) + incidence->instanceIdentifier(); + Akonadi::Item extraItem = item; + extraItem.setRemoteId( rid ); + extraItem.setRemoteRevision( davItem.etag() ); + extraItem.setMimeType( incidence->mimeType() ); + extraItem.setPayload( incidence ); + items << extraItem; + } + */ + } + + return true; +} diff --git a/kdepim-runtime/resources/dav/common/davutils.h b/kdepim-runtime/resources/dav/common/davutils.h new file mode 100644 index 00000000..591b3d7a --- /dev/null +++ b/kdepim-runtime/resources/dav/common/davutils.h @@ -0,0 +1,179 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVUTILS_H +#define DAVUTILS_H + +#include +#include + +#include + +#include + +class DavItem; +namespace Akonadi { + class Collection; + class Item; +} + +/** + * @short A namespace that contains helper methods for DAV functionality. + */ +namespace DavUtils +{ + /** + * Describes the DAV protocol dialect. + */ + enum Protocol { + CalDav = 0, ///< The CalDav protocol as defined in http://caldav.calconnect.org + CardDav, ///< The CardDav protocol as defined in http://carddav.calconnect.org + GroupDav ///< The GroupDav protocol as defined in http://www.groupdav.org + }; + + /** + * Describes the DAV privileges on a resource (see RFC3744) + */ + enum Privilege { + None = 0x0, + Read = 0x1, + Write = 0x2, + WriteProperties = 0x4, + WriteContent = 0x8, + Unlock = 0x10, + ReadAcl = 0x20, + ReadCurrentUserPrivilegeSet = 0x40, + WriteAcl = 0x80, + Bind = 0x100, + Unbind = 0x200, + All = 0x400 + }; + Q_DECLARE_FLAGS( Privileges, Privilege ) + Q_DECLARE_OPERATORS_FOR_FLAGS( Privileges ) + + /** + * Returns the untranslated name of the given DAV @p protocol dialect. + */ + QLatin1String protocolName( Protocol protocol ); + + /** + * Returns the i18n'ed name of the given DAV @p protocol dialect. + */ + QString translatedProtocolName( Protocol protocol ); + + /** + * Returns the protocol matching the given name. This is the opposite of + * DavUtils::protocolName(). + */ + Protocol protocolByName( const QString &name ); + + /** + * Returns the protocol matching the given i18n'ed @p name. This is the opposite + * of DavUtils::translatedProtocolName(). + */ + Protocol protocolByTranslatedName( const QString &name ); + + /** + * @short A helper class to combine url and protocol of a DAV url. + */ + class DavUrl + { + public: + /** + * Defines a list of DAV url objects. + */ + typedef QList List; + + /** + * Creates an empty DAV url. + */ + DavUrl(); + + /** + * Creates a new DAV url. + * + * @param url The url that identifies the DAV object. + * @param protocol The DAV protocol dialect that is used to retrieve the DAV object. + */ + DavUrl( const KUrl &url, Protocol protocol ); + + /** + * Sets the @p url that identifies the DAV object. + */ + void setUrl( const KUrl &url ); + + /** + * Returns the url that identifies the DAV object. + */ + KUrl url() const; + + /** + * Sets the DAV @p protocol dialect that is used to retrieve the DAV object. + */ + void setProtocol( Protocol protocol ); + + /** + * Returns the DAV protocol dialect that is used to retrieve the DAV object. + */ + Protocol protocol() const; + + private: + KUrl mUrl; + Protocol mProtocol; + }; + + /** + * Returns the first child element of @p parent that has the given @p tagName and is part of the @p namespaceUri. + */ + QDomElement firstChildElementNS( const QDomElement &parent, const QString &namespaceUri, const QString &tagName ); + + /** + * Returns the next sibling element of @p element that has the given @p tagName and is part of the @p namespaceUri. + */ + QDomElement nextSiblingElementNS( const QDomElement &element, const QString &namespaceUri, const QString &tagName ); + + /** + * Extracts privileges from @p element. The tags are expected to be first level children of @p element. + */ + Privileges extractPrivileges( const QDomElement &element ); + + /** + * Parses a single tag and returns the final Privileges. + */ + Privileges parsePrivilege( const QDomElement &element ); + + /** + * Creates a unique identifier that can be used as a file name to upload the dav item + */ + QString createUniqueId(); + + /** + * Creates a new DavItem from the Akonadi::Item @p item. + * + * The returned item will have no payload (DavItem::data() will return an empty + * QByteArray) if the @p item payload is not recognized. + */ + DavItem createDavItem( const Akonadi::Item &item, const Akonadi::Collection &collection, const Akonadi::Item::List &dependentItems = Akonadi::Item::List() ); + + /** + * Parses the DAV data contained in @p source and puts it in @p target and @extraItems. + */ + bool parseDavData( const DavItem &source, Akonadi::Item &target, Akonadi::Item::List &extraItems ); +} + +#endif diff --git a/kdepim-runtime/resources/dav/common/etagcache.cpp b/kdepim-runtime/resources/dav/common/etagcache.cpp new file mode 100644 index 00000000..0cc6207a --- /dev/null +++ b/kdepim-runtime/resources/dav/common/etagcache.cpp @@ -0,0 +1,97 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "etagcache.h" + +#include +#include +#include +#include +#include +#include + +EtagCache::EtagCache() +{ +} + +void EtagCache::sync( const Akonadi::Collection &collection ) +{ + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( collection ); + job->fetchScope().fetchFullPayload( false ); // We only need the remote id and the revision + connect( job, SIGNAL(result(KJob*)), this, SLOT(onItemFetchJobFinished(KJob*)) ); + job->start(); +} + +void EtagCache::setEtag( const QString &remoteId, const QString &etag ) +{ + mCache[ remoteId ] = etag; + + if ( mChangedRemoteIds.contains( remoteId ) ) + mChangedRemoteIds.remove( remoteId ); +} + +bool EtagCache::contains( const QString &remoteId ) +{ + return mCache.contains( remoteId ); +} + +bool EtagCache::etagChanged( const QString &remoteId, const QString &refEtag ) +{ + return mCache.value( remoteId ) != refEtag; +} + +void EtagCache::markAsChanged( const QString &remoteId ) +{ + mChangedRemoteIds.insert( remoteId ); +} + +bool EtagCache::isOutOfDate( const QString &remoteId ) const +{ + return mChangedRemoteIds.contains( remoteId ); +} + +void EtagCache::removeEtag( const QString &remoteId ) +{ + mChangedRemoteIds.remove( remoteId ); + mCache.remove( remoteId ); +} + +QStringList EtagCache::etags() const +{ + return mCache.keys(); +} + +QStringList EtagCache::changedRemoteIds() const +{ + return mChangedRemoteIds.toList(); +} + +void EtagCache::onItemFetchJobFinished( KJob *job ) +{ + if ( job->error() ) + return; + + const Akonadi::ItemFetchJob *fetchJob = qobject_cast( job ); + const Akonadi::Item::List items = fetchJob->items(); + + foreach ( const Akonadi::Item &item, items ) { + if ( !mCache.contains( item.remoteId() ) ) + mCache[item.remoteId()] = item.remoteRevision(); + } +} + diff --git a/kdepim-runtime/resources/dav/common/etagcache.h b/kdepim-runtime/resources/dav/common/etagcache.h new file mode 100644 index 00000000..aa60864a --- /dev/null +++ b/kdepim-runtime/resources/dav/common/etagcache.h @@ -0,0 +1,108 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef ETAGCACHE_H +#define ETAGCACHE_H + +#include +#include +#include + +namespace Akonadi { + class Collection; +} + +class KJob; + +/** + * @short A helper class to cache etags. + * + * The EtagCache caches the remote ids and etags of all items + * that are known to the resource. This cache is needed to find + * out which items have been changed in the backend and have to + * be refetched on the next call of ResourceBase::retrieveItems() + */ +class EtagCache : public QObject +{ + Q_OBJECT + + public: + /** + * Creates a new etag cache. + */ + EtagCache(); + + /** + * Populates the cache with the items found in @p collection. + */ + void sync( const Akonadi::Collection &collection ); + + /** + * Sets the ETag for the remote ID. If the remote ID is marked as + * changed (is contained in the return of changedRemoteIds), remove + * it from the changed list. + */ + void setEtag( const QString &remoteId, const QString &etag ); + + /** + * Checks if the given item is in the cache + */ + bool contains( const QString &remoteId ); + + /** + * Check if the known ETag for the remote ID is equal to @p refEtag and, if not, + * mark it as changed. + */ + bool etagChanged( const QString &remoteId, const QString &refEtag ); + + /** + * Mark an item as changed in the backend. + */ + void markAsChanged( const QString &remoteId ); + + /** + * Returns true if the remote ID is marked as changed (is contained in the + * return of changedRemoteIds) + */ + bool isOutOfDate( const QString &remoteId ) const; + + /** + * Removes the entry for item with remote ID @p remoteId. + */ + void removeEtag( const QString &remoteId ); + + /** + * Returns the list of all etags. + */ + QStringList etags() const; + + /** + * Returns the list of remote ids of items that have been changed + * in the backend but not been refetched. + */ + QStringList changedRemoteIds() const; + + private Q_SLOTS: + void onItemFetchJobFinished( KJob *job ); + + private: + QMap mCache; + QSet mChangedRemoteIds; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/protocols/caldavprotocol.cpp b/kdepim-runtime/resources/dav/protocols/caldavprotocol.cpp new file mode 100644 index 00000000..06b76867 --- /dev/null +++ b/kdepim-runtime/resources/dav/protocols/caldavprotocol.cpp @@ -0,0 +1,373 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "caldavprotocol.h" +#include "davutils.h" + +#include +#include +#include +#include + +#include +#include +#include + +CaldavProtocol::CaldavProtocol() +{ + // Only fetch items for the last 3 months + QString startTime = QDateTime::currentDateTimeUtc().addMonths( -3 ).toString( "yyyyMMddTHHMMssZ" ); + + /* + * Create a document like the following: + * + * + * + * + * + * + * + * + * + * + * + * + */ + { + QDomDocument document; + + QDomElement queryElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-query") ); + document.appendChild( queryElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + queryElement.appendChild( propElement ); + + QDomElement getetagElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("getetag") ); + propElement.appendChild( getetagElement ); + + QDomElement getRTypeElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype") ); + propElement.appendChild( getRTypeElement ); + + QDomElement filterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("filter") ); + queryElement.appendChild( filterElement ); + + QDomElement compfilterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp-filter") ); + + QDomAttr nameAttribute = document.createAttribute( QLatin1String("name") ); + nameAttribute.setValue( QLatin1String("VCALENDAR") ); + compfilterElement.setAttributeNode( nameAttribute ); + filterElement.appendChild( compfilterElement ); + + QDomElement subcompfilterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp-filter") ); + nameAttribute = document.createAttribute( QLatin1String("name") ); + nameAttribute.setValue( QLatin1String("VEVENT") ); + subcompfilterElement.setAttributeNode( nameAttribute ); + + QDomElement timeRangeElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("time-range") ); + QDomAttr startAttribute = document.createAttribute( QLatin1String("start") ); + startAttribute.setValue( startTime ); + timeRangeElement.setAttributeNode( startAttribute ); + subcompfilterElement.appendChild( timeRangeElement ); + + compfilterElement.appendChild( subcompfilterElement ); + + mItemsQueries << document; + mItemsMimeTypes << KCalCore::Event::eventMimeType(); + } + + /* + * Create a document like the following: + * + * + * + * + * + * + * + * + * + * + * + * + */ + { + QDomDocument document; + + QDomElement queryElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-query") ); + document.appendChild( queryElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + queryElement.appendChild( propElement ); + + QDomElement getetagElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("getetag") ); + propElement.appendChild( getetagElement ); + + QDomElement getRTypeElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype") ); + propElement.appendChild( getRTypeElement ); + + QDomElement filterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("filter") ); + queryElement.appendChild( filterElement ); + + QDomElement compfilterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp-filter") ); + + QDomAttr nameAttribute = document.createAttribute( QLatin1String("name") ); + nameAttribute.setValue( QLatin1String("VCALENDAR") ); + compfilterElement.setAttributeNode( nameAttribute ); + filterElement.appendChild( compfilterElement ); + + QDomElement subcompfilterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp-filter") ); + nameAttribute = document.createAttribute( QLatin1String("name") ); + nameAttribute.setValue( QLatin1String("VTODO") ); + subcompfilterElement.setAttributeNode( nameAttribute ); + + QDomElement timeRangeElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("time-range") ); + QDomAttr startAttribute = document.createAttribute( QLatin1String("start") ); + startAttribute.setValue( startTime ); + timeRangeElement.setAttributeNode( startAttribute ); + subcompfilterElement.appendChild( timeRangeElement ); + + compfilterElement.appendChild( subcompfilterElement ); + + mItemsQueries << document; + mItemsMimeTypes << KCalCore::Todo::todoMimeType(); + } + + /* + * Create a document like the following: + * + * + * + * + * + * + * + * + * + * + * + * + */ + { + QDomDocument document; + + QDomElement queryElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-query") ); + document.appendChild( queryElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"),QLatin1String( "prop") ); + queryElement.appendChild( propElement ); + + QDomElement getetagElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("getetag") ); + propElement.appendChild( getetagElement ); + + QDomElement getRTypeElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype") ); + propElement.appendChild( getRTypeElement ); + + QDomElement filterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("filter") ); + queryElement.appendChild( filterElement ); + + QDomElement compfilterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp-filter") ); + + QDomAttr nameAttribute = document.createAttribute( QLatin1String("name") ); + nameAttribute.setValue( QLatin1String("VCALENDAR") ); + compfilterElement.setAttributeNode( nameAttribute ); + filterElement.appendChild( compfilterElement ); + + QDomElement subcompfilterElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp-filter") ); + nameAttribute = document.createAttribute( QLatin1String("name") ); + nameAttribute.setValue( QLatin1String("VJOURNAL") ); + subcompfilterElement.setAttributeNode( nameAttribute ); + + QDomElement timeRangeElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("time-range") ); + QDomAttr startAttribute = document.createAttribute( QLatin1String("start") ); + startAttribute.setValue( startTime ); + timeRangeElement.setAttributeNode( startAttribute ); + subcompfilterElement.appendChild( timeRangeElement ); + + compfilterElement.appendChild( subcompfilterElement ); + + mItemsQueries << document; + mItemsMimeTypes << KCalCore::Journal::journalMimeType(); + } +} + +bool CaldavProtocol::supportsPrincipals() const +{ + return true; +} + +bool CaldavProtocol::useReport() const +{ + return true; +} + +bool CaldavProtocol::useMultiget() const +{ + return true; +} + +QString CaldavProtocol::principalHomeSet() const +{ + return QLatin1String( "calendar-home-set" ); +} + +QString CaldavProtocol::principalHomeSetNS() const +{ + return QLatin1String( "urn:ietf:params:xml:ns:caldav" ); +} + +QDomDocument CaldavProtocol::collectionsQuery() const +{ + QDomDocument document; + + QDomElement propfindElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + document.appendChild( propfindElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propfindElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("displayname") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype" )) ); + propElement.appendChild( document.createElementNS( QLatin1String("http://apple.com/ns/ical/"), QLatin1String("calendar-color") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("supported-calendar-component-set") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("current-user-privilege-set") ) ); + + return document; +} + +QString CaldavProtocol::collectionsXQuery() const +{ + //const QString query( "//*[local-name()='calendar' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/ancestor::*[local-name()='prop' and namespace-uri()='DAV:']/*[local-name()='supported-calendar-component-set' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/*[local-name()='comp' and namespace-uri()='urn:ietf:params:xml:ns:caldav' and (@name='VTODO' or @name='VEVENT')]/ancestor::*[local-name()='response' and namespace-uri()='DAV:']" ); + const QString query( QLatin1String("//*[local-name()='calendar' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/ancestor::*[local-name()='prop' and namespace-uri()='DAV:']/ancestor::*[local-name()='response' and namespace-uri()='DAV:']") ); + + return query; +} + +QList CaldavProtocol::itemsQueries() const +{ + return mItemsQueries; +} + +QString CaldavProtocol::mimeTypeForQuery( int index ) const +{ + return mItemsMimeTypes.at( index ); +} + +QDomDocument CaldavProtocol::itemsReportQuery( const QStringList &urls ) const +{ + QDomDocument document; + + QDomElement multigetElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-multiget") ); + document.appendChild( multigetElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + multigetElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"),QLatin1String("getetag") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-data") ) ); + + foreach ( const QString &url, urls ) { + QDomElement hrefElement = document.createElementNS(QLatin1String( "DAV:"), QLatin1String("href") ); + const KUrl pathUrl( url ); + + const QDomText textNode = document.createTextNode( pathUrl.encodedPathAndQuery() ); + hrefElement.appendChild( textNode ); + + multigetElement.appendChild( hrefElement ); + } + + return document; +} + +QString CaldavProtocol::responseNamespace() const +{ + return QLatin1String("urn:ietf:params:xml:ns:caldav"); +} + +QString CaldavProtocol::dataTagName() const +{ + return QLatin1String("calendar-data"); +} + +DavCollection::ContentTypes CaldavProtocol::collectionContentTypes( const QDomElement &propstatElement ) const +{ + /* + * Extract the content type information from a propstat like the following + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Test1 User + * + * HTTP/1.1 200 OK + * + */ + + const QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + const QDomElement supportedcomponentElement = DavUtils::firstChildElementNS( propElement, QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("supported-calendar-component-set") ); + + DavCollection::ContentTypes contentTypes; + QDomElement compElement = DavUtils::firstChildElementNS( supportedcomponentElement, QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp") ); + + /* + * Assign the content-type if the server didn't return anything. + * According to RFC4791, §5.2.3: + * In the absence of this property, the server MUST accept all + * component types, and the client can assume that all component + * types are accepted. + */ + if ( compElement.isNull() ) { + contentTypes |= DavCollection::Calendar; + contentTypes |= DavCollection::Events; + contentTypes |= DavCollection::Todos; + contentTypes |= DavCollection::FreeBusy; + contentTypes |= DavCollection::Journal; + } + + while ( !compElement.isNull() ) { + const QString type = compElement.attribute( QLatin1String("name") ).toLower(); + if ( type == QLatin1String( "vcalendar" ) ) + contentTypes |= DavCollection::Calendar; + else if ( type == QLatin1String( "vevent" ) ) + contentTypes |= DavCollection::Events; + else if ( type == QLatin1String( "vtodo" ) ) + contentTypes |= DavCollection::Todos; + else if ( type == QLatin1String( "vfreebusy" ) ) + contentTypes |= DavCollection::FreeBusy; + else if ( type == QLatin1String( "vjournal" ) ) + contentTypes |= DavCollection::Journal; + + compElement = DavUtils::nextSiblingElementNS( compElement, QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("comp") ); + } + + return contentTypes; +} + +QString CaldavProtocol::contactsMimeType() const +{ + return QString(); // will never be called for this protocol +} diff --git a/kdepim-runtime/resources/dav/protocols/caldavprotocol.h b/kdepim-runtime/resources/dav/protocols/caldavprotocol.h new file mode 100644 index 00000000..cb4fb880 --- /dev/null +++ b/kdepim-runtime/resources/dav/protocols/caldavprotocol.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef CALDAVPROTOCOL_H +#define CALDAVPROTOCOL_H + +#include "davmultigetprotocol.h" + +class CaldavProtocol : public DavMultigetProtocol +{ + public: + CaldavProtocol(); + virtual bool supportsPrincipals() const; + virtual bool useReport() const; + virtual bool useMultiget() const; + virtual QString principalHomeSet() const; + virtual QString principalHomeSetNS() const; + virtual QDomDocument collectionsQuery() const; + virtual QString collectionsXQuery() const; + virtual QList itemsQueries() const; + virtual QString mimeTypeForQuery( int index ) const; + virtual QDomDocument itemsReportQuery( const QStringList &urls ) const; + virtual QString responseNamespace() const; + virtual QString dataTagName() const; + + virtual DavCollection::ContentTypes collectionContentTypes( const QDomElement &propstat ) const; + virtual QString contactsMimeType() const; + + private: + QList mItemsQueries; + QStringList mItemsMimeTypes; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/protocols/carddavprotocol.cpp b/kdepim-runtime/resources/dav/protocols/carddavprotocol.cpp new file mode 100644 index 00000000..7979e27c --- /dev/null +++ b/kdepim-runtime/resources/dav/protocols/carddavprotocol.cpp @@ -0,0 +1,148 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "carddavprotocol.h" + +#include + +#include +#include + +CarddavProtocol::CarddavProtocol() +{ + QDomDocument document; + + QDomElement propfindElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + document.appendChild( propfindElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propfindElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("displayname") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("getetag") ) ); + + mItemsQueries << document; + mItemsMimeTypes << KABC::Addressee::mimeType(); +} + +bool CarddavProtocol::supportsPrincipals() const +{ + return true; +} + +bool CarddavProtocol::useReport() const +{ + return false; +} + +bool CarddavProtocol::useMultiget() const +{ + return true; +} + +QString CarddavProtocol::principalHomeSet() const +{ + return QLatin1String( "addressbook-home-set" ); +} + +QString CarddavProtocol::principalHomeSetNS() const +{ + return QLatin1String( "urn:ietf:params:xml:ns:carddav" ); +} + +QDomDocument CarddavProtocol::collectionsQuery() const +{ + QDomDocument document; + + QDomElement propfindElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + document.appendChild( propfindElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propfindElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("displayname") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype") ) ); + + return document; +} + +QString CarddavProtocol::collectionsXQuery() const +{ + const QString query( QLatin1String("//*[local-name()='addressbook' and namespace-uri()='urn:ietf:params:xml:ns:carddav']/ancestor::*[local-name()='response' and namespace-uri()='DAV:']") ); + + return query; +} + +QList CarddavProtocol::itemsQueries() const +{ + return mItemsQueries; +} + +QString CarddavProtocol::mimeTypeForQuery( int index ) const +{ + return mItemsMimeTypes.at( index ); +} + +QDomDocument CarddavProtocol::itemsReportQuery( const QStringList &urls ) const +{ + QDomDocument document; + + QDomElement multigetElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:carddav"), QLatin1String("addressbook-multiget") ); + document.appendChild( multigetElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + multigetElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("getetag") ) ); + QDomElement addressDataElement = document.createElementNS( QLatin1String("urn:ietf:params:xml:ns:carddav"), QLatin1String("address-data") ); + addressDataElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("allprop") ) ); + propElement.appendChild( addressDataElement ); + + foreach ( const QString &url, urls ) { + QDomElement hrefElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("href") ); + const KUrl pathUrl( url ); + + const QDomText textNode = document.createTextNode( pathUrl.encodedPathAndQuery() ); + hrefElement.appendChild( textNode ); + + multigetElement.appendChild( hrefElement ); + } + + return document; +} + +QString CarddavProtocol::responseNamespace() const +{ + return QLatin1String("urn:ietf:params:xml:ns:carddav"); +} + +QString CarddavProtocol::dataTagName() const +{ + return QLatin1String("address-data"); +} + +DavCollection::ContentTypes CarddavProtocol::collectionContentTypes( const QDomElement& ) const +{ + return DavCollection::Contacts; +} + +QString CarddavProtocol::contactsMimeType() const +{ + return QLatin1String( "text/vcard" ); +} diff --git a/kdepim-runtime/resources/dav/protocols/carddavprotocol.h b/kdepim-runtime/resources/dav/protocols/carddavprotocol.h new file mode 100644 index 00000000..683928db --- /dev/null +++ b/kdepim-runtime/resources/dav/protocols/carddavprotocol.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef CARDDAVPROTOCOL_H +#define CARDDAVPROTOCOL_H + +#include "davmultigetprotocol.h" + +class CarddavProtocol : public DavMultigetProtocol +{ + public: + CarddavProtocol(); + virtual bool supportsPrincipals() const; + virtual bool useReport() const; + virtual bool useMultiget() const; + virtual QString principalHomeSet() const; + virtual QString principalHomeSetNS() const; + virtual QDomDocument collectionsQuery() const; + virtual QString collectionsXQuery() const; + virtual QList itemsQueries() const; + virtual QString mimeTypeForQuery( int index ) const; + virtual QDomDocument itemsReportQuery( const QStringList &urls ) const; + virtual QString responseNamespace() const; + virtual QString dataTagName() const; + + virtual DavCollection::ContentTypes collectionContentTypes( const QDomElement &propstat ) const; + virtual QString contactsMimeType() const; + + private: + QList mItemsQueries; + QStringList mItemsMimeTypes; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/protocols/groupdavprotocol.cpp b/kdepim-runtime/resources/dav/protocols/groupdavprotocol.cpp new file mode 100644 index 00000000..c0ef6c4f --- /dev/null +++ b/kdepim-runtime/resources/dav/protocols/groupdavprotocol.cpp @@ -0,0 +1,128 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "groupdavprotocol.h" + +#include "davutils.h" + +#include + +GroupdavProtocol::GroupdavProtocol() +{ + QDomDocument document; + + QDomElement propfindElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + document.appendChild( propfindElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propfindElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("displayname") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype" )) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("getetag") ) ); + + mItemsQueries << document; +} + +bool GroupdavProtocol::supportsPrincipals() const +{ + return false; +} + +bool GroupdavProtocol::useReport() const +{ + return false; +} + +bool GroupdavProtocol::useMultiget() const +{ + return false; +} + +QDomDocument GroupdavProtocol::collectionsQuery() const +{ + QDomDocument document; + + QDomElement propfindElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("propfind") ); + document.appendChild( propfindElement ); + + QDomElement propElement = document.createElementNS( QLatin1String("DAV:"), QLatin1String("prop") ); + propfindElement.appendChild( propElement ); + + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("displayname") ) ); + propElement.appendChild( document.createElementNS( QLatin1String("DAV:"), QLatin1String("resourcetype") ) ); + + return document; +} + +QString GroupdavProtocol::collectionsXQuery() const +{ + const QString query( QLatin1String("//*[(local-name()='vevent-collection' or local-name()='vtodo-collection' or local-name()='vcard-collection') and namespace-uri()='http://groupdav.org/']/ancestor::*[local-name()='response' and namespace-uri()='DAV:']") ); + + return query; +} + +QList GroupdavProtocol::itemsQueries() const +{ + return mItemsQueries; +} + +QString GroupdavProtocol::mimeTypeForQuery( int index ) const +{ + return QString(); +} + +DavCollection::ContentTypes GroupdavProtocol::collectionContentTypes( const QDomElement &propstatElement ) const +{ + /* + * Extract the content type information from a propstat like the following + * + * + * HTTP/1.1 200 OK + * + * Tasks + * + * + * + * + * Sat, 30 Jan 2010 17:52:41 -0100 + * + * + */ + + const QDomElement propElement = DavUtils::firstChildElementNS( propstatElement, QLatin1String("DAV:"), QLatin1String("prop") ); + const QDomElement resourcetypeElement = DavUtils::firstChildElementNS( propElement, QLatin1String("DAV:"), QLatin1String("resourcetype") ); + + DavCollection::ContentTypes contentTypes; + + if ( !DavUtils::firstChildElementNS( resourcetypeElement, QLatin1String("http://groupdav.org/"), QLatin1String("vevent-collection") ).isNull() ) + contentTypes |= DavCollection::Events; + + if ( !DavUtils::firstChildElementNS( resourcetypeElement, QLatin1String("http://groupdav.org/"), QLatin1String("vtodo-collection") ).isNull() ) + contentTypes |= DavCollection::Todos; + + if ( !DavUtils::firstChildElementNS( resourcetypeElement, QLatin1String("http://groupdav.org/"), QLatin1String("vcard-collection") ).isNull() ) + contentTypes |= DavCollection::Contacts; + + return contentTypes; +} + +QString GroupdavProtocol::contactsMimeType() const +{ + return QLatin1String( "text/x-vcard" ); +} diff --git a/kdepim-runtime/resources/dav/protocols/groupdavprotocol.h b/kdepim-runtime/resources/dav/protocols/groupdavprotocol.h new file mode 100644 index 00000000..e1ac7b94 --- /dev/null +++ b/kdepim-runtime/resources/dav/protocols/groupdavprotocol.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef GROUPDAVPROTOCOL_H +#define GROUPDAVPROTOCOL_H + +#include "davprotocolbase.h" + +class GroupdavProtocol : public DavProtocolBase +{ + public: + GroupdavProtocol(); + virtual bool supportsPrincipals() const; + virtual bool useReport() const; + virtual bool useMultiget() const; + virtual QDomDocument collectionsQuery() const; + virtual QString collectionsXQuery() const; + virtual QList itemsQueries() const; + virtual QString mimeTypeForQuery( int index ) const; + + virtual DavCollection::ContentTypes collectionContentTypes( const QDomElement &propstat ) const; + virtual QString contactsMimeType() const; + + private: + QList mItemsQueries; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/CMakeLists.txt b/kdepim-runtime/resources/dav/resource/CMakeLists.txt new file mode 100644 index 00000000..8b50d933 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/CMakeLists.txt @@ -0,0 +1,107 @@ +project(davgroupware) + +if (QT_QTXMLPATTERNS_LIBRARY) + if(WIN32) + set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ) + endif() + + include_directories( + ${KDE4_INCLUDES} + ${KDEPIMLIBS_INCLUDE_DIRS} + ../common/ + ../protocols/ + ) + + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + + ########### next target ############### + + set( davgroupwareresource_SRCS + ../common/davjobbase.cpp + ../common/davcollection.cpp + ../common/davcollectiondeletejob.cpp + ../common/davcollectionsfetchjob.cpp + ../common/davcollectionmodifyjob.cpp + ../common/davcollectionsmultifetchjob.cpp + ../common/davprotocolbase.cpp + ../common/davitem.cpp + ../common/davitemcreatejob.cpp + ../common/davitemdeletejob.cpp + ../common/davitemfetchjob.cpp + ../common/davitemmodifyjob.cpp + ../common/davitemsfetchjob.cpp + ../common/davitemslistjob.cpp + ../common/davmanager.cpp + ../common/davmultigetprotocol.cpp + ../common/davprincipalhomesetsfetchjob.cpp + ../common/davprincipalsearchjob.cpp + ../common/davutils.cpp + ../common/etagcache.cpp + + ../protocols/caldavprotocol.cpp + ../protocols/carddavprotocol.cpp + ../protocols/groupdavprotocol.cpp + + configdialog.cpp + davfreebusyhandler.cpp + davgroupwareresource.cpp + davprotocolattribute.cpp + searchdialog.cpp + setupwizard.cpp + settings.cpp + urlconfigurationdialog.cpp + ) + + if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + include_directories(${ACCOUNTSQT_INCLUDE_DIRS} ${SIGNONQT_INCLUDE_DIRS}) + add_definitions(-DHAVE_ACCOUNTS) + set(davgroupwareresource_SRCS ../../shared/getcredentialsjob.cpp ${davgroupwareresource_SRCS}) + endif() + + install( FILES davgroupwareresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + install( FILES davgroupwareprovider.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) + + file( GLOB providersFiles "../services/*.desktop" ) + install( FILES ${providersFiles} DESTINATION "${SERVICES_INSTALL_DIR}/akonadi/davgroupware-providers" ) + + kde4_add_kcfg_files(davgroupwareresource_SRCS settingsbase.kcfgc) + kde4_add_ui_files(davgroupwareresource_SRCS configdialog.ui urlconfigurationdialog.ui searchdialog.ui) + kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/davgroupwareresource.kcfg org.kde.Akonadi.davGroupware.Settings) + qt4_add_dbus_adaptor(davgroupwareresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.davGroupware.Settings.xml settings.h Settings + ) + + kde4_add_executable(akonadi_davgroupware_resource RUN_UNINSTALLED ${davgroupwareresource_SRCS}) + + if (Q_WS_MAC) + set_target_properties(akonadi_davgroupware_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../../Info.plist.template) + set_target_properties(akonadi_davgroupware_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.davGroupware") + set_target_properties(akonadi_davgroupware_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi davGroupware Resource") + endif () + + target_link_libraries(akonadi_davgroupware_resource + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTXML_LIBRARY} + ${QT_QTXMLPATTERNS_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_CALENDAR_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS}) + + if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + target_link_libraries(akonadi_davgroupware_resource + ${ACCOUNTSQT_LIBRARIES} + ${SIGNONQT_LIBRARIES}) + endif() + install(TARGETS akonadi_davgroupware_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) +else() + add_feature_info("Davgroupware resource" QT_QTXMLPATTERNS_LIBRARY "The QtXmlPatterns library was not found. It is needed for building the davgroupware resource.") +endif() + diff --git a/kdepim-runtime/resources/dav/resource/Messages.sh b/kdepim-runtime/resources/dav/resource/Messages.sh new file mode 100644 index 00000000..dbbaca50 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp ../common/*.cpp -o $podir/akonadi_davgroupware_resource.pot diff --git a/kdepim-runtime/resources/dav/resource/akonadi-resources.png b/kdepim-runtime/resources/dav/resource/akonadi-resources.png new file mode 100644 index 0000000000000000000000000000000000000000..8513893d7ec8eb0f0a98327d3d985a7dba16eef7 GIT binary patch literal 69059 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 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "configdialog.h" +#include "searchdialog.h" +#include "settings.h" +#include "urlconfigurationdialog.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +ConfigDialog::ConfigDialog( QWidget *parent ) + : KDialog( parent ) +{ + mUi.setupUi( mainWidget() ); + setWindowIcon( KIcon( QLatin1String("folder-remote") ) ); + + mModel = new QStandardItemModel(); + QStringList headers; + headers << i18n( "Protocol" ) << i18n( "URL" ); + mModel->setHorizontalHeaderLabels( headers ); + + mUi.configuredUrls->setModel( mModel ); + mUi.configuredUrls->setRootIsDecorated( false ); + + foreach ( const DavUtils::DavUrl &url, Settings::self()->configuredDavUrls() ) { + KUrl displayUrl = url.url(); + displayUrl.setUser( QString() ); + addModelRow( DavUtils::translatedProtocolName( url.protocol() ), displayUrl.prettyUrl() ); + } + + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); + + connect( mUi.kcfg_displayName, SIGNAL(textChanged(QString)), this, SLOT(checkUserInput()) ); + connect( mUi.configuredUrls->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(checkConfiguredUrlsButtonsState()) ); + + connect( mUi.addButton, SIGNAL(clicked()), this, SLOT(onAddButtonClicked()) ); + connect( mUi.searchButton, SIGNAL(clicked()), this, SLOT(onSearchButtonClicked()) ); + connect( mUi.removeButton, SIGNAL(clicked()), this, SLOT(onRemoveButtonClicked()) ); + connect( mUi.editButton, SIGNAL(clicked()), this, SLOT(onEditButtonClicked()) ); + + connect( this, SIGNAL(okClicked()), this, SLOT(onOkClicked()) ); + connect( this, SIGNAL(cancelClicked()), this, SLOT(onCancelClicked()) ); + + checkUserInput(); +} + +ConfigDialog::~ConfigDialog() +{ +} + +void ConfigDialog::setPassword(const QString& password) +{ + mUi.password->setText( password ); +} + +void ConfigDialog::checkUserInput() +{ + checkConfiguredUrlsButtonsState(); + + if ( !mUi.kcfg_displayName->text().isEmpty() && !( mModel->invisibleRootItem()->rowCount() == 0 ) ) + enableButtonOk( true ); + else + enableButtonOk( false ); +} + +void ConfigDialog::onAddButtonClicked() +{ + QPointer dlg = new UrlConfigurationDialog( this ); + dlg->setDefaultUsername( mUi.kcfg_defaultUsername->text() ); + dlg->setDefaultPassword( mUi.password->text() ); + const int result = dlg->exec(); + + if ( result == QDialog::Accepted && !dlg.isNull() ) { + if ( Settings::self()->urlConfiguration( DavUtils::Protocol( dlg->protocol() ), dlg->remoteUrl() ) ) { + KMessageBox::error( this, i18n( "Another configuration entry already uses the same URL/protocol couple.\n" + "Please use a different URL" ) ); + } else { + Settings::UrlConfiguration *urlConfig = new Settings::UrlConfiguration(); + + urlConfig->mUrl = dlg->remoteUrl(); + if ( dlg->useDefaultCredentials() ) { + urlConfig->mUser = QLatin1String("$default$"); + } else { + urlConfig->mUser = dlg->username(); + urlConfig->mPassword = dlg->password(); + } + urlConfig->mProtocol = dlg->protocol(); + + Settings::self()->newUrlConfiguration( urlConfig ); + + const QString protocolName = DavUtils::translatedProtocolName( dlg->protocol() ); + + addModelRow( protocolName, dlg->remoteUrl() ); + mAddedUrls << QPair( dlg->remoteUrl(), DavUtils::Protocol( dlg->protocol() ) ); + checkUserInput(); + } + } + + delete dlg; +} + +void ConfigDialog::onSearchButtonClicked() +{ + QPointer dlg = new SearchDialog( this ); + dlg->setUsername( mUi.kcfg_defaultUsername->text() ); + dlg->setPassword( mUi.password->text() ); + const int result = dlg->exec(); + + if ( result == QDialog::Accepted && !dlg.isNull() ) { + QStringList results = dlg->selection(); + foreach ( const QString &result, results ) { + QStringList split = result.split( QLatin1Char('|') ); + DavUtils::Protocol protocol = DavUtils::protocolByName( split.at( 0 ) ); + if ( !Settings::self()->urlConfiguration( protocol, split.at( 1 ) ) ) { + Settings::UrlConfiguration *urlConfig = new Settings::UrlConfiguration(); + + urlConfig->mUrl = split.at( 1 ); + if ( dlg->useDefaultCredentials() ) { + urlConfig->mUser = QLatin1String("$default$"); + } else { + urlConfig->mUser = dlg->username(); + urlConfig->mPassword = dlg->password(); + } + urlConfig->mProtocol = protocol; + + Settings::self()->newUrlConfiguration( urlConfig ); + + addModelRow( DavUtils::translatedProtocolName( protocol ), split.at( 1 ) ); + mAddedUrls << QPair( split.at( 1 ), protocol ); + checkUserInput(); + } + } + } + + delete dlg; +} + +void ConfigDialog::onRemoveButtonClicked() +{ + const QModelIndexList indexes = mUi.configuredUrls->selectionModel()->selectedRows(); + if ( indexes.size() == 0 ) + return; + + QString proto = mModel->index( indexes.at( 0 ).row(), 0 ).data().toString(); + QString url = mModel->index( indexes.at( 0 ).row(), 1 ).data().toString(); + + mRemovedUrls << QPair( url, DavUtils::protocolByTranslatedName( proto ) ); + mModel->removeRow( indexes.at( 0 ).row() ); + + checkUserInput(); +} + +void ConfigDialog::onEditButtonClicked() +{ + const QModelIndexList indexes = mUi.configuredUrls->selectionModel()->selectedRows(); + if ( indexes.size() == 0 ) + return; + + const int row = indexes.at( 0 ).row(); + const QString proto = mModel->index( row, 0 ).data().toString(); + const QString url = mModel->index( row, 1 ).data().toString(); + + Settings::UrlConfiguration *urlConfig = Settings::self()->urlConfiguration( DavUtils::protocolByTranslatedName( proto ), url ); + if ( !urlConfig ) + return; + + QPointer dlg = new UrlConfigurationDialog( this ); + dlg->setRemoteUrl( urlConfig->mUrl ); + dlg->setProtocol( DavUtils::Protocol( urlConfig->mProtocol ) ); + + if ( urlConfig->mUser == QLatin1String( "$default$" ) ) { + dlg->setUseDefaultCredentials( true ); + } else { + dlg->setUseDefaultCredentials( false ); + dlg->setUsername( urlConfig->mUser ); + dlg->setPassword( urlConfig->mPassword ); + } + dlg->setDefaultUsername( mUi.kcfg_defaultUsername->text() ); + dlg->setDefaultPassword( mUi.password->text() ); + + const int result = dlg->exec(); + + if ( result == QDialog::Accepted && !dlg.isNull() ) { + Settings::self()->removeUrlConfiguration( DavUtils::protocolByTranslatedName( proto ), url ); + Settings::UrlConfiguration *urlConfigAccepted = new Settings::UrlConfiguration(); + urlConfigAccepted->mUrl = dlg->remoteUrl(); + if ( dlg->useDefaultCredentials() ) { + urlConfigAccepted->mUser = QLatin1String("$default$"); + } else { + urlConfigAccepted->mUser = dlg->username(); + urlConfigAccepted->mPassword = dlg->password(); + } + urlConfigAccepted->mProtocol = dlg->protocol(); + Settings::self()->newUrlConfiguration( urlConfigAccepted ); + + mModel->removeRow( row ); + insertModelRow( row, DavUtils::translatedProtocolName( dlg->protocol() ), dlg->remoteUrl() ); + } + delete dlg; +} + +void ConfigDialog::onOkClicked() +{ + typedef QPair UrlPair; + foreach ( const UrlPair &url, mRemovedUrls ) + Settings::self()->removeUrlConfiguration( url.second, url.first ); + + mManager->updateSettings(); + Settings::self()->setDefaultPassword( mUi.password->text() ); +} + +void ConfigDialog::onCancelClicked() +{ + mRemovedUrls.clear(); + + typedef QPair UrlPair; + foreach ( const UrlPair &url, mAddedUrls ) + Settings::self()->removeUrlConfiguration( url.second, url.first ); +} + +void ConfigDialog::checkConfiguredUrlsButtonsState() +{ + const bool enabled = mUi.configuredUrls->selectionModel()->hasSelection(); + + mUi.removeButton->setEnabled( enabled ); + mUi.editButton->setEnabled( enabled ); +} + +void ConfigDialog::addModelRow( const QString &protocol, const QString &url ) +{ + insertModelRow( -1, protocol, url ); +} + +void ConfigDialog::insertModelRow( int index, const QString &protocol, const QString &url ) +{ + QStandardItem *rootItem = mModel->invisibleRootItem(); + QList items; + + QStandardItem *protocolStandardItem = new QStandardItem( protocol ); + protocolStandardItem->setEditable( false ); + items << protocolStandardItem; + + QStandardItem *urlStandardItem = new QStandardItem( url ); + urlStandardItem->setEditable( false ); + items << urlStandardItem; + + if ( index == -1 ) + rootItem->appendRow( items ); + else + rootItem->insertRow( index, items ); +} + diff --git a/kdepim-runtime/resources/dav/resource/configdialog.h b/kdepim-runtime/resources/dav/resource/configdialog.h new file mode 100644 index 00000000..c47ceea7 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/configdialog.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include "ui_configdialog.h" + +#include "davutils.h" + +#include + +#include +#include +#include + +class KConfigDialogManager; +class QStandardItemModel; + +class ConfigDialog : public KDialog +{ + Q_OBJECT + + public: + explicit ConfigDialog( QWidget *parent = 0 ); + virtual ~ConfigDialog(); + + void setPassword( const QString &password ); + + private Q_SLOTS: + void checkUserInput(); + void onAddButtonClicked(); + void onSearchButtonClicked(); + void onRemoveButtonClicked(); + void onEditButtonClicked(); + void checkConfiguredUrlsButtonsState(); + void onOkClicked(); + void onCancelClicked(); + + private: + void addModelRow( const QString &protocol, const QString &url ); + void insertModelRow( int index, const QString &protocol, const QString &url ); + + Ui::ConfigDialog mUi; + KConfigDialogManager *mManager; + QList< QPair > mAddedUrls; + QList< QPair > mRemovedUrls; + QStandardItemModel *mModel; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/configdialog.ui b/kdepim-runtime/resources/dav/resource/configdialog.ui new file mode 100644 index 00000000..54ad1cae --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/configdialog.ui @@ -0,0 +1,238 @@ + + + ConfigDialog + + + + 0 + 0 + 650 + 434 + + + + + + + General Configuration + + + + + + + + + + + Display name: + + + + + + + + + + + + + Refresh every: + + + + + + + + + + + + 5 + + + + + + + + + + minutes + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Username: + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Password: + + + + + + + + + + + + QLineEdit::Password + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Server Configuration + + + + + + + + + + + + + Add + + + + + + + Search + + + + + + + Remove + + + + + + + Edit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + KPushButton + QPushButton +

kpushbutton.h
+ + + KLineEdit + QLineEdit +
klineedit.h
+
+ + + + diff --git a/kdepim-runtime/resources/dav/resource/davfreebusyhandler.cpp b/kdepim-runtime/resources/dav/resource/davfreebusyhandler.cpp new file mode 100644 index 00000000..517e0bec --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davfreebusyhandler.cpp @@ -0,0 +1,219 @@ +/* + Copyright (c) 2011 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davfreebusyhandler.h" + +#include "davcollectionsfetchjob.h" +#include "davmanager.h" +#include "davprincipalsearchjob.h" +#include "settings.h" + +#include +#include +#include +#include +#include + +DavFreeBusyHandler::DavFreeBusyHandler( QObject* parent ) + : QObject( parent ), mNextRequestId( 0 ) +{ +} + +void DavFreeBusyHandler::canHandleFreeBusy( const QString& email ) +{ + DavUtils::DavUrl::List urls = Settings::self()->configuredDavUrls(); + foreach ( const DavUtils::DavUrl &url, urls ) { + if ( url.protocol() == DavUtils::CalDav ) { + ++mRequestsTracker[email].handlingJobCount; + DavPrincipalSearchJob *job = new DavPrincipalSearchJob( url, DavPrincipalSearchJob::EmailAddress, email ); + job->setProperty( "email", QVariant::fromValue( email ) ); + job->setProperty( "url", QVariant::fromValue( url.url().url() ) ); + job->fetchProperty( QLatin1String("schedule-inbox-URL"), QLatin1String("urn:ietf:params:xml:ns:caldav") ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onPrincipalSearchJobFinished(KJob*)) ); + job->start(); + } + } +} + +void DavFreeBusyHandler::retrieveFreeBusy( const QString& email, const KDateTime& start, const KDateTime& end ) +{ + if ( !mPrincipalScheduleOutbox.contains( email ) ) { + emit freeBusyRetrieved( email, QString(), false, + i18n( "No schedule-outbox found for %1", email ) ); + return; + } + + KCalCore::FreeBusy::Ptr fb( new KCalCore::FreeBusy( start, end ) ); + KCalCore::Attendee::Ptr att( new KCalCore::Attendee( QString(), email ) ); + fb->addAttendee( att ); + + KCalCore::ICalFormat formatter; + QByteArray fbData = formatter.createScheduleMessage( fb, KCalCore::iTIPRequest ).toUtf8(); + + foreach ( const QString &outbox, mPrincipalScheduleOutbox[email] ) { + ++mRequestsTracker[email].retrievalJobCount; + uint requestId = mNextRequestId++; + + KUrl url( outbox ); + KIO::StoredTransferJob *job = KIO::storedHttpPost( fbData, url ); + job->addMetaData( QLatin1String("content-type"), QLatin1String("text/calendar") ); + job->setProperty( "email", QVariant::fromValue( email ) ); + job->setProperty( "request-id", QVariant::fromValue( requestId ) ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onRetrieveFreeBusyJobFinished(KJob*)) ); + job->start(); + } +} + +void DavFreeBusyHandler::onPrincipalSearchJobFinished( KJob* job ) +{ + QString email = job->property( "email" ).toString(); + int handlingJobCount = --mRequestsTracker[email].handlingJobCount; + + if ( job->error() ) { + if ( handlingJobCount == 0 && !mRequestsTracker[email].handlingJobSuccessful ) + emit handlesFreeBusy( email, false ); + return; + } + + DavPrincipalSearchJob *davJob = qobject_cast( job ); + QList results = davJob->results(); + + if ( results.isEmpty() ) { + if ( handlingJobCount == 0 && !mRequestsTracker[email].handlingJobSuccessful ) + emit handlesFreeBusy( email, false ); + return; + } + + mRequestsTracker[email].handlingJobSuccessful = true; + + foreach ( const DavPrincipalSearchJob::Result &result, results ) { + kDebug() << result.value; + KUrl url( davJob->property( "url" ).toString() ); + if ( result.value.startsWith( QLatin1Char('/') ) ) { + // href is only a path, use request url to complete + url.setEncodedPath( result.value.toLatin1() ); + } else { + // href is a complete url + KUrl tmpUrl( result.value ); + url = tmpUrl; + } + + if ( !mPrincipalScheduleOutbox[email].contains( url.url() ) ) + mPrincipalScheduleOutbox[email] << url.url(); + } + + if ( handlingJobCount == 0 ) + emit handlesFreeBusy( email, true ); +} + +void DavFreeBusyHandler::onRetrieveFreeBusyJobFinished( KJob* job ) +{ + QString email = job->property( "email" ).toString(); + uint requestId = job->property( "request-id" ).toUInt(); + int retrievalJobCount = --mRequestsTracker[email].retrievalJobCount; + + if ( job->error() ) { + if ( retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful ) + emit( freeBusyRetrieved( email, QString(), false, job->errorString() ) ); + return; + } + + /* + * Extract info from a document like the following: + * + * + * + * + * mailto:wilfredo@example.com + * + * 2.0;Success + * BEGIN:VCALENDAR + * VERSION:2.0 + * PRODID:-//Example Corp.//CalDAV Server//EN + * METHOD:REPLY + * BEGIN:VFREEBUSY + * UID:4FD3AD926350 + * DTSTAMP:20090602T200733Z + * DTSTART:20090602T000000Z + * DTEND:20090604T000000Z + * ORGANIZER;CN="Cyrus Daboo":mailto:cyrus@example.com + * ATTENDEE;CN="Wilfredo Sanchez Vega":mailto:wilfredo@example.com + * FREEBUSY;FBTYPE=BUSY:20090602T110000Z/20090602T120000Z + * FREEBUSY;FBTYPE=BUSY:20090603T170000Z/20090603T180000Z + * END:VFREEBUSY + * END:VCALENDAR + * + * + * + * + * mailto:mike@example.org + * + * 3.7;Invalid calendar user + * + * + */ + + KIO::StoredTransferJob *postJob = qobject_cast( job ); + QDomDocument response; + response.setContent( postJob->data(), true ); + + QDomElement scheduleResponse = response.documentElement(); + + // We are only expecting one response tag + QDomElement responseElement = DavUtils::firstChildElementNS( scheduleResponse, QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("response") ); + if ( responseElement.isNull() ) { + if ( retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful ) + emit( freeBusyRetrieved( email, QString(), false, i18n( "Invalid response from the server" ) ) ); + return; + } + + // We can load directly the calendar-data and use its content to create + // an incidence base that will give us everything we need to test + // the success + QDomElement calendarDataElement = DavUtils::firstChildElementNS( responseElement, QLatin1String("urn:ietf:params:xml:ns:caldav"), QLatin1String("calendar-data") ); + if ( calendarDataElement.isNull() ) { + if ( retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful ) + emit( freeBusyRetrieved( email, QString(), false, i18n( "Invalid response from the server" ) ) ); + return; + } + + QString rawData = calendarDataElement.text(); + + KCalCore::ICalFormat format; + KCalCore::FreeBusy::Ptr fb = format.parseFreeBusy( rawData ); + if ( fb.isNull() ) { + if ( retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful ) + emit( freeBusyRetrieved( email, QString(), false, i18n( "Unable to parse free-busy data received" ) ) ); + return; + } + + // We're safe now + mRequestsTracker[email].retrievalJobSuccessful = true; + +// fb->clearAttendees(); + if ( mRequestsTracker[email].resultingFreeBusy[requestId].isNull() ) + mRequestsTracker[email].resultingFreeBusy[requestId] = fb; + else + mRequestsTracker[email].resultingFreeBusy[requestId]->merge( fb ); + + if ( retrievalJobCount == 0 ) { + QString fbStr = format.createScheduleMessage( mRequestsTracker[email].resultingFreeBusy[requestId], + KCalCore::iTIPRequest ); + emit freeBusyRetrieved( email, fbStr, true, QString() ); + } +} diff --git a/kdepim-runtime/resources/dav/resource/davfreebusyhandler.h b/kdepim-runtime/resources/dav/resource/davfreebusyhandler.h new file mode 100644 index 00000000..5bb6caa0 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davfreebusyhandler.h @@ -0,0 +1,99 @@ +/* + Copyright (c) 2011 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVFREEBUSYHANDLER_H +#define DAVFREEBUSYHANDLER_H + +#include + +#include +#include +#include + +class KDateTime; +class KJob; + +/** + * @short The class that will manage DAV free-busy requests + */ +class DavFreeBusyHandler : public QObject +{ + Q_OBJECT + + public: + /** + * Constructs a new DavFreeBusyHandler + */ + explicit DavFreeBusyHandler( QObject *parent = 0 ); + + /** + * Checks if the free-busy info for @p email can be handled + * + * @param email The email address of the contact. + */ + void canHandleFreeBusy( const QString &email ); + + /** + * Retrieve the free-busy info for @p email between @p start and @p end + * + * @param email The email address to retrieve the free-busy for + * @param start The start of the free-busy period to report + * @param end The end of the free-busy period to report + */ + void retrieveFreeBusy( const QString &email, const KDateTime &start, const KDateTime &end ); + + signals: + /** + * Emitted once we know if the free-busy info for @p email + * can be handled or not. + */ + void handlesFreeBusy( const QString &email, bool handles ); + + /** + * Emitted once the free-busy has been retrieved + */ + void freeBusyRetrieved( const QString &email, const QString &freeBusy, bool success, const QString &errorText ); + + private slots: + void onPrincipalSearchJobFinished( KJob *job ); + void onRetrieveFreeBusyJobFinished( KJob *job ); + + private: + /** + * Simple struct to track the state of requests + */ + struct RequestTracker { + RequestTracker() + : handlingJobCount( 0 ), handlingJobSuccessful( false ), + retrievalJobCount( 0 ), retrievalJobSuccessful( false ) + { + } + + int handlingJobCount; + bool handlingJobSuccessful; + int retrievalJobCount; + bool retrievalJobSuccessful; + QMap resultingFreeBusy; + }; + + QMap mRequestsTracker; + QMap mPrincipalScheduleOutbox; + uint mNextRequestId; +}; + +#endif // DAVFREEBUSYHANDLER_H diff --git a/kdepim-runtime/resources/dav/resource/davgroupwareprovider.desktop b/kdepim-runtime/resources/dav/resource/davgroupwareprovider.desktop new file mode 100644 index 00000000..a12a8fd4 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davgroupwareprovider.desktop @@ -0,0 +1,68 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=DavGroupwareProvider +Name=DAV Groupware resource provider +Name[bs]=DAV dobavljać grupnih resursa +Name[ca]=Proveïdor de recurs de treball en grup DAV +Name[ca@valencia]=Proveïdor de recurs de treball en grup DAV +Name[da]=Ressourceudbyder til DAV-groupware +Name[de]=DAV-Groupware-Ressource-Anbieter +Name[el]=ΠάÏοχος πόÏου DAV groupware +Name[en_GB]=DAV Groupware resource provider +Name[es]=Proveedor de recurso de trabajo en grupo DAV +Name[et]=DAV grupitöö ressursi pakkuja +Name[fi]=DAV-työryhmäresurssitarjoaja +Name[fr]=Fournisseur de ressources pour logiciels de collaboration DAV +Name[ga]=Soláthraí acmhainní groupware DAV +Name[gl]=Fornecedor de recursos de grupo DAV +Name[hu]=DAV csoportmunka-erÅ‘forrás szolgáltató +Name[ia]=Fornitor de ressource de DAV Groupware +Name[it]=Fornitore di risorsa di groupware DAV +Name[kk]=DAV топтық Ð¶Ò±Ð¼Ñ‹Ñ Ñ€ÐµÑÑƒÑ€Ñ Ð¿Ñ€Ð¾Ð²Ð°Ð¹Ð´ÐµÑ€Ñ– +Name[km]=ក្រុមហ៊ុន​ផ្ដល់​ធនធាន DAV Groupware +Name[ko]=DAV 그룹웨어 ìžì› ê³µê¸‰ìž +Name[lt]=DAV grupinÄ—s įrangos resursų tiekÄ—jas +Name[lv]=DAV grupprogrammatÅ«ras resursa nodroÅ¡inÄtÄjs +Name[nb]=DAV Groupware-ressursleverandør +Name[nds]=Anbeder vun en DAV-Arbeitkoppel-Ressource +Name[nl]=Leverancier van DAV-groupware-hulpbron +Name[pl]=Dostawca zasobu Groupware DAV +Name[pt]=Fornecedor de recursos de 'groupware' em DAV +Name[pt_BR]=Provedor de recursos de groupware em DAV +Name[ru]=ПоÑтавщик иÑточников данных ÑовмеÑтной работы DAV +Name[sk]=Poskytovateľ zdroja DAV Groupware +Name[sl]=Ponudnik vira za skupinsko delo DAV +Name[sr]=Добављач групверÑких ДÐÐ’ реÑурÑа +Name[sr@ijekavian]=Добављач групверÑких ДÐÐ’ реÑурÑа +Name[sr@ijekavianlatin]=DobavljaÄ grupverskih DAV resursa +Name[sr@latin]=DobavljaÄ grupverskih DAV resursa +Name[sv]=Leverantör av DAV-grupprogramresurs +Name[tr]=DAV Groupware kaynak saÄŸlayıcı +Name[uk]=Ðадавач реÑурÑу групової роботи DAV +Name[x-test]=xxDAV Groupware resource providerxx +Name[zh_CN]=DAV 群件资æºæ供方 +Name[zh_TW]=DAV 群組資æºæ供者 + +[PropertyDef::X-DavGroupware-SupportedProtocols] +Type=QStringList + +[PropertyDef::X-DavGroupware-InstallationPath] +Type=QString + +[PropertyDef::X-DavGroupware-ProviderUsesSSL] +Type=bool + +[PropertyDef::X-DavGroupware-CalDavHost] +Type=QString + +[PropertyDef::X-DavGroupware-CalDavPath] +Type=QString + +[PropertyDef::X-DavGroupware-CardDavHost] +Type=QString + +[PropertyDef::X-DavGroupware-CardDavPath] +Type=QString + +[PropertyDef::X-DavGroupware-GroupDavPath] +Type=QString diff --git a/kdepim-runtime/resources/dav/resource/davgroupwareresource.cpp b/kdepim-runtime/resources/dav/resource/davgroupwareresource.cpp new file mode 100644 index 00000000..53113701 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davgroupwareresource.cpp @@ -0,0 +1,1023 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davgroupwareresource.h" + +#include "configdialog.h" +#include "davcollectiondeletejob.h" +#include "davcollectionsfetchjob.h" +#include "davcollectionsmultifetchjob.h" +#include "davfreebusyhandler.h" +#include "davitemcreatejob.h" +#include "davitemdeletejob.h" +#include "davitemfetchjob.h" +#include "davitemmodifyjob.h" +#include "davitemsfetchjob.h" +#include "davitemslistjob.h" +#include "davmanager.h" +#include "davprotocolattribute.h" +#include "davprotocolbase.h" +#include "settings.h" +#include "settingsadaptor.h" +#include "setupwizard.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace Akonadi; + +typedef QSharedPointer IncidencePtr; + +DavGroupwareResource::DavGroupwareResource( const QString &id ) + : ResourceBase( id ), FreeBusyProviderBase(), mSyncErrorNotified( false ) +{ + AttributeFactory::registerAttribute(); + AttributeFactory::registerAttribute(); + + setNeedsNetwork( true ); + + mDavCollectionRoot.setParentCollection( Collection::root() ); + mDavCollectionRoot.setName( identifier() ); + mDavCollectionRoot.setRemoteId( identifier() ); + mDavCollectionRoot.setContentMimeTypes( QStringList() << Collection::mimeType() ); + mDavCollectionRoot.setRights( Collection::CanCreateCollection | Collection::CanDeleteCollection | Collection::CanChangeCollection ); + + EntityDisplayAttribute *attribute = mDavCollectionRoot.attribute( Collection::AddIfMissing ); + attribute->setIconName( QLatin1String( "folder-remote" ) ); + + int refreshInterval = Settings::self()->refreshInterval(); + if ( refreshInterval == 0 ) + refreshInterval = -1; + + Akonadi::CachePolicy cachePolicy; + cachePolicy.setInheritFromParent( false ); + cachePolicy.setSyncOnDemand( false ); + cachePolicy.setCacheTimeout( -1 ); + cachePolicy.setIntervalCheckTime( refreshInterval ); + cachePolicy.setLocalParts( QStringList() << QLatin1String( "ALL" ) ); + mDavCollectionRoot.setCachePolicy( cachePolicy ); + + changeRecorder()->fetchCollection( true ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::All ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All ); + + Settings::self()->setWinId( winIdForDialogs() ); + Settings::self()->setResourceIdentifier( identifier() ); + + mFreeBusyHandler = new DavFreeBusyHandler( this ); + connect( mFreeBusyHandler, SIGNAL(handlesFreeBusy(QString,bool)), this, SLOT(onHandlesFreeBusy(QString,bool)) ); + connect( mFreeBusyHandler, SIGNAL(freeBusyRetrieved(QString,QString,bool,QString)), this, SLOT(onFreeBusyRetrieved(QString,QString,bool,QString)) ); + + connect(this, SIGNAL(reloadConfiguration()), this, SLOT(onReloadConfig())); + + scheduleCustomTask( this, "createInitialCache", QVariant(), ResourceBase::AfterChangeReplay ); +} + +DavGroupwareResource::~DavGroupwareResource() +{ + delete mFreeBusyHandler; +} + +void DavGroupwareResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + kDebug() << "Removing collection " << collection.remoteId(); + + if ( !configurationIsValid() ) { + return; + } + + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( collection.remoteId() ); + + DavCollectionDeleteJob *job = new DavCollectionDeleteJob( davUrl ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onCollectionRemovedFinished(KJob*)) ); + job->start(); +} + +void DavGroupwareResource::cleanup() +{ + Settings::self()->cleanup(); + Akonadi::AgentBase::cleanup(); +} + +KDateTime DavGroupwareResource::lastCacheUpdate() const +{ + return KDateTime::currentLocalDateTime(); +} + +void DavGroupwareResource::canHandleFreeBusy( const QString& email ) const +{ + if ( !isOnline() ) + handlesFreeBusy( email, false ); + else + mFreeBusyHandler->canHandleFreeBusy( email ); +} + +void DavGroupwareResource::onHandlesFreeBusy( const QString &email, bool handles ) +{ + handlesFreeBusy( email, handles ); +} + +void DavGroupwareResource::retrieveFreeBusy( const QString& email, const KDateTime& start, const KDateTime& end ) +{ + if ( !isOnline() ) + freeBusyRetrieved( email, QString(), false, i18n( "Unable to retrieve free-busy info while offline" ) ); + else + mFreeBusyHandler->retrieveFreeBusy( email, start, end ); +} + +void DavGroupwareResource::onFreeBusyRetrieved( const QString& email, const QString& freeBusy, bool success, const QString &errorText ) +{ + freeBusyRetrieved( email, freeBusy, success, errorText ); +} + +void DavGroupwareResource::configure( WId windowId ) +{ + Settings::self()->setWinId( windowId ); + + // On the initial configuration we start the setup wizard + if ( Settings::self()->configuredDavUrls().isEmpty() ) { + SetupWizard wizard; + + if ( windowId ) + KWindowSystem::setMainWindow( &wizard, windowId ); + + const int result = wizard.exec(); + if ( result == QDialog::Accepted ) { + const SetupWizard::Url::List urls = wizard.urls(); + foreach ( const SetupWizard::Url &url, urls ) { + Settings::UrlConfiguration *urlConfig = new Settings::UrlConfiguration(); + + urlConfig->mUrl = url.url; + urlConfig->mProtocol = url.protocol; + urlConfig->mUser = url.userName; + urlConfig->mPassword = wizard.field( QLatin1String("credentialsPassword") ).toString(); + + Settings::self()->newUrlConfiguration( urlConfig ); + } + + if ( !urls.isEmpty() ) + Settings::self()->setDisplayName( wizard.displayName() ); + + QString defaultUser = wizard.field( QLatin1String("credentialsUserName") ).toString(); + if ( !defaultUser.isEmpty() ) { + Settings::self()->setDefaultUsername( defaultUser ); + Settings::self()->setDefaultPassword( wizard.field( QLatin1String("credentialsPassword") ).toString() ); + } + } + } + + // continue with the normal config dialog + ConfigDialog dialog; + + if ( windowId ) + KWindowSystem::setMainWindow( &dialog, windowId ); + + if ( !Settings::self()->defaultUsername().isEmpty() ) + dialog.setPassword( Settings::self()->defaultPassword() ); + + const int result = dialog.exec(); + + if ( result == QDialog::Accepted ) { + Settings::self()->setSettingsVersion( 3 ); + Settings::self()->writeConfig(); + synchronize(); + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } +} + +void DavGroupwareResource::retrieveCollections() +{ + kDebug() << "Retrieving collections list"; + mSyncErrorNotified = false; + + if ( !configurationIsValid() ) { + return; + } + + emit status( Running, i18n( "Fetching collections" ) ); + + DavCollectionsMultiFetchJob *job = new DavCollectionsMultiFetchJob( Settings::self()->configuredDavUrls() ); + connect( job, SIGNAL(result(KJob*)), SLOT(onRetrieveCollectionsFinished(KJob*)) ); + connect( job, SIGNAL(collectionDiscovered(int,QString,QString)), + SLOT(onCollectionDiscovered(int,QString,QString)) ); + job->start(); +} + +void DavGroupwareResource::retrieveItems( const Akonadi::Collection &collection ) +{ + kDebug() << "Retrieving items for collection " << collection.remoteId(); + + if ( !configurationIsValid() ) { + return; + } + + // As the resource root collection contains mime types for items we must + // work around the fact that Akonadi will rightfully try to retrieve items + // from it. So just return an empty list + if ( collection.remoteId() == identifier() ) { + itemsRetrievalDone(); + return; + } + + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( collection.remoteId() ); + + if ( !davUrl.url().isValid() ) { + kError() << "Can't find a configured URL, collection.remoteId() is " << collection.remoteId(); + cancelTask( i18n( "Asked to retrieve items for an unknown collection: %1", collection.remoteId() ) ); + //Q_ASSERT_X( false, "DavGroupwareResource::retrieveItems", "Url is invalid" ); + return; + } + + DavItemsListJob *job = new DavItemsListJob( davUrl ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + job->setContentMimeTypes( collection.contentMimeTypes() ); + connect( job, SIGNAL(result(KJob*)), SLOT(onRetrieveItemsFinished(KJob*)) ); + job->start(); +} + +bool DavGroupwareResource::retrieveItem( const Akonadi::Item &item, const QSet& ) +{ + kDebug() << "Retrieving single item. Remote id = " << item.remoteId(); + + if ( !configurationIsValid() ) { + return false; + } + + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( item.parentCollection().remoteId(), item.remoteId() ); + if ( !davUrl.url().isValid() ) { + cancelTask(); + return false; + } + + DavItem davItem; + davItem.setUrl( item.remoteId() ); + davItem.setContentType( QLatin1String("text/calendar") ); + davItem.setEtag( item.remoteRevision() ); + + DavItemFetchJob *job = new DavItemFetchJob( davUrl, davItem ); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onRetrieveItemFinished(KJob*)) ); + job->start(); + + return true; +} + +void DavGroupwareResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + kDebug() << "Received notification for added item. Local id = " + << item.id() << ". Remote id = " << item.remoteId() + << ". Collection remote id = " << collection.remoteId(); + + if ( !configurationIsValid() ) { + return; + } + + if ( collection.remoteId().isEmpty() ) { + kError() << "Invalid remote id for collection " << collection.id() << " = " << collection.remoteId(); + cancelTask( i18n( "Invalid collection for item %1.", item.id() ) ); + return; + } + + DavItem davItem = DavUtils::createDavItem( item, collection ); + if ( davItem.data().isEmpty() ) { + kError() << "Item " << item.id() << " doesn't has a valid payload"; + cancelTask(); + return; + } + + QString urlStr = davItem.url(); + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( collection.remoteId(), urlStr ); + kDebug() << "Item " << item.id() << " will be put to " << urlStr; + + DavItemCreateJob *job = new DavItemCreateJob( davUrl, davItem ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onItemAddedFinished(KJob*)) ); + job->start(); +} + +void DavGroupwareResource::itemChanged( const Akonadi::Item &item, const QSet& ) +{ + kDebug() << "Received notification for changed item. Local id = " << item.id() + << ". Remote id = " << item.remoteId(); + + if ( !configurationIsValid() ) { + return; + } + + QString ridBase = item.remoteId(); + if ( ridBase.contains( QChar( '#' ) ) ) + ridBase.truncate( ridBase.indexOf( QChar( '#' ) ) ); + + Akonadi::Item::List extraItems; + foreach ( const QString &rid, mEtagCache.etags() ) { + if ( rid.startsWith( ridBase ) && rid != item.remoteId() ) { + Akonadi::Item extraItem; + extraItem.setRemoteId( rid ); + extraItems << extraItem; + } + } + + if ( extraItems.isEmpty() ) { + doItemChange( item ); + } + else { + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( extraItems ); + job->setCollection( item.parentCollection() ); + job->fetchScope().fetchFullPayload(); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onItemChangePrepared(KJob*)) ); + } +} + +void DavGroupwareResource::onItemChangePrepared( KJob *job ) +{ + Akonadi::ItemFetchJob *fetchJob = qobject_cast( job ); + Akonadi::Item item = job->property( "item" ).value(); + doItemChange( item, fetchJob->items() ); +} + +void DavGroupwareResource::doItemChange( const Akonadi::Item &item, const Akonadi::Item::List &dependentItems ) +{ + DavItem davItem = DavUtils::createDavItem( item, item.parentCollection(), dependentItems ); + if ( davItem.data().isEmpty() ) { + cancelTask(); + return; + } + + QString url = item.remoteId(); + if ( url.contains( QChar( '#' ) ) ) + url.truncate( url.indexOf( QChar( '#' ) ) ); + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( item.parentCollection().remoteId(), url ); + + // We have to re-set the URL as it's not necessarily valid after createDavItem() + davItem.setUrl( url ); + davItem.setEtag( item.remoteRevision() ); + + DavItemModifyJob *modJob = new DavItemModifyJob( davUrl, davItem ); + modJob->setProperty( "item", QVariant::fromValue( item ) ); + modJob->setProperty( "dependentItems", QVariant::fromValue( dependentItems ) ); + connect( modJob, SIGNAL(result(KJob*)), SLOT(onItemChangedFinished(KJob*)) ); + modJob->start(); +} + +void DavGroupwareResource::itemRemoved( const Akonadi::Item &item ) +{ + if ( !configurationIsValid() ) { + return; + } + + QString ridBase = item.remoteId(); + if ( ridBase.contains( QChar( '#' ) ) ) { + // A bit tricky: we must remove an incidence contained in a resource + // containing multiple ones. + ridBase.truncate( ridBase.indexOf( QChar( '#' ) ) ); + + Akonadi::Item::List extraItems; + foreach ( const QString &rid, mEtagCache.etags() ) { + if ( rid.startsWith( ridBase ) && rid != item.remoteId() ) { + Akonadi::Item extraItem; + extraItem.setRemoteId( rid ); + extraItems << extraItem; + } + } + + if ( extraItems.isEmpty() ) { + // Urrrr? + // Well, just delete the item. + doItemRemoval( item ); + } + else { + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( extraItems ); + job->setCollection( item.parentCollection() ); + job->fetchScope().fetchFullPayload(); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onItemRemovalPrepared(KJob*)) ); + } + } + else { + // easy as pie: just remove everything at the URL. + doItemRemoval( item ); + } +} + +void DavGroupwareResource::onItemRemovalPrepared( KJob *job ) +{ + Akonadi::ItemFetchJob *fetchJob = qobject_cast( job ); + Akonadi::Item item = job->property( "item" ).value(); + Akonadi::Item::List keptItems = fetchJob->items(); + + if ( keptItems.isEmpty() ) { + // Urrrr? Not again! + doItemRemoval( item ); + } + else { + Akonadi::Item mainItem; + Akonadi::Item::List extraItems; + QString ridBase = item.remoteId(); + ridBase.truncate( ridBase.indexOf( QChar( '#' ) ) ); + + foreach ( const Akonadi::Item &kept, keptItems ) { + if ( kept.remoteId() == ridBase && extraItems.isEmpty() ) + mainItem = kept; + else + extraItems << kept; + } + + if ( !mainItem.hasPayload() ) + mainItem = extraItems.takeFirst(); + + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( item.parentCollection().remoteId(), ridBase ); + + DavItem davItem = DavUtils::createDavItem( mainItem, mainItem.parentCollection(), extraItems ); + davItem.setUrl( ridBase ); + davItem.setEtag( item.remoteRevision() ); + + DavItemModifyJob *modJob = new DavItemModifyJob( davUrl, davItem ); + modJob->setProperty( "item", QVariant::fromValue( mainItem ) ); + modJob->setProperty( "dependentItems", QVariant::fromValue( extraItems ) ); + modJob->setProperty( "isRemoval", QVariant::fromValue( true ) ); + modJob->setProperty( "removedItem", QVariant::fromValue( item ) ); + connect( modJob, SIGNAL(result(KJob*)), SLOT(onItemChangedFinished(KJob*)) ); + modJob->start(); + } +} + +void DavGroupwareResource::doItemRemoval( const Akonadi::Item &item ) +{ + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( item.parentCollection().remoteId(), item.remoteId() ); + + DavItem davItem; + davItem.setUrl( item.remoteId() ); + davItem.setEtag( item.remoteRevision() ); + + DavItemDeleteJob *job = new DavItemDeleteJob( davUrl, davItem ); + job->setProperty( "item", QVariant::fromValue( item ) ); + job->setProperty( "collection", QVariant::fromValue( item.parentCollection() ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onItemRemovedFinished(KJob*)) ); + job->start(); +} + +void DavGroupwareResource::doSetOnline( bool online ) +{ + kDebug() << "Resource changed online status to" << online; + + if ( online ) { + synchronize(); + } + + ResourceBase::doSetOnline( online ); +} + +void DavGroupwareResource::createInitialCache() +{ + // Get all the items fetched by this resource + Akonadi::RecursiveItemFetchJob *job = new Akonadi::RecursiveItemFetchJob( mDavCollectionRoot, QStringList() ); + job->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onCreateInitialCacheReady(KJob*)) ); + job->start(); +} + +void DavGroupwareResource::onCreateInitialCacheReady( KJob *job ) +{ + Akonadi::RecursiveItemFetchJob *fetchJob = qobject_cast( job ); + + foreach ( const Akonadi::Item &item, fetchJob->items() ) { + const QString rid = item.remoteId(); + if ( rid.isEmpty() ) + continue; + + const QString colRid = item.parentCollection().remoteId(); + if ( colRid.isEmpty() ) + continue; + + const QString etag = item.remoteRevision(); + if ( etag.isEmpty() ) + continue; + + mEtagCache.setEtag( rid, etag ); + + if ( !mItemsRidCache.contains( colRid ) ) + mItemsRidCache.insert( colRid, QSet() ); + + mItemsRidCache[colRid].insert( rid ); + } + + taskDone(); +} + +void DavGroupwareResource::onReloadConfig() +{ + Settings::self()->reloadConfig(); + synchronize(); +} + +void DavGroupwareResource::onCollectionRemovedFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( i18n( "Unable to remove collection: %1", job->errorText() ) ); + return; + } + + Akonadi::Collection collection = job->property( "collection" ).value(); + if ( mItemsRidCache.contains( collection.remoteId() ) ) { + foreach ( const QString &rid, mItemsRidCache.value( collection.remoteId() ) ) { + mEtagCache.removeEtag( rid ); + } + mItemsRidCache.remove( collection.remoteId() ); + } + changeProcessed(); +} + +void DavGroupwareResource::onRetrieveCollectionsFinished( KJob *job ) +{ + const DavCollectionsMultiFetchJob *fetchJob = qobject_cast( job ); + + if ( job->error() ) { + kWarning() << "Unable to fetch collections" << job->error() << job->errorText(); + cancelTask( i18n( "Unable to retrieve collections: %1", job->errorText() ) ); + mSyncErrorNotified = true; + return; + } + + Akonadi::Collection::List collections; + collections << mDavCollectionRoot; + QSet seenCollectionsUrls; + + const DavCollection::List davCollections = fetchJob->collections(); + + foreach ( const DavCollection &davCollection, davCollections ) { + if ( seenCollectionsUrls.contains( davCollection.url() ) ) + continue; + else + seenCollectionsUrls.insert( davCollection.url() ); + + if ( !mItemsRidCache.contains( davCollection.url() ) ) + mItemsRidCache.insert( davCollection.url(), QSet() ); + + Akonadi::Collection collection; + collection.setParentCollection( mDavCollectionRoot ); + collection.setRemoteId( davCollection.url() ); + collection.setName( collection.remoteId() ); + + if ( !davCollection.displayName().isEmpty() ) { + EntityDisplayAttribute *attr = collection.attribute( Collection::AddIfMissing ); + attr->setDisplayName( davCollection.displayName() ); + } + + QStringList mimeTypes; + mimeTypes << Collection::mimeType(); + + const DavCollection::ContentTypes contentTypes = davCollection.contentTypes(); + if ( contentTypes & DavCollection::Calendar ) + mimeTypes << QLatin1String( "text/calendar" ); + + if ( contentTypes & DavCollection::Events ) + mimeTypes << KCalCore::Event::eventMimeType(); + + if ( contentTypes & DavCollection::Todos ) + mimeTypes << KCalCore::Todo::todoMimeType(); + + if ( contentTypes & DavCollection::Contacts ) + mimeTypes << KABC::Addressee::mimeType(); + + if ( contentTypes & DavCollection::FreeBusy ) + mimeTypes << KCalCore::FreeBusy::freeBusyMimeType(); + + if ( contentTypes & DavCollection::Journal ) + mimeTypes << KCalCore::Journal::journalMimeType(); + + collection.setContentMimeTypes( mimeTypes ); + setCollectionIcon( collection /*by-ref*/ ); + + DavProtocolAttribute *protoAttr = collection.attribute( Collection::AddIfMissing ); + protoAttr->setDavProtocol( davCollection.protocol() ); + + DavUtils::Privileges privileges = davCollection.privileges(); + Akonadi::Collection::Rights rights; + + if ( privileges & DavUtils::All || privileges & DavUtils::Write ) + rights |= Akonadi::Collection::AllRights; + + if ( privileges & DavUtils::WriteContent ) + rights |= Akonadi::Collection::CanChangeItem; + + if ( privileges & DavUtils::Bind ) + rights |= Akonadi::Collection::CanCreateItem; + + if ( privileges & DavUtils::Unbind ) + rights |= Akonadi::Collection::CanDeleteItem; + + if ( privileges == DavUtils::Read ) + rights |= Akonadi::Collection::ReadOnly; + + + collection.setRights( rights ); + mEtagCache.sync( collection ); + collections << collection; + } + + foreach ( const QString &rid, mItemsRidCache.keys() ) { + if ( !seenCollectionsUrls.contains( rid ) ) { + foreach ( const QString &itemRid, mItemsRidCache[rid] ) { + mEtagCache.removeEtag( itemRid ); + } + mItemsRidCache.remove( rid ); + } + } + + collectionsRetrieved( collections ); +} + +void DavGroupwareResource::onRetrieveItemsFinished( KJob *job ) +{ + if ( job->error() ) { + if ( mSyncErrorNotified ) { + cancelTask(); + } + else { + cancelTask( i18n( "Unable to retrieve items: %1", job->errorText() ) ); + mSyncErrorNotified = true; + } + return; + } + + const Collection collection = job->property( "collection" ).value(); + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( collection.remoteId() ); + const bool protocolSupportsMultiget = DavManager::self()->davProtocol( davUrl.protocol() )->useMultiget(); + + const DavItemsListJob *listJob = qobject_cast( job ); + + Akonadi::Item::List changedItems; + QSet seenRids; + QStringList changedRids; + + const DavItem::List davItems = listJob->items(); + foreach ( const DavItem &davItem, davItems ) { + seenRids.insert( davItem.url() ); + + Akonadi::Item item; + item.setParentCollection( collection ); + item.setRemoteId( davItem.url() ); + item.setMimeType( davItem.contentType() ); + item.setRemoteRevision( davItem.etag() ); + + if ( mEtagCache.etagChanged( item.remoteId(), davItem.etag() ) ) { + mEtagCache.markAsChanged( item.remoteId() ); + changedRids << item.remoteId(); + changedItems << item; + + // Only clear the payload (and therefor trigger a refetch from the backend) if we + // do not use multiget, because in this case we fetch the complete payload + // some lines below already. + if ( !protocolSupportsMultiget ) { + kDebug() << "Outdated item " << item.remoteId() << " (etag = " << davItem.etag() << ")"; + item.clearPayload(); + } + } + } + + QSet removedRids = mItemsRidCache[collection.remoteId()]; + mItemsRidCache[collection.remoteId()] = seenRids; + removedRids.subtract( seenRids ); + foreach ( const QString &rmd, removedRids ) { + Akonadi::Item item; + item.setParentCollection( collection ); + item.setRemoteId( rmd ); + mEtagCache.removeEtag( rmd ); + + // Use a job to delete items as itemsRetrievedIncremental seem to choke + // when many items are given with just their RID. + Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob( item ); + deleteJob->start(); + } + + + // If the protocol supports multiget then deviate from the expected behavior + // and fetch all items with payload now instead of waiting for Akonadi to + // request it item by item in retrieveItem(). + // This allows the resource to use the multiget query and let it be nice + // to the remote server : only one request for n items instead of n requests. + if ( protocolSupportsMultiget && !changedRids.isEmpty() ) { + DavItemsFetchJob *fetchJob = new DavItemsFetchJob( davUrl, changedRids ); + connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(onMultigetFinished(KJob*)) ); + fetchJob->setProperty( "items", QVariant::fromValue( changedItems ) ); + fetchJob->start(); + // delay the call of itemsRetrieved() to onMultigetFinished() + } else { + itemsRetrievedIncremental( changedItems, Akonadi::Item::List() ); + } +} + +void DavGroupwareResource::onMultigetFinished( KJob *job ) +{ + if ( job->error() ) { + if ( mSyncErrorNotified ) { + cancelTask(); + } + else { + cancelTask( i18n( "Unable to retrieve items: %1", job->errorText() ) ); + mSyncErrorNotified = true; + } + return; + } + + const Akonadi::Item::List origItems = job->property( "items" ).value(); + const DavItemsFetchJob *davJob = qobject_cast( job ); + + Akonadi::Item::List items; + foreach ( Akonadi::Item item, origItems ) { //krazy:exclude=foreach non-const is intended here + const DavItem davItem = davJob->item( item.remoteId() ); + + // No data was retrieved for this item, maybe because it is not out of date + if ( davItem.data().isEmpty() ) { + if ( !mEtagCache.isOutOfDate( item.remoteId() ) ) + items << item; + continue; + } + + Akonadi::Item::List extraItems; + if ( !DavUtils::parseDavData( davItem, item, extraItems ) ) + continue; + + // update etag + item.setRemoteRevision( davItem.etag() ); + mEtagCache.setEtag( item.remoteId(), davItem.etag() ); + items << item; + foreach ( const Akonadi::Item &extraItem, extraItems ) { + mEtagCache.setEtag( extraItem.remoteId(), davItem.etag() ); + items << extraItem; + } + } + + itemsRetrievedIncremental( items, Akonadi::Item::List() ); +} + +void DavGroupwareResource::onRetrieveItemFinished( KJob *job ) +{ + onItemFetched( job, ItemUpdateAdd ); +} + +void DavGroupwareResource::onItemRefreshed( KJob* job ) +{ + ItemFetchUpdateType update = ItemUpdateChange; + if ( job->property( "isRemoval" ).isValid() && job->property( "isRemoval" ).toBool() ) + update = ItemUpdateNone; + + onItemFetched( job, update ); +} + +void DavGroupwareResource::onItemFetched( KJob* job, ItemFetchUpdateType updateType ) +{ + if ( job->error() ) { + if ( mSyncErrorNotified ) { + cancelTask(); + } + else { + cancelTask( i18n( "Unable to retrieve item: %1", job->errorText() ) ); + mSyncErrorNotified = true; + } + return; + } + + const DavItemFetchJob *fetchJob = qobject_cast( job ); + const DavItem davItem = fetchJob->item(); + Akonadi::Item item = fetchJob->property( "item" ).value(); + + Akonadi::Item::List extraItems; + if ( !DavUtils::parseDavData( davItem, item, extraItems ) ) + return; + + // update etag + item.setRemoteRevision( davItem.etag() ); + mEtagCache.setEtag( item.remoteId(), davItem.etag() ); + + if ( !extraItems.isEmpty() ) { + for ( int i = 0; i < extraItems.size(); ++i ) + mEtagCache.setEtag( extraItems.at( i ).remoteId(), davItem.etag() ); + + Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob( extraItems ); + j->setIgnorePayload( true ); + } + + if ( updateType == ItemUpdateChange ) + changeCommitted( item ); + else if ( updateType == ItemUpdateAdd ) + itemRetrieved( item ); +} + +void DavGroupwareResource::onItemAddedFinished( KJob *job ) +{ + const DavItemCreateJob *createJob = qobject_cast( job ); + const DavItem davItem = createJob->item(); + Akonadi::Item item = createJob->property( "item" ).value(); + item.setRemoteId( davItem.url() ); + + if ( createJob->error() ) { + kError() << "Error when uploading item:" << createJob->error() << createJob->errorString(); + if ( createJob->canRetryLater() ) { + retryAfterFailure(createJob->errorString()); + } + else { + cancelTask( i18n( "Unable to add item: %1", createJob->errorString() ) ); + } + return; + } + + Akonadi::Collection collection = createJob->property( "collection" ).value(); + mItemsRidCache[collection.remoteId()].insert( davItem.url() ); + + if ( davItem.etag().isEmpty() ) { + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( item.parentCollection().remoteId(), item.remoteId() ); + DavItemFetchJob *fetchJob = new DavItemFetchJob( davUrl, davItem ); + fetchJob->setProperty( "item", QVariant::fromValue( item ) ); + connect( fetchJob, SIGNAL(result(KJob*)), SLOT(onItemRefreshed(KJob*)) ); + fetchJob->start(); + } else { + item.setRemoteRevision( davItem.etag() ); + mEtagCache.setEtag( davItem.url(), davItem.etag() ); + changeCommitted( item ); + } +} + +void DavGroupwareResource::onItemChangedFinished( KJob *job ) +{ + const DavItemModifyJob *modifyJob = qobject_cast( job ); + const DavItem davItem = modifyJob->item(); + Akonadi::Item item = modifyJob->property( "item" ).value(); + Akonadi::Item::List dependentItems = modifyJob->property( "dependentItems" ).value(); + bool isRemoval = modifyJob->property( "isRemoval" ).isValid() && modifyJob->property( "isRemoval" ).toBool(); + + if ( modifyJob->error() ) { + kError() << "Error when uploading item:" << modifyJob->error() << modifyJob->errorString(); + if ( modifyJob->canRetryLater() ) { + retryAfterFailure(modifyJob->errorString()); + } + else { + cancelTask( i18n( "Unable to change item: %1", modifyJob->errorString() ) ); + } + return; + } + + if ( isRemoval ) { + Akonadi::Item removedItem = job->property( "removedItem" ).value(); + mEtagCache.removeEtag( removedItem.remoteId() ); + mItemsRidCache[removedItem.parentCollection().remoteId()].remove( removedItem.remoteId() ); + changeProcessed(); + } + + if ( davItem.etag().isEmpty() ) { + const DavUtils::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl( item.parentCollection().remoteId(), item.remoteId() ); + DavItemFetchJob *fetchJob = new DavItemFetchJob( davUrl, davItem ); + fetchJob->setProperty( "item", QVariant::fromValue( item ) ); + fetchJob->setProperty( "dependentItems", QVariant::fromValue( dependentItems ) ); + fetchJob->setProperty( "isRemoval", QVariant::fromValue( isRemoval ) ); + connect( fetchJob, SIGNAL(result(KJob*)), SLOT(onItemRefreshed(KJob*)) ); + fetchJob->start(); + } else { + if ( !isRemoval ) { + item.setRemoteRevision( davItem.etag() ); + mEtagCache.setEtag( davItem.url(), davItem.etag() ); + changeCommitted( item ); + } + + if ( !dependentItems.isEmpty() ) { + for ( int i = 0; i < dependentItems.size(); ++i ) { + dependentItems[i].setRemoteRevision( davItem.etag() ); + mEtagCache.setEtag( dependentItems.at( i ).remoteId(), davItem.etag() ); + } + + Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob( dependentItems ); + j->setIgnorePayload( true ); + } + } +} + +void DavGroupwareResource::onItemRemovedFinished( KJob *job ) +{ + if ( job->error() ) { + const DavItemDeleteJob *deleteJob = qobject_cast( job ); + + if ( deleteJob->canRetryLater() ) { + retryAfterFailure(job->errorString()); + } + else { + cancelTask( i18n( "Unable to remove item: %1", job->errorString() ) ); + } + } + else { + Akonadi::Item item = job->property( "item" ).value(); + Akonadi::Collection collection = job->property( "collection" ).value(); + mItemsRidCache[collection.remoteId()].remove( item.remoteId() ); + mEtagCache.removeEtag( item.remoteId() ); + changeProcessed(); + } +} + +void DavGroupwareResource::onCollectionDiscovered( int protocol, const QString &collection, const QString &config ) +{ + Settings::self()->addCollectionUrlMapping( DavUtils::Protocol( protocol ), collection, config ); +} + +void DavGroupwareResource::onEtagChanged(const QString& itemUrl, const QString& etag) +{ + mEtagCache.setEtag( itemUrl, etag ); +} + +bool DavGroupwareResource::configurationIsValid() +{ + if ( Settings::self()->configuredDavUrls().empty() ) { + emit status( NotConfigured, i18n( "The resource is not configured yet" ) ); + cancelTask( i18n( "The resource is not configured yet" ) ); + return false; + } + + int newICT = Settings::self()->refreshInterval(); + if ( newICT == 0 ) + newICT = -1; + + if ( newICT != mDavCollectionRoot.cachePolicy().intervalCheckTime() ) { + Akonadi::CachePolicy cachePolicy = mDavCollectionRoot.cachePolicy(); + cachePolicy.setIntervalCheckTime( newICT ); + + mDavCollectionRoot.setCachePolicy( cachePolicy ); + } + + if ( !Settings::self()->displayName().isEmpty() ) { + EntityDisplayAttribute *attribute = mDavCollectionRoot.attribute( Collection::AddIfMissing ); + attribute->setDisplayName( Settings::self()->displayName() ); + setName( Settings::self()->displayName() ); + } + + return true; +} + +void DavGroupwareResource::retryAfterFailure(const QString &errorMessage) +{ + emit status(Broken, errorMessage); + deferTask(); + setTemporaryOffline(Settings::self()->refreshInterval() <= 0 ? 300 : Settings::self()->refreshInterval() * 60); +} + +/*static*/ +void DavGroupwareResource::setCollectionIcon( Akonadi::Collection &collection ) +{ + const QStringList mimeTypes = collection.contentMimeTypes(); + if ( mimeTypes.count() == 1 ) { + QHash mapping; + mapping.insert( KCalCore::Event::eventMimeType(), QLatin1String( "view-calendar" ) ); + mapping.insert( KCalCore::Todo::todoMimeType(), QLatin1String( "view-calendar-tasks" ) ); + mapping.insert( KCalCore::Journal::journalMimeType(), QLatin1String( "view-pim-journal" ) ); + mapping.insert( KABC::Addressee::mimeType(), QLatin1String( "view-pim-contacts" ) ); + + if ( mapping.contains( mimeTypes.first() ) ) { + EntityDisplayAttribute *attribute = collection.attribute( Collection::AddIfMissing ); + attribute->setIconName( mapping.value( mimeTypes.first() ) ); + } + } +} + +AKONADI_RESOURCE_MAIN( DavGroupwareResource ) + diff --git a/kdepim-runtime/resources/dav/resource/davgroupwareresource.desktop b/kdepim-runtime/resources/dav/resource/davgroupwareresource.desktop new file mode 100644 index 00000000..5a899474 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davgroupwareresource.desktop @@ -0,0 +1,89 @@ +[Desktop Entry] +Name=DAV groupware resource +Name[bs]=DAV grupni resurs +Name[ca]=Recurs de treball en grup DAV +Name[ca@valencia]=Recurs de treball en grup DAV +Name[da]=Ressource til DAV-groupware +Name[de]=DAV-Groupware-Ressource +Name[el]=ΠόÏος DAV groupware +Name[en_GB]=DAV groupware resource +Name[es]=Recurso de trabajo en grupo DAV +Name[et]=DAV grupitöö ressurss +Name[fi]=DAV-työryhmäresurssi +Name[fr]=Ressource pour logiciels de collaboration DAV +Name[ga]=Acmhainn groupware DAV +Name[gl]=Servidor de traballo en grupo DAV +Name[hu]=DAV csoportmunka-erÅ‘forrás +Name[ia]=Ressource de DAV Groupware +Name[it]=Risorsa di groupware DAV +Name[ja]=DAV グループウェアリソース +Name[kk]=DAV топтық реÑурÑÑ‹ +Name[km]=ធនធាន DAV groupware +Name[ko]=DAV 그룹웨어 ìžì› +Name[lt]=DAV grupinÄ—s įrangos resursai +Name[lv]=DAV grupprogrammatÅ«ras resurss +Name[nb]=DAV Groupware-ressurs +Name[nds]=DAV-Arbeitkoppel-Ressource +Name[nl]=DAV-groupware-hulpbron +Name[pa]=DAV ਗਰà©à©±à¨ªà¨µà©‡à¨…ਰ ਸਰੋਤ +Name[pl]=Zasób groupware DAV +Name[pt]=Recurso de 'groupware' em DAV +Name[pt_BR]=Recurso de groupware por DAV +Name[ru]=ИÑточник данных ÑовмеÑтной работы DAV +Name[sk]=Zdroj DAV groupware +Name[sl]=Vir za skupinsko delo DAV +Name[sr]=ГрупверÑки ДÐÐ’ реÑÑƒÑ€Ñ +Name[sr@ijekavian]=ГрупверÑки ДÐÐ’ реÑÑƒÑ€Ñ +Name[sr@ijekavianlatin]=Grupverski DAV resurs +Name[sr@latin]=Grupverski DAV resurs +Name[sv]=DAV-grupprogramresurs +Name[tr]=DAV groupware kaynağı +Name[uk]=РеÑÑƒÑ€Ñ Ð³Ñ€ÑƒÐ¿Ð¾Ð²Ð¾Ñ— роботи DAV +Name[x-test]=xxDAV groupware resourcexx +Name[zh_CN]=DAV ç¾¤ä»¶èµ„æº +Name[zh_TW]=DAV ç¾¤çµ„è³‡æº +Comment="Resource to manage DAV calendars and address books (CalDAV, GroupDAV)" +Comment[bs]="Resurs za upravaljanje DAV kalendarima i adresarima (CalDAV, GroupDAV)" +Comment[ca]=«Recurs per gestionar calendaris i llibretes d'adreces DAV (CalDAV, GroupDAV)» +Comment[ca@valencia]=«Recurs per gestionar calendaris i llibretes d'adreces DAV (CalDAV, GroupDAV)» +Comment[da]="Ressource til hÃ¥ndtering af DAV-kalendere og -adressebøger (CalDAV, GroupDAV)" +Comment[de]="Ressource zur Verwaltung von DAV-Kalendern und -Adressbüchern (CalDAV, GroupDAV)" +Comment[el]="ΠόÏος για τη διαχείÏιση ημεÏολογίων DAV και βιβλίων διευθÏνσεων (CalDAV, GroupDAV)" +Comment[en_GB]="Resource to manage DAV calendars and address books (CalDAV, GroupDAV)" +Comment[es]=«Recurso para gestionar calendarios DAV y libretas de direcciones (CalDAV, GroupDAV)» +Comment[et]="Ressurss DAV-kalendrite ja aadressiraamatute (CalDAV, GroupDAV) haldamiseks" +Comment[fi]="DAV-kalenterien ja -osoitekirjojen (CalDAV, GroupDAV) hallintaresurssi" +Comment[fr]=« Ressource pour gérer les agendas et carnets d'adresses DAV (CalDAV, GroupDAV) » +Comment[gl]=«Recurso para xestionar os calendarios e cadernos de enderezos DAV (CalDAV, GroupDAV)» +Comment[hu]=„ErÅ‘forrás DAV naptárak és címjegyzékek (CalDAV, GroupDAV) kezeléséhez†+Comment[ia]="Ressource pro administrar calendarios e adressarios de DAV (CalDAV, GroupDAV)" +Comment[it]="Risorsa per gestire calendari e rubriche DAV (CalDAV, GroupDAV)" +Comment[kk]="DAV күнтізбе және адреÑтік кітапшаÑын (CalDAV, GroupDAV) баÑқару реÑурÑÑ‹" +Comment[ko]="DAV 달력과 주소ë¡ì„ 관리하는 ìžì› (CalDAV, GroupDAV)" +Comment[lt]=„Resursas, DAV kalendorių ir adresų knygų, valdymui (CalDAV, GroupDAV)“ +Comment[nb]=«Ressurs som hÃ¥ndterer DAV-kalendere og adressebøker (CalDAV, GroupDAV)» +Comment[nds]=„Ressource för de Pleeg vun DAV-Kalenners un -Adressböker (CalDAV, GroupDAV)“ +Comment[nl]="Hulpbron om DAV-agenda's en adresboeken (CalDAV, GroupDAV) te beheren" +Comment[pl]="Zasób do obsÅ‚ugi kalendarzy i książek adresowych DAV (CalDAV, GroupDAV)" +Comment[pt]=Recurso para gerir os calendários e livros de endereços em DAV (CalDAV, GroupDAV) +Comment[pt_BR]="Recurso para gerenciar calendários e livros de endereços por DAV (CalDAV, GroupDAV)" +Comment[ru]="ИÑточники данных Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñми и адреÑными книгами DAV (CalDAV, GroupDAV)" +Comment[sk]="Zdroj na správu DAV kalendárov a adresárov kontaktov (CalDAV, GroupDAV)" +Comment[sr]="РеÑÑƒÑ€Ñ Ð·Ð° управљање ДÐÐ’ календарима и адреÑарима (калДÐÐ’, групДÐÐ’)" +Comment[sr@ijekavian]="РеÑÑƒÑ€Ñ Ð·Ð° управљање ДÐÐ’ календарима и адреÑарима (калДÐÐ’, групДÐÐ’)" +Comment[sr@ijekavianlatin]="Resurs za upravljanje DAV kalendarima i adresarima (CalDAV, GroupDAV)" +Comment[sr@latin]="Resurs za upravljanje DAV kalendarima i adresarima (CalDAV, GroupDAV)" +Comment[sv]="Resurs för att hantera DAV-kalendrar och adressböcker (CalDAV, GroupDAV)" +Comment[tr]="DAV takvim ve adres defterlerini yönetmek için kaynak (CalDAV, GroupDAV)" +Comment[uk]="РеÑÑƒÑ€Ñ Ð´Ð»Ñ ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñми Ñ– адреÑними книгами DAV (CalDAV, GroupDAV)" +Comment[x-test]=xx"Resource to manage DAV calendars and address books (CalDAV, GroupDAV)"xx +Comment[zh_CN]=â€œç®¡ç† DAV 日历和地å€ç°¿çš„èµ„æº (CalDAVã€GroupDAV)†+Comment[zh_TW]="ç®¡ç† DAV 行事曆與通訊錄的資æºï¼ˆCalDAV, GroupDAV)" +Type=AkonadiResource +Exec=akonadi_davgroupware_resource +Icon=folder-remote + +X-Akonadi-Custom-KAccounts=dav-calendar,dav-contacts +X-Akonadi-MimeTypes=text/calendar,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy,text/directory +X-Akonadi-Capabilities=Resource,FreeBusyProvider +X-Akonadi-Identifier=akonadi_davgroupware_resource diff --git a/kdepim-runtime/resources/dav/resource/davgroupwareresource.h b/kdepim-runtime/resources/dav/resource/davgroupwareresource.h new file mode 100644 index 00000000..739aa766 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davgroupwareresource.h @@ -0,0 +1,124 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVGROUPWARERESOURCE_H +#define DAVGROUPWARERESOURCE_H + +#include "etagcache.h" + +#include +#include + +class DavFreeBusyHandler; +class DavItem; +class KDateTime; + +#include +#include + +class DavGroupwareResource : public Akonadi::ResourceBase, + public Akonadi::AgentBase::Observer, + public Akonadi::FreeBusyProviderBase +{ + Q_OBJECT + + public: + explicit DavGroupwareResource( const QString &id ); + ~DavGroupwareResource(); + + virtual void collectionRemoved( const Akonadi::Collection &collection ); + virtual void cleanup(); + + virtual KDateTime lastCacheUpdate() const; + virtual void canHandleFreeBusy( const QString &email ) const; + virtual void retrieveFreeBusy( const QString &email, const KDateTime &start, const KDateTime &end ); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &collection ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + virtual void doSetOnline( bool online ); + + private: + enum ItemFetchUpdateType { + ItemUpdateNone, + ItemUpdateAdd, + ItemUpdateChange + }; + + private Q_SLOTS: + void createInitialCache(); + void onCreateInitialCacheReady( KJob* ); + + void onReloadConfig(); + void onCollectionRemovedFinished( KJob* ); + + void onHandlesFreeBusy( const QString &email, bool handles ); + void onFreeBusyRetrieved( const QString &email, const QString &freeBusy, bool success, const QString &errorText ); + + void onRetrieveCollectionsFinished( KJob* ); + void onRetrieveItemsFinished( KJob* ); + void onMultigetFinished( KJob* ); + void onRetrieveItemFinished( KJob* ); + /** + * Called when a new item has been fetched from the backend. + * + * @param job The job that fetched the item + * @param updateType The type of update that triggered this call. The task notification sent + * sent to Akonadi will depend on this flag. + */ + void onItemFetched( KJob* job, ItemFetchUpdateType updateType ); + void onItemRefreshed( KJob* job ); + + void onItemAddedFinished( KJob* ); + void onItemChangePrepared( KJob* ); + void onItemChangedFinished( KJob* ); + void onItemRemovalPrepared( KJob* ); + void onItemRemovedFinished( KJob* ); + + void onCollectionDiscovered( int protocol, const QString &collectionUrl, const QString &configuredUrl ); + void onEtagChanged( const QString &itemUrl, const QString &etag ); + + private: + void doItemChange( const Akonadi::Item &item, const Akonadi::Item::List &dependentItems = Akonadi::Item::List() ); + void doItemRemoval( const Akonadi::Item &item ); + + bool configurationIsValid(); + void retryAfterFailure(const QString &errorMessage); + + /** + * Collections which only support one mime type have an icon indicating what they support. + */ + static void setCollectionIcon( Akonadi::Collection &collection ); + + Akonadi::Collection mDavCollectionRoot; + EtagCache mEtagCache; + DavFreeBusyHandler *mFreeBusyHandler; + QMap< QString, QSet > mItemsRidCache; + bool mSyncErrorNotified; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/davgroupwareresource.kcfg b/kdepim-runtime/resources/dav/resource/davgroupwareresource.kcfg new file mode 100644 index 00000000..2e22df0b --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davgroupwareresource.kcfg @@ -0,0 +1,45 @@ + + + + + + + 1 + + + + + + + + 5 + + + + + + + + + + + + + + + + false + + + + + + + + + + diff --git a/kdepim-runtime/resources/dav/resource/davprotocolattribute.cpp b/kdepim-runtime/resources/dav/resource/davprotocolattribute.cpp new file mode 100644 index 00000000..e961ee9a --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davprotocolattribute.cpp @@ -0,0 +1,54 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "davprotocolattribute.h" + +DavProtocolAttribute::DavProtocolAttribute( int protocol ) + : mDavProtocol( protocol ) +{ +} + +int DavProtocolAttribute::davProtocol() const +{ + return mDavProtocol; +} + +void DavProtocolAttribute::setDavProtocol( int protocol ) +{ + mDavProtocol = protocol; +} + +Akonadi::Attribute* DavProtocolAttribute::clone() const +{ + return new DavProtocolAttribute( mDavProtocol ); +} + +QByteArray DavProtocolAttribute::type() const +{ + return "davprotocol"; +} + +QByteArray DavProtocolAttribute::serialized() const +{ + return QByteArray::number( mDavProtocol ); +} + +void DavProtocolAttribute::deserialize( const QByteArray &data ) +{ + mDavProtocol = data.toInt(); +} diff --git a/kdepim-runtime/resources/dav/resource/davprotocolattribute.h b/kdepim-runtime/resources/dav/resource/davprotocolattribute.h new file mode 100644 index 00000000..74a1e303 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/davprotocolattribute.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DAVPROTOCOLATTRIBUTE_H +#define DAVPROTOCOLATTRIBUTE_H + +#include + +#include + +class DavProtocolAttribute : public Akonadi::Attribute +{ + public: + explicit DavProtocolAttribute( int protocol = 0 ); + + void setDavProtocol( int protocol ); + int davProtocol() const; + + virtual Akonadi::Attribute* clone() const; + virtual QByteArray type() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + + private: + int mDavProtocol; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/searchdialog.cpp b/kdepim-runtime/resources/dav/resource/searchdialog.cpp new file mode 100644 index 00000000..ca0a19bf --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/searchdialog.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2011 Grégory Oestreicher + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "searchdialog.h" + +#include "davcollectionsfetchjob.h" +#include "davmanager.h" +#include "davprincipalsearchjob.h" +#include "davprotocolbase.h" +#include "davutils.h" + +#include +#include +#include +#include + +#include + +SearchDialog::SearchDialog( QWidget *parent ) + : KDialog( parent ), mModel( new QStandardItemModel( this ) ), mSubJobCount( 0 ) +{ + mUi.setupUi( mainWidget() ); + mUi.credentialsGroup->setVisible( false ); + mUi.searchResults->setModel( mModel ); + + setButtonFocus( KDialog::None ); + setDefaultButton( KDialog::None ); + setButtonText( KDialog::Ok, i18n( "Add selected items" ) ); + + connect( mUi.searchUrl, SIGNAL(textChanged(QString)), this, SLOT(checkUserInput()) ); + connect( mUi.searchParam, SIGNAL(textChanged(QString)), this, SLOT(checkUserInput()) ); + connect( mUi.searchButton, SIGNAL(clicked()), this, SLOT(search()) ); + + checkUserInput(); +} + +SearchDialog::~SearchDialog() +{ +} + +bool SearchDialog::useDefaultCredentials() const +{ + return mUi.useDefaultCreds->isChecked(); +} + +void SearchDialog::setUsername( const QString& user ) +{ + mUi.username->setText( user ); +} + +QString SearchDialog::username() const +{ + return mUi.username->text(); +} + +void SearchDialog::setPassword( const QString& password ) +{ + mUi.password->setText( password ); +} + +QString SearchDialog::password() const +{ + return mUi.password->text(); +} + +QStringList SearchDialog::selection() const +{ + QModelIndexList indexes = mUi.searchResults->selectionModel()->selectedIndexes(); + QStringList ret; + foreach ( const QModelIndex &index, indexes ) { + kError() << "SELECTED DATA: " << index.data( Qt::UserRole + 1 ).toString(); + ret << index.data( Qt::UserRole + 1 ).toString(); + } + return ret; +} + +void SearchDialog::checkUserInput() +{ + if ( mUi.searchUrl->text().isEmpty() || mUi.searchParam->text().isEmpty() ) + mUi.searchButton->setEnabled( false ); + else + mUi.searchButton->setEnabled( true ); +} + +void SearchDialog::search() +{ + mUi.searchResults->setEnabled( false ); + mModel->clear(); + DavPrincipalSearchJob::FilterType filter; + + if ( mUi.searchType->currentIndex() == 0 ) + filter = DavPrincipalSearchJob::DisplayName; + else + filter = DavPrincipalSearchJob::EmailAddress; + + KUrl url( mUi.searchUrl->text() ); + url.setUser( mUi.username->text() ); + url.setPassword( mUi.password->text() ); + DavUtils::DavUrl davUrl; + davUrl.setUrl( url ); + + DavPrincipalSearchJob *job = new DavPrincipalSearchJob( davUrl, filter, mUi.searchParam->text() ); + + const DavProtocolBase *proto = DavManager::self()->davProtocol( DavUtils::CalDav ); + job->fetchProperty( proto->principalHomeSet(), proto->principalHomeSetNS() ); + + proto = DavManager::self()->davProtocol( DavUtils::CardDav ); + job->fetchProperty( proto->principalHomeSet(), proto->principalHomeSetNS() ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(onSearchJobFinished(KJob*)) ); + job->start(); +} + +void SearchDialog::onSearchJobFinished( KJob* job ) +{ + if ( job->error() ) { + KMessageBox::error( this, i18n( "An error occurred when executing search:\n%1", job->errorText() ) ); + return; + } + + DavPrincipalSearchJob *davJob = qobject_cast( job ); + QList results = davJob->results(); + + const DavProtocolBase *caldav = DavManager::self()->davProtocol( DavUtils::CalDav ); + DavUtils::DavUrl davUrl = davJob->davUrl(); + KUrl url = davUrl.url(); + + foreach ( const DavPrincipalSearchJob::Result &result, results ) { + if ( result.value.startsWith( QLatin1Char('/') ) ) { + url.setPath( result.value ); + } else { + KUrl tmp( result.value ); + tmp.setUser( url.user() ); + tmp.setPassword( url.password() ); + url = tmp; + } + davUrl.setUrl( url ); + + if ( result.property == caldav->principalHomeSet() ) + davUrl.setProtocol( DavUtils::CalDav ); + else + davUrl.setProtocol( DavUtils::CardDav ); + + DavCollectionsFetchJob *fetchJob = new DavCollectionsFetchJob( davUrl ); + connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFetchJobFinished(KJob*)) ); + fetchJob->start(); + ++mSubJobCount; + } +} + +void SearchDialog::onCollectionsFetchJobFinished( KJob* job ) +{ + --mSubJobCount; + + if ( mSubJobCount == 0 ) + mUi.searchResults->setEnabled( true ); + + if ( job->error() ) { + if ( mSubJobCount == 0 ) + KMessageBox::error( this, i18n( "An error occurred when executing search:\n%1", job->errorText() ) ); + return; + } + + DavCollectionsFetchJob *davJob = qobject_cast( job ); + DavCollection::List collections = davJob->collections(); + + foreach ( const DavCollection &collection, collections ) { + QStandardItem *item = new QStandardItem( collection.displayName() ); + QString data( DavUtils::protocolName( collection.protocol() ) + QLatin1Char('|') + collection.url() ); + item->setData( data, Qt::UserRole + 1 ); + item->setToolTip( collection.url() ); + if ( collection.protocol() == DavUtils::CalDav ) + item->setIcon( KIcon( QLatin1String("view-calendar") ) ); + else + item->setIcon( KIcon( QLatin1String("view-pim-contacts" )) ); + mModel->appendRow( item ); + } +} diff --git a/kdepim-runtime/resources/dav/resource/searchdialog.h b/kdepim-runtime/resources/dav/resource/searchdialog.h new file mode 100644 index 00000000..08c8fda2 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/searchdialog.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2011 Grégory Oestreicher + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SEARCHDIALOG_H +#define SEARCHDIALOG_H + +#include "ui_searchdialog.h" + +#include + +class KJob; +class QStandardItemModel; + +class SearchDialog : public KDialog +{ + Q_OBJECT + + public: + explicit SearchDialog( QWidget *parent = 0 ); + virtual ~SearchDialog(); + + bool useDefaultCredentials() const; + + void setUsername( const QString &user ); + QString username() const; + + void setPassword( const QString &password ); + QString password() const; + + QStringList selection() const; + + private Q_SLOTS: + void checkUserInput(); + void search(); + void onSearchJobFinished( KJob *job ); + void onCollectionsFetchJobFinished( KJob *job ); + + private: + Ui::SearchDialog mUi; + QStandardItemModel *mModel; + int mSubJobCount; +}; + +#endif // SEARCHDIALOG_H diff --git a/kdepim-runtime/resources/dav/resource/searchdialog.ui b/kdepim-runtime/resources/dav/resource/searchdialog.ui new file mode 100644 index 00000000..f101297f --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/searchdialog.ui @@ -0,0 +1,221 @@ + + + SearchDialog + + + + 0 + 0 + 557 + 670 + + + + + + + + + + Use global credentials + + + true + + + + + + + Use specific credentials + + + + + + + Credentials + + + + + + + + + + + + Username + + + + + + + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + + + + + + Search URL + + + + + + + + + + Search for + + + + + + + + + + a person named + + + + + a contact with email + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Search + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + + 24 + 24 + + + + 2 + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + + + searchParam + returnPressed() + searchButton + animateClick() + + + 358 + 212 + + + 107 + 243 + + + + + useSpecificCreds + toggled(bool) + credentialsGroup + setVisible(bool) + + + 82 + 43 + + + 100 + 68 + + + + +
diff --git a/kdepim-runtime/resources/dav/resource/settings.cpp b/kdepim-runtime/resources/dav/resource/settings.cpp new file mode 100644 index 00000000..1d32418f --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/settings.cpp @@ -0,0 +1,624 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + Based on an original work for the IMAP resource which is : + Copyright (c) 2008 Volker Krause + Copyright (c) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "settings.h" + +#include "settingsadaptor.h" +#ifdef HAVE_ACCOUNTS +#include "../shared/getcredentialsjob.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ACCOUNTS +#include +#include +#endif + +class SettingsHelper +{ + public: + SettingsHelper() + : q( 0 ) + { + } + + ~SettingsHelper() + { + delete q; + } + + Settings *q; +}; + +K_GLOBAL_STATIC( SettingsHelper, s_globalSettings ) + +Settings::UrlConfiguration::UrlConfiguration() +{ +} + +Settings::UrlConfiguration::UrlConfiguration( const QString &serialized ) +{ + const QStringList splitString = serialized.split( QLatin1Char('|') ); + + if ( splitString.size() == 3 ) { + mUrl = splitString.at( 2 ); + mProtocol = DavUtils::protocolByName( splitString.at( 1 ) ); + mUser = splitString.at( 0 ); + } +} + +QString Settings::UrlConfiguration::serialize() +{ + QString serialized = mUser; + serialized.append( QLatin1Char('|') ).append( DavUtils::protocolName( DavUtils::Protocol( mProtocol ) ) ); + serialized.append( QLatin1Char('|') ).append( mUrl ); + return serialized; +} + +Settings *Settings::self() +{ + if ( !s_globalSettings->q ) { + new Settings; + s_globalSettings->q->readConfig(); + } + + return s_globalSettings->q; +} + +Settings::Settings() + : SettingsBase(), mWinId( 0 ) +{ + +#ifdef HAVE_ACCOUNTS + m_manager = 0; +#endif + Q_ASSERT( !s_globalSettings->q ); + s_globalSettings->q = this; + + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), this, + QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); + + if ( settingsVersion() == 1 ) + updateToV2(); + + if ( settingsVersion() == 2 ) + updateToV3(); +} + +Settings::~Settings() +{ + QMapIterator it( mUrls ); + while ( it.hasNext() ) { + it.next(); + delete it.value(); + } +} + +void Settings::setWinId( WId winId ) +{ + mWinId = winId; +} + +void Settings::cleanup() +{ + QFile cacheFile( mCollectionsUrlsMappingCache ); + cacheFile.remove(); +} + +void Settings::setResourceIdentifier(const QString& identifier) +{ + mResourceIdentifier = identifier; +} + +void Settings::setDefaultPassword( const QString &password ) +{ + savePassword( mResourceIdentifier, QLatin1String("$default$"), password ); +} + +QString Settings::defaultPassword() +{ + return loadPassword( mResourceIdentifier, QLatin1String("$default$") ); +} + +DavUtils::DavUrl::List Settings::configuredDavUrls() +{ + if ( mUrls.isEmpty() ) + buildUrlsList(); + DavUtils::DavUrl::List davUrls; + QMapIterator it( mUrls ); + + while ( it.hasNext() ) { + it.next(); + QStringList split = it.key().split( QLatin1Char(',') ); + davUrls << configuredDavUrl( DavUtils::protocolByName( split.at( 1 ) ), split.at( 0 ) ); + } + + return davUrls; +} + +DavUtils::DavUrl Settings::configuredDavUrl( DavUtils::Protocol proto, const QString &searchUrl, const QString &finalUrl ) +{ + if ( mUrls.isEmpty() ) + buildUrlsList(); + + KUrl fullUrl; + + if ( !finalUrl.isEmpty() ) + fullUrl = finalUrl; + else + fullUrl = searchUrl; + + const QString user = username( proto, searchUrl ); + fullUrl.setUser( user ); + fullUrl.setPassword( password( proto, searchUrl ) ); + + return DavUtils::DavUrl( fullUrl, proto ); +} + +DavUtils::DavUrl Settings::davUrlFromCollectionUrl( const QString &collectionUrl, const QString &finalUrl ) +{ + if ( mCollectionsUrlsMapping.isEmpty() ) + loadMappings(); + + DavUtils::DavUrl davUrl; + QString targetUrl = finalUrl.isEmpty() ? collectionUrl : finalUrl; + + if ( mCollectionsUrlsMapping.contains( collectionUrl ) ) { + QStringList split = mCollectionsUrlsMapping[ collectionUrl ].split( QLatin1Char(',') ); + if ( split.size() == 2 ) + davUrl = configuredDavUrl( DavUtils::protocolByName( split.at( 1 ) ), split.at( 0 ), targetUrl ); + } + + return davUrl; +} + +void Settings::addCollectionUrlMapping( DavUtils::Protocol proto, const QString &collectionUrl, const QString &configuredUrl ) +{ + if ( mCollectionsUrlsMapping.isEmpty() ) + loadMappings(); + + QString value = configuredUrl + QLatin1Char(',') + DavUtils::protocolName( proto ); + mCollectionsUrlsMapping.insert( collectionUrl, value ); + + // Update the cache now + //QMap tmp( mCollectionsUrlsMapping ); + QFile cacheFile( mCollectionsUrlsMappingCache ); + if ( cacheFile.open( QIODevice::WriteOnly ) ) { + QDataStream cache( &cacheFile ); + cache.setVersion( QDataStream::Qt_4_7 ); + cache << mCollectionsUrlsMapping; + cacheFile.close(); + } +} + +QStringList Settings::mappedCollections( DavUtils::Protocol proto, const QString &configuredUrl ) +{ + if ( mCollectionsUrlsMapping.isEmpty() ) + loadMappings(); + + QString value = configuredUrl + QLatin1Char(',') + DavUtils::protocolName( proto ); + return mCollectionsUrlsMapping.keys( value ); +} + +void Settings::reloadConfig() +{ +#ifdef HAVE_ACCOUNTS + importFromAccounts(); +#endif + buildUrlsList(); + updateRemoteUrls(); + loadMappings(); +} + +void Settings::newUrlConfiguration( Settings::UrlConfiguration *urlConfig ) +{ + QString key = urlConfig->mUrl + QLatin1Char(',') + DavUtils::protocolName( DavUtils::Protocol( urlConfig->mProtocol ) ); + + if ( mUrls.contains( key ) ) { + removeUrlConfiguration( DavUtils::Protocol( urlConfig->mProtocol ), urlConfig->mUrl ); + } + + mUrls[ key ] = urlConfig; + if ( urlConfig->mUser != QLatin1String( "$default$" ) ) + savePassword( key, urlConfig->mUser, urlConfig->mPassword ); + updateRemoteUrls(); +} + +void Settings::removeUrlConfiguration( DavUtils::Protocol proto, const QString &url ) +{ + QString key = url + QLatin1Char(',') + DavUtils::protocolName( proto ); + + if ( !mUrls.contains( key ) ) + return; + + delete mUrls[ key ]; + mUrls.remove( key ); + updateRemoteUrls(); +} + +Settings::UrlConfiguration * Settings::urlConfiguration( DavUtils::Protocol proto, const QString &url ) +{ + QString key = url + QLatin1Char(',') + DavUtils::protocolName( proto ); + + UrlConfiguration *ret = 0; + if ( mUrls.contains( key ) ) + ret = mUrls[ key ]; + + return ret; +} + +// DavUtils::Protocol Settings::protocol( const QString &url ) const +// { +// if ( mUrls.contains( url ) ) +// return DavUtils::Protocol( mUrls[ url ]->mProtocol ); +// else +// return DavUtils::CalDav; +// } + +QString Settings::username( DavUtils::Protocol proto, const QString &url ) const +{ + QString key = url + QLatin1Char(',') + DavUtils::protocolName( proto ); + + if ( mUrls.contains( key ) ) + if ( mUrls[ key ]->mUser == QLatin1String( "$default$" ) ) + return defaultUsername(); +#ifdef HAVE_ACCOUNTS + else if ( mUrls[ key ]->mUser == QLatin1String( "$accounts$" ) ) + return accountsUsername(); +#endif + else + return mUrls[ key ]->mUser; + else + return QString(); +} + +QString Settings::password(DavUtils::Protocol proto, const QString& url) +{ + QString key = url + QLatin1Char(',') + DavUtils::protocolName( proto ); + + if ( mUrls.contains( key ) ) + if ( mUrls[ key ]->mUser == QLatin1String( "$default$" ) ) + return defaultPassword(); + else + return mUrls[ key ]->mPassword; + else + return QString(); +} +#ifdef HAVE_ACCOUNTS +void Settings::importFromAccounts() +{ + kDebug(); + Accounts::AccountId id = accountId(); + kDebug() << "Account Id: " << id; + + if ( !m_manager ) { + m_manager = new Accounts::Manager( this ); + } + + if ( !m_manager->accountList().contains( id ) ) { + return; + } + + removeAccountsDisabledServices(); + addAccountsEnabledServices(); + + setSettingsVersion( 3 ); + writeConfig(); +} + +void Settings::addAccountsEnabledServices() +{ + kDebug(); + Accounts::Account *acc = m_manager->account( accountId() ); + QStringList enabledServices = accountServices(); + kDebug() << "Enabled" << enabledServices; + foreach( QString serviceType, enabledServices ) { + Accounts::ServiceList services = acc->services( serviceType ); + foreach( const Accounts::Service &service, services ) { + configureAccountService( acc, service ); + } + } +} + +void Settings::removeAccountsDisabledServices() +{ + kDebug(); + QStringList urls = remoteUrls(); + for (int i = 0; i < urls.size(); ++i) { + if ( !urls.at( i ).startsWith( "$accounts$" ) ) { + continue; + } + + if (urls.at( i ).contains( "carddav" ) + && accountServices().contains( "dav-contacts" )) { + continue; + } + if (urls.at( i ).contains( "caldav" ) + && accountServices().contains( "dav-calendar" )) { + continue; + } + + urls.removeAt( i ); + } + + setRemoteUrls( urls ); +} + +void Settings::configureAccountService(Accounts::Account *acc, const Accounts::Service& service) +{ + kDebug() << "Configuring service: " << service.name(); + + acc->selectService(); + QString domain = acc->valueAsString( "dav/scheme" ) + "://" + acc->valueAsString( "dav/host" ); + acc->selectService( service ); + + QString type; + if ( service.serviceType() == "dav-contacts" ) { + type = "CardDav"; + } else { + type = "CalDav"; + } + + QString url = "$accounts$|" + type + "|" + domain + acc->valueAsString("dav/path"); + kDebug() << url; + acc->selectService(); + + QStringList urls = remoteUrls(); + foreach ( const QString &serializedUrl, urls ) { + if ( url == serializedUrl ) { + kDebug() << "Url already configured"; + return; + } + } + + kDebug() << "Adding url"; + urls.append( url ); + setRemoteUrls( urls ); +} +#endif + +void Settings::buildUrlsList() +{ + foreach ( const QString &serializedUrl, remoteUrls() ) { + UrlConfiguration *urlConfig = new UrlConfiguration( serializedUrl ); + QString key = urlConfig->mUrl + QLatin1Char(',') + DavUtils::protocolName( DavUtils::Protocol( urlConfig->mProtocol ) ); + QString pass = loadPassword( key, urlConfig->mUser ); + if ( !pass.isNull() ) { + urlConfig->mPassword = pass; + mUrls[ key ] = urlConfig; + } + } +} + +void Settings::loadMappings() +{ + QString collectionsMappingCacheBase = QString::fromLatin1( "akonadi-davgroupware/%1_c2u.dat" ).arg( KApplication::applicationName() ); + mCollectionsUrlsMappingCache = KStandardDirs::locateLocal( "data", collectionsMappingCacheBase ); + QFile collectionsMappingsCache( mCollectionsUrlsMappingCache ); + + if ( collectionsMappingsCache.exists() ) { + if ( collectionsMappingsCache.open( QIODevice::ReadOnly ) ) { + QDataStream cache( &collectionsMappingsCache ); + cache >> mCollectionsUrlsMapping; + collectionsMappingsCache.close(); + } + } else if ( !collectionsUrlsMappings().isEmpty() ) { + QByteArray rawMappings = QByteArray::fromBase64( collectionsUrlsMappings().toLatin1() ); + QDataStream stream( &rawMappings, QIODevice::ReadOnly ); + stream >> mCollectionsUrlsMapping; + setCollectionsUrlsMappings( QString() ); + } +} + +void Settings::updateRemoteUrls() +{ + QStringList newUrls; + + QMapIterator it( mUrls ); + while ( it.hasNext() ) { + it.next(); + newUrls << it.value()->serialize(); + } + + setRemoteUrls( newUrls ); +} + +void Settings::savePassword( const QString &key, const QString &user, const QString &password ) +{ + QString entry = key + QLatin1Char(',') + user; + mPasswordsCache[entry] = password; + + KWallet::Wallet *wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), mWinId ); + if ( !wallet ) + return; + + if ( !wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) ) + wallet->createFolder( KWallet::Wallet::PasswordFolder() ); + + if ( !wallet->setFolder( KWallet::Wallet::PasswordFolder() ) ) + return; + + wallet->writePassword( entry, password ); +} + +QString Settings::loadPassword( const QString &key, const QString &user ) +{ + QString entry; + QString pass; + + if ( user == QLatin1String( "$default$" ) ) + entry = mResourceIdentifier + QLatin1Char(',') + user; +#ifdef HAVE_ACCOUNTS + else if (user == QLatin1String( "$accounts$" )) + return loadPasswordFromAccounts(); +#endif + else + entry = key + QLatin1Char(',') + user; + + if ( mPasswordsCache.contains( entry ) ) + return mPasswordsCache[entry]; + + KWallet::Wallet *wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), mWinId ); + if ( wallet ) { + if ( !wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) ) + wallet->createFolder( KWallet::Wallet::PasswordFolder() ); + + if ( wallet->setFolder( KWallet::Wallet::PasswordFolder() ) ) { + if ( !wallet->hasEntry( entry ) ) { + pass = promptForPassword( user ); + wallet->writePassword( entry, pass ); + } else { + wallet->readPassword( entry, pass ); + } + } + } + + if ( pass.isNull() && !KWallet::Wallet::isEnabled() ) + pass = promptForPassword( user ); + + if ( !pass.isNull() ) + mPasswordsCache[entry] = pass; + + return pass; +} + +#ifdef HAVE_ACCOUNTS +QString Settings::loadPasswordFromAccounts() +{ + kDebug() << "Getting credentials for: " << accountId(); + GetCredentialsJob *job = new GetCredentialsJob(accountId()); + job->exec(); + + return job->credentialsData().value("Secret").toString(); +} + +QString Settings::accountsUsername() const +{ + kDebug() << "Getting credentials for: " << accountId(); + GetCredentialsJob *job = new GetCredentialsJob(accountId()); + job->exec(); + + kDebug() << "Got some: " << job->credentialsData(); + + return job->credentialsData().value("UserName").toString(); +} +#endif + +QString Settings::promptForPassword( const QString &user ) +{ + QPointer dlg = new KDialog(); + QString password; + + QWidget *mainWidget = new QWidget( dlg ); + QVBoxLayout *vLayout = new QVBoxLayout(); + mainWidget->setLayout( vLayout ); + QLabel *label = new QLabel( i18n( "A password is required for user %1", + ( user == QLatin1String( "$default$" ) ? defaultUsername() : user ) ), + mainWidget + ); + vLayout->addWidget( label ); + QHBoxLayout *hLayout = new QHBoxLayout(); + label = new QLabel( i18n( "Password: " ), mainWidget ); + hLayout->addWidget( label ); + KLineEdit *lineEdit = new KLineEdit(); + lineEdit->setPasswordMode( true ); + hLayout->addWidget( lineEdit ); + vLayout->addLayout( hLayout ); + dlg->setMainWidget( mainWidget ); + lineEdit->setFocus(); + + const int result = dlg->exec(); + + if ( result == QDialog::Accepted && !dlg.isNull() ) { + password = lineEdit->text(); + } + + delete dlg; + return password; +} + +void Settings::updateToV2() +{ + // Take the first URL that was configured to get the username that + // has the most chances being the default + + QStringList urls = remoteUrls(); + if ( urls.isEmpty() ) + return; + + QString urlConfigStr = urls.at( 0 ); + UrlConfiguration urlConfig( urlConfigStr ); + QRegExp regexp( QLatin1Char('^') + urlConfig.mUser ); + + QMutableStringListIterator it( urls ); + while ( it.hasNext() ) { + it.next(); + it.value().replace( regexp, QLatin1String( "$default$" ) ); + } + + setDefaultUsername( urlConfig.mUser ); + QString key = urlConfig.mUrl + QLatin1Char(',') + DavUtils::protocolName( DavUtils::Protocol( urlConfig.mProtocol ) ); + QString pass = loadPassword( key, urlConfig.mUser ); + if ( !pass.isNull() ) + setDefaultPassword( pass ); + setRemoteUrls( urls ); + setSettingsVersion( 2 ); + writeConfig(); +} + +void Settings::updateToV3() +{ + QStringList updatedUrls; + + foreach ( const QString &url, remoteUrls() ) { + QStringList splitUrl = url.split( QLatin1Char('|') ); + + if ( splitUrl.size() == 3 ) { + DavUtils::Protocol protocol = DavUtils::protocolByTranslatedName( splitUrl.at( 1 ) ); + splitUrl[1] = DavUtils::protocolName( protocol ); + updatedUrls << splitUrl.join( QLatin1String("|") ); + } + } + + setRemoteUrls( updatedUrls ); + setSettingsVersion( 3 ); + writeConfig(); +} + diff --git a/kdepim-runtime/resources/dav/resource/settings.h b/kdepim-runtime/resources/dav/resource/settings.h new file mode 100644 index 00000000..13284a61 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/settings.h @@ -0,0 +1,150 @@ +/* + Copyright (c) 2009 Grégory Oestreicher + Based on an original work for the IMAP resource which is : + Copyright (c) 2008 Volker Krause + Copyright (c) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include "settingsbase.h" + +#include "davutils.h" + +#include + +#ifdef HAVE_ACCOUNTS +namespace Accounts { + class Service; + class Account; + class Manager; +}; +#endif + +class Settings : public SettingsBase +{ + Q_OBJECT + + public: + class UrlConfiguration + { + public: + UrlConfiguration(); + explicit UrlConfiguration( const QString &serialized ); + + /** + * Serializes the object. + * The string is the concatenation of the fields separated by a "|" : + * user|protocol|url + * The "protocol" component is the symbolic name of the protocol, + * as returned by DavUtils::protocolName(). + */ + QString serialize(); + QString mUrl; + QString mUser; + QString mPassword; + int mProtocol; + }; + + Settings(); + virtual ~Settings(); + static Settings* self(); + void setWinId( WId wid ); + void cleanup(); + + void setResourceIdentifier( const QString &identifier ); + void setDefaultPassword( const QString &password ); + QString defaultPassword(); + + DavUtils::DavUrl::List configuredDavUrls(); + + /** + * Creates and returns the DavUrl that corresponds to the configuration for searchUrl. + * If finalUrl is supplied, then it will be used in the returned object instead of the searchUrl. + */ + DavUtils::DavUrl configuredDavUrl( DavUtils::Protocol protocol, const QString &searchUrl, const QString &finalUrl = QString() ); + + /** + * Creates and return the DavUrl from the configured URL that has a mapping with @p collectionUrl. + * If @p finalUrl is supplied it will be used in the returned object, else @p collectionUrl will + * be used. + * If no configured URL can be found the returned DavUrl will have an empty url(). + */ + DavUtils::DavUrl davUrlFromCollectionUrl( const QString &collectionUrl, const QString &finalUrl = QString() ); + + /** + * Add a new mapping between the collection URL, as seen on the backend, and the + * URL configured by the user. A mapping here means that the collectionUrl has + * been discovered by a DavCollectionsFetchJob on the configuredUrl. + */ + void addCollectionUrlMapping( DavUtils::Protocol protocol, const QString &collectionUrl, const QString &configuredUrl ); + + /** + * Returns the collections URLs mapped behing @p configuredUrl and @p protocol. + */ + QStringList mappedCollections( DavUtils::Protocol protocol, const QString &configuredUrl ); + + /** + * Reloads the resource configuration taking into account any new modification + * + * Whenever the resource configuration is modified it needs to be reload in order + * to make the resource use the new config. This slot will call the needed methods + * to be sure that any new setting is taken into account. + */ + void reloadConfig(); + + void newUrlConfiguration( UrlConfiguration *urlConfig ); + void removeUrlConfiguration( DavUtils::Protocol protocol, const QString &url ); + UrlConfiguration * urlConfiguration( DavUtils::Protocol protocol, const QString &url ); + + //DavUtils::Protocol protocol( const QString &url ) const; + QString username( DavUtils::Protocol protocol, const QString &url ) const; + QString password( DavUtils::Protocol protocol, const QString &url ); + + private: +#ifdef HAVE_ACCOUNTS + void addAccountsEnabledServices(); + void removeAccountsDisabledServices(); + void configureAccountService(Accounts::Account *acc, const Accounts::Service &service); + void importFromAccounts(); + QString loadPasswordFromAccounts(); + QString accountsUsername() const; +#endif + void buildUrlsList(); + void loadMappings(); + void updateRemoteUrls(); + void savePassword( const QString &key, const QString &user, const QString &password ); + QString loadPassword( const QString &key, const QString &user ); + QString promptForPassword( const QString &user ); + + void updateToV2(); + void updateToV3(); + + WId mWinId; +#ifdef HAVE_ACCOUNTS + Accounts::Manager *m_manager; +#endif + QString mResourceIdentifier; + QMap mUrls; + QMap mPasswordsCache; + QString mCollectionsUrlsMappingCache; + QMap mCollectionsUrlsMapping; + QList mToDeleteUrlConfigs; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/settingsbase.kcfgc b/kdepim-runtime/resources/dav/resource/settingsbase.kcfgc new file mode 100644 index 00000000..6f577565 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/settingsbase.kcfgc @@ -0,0 +1,8 @@ +File=davgroupwareresource.kcfg +ClassName=SettingsBase +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/dav/resource/setupwizard.cpp b/kdepim-runtime/resources/dav/resource/setupwizard.cpp new file mode 100644 index 00000000..44b4777d --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/setupwizard.cpp @@ -0,0 +1,535 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "setupwizard.h" + +#include "davcollectionsmultifetchjob.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum GroupwareServers +{ + Citadel, + DAVical, + eGroupware, + OpenGroupware, + ScalableOGo, + Scalix, + Zarafa, + Zimbra +}; + +static QString settingsToUrl( const QWizard *wizard, const QString &protocol ) +{ + QString desktopFilePath = wizard->property( "providerDesktopFilePath" ).toString(); + if ( desktopFilePath.isEmpty() ) + return QString(); + + KService::Ptr service = KService::serviceByStorageId( desktopFilePath ); + if ( !service ) + return QString(); + + QStringList supportedProtocols = service->property( QLatin1String("X-DavGroupware-SupportedProtocols") ).toStringList(); + if ( !supportedProtocols.contains( protocol ) ) + return QString(); + + QString pathPattern; + + QString pathPropertyName( QLatin1String("X-DavGroupware-") + protocol + QLatin1String("Path") ); + if ( service->property( pathPropertyName ).isNull() ) + return QString(); + + pathPattern.append( service->property( pathPropertyName ).toString() + QLatin1Char('/') ); + + QString username = wizard->field( QLatin1String("credentialsUserName") ).toString(); + QString localPart( username ); + localPart.remove( QRegExp( QLatin1String("@.*$") ) ); + pathPattern.replace( QLatin1String("$user$"), username ); + pathPattern.replace( QLatin1String("$localpart$"), localPart ); + + QString localPath = wizard->field( QLatin1String("installationPath") ).toString(); + if ( !localPath.isEmpty() ) { + if ( !localPath.startsWith( QLatin1Char('/') ) ) + pathPattern.prepend( QLatin1Char('/') + localPath ); + else + pathPattern.prepend( localPath ); + } + + QUrl url; + + if ( !wizard->property( "usePredefinedProvider" ).isNull() ) { + if ( service->property( QLatin1String("X-DavGroupware-ProviderUsesSSL") ).toBool() ) + url.setScheme( QLatin1String("https") ); + else + url.setScheme( QLatin1String("http") ); + + QString hostPropertyName( QLatin1String("X-DavGroupware-") + protocol + QLatin1String("Host") ); + if ( service->property( hostPropertyName ).isNull() ) + return QString(); + + url.setHost( service->property( hostPropertyName ).toString() ); + url.setPath( pathPattern ); + } else { + if ( wizard->field( QLatin1String("connectionUseSecureConnection") ).toBool() ) + url.setScheme( QLatin1String("https") ); + else + url.setScheme( QLatin1String("http") ); + + QString host = wizard->field( QLatin1String("connectionHost") ).toString(); + if ( host.isEmpty() ) + return QString(); + QStringList hostParts = host.split( QLatin1Char(':') ); + url.setHost( hostParts.at( 0 ) ); + url.setPath( pathPattern ); + + if ( hostParts.size() == 2 ) { + int port = hostParts.at( 1 ).toInt(); + if ( port ) + url.setPort( port ); + } + } + + return url.toString(); +} + +/* + * SetupWizard + */ + +SetupWizard::SetupWizard( QWidget *parent ) + : QWizard( parent ) +{ + setWindowTitle( i18n( "DAV groupware configuration wizard" ) ); + setWindowIcon( KIcon( QLatin1String("folder-remote") ) ); + setPage( W_CredentialsPage, new CredentialsPage ); + setPage( W_PredefinedProviderPage, new PredefinedProviderPage ); + setPage( W_ServerTypePage, new ServerTypePage ); + setPage( W_ConnectionPage, new ConnectionPage ); + setPage( W_CheckPage, new CheckPage ); +} + +QString SetupWizard::displayName() const +{ + QString desktopFilePath = property( "providerDesktopFilePath" ).toString(); + if ( desktopFilePath.isEmpty() ) + return QString(); + + KService::Ptr service = KService::serviceByStorageId( desktopFilePath ); + if ( !service ) + return QString(); + + return service->name(); +} + +SetupWizard::Url::List SetupWizard::urls() const +{ + Url::List urls; + + QString desktopFilePath = property( "providerDesktopFilePath" ).toString(); + if ( desktopFilePath.isEmpty() ) + return urls; + + KService::Ptr service = KService::serviceByStorageId( desktopFilePath ); + if ( !service ) + return urls; + + QStringList supportedProtocols = service->property( QLatin1String("X-DavGroupware-SupportedProtocols") ).toStringList(); + foreach ( const QString &protocol, supportedProtocols ) { + Url url; + + if ( protocol == QLatin1String("CalDav") ) + url.protocol = DavUtils::CalDav; + else if ( protocol == QLatin1String("CardDav") ) + url.protocol = DavUtils::CardDav; + else if ( protocol == QLatin1String("GroupDav") ) + url.protocol = DavUtils::GroupDav; + else + return urls; + + QString urlStr = settingsToUrl( this, protocol ); + + if ( !urlStr.isEmpty() ) { + url.url = urlStr; + url.userName = QLatin1String( "$default$" ); + urls << url; + } + } + + return urls; +} + +/* + * CredentialsPage + */ + +CredentialsPage::CredentialsPage( QWidget *parent ) + : QWizardPage( parent ) +{ + setTitle( i18n( "Login Credentials" ) ); + setSubTitle( i18n( "Enter your credentials to login to the groupware server" ) ); + + QFormLayout *layout = new QFormLayout( this ); + + mUserName = new KLineEdit; + layout->addRow( i18n( "User" ), mUserName ); + registerField( QLatin1String("credentialsUserName*"), mUserName ); + + mPassword = new KLineEdit; + mPassword->setPasswordMode( true ); + layout->addRow( i18n( "Password" ), mPassword ); + registerField( QLatin1String("credentialsPassword*"), mPassword ); +} + +int CredentialsPage::nextId() const +{ + QString userName = field( QLatin1String("credentialsUserName") ).toString(); + if ( userName.endsWith( QLatin1String( "@yahoo.com" ) ) ) { + KService::List offers; + offers = KServiceTypeTrader::self()->query( QLatin1String("DavGroupwareProvider"), QLatin1String("Name == 'Yahoo!'") ); + if ( offers.isEmpty() ) + return SetupWizard::W_ServerTypePage; + + wizard()->setProperty( "usePredefinedProvider", true ); + wizard()->setProperty( "predefinedProviderName", offers.at( 0 )->name() ); + wizard()->setProperty( "providerDesktopFilePath", offers.at( 0 )->entryPath() ); + return SetupWizard::W_PredefinedProviderPage; + } else { + return SetupWizard::W_ServerTypePage; + } +} + +/* + * PredefinedProviderPage + */ + +PredefinedProviderPage::PredefinedProviderPage( QWidget* parent ) + : QWizardPage( parent ) +{ + setTitle( i18n( "Predefined provider found" ) ); + setSubTitle( i18n( "Select if you want to use the auto-detected provider" ) ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + + mLabel = new QLabel; + layout->addWidget( mLabel ); + + mProviderGroup = new QButtonGroup( this ); + mProviderGroup->setExclusive( true ); + + mUseProvider = new QRadioButton; + mProviderGroup->addButton( mUseProvider ); + mUseProvider->setChecked( true ); + layout->addWidget( mUseProvider ); + + mDontUseProvider = new QRadioButton( i18n( "No, choose another server" ) ); + mProviderGroup->addButton( mDontUseProvider ); + layout->addWidget( mDontUseProvider ); +} + +void PredefinedProviderPage::initializePage() +{ + mLabel->setText( i18n( "Based on the email address you used as a login, this wizard\n" + "can configure automatically an account for %1 services.\n" + "Do you wish to do so?", wizard()->property( "predefinedProviderName" ).toString() ) ); + + mUseProvider->setText( i18n( "Yes, use %1 as provider", wizard()->property( "predefinedProviderName" ).toString() ) ); +} + +int PredefinedProviderPage::nextId() const +{ + if ( mUseProvider->isChecked() ) { + return SetupWizard::W_CheckPage; + } + else { + wizard()->setProperty( "usePredefinedProvider", QVariant() ); + wizard()->setProperty( "providerDesktopFilePath", QVariant() ); + return SetupWizard::W_ServerTypePage; + } +} + +/* + * ServerTypePage + */ + +bool compareServiceOffers( QPair off1, QPair off2 ) +{ + return off1.first.toLower() < off2.first.toLower(); +} + +ServerTypePage::ServerTypePage( QWidget *parent ) + : QWizardPage( parent ) +{ + setTitle( i18n( "Groupware Server" ) ); + setSubTitle( i18n( "Select the groupware server the resource shall be configured for" ) ); + + mProvidersCombo = new QComboBox( this ); + KService::List providers; + KServiceTypeTrader *trader = KServiceTypeTrader::self(); + providers = trader->query( QLatin1String("DavGroupwareProvider") ); + QList< QPair > offers; + foreach ( const KService::Ptr &provider, providers ) { + offers.append( QPair( provider->name(), provider->entryPath() ) ); + } + qSort( offers.begin(), offers.end(), compareServiceOffers ); + QListIterator< QPair > it( offers ); + while ( it.hasNext() ) { + QPair p = it.next(); + mProvidersCombo->addItem( p.first, p.second ); + } + registerField( QLatin1String("provider"), mProvidersCombo, "currentText" ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + + mServerGroup = new QButtonGroup( this ); + mServerGroup->setExclusive( true ); + + QRadioButton *button; + + QHBoxLayout *hLayout = new QHBoxLayout; + button = new QRadioButton( i18n( "Use one of those servers:" ) ); + registerField( QLatin1String("templateConfiguration"), button ); + mServerGroup->addButton( button ); + mServerGroup->setId( button, 0 ); + button->setChecked( true ); + hLayout->addWidget( button ); + hLayout->addWidget( mProvidersCombo ); + hLayout->addStretch( 1 ); + layout->addLayout( hLayout ); + + button = new QRadioButton( i18n( "Configure the resource manually" ) ); + connect( button, SIGNAL(toggled(bool)), + this, SLOT(manualConfigToggled(bool)) ); + registerField( QLatin1String("manualConfiguration"), button ); + mServerGroup->addButton( button ); + mServerGroup->setId( button, 1 ); + layout->addWidget( button ); + + layout->addStretch( 1 ); +} + +void ServerTypePage::manualConfigToggled( bool state ) +{ + setFinalPage( state ); + wizard()->button( QWizard::NextButton )->setEnabled( !state ); +} + +bool ServerTypePage::validatePage() +{ + QVariant desktopFilePath = mProvidersCombo->itemData( mProvidersCombo->currentIndex() ); + if ( desktopFilePath.isNull() ) { + return false; + } + else { + wizard()->setProperty( "providerDesktopFilePath", desktopFilePath ); + return true; + } +} + +/* + * ConnectionPage + */ + +ConnectionPage::ConnectionPage( QWidget *parent ) + : QWizardPage( parent ), mPreviewLayout( 0 ), mCalDavUrlPreview( 0 ), mCardDavUrlPreview( 0 ), mGroupDavUrlPreview( 0 ) +{ + setTitle( i18n( "Connection" ) ); + setSubTitle( i18n( "Enter the connection information for the groupware server" ) ); + + mLayout = new QFormLayout; + setLayout(mLayout); + QRegExp hostnameRegexp( QLatin1String("^[a-z0-9][.a-z0-9-]*[a-z0-9](?::[0-9]+)?$") ); + mHost = new KLineEdit; + registerField( QLatin1String("connectionHost*"), mHost ); + mHost->setValidator( new QRegExpValidator( hostnameRegexp, this ) ); + mLayout->addRow( i18n( "Host" ), mHost ); + + mPath = new KLineEdit; + mLayout->addRow( i18n( "Installation path" ), mPath ); + registerField( QLatin1String("installationPath"), mPath ); + + mUseSecureConnection = new QCheckBox( i18n( "Use secure connection" ) ); + mUseSecureConnection->setChecked( true ); + registerField( QLatin1String("connectionUseSecureConnection"), mUseSecureConnection ); + mLayout->addRow( QString(), mUseSecureConnection ); + + connect( mHost, SIGNAL(textChanged(QString)), this, SLOT(urlElementChanged()) ); + connect( mPath, SIGNAL(textChanged(QString)), this, SLOT(urlElementChanged()) ); + connect( mUseSecureConnection, SIGNAL(toggled(bool)), this, SLOT(urlElementChanged()) ); +} + +void ConnectionPage::initializePage() +{ + KService::Ptr service = KService::serviceByStorageId( wizard()->property( "providerDesktopFilePath" ).toString() ); + if ( !service ) + return; + + QString providerInstallationPath = service->property( QLatin1String("X-DavGroupware-InstallationPath") ).toString(); + if ( !providerInstallationPath.isEmpty() ) + mPath->setText( providerInstallationPath ); + + QStringList supportedProtocols = service->property( QLatin1String("X-DavGroupware-SupportedProtocols") ).toStringList(); + + mPreviewLayout = new QFormLayout; + mLayout->addRow( mPreviewLayout ); + + if ( supportedProtocols.contains( QLatin1String("CalDav") ) ) { + mCalDavUrlLabel = new QLabel( i18n( "Final URL (CalDav)" ) ); + mCalDavUrlPreview = new QLabel; + mPreviewLayout->addRow( mCalDavUrlLabel, mCalDavUrlPreview ); + } + if ( supportedProtocols.contains( QLatin1String("CardDav") ) ) { + mCardDavUrlLabel = new QLabel( i18n( "Final URL (CardDav)" ) ); + mCardDavUrlPreview = new QLabel; + mPreviewLayout->addRow( mCardDavUrlLabel, mCardDavUrlPreview ); + } + if ( supportedProtocols.contains( QLatin1String("GroupDav") ) ) { + mGroupDavUrlLabel = new QLabel( i18n( "Final URL (GroupDav)" ) ); + mGroupDavUrlPreview = new QLabel; + mPreviewLayout->addRow( mGroupDavUrlLabel, mGroupDavUrlPreview ); + } +} + +void ConnectionPage::cleanupPage() +{ + delete mPreviewLayout; + + if ( mCalDavUrlPreview ) { + delete mCalDavUrlLabel; + delete mCalDavUrlPreview; + mCalDavUrlPreview = 0; + } + + if ( mCardDavUrlPreview ) { + delete mCardDavUrlLabel; + delete mCardDavUrlPreview; + mCardDavUrlPreview = 0; + } + + if ( mGroupDavUrlPreview ) { + delete mGroupDavUrlLabel; + delete mGroupDavUrlPreview; + mGroupDavUrlPreview = 0; + } + + QWizardPage::cleanupPage(); +} + +void ConnectionPage::urlElementChanged() +{ + if ( mHost->text().isEmpty() ) { + if ( mCalDavUrlPreview ) + mCalDavUrlPreview->setText( QLatin1String("-") ); + if ( mCardDavUrlPreview ) + mCardDavUrlPreview->setText( QLatin1String("-") ); + if ( mGroupDavUrlPreview ) + mGroupDavUrlPreview->setText( QLatin1String("-") ); + } else { + if ( mCalDavUrlPreview ) + mCalDavUrlPreview->setText( settingsToUrl( this->wizard(), QLatin1String("CalDav") ) ); + if ( mCardDavUrlPreview ) + mCardDavUrlPreview->setText( settingsToUrl( this->wizard(), QLatin1String("CardDav") ) ); + if ( mGroupDavUrlPreview ) + mGroupDavUrlPreview->setText( settingsToUrl( this->wizard(), QLatin1String("GroupDav") ) ); + } +} + +/* + * CheckPage + */ + +CheckPage::CheckPage( QWidget *parent ) + : QWizardPage( parent ) +{ + setTitle( i18n( "Test Connection" ) ); + setSubTitle( i18n( "You can test now whether the groupware server can be accessed with the current configuration" ) ); + setFinalPage( true ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + + QPushButton *button = new QPushButton( i18n( "Test Connection" ) ); + layout->addWidget( button ); + + mStatusLabel = new KTextBrowser; + layout->addWidget( mStatusLabel ); + + connect( button, SIGNAL(clicked()), SLOT(checkConnection()) ); +} + +void CheckPage::checkConnection() +{ + mStatusLabel->clear(); + + DavUtils::DavUrl::List davUrls; + + // convert list of SetupWizard::Url to list of DavUtils::DavUrl + const SetupWizard::Url::List urls = static_cast( wizard() )->urls(); + foreach ( const SetupWizard::Url &url, urls ) { + DavUtils::DavUrl davUrl; + davUrl.setProtocol( url.protocol ); + + KUrl serverUrl( url.url ); + serverUrl.setUser( wizard()->field( QLatin1String("credentialsUserName") ).toString() ); + serverUrl.setPass( wizard()->field( QLatin1String("credentialsPassword") ).toString() ); + davUrl.setUrl( serverUrl ); + + davUrls << davUrl; + } + + // start the dav collections fetch job to test connectivity + DavCollectionsMultiFetchJob *job = new DavCollectionsMultiFetchJob( davUrls, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFetchDone(KJob*)) ); + job->start(); +} + +void CheckPage::onFetchDone( KJob *job ) +{ + QString msg; + QPixmap icon; + + if ( job->error() ) { + msg = i18n( "An error occurred: %1",job->errorText()); + icon = KIcon( QLatin1String("dialog-close") ).pixmap( 16, 16 ); + } else { + msg = i18n( "Connected successfully" ); + icon = KIcon( QLatin1String("dialog-ok-apply") ).pixmap( 16, 16 ); + } + + mStatusLabel->setHtml( QString::fromLatin1( " %1" ).arg( msg ) ); + mStatusLabel->document()->addResource( QTextDocument::ImageResource, QUrl( QLatin1String("icon") ), QVariant( icon ) ); +} + diff --git a/kdepim-runtime/resources/dav/resource/setupwizard.h b/kdepim-runtime/resources/dav/resource/setupwizard.h new file mode 100644 index 00000000..7ba387a0 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/setupwizard.h @@ -0,0 +1,154 @@ +/* + Copyright (c) 2010 Tobias Koenig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef SETUPWIZARD_H +#define SETUPWIZARD_H + +#include "davutils.h" + +#include +#include +#include + +class KJob; +class KLineEdit; +class KTextBrowser; + +class QButtonGroup; +class QCheckBox; +class QComboBox; +class QFormLayout; +class QLabel; +class QRadioButton; + +class SetupWizard : public QWizard +{ + Q_OBJECT + + public: + explicit SetupWizard( QWidget *parent = 0 ); + + enum { + W_CredentialsPage, + W_PredefinedProviderPage, + W_ServerTypePage, + W_ConnectionPage, + W_CheckPage + }; + + class Url + { + public: + typedef QList List; + + DavUtils::Protocol protocol; + QString url; + QString userName; + QString password; + }; + + Url::List urls() const; + QString displayName() const; +}; + +class PredefinedProviderPage : public QWizardPage +{ + public: + PredefinedProviderPage( QWidget* parent = 0 ); + + virtual void initializePage(); + virtual int nextId() const; + + private: + QLabel *mLabel; + QButtonGroup *mProviderGroup; + QRadioButton *mUseProvider; + QRadioButton *mDontUseProvider; +}; + +class CredentialsPage : public QWizardPage +{ + public: + CredentialsPage( QWidget *parent = 0 ); + virtual int nextId() const; + + private: + KLineEdit *mUserName; + KLineEdit *mPassword; +}; + +class ServerTypePage : public QWizardPage +{ + Q_OBJECT + + public: + ServerTypePage( QWidget *parent = 0 ); + + virtual bool validatePage(); + + private slots: + void manualConfigToggled( bool toggled ); + + private: + QButtonGroup *mServerGroup; + QComboBox *mProvidersCombo; +}; + +class ConnectionPage : public QWizardPage +{ + Q_OBJECT + + public: + ConnectionPage( QWidget *parent = 0 ); + + virtual void initializePage(); + virtual void cleanupPage(); + + private slots: + void urlElementChanged(); + + private: + QFormLayout *mLayout; + KLineEdit *mHost; + KLineEdit *mPath; + QCheckBox *mUseSecureConnection; + QFormLayout *mPreviewLayout; + QLabel *mCalDavUrlLabel; + QLabel *mCalDavUrlPreview; + QLabel *mCardDavUrlLabel; + QLabel *mCardDavUrlPreview; + QLabel *mGroupDavUrlLabel; + QLabel *mGroupDavUrlPreview; +}; + +class CheckPage : public QWizardPage +{ + Q_OBJECT + + public: + CheckPage( QWidget *parent = 0 ); + + private Q_SLOTS: + void checkConnection(); + void onFetchDone( KJob* ); + + private: + KTextBrowser *mStatusLabel; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.cpp b/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.cpp new file mode 100644 index 00000000..fe85468e --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.cpp @@ -0,0 +1,256 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "urlconfigurationdialog.h" + +#include "davcollectionmodifyjob.h" +#include "davcollectionsfetchjob.h" +#include "davutils.h" +#include "settings.h" + +#include + +#include +#include + +UrlConfigurationDialog::UrlConfigurationDialog( QWidget *parent ) + : KDialog( parent ) +{ + mUi.setupUi( mainWidget() ); + mUi.credentialsGroup->setVisible( false ); + + mModel = new QStandardItemModel(); + initModel(); + + mUi.discoveredUrls->setModel( mModel ); + mUi.discoveredUrls->setRootIsDecorated( false ); + connect( mModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(onModelDataChanged(QModelIndex,QModelIndex)) ); + + connect( mUi.remoteProtocol, SIGNAL(changed(int)), this, SLOT(onConfigChanged()) ); + connect( mUi.remoteUrl, SIGNAL(textChanged(QString)), this, SLOT(onConfigChanged()) ); + connect( mUi.useDefaultCreds, SIGNAL(toggled(bool)), this, SLOT(onConfigChanged()) ); + connect( mUi.username, SIGNAL(textChanged(QString)), this, SLOT(onConfigChanged()) ); + connect( mUi.password, SIGNAL(textChanged(QString)), this, SLOT(onConfigChanged()) ); + + connect( mUi.fetchButton, SIGNAL(clicked()), this, SLOT(onFetchButtonClicked()) ); + connect( this, SIGNAL(okClicked()), this, SLOT(onOkButtonClicked()) ); + + checkUserInput(); +} + +UrlConfigurationDialog::~UrlConfigurationDialog() +{ +} + +DavUtils::Protocol UrlConfigurationDialog::protocol() const +{ + return DavUtils::Protocol( mUi.remoteProtocol->selected() ); +} + +void UrlConfigurationDialog::setProtocol( DavUtils::Protocol protocol ) +{ + mUi.remoteProtocol->setSelected( protocol ); +} + +QString UrlConfigurationDialog::remoteUrl() const +{ + return mUi.remoteUrl->text(); +} + +void UrlConfigurationDialog::setRemoteUrl( const QString &url ) +{ + mUi.remoteUrl->setText( url ); +} + +bool UrlConfigurationDialog::useDefaultCredentials() const +{ + return mUi.useDefaultCreds->isChecked(); +} + +void UrlConfigurationDialog::setUseDefaultCredentials( bool defaultCreds ) +{ + if ( defaultCreds ) + mUi.useDefaultCreds->setChecked( true ); + else + mUi.useSpecificCreds->setChecked( true ); +} + +QString UrlConfigurationDialog::username() const +{ + if ( mUi.useDefaultCreds->isChecked() ) + return mDefaultUsername; + else + return mUi.username->text(); +} + +void UrlConfigurationDialog::setDefaultUsername( const QString &userName ) +{ + mDefaultUsername = userName; +} + +void UrlConfigurationDialog::setUsername( const QString &userName ) +{ + mUi.username->setText( userName ); +} + +QString UrlConfigurationDialog::password() const +{ + if ( mUi.useDefaultCreds->isChecked() ) + return mDefaultPassword; + else + return mUi.password->text(); +} + +void UrlConfigurationDialog::setDefaultPassword( const QString &password ) +{ + mDefaultPassword = password; +} + +void UrlConfigurationDialog::setPassword(const QString& password) +{ + mUi.password->setText( password ); +} + +void UrlConfigurationDialog::onConfigChanged() +{ + initModel(); + mUi.fetchButton->setEnabled( false ); + enableButtonOk( false ); + checkUserInput(); +} + +void UrlConfigurationDialog::checkUserInput() +{ + if ( !mUi.remoteUrl->text().isEmpty() && checkUserAuthInput() ) { + mUi.fetchButton->setEnabled( true ); + if ( mModel->rowCount() > 0 ) + enableButtonOk( true ); + } else { + mUi.fetchButton->setEnabled( false ); + enableButtonOk( false ); + } +} + +void UrlConfigurationDialog::onFetchButtonClicked() +{ + mUi.discoveredUrls->setEnabled( false ); + initModel(); + + if ( !remoteUrl().endsWith( QLatin1Char( '/' ) ) ) + setRemoteUrl( remoteUrl() + QLatin1Char( '/' ) ); + + if ( !remoteUrl().startsWith( QLatin1String( "https://" ) ) && !remoteUrl().startsWith( QLatin1String( "http://" ) ) ) + setRemoteUrl( QString( "https://" ) + remoteUrl() ); + + KUrl url( mUi.remoteUrl->text() ); + if ( mUi.useDefaultCreds->isChecked() ) { + url.setUser( mDefaultUsername ); + url.setPassword( mDefaultPassword ); + } else { + url.setUser( username() ); + url.setPassword( password() ); + } + + DavUtils::DavUrl davUrl( url, protocol() ); + DavCollectionsFetchJob *job = new DavCollectionsFetchJob( davUrl ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFetchDone(KJob*)) ); + job->start(); +} + +void UrlConfigurationDialog::onOkButtonClicked() +{ + if ( !remoteUrl().endsWith( QLatin1Char( '/' ) ) ) + setRemoteUrl( remoteUrl() + QLatin1Char( '/' ) ); +} + +void UrlConfigurationDialog::onCollectionsFetchDone( KJob *job ) +{ + mUi.discoveredUrls->setEnabled( true ); + + if ( job->error() ) { + KMessageBox::error( this, job->errorText() ); + return; + } + + DavCollectionsFetchJob *davJob = qobject_cast( job ); + + const DavCollection::List collections = davJob->collections(); + + foreach ( const DavCollection &collection, collections ) + addModelRow( collection.displayName(), collection.url() ); + + checkUserInput(); +} + +void UrlConfigurationDialog::onModelDataChanged( const QModelIndex &topLeft, const QModelIndex& ) +{ + // Actually only the display name can be changed, so no stricts checks are required + const QString newName = topLeft.data().toString(); + const QString url = topLeft.sibling( topLeft.row(), 1 ).data().toString(); + + KUrl fullUrl( url ); + fullUrl.setUser( username() ); + fullUrl.setPassword( password() ); + + DavUtils::DavUrl davUrl( fullUrl, protocol() ); + DavCollectionModifyJob *job = new DavCollectionModifyJob( davUrl ); + job->setProperty( QLatin1String("displayname"), newName ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onChangeDisplayNameFinished(KJob*)) ); + job->start(); + mUi.discoveredUrls->setEnabled( false ); +} + +void UrlConfigurationDialog::onChangeDisplayNameFinished( KJob *job ) +{ + if ( job->error() ) { + KMessageBox::error( this, job->errorText() ); + } + + onFetchButtonClicked(); +} + +void UrlConfigurationDialog::initModel() +{ + mModel->clear(); + QStringList headers; + headers << i18n( "Display name" ) << i18n( "URL" ); + mModel->setHorizontalHeaderLabels( headers ); +} + +bool UrlConfigurationDialog::checkUserAuthInput() +{ + return ( mUi.useDefaultCreds->isChecked() || !( mUi.username->text().isEmpty() || mUi.password->text().isEmpty() ) ); +} + +void UrlConfigurationDialog::addModelRow( const QString &displayName, const QString &url ) +{ + QStandardItem *rootItem = mModel->invisibleRootItem(); + + QList items; + + QStandardItem *displayNameStandardItem = new QStandardItem( displayName ); + displayNameStandardItem->setEditable( true ); + items << displayNameStandardItem; + + QStandardItem *urlStandardItem = new QStandardItem( url ); + urlStandardItem->setEditable( false ); + items << urlStandardItem; + + rootItem->appendRow( items ); +} diff --git a/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.h b/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.h new file mode 100644 index 00000000..1b9a2cd2 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2010 Grégory Oestreicher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef URLCONFIGURATIONDIALOG_H +#define URLCONFIGURATIONDIALOG_H + +#include "ui_urlconfigurationdialog.h" + +#include "davutils.h" + +#include + +class KJob; +class QModelIndex; +class QStandardItemModel; + +class UrlConfigurationDialog : public KDialog +{ + Q_OBJECT + + public: + explicit UrlConfigurationDialog( QWidget *parent = 0 ); + ~UrlConfigurationDialog(); + + DavUtils::Protocol protocol() const; + void setProtocol( DavUtils::Protocol protocol ); + + QString remoteUrl() const; + void setRemoteUrl( const QString &url ); + + bool useDefaultCredentials() const; + void setUseDefaultCredentials( bool defaultCreds ); + + QString username() const; + void setDefaultUsername( const QString &name ); + void setUsername( const QString &name ); + + QString password() const; + void setDefaultPassword( const QString &password ); + void setPassword( const QString &password ); + + private Q_SLOTS: + void onConfigChanged(); + void checkUserInput(); + void onFetchButtonClicked(); + void onOkButtonClicked(); + void onCollectionsFetchDone( KJob *job ); + void onModelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ); + void onChangeDisplayNameFinished( KJob *job ); + + private: + void initModel(); + bool checkUserAuthInput(); + void addModelRow( const QString &displayName, const QString &url ); + + Ui::UrlConfigurationDialog mUi; + QStandardItemModel *mModel; + QString mDefaultUsername; + QString mDefaultPassword; +}; + +#endif diff --git a/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.ui b/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.ui new file mode 100644 index 00000000..3d68d3a4 --- /dev/null +++ b/kdepim-runtime/resources/dav/resource/urlconfigurationdialog.ui @@ -0,0 +1,237 @@ + + + UrlConfigurationDialog + + + + 0 + 0 + 559 + 698 + + + + + 0 + 0 + + + + + + + + + + + + + Remote calendar access protocol + + + 0 + + + + + + CalDAV + + + true + + + + + + + CardDAV + + + + + + + GroupDAV + + + + + + + + + + + + + Remote calendar access + + + + + + + + + Use global credentials + + + true + + + + + + + Use specific credentials + + + + + + + Credentials + + + + + + + + + + + + Username + + + + + + + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + + + + Remote URL + + + + + + + + + + + + + Discovered collections + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Fetch + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + KButtonGroup + QGroupBox +
kbuttongroup.h
+ 1 +
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + + + useSpecificCreds + toggled(bool) + credentialsGroup + setVisible(bool) + + + 22 + 206 + + + 59 + 241 + + + + +
diff --git a/kdepim-runtime/resources/dav/services/citadel.desktop b/kdepim-runtime/resources/dav/services/citadel.desktop new file mode 100644 index 00000000..5e253cc9 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/citadel.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Name=Citadel +Name[bg]=Citadel +Name[bs]=Citadel +Name[ca]=Citadel +Name[ca@valencia]=Citadel +Name[cs]=Citadel +Name[da]=Citadel +Name[de]=Citadel +Name[el]=Citadel +Name[en_GB]=Citadel +Name[es]=Citadel +Name[et]=Citadel +Name[fi]=Citadel +Name[fr]=Citadel +Name[ga]=Citadel +Name[gl]=Citadel +Name[hu]=Citadel +Name[ia]=Citadel +Name[it]=Citadel +Name[kk]=Citadel +Name[km]=Citadel +Name[ko]=Citadel +Name[lt]=Citadel +Name[lv]=Citadel +Name[nb]=Citadel +Name[nds]=Citadel +Name[nl]=Citadel +Name[pl]=Citadel +Name[pt]=Citadel +Name[pt_BR]=Citadel +Name[ro]=Citadel +Name[ru]=Citadel +Name[sk]=Citadel +Name[sl]=Citadel +Name[sr]=Цитадела +Name[sr@ijekavian]=Цитадела +Name[sr@ijekavianlatin]=Citadela +Name[sr@latin]=Citadela +Name[sv]=Citadel +Name[tr]=Citadel +Name[uk]=Citadel +Name[x-test]=xxCitadelxx +Name[zh_CN]=Citadel +Name[zh_TW]=Citadel +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=GroupDav +X-DavGroupware-GroupDavPath=/groupdav diff --git a/kdepim-runtime/resources/dav/services/davical.desktop b/kdepim-runtime/resources/dav/services/davical.desktop new file mode 100644 index 00000000..892ea4c0 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/davical.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Name=Davical +Name[bg]=Davical +Name[bs]=Davical +Name[ca]=Davical +Name[ca@valencia]=Davical +Name[cs]=Davical +Name[da]=Davical +Name[de]=Davical +Name[el]=Davical +Name[en_GB]=Davical +Name[es]=Davical +Name[et]=Davical +Name[fi]=Davical +Name[fr]=Davical +Name[ga]=Davical +Name[gl]=Davical +Name[hu]=Davical +Name[ia]=Davical +Name[it]=Davical +Name[kk]=Davical +Name[km]=Davical +Name[ko]=Davical +Name[lt]=Davical +Name[lv]=Davical +Name[nb]=Davical +Name[nds]=DAVical +Name[nl]=Davical +Name[pl]=Davical +Name[pt]=Davical +Name[pt_BR]=Davical +Name[ro]=Davical +Name[ru]=DAViCal +Name[sk]=Davical +Name[sl]=Davical +Name[sr]=ДÐВикал +Name[sr@ijekavian]=ДÐВикал +Name[sr@ijekavianlatin]=DAViCal +Name[sr@latin]=DAViCal +Name[sv]=Davical +Name[tr]=Davical +Name[uk]=Davical +Name[x-test]=xxDavicalxx +Name[zh_CN]=Davical +Name[zh_TW]=Davical +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav +X-DavGroupware-CalDavPath=/caldav.php diff --git a/kdepim-runtime/resources/dav/services/egroupware.desktop b/kdepim-runtime/resources/dav/services/egroupware.desktop new file mode 100644 index 00000000..ddde6f97 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/egroupware.desktop @@ -0,0 +1,52 @@ +[Desktop Entry] +Name=eGroupware +Name[bg]=eGroupware +Name[bs]=eGroupware +Name[ca]=eGroupware +Name[ca@valencia]=eGroupware +Name[cs]=eGroupware +Name[da]=eGroupware +Name[de]=eGroupware +Name[el]=eGroupware +Name[en_GB]=eGroupware +Name[es]=eGroupware +Name[et]=eGroupware +Name[fi]=eGroupware +Name[fr]=eGroupware +Name[ga]=eGroupware +Name[gl]=eGroupware +Name[hu]=eGroupware +Name[ia]=eGroupware +Name[it]=eGroupware +Name[kk]=eGroupware +Name[km]=eGroupware +Name[ko]=eGroupware +Name[lt]=eGroupware +Name[lv]=eGroupware +Name[mr]=इ-गà¥à¤°à¥à¤ªà¤µà¥‡à¤…र +Name[nb]=eGroupware +Name[nds]=eGroupware +Name[nl]=eGroupware +Name[pl]=eGroupware +Name[pt]=eGroupware +Name[pt_BR]=eGroupware +Name[ro]=eGroupware +Name[ru]=eGroupware +Name[sk]=eGroupware +Name[sl]=eGroupware +Name[sr]=Е‑групвер +Name[sr@ijekavian]=Е‑групвер +Name[sr@ijekavianlatin]=EGroupware +Name[sr@latin]=EGroupware +Name[sv]=eGroupware +Name[tr]=eGroupware +Name[uk]=eGroupware +Name[x-test]=xxeGroupwarexx +Name[zh_CN]=eGroupware +Name[zh_TW]=eGroupware +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav,CardDav +X-DavGroupware-CalDavPath=/groupdav.php +X-DavGroupware-CardDavPath=/groupdav.php diff --git a/kdepim-runtime/resources/dav/services/opengroupware.desktop b/kdepim-runtime/resources/dav/services/opengroupware.desktop new file mode 100644 index 00000000..9c570487 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/opengroupware.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Name=OpenGroupware +Name[bg]=OpenGroupware +Name[bs]=OpenGroupware +Name[ca]=OpenGroupware +Name[ca@valencia]=OpenGroupware +Name[cs]=OpenGroupware +Name[da]=OpenGroupware +Name[de]=OpenGroupware +Name[el]=OpenGroupware +Name[en_GB]=OpenGroupware +Name[es]=OpenGroupware +Name[et]=OpenGroupware +Name[fi]=OpenGroupware +Name[fr]=OpenGroupware +Name[ga]=OpenGroupware +Name[gl]=OpenGroupware +Name[hu]=OpenGroupware +Name[ia]=OpenGroupware +Name[it]=OpenGroupware +Name[kk]=OpenGroupware +Name[km]=OpenGroupware +Name[ko]=OpenGroupware +Name[lt]=OpenGroupware +Name[lv]=OpenGroupware +Name[mr]=ओपन-गà¥à¤°à¥à¤ªà¤µà¥‡à¤…र +Name[nb]=OpenGroupware +Name[nds]=OpenGroupware +Name[nl]=OpenGroupware +Name[pl]=OpenGroupware +Name[pt]=OpenGroupware +Name[pt_BR]=OpenGroupware +Name[ro]=OpenGroupware +Name[ru]=OpenGroupware +Name[sk]=OpenGroupware +Name[sl]=OpenGroupware +Name[sr]=Опенгрупвер +Name[sr@ijekavian]=Опенгрупвер +Name[sr@ijekavianlatin]=OpenGroupware +Name[sr@latin]=OpenGroupware +Name[sv]=OpenGroupware +Name[tr]=OpenGroupware +Name[uk]=OpenGroupware +Name[x-test]=xxOpenGroupwarexx +Name[zh_CN]=OpenGroupware +Name[zh_TW]=OpenGroupware +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=GroupDav +X-DavGroupware-GroupDavPath=/zidestore/dav/$user diff --git a/kdepim-runtime/resources/dav/services/owncloud-pre5.desktop b/kdepim-runtime/resources/dav/services/owncloud-pre5.desktop new file mode 100644 index 00000000..d64af271 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/owncloud-pre5.desktop @@ -0,0 +1,45 @@ +[Desktop Entry] +Name=ownCloud (< 5.0) +Name[bs]=ownCloud (< 5.0) +Name[ca]=ownCloud (< 5.0) +Name[ca@valencia]=ownCloud (< 5.0) +Name[cs]=ownCloud (< 5.0) +Name[da]=ownCloud (< 5.0) +Name[de]=ownCloud (< 5.0) +Name[el]=ownCloud (<5.0) +Name[en_GB]=ownCloud (< 5.0) +Name[es]=ownCloud (< 5.0) +Name[et]=ownCloud (< 5.0) +Name[fi]=ownCloud (< 5.0) +Name[fr]=ownCloud (< 5.0) +Name[gl]=ownCloud (< 5.0) +Name[hu]=ownCloud (< 5.0) +Name[ia]=ownCloud (<5.0) +Name[it]=ownCloud (< 5.0) +Name[kk]=ownCloud (< 5.0) +Name[ko]=ownCloud (< 5.0) +Name[lt]=ownCloud (< 5.0) +Name[nb]=ownCloud (< 5.0) +Name[nds]=ownCloud (< 5.0) +Name[nl]=ownCloud (< 5.0) +Name[pl]=ownCloud (< 5.0) +Name[pt]=ownCloud (< 5.0) +Name[pt_BR]=ownCloud (< 5.0) +Name[ru]=ownCloud (< 5.0) +Name[sk]=ownCloud (< 5.0) +Name[sr]=Оунклауд (< 5.0) +Name[sr@ijekavian]=Оунклауд (< 5.0) +Name[sr@ijekavianlatin]=ownCloud (< 5.0) +Name[sr@latin]=ownCloud (< 5.0) +Name[sv]=ownCloud (< 5.0) +Name[tr]=ownCloud (< 5.0) +Name[uk]=ownCloud (< 5.0) +Name[x-test]=xxownCloud (< 5.0)xx +Name[zh_CN]=ownCloud (< 5.0) +Name[zh_TW]=ownCloud (< 5.0) +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav,CardDav +X-DavGroupware-CalDavPath=/apps/calendar/caldav.php +X-DavGroupware-CardDavPath=/apps/contacts/carddav.php diff --git a/kdepim-runtime/resources/dav/services/owncloud.desktop b/kdepim-runtime/resources/dav/services/owncloud.desktop new file mode 100644 index 00000000..bd7948ec --- /dev/null +++ b/kdepim-runtime/resources/dav/services/owncloud.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Name=ownCloud +Name[bs]=ownCloud +Name[ca]=ownCloud +Name[ca@valencia]=ownCloud +Name[cs]=ownCloud +Name[da]=ownCloud +Name[de]=ownCloud +Name[el]=ownCloud +Name[en_GB]=ownCloud +Name[es]=ownCloud +Name[et]=ownCloud +Name[fi]=ownCloud +Name[fr]=ownCloud +Name[gl]=ownCloud +Name[hu]=ownCloud +Name[ia]=proprie Nube (ownCloud) +Name[it]=ownCloud +Name[kk]=ownCloud +Name[km]=ownCloud +Name[ko]=ownCloud +Name[lt]=ownCloud +Name[lv]=ownCloud +Name[mr]=ओन-कà¥à¤²à¤¾à¤‰à¤¡ +Name[nb]=ownCloud +Name[nds]=ownCloud +Name[nl]=ownCloud +Name[pl]=ownCloud +Name[pt]=ownCloud +Name[pt_BR]=ownCloud +Name[ru]=ownCloud +Name[sk]=ownCloud +Name[sl]=ownCloud +Name[sr]=Оунклауд +Name[sr@ijekavian]=Оунклауд +Name[sr@ijekavianlatin]=ownCloud +Name[sr@latin]=ownCloud +Name[sv]=ownCloud +Name[tr]=ownCloud +Name[ug]=ownCloud +Name[uk]=ownCloud +Name[x-test]=xxownCloudxx +Name[zh_CN]=ownCloud +Name[zh_TW]=ownCloud +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav,CardDav +X-DavGroupware-CalDavPath=/remote.php/caldav +X-DavGroupware-CardDavPath=/remote.php/carddav diff --git a/kdepim-runtime/resources/dav/services/scalix.desktop b/kdepim-runtime/resources/dav/services/scalix.desktop new file mode 100644 index 00000000..ba613979 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/scalix.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Name=Scalix +Name[bg]=Scalix +Name[bs]=Scalix +Name[ca]=Scalix +Name[ca@valencia]=Scalix +Name[cs]=Scalix +Name[da]=Scalix +Name[de]=Scalix +Name[el]=Scalix +Name[en_GB]=Scalix +Name[es]=Scalix +Name[et]=Scalix +Name[fi]=Scalix +Name[fr]=Scalix +Name[ga]=Scalix +Name[gl]=Scalix +Name[hu]=Scalix +Name[ia]=Scalix +Name[it]=Scalix +Name[kk]=Scalix +Name[km]=Scalix +Name[ko]=Scalix +Name[lt]=Scalix +Name[lv]=Scalix +Name[mr]=सà¥à¤•à¥‡à¤²à¤¿à¤•à¥à¤¸ +Name[nb]=Scalix +Name[nds]=Scalix +Name[nl]=Scalix +Name[pl]=Scalix +Name[pt]=Scalix +Name[pt_BR]=Scalix +Name[ro]=Scalix +Name[ru]=Scalix +Name[sk]=Scalix +Name[sl]=Scalix +Name[sr]=СкejÐ»Ð¸ÐºÑ +Name[sr@ijekavian]=СкejÐ»Ð¸ÐºÑ +Name[sr@ijekavianlatin]=Scalix +Name[sr@latin]=Scalix +Name[sv]=Scalix +Name[tr]=Scalix +Name[uk]=Scalix +Name[x-test]=xxScalixxx +Name[zh_CN]=Scalix +Name[zh_TW]=Scalix +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav +X-DavGroupware-CalDavPath=/api/dav/Principals/$user$ diff --git a/kdepim-runtime/resources/dav/services/sogo.desktop b/kdepim-runtime/resources/dav/services/sogo.desktop new file mode 100644 index 00000000..63177377 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/sogo.desktop @@ -0,0 +1,52 @@ +[Desktop Entry] +Name=ScalableOGo +Name[bg]=ScalableOGo +Name[bs]=ScalableOGo +Name[ca]=ScalableOGo +Name[ca@valencia]=ScalableOGo +Name[cs]=ScalableOGo +Name[da]=ScalableOGo +Name[de]=ScalableOGo +Name[el]=ScalableOGo +Name[en_GB]=ScalableOGo +Name[es]=ScalableOGo +Name[et]=ScalableOGo +Name[fi]=ScalableOGo +Name[fr]=ScalableOGo +Name[ga]=ScalableOGo +Name[gl]=ScalableOGo +Name[hu]=ScalableOGo +Name[ia]=ScalableOGo +Name[it]=ScalableOGo +Name[kk]=ScalableOGo +Name[km]=ScalableOGo +Name[ko]=ScalableOGo +Name[lt]=ScalableOGo +Name[lv]=ScalableOGo +Name[nb]=ScalableOGo +Name[nds]=ScalableOGo +Name[nl]=ScalableOGo +Name[pl]=ScalableOGo +Name[pt]=ScalableOGo +Name[pt_BR]=ScalableOGo +Name[ro]=ScalableOGo +Name[ru]=ScalableOGo +Name[sk]=ScalableOGo +Name[sl]=ScalableOGo +Name[sr]=Скејлабл‑ого +Name[sr@ijekavian]=Скејлабл‑ого +Name[sr@ijekavianlatin]=ScalableOGo +Name[sr@latin]=ScalableOGo +Name[sv]=ScalableOGo +Name[tr]=ScalableOGo +Name[uk]=ScalableOGo +Name[x-test]=xxScalableOGoxx +Name[zh_CN]=ScalableOGo +Name[zh_TW]=ScalableOGo +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav,CardDav +X-DavGroupware-InstallationPath=/SOGo +X-DavGroupware-CalDavPath=/dav/$user$/Calendar +X-DavGroupware-CardDavPath=/dav/$user$/Contacts diff --git a/kdepim-runtime/resources/dav/services/yahoo.desktop b/kdepim-runtime/resources/dav/services/yahoo.desktop new file mode 100644 index 00000000..f81564b0 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/yahoo.desktop @@ -0,0 +1,56 @@ +[Desktop Entry] +Name=Yahoo! +Name[bg]=Yahoo! +Name[bs]=Yahoo! +Name[ca]=Yahoo! +Name[ca@valencia]=Yahoo! +Name[cs]=Yahoo! +Name[da]=Yahoo! +Name[de]=Yahoo! +Name[el]=Yahoo! +Name[en_GB]=Yahoo! +Name[es]=Yahoo! +Name[et]=Yahoo! +Name[fi]=Yahoo! +Name[fr]=Yahoo! +Name[ga]=Yahoo! +Name[gl]=Yahoo! +Name[hu]=Yahoo! +Name[ia]=Yahoo! +Name[it]=Yahoo! +Name[kk]=Yahoo! +Name[km]=Yahoo! +Name[ko]=Yahoo! +Name[lt]=Yahoo! +Name[lv]=Yahoo! +Name[mr]=याहू! +Name[nb]=Yahoo! +Name[nds]=Yahoo! +Name[nl]=Yahoo! +Name[pl]=Yahoo! +Name[pt]=Yahoo! +Name[pt_BR]=Yahoo! +Name[ro]=Yahoo! +Name[ru]=Yahoo! +Name[sk]=Yahoo! +Name[sl]=Yahoo! +Name[sr]=Јаху +Name[sr@ijekavian]=Јаху +Name[sr@ijekavianlatin]=Yahoo +Name[sr@latin]=Yahoo +Name[sv]=Yahoo! +Name[tr]=Yahoo! +Name[ug]=Yahoo! +Name[uk]=Yahoo! +Name[x-test]=xxYahoo!xx +Name[zh_CN]=Yahoo! +Name[zh_TW]=Yahoo! +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav,CardDav +X-DavGroupware-ProviderUsesSSL=1 +X-DavGroupware-CalDavHost=caldav.calendar.yahoo.com +X-DavGroupware-CalDavPath=/principals/users/$localpart$ +X-DavGroupware-CardDavHost=carddav.address.yahoo.com +X-DavGroupware-CardDavPath=/principals/users/$localpart$ diff --git a/kdepim-runtime/resources/dav/services/zarafa.desktop b/kdepim-runtime/resources/dav/services/zarafa.desktop new file mode 100644 index 00000000..15add02b --- /dev/null +++ b/kdepim-runtime/resources/dav/services/zarafa.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Name=Zarafa +Name[bg]=Zarafa +Name[bs]=Zarafa +Name[ca]=Zarafa +Name[ca@valencia]=Zarafa +Name[cs]=Zarafa +Name[da]=Zarafa +Name[de]=Zarafa +Name[el]=Zarafa +Name[en_GB]=Zarafa +Name[es]=Zarafa +Name[et]=Zarafa +Name[fi]=Zarafa +Name[fr]=Zarafa +Name[ga]=Zarafa +Name[gl]=Zarafa +Name[hu]=Zarafa +Name[ia]=Zarafa +Name[it]=Zarafa +Name[kk]=Zarafa +Name[km]=Zarafa +Name[ko]=Zarafa +Name[lt]=Zarafa +Name[lv]=Zarafa +Name[mr]=à¤à¤°à¤¾à¤«à¤¾ +Name[nb]=Zarafa +Name[nds]=Zarafa +Name[nl]=Zarafa +Name[pl]=Zarafa +Name[pt]=Zarafa +Name[pt_BR]=Zarafa +Name[ro]=Zarafa +Name[ru]=Zarafa +Name[sk]=Zarafa +Name[sl]=Zarafa +Name[sr]=Зарафа +Name[sr@ijekavian]=Зарафа +Name[sr@ijekavianlatin]=Zarafa +Name[sr@latin]=Zarafa +Name[sv]=Zarafa +Name[tr]=Zarafa +Name[uk]=Zarafa +Name[x-test]=xxZarafaxx +Name[zh_CN]=Zarafa +Name[zh_TW]=Zarafa +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav +X-DavGroupware-CalDavPath=/caldav/$user$ diff --git a/kdepim-runtime/resources/dav/services/zimbra.desktop b/kdepim-runtime/resources/dav/services/zimbra.desktop new file mode 100644 index 00000000..6e88cb71 --- /dev/null +++ b/kdepim-runtime/resources/dav/services/zimbra.desktop @@ -0,0 +1,52 @@ +[Desktop Entry] +Name=Zimbra +Name[bg]=Zimbra +Name[bs]=Zimbra +Name[ca]=Zimbra +Name[ca@valencia]=Zimbra +Name[cs]=Zimbra +Name[da]=Zimbra +Name[de]=Zimbra +Name[el]=Zimbra +Name[en_GB]=Zimbra +Name[es]=Zimbra +Name[et]=Zimbra +Name[fi]=Zimbra +Name[fr]=Zimbra +Name[ga]=Zimbra +Name[gl]=Zimbra +Name[hu]=Zimbra +Name[ia]=Zimbra +Name[it]=Zimbra +Name[kk]=Zimbra +Name[km]=Zimbra +Name[ko]=Zimbra +Name[lt]=Zimbra +Name[lv]=Zimbra +Name[mr]=à¤à¤¿à¤‚बà¥à¤°à¤¾ +Name[nb]=Zimbra +Name[nds]=Zimbra +Name[nl]=Zimbra +Name[pl]=Zimbra +Name[pt]=Zimbra +Name[pt_BR]=Zimbra +Name[ro]=Zimbra +Name[ru]=Zimbra +Name[sk]=Zimbra +Name[sl]=Zimbra +Name[sr]=Зимбра +Name[sr@ijekavian]=Зимбра +Name[sr@ijekavianlatin]=Zimbra +Name[sr@latin]=Zimbra +Name[sv]=Zimbra +Name[tr]=Zimbra +Name[uk]=Zimbra +Name[x-test]=xxZimbraxx +Name[zh_CN]=Zimbra +Name[zh_TW]=Zimbra +Type=Service +X-KDE-ServiceTypes=DavGroupwareProvider + +X-DavGroupware-SupportedProtocols=CalDav,CardDav +X-DavGroupware-CalDavPath=/principals/users/$user$ +X-DavGroupware-CardDavPath=/principals/users/$user$ diff --git a/kdepim-runtime/resources/facebook/CMakeLists.txt b/kdepim-runtime/resources/facebook/CMakeLists.txt new file mode 100644 index 00000000..0623ee7e --- /dev/null +++ b/kdepim-runtime/resources/facebook/CMakeLists.txt @@ -0,0 +1,83 @@ +project(facebookresource) +include_directories(${LibKFbAPI_INCLUDE_DIR}) +include_directories(${QJSON_INCLUDE_DIR} ${qjson_INCLUDE_DIR}) +if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + include_directories(${ACCOUNTSQT_INCLUDE_DIRS} ${SIGNONQT_INCLUDE_DIRS}) + add_definitions(-DHAVE_ACCOUNTS) + set(facebookresource_SRCS ../shared/getcredentialsjob.cpp) +endif() + +set(facebookresource_SRCS + facebookresource.cpp + facebookresource_events.cpp + facebookresource_friends.cpp + facebookresource_notes.cpp + facebookresource_posts.cpp + facebookresource_notifications.cpp + settings.cpp + settingsdialog.cpp + timestampattribute.cpp + ${facebookresource_SRCS} +) + +install(FILES facebookresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") +install(FILES akonadi_facebook_resource.notifyrc DESTINATION "${DATA_INSTALL_DIR}/akonadi_facebook_resource") + +kde4_add_ui_files(facebookresource_SRCS settingsdialog.ui) +kde4_add_kcfg_files(facebookresource_SRCS settingsbase.kcfgc) + +kcfg_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfg + org.kde.Akonadi.Facebook.Settings +) + +qt4_add_dbus_adaptor(facebookresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Facebook.Settings.xml + settings.h + Settings +) + +kde4_add_executable(akonadi_facebook_resource RUN_UNINSTALLED ${facebookresource_SRCS}) + +if(Q_WS_MAC) + set_target_properties( + akonadi_facebook_resource PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.template + ) + set_target_properties( + akonadi_facebook_resource PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.Facebook" + ) + set_target_properties( + akonadi_facebook_resource PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Facebook Resource" + ) +endif() + +include_directories(${Boost_INCLUDE_DIR}) + +target_link_libraries(akonadi_facebook_resource + ${KDEPIMLIBS_AKONADI_SOCIALUTILS_LIBS} + ${KDEPIMLIBS_AKONADI_NOTES_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QJSON_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${LibKFbAPI_LIBRARY} +) + +if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + target_link_libraries(akonadi_facebook_resource + ${ACCOUNTSQT_LIBRARIES} + ${SIGNONQT_LIBRARIES}) +endif() + +add_subdirectory(icons) +add_subdirectory(serializer) + +install(TARGETS akonadi_facebook_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/facebook/Info.plist.template b/kdepim-runtime/resources/facebook/Info.plist.template new file mode 100644 index 00000000..c39ddb95 --- /dev/null +++ b/kdepim-runtime/resources/facebook/Info.plist.template @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + LSUIElement + 1 + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + diff --git a/kdepim-runtime/resources/facebook/Messages.sh b/kdepim-runtime/resources/facebook/Messages.sh new file mode 100644 index 00000000..ea762771 --- /dev/null +++ b/kdepim-runtime/resources/facebook/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name '*.ui' -o -name '*.kcfg'` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_facebook_resource.pot +rm -f rc.cpp diff --git a/kdepim-runtime/resources/facebook/akonadi_facebook_resource.notifyrc b/kdepim-runtime/resources/facebook/akonadi_facebook_resource.notifyrc new file mode 100644 index 00000000..fdfe5298 --- /dev/null +++ b/kdepim-runtime/resources/facebook/akonadi_facebook_resource.notifyrc @@ -0,0 +1,236 @@ +[Global] +Comment=Akonadi's Facebook integration +Comment[bs]=Akonadi Facebook integracija +Comment[ca]=Integració de Facebook amb Akonadi +Comment[ca@valencia]=Integració de Facebook amb Akonadi +Comment[cs]=Integrace Facebooku do Akonadi +Comment[da]=Akonadis Facebook-integration +Comment[de]=Facebook-Integration für Akonadi +Comment[el]=Ενσωμάτωση του Facebook στο Akonadi +Comment[en_GB]=Akonadi's Facebook integration +Comment[es]=Integración de Facebook con Akonadi +Comment[et]=Akonadi Facebooki lõimimine +Comment[fi]=Akonadin Facebook-integraatio +Comment[fr]=Intégration de Facebook avec Akonadi +Comment[gl]=Integración do Akonadi no Facebook +Comment[hu]=Az Akonadi Facebook integrációja +Comment[ia]=Indegration de Facebook de Akonadi +Comment[it]=Integrazione con Facebook di Akonadi +Comment[kk]=Akonadi-дің Facebook-пен бірігуі +Comment[ko]=Akonadi Facebook 통합 +Comment[lt]=Akonadi Facebook integracija +Comment[mr]=आकोनाडीचे फेसबूकशी à¤à¤•à¥€à¤•à¤°à¤£ +Comment[nb]=Akonadi Facebook-integrering +Comment[nds]=Akonadi sien Facebook-Koppelsteed +Comment[nl]=Facebook-integratie van Akonadi +Comment[pl]=Integracja Facebooka z Akonadi +Comment[pt]=Integração do Facebook com o Akonadi +Comment[pt_BR]=Integração com o Facebook do Akonadi +Comment[ru]=Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Akonadi Ñ Facebook +Comment[sk]=Akonadi Facebook integrácia +Comment[sr]=Уклапање ФејÑбука у Ðконади +Comment[sr@ijekavian]=Уклапање ФејÑбука у Ðконади +Comment[sr@ijekavianlatin]=Uklapanje Facebooka u Akonadi +Comment[sr@latin]=Uklapanje Facebooka u Akonadi +Comment[sv]=Akonadi integrering med Facebook +Comment[tr]=Akonadi Facebook bütünleÅŸtirmesi +Comment[uk]=Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Akonadi з Facebook +Comment[x-test]=xxAkonadi's Facebook integrationxx +Comment[zh_CN]=Akonadi Facebook æ•´åˆ +Comment[zh_TW]=Akonadi Facebook æ•´åˆ +Name=Facebook Resource +Name[bs]=Facebook resurs +Name[ca]=Recurs de Facebook +Name[ca@valencia]=Recurs de Facebook +Name[cs]=Zdroj Facebooku +Name[da]=Facebook-ressource +Name[de]=Facebook-Ressource +Name[el]=ΠόÏος Facebook +Name[en_GB]=Facebook Resource +Name[es]=Recurso de Facebook +Name[et]=Facebooki ressurss +Name[fi]=Facebook-resurssi +Name[fr]=Ressource Facebook +Name[gl]=Recurso do Facebook +Name[hu]=Facebook erÅ‘forrás +Name[ia]=Ressource de Facebook +Name[it]=Risorsa Facebook +Name[kk]=Facebook реÑурÑÑ‹ +Name[ko]=Facebook ìžì› +Name[lt]=Facebook resursas +Name[nb]=Facebook-ressurs +Name[nds]=Facebook-Ressource +Name[nl]=Facebook-hulpmiddel +Name[pl]=Zasób Facebooka +Name[pt]=Recurso do Facebook +Name[pt_BR]=Recurso do Facebook +Name[ru]=ИÑточник данных Facebook +Name[sk]=Facebook zdroj +Name[sr]=РеÑÑƒÑ€Ñ Ð¤ÐµÑ˜Ñбука +Name[sr@ijekavian]=РеÑÑƒÑ€Ñ Ð¤ÐµÑ˜Ñбука +Name[sr@ijekavianlatin]=Resurs Facebooka +Name[sr@latin]=Resurs Facebooka +Name[sv]=Facebook-resurs +Name[tr]=Facebook Kaynağı +Name[uk]=РеÑÑƒÑ€Ñ Facebook +Name[x-test]=xxFacebook Resourcexx +Name[zh_CN]=Facebook èµ„æº +Name[zh_TW]=Facebook è³‡æº +IconName=kde + +[Context/Application] +Name=Facebook +Name[bs]=Facebook +Name[ca]=Facebook +Name[ca@valencia]=Facebook +Name[cs]=Facebook +Name[da]=Facebook +Name[de]=Facebook +Name[el]=Facebook +Name[en_GB]=Facebook +Name[es]=Facebook +Name[et]=Facebook +Name[fi]=Facebook +Name[fr]=Facebook +Name[gl]=Facebook +Name[hu]=Facebook +Name[ia]=Facebook +Name[it]=Facebook +Name[kk]=Facebook +Name[ko]=Facebook +Name[lt]=Facebook +Name[nb]=Facebook +Name[nds]=Facebook +Name[nl]=Facebook +Name[nn]=Facebook +Name[pl]=Facebook +Name[pt]=Facebook +Name[pt_BR]=Facebook +Name[ru]=Facebook +Name[sk]=Facebook +Name[sr]=ФејÑбук +Name[sr@ijekavian]=ФејÑбук +Name[sr@ijekavianlatin]=Facebook +Name[sr@latin]=Facebook +Name[sv]=Facebook +Name[tr]=Facebook +Name[ug]=ÙÛيىسبۇك(Facebook) +Name[uk]=Facebook +Name[x-test]=xxFacebookxx +Name[zh_CN]=Facebook +Name[zh_TW]=Facebook +Comment=Notification coming from Facebook +Comment[bs]=Poruka koju je poslao Facebook +Comment[ca]=Notificació provinent de Facebook +Comment[ca@valencia]=Notificació provinent de Facebook +Comment[da]=Notifikation fra Facebook +Comment[de]=Benachrichtigungen von Facebook +Comment[el]=Η ειδοποίηση Ï€ÏοέÏχεαι από το Facebook +Comment[en_GB]=Notification coming from Facebook +Comment[es]=Notificación originada desde Facebook +Comment[et]=Facebooki märguanne +Comment[fi]=Facebookista saapuva ilmoitus +Comment[fr]=Notification provenant de Facebook +Comment[gl]=Notificación entrante desde o Facebook +Comment[hu]=Értesítés érkezik a Facebookról +Comment[ia]=Notification in arrivata ex Facebook +Comment[it]=Notifica proveniente da Facebook +Comment[kk]=Facebook-тан келетін құлақтандырулар +Comment[ko]=Facebookì—ì„œ 보낸 알림 +Comment[lt]=PraneÅ¡imas iÅ¡ Facebook +Comment[nb]=Varsling som kommer fra Facebook +Comment[nds]=Bescheed vun Facebook +Comment[nl]=Melding uit Facebook +Comment[pl]=Powiadomienia przychodzÄ…ce z Facebooka +Comment[pt]=Notificação proveniente do Facebook +Comment[pt_BR]=Notificação originada do Facebook +Comment[ru]=ПоÑтупило уведомление из Facebook +Comment[sk]=Upozornenie prichádzajúce z Facebooku +Comment[sr]=Обавештење Ñа ФејÑбука +Comment[sr@ijekavian]=Обавештење Ñа ФејÑбука +Comment[sr@ijekavianlatin]=ObaveÅ¡tenje sa Facebooka +Comment[sr@latin]=ObaveÅ¡tenje sa Facebooka +Comment[sv]=Underrättelse som kommer frÃ¥n Facebook +Comment[tr]=Facebook'tan gelen bilgilendirme +Comment[uk]="СповіщеннÑ, що надходÑÑ‚ÑŒ з Facebook" +Comment[x-test]=xxNotification coming from Facebookxx +Comment[zh_CN]=æ¥è‡ª Facebook 的通知 +Comment[zh_TW]=從 Facebook 來的通知 + +[Event/facebookNotification] +Name=New Facebook notification +Name[bs]=Nova Facebook poruka +Name[ca]=Notificació nova de Facebook +Name[ca@valencia]=Notificació nova de Facebook +Name[da]=Ny Facebook-notifikation +Name[de]=Neue Facebook-Benachrichtigung +Name[el]=Îέα ειδοποίηση Facebook +Name[en_GB]=New Facebook notification +Name[es]=Nueva notificación de Facebook +Name[et]=Uus Facebooki märguanne +Name[fi]=Uusi Facebook-ilmoitus +Name[fr]=Nouvelle notification Facebook +Name[gl]=Nova notificación do Facebook +Name[hu]=Új Facebook értesítés +Name[ia]=Nove notification de Facebook +Name[it]=Nuova notifica Facebook +Name[kk]=Жаңа Facebook құлақтандыруы +Name[ko]=새 Facebook 알림 +Name[lt]=Naujas Facebook praneÅ¡imas +Name[nb]=Ny Facebook-varsling +Name[nds]=Nieg Facebook-Bescheed +Name[nl]=Nieuwe Facebook-melding +Name[pl]=Nowe powiadomienie na Facebooku +Name[pt]=Nova notificação do Facebook +Name[pt_BR]=Nova notificação do Facebook +Name[ru]=Ðовое уведомление из Facebook +Name[sk]=Nové upozornenie Facebooku +Name[sr]=Ðово обавештење Ñа ФејÑбука +Name[sr@ijekavian]=Ðово обавештење Ñа ФејÑбука +Name[sr@ijekavianlatin]=Novo obaveÅ¡tenje sa Facebooka +Name[sr@latin]=Novo obaveÅ¡tenje sa Facebooka +Name[sv]=Ny underrättelse frÃ¥n Facebook +Name[tr]=Yeni Facebook bildirimi +Name[uk]=Ðове ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð· Facebook +Name[x-test]=xxNew Facebook notificationxx +Name[zh_CN]=æ–° Facebook 通知 +Name[zh_TW]=æ–°çš„ Facebook 通知器 +Comment=You have new Facebook notification +Comment[bs]=Imate novu Facebook poruku +Comment[ca]=Teniu una notificació nova al Facebook +Comment[ca@valencia]=Teniu una notificació nova al Facebook +Comment[da]=Du har en ny Facebook-notifikation +Comment[de]=Sie haben eine Facebook-Benachrichtigungen +Comment[el]=Έχετε νέα ειδοποίηση στο Facebook +Comment[en_GB]=You have new Facebook notification +Comment[es]=Usted tiene una nueva notificación de Facebook +Comment[et]=Sul on uus Facebooki märguanne +Comment[fi]=Sinulle on uusi Facebook-ilmoitus +Comment[fr]=Vous avez une nouvelle notification Facebook +Comment[gl]=Ten unha nova notificación do Facebook +Comment[hu]=Új Facebook értesítése van +Comment[ia]=Tu ha nove notificationes de Facebook +Comment[it]=Hai una nuova notifica da Facebook +Comment[kk]=Жаңа Facebook құлақтандыруы келді +Comment[ko]=새 Facebook ì•Œë¦¼ì´ ìžˆìŒ +Comment[lt]=Turite naujÄ… Facebook praneÅ¡imÄ… +Comment[nb]=Du har ny Facebook-varsling +Comment[nds]=Du hest en nieg Facebook-Bescheed +Comment[nl]=Er is een nieuwe Facebook-melding +Comment[pl]=Masz nowe powiadomienie na Facebooku +Comment[pt]=Tem uma notificação nova do Facebook +Comment[pt_BR]=Você tem uma nova notificação do Facebook +Comment[ru]=Ð’Ñ‹ получили уведомление из Facebook +Comment[sk]=Máte nové upozornenie Facebooku +Comment[sr]=Имате ново обавештење Ñа ФејÑбука +Comment[sr@ijekavian]=Имате ново обавештење Ñа ФејÑбука +Comment[sr@ijekavianlatin]=Imate novo obaveÅ¡tenje sa Facebooka +Comment[sr@latin]=Imate novo obaveÅ¡tenje sa Facebooka +Comment[sv]=Det finns en ny underrättelse frÃ¥n Facebook +Comment[tr]=Yeni Facebook bildiriminiz var +Comment[uk]=Вам надійшло ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð· Facebook +Comment[x-test]=xxYou have new Facebook notificationxx +Comment[zh_CN]=您有新的 Facebook 通知 +Comment[zh_TW]=您有新的 Facebook 通知 +Contexts=Application +Action=Popup diff --git a/kdepim-runtime/resources/facebook/facebookresource.cpp b/kdepim-runtime/resources/facebook/facebookresource.cpp new file mode 100644 index 00000000..d4ebbb39 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource.cpp @@ -0,0 +1,426 @@ +/* + Copyright 2010, 2011 Thomas McGuire + Copyright 2011 Roeland Jago Douma + Copyright 2012 Martin Klapetek + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "facebookresource.h" +#include "settings.h" +#include "settingsdialog.h" +#include "timestampattribute.h" +#ifdef HAVE_ACCOUNTS +#include "../shared/getcredentialsjob.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include //krazy:exclude=camelcase wait for kdepimlibs 4.11 + +#include + +using namespace Akonadi; + +FacebookResource::FacebookResource( const QString &id ) + : ResourceBase( id ) +{ + AttributeFactory::registerAttribute(); + setNeedsNetwork( true ); + setObjectName( QLatin1String( "FacebookResource" ) ); + resetState(); + Settings::self()->setResourceId( identifier() ); + + connect( this, SIGNAL(abortRequested()), + this, SLOT(slotAbortRequested()) ); + connect( this, SIGNAL(reloadConfiguration()), SLOT(configurationChanged()) ); + + changeRecorder()->fetchCollection( true ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + + AttributeFactory::registerAttribute(); +} + +FacebookResource::~FacebookResource() +{ + Settings::self()->writeConfig(); +} + +void FacebookResource::configurationChanged() +{ +#ifdef HAVE_ACCOUNTS + if ( Settings::self()->accountId() ) { + configureByAccount( Settings::self()->accountId() ); + } +#endif + Settings::self()->writeConfig(); + synchronize(); +} + +#ifdef HAVE_ACCOUNTS +void FacebookResource::configureByAccount( int accountId ) +{ + kDebug() << "Starting credentials job"; + GetCredentialsJob *gc = new GetCredentialsJob( accountId, this ); + connect(gc, SIGNAL(finished(KJob*)), SLOT(slotGetCredentials(KJob*))); + gc->start(); +} + +void FacebookResource::slotGetCredentials(KJob *job) +{ + if (job->error()) { + return;// We will get kDebug from within the job if it fails + } + + GetCredentialsJob *gJob = qobject_cast(job); + const QVariantMap data = gJob->credentialsData(); + const QString accessToken = data.value(QLatin1String("AccessToken")).toString(); + Settings::self()->setAccessToken(accessToken); + + synchronize(); +} + +#endif +void FacebookResource::aboutToQuit() +{ + slotAbortRequested(); +} + +void FacebookResource::abort() +{ + resetState(); + cancelTask(); +} + +void FacebookResource::abortWithError( const QString &errorMessage, bool authFailure ) +{ + resetState(); + cancelTask( errorMessage ); + + // This doesn't work, why? + if ( authFailure ) { + emit status( Broken, i18n( "Unable to login to Facebook, authentication failure." ) ); + } +} + +void FacebookResource::resetState() +{ + mIdle = true; + mNumFriends = -1; + mNumPhotosFetched = 0; + mCurrentJobs.clear(); + mExistingFriends.clear(); + mNewOrChangedFriends.clear(); + mPendingFriends.clear(); +} + +void FacebookResource::slotAbortRequested() +{ + if ( !mIdle ) { + foreach ( const QPointer &job, mCurrentJobs ) { + kDebug() << "Killing current job:" << job; + job->kill( KJob::Quietly ); + } + abort(); + } +} + +void FacebookResource::configure( WId windowId ) +{ + const QPointer settingsDialog( new SettingsDialog( this, windowId ) ); + if ( settingsDialog->exec() == QDialog::Accepted ) { + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + + delete settingsDialog; +} + +void FacebookResource::retrieveItems( const Akonadi::Collection &collection ) +{ + Q_ASSERT( mIdle ); + + if ( collection.remoteId() == friendsRID ) { + mIdle = false; + emit status( Running, i18n( "Preparing sync of friends list." ) ); + emit percent( 0 ); + ItemFetchJob * const fetchJob = new ItemFetchJob( collection ); + fetchJob->fetchScope().fetchAttribute(); + fetchJob->fetchScope().fetchFullPayload( false ); + mCurrentJobs << fetchJob; + connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(initialItemFetchFinished(KJob*)) ); + } else if ( collection.remoteId() == eventsRID ) { + mIdle = false; + emit status( Running, i18n( "Preparing sync of events list." ) ); + emit percent( 0 ); + KFbAPI::AllEventsListJob * const listJob = + new KFbAPI::AllEventsListJob( Settings::self()->accessToken(), this ); + listJob->setLowerLimit( KDateTime::fromString( Settings::self()->lowerLimit(), QLatin1String("%Y-%m-%d") ) ); + mCurrentJobs << listJob; + connect( listJob, SIGNAL(result(KJob*)), this, SLOT(eventListFetched(KJob*)) ); + listJob->start(); + } else if ( collection.remoteId() == notesRID ) { + mIdle = false; + emit status( Running, i18n( "Preparing sync of notes list." ) ); + emit percent( 0 ); + KFbAPI::AllNotesListJob * const notesJob = + new KFbAPI::AllNotesListJob( Settings::self()->accessToken(), this ); + notesJob->setLowerLimit( KDateTime::fromString( Settings::self()->lowerLimit(), QLatin1String("%Y-%m-%d") ) ); + mCurrentJobs << notesJob; + connect( notesJob, SIGNAL(result(KJob*)), this, SLOT(noteListFetched(KJob*)) ); + notesJob->start(); + } else if ( collection.remoteId() == postsRID ) { + mIdle = false; + emit status( Running, i18n( "Preparing sync of posts." ) ); + emit percent( 0 ); + KFbAPI::AllPostsListJob * const postsJob = + new KFbAPI::AllPostsListJob( Settings::self()->accessToken(), this ); + postsJob->setLowerLimit( + KDateTime::fromString( + KDateTime::currentLocalDateTime().addDays( -3 ).toString(), QLatin1String("%Y-%m-%d") ) ); + mCurrentJobs << postsJob; + connect( postsJob, SIGNAL(result(KJob*)), this, SLOT(postsListFetched(KJob*)) ); + postsJob->start(); + } else if ( collection.remoteId() == notificationsRID ) { + mIdle = false; + emit status( Running, i18n( "Preparing sync of notifications." ) ); + emit percent( 0 ); + KFbAPI::NotificationsListJob * const notificationsJob = + new KFbAPI::NotificationsListJob( Settings::self()->accessToken(), this ); + mCurrentJobs << notificationsJob; + connect( notificationsJob, SIGNAL(result(KJob*)), this, SLOT(notificationsListFetched(KJob*)) ); + notificationsJob->start(); + } else { + // This can not happen + Q_ASSERT( !"Unknown Collection" ); + cancelTask(); + } +} + +bool FacebookResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( parts ); + + kDebug() << item.mimeType(); + + if ( item.mimeType() == KABC::Addressee::mimeType() ) { + // TODO: Is this ever called?? + mIdle = false; + KFbAPI::FriendJob * const friendJob = + new KFbAPI::FriendJob( item.remoteId(), Settings::self()->accessToken(), this ); + mCurrentJobs << friendJob; + friendJob->setProperty( "Item", QVariant::fromValue( item ) ); + connect( friendJob, SIGNAL(result(KJob*)), this, SLOT(friendJobFinished(KJob*)) ); + friendJob->start(); + } else if ( item.mimeType() == Akonadi::NoteUtils::noteMimeType() ) { + mIdle = false; + KFbAPI::NoteJob * const noteJob = + new KFbAPI::NoteJob( item.remoteId(), Settings::self()->accessToken(), this ); + mCurrentJobs << noteJob; + noteJob->setProperty( "Item", QVariant::fromValue( item ) ); + connect( noteJob, SIGNAL(result(KJob*)), this, SLOT(noteJobFinished(KJob*)) ); + noteJob->start(); + } else if( item.mimeType() == QLatin1String("text/x-vnd.akonadi.socialfeeditem") ) { + mIdle = false; + KFbAPI::PostJob * const postJob = + new KFbAPI::PostJob( item.remoteId(), Settings::self()->accessToken(), this ); + mCurrentJobs << postJob; + postJob->setProperty( "Item", QVariant::fromValue( item ) ); + connect( postJob, SIGNAL(result(KJob*)), this, SLOT(postJobFinished(KJob*)) ); + postJob->start(); + } else if ( item.mimeType() == QLatin1String("text/x-vnd.akonadi.socialnotification") ) { + //FIXME: Need to figure out how to fetch single notification + kDebug() << "Notifications listjob"; + } + return true; +} + +void FacebookResource::retrieveCollections() +{ + Collection::List collections; + if (Settings::self()->accountServices().contains(QLatin1String("facebook-contacts"))) { + Collection friends; + friends.setRemoteId( friendsRID ); + friends.setName( i18nc( "@title: addressbook name", "Friends on Facebook" ) ); + friends.setParentCollection( Akonadi::Collection::root() ); + friends.setContentMimeTypes( QStringList() << KABC::Addressee::mimeType() ); + friends.setRights( Collection::ReadOnly ); + EntityDisplayAttribute * const friendsDisplayAttribute = new EntityDisplayAttribute(); + friendsDisplayAttribute->setIconName( QLatin1String("facebookresource") ); + friends.addAttribute( friendsDisplayAttribute ); + collections << friends; + } + + if (Settings::self()->accountServices().contains(QLatin1String("facebook-events"))) { + Collection events; + events.setRemoteId( eventsRID ); + events.setName( i18nc( "@title: events collection title", "Events on Facebook" ) ); + events.setParentCollection( Akonadi::Collection::root() ); + events.setContentMimeTypes( QStringList() << QLatin1String("text/calendar") << eventMimeType ); + events.setRights( Collection::ReadOnly ); + EntityDisplayAttribute * const evendDisplayAttribute = new EntityDisplayAttribute(); + evendDisplayAttribute->setIconName( QLatin1String("facebookresource") ); + events.addAttribute( evendDisplayAttribute ); + collections << events; + } + + if (Settings::self()->accountServices().contains(QLatin1String("facebook-notes"))) { + Collection notes; + notes.setRemoteId( notesRID ); + notes.setName( i18nc( "@title: notes collection", "Notes on Facebook" ) ); + notes.setParentCollection( Akonadi::Collection::root() ); + notes.setContentMimeTypes( QStringList() << Akonadi::NoteUtils::noteMimeType() ); + notes.setRights( Collection::ReadOnly ); + EntityDisplayAttribute * const notesDisplayAttribute = new EntityDisplayAttribute(); + notesDisplayAttribute->setIconName( QLatin1String("facebookresource") ); + notes.addAttribute( notesDisplayAttribute ); + collections << notes; + } + + if (Settings::self()->accountServices().contains(QLatin1String("facebook-feed"))) { + Collection posts; + posts.setRemoteId( postsRID ); + posts.setName( i18nc( "@title: posts collection", "Posts on Facebook" ) ); + posts.setParentCollection( Akonadi::Collection::root() ); + posts.setContentMimeTypes( QStringList() << QLatin1String("text/x-vnd.akonadi.socialfeeditem") ); + posts.setRights( Collection::CanCreateItem ); + EntityDisplayAttribute * const postsDisplayAttribute = new EntityDisplayAttribute(); + postsDisplayAttribute->setIconName( QLatin1String("facebookresource") ); + //facebook's max post length is 63206 as of September 2012 + //(Facebook ... Face Boo K ... hex(FACE) – K ... 64206 – 1000 = 63206)...don't ask me. + SocialNetworkAttributes * const socialAttributes = + new SocialNetworkAttributes( Settings::self()->userName(), + QLatin1String( "Facebook" ), + true, + 63206 ); + posts.addAttribute( postsDisplayAttribute ); + posts.addAttribute( socialAttributes ); + collections << posts; + } + + if (Settings::self()->accountServices().contains(QLatin1String("facebook-notifications"))) { + Collection notifications; + notifications.setRemoteId( notificationsRID ); + notifications.setName( i18nc( "@title: notifications collection", "Notifications on Facebook" ) ); + notifications.setParentCollection( Akonadi::Collection::root() ); + notifications.setContentMimeTypes( QStringList() << QLatin1String("text/x-vnd.akonadi.socialnotification") ); + notifications.setRights( Collection::ReadOnly ); + EntityDisplayAttribute * const notificationsDisplayAttribute = new EntityDisplayAttribute(); + notificationsDisplayAttribute->setIconName( QLatin1String("facebookresource") ); + notifications.addAttribute( notificationsDisplayAttribute ); + collections << notifications; + } + + collectionsRetrieved( collections ); +} + +void FacebookResource::itemRemoved( const Akonadi::Item &item ) +{ + if ( item.mimeType() == QLatin1String("text/x-vnd.akonadi.note") ) { + mIdle = false; + KFbAPI::FacebookDeleteJob * const deleteJob = + new KFbAPI::FacebookDeleteJob( item.remoteId(), + Settings::self()->accessToken(), + this ); + mCurrentJobs << deleteJob; + deleteJob->setProperty( "Item", QVariant::fromValue( item ) ); + connect( deleteJob, SIGNAL(result(KJob*)), this, SLOT(deleteJobFinished(KJob*)) ); + deleteJob->start(); + } else { + // Shouldn't happen, all other items are read-only + Q_ASSERT( !"Unable to delete item, not ours." ); + cancelTask(); + } +} + +void FacebookResource::deleteJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + mCurrentJobs.removeAll( job ); + if ( job->error() ) { + abortWithError( i18n( "Unable to delete note from server: %1", job->errorText() ) ); + } else { + const Item item = job->property( "Item" ).value(); + changeCommitted( item ); + resetState(); + } +} + +void FacebookResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + if ( collection.remoteId() == notesRID ) { + if ( item.hasPayload() ) { + const KMime::Message::Ptr note = item.payload(); + const QString subject = note->subject()->asUnicodeString(); + const QString message = QString::fromUtf8(note->body()); + + mIdle = false; + KFbAPI::NoteAddJob * const addJob = + new KFbAPI::NoteAddJob( subject, message, Settings::self()->accessToken(), this ); + mCurrentJobs << addJob; + addJob->setProperty( "Item", QVariant::fromValue( item ) ); + connect( addJob, SIGNAL(result(KJob*)), this, SLOT(noteAddJobFinished(KJob*)) ); + addJob->start(); + } else { + Q_ASSERT( !"Note has wrong mimetype." ); + cancelTask(); + } + } else if ( collection.remoteId() == postsRID ) { + kDebug() << "Adding new status"; + QString message; + if ( item.hasPayload() ) { + message = item.payload().message(); + mIdle = false; + } else if ( item.hasPayload() ) { + message = item.payload().postText(); + mIdle = false; + } + + KFbAPI::PostAddJob * const addJob = + new KFbAPI::PostAddJob( message, Settings::self()->accessToken(), this ); + mCurrentJobs << addJob; + addJob->setProperty( "Item", QVariant::fromValue( item ) ); + connect( addJob, SIGNAL(result(KJob*)), this, SLOT(postAddJobFinished(KJob*)) ); + addJob->start(); + + } else { + Q_ASSERT( !"Can not add this type of item!" ); + cancelTask(); + } +} + +AKONADI_RESOURCE_MAIN( FacebookResource ) diff --git a/kdepim-runtime/resources/facebook/facebookresource.desktop b/kdepim-runtime/resources/facebook/facebookresource.desktop new file mode 100644 index 00000000..11108084 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +Name=Facebook +Name[bs]=Facebook +Name[ca]=Facebook +Name[ca@valencia]=Facebook +Name[cs]=Facebook +Name[da]=Facebook +Name[de]=Facebook +Name[el]=Facebook +Name[en_GB]=Facebook +Name[es]=Facebook +Name[et]=Facebook +Name[fi]=Facebook +Name[fr]=Facebook +Name[gl]=Facebook +Name[hu]=Facebook +Name[ia]=Facebook +Name[it]=Facebook +Name[kk]=Facebook +Name[ko]=Facebook +Name[lt]=Facebook +Name[nb]=Facebook +Name[nds]=Facebook +Name[nl]=Facebook +Name[nn]=Facebook +Name[pl]=Facebook +Name[pt]=Facebook +Name[pt_BR]=Facebook +Name[ru]=Facebook +Name[sk]=Facebook +Name[sr]=ФејÑбук +Name[sr@ijekavian]=ФејÑбук +Name[sr@ijekavianlatin]=Facebook +Name[sr@latin]=Facebook +Name[sv]=Facebook +Name[tr]=Facebook +Name[ug]=ÙÛيىسبۇك(Facebook) +Name[uk]=Facebook +Name[x-test]=xxFacebookxx +Name[zh_CN]=Facebook +Name[zh_TW]=Facebook +Comment=Makes your Facebook data available in KDE +Comment[bs]=ÄŒini vaÅ¡e Facebook podatke dostupnim u KDE +Comment[ca]=Fa que les vostres dades de Facebook estiguin disponibles al KDE +Comment[ca@valencia]=Fa que les vostres dades de Facebook estiguen disponibles al KDE +Comment[cs]=Zpřístupňuje váš Facebook v KDE +Comment[da]=Gør dine Facebook-data tilgængelige i KDE +Comment[de]=Stellt Ihre Facebook-Daten in KDE zur Verfügung +Comment[el]=Κάνει τα δεδομένα σας στο Facebook διαθέσιμα στο KDE +Comment[en_GB]=Makes your Facebook data available in KDE +Comment[es]=Hace que sus datos de Facebook estén disponibles en KDE +Comment[et]=Oma Facebooki andmete kättesaadavaks tegemine KDE-s +Comment[fi]=Tuo Facebook-tietosi KDE:ssa käytettäviksi +Comment[fr]=Permet d'accéder à vos données Facebook depuis KDE +Comment[gl]=Pon os seus datos do Facebook a disposición do KDE +Comment[hu]=ElérhetÅ‘vé teszi a Facebook adatait a KDE-ben +Comment[ia]=face que tu datos de Facebook es disponibile in KDE +Comment[it]=Rende disponibile i tuoi dati Facebook in KDE +Comment[kk]=Facebook деректеріңізді KDE-де қол жетімді қылу +Comment[ko]=KDEì—ì„œ Facebook ë°ì´í„° 사용 +Comment[lt]=Padaro JÅ«sų Facebook duomenis pasiekiamus KDE +Comment[nb]=Gjør dine Facebook-data tilgjengelige i KDE +Comment[nds]=Maakt Dien Facebook-Daten binnen KDE verföögbor +Comment[nl]=Maakt uw Facebook gegevens beschikbaar in KDE +Comment[pl]=Sprawia, że twoje dane z Facebook sÄ… dostÄ™pne w KDE +Comment[pt]=Disponibiliza os seus dados do Facebook no KDE +Comment[pt_BR]=Disponibiliza os seus dados do Facebook no KDE +Comment[ru]=Передаёт в KDE ваши данные Facebook +Comment[sk]=Sprístupní vaÅ¡e Facebook dáta v KDE +Comment[sr]=Чини ваше податке Ñа ФејÑбука доÑтупнима под КДЕ‑ом +Comment[sr@ijekavian]=Чини ваше податке Ñа ФејÑбука доÑтупнима под КДЕ‑ом +Comment[sr@ijekavianlatin]=ÄŒini vaÅ¡e podatke sa Facebooka dostupnima pod KDE‑om +Comment[sr@latin]=ÄŒini vaÅ¡e podatke sa Facebooka dostupnima pod KDE‑om +Comment[sv]=Gör data frÃ¥n Facebook tillgänglig i KDE +Comment[tr]=Facebook verilerinizi KDE içinde kullanılabilir yapar +Comment[ug]=ÙÛيىسبۇكدىكى سانلىق-مەلۇماتلىرىڭىزنى KDE دا ئىشلەيدىغان قىلىدۇ +Comment[uk]=Робить доÑтупними дані Facebook у KDE +Comment[x-test]=xxMakes your Facebook data available in KDExx +Comment[zh_CN]=使得您的 Facebook æ•°æ®åœ¨ KDE 中å¯ç”¨ +Comment[zh_TW]=é€éŽ Akonadi 將您在 Facebook 上的資料與 KDE åŒæ­¥ã€‚ +Type=AkonadiResource +Exec=akonadi_facebook_resource +Icon=facebookresource +X-Akonadi-Custom-KAccounts=facebook-contacts,facebook-feed,facebook-events,facebook-notes,facebook-notifications +X-Akonadi-MimeTypes=text/directory,text/calendar,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.note,text/x-vnd.akonadi.socialfeeditem,text/x-vnd.akonadi.socialnotification +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_facebook_resource diff --git a/kdepim-runtime/resources/facebook/facebookresource.h b/kdepim-runtime/resources/facebook/facebookresource.h new file mode 100644 index 00000000..df69a0e4 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource.h @@ -0,0 +1,145 @@ +/* + Copyright 2010, 2011 Thomas McGuire + Copyright 2011 Roeland Jago Douma + Copyright 2012 Martin Klapetek + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 FACEBOOK_FACEBOOKRESOURCE_H +#define FACEBOOK_FACEBOOKRESOURCE_H + +#include +#include +#include + +#include + +#include +#include + +class KStatusNotifierItem; + +static const QLatin1String notificationsRID( "notifications" ); +static const QLatin1String friendsRID( "friends" ); +static const QLatin1String eventsRID( "events" ); +static const QLatin1String eventMimeType( "application/x-vnd.akonadi.calendar.event" ); +static const QLatin1String notesRID( "notes" ); +static const QLatin1String postsRID( "posts" ); + +class FacebookResource : public Akonadi::ResourceBase, + public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + explicit FacebookResource( const QString &id ); + ~FacebookResource(); + + using ResourceBase::synchronize; + + public Q_SLOTS: + void configure( WId windowId ); +#ifdef HAVE_ACCOUNTS + void configureByAccount( int accountId ); + void slotGetCredentials(KJob *job); +#endif + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + void itemRemoved( const Akonadi::Item &item ); + void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + + protected: + void aboutToQuit(); + + private Q_SLOTS: + + void slotAbortRequested(); + void configurationChanged(); + void friendListJobFinished( KJob *job ); + void friendJobFinished( KJob *job ); + void photoJobFinished( KJob *job ); + void detailedFriendListJobFinished( KJob *job ); + void initialItemFetchFinished( KJob *job ); + void eventListFetched( KJob *job ); + void detailedEventListJobFinished( KJob *job ); + void noteListFetched( KJob *job ); + void noteJobFinished( KJob *job ); + void noteAddJobFinished( KJob *job ); + void postJobFinished( KJob *job ); + void postsListFetched( KJob *job ); + void postAddJobFinished( KJob *job ); + void notificationsListFetched( KJob *job ); + void notificationSNIActivated(bool active, const QPoint &position); + void notificationLinkActivated(); + void notificationMarkAsReadJobFinished(KJob *job); + void notificationCollectionFetchJobFinished(KJob *job); + void markNotificationsAsReadTriggered(); + void hideNotificationsSNITriggered(); + + void deleteJobFinished( KJob *job ); + + private: + void fetchPhotos(); + void resetState(); + void abortWithError( const QString &errorMessage, bool authFailure = false ); + void abort(); + + enum FormattingStringType { + FacebookComment = 0, + FacebookLike = 1 + }; + QString formatI18nString( FormattingStringType type, int n ); + + enum FbNotificationPresentation { + KSNIonly = 0, + KSNIandKNotification = 1 + }; + + void displayNotificationsToUser(FbNotificationPresentation displayType); + + void fetchNewOrChangedFriends(); + void finishFriendFetching(); + void finishEventsFetching(); + void finishNotesFetching(); + void finishPostsFetching(); + void finishNotificationsFetching(); + Akonadi::SocialFeedItem convertToSocialFeedItem( const KFbAPI::PostInfo &postinfo ); + + // Friends that are already stored on the Akonadi server + QMap mExistingFriends; + + // Pending new/changed friends we still need to download + QList mPendingFriends; + + QList mNewOrChangedFriends; + + QList mDisplayedNotifications; + + // Total number of new & changed friends + int mNumFriends; + int mNumPhotosFetched; + + bool mIdle; + QList< QPointer > mCurrentJobs; + + QPointer mNotificationSNI; +}; + +#endif diff --git a/kdepim-runtime/resources/facebook/facebookresource_events.cpp b/kdepim-runtime/resources/facebook/facebookresource_events.cpp new file mode 100644 index 00000000..3b748eb2 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource_events.cpp @@ -0,0 +1,108 @@ +/* + Copyright 2010, 2011 Thomas McGuire + Copyright 2011 Roeland Jago Douma + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "facebookresource.h" +#include "settings.h" +#include "settingsdialog.h" +#include "timestampattribute.h" + +#include +#include + +#include +#include +#include +#include +#include + +using namespace Akonadi; + +void FacebookResource::eventListFetched( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::AllEventsListJob * const listJob = qobject_cast( job ); + Q_ASSERT( listJob ); + mCurrentJobs.removeAll( job ); + + if ( listJob->error() ) { + abortWithError( i18n( "Unable to get events from server: %1", listJob->errorString() ), + listJob->error() == KFbAPI::FacebookJob::AuthenticationProblem ); + } else { + QStringList eventIds; + foreach ( const KFbAPI::EventInfo &event, listJob->allEvents() ) { + eventIds.append( event.id() ); + } + if ( eventIds.isEmpty() ) { + itemsRetrievalDone(); + finishEventsFetching(); + return; + } + KFbAPI::EventJob * const eventJob = + new KFbAPI::EventJob( eventIds, Settings::self()->accessToken(), this ); + mCurrentJobs << eventJob; + connect( eventJob, SIGNAL(result(KJob*)), this, SLOT(detailedEventListJobFinished(KJob*)) ); + eventJob->start(); + } + + listJob->deleteLater(); +} + +void FacebookResource::detailedEventListJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::EventJob * const eventJob = qobject_cast( job ); + Q_ASSERT( eventJob ); + mCurrentJobs.removeAll( job ); + + if ( job->error() ) { + abortWithError( i18n( "Unable to get list of events from server: %1", eventJob->errorText() ) ); + } else { + setItemStreamingEnabled( true ); + + Item::List eventItems; + foreach ( const KFbAPI::EventInfo &eventInfo, eventJob->eventInfo() ) { + if (eventInfo.id().isEmpty()) { + //skip invalid events + continue; + } + Item event; + event.setRemoteId( eventInfo.id() ); + event.setPayload( eventInfo.asEvent() ); + event.setMimeType( eventMimeType ); + eventItems.append( event ); + } + itemsRetrieved( eventItems ); + itemsRetrievalDone(); + finishEventsFetching(); + } + + eventJob->deleteLater(); +} + +void FacebookResource::finishEventsFetching() +{ + emit percent( 100 ); + // TODO: Use an actual number here + emit status( Idle, i18n( "All events fetched from server." ) ); + resetState(); +} diff --git a/kdepim-runtime/resources/facebook/facebookresource_friends.cpp b/kdepim-runtime/resources/facebook/facebookresource_friends.cpp new file mode 100644 index 00000000..5b418f53 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource_friends.cpp @@ -0,0 +1,260 @@ +/* + Copyright 2010, 2011 Thomas McGuire + Copyright 2011 Roeland Jago Douma + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "facebookresource.h" +#include "settings.h" +#include "settingsdialog.h" +#include "timestampattribute.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Akonadi; + +void FacebookResource::initialItemFetchFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + ItemFetchJob * const itemFetchJob = dynamic_cast( job ); + Q_ASSERT( itemFetchJob ); + mCurrentJobs.removeAll( job ); + + if ( itemFetchJob->error() ) { + abortWithError( i18n( "Unable to get list of existing friends from the Akonadi server: %1", + itemFetchJob->errorString() ) ); + } else { + foreach ( const Item &item, itemFetchJob->items() ) { + if ( item.hasAttribute() ) { + mExistingFriends.insert( item.remoteId(), + item.attribute()->timeStamp() ); + } + } + + setItemStreamingEnabled( true ); + KFbAPI::FriendListJob * const friendListJob = + new KFbAPI::FriendListJob( Settings::self()->accessToken(), this ); + mCurrentJobs << friendListJob; + connect( friendListJob, SIGNAL(result(KJob*)), this, SLOT(friendListJobFinished(KJob*)) ); + emit status( Running, i18n( "Retrieving friends list." ) ); + emit percent( 2 ); + friendListJob->start(); + } + + itemFetchJob->deleteLater(); +} + +void FacebookResource::friendListJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::FriendListJob * const friendListJob = dynamic_cast( job ); + Q_ASSERT( friendListJob ); + mCurrentJobs.removeAll( job ); + + if ( friendListJob->error() ) { + abortWithError( i18n( "Unable to get list of friends from server: %1", + friendListJob->errorText() ), + friendListJob->error() == KFbAPI::FacebookJob::AuthenticationProblem ); + } else { + + // Figure out which items are new or changed + foreach ( const KFbAPI::UserInfo &user, friendListJob->friends() ) { +#if 0 // Bah, Facebook's timestamp doesn't seem to get updated when a user's profile changes :( + // See http://bugs.developers.facebook.net/show_bug.cgi?id=15475 + const KDateTime stampOfExisting = mExistingFriends.value( user->id(), KDateTime() ); + if ( !stampOfExisting.isValid() ) { + kDebug() << "Friend" << user->id() << user->name() << "is new!"; + mNewOrChangedFriends.append( user ); + } else if ( user->updatedTime() > stampOfExisting ) { + kDebug() << "Friend" << user->id() << user->name() << "is updated!"; + mNewOrChangedFriends.append( user ); + } else { + //kDebug() << "Friend" << user->id() << user->name() << "is old."; + } +#else + mNewOrChangedFriends.append( user ); +#endif + } + + // Delete items that are in the Akonadi DB but no on FB + Item::List removedItems; + foreach ( const QString &friendId, mExistingFriends.keys() ) { //krazy:exclude=foreach + bool found = false; + foreach ( const KFbAPI::UserInfo &user, friendListJob->friends() ) { + if ( user.id() == friendId ) { + found = true; + break; + } + } + if ( !found ) { + kDebug() << friendId << "is no longer your friend :("; + Item removedItem; + removedItem.setRemoteId( friendId ); + removedItems.append( removedItem ); + } + } + itemsRetrievedIncremental( Item::List(), removedItems ); + + if ( mNewOrChangedFriends.isEmpty() ) { + finishFriendFetching(); + } else { + emit status( Running, i18n( "Retrieving friends' details." ) ); + emit percent( 5 ); + fetchNewOrChangedFriends(); + } + } + + friendListJob->deleteLater(); +} + +void FacebookResource::fetchNewOrChangedFriends() +{ + QStringList mewOrChangedFriendIds; + Q_FOREACH ( const KFbAPI::UserInfo &user, mNewOrChangedFriends ) { + mewOrChangedFriendIds.append( user.id() ); + } + KFbAPI::FriendJob * const friendJob = + new KFbAPI::FriendJob( mewOrChangedFriendIds, Settings::self()->accessToken(), this ); + mCurrentJobs << friendJob; + connect( friendJob, SIGNAL(result(KJob*)), this, SLOT(detailedFriendListJobFinished(KJob*)) ); + friendJob->start(); +} + +void FacebookResource::detailedFriendListJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::FriendJob * const friendJob = dynamic_cast( job ); + Q_ASSERT( friendJob ); + mCurrentJobs.removeAll( job ); + + if ( friendJob->error() ) { + abortWithError( i18n( "Unable to retrieve friends' information from server: %1", + friendJob->errorText() ) ); + } else { + mPendingFriends = friendJob->friendInfo(); + mNumFriends = mPendingFriends.size(); + emit status( Running, i18n( "Retrieving friends' photos." ) ); + emit percent( 10 ); + fetchPhotos(); + } + + friendJob->deleteLater(); +} + +void FacebookResource::fetchPhotos() +{ + mIdle = false; + mNumPhotosFetched = 0; + Q_FOREACH ( const KFbAPI::UserInfo &f, mPendingFriends ) { + KFbAPI::PhotoJob * const photoJob = + new KFbAPI::PhotoJob( f.id(), Settings::self()->accessToken(), this ); + mCurrentJobs << photoJob; + photoJob->setProperty( "friend", QVariant::fromValue( f ) ); + connect( photoJob, SIGNAL(result(KJob*)), this, SLOT(photoJobFinished(KJob*)) ); + photoJob->start(); + } +} + +void FacebookResource::finishFriendFetching() +{ + Q_ASSERT( mCurrentJobs.size() == 0 ); + + mPendingFriends.clear(); + emit percent( 100 ); + if ( mNumFriends > 0 ) { + emit status( Idle, i18np( "Updated one friend from the server.", + "Updated %1 friends from the server.", mNumFriends ) ); + } else { + emit status( Idle, i18n( "Finished, no friends needed to be updated." ) ); + } + resetState(); +} + +void FacebookResource::photoJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::PhotoJob * const photoJob = dynamic_cast( job ); + Q_ASSERT( photoJob ); + const KFbAPI::UserInfo user = job->property( "friend" ).value(); + mCurrentJobs.removeOne( job ); + + if ( photoJob->error() ) { + abortWithError( i18n( "Unable to retrieve friends' photo from server: %1", + photoJob->errorText() ) ); + } else { + // Create Item + KABC::Addressee addressee = user.toAddressee(); + addressee.setPhoto( KABC::Picture( photoJob->photo() ) ); + Item newUser; + newUser.setRemoteId( user.id() ); + newUser.setMimeType( QLatin1String("text/directory") ); + newUser.setPayload( addressee ); + TimeStampAttribute * const timeStampAttribute = new TimeStampAttribute(); + timeStampAttribute->setTimeStamp( user.updatedTime() ); + newUser.addAttribute( timeStampAttribute ); + + itemsRetrievedIncremental( Item::List() << newUser, Item::List() ); + mNumPhotosFetched++; + + if ( mNumPhotosFetched != mNumFriends ) { + const int alreadyDownloadedFriends = mNumPhotosFetched; + const float percentageDone = alreadyDownloadedFriends / ( float )mNumFriends * 100.0f; + emit percent( 10 + percentageDone * 0.9f ); + } else { + itemsRetrievalDone(); + finishFriendFetching(); + } + } + + photoJob->deleteLater(); +} + +void FacebookResource::friendJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::FriendJob * const friendJob = dynamic_cast( job ); + Q_ASSERT( friendJob ); + Q_ASSERT( friendJob->friendInfo().size() == 1 ); + mCurrentJobs.removeAll( job ); + + if ( friendJob->error() ) { + abortWithError( i18n( "Unable to get information about friend from server: %1", + friendJob->errorText() ) ); + } else { + Item user = friendJob->property( "Item" ).value(); + user.setPayload( friendJob->friendInfo().first().toAddressee() ); + // TODO: What about picture here? + itemRetrieved( user ); + resetState(); + } + + friendJob->deleteLater(); +} diff --git a/kdepim-runtime/resources/facebook/facebookresource_notes.cpp b/kdepim-runtime/resources/facebook/facebookresource_notes.cpp new file mode 100644 index 00000000..ef0912ff --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource_notes.cpp @@ -0,0 +1,117 @@ +/* + Copyright 2010, 2011 Thomas McGuire + Copyright 2011 Roeland Jago Douma + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "facebookresource.h" +#include "settings.h" +#include "settingsdialog.h" +#include "timestampattribute.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Akonadi; + +void FacebookResource::noteListFetched( KJob *job ) +{ + Q_ASSERT( !mIdle ); + KFbAPI::AllNotesListJob * const listJob = dynamic_cast( job ); + Q_ASSERT( listJob ); + mCurrentJobs.removeAll( job ); + + if ( listJob->error() ) { + abortWithError( i18n( "Unable to get notes from server: %1", listJob->errorString() ), + listJob->error() == KFbAPI::FacebookJob::AuthenticationProblem ); + } else { + setItemStreamingEnabled( true ); + + Item::List noteItems; + foreach ( const KFbAPI::NoteInfo ¬eInfo, listJob->allNotes() ) { + Item note; + note.setRemoteId( noteInfo.id() ); + note.setPayload( noteInfo.asNote() ); + note.setSize( noteInfo.asNote()->size() ); + note.setMimeType( QLatin1String("text/x-vnd.akonadi.note") ); + noteItems.append( note ); + } + + itemsRetrieved( noteItems ); + itemsRetrievalDone(); + finishNotesFetching(); + } + + listJob->deleteLater(); +} + +void FacebookResource::finishNotesFetching() +{ + emit percent( 100 ); + emit status( Idle, i18n( "All notes fetched from server." ) ); + resetState(); +} + +void FacebookResource::noteJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::NoteJob * const noteJob = dynamic_cast( job ); + Q_ASSERT( noteJob ); + Q_ASSERT( noteJob->noteInfo().size() == 1 ); + mCurrentJobs.removeAll( job ); + + if ( noteJob->error() ) { + abortWithError( i18n( "Unable to get information about note from server: %1", + noteJob->errorText() ) ); + } else { + Item note = noteJob->property( "Item" ).value(); + note.setPayload( noteJob->noteInfo().first().asNote() ); + itemRetrieved( note ); + resetState(); + } + + noteJob->deleteLater(); +} + +void FacebookResource::noteAddJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::NoteAddJob * const addJob = dynamic_cast( job ); + Q_ASSERT( addJob ); + mCurrentJobs.removeAll( job ); + + if ( job->error() ) { + abortWithError( i18n( "Unable to get upload note to server: %1", job->errorText() ) ); + } else { + Item note = addJob->property( "Item" ).value(); + note.setRemoteId( addJob->property( "id" ).value() ); + changeCommitted( note ); + resetState(); + } + + addJob->deleteLater(); +} diff --git a/kdepim-runtime/resources/facebook/facebookresource_notifications.cpp b/kdepim-runtime/resources/facebook/facebookresource_notifications.cpp new file mode 100644 index 00000000..85f43fa0 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource_notifications.cpp @@ -0,0 +1,397 @@ +/* + Copyright 2012 Martin Klapetek + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "facebookresource.h" +#include "settings.h" +#include "timestampattribute.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; + +void FacebookResource::notificationsListFetched( KJob *job ) +{ + Q_ASSERT( !mIdle ); + KFbAPI::NotificationsListJob * const listJob = dynamic_cast( job ); + Q_ASSERT( listJob ); + mCurrentJobs.removeAll( job ); + + if ( listJob->error() ) { + abortWithError( i18n( "Unable to get notifications from server: %1", listJob->errorString() ), + listJob->error() == KFbAPI::FacebookJob::AuthenticationProblem ); + } else { + setItemStreamingEnabled( true ); + + Item::List notificationItems; + + //clear any notifications cached for display + mDisplayedNotifications.clear(); + + KSharedConfigPtr knotificationHistory = KSharedConfig::openConfig(QLatin1String("facebook-notificationsrc")); + + Q_FOREACH ( const KFbAPI::NotificationInfo ¬ificationInfo, listJob->notifications() ) { + Item notification; + notification.setRemoteId( notificationInfo.id() ); + notification.setMimeType( QLatin1String("text/x-vnd.akonadi.socialnotification") ); + notification.setPayload( notificationInfo ); + notificationItems.append( notification ); + + if (Settings::self()->displayNotifications()) { + if (notificationInfo.unread()) { + mDisplayedNotifications.append(notificationInfo); + } else { + //if the notification is marked as read, remove it from saved notifications + //to keep it small + knotificationHistory->deleteGroup(notificationInfo.id()); + } + } + } + + if (Settings::self()->displayNotifications()) { + displayNotificationsToUser(FacebookResource::KSNIandKNotification); + } + + itemsRetrieved( notificationItems ); + itemsRetrievalDone(); + finishNotificationsFetching(); + } + + listJob->deleteLater(); +} + +void FacebookResource::finishNotificationsFetching() +{ + emit percent( 100 ); + emit status( Idle, i18n( "All notifications fetched from server." ) ); + resetState(); +} + +void FacebookResource::displayNotificationsToUser(FbNotificationPresentation displayType) +{ + if (mDisplayedNotifications.isEmpty()) { + //if we have no notifications to process, quit + if (!mNotificationSNI.isNull()) { + //also check if the SNI exists, destroy if it is + mNotificationSNI.data()->deleteLater(); + } + return; + } + + if (mNotificationSNI.isNull()) { + mNotificationSNI = new KStatusNotifierItem(this); + mNotificationSNI->setCategory(KStatusNotifierItem::Communications); + mNotificationSNI->setIconByName(QLatin1String("facebookresource")); + mNotificationSNI->setAttentionIconByName(QLatin1String("facebookresource")); + mNotificationSNI->setStandardActionsEnabled(false); + + connect(mNotificationSNI.data(), SIGNAL(activateRequested(bool,QPoint)), + this, SLOT(notificationSNIActivated(bool,QPoint))); + } + + mNotificationSNI->setContextMenu(0); + + //the menu is deleted by SNI whenever we set new context menu + //and/or when the SNI is destroyed + KMenu *contextMenu = new KMenu(); + contextMenu->addTitle(i18n("Facebook Notifications")); + + //use separate config file for the displayed notifications + KSharedConfigPtr knotificationHistory = KSharedConfig::openConfig(QLatin1String("facebook-notificationsrc")); + + //the logic here is - we want to display KNotification for each batch of Facebook notifications + //only once (KNotification will just show the newest not-yet-displayed with "...and N more" appended), + //but on the SNI tooltip we want to show always the newest three, so different strings must be constructed + int tooltipCount = 0; + int notificationCount = 0; + + QString sniTooltip = QLatin1String("
    "); + QString notificationString; + + Q_FOREACH (const KFbAPI::NotificationInfo ¬ification, mDisplayedNotifications) { + + //**** Process KNotification first + + //The KNotification plasmoid shows max 4 lines of text, so unfortunately we need to limit this + //to only one notification being displayed in the notification and then add the "...and N more" + //but this "N more" means "N more notifications that were not displayed before", + //so we need to count how many new notifications we got using the notificationCount var + if (displayType == FacebookResource::KSNIandKNotification) { + KConfigGroup notificationConfig = knotificationHistory->group(notification.id()); + + //this is true if this notification was already in KNotification before + bool skip = false; + + //if we don't have any Facebook notifications to show yet + if (notificationString.isEmpty()) { + + //check if we have stored config for this notification + if (notificationConfig.isValid()) { + //now check if the stored notification has changed or not + if (notificationConfig.readEntry(QLatin1String("updatedTime")) == notification.updatedTimeString()) { + //if it was not changed, just skip it + skip = true; + } + } + + //if the config group does not exist, skip == false and we continue + if (!skip) { + notificationString.append(notification.title()); + notificationString.append(QLatin1String("\n")); + + notificationCount++; + notificationConfig.writeEntry(QLatin1String("updatedTime"), notification.updatedTimeString()); + } else { + kDebug() << "Skipping notification" << notification.id(); + } + } + } + + //**** Process KStatusNotifierItem entries + + //include only up to 3 notifications in the KNotification/SNI tooltip + if (tooltipCount < 3) { + sniTooltip.append(QString::fromLatin1("
  • %1
  • ").arg(notification.title())); + tooltipCount++; + } + + //create for each notification a qaction to put in SNI; + //set the link as property for fast access to it, id is used to remove it + //from the SNI menu + QAction *action = new QAction(notification.title().simplified(), contextMenu); + action->setProperty("notificationLink", notification.link()); + action->setProperty("notificationId", notification.id()); + action->setToolTip(i18nc("@info:tooltip", "Open browser")); + connect(action, SIGNAL(triggered(bool)), + this, SLOT(notificationLinkActivated())); + contextMenu->addAction(action); + } + + sniTooltip = sniTooltip.append(QLatin1String("
")); + + notificationString.append(i18ncp("This string is appended to a Facebook notification displayed " + "in KNotification, indicating how many more notifications " + "were received, but are not displayed. So the KNotification " + "meaning will be 'You have X new Facebook notifications, here's the " + "first one and then there are Y more", + "...and one more", + "...and %1 more", + mDisplayedNotifications.size() - notificationCount)); + + //save which notifications are being displayed in KNotification + knotificationHistory->sync(); + + contextMenu->addSeparator(); + + QAction *dismissAll = new QAction(i18n("Mark all notifications as read"), contextMenu); + connect(dismissAll, SIGNAL(triggered(bool)), + this, SLOT(markNotificationsAsReadTriggered())); + + contextMenu->addAction(dismissAll); + + QAction *hideSNI = new QAction(i18n("Hide"), contextMenu); + connect(hideSNI, SIGNAL(triggered(bool)), + this, SLOT(hideNotificationsSNITriggered())); + + contextMenu->addAction(hideSNI); + + //if we have more than 3 notifications, append "...and N more" at the end of the string + if (mDisplayedNotifications.size() > 3) { + sniTooltip.append(i18ncp("This string is appended at the end of Facebook notifications " + "displayed in the system notifications, indicating there are more " + "notifications, which are not displayed.", + "...and 1 more", + "...and %1 more", + mDisplayedNotifications.size() - 3)); + } + + if (displayType == FacebookResource::KSNIandKNotification && !notificationString.isEmpty()) { + //create the system notification + KNotification *notification = new KNotification(QLatin1String("facebookNotification"), KNotification::CloseOnTimeout); + notification->setTitle(i18np("New Facebook Notification", + "%1 New Facebook Notifications", + mDisplayedNotifications.size())); + notification->setText(notificationString); + //we need to manually create the KAboutData because KGlobal::mainComponent() returns + //the name with appended resource id, ie "akonadi_facebook_resource_72" + //this in turn causes KNotification to not find the .notifyrc file + //so we must set it manually to the name of the .notifyrc file + KAboutData aboutData("akonadi_facebook_resource", 0, KLocalizedString(), 0); + notification->setComponentData(KComponentData(aboutData)); + notification->sendEvent(); + } + + mNotificationSNI->setStatus(KStatusNotifierItem::NeedsAttention); + mNotificationSNI->setContextMenu(contextMenu); + + //if we're handling just one notification, show its text directly in the tooltip, + //otherwise show the tooltip string built above + if (mDisplayedNotifications.size() == 1) { + mNotificationSNI->setProperty("notificationLink", mDisplayedNotifications.first().link()); + mNotificationSNI->setToolTip(QLatin1String("facebookresource"), + mDisplayedNotifications.first().title(), + QString()); + } else { + mNotificationSNI->setToolTip(QLatin1String("facebookresource"), + i18np("You have one new Facebook notification", + "You have %1 new Facebook notifications", + mDisplayedNotifications.size()), + sniTooltip); + } +} + +void FacebookResource::notificationSNIActivated(bool active, const QPoint &point) +{ + Q_UNUSED(active); + + KStatusNotifierItem *sni = qobject_cast(sender()); + + Q_ASSERT(sni); + if (!sni) { + return; + } + + //this property is set only in case we're handling single notification + QUrl link = sni->property("notificationLink").toUrl(); + + if (link.isEmpty()) { + //this^ means we have multiple notifications and we should show the menu + sni->contextMenu()->popup(point); + //The SNI is not deleted in this branch, here's what happens in here: + // - the context menu opens + // - user clicks one of the notifications + // - this notification is removed from the cached list (mDisplayedNotifications) + // - displayNotificationsToUser(..) is called + // - this checks if mDisplayedNotifications is empty, if yes, the SNI is deleted. + } else { + //if we're handling single notification, open the browser and destroy the SNI as it's useful no more + KToolInvocation::invokeBrowser(link.toString()); + sni->deleteLater(); + } +} + +void FacebookResource::notificationLinkActivated() +{ + QUrl link = sender()->property("notificationLink").toUrl(); + QString id = sender()->property("notificationId").toString(); + + //remove the notification from cached notifications + //so it does not reappear in the menu + QMutableListIterator i(mDisplayedNotifications); + while (i.hasNext()) { + if (i.next().id() == id) { + i.remove(); + } + } + + //open the browser with the link + if (!link.isEmpty()) { + KToolInvocation::invokeBrowser(link.toString()); + } + + //mark the notification as read on the server + KFbAPI::NotificationsMarkReadJob * const markNotificationJob = + new KFbAPI::NotificationsMarkReadJob(id, Settings::self()->accessToken(), this); + + mCurrentJobs << markNotificationJob; + connect(markNotificationJob, SIGNAL(result(KJob*)), + this, SLOT(notificationMarkAsReadJobFinished(KJob*))); + + markNotificationJob->start(); + + //trigger reupdate of the SNI + displayNotificationsToUser(FacebookResource::KSNIonly); +} + +void FacebookResource::notificationMarkAsReadJobFinished(KJob *job) +{ + if (job->error()) { + kWarning() << job->errorString() << job->errorText(); + } + + mCurrentJobs.removeAll( job ); + + //only refetch the notifications if there are no more jobs running + if (mCurrentJobs.isEmpty()) { + //refetch notifications + Collection notifications; + notifications.setRemoteId(notificationsRID); + CollectionFetchJob *collectionJob = new CollectionFetchJob(notifications, CollectionFetchJob::Base, this); + + connect(collectionJob, SIGNAL(result(KJob*)), + this, SLOT(notificationCollectionFetchJobFinished(KJob*))); + } + + job->deleteLater(); +} + +void FacebookResource::notificationCollectionFetchJobFinished(KJob *job) +{ + CollectionFetchJob *collectionJob = qobject_cast(job); + + Q_FOREACH(const Akonadi::Collection &collection, collectionJob->collections()) { + if (collection.remoteId() == notificationsRID) { + synchronizeCollection(collection.id()); + //there is only one notifications collection, bail out if it was found + break; + } + } +} + +void FacebookResource::markNotificationsAsReadTriggered() +{ + for (int i = 0; i < mDisplayedNotifications.size(); i++) { + KFbAPI::NotificationsMarkReadJob * const markNotificationJob = + new KFbAPI::NotificationsMarkReadJob(mDisplayedNotifications.at(i).id(), + Settings::self()->accessToken(), + this); + + mCurrentJobs << markNotificationJob; + connect(markNotificationJob, SIGNAL(result(KJob*)), + this, SLOT(notificationMarkAsReadJobFinished(KJob*))); + + markNotificationJob->start(); + } +} + +void FacebookResource::hideNotificationsSNITriggered() +{ + Q_ASSERT(!mNotificationSNI.isNull()); + + if (!mNotificationSNI.isNull()) { + mNotificationSNI->setStatus(KStatusNotifierItem::Passive); + } +} diff --git a/kdepim-runtime/resources/facebook/facebookresource_posts.cpp b/kdepim-runtime/resources/facebook/facebookresource_posts.cpp new file mode 100644 index 00000000..7db02f91 --- /dev/null +++ b/kdepim-runtime/resources/facebook/facebookresource_posts.cpp @@ -0,0 +1,208 @@ +/* + Copyright (C) 2012 Martin Klapetek + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "facebookresource.h" +#include "settings.h" +#include "settingsdialog.h" +#include "timestampattribute.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Akonadi; + +void FacebookResource::postsListFetched( KJob *job ) +{ + Q_ASSERT( !mIdle ); + KFbAPI::AllPostsListJob * const listJob = dynamic_cast( job ); + Q_ASSERT( listJob ); + mCurrentJobs.removeAll( job ); + + if ( listJob->error() ) { + abortWithError( i18n( "Unable to get posts from server: %1", listJob->errorString() ), + listJob->error() == KFbAPI::FacebookJob::AuthenticationProblem ); + } else { + setItemStreamingEnabled( true ); + + Item::List postItems; + kDebug() << "Going into foreach"; + Q_FOREACH ( const KFbAPI::PostInfo &postInfo, listJob->allPosts() ) { + Item post; + post.setRemoteId( postInfo.id() ); + post.setMimeType( QLatin1String("text/x-vnd.akonadi.socialfeeditem") ); + post.setPayload( convertToSocialFeedItem( postInfo ) ); + postItems.append( post ); + } + + itemsRetrieved( postItems ); + itemsRetrievalDone(); + finishPostsFetching(); + } + + listJob->deleteLater(); +} + +void FacebookResource::finishPostsFetching() +{ + emit percent( 100 ); + emit status( Idle, i18n( "All posts fetched from server." ) ); + resetState(); +} + +void FacebookResource::postJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::PostJob * const postJob = dynamic_cast( job ); + Q_ASSERT( postJob ); + Q_ASSERT( postJob->postInfo().size() == 1 ); + mCurrentJobs.removeAll( job ); + + if ( postJob->error() ) { + abortWithError( i18n( "Unable to get information about post from server: %1", + postJob->errorText() ) ); + } else { + Item post = postJob->property( "Item" ).value(); + post.setPayload( convertToSocialFeedItem( postJob->postInfo().first() ) ); + itemRetrieved( post ); + resetState(); + } + + postJob->deleteLater(); +} + +SocialFeedItem FacebookResource::convertToSocialFeedItem( const KFbAPI::PostInfo &postinfo ) +{ + SocialFeedItem item; + item.setPostId( postinfo.id() ); + item.setPostText( postinfo.message().isEmpty() ? postinfo.story() : postinfo.message() ); + item.setPostLink( postinfo.link() ); + item.setPostLinkTitle( postinfo.name() ); + item.setPostImageUrl( postinfo.pictureUrl() ); + item.setPostTime( postinfo.createdTimeString(), QLatin1String( "%Y-%m-%dT%H:%M:%S%z" ) ); + + QString infoString; + + qlonglong commentsCount = postinfo.commentsMap().value( QLatin1String("count") ).toLongLong(); + qlonglong likesCount = postinfo.likesMap().value( QLatin1String("count") ).toLongLong(); + + if ( commentsCount > 0 && likesCount > 0 ) { + infoString = + i18nc( "This is a string putting together two previously translated strings, " + "resulting form is eg '1 comment, 3 likes'. " + "Main purpose of this is to give the ability to change the " + "comma delimiter to something more appropriate for some languages.", + "%1, %2", + formatI18nString( FacebookResource::FacebookComment, commentsCount ), + formatI18nString( FacebookResource::FacebookLike, likesCount ) ); + } else if ( commentsCount > 0 && likesCount == 0 ) { + infoString = formatI18nString( FacebookResource::FacebookComment, commentsCount ); + } else if ( commentsCount == 0 && likesCount > 0 ) { + infoString = formatI18nString( FacebookResource::FacebookLike, likesCount ); + } + +// QList replies; +// +// Q_FOREACH ( const QVariant &listItem, postinfo.commentsMap().value( "data" ).toList() ) { +// QVariantMap listItemMap = listItem.toMap(); +// Akonadi::PostReply postReply; +// postReply.replyText = listItemMap.value( "message" ).toString(); +// postReply.replyId = listItemMap.value( "id" ).toString(); +// postReply.postId = postinfo.id(); +// postReply.userId = listItemMap.value( "from" ).toMap().value( "id" ).toString(); +// postReply.userName = listItemMap.value( "from" ).toMap().value( "name" ).toString(); +// +// replies.append( postReply ); +// } +// +// item.setPostReplies( replies ); + + item.setPostInfo( infoString ); + + KFbAPI::UserInfo user = postinfo.from(); + + item.setUserId( user.id() ); + if ( user.username().isEmpty() ) { + item.setUserName( user.id() ); + } else { + item.setUserName( user.username() ); + } + item.setUserDisplayName( user.name() ); + item.setNetworkString( + i18nc( "This string is used in a sentence " + "'Some Name on Facebook: Just had lunch.', " + "so should be translated in such form. " + "This string is defined by the resource and " + "the whole sentence is composed in the UI.", + "on Facebook" ) ); + item.setAvatarUrl( + QString::fromLatin1( "https://graph.facebook.com/%1/picture?type=square" ).arg( user.id() ) ); + +// item.setItemSourceMap( QJson::QObjectHelper::qobject2qvariant( postinfo.data() ) ); + + return item; +} + +void FacebookResource::postAddJobFinished( KJob *job ) +{ + Q_ASSERT( !mIdle ); + Q_ASSERT( mCurrentJobs.indexOf( job ) != -1 ); + KFbAPI::PostAddJob * const addJob = dynamic_cast( job ); + Q_ASSERT( addJob ); + mCurrentJobs.removeAll( job ); + + if ( job->error() ) { + abortWithError( i18n( "Unable to post the status to server: %1", job->errorText() ) ); + } else { + Item post = addJob->property( "Item" ).value(); + //we fill in a random fake id to prevent duplicates - this post would be in the collection twice + //once the resource syncs again, filling a random id guarantees that this Item will be removed + //with the next sync and will be replaced by the real item from the server + post.setRemoteId( QLatin1String("non-existing-id") ); + changeCommitted( post ); + resetState(); + kDebug() << "Status posted to server"; + } + + addJob->deleteLater(); +} + +QString FacebookResource::formatI18nString( FormattingStringType type, int n ) +{ + switch ( type ) { + case FacebookResource::FacebookComment: + return i18ncp( "Text denoting how many comments given post have", + "1 comment", "%1 comments", n ); + case FacebookResource::FacebookLike: + return i18ncp( "Text denoting how many 'facebook likes' given post have", + "1 like", "%1 likes", n ); + } + + return QString(); +} diff --git a/kdepim-runtime/resources/facebook/icons/CMakeLists.txt b/kdepim-runtime/resources/facebook/icons/CMakeLists.txt new file mode 100644 index 00000000..02e3c9ec --- /dev/null +++ b/kdepim-runtime/resources/facebook/icons/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons(${ICON_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/facebook/icons/hi16-apps-facebookresource.png b/kdepim-runtime/resources/facebook/icons/hi16-apps-facebookresource.png new file mode 100644 index 0000000000000000000000000000000000000000..ba91caf1a40a675feaa0bb77a63af42a7c47ca64 GIT binary patch literal 698 zcmV;r0!96aP)@`q2Kx3j#sCCiw}lYOg-EgcY@_w-wQCFwjvXEudA=udmpM}|j2@$>JE1%Eym&Li zhnH`!I4J&?c#qw`bBT**hU@*O*&je5&P-7e5G@@=WQ$l(6a^4Df9faz@2BUPonK&a zWeqdMshXHeKnvaW zBaj!g);pN3*9xY-Eb(V^9i;4;6IDb#>O7)J2@wSmC<=-g5Zf+6%n)^m20(F$R+&9& z9x;zp%`5?(e*tQ>BZ3nIk&0@rEXQ0q6EP5x^70lybccg8=j6ojA<{Ih$PQ#nL8OR* zp?+5bPM;j6-ENa@ZZi3GN}QTXZ%>L@87QiC52_&~@!QXuCLR&+rsW{zSv!85o+C-S z5KB;(R4N3DKnxv>4!0jKK*;$#*Cga^+SvwqJBu=KF@Y g#7^abe7*JVFGdiwP6MO zpHzV$h2C=K$+t!1U1_yWRZl+g)X!OcVMVeAi2Nr`W3Ix5bI)>OWr>9&RYqguvoF8p z!;e?5UHSgIhkd!cS8zI>*WwY-Y*xZ_5Hm>WWSQr!S6}4V(PsWSQ^-nHoQ~(q<-LLj z5EWD%IG14Yk}0qm2^?6ycX0-KYp=(x{XSbe9Yo`q2u4t!A+7+TNCG2CISFcpVYHSS zdC;rBZ}QruRe}WwqjYu(EaDLL@qp+QaN3N_E3IY&iz(3lYoB_(0v70WIt)^|JiyLw z$L_n|gZh+;Cb5&UMCV4W$op@;kV+GuojrY=v!{;(@XmW*@cA#7EeGG&z8OZFd)(7( zQmYkHI{FXM>a&@;-)ZfgE;lx}gFh^V(i<3mw7XW zR1;isN3(!fz`_KSxFYel8Ky25la`J|rRovqqhqc*iLroLhzDmB$5Ay6GmZ7r=^S23 zxhNtMN6P|)F_Y5eTqI^_8UxNM1`(I)dsq@lsfihupi%zR$cZ2*#$nlVaG{XN;zAvl zRY5&E16Htrg)tUJgfOC7F6Mx8sYr9NfrTM9EU~eXNtl2ieDtxx^0A|FIJ~}`}N^( z>+9{I)*o=SCB$X1fugB-8mA#mg@rMfU7z8g4;J`wz0I}j8!$c~^lx(g*DLLQzdjT| zSI(S&+3CSWM*T|c$7B~zjTHei%O@nClvEzWFfI0mqr%0FwNF2g$;WhQ>5)pc+P%w< hX?3)<^;6Hx!rv_sSW${s*bD#w002ovPDHLkV1h%d*Qfvh literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/facebook/icons/hi32-apps-facebookresource.png b/kdepim-runtime/resources/facebook/icons/hi32-apps-facebookresource.png new file mode 100644 index 0000000000000000000000000000000000000000..d1dcb43824afaa60fdf0e334c1ef99c76a9698c6 GIT binary patch literal 1545 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 B$FTqa literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/facebook/icons/hisc-apps-facebookresource.sgvz b/kdepim-runtime/resources/facebook/icons/hisc-apps-facebookresource.sgvz new file mode 100644 index 0000000000000000000000000000000000000000..54a71dbb52e5d41dcce4ba94e9b2a9baf4cf5698 GIT binary patch literal 1478 zcmV;%1v&a3iwFP!000000L56@a@#f(ea~00^ouha!%ucop0y)i(Go`&uren>aY%R@KdXHoL#S zCrJyEyx7bxg6R}1%e&2+AP8`5R?ab4guMm#q88~AHCR)bjY>(IL2ZVXxK3P>;47sg`+c?>5t8SdE79lj{;X8~jSMj>^ zXoLHatS2x94dbZzSVXHN&Zas=1nIYO(Wx_qW1FEzo+%RSj=mQ^>1*$0}TyxG2 zs6T#qqR@tSTY0|b+ag{s!m}ee;nYEH(}Gu7Qo&NUWnBE}N&6$aD&t2|pNl9f*LiWh z2rK_Hg%h1mIVXl&DfmPZDk*fe7hy1KY4J;=^q7|AgTJaEj5*n9z1-odOc5t&kCtK) z3K&Z$!#c&+qZ_P8H}G5y)c{xYMO;b7|2tRLQB@=l=Tl0AvX(M6nKB}obxf-XehJ03 z;}buxxf5DWrkX0ExKVb(n3RM$#(SKeb~WLM8hDwe1kS{y7bs6LSo!(EK>b~hztF2XBjGW4PYz=An zx2*ZNdrp7J+ z{=6DTublw7GUl}s;Qw|b_bNBKocZ3U{qf*I;HHsn0OentH zXetCJj4Q`bLaB0uI)%d6zu*S-vzKuTl!MN}+Fnniofr_GY%!>bTl?-Gs0sx&IIxZ&7s8`E8M&pFQqiF6!AXk`P!${xxob znty4B)B4g#-23^dw=K%$yvT2}RUiB-PqHC;ouK5U3I5FG9$Q7_HY$o}H_z~{*rQ!M z_Y%b@`$`kaX}??T=pUQ&{0m|=HQGG3gAo>~R~;Kd5H6fHM}{zzIAvYmK4Z>DeJT^W zb-w**iq4!_SQpO689|@xw7VFyspRxoo7UW&upe8pHJ3yCG2he*i-zdOnDK4X?livF zHDedgo7WMiML$(`=C#CVCycg7=H*9b<;Uh^cq%EOhvw8w6Ety*jmFg=IFD>V2^wvQ zn$8 zz$Vhxs5N+12b^0ZJ0%3L`Ymc}NLeb`L|Kg|CtjlMmXsPQPoWO46)BtJu><_ys1qoZ zL<{MZ_9%0NQ}nPWP``t~%Tm-RUY8t6;1mkY5SvD|J0M$F*+W4Avv^2x2V%7UWq{Tc zNaj>cA=Y5mF3c=YIWMB@c5Ii{v7+(m)oj( zg&$Yc-DL gzE|mZ>_wNeP1E+_&!zv*-n%#d08BRxQ=k+80EMFNJpcdz literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/facebook/serializer/CMakeLists.txt b/kdepim-runtime/resources/facebook/serializer/CMakeLists.txt new file mode 100644 index 00000000..6842215e --- /dev/null +++ b/kdepim-runtime/resources/facebook/serializer/CMakeLists.txt @@ -0,0 +1,36 @@ +project(facebook-notifications-serializer) + +include_directories(${KDE4_INCLUDES} ${KDEPIMLIBS_INCLUDE_DIRS}) + +set(akonadi_serializer_socialnotification_SRCS + akonadi_serializer_socialnotification.cpp +) + +kde4_add_plugin(akonadi_serializer_socialnotification ${akonadi_serializer_socialnotification_SRCS}) + +target_link_libraries(akonadi_serializer_socialnotification + ${KDE4_AKONADI_LIBS} + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${LibKFbAPI_LIBRARY} + ${qjson_LIBRARIES} # for Debian + ${QJSON_LIBRARIES} + ${QJSON_LIBRARY} +) + +install( + TARGETS akonadi_serializer_socialnotification + DESTINATION ${PLUGIN_INSTALL_DIR} +) + +install( + FILES akonadi_serializer_socialnotification.desktop + DESTINATION ${DATA_INSTALL_DIR}/akonadi/plugins/serializer +) + +install( + FILES x-vnd.akonadi.socialnotification.xml + DESTINATION ${XDG_MIME_INSTALL_DIR} +) + +update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.cpp b/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.cpp new file mode 100644 index 00000000..6d7a3716 --- /dev/null +++ b/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2012 Martin Klapetek + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "akonadi_serializer_socialnotification.h" + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +using namespace Akonadi; + +bool SerializerPluginSocialNotification::deserialize( Item &item, const QByteArray &label, + QIODevice &data, int version ) +{ + Q_UNUSED( version ) + + if ( label != Item::FullPayload ) { + return false; + } + + KFbAPI::NotificationInfo object; + + //FIXME: Use QJson::QObjectHelper::qvariant2qobject( item.toMap(), postInfo.data() ); + QJson::Parser parser; + QVariantMap map = parser.parse( data.readAll() ).toMap(); + + object.setId( map[QLatin1String("id")].toString() ); + object.setFrom( map[QLatin1String("from")].toMap() ); + object.setTo( map[QLatin1String("to")].toMap() ); + object.setCreatedTimeString( map[QLatin1String("created_time")].toString() ); + object.setUpdatedTimeString( map[QLatin1String("updated_time")].toString() ); + object.setTitle( map[QLatin1String("title")].toString() ); + object.setMessage( map[QLatin1String("message")].toString() ); + object.setLink( map[QLatin1String("link")].toUrl() ); + object.setApplication( map[QLatin1String("application")].toMap() ); + object.setUnread( map[QLatin1String("unread")].toBool() ); + + item.setMimeType( QLatin1String("text/x-vnd.akonadi.socialnotification") ); + item.setPayload< KFbAPI::NotificationInfo >( object ); + + return true; +} + +void SerializerPluginSocialNotification::serialize( const Item &item, const QByteArray &label, + QIODevice &data, int &version ) +{ + Q_UNUSED( label ) + Q_UNUSED( version ) + + if ( !item.hasPayload< KFbAPI::NotificationInfo >() ) { + return; + } + + KFbAPI::NotificationInfo notificationInfo = item.payload< KFbAPI::NotificationInfo >(); + + QVariantMap map; + + map[QLatin1String("id")] = notificationInfo.id(); + + QVariantMap fromMap; + if ( !notificationInfo.from().id().isEmpty() ) { + fromMap[QLatin1String("name")] = notificationInfo.from().name(); + fromMap[QLatin1String("id")] = notificationInfo.from().id(); + } + + map[QLatin1String("from")] = fromMap; + + QVariantMap toMap; + if ( !notificationInfo.to().id().isEmpty() ) { + toMap[QLatin1String("name")] = notificationInfo.to().name(); + toMap[QLatin1String("id")] = notificationInfo.to().id(); + } + + map[QLatin1String("to")] = toMap; + map[QLatin1String("created_time")] = notificationInfo.createdTimeString(); + map[QLatin1String("updated_time")] = notificationInfo.updatedTimeString(); + map[QLatin1String("title")] = notificationInfo.title(); + map[QLatin1String("message")] = notificationInfo.message(); + map[QLatin1String("link")] = notificationInfo.link(); + + QVariantMap appMap; + if ( !notificationInfo.application().id().isEmpty() ) { + appMap[QLatin1String("name")] = notificationInfo.application().name(); + appMap[QLatin1String("id")] = notificationInfo.application().id(); + } + + map[QLatin1String("app")] = appMap; + map[QLatin1String("unread")] = notificationInfo.unread(); + + QJson::Serializer serializer; + + data.write( serializer.serialize( map ) ); +} + +Q_EXPORT_PLUGIN2( akonadi_serializer_socialnotification, + Akonadi::SerializerPluginSocialNotification ) diff --git a/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.desktop b/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.desktop new file mode 100644 index 00000000..86a38bc6 --- /dev/null +++ b/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.desktop @@ -0,0 +1,84 @@ +[Misc] +Name=Facebook Notification Serializer +Name[bs]=Serializator Facebook poruka +Name[ca]=Serialitzador de notificacions de Facebook +Name[ca@valencia]=Serialitzador de notificacions de Facebook +Name[cs]=ŘadiÄ upomínek Facebooku +Name[da]=Facebook-notifikation-serializer +Name[de]=Facebook-Benachrichtigungs-Serialisierung +Name[el]=ΣειÏιακοποιητής ειδοποιήσεων Facebook +Name[en_GB]=Facebook Notification Serializer +Name[es]=Serializador de notificaciones de Facebook +Name[et]=Facebooki märguannete jadasti +Name[fi]=Facebookin ilmoitusten serialisoija +Name[fr]=Sérialiseur de notifications Facebook +Name[gl]=Serialización de notificacións do Facebook +Name[hu]=Facebook értesítés leképezÅ‘ +Name[ia]=Facebook Notification Serializer (Divulgator partial de notification de Facebook) +Name[it]=Serializzatore di notifiche da Facebook +Name[kk]=Facebook құлақтандыруды тізбектеуі +Name[ko]=Facebook 알림 시리얼ë¼ì´ì € +Name[lt]=Facebook praneÅ¡imų serializatorius +Name[nb]=Facebook varslings-serialisator +Name[nds]=Facebook-Bescheed-Reegmoduul +Name[nl]=Serialisator van Facebook-meldingen +Name[pl]=Program szeregujÄ…cy powiadomienia na Facebooku +Name[pt]=Serialização das Notificações do Facebook +Name[pt_BR]=Serialização das notificações do Facebook +Name[ru]=Сохранение уведомлений Facebook +Name[sk]=Serializátor upozornení Facebook +Name[sr]=Серијализатор обавештења Ñа ФејÑбука +Name[sr@ijekavian]=Серијализатор обавештења Ñа ФејÑбука +Name[sr@ijekavianlatin]=Serijalizator obaveÅ¡tenja sa Facebooka +Name[sr@latin]=Serijalizator obaveÅ¡tenja sa Facebooka +Name[sv]=Serialisering av underrättelser frÃ¥n Facebook +Name[tr]=Facebook Bildirim Sıralandırıcısı +Name[ug]=ÙÛيىسبۇك ئۇقتۇرۇشىنى تىزغۇچ +Name[uk]=Серіалізатор Ñповіщень Facebook +Name[x-test]=xxFacebook Notification Serializerxx +Name[zh_CN]=Facebook 通知åºåˆ—化器 +Name[zh_TW]=Facebook 通知åºåˆ—器 +Comment=An Akonadi serializer plugin for Facebook notifications +Comment[bs]=Akonadi dodatak serializatora za Facebook poruke +Comment[ca]=Un connector de serialització de l'Akonadi per a les notificacions de Facebook +Comment[ca@valencia]=Un connector de serialització de l'Akonadi per a les notificacions de Facebook +Comment[cs]=Modul Akonadi pro seÅ™azování upomínek Facebooku +Comment[da]=Et Akonadi-serializerplugin til Facebook-notifikationer +Comment[de]=Akonadi-Modul zur Serialisierung von Facebook-Benachrichtigungen +Comment[el]=Ένα Ï€Ïόσθετο σειÏιακοποιητή Akonadi για ειδοποιήσεις στο Facebook +Comment[en_GB]=An Akonadi serializer plugin for Facebook notifications +Comment[es]=Un complemento serializador de Akonadi para notificaciones de Facebook +Comment[et]=Akonadi Facebooki märguannete jadastamisplugin +Comment[fi]=Akonadi-serialisoijaliitännäinen Facebookin ilmoituksille +Comment[fr]=Un module externe Akonadi pour la sérialisation des notifications Facebook +Comment[gl]=Un complemento de serialización do Akonadi para notificacións do Facebook +Comment[hu]=Akonadi-leképezÅ‘ modul a Facebook értesítésekhez +Comment[ia]=Un plug-in pro divulgator partial de Akonadi pro notificationes de Facebook +Comment[it]=Un'estensione di Akonadi per la serializzazione delle notifiche di Facebook +Comment[kk]=Akonadi-дің Facebook құлақтандырулар тізбектеуіш плагині +Comment[ko]=Facebook ì•Œë¦¼ì„ ìœ„í•œ Akonadi 시리얼ë¼ì´ì € í”ŒëŸ¬ê·¸ì¸ +Comment[lt]=Akonadi serializatoriaus įskiepis Facebook praneÅ¡imams +Comment[nb]=Et Akonadi programtillegg for serialisering av Facebook-varslinger +Comment[nds]=Akonadi-Inreegmoduul för Facebook-Bescheden +Comment[nl]=Een Akonadi serialisatorplug-in voor Facebook-meldingen +Comment[pl]=Wtyczka Akonadi do szeregowania powiadomieÅ„ Facebooka +Comment[pt]=Um 'plugin' de serialização do Akonadi para as notificações do Facebook +Comment[pt_BR]=Plugin de serialização do Akonadi para as notificações do Facebook +Comment[ru]=Модуль ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹ Facebook Ð´Ð»Ñ Akonadi +Comment[sk]=Plugin serializátora Akonadi pre objekty Facebook +Comment[sr]=Ðконадијев прикључак Ñеријализатора за обавештења Ñа ФејÑбука +Comment[sr@ijekavian]=Ðконадијев прикључак Ñеријализатора за обавештења Ñа ФејÑбука +Comment[sr@ijekavianlatin]=Akonadijev prikljuÄak serijalizatora za obaveÅ¡tenja sa Facebooka +Comment[sr@latin]=Akonadijev prikljuÄak serijalizatora za obaveÅ¡tenja sa Facebooka +Comment[sv]=Ett Akonadi serialiseringsinsticksprogram för underrättelser frÃ¥n Facebook +Comment[tr]=Facebook bildirileri için bir Akonadi sıralandırıcı eklentisi +Comment[uk]=Додаток Ñеріалізації даних Akonadi Ð´Ð»Ñ Ñповіщень Facebook +Comment[x-test]=xxAn Akonadi serializer plugin for Facebook notificationsxx +Comment[zh_CN]=对 Facebook æ醒进行åºåˆ—转æ¢çš„ Akonadi æ’件 +Comment[zh_TW]=Facebook 通知的 Akonadi åºåˆ—å™¨å¤–æŽ›ç¨‹å¼ + +[Plugin] +Type=text/x-vnd.akonadi.socialnotification +X-Akonadi-Class=KFbAPI::NotificationInfo; +X-KDE-Library=akonadi_serializer_socialnotification +X-KDE-ClassName=Akonadi::SerializerPluginSocialNotification diff --git a/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.h b/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.h new file mode 100644 index 00000000..04811403 --- /dev/null +++ b/kdepim-runtime/resources/facebook/serializer/akonadi_serializer_socialnotification.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2012 Martin Klapetek + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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_SERIALIZER_SOCIALNOTIFICATION_H +#define AKONADI_SERIALIZER_SOCIALNOTIFICATION_H + +#include +#include + +namespace Akonadi { + +class SerializerPluginSocialNotification : public QObject, public ItemSerializerPlugin +{ + Q_OBJECT + Q_INTERFACES( Akonadi::ItemSerializerPlugin ) + + public: + bool deserialize( Item &item, const QByteArray &label, QIODevice &data, int version ); + void serialize( const Item &item, const QByteArray &label, QIODevice &data, int &version ); +}; + +} + +#endif // AKONADI_SERIALIZER_SOCIALNOTIFICATION_H diff --git a/kdepim-runtime/resources/facebook/serializer/x-vnd.akonadi.socialnotification.xml b/kdepim-runtime/resources/facebook/serializer/x-vnd.akonadi.socialnotification.xml new file mode 100644 index 00000000..b146a553 --- /dev/null +++ b/kdepim-runtime/resources/facebook/serializer/x-vnd.akonadi.socialnotification.xml @@ -0,0 +1,8 @@ + + + + + + Akonadi Social Notification + + \ No newline at end of file diff --git a/kdepim-runtime/resources/facebook/settings.cpp b/kdepim-runtime/resources/facebook/settings.cpp new file mode 100644 index 00000000..d7236bd9 --- /dev/null +++ b/kdepim-runtime/resources/facebook/settings.cpp @@ -0,0 +1,82 @@ +/* + Copyright 2010 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "settings.h" +#include "settingsadaptor.h" + + +class SettingsHelper +{ + public: + SettingsHelper() : q( 0 ) {} + ~SettingsHelper() { + delete q; + q = 0; + } + Settings *q; +}; + +K_GLOBAL_STATIC( SettingsHelper, s_globalSettings ) + +Settings *Settings::self() +{ + if ( !s_globalSettings->q ) { + new Settings; + s_globalSettings->q->readConfig(); + } + return s_globalSettings->q; +} + +Settings::Settings() +{ + Q_ASSERT( !s_globalSettings->q ); + s_globalSettings->q = this; + + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( + QLatin1String( "/Settings" ), + this, + QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); +} + +void Settings::setWindowId( WId id ) +{ + mWinId = id; +} + +void Settings::setResourceId( const QString &resourceIdentifier ) +{ + mResourceId = resourceIdentifier; +} + +QString Settings::apiKey() const +{ + return QLatin1String("9c41511dae89d7dfb4cde6be07365475"); +} + +QString Settings::appID() const +{ + return QLatin1String("175243235841602"); +} + +QString Settings::appSecret() const +{ + return QLatin1String("57b6eadd130bb9ecff7dcb701558833d"); +} diff --git a/kdepim-runtime/resources/facebook/settings.h b/kdepim-runtime/resources/facebook/settings.h new file mode 100644 index 00000000..9ed9ab1f --- /dev/null +++ b/kdepim-runtime/resources/facebook/settings.h @@ -0,0 +1,46 @@ +/* + Copyright 2010 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 FACEBOOK_SETTINGS_H +#define FACEBOOK_SETTINGS_H + +#include "settingsbase.h" + +#include + +class Settings : public SettingsBase +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.Facebook.ExtendedSettings" ) + public: + Settings(); + void setWindowId( WId id ); + void setResourceId( const QString &resourceIdentifier ); + static Settings *self(); + + QString appID() const; + QString apiKey() const; + QString appSecret() const; + + private: + WId mWinId; + QString mResourceId; +}; + +#endif diff --git a/kdepim-runtime/resources/facebook/settingsbase.kcfg b/kdepim-runtime/resources/facebook/settingsbase.kcfg new file mode 100644 index 00000000..8e0036d9 --- /dev/null +++ b/kdepim-runtime/resources/facebook/settingsbase.kcfg @@ -0,0 +1,26 @@ + + + + + + + + + + 2000-01-01 + + + + + + + + + true + + + diff --git a/kdepim-runtime/resources/facebook/settingsbase.kcfgc b/kdepim-runtime/resources/facebook/settingsbase.kcfgc new file mode 100644 index 00000000..b03757f5 --- /dev/null +++ b/kdepim-runtime/resources/facebook/settingsbase.kcfgc @@ -0,0 +1,7 @@ +File=settingsbase.kcfg +ClassName=SettingsBase +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +GlobalEnums=true diff --git a/kdepim-runtime/resources/facebook/settingsdialog.cpp b/kdepim-runtime/resources/facebook/settingsdialog.cpp new file mode 100644 index 00000000..b484b4b8 --- /dev/null +++ b/kdepim-runtime/resources/facebook/settingsdialog.cpp @@ -0,0 +1,226 @@ +/* + Copyright 2010 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "settingsdialog.h" +#include "facebookresource.h" +#include "settings.h" +#include "akonadi-version.h" + +#include +#include + +#include +#include +#include + +using namespace Akonadi; + +SettingsDialog::SettingsDialog( FacebookResource *parentResource, WId parentWindow ) + : KDialog(), + mParentResource( parentResource ), + mTriggerSync( false ) +{ + KWindowSystem::setMainWindow( this, parentWindow ); + setButtons( Ok|Cancel|User1 ); + setButtonText( User1, i18n( "About" ) ); + setButtonIcon( User1, KIcon( QLatin1String("help-about") ) ); + setWindowIcon( KIcon( QLatin1String("facebookresource") ) ); + setWindowTitle( i18n( "Facebook Settings" ) ); + + setupWidgets(); + loadSettings(); +} + +SettingsDialog::~SettingsDialog() +{ + if ( mTriggerSync ) { + mParentResource->synchronize(); + } +} + +void SettingsDialog::setupWidgets() +{ + QWidget * const page = new QWidget( this ); + setupUi( page ); + setMainWidget( page ); + updateAuthenticationWidgets(); + updateUserName(); + connect( resetButton, SIGNAL(clicked(bool)), this, SLOT(resetAuthentication()) ); + connect( authenticateButton, SIGNAL(clicked(bool)), this, SLOT(showAuthenticationDialog()) ); +} + +void SettingsDialog::showAuthenticationDialog() +{ + QStringList permissions; + permissions << QLatin1String("offline_access") + << QLatin1String("friends_birthday") + << QLatin1String("friends_website") + << QLatin1String("friends_location") + << QLatin1String("friends_work_history") + << QLatin1String("friends_relationships") + << QLatin1String("manage_notifications") + << QLatin1String("publish_actions") + << QLatin1String("read_stream") + << QLatin1String("user_events") + << QLatin1String("user_notes"); + KFbAPI::AuthenticationDialog * const authDialog = new KFbAPI::AuthenticationDialog( this ); + authDialog->setAppId( Settings::self()->appID() ); + authDialog->setPermissions( permissions ); + connect( authDialog, SIGNAL(authenticated(QString)), + this, SLOT(authenticationDone(QString)) ); + connect( authDialog, SIGNAL(canceled()), + this, SLOT(authenticationCanceled()) ); + authenticateButton->setEnabled( false ); + authDialog->start(); +} + +void SettingsDialog::authenticationCanceled() +{ + authenticateButton->setEnabled( true ); +} + +void SettingsDialog::authenticationDone( const QString &accessToken ) +{ + if ( Settings::self()->accessToken() != accessToken && !accessToken.isEmpty() ) { + mTriggerSync = true; + } + Settings::self()->setAccessToken( accessToken ); + updateAuthenticationWidgets(); + updateUserName(); +} + +void SettingsDialog::updateAuthenticationWidgets() +{ + if ( Settings::self()->accessToken().isEmpty() ) { + authenticationStack->setCurrentIndex( 0 ); + } else { + authenticationStack->setCurrentIndex( 1 ); + if ( Settings::self()->userName().isEmpty() ) { + authenticationLabel->setText( i18n( "Authenticated." ) ); + } else { + authenticationLabel->setText( i18n( "Authenticated as %1.", + Settings::self()->userName() ) ); + } + } +} + +void SettingsDialog::resetAuthentication() +{ + Settings::self()->setAccessToken( QString() ); + Settings::self()->setUserName( QString() ); + updateAuthenticationWidgets(); +} + +void SettingsDialog::updateUserName() +{ + if ( Settings::self()->userName().isEmpty() && ! Settings::self()->accessToken().isEmpty() ) { + KFbAPI::UserInfoJob * const job = + new KFbAPI::UserInfoJob( Settings::self()->accessToken(), this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(userInfoJobDone(KJob*)) ); + job->start(); + } +} + +void SettingsDialog::userInfoJobDone( KJob *job ) +{ + KFbAPI::UserInfoJob * const userInfoJob = dynamic_cast( job ); + Q_ASSERT( userInfoJob ); + if ( !userInfoJob->error() ) { + Settings::self()->setUserName( userInfoJob->userInfo().name() ); + updateAuthenticationWidgets(); + } else { + kWarning() << "Can't get user info: " << userInfoJob->errorText(); + } +} + +void SettingsDialog::loadSettings() +{ + if ( mParentResource->name() == mParentResource->identifier() ) { + mParentResource->setName( i18n( "Facebook" ) ); + } + + nameEdit->setText( mParentResource->name() ); + nameEdit->setFocus(); + enableNotificationsCheckBox->setChecked( Settings::self()->displayNotifications() ); + +} + +void SettingsDialog::saveSettings() +{ + mParentResource->setName( nameEdit->text() ); + Settings::self()->setDisplayNotifications( enableNotificationsCheckBox->isChecked() ); + if ( !Settings::self()->accountId() ) { + QStringList services; + services << QLatin1String("facebook-contacts") + << QLatin1String("facebook-feed") + << QLatin1String("facebook-events") + << QLatin1String("facebook-notes") + << QLatin1String("facebook-notifications"); + Settings::self()->setAccountServices(services); + } + Settings::self()->writeConfig(); +} + +void SettingsDialog::slotButtonClicked( int button ) +{ + switch( button ) { + case Ok: + saveSettings(); + accept(); + break; + case Cancel: + reject(); + return; + case User1: + { + KAboutData aboutData( + QByteArray( "akonadi_facebook_resource" ), + QByteArray(), + ki18n( "Akonadi Facebook Resource" ), + QByteArray( AKONADI_VERSION ), + ki18n( "Makes your friends, events, notes, posts and messages on Facebook " + "available in KDE via Akonadi." ), + KAboutData::License_GPL_V2, + ki18n( "Copyright (C) 2010,2011,2012,2013 Akonadi Facebook Resource Developers" ) ); + + aboutData.addAuthor( ki18n( "Martin Klapetek" ), + ki18n( "Developer" ), "mklapetek@kde.org" ); + + aboutData.addAuthor( ki18n( "Thomas McGuire" ), + ki18n( "Past Maintainer" ), "mcguire@kde.org" ); + + aboutData.addAuthor( ki18n( "Roeland Jago Douma" ), + ki18n( "Past Developer" ), "unix@rullzer.com" ); + + aboutData.addCredit( ki18n( "Till Adam" ), + ki18n( "MacOS Support" ), "adam@kde.org" ); + + aboutData.setProgramIconName( QLatin1String("facebookresource") ); + aboutData.setTranslator( ki18nc( "NAME OF TRANSLATORS", "Your names" ), + ki18nc( "EMAIL OF TRANSLATORS", "Your emails" ) ); + + KAboutApplicationDialog *dialog = new KAboutApplicationDialog( &aboutData, this ); + dialog->setAttribute( Qt::WA_DeleteOnClose, true ); + dialog->show(); + break; + } + } +} + diff --git a/kdepim-runtime/resources/facebook/settingsdialog.h b/kdepim-runtime/resources/facebook/settingsdialog.h new file mode 100644 index 00000000..e9ec88f4 --- /dev/null +++ b/kdepim-runtime/resources/facebook/settingsdialog.h @@ -0,0 +1,55 @@ +/* + Copyright 2010 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 FACEBOOK_SETTINGSDIALOG_H +#define FACEBOOK_SETTINGSDIALOG_H + +#include "ui_settingsdialog.h" + +class FacebookResource; +class KJob; + +class SettingsDialog : public KDialog, private Ui::SettingsDialog +{ + Q_OBJECT + + public: + SettingsDialog( FacebookResource *parentResource, WId parentWindow ); + ~SettingsDialog(); + + private slots: + virtual void slotButtonClicked( int button ); + void resetAuthentication(); + void showAuthenticationDialog(); + void authenticationDone( const QString &accessToken ); + void authenticationCanceled(); + void userInfoJobDone( KJob *job ); + + private: + void setupWidgets(); + void loadSettings(); + void saveSettings(); + void updateAuthenticationWidgets(); + void updateUserName(); + + FacebookResource *mParentResource; + bool mTriggerSync; +}; + +#endif diff --git a/kdepim-runtime/resources/facebook/settingsdialog.ui b/kdepim-runtime/resources/facebook/settingsdialog.ui new file mode 100644 index 00000000..68b5687f --- /dev/null +++ b/kdepim-runtime/resources/facebook/settingsdialog.ui @@ -0,0 +1,181 @@ + + + SettingsDialog + + + + 0 + 0 + 427 + 132 + + + + + + + + + Your Internet Service Provider gave you a <em>user name</em> which is used to authenticate you with their servers. It usually is the first part of your email address (the part before <em>@</em>). + + + Account &name: + + + nameEdit + + + + + + + Name displayed in the list of accounts + + + Account name: This defines the name displayed in the account list. + + + + + + + + + 1 + + + + + 0 + + + + + + + Not yet authenticated with Facebook. + + + + + + + Authenticate... + + + + + + + + + + true + + + + In order to access your Facebook data in KDE, you need to authenticate this application with Facebook. To do so, click the above button and follow the instructions on the screen. + + + true + + + + + + + Qt::Vertical + + + + 20 + 54 + + + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + TextLabel + + + + + + + Reset + + + + + + + Display Facebook notifications: + + + + + + + Enabled + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 102 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 34 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/resources/facebook/timestampattribute.cpp b/kdepim-runtime/resources/facebook/timestampattribute.cpp new file mode 100644 index 00000000..0d2adfbe --- /dev/null +++ b/kdepim-runtime/resources/facebook/timestampattribute.cpp @@ -0,0 +1,58 @@ +/* + Copyright 2011 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "timestampattribute.h" + +TimeStampAttribute::TimeStampAttribute() +{ +} + +Akonadi::Attribute *TimeStampAttribute::clone() const +{ + TimeStampAttribute * const attribute = new TimeStampAttribute(); + attribute->setTimeStamp( mTimeStamp ); + return attribute; +} + +void TimeStampAttribute::deserialize( const QByteArray &data ) +{ + const QString timeStamp = QString::fromUtf8( data ); + mTimeStamp = KDateTime::fromString( timeStamp ); +} + +QByteArray TimeStampAttribute::serialized() const +{ + return mTimeStamp.toString().toUtf8(); +} + +QByteArray TimeStampAttribute::type() const +{ + return "FACEBOOK_TIMESTAMP"; +} + +void TimeStampAttribute::setTimeStamp( const KDateTime &dateTime ) +{ + mTimeStamp = dateTime; +} + +KDateTime TimeStampAttribute::timeStamp() const +{ + return mTimeStamp; +} diff --git a/kdepim-runtime/resources/facebook/timestampattribute.h b/kdepim-runtime/resources/facebook/timestampattribute.h new file mode 100644 index 00000000..4b64693d --- /dev/null +++ b/kdepim-runtime/resources/facebook/timestampattribute.h @@ -0,0 +1,40 @@ +/* + Copyright 2011 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 FACEBOOK_TIMESTAMPATTRIBUTE_H +#define FACEBOOK_TIMESTAMPATTRIBUTE_H + +#include +#include + +class TimeStampAttribute : public Akonadi::Attribute +{ + public: + TimeStampAttribute(); + virtual Attribute * clone() const; + virtual void deserialize( const QByteArray &data ); + virtual QByteArray serialized() const; + virtual QByteArray type() const; + void setTimeStamp( const KDateTime &dateTime ); + KDateTime timeStamp() const; + private: + KDateTime mTimeStamp; +}; + +#endif diff --git a/kdepim-runtime/resources/folderarchivesettings/CMakeLists.txt b/kdepim-runtime/resources/folderarchivesettings/CMakeLists.txt new file mode 100644 index 00000000..6d643b8a --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/CMakeLists.txt @@ -0,0 +1,31 @@ +project(folderarchivesettings) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${KDEPIMLIBS_INCLUDE_DIR} +) + +include(KDE4Defaults) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +set(folderarchivesettings_SRCS + folderarchivesettingpage.cpp + folderarchiveutil.cpp + folderarchiveaccountinfo.cpp +) + +kde4_add_library(folderarchivesettings ${LIBRARY_TYPE} ${folderarchivesettings_SRCS} ) + +target_link_libraries(folderarchivesettings + ${KDEPIMLIBS_KMIME_LIBS} + ${AKONADI_COMMON_LIBRARIES} + ${QT_QTGUI_LIBRARY} + ${KDEPIMLIBS_AKONADI_LIBS} +) + +set_target_properties(folderarchivesettings PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) + +install(TARGETS folderarchivesettings ${INSTALL_TARGETS_DEFAULT_ARGS}) +add_subdirectory(autotests) + diff --git a/kdepim-runtime/resources/folderarchivesettings/Messages.sh b/kdepim-runtime/resources/folderarchivesettings/Messages.sh new file mode 100755 index 00000000..800a8b3c --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/libfolderarchivesettings.pot diff --git a/kdepim-runtime/resources/folderarchivesettings/autotests/CMakeLists.txt b/kdepim-runtime/resources/folderarchivesettings/autotests/CMakeLists.txt new file mode 100644 index 00000000..b196e7c6 --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/autotests/CMakeLists.txt @@ -0,0 +1,10 @@ + +# Convenience macro to add unit tests. +macro( folderarchive_kmail _source ) + set( _test ${_source} ../folderarchiveaccountinfo.cpp ) + get_filename_component( _name ${_source} NAME_WE ) + kde4_add_unit_test( ${_name} TESTNAME folderarchive-${_name} ${_test} ) + target_link_libraries( ${_name} ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDEPIMLIBS_AKONADI_LIBS}) +endmacro() + +folderarchive_kmail(folderarchiveaccountinfotest.cpp) diff --git a/kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.cpp b/kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.cpp new file mode 100644 index 00000000..d01cc176 --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.cpp @@ -0,0 +1,74 @@ +/* + Copyright (c) 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "folderarchiveaccountinfotest.h" +#include "../folderarchiveaccountinfo.h" +#include +#include +#include + + +FolderArchiveAccountInfoTest::FolderArchiveAccountInfoTest(QObject *parent) + : QObject(parent) +{ + +} + +FolderArchiveAccountInfoTest::~FolderArchiveAccountInfoTest() +{ + +} + +void FolderArchiveAccountInfoTest::shouldHaveDefaultValue() +{ + FolderArchiveAccountInfo info; + QVERIFY(info.instanceName().isEmpty()); + QCOMPARE(info.archiveTopLevel(), Akonadi::Collection(-1).id()); + QCOMPARE(info.folderArchiveType(), FolderArchiveAccountInfo::UniqueFolder); + QCOMPARE(info.enabled(), false); + QCOMPARE(info.keepExistingStructure(), false); + QCOMPARE(info.isValid(), false); + +} + +void FolderArchiveAccountInfoTest::shouldBeValid() +{ + FolderArchiveAccountInfo info; + QVERIFY(!info.isValid()); + info.setArchiveTopLevel(Akonadi::Collection(42).id()); + QVERIFY(!info.isValid()); + info.setInstanceName(QLatin1String("FOO")); + QVERIFY(info.isValid()); +} + +void FolderArchiveAccountInfoTest::shouldRestoreFromSettings() +{ + FolderArchiveAccountInfo info; + info.setInstanceName(QLatin1String("FOO1")); + info.setArchiveTopLevel(Akonadi::Collection(42).id()); + info.setFolderArchiveType(FolderArchiveAccountInfo::FolderByMonths); + info.setEnabled(true); + info.setKeepExistingStructure(true); + + KConfigGroup grp(KGlobal::config(), "testsettings"); + info.writeConfig(grp); + + FolderArchiveAccountInfo restoreInfo(grp); + QCOMPARE(info, restoreInfo); +} + +QTEST_KDEMAIN(FolderArchiveAccountInfoTest, NoGUI) diff --git a/kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.h b/kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.h new file mode 100644 index 00000000..c0fa2192 --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/autotests/folderarchiveaccountinfotest.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef FOLDERARCHIVEACCOUNTINFOTEST_H +#define FOLDERARCHIVEACCOUNTINFOTEST_H + +#include + + +class FolderArchiveAccountInfoTest : public QObject +{ + Q_OBJECT +public: + explicit FolderArchiveAccountInfoTest(QObject *parent = 0); + ~FolderArchiveAccountInfoTest(); + +private Q_SLOTS: + void shouldHaveDefaultValue(); + void shouldBeValid(); + void shouldRestoreFromSettings(); +}; + + + +#endif // FOLDERARCHIVEACCOUNTINFOTEST_H + diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.cpp b/kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.cpp new file mode 100644 index 00000000..e02621aa --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.cpp @@ -0,0 +1,127 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "folderarchiveaccountinfo.h" + +#include + +FolderArchiveAccountInfo::FolderArchiveAccountInfo() + : mArchiveType(UniqueFolder), + mArchiveTopLevelCollectionId(-1), + mEnabled(false), + mKeepExistingStructure(false) +{ +} + +FolderArchiveAccountInfo::FolderArchiveAccountInfo(const KConfigGroup &config) + : mArchiveType(UniqueFolder), + mArchiveTopLevelCollectionId(-1), + mEnabled(false), + mKeepExistingStructure(false) +{ + readConfig(config); +} + +FolderArchiveAccountInfo::~FolderArchiveAccountInfo() +{ +} + +bool FolderArchiveAccountInfo::isValid() const +{ + return (mArchiveTopLevelCollectionId > -1) && (!mInstanceName.isEmpty()); +} + +void FolderArchiveAccountInfo::setFolderArchiveType(FolderArchiveAccountInfo::FolderArchiveType type) +{ + mArchiveType = type; +} + +FolderArchiveAccountInfo::FolderArchiveType FolderArchiveAccountInfo::folderArchiveType() const +{ + return mArchiveType; +} + +void FolderArchiveAccountInfo::setArchiveTopLevel(Akonadi::Collection::Id id) +{ + mArchiveTopLevelCollectionId = id; +} + +Akonadi::Collection::Id FolderArchiveAccountInfo::archiveTopLevel() const +{ + return mArchiveTopLevelCollectionId; +} + +QString FolderArchiveAccountInfo::instanceName() const +{ + return mInstanceName; +} + +void FolderArchiveAccountInfo::setInstanceName(const QString &instance) +{ + mInstanceName = instance; +} + +void FolderArchiveAccountInfo::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + +bool FolderArchiveAccountInfo::enabled() const +{ + return mEnabled; +} + +void FolderArchiveAccountInfo::setKeepExistingStructure(bool b) +{ + mKeepExistingStructure = b; +} + +bool FolderArchiveAccountInfo::keepExistingStructure() const +{ + return mKeepExistingStructure; +} + +void FolderArchiveAccountInfo::readConfig(const KConfigGroup &config) +{ + mInstanceName = config.readEntry(QLatin1String("instanceName")); + mArchiveTopLevelCollectionId = config.readEntry(QLatin1String("topLevelCollectionId"), -1); + mArchiveType = static_cast(config.readEntry("folderArchiveType", (int)UniqueFolder)); + mEnabled = config.readEntry("enabled", false); + mKeepExistingStructure = config.readEntry("keepExistingStructure", false); +} + +void FolderArchiveAccountInfo::writeConfig(KConfigGroup &config ) +{ + config.writeEntry(QLatin1String("instanceName"), mInstanceName); + if (mArchiveTopLevelCollectionId>-1) + config.writeEntry(QLatin1String("topLevelCollectionId"), mArchiveTopLevelCollectionId); + else + config.deleteEntry(QLatin1String("topLevelCollectionId")); + + config.writeEntry(QLatin1String("folderArchiveType"), (int)mArchiveType); + config.writeEntry(QLatin1String("enabled"), mEnabled); + config.writeEntry("keepExistingStructure", mKeepExistingStructure); +} + +bool FolderArchiveAccountInfo::operator==( const FolderArchiveAccountInfo& other ) const +{ + return (mInstanceName == other.instanceName()) + && (mArchiveTopLevelCollectionId == other.archiveTopLevel()) + && (mArchiveType == other.folderArchiveType()) + && (mEnabled == other.enabled()) + && (mKeepExistingStructure == other.keepExistingStructure()); +} diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.h b/kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.h new file mode 100644 index 00000000..4d52da36 --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchiveaccountinfo.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef FOLDERARCHIVEACCOUNTINFO_H +#define FOLDERARCHIVEACCOUNTINFO_H + +#include +#include + +class FolderArchiveAccountInfo +{ +public: + FolderArchiveAccountInfo(); + FolderArchiveAccountInfo(const KConfigGroup &config); + ~FolderArchiveAccountInfo(); + + enum FolderArchiveType { + UniqueFolder, + FolderByMonths, + FolderByYears + }; + + bool isValid() const; + + QString instanceName() const; + void setInstanceName(const QString &instance); + + void setArchiveTopLevel(Akonadi::Collection::Id id); + Akonadi::Collection::Id archiveTopLevel() const; + + void setFolderArchiveType(FolderArchiveType type); + FolderArchiveType folderArchiveType() const; + + void setEnabled(bool enabled); + bool enabled() const; + + void setKeepExistingStructure(bool b); + bool keepExistingStructure() const; + + void writeConfig(KConfigGroup &config ); + void readConfig(const KConfigGroup &config); + + bool operator==( const FolderArchiveAccountInfo& other ) const; + +private: + FolderArchiveAccountInfo::FolderArchiveType mArchiveType; + Akonadi::Collection::Id mArchiveTopLevelCollectionId; + QString mInstanceName; + bool mEnabled; + bool mKeepExistingStructure; +}; + +#endif // FOLDERARCHIVEACCOUNTINFO_H diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.cpp b/kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.cpp new file mode 100644 index 00000000..5b1ed51e --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.cpp @@ -0,0 +1,162 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "folderarchivesettingpage.h" +#include "folderarchiveaccountinfo.h" +#include "folderarchiveutil.h" + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static const KCatalogLoader loader( QLatin1String("libfolderarchivesettings") ); + +FolderArchiveComboBox::FolderArchiveComboBox(QWidget *parent) + : QComboBox(parent) +{ + initialize(); +} + +FolderArchiveComboBox::~FolderArchiveComboBox() +{ +} + +void FolderArchiveComboBox::initialize() +{ + addItem(i18nc("@item:inlistbox for option \"Archive folder name\"", "Unique"), + FolderArchiveAccountInfo::UniqueFolder); + addItem(i18nc("@item:inlistbox for option \"Archive folder name\"", "Month and year"), + FolderArchiveAccountInfo::FolderByMonths); + addItem(i18nc("@item:inlistbox for option \"Archive folder name\"", "Year"), + FolderArchiveAccountInfo::FolderByYears); +} + +void FolderArchiveComboBox::setType(FolderArchiveAccountInfo::FolderArchiveType type) +{ + const int index = findData(static_cast(type)); + if (index != -1) { + setCurrentIndex(index); + } else { + setCurrentIndex(0); + } +} + +FolderArchiveAccountInfo::FolderArchiveType FolderArchiveComboBox::type() const +{ + return static_cast(itemData(currentIndex()).toInt()); +} + +FolderArchiveSettingPage::FolderArchiveSettingPage(const QString &instanceName, QWidget *parent) + : QWidget(parent), + mInstanceName(instanceName), + mInfo(0) +{ + QVBoxLayout *lay = new QVBoxLayout; + mEnabled = new QCheckBox(i18n("Enable")); + connect(mEnabled, SIGNAL(toggled(bool)), this, SLOT(slotEnableChanged(bool))); + lay->addWidget(mEnabled); + + QHBoxLayout *hbox = new QHBoxLayout; + QLabel *lab = new QLabel(i18nc( + "@label:chooser for the folder that messages will be archived under", + "Archive into:")); + hbox->addWidget(lab); + mArchiveFolder = new Akonadi::CollectionRequester; + mArchiveFolder->setMimeTypeFilter(QStringList() << KMime::Message::mimeType()); + hbox->addWidget(mArchiveFolder); + lay->addLayout(hbox); + + hbox = new QHBoxLayout; + lab = new QLabel(i18nc("@label:listbox", "Archive folder name:")); + hbox->addWidget(lab); + mArchiveNamed = new FolderArchiveComboBox; + hbox->addWidget(mArchiveNamed); + + lay->addLayout(hbox); + + lay->addStretch(); + + setLayout(lay); +} + +FolderArchiveSettingPage::~FolderArchiveSettingPage() +{ + delete mInfo; +} + +void FolderArchiveSettingPage::slotEnableChanged(bool enabled) +{ + mArchiveFolder->setEnabled(enabled); + mArchiveNamed->setEnabled(enabled); +} + +void FolderArchiveSettingPage::loadSettings() +{ + KConfig config(FolderArchive::FolderArchiveUtil::configFileName()); + const QString groupName = FolderArchive::FolderArchiveUtil::groupConfigPattern() + mInstanceName; + if (config.hasGroup(groupName)) { + KConfigGroup grp = config.group(groupName); + mInfo = new FolderArchiveAccountInfo(grp); + mEnabled->setChecked(mInfo->enabled()); + mArchiveFolder->setCollection(Akonadi::Collection(mInfo->archiveTopLevel())); + mArchiveNamed->setType(mInfo->folderArchiveType()); + } else { + mInfo = new FolderArchiveAccountInfo(); + mEnabled->setChecked(false); + } + slotEnableChanged(mEnabled->isChecked()); +} + +void FolderArchiveSettingPage::writeSettings() +{ + KConfig config(FolderArchive::FolderArchiveUtil::configFileName()); + KConfigGroup grp = config.group(FolderArchive::FolderArchiveUtil::groupConfigPattern() + mInstanceName); + mInfo->setInstanceName(mInstanceName); + if (mArchiveFolder->collection().isValid()) { + mInfo->setEnabled(mEnabled->isChecked()); + mInfo->setArchiveTopLevel(mArchiveFolder->collection().id()); + } else { + mInfo->setEnabled(false); + mInfo->setArchiveTopLevel(-1); + } + + mInfo->setFolderArchiveType(mArchiveNamed->type()); + mInfo->writeConfig(grp); + + //Update cache from KMail + const QString kmailInterface = QLatin1String("org.kde.kmail"); + QDBusReply reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(kmailInterface); + if (!reply.isValid() || !reply.value()) { + return; + } + QDBusInterface kmail(kmailInterface, QLatin1String("/KMail"), QLatin1String("org.kde.kmail.kmail")); + kmail.asyncCall(QLatin1String("reloadFolderArchiveConfig")); +} + diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.h b/kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.h new file mode 100644 index 00000000..a27ae7ee --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchivesettingpage.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef FOLDERARCHIVESETTINGPAGE_H +#define FOLDERARCHIVESETTINGPAGE_H + +#include "folderarchiveaccountinfo.h" +#include "folderarchivesettings_export.h" +#include +#include + +class QCheckBox; +namespace Akonadi { +class CollectionRequester; +} + +class FolderArchiveComboBox : public QComboBox +{ + Q_OBJECT +public: + explicit FolderArchiveComboBox(QWidget *parent = 0); + ~FolderArchiveComboBox(); + + void setType(FolderArchiveAccountInfo::FolderArchiveType type); + FolderArchiveAccountInfo::FolderArchiveType type() const; + +private: + void initialize(); +}; + +class FolderArchiveAccountInfo; +class FOLDERARCHIVESETTINGS_EXPORT FolderArchiveSettingPage : public QWidget +{ + Q_OBJECT +public: + explicit FolderArchiveSettingPage(const QString &instanceName, QWidget *parent=0); + ~FolderArchiveSettingPage(); + + void loadSettings(); + void writeSettings(); + +private Q_SLOTS: + void slotEnableChanged(bool enabled); + +private: + QString mInstanceName; + QCheckBox *mEnabled; + FolderArchiveComboBox *mArchiveNamed; + Akonadi::CollectionRequester *mArchiveFolder; + FolderArchiveAccountInfo *mInfo; +}; + +#endif // FOLDERARCHIVESETTINGPAGE_H diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchivesettings_export.h b/kdepim-runtime/resources/folderarchivesettings/folderarchivesettings_export.h new file mode 100644 index 00000000..c01a054a --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchivesettings_export.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure + + 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 FOLDERARCHIVESETTINGS_EXPORT_H +#define FOLDERARCHIVESETTINGS_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef FOLDERARCHIVESETTINGS_EXPORT +# if defined(KDEPIM_STATIC_LIBS) + /* No export/import for static libraries */ +# define FOLDERARCHIVESETTINGS_EXPORT +# elif defined(MAKE_FOLDERARCHIVESETTINGS_LIB) + /* We are building this library */ +# define FOLDERARCHIVESETTINGS_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define FOLDERARCHIVESETTINGS_EXPORT KDE_IMPORT +# endif +#endif + +#endif diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.cpp b/kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.cpp new file mode 100644 index 00000000..33c72775 --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.cpp @@ -0,0 +1,30 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "folderarchiveutil.h" + +using namespace FolderArchive; + +QString FolderArchiveUtil::groupConfigPattern() +{ + return QLatin1String("FolderArchiveAccount "); +} + +QString FolderArchiveUtil::configFileName() +{ + return QLatin1String("foldermailarchiverc"); +} diff --git a/kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.h b/kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.h new file mode 100644 index 00000000..440e20b8 --- /dev/null +++ b/kdepim-runtime/resources/folderarchivesettings/folderarchiveutil.h @@ -0,0 +1,31 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef FOLDERARCHIVEUTIL_H +#define FOLDERARCHIVEUTIL_H + +#include +namespace FolderArchive { +namespace FolderArchiveUtil +{ +QString groupConfigPattern(); +QString configFileName(); +} +} + + +#endif // FOLDERARCHIVEUTIL_H diff --git a/kdepim-runtime/resources/gmail/CMakeLists.txt b/kdepim-runtime/resources/gmail/CMakeLists.txt new file mode 100644 index 00000000..6060e454 --- /dev/null +++ b/kdepim-runtime/resources/gmail/CMakeLists.txt @@ -0,0 +1,59 @@ +add_subdirectory(saslplugin) +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + + + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../imap + ${CMAKE_CURRENT_BINARY_DIR}/../imap + ${LibKGAPI2_INCLUDE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${QJSON_INCLUDE_DIR} + ${qjson_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../folderarchivesettings/ +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +########### next target ############### + +set(gmailresource_SRCS + gmailresource.cpp + gmailretrievecollectionstask.cpp + gmailretrieveitemstask.cpp + gmailmessagehelper.cpp + gmailpasswordrequester.cpp + gmailresourcestate.cpp + gmailconfigdialog.cpp + gmailsettings.cpp + gmaillinkitemstask.cpp + gmaillabelattribute.cpp + gmailchangeitemslabelstask.cpp +) + +kde4_add_ui_files(gmailresource_SRCS gmailconfigdialog.ui) +#kde4_add_kcfg_files(gmailresource_SRCS settingsbase.kcfgc) + +kde4_add_executable(akonadi_gmail_resource ${gmailresource_SRCS}) +target_link_libraries(akonadi_gmail_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTDBUS_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ${KDEPIMLIBS_KIMAP_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} + ${LibKGAPI2_LIBRARY} + ${QJSON_LIBRARIES} + imapresource + folderarchivesettings +) + +install(FILES gmailresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") +install(TARGETS akonadi_gmail_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/gmail/Messages.sh b/kdepim-runtime/resources/gmail/Messages.sh new file mode 100755 index 00000000..780159e6 --- /dev/null +++ b/kdepim-runtime/resources/gmail/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_gmail_resource.pot diff --git a/kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.cpp b/kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.cpp new file mode 100644 index 00000000..668e88a3 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmailchangeitemslabelstask.h" + +#include +#include +#include + + +GmailChangeItemsLabelsTask::GmailChangeItemsLabelsTask(ResourceStateInterface::Ptr resource, QObject *parent) + : ChangeItemsFlagsTask(resource, parent) +{ +} + +GmailChangeItemsLabelsTask::~GmailChangeItemsLabelsTask() +{ +} + +void GmailChangeItemsLabelsTask::doStart(KIMAP::Session *session) +{ + const QString mailbox = QLatin1String("[Gmail]/All Mail"); + if (session->selectedMailBox() != mailbox) { + KIMAP::SelectJob *select = new KIMAP::SelectJob(session); + select->setMailBox(mailbox); + connect(select, SIGNAL(finished(KJob*)), + this, SLOT(onSelectDone(KJob*))); + select->start(); + } else { + if (!addedFlags().isEmpty()) { + triggerAppendFlagsJob(session); + } else if (!removedFlags().isEmpty()) { + triggerRemoveFlagsJob(session); + } else { + changeProcessed(); + } + } +} + +void GmailChangeItemsLabelsTask::triggerAppendFlagsJob(KIMAP::Session *session) +{ + KIMAP::StoreJob *store = prepareJob(session); + store->setGMLabels(addedFlags().toList()); + store->setMode(KIMAP::StoreJob::AppendFlags); + connect(store, SIGNAL(result(KJob*)), + this, SLOT(onAppendFlagsDone(KJob*))); + store->start(); +} + +void GmailChangeItemsLabelsTask::triggerRemoveFlagsJob(KIMAP::Session *session) +{ + KIMAP::StoreJob *store = prepareJob(session); + store->setGMLabels(removedFlags().toList()); + store->setMode(KIMAP::StoreJob::RemoveFlags); + connect(store, SIGNAL(result(KJob*)), + this, SLOT(onRemoveFlagsDone(KJob*))); + store->start(); +} diff --git a/kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.h b/kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.h new file mode 100644 index 00000000..0bc9bf53 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailchangeitemslabelstask.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILCHANGEGMLABELTASK_H +#define GMAILCHANGEGMLABELTASK_H + +#include + +class GmailChangeItemsLabelsTask : public ChangeItemsFlagsTask +{ + Q_OBJECT +public: + explicit GmailChangeItemsLabelsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0); + virtual ~GmailChangeItemsLabelsTask(); + +protected: + void doStart(KIMAP::Session *session); + +protected: + void triggerAppendFlagsJob(KIMAP::Session *session); + void triggerRemoveFlagsJob(KIMAP::Session *session); +}; + +#endif // GMAILCHANGEGMLABELTASK_H diff --git a/kdepim-runtime/resources/gmail/gmailconfigdialog.cpp b/kdepim-runtime/resources/gmail/gmailconfigdialog.cpp new file mode 100644 index 00000000..35b3afc8 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailconfigdialog.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmailconfigdialog.h" +#include "gmailsettings.h" +#include "gmailresource.h" +#include "ui_gmailconfigdialog.h" +#include + +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +GmailConfigDialog::GmailConfigDialog(GmailResource *resource, WId parent) + : KDialog() + , m_parentResource(resource) + , m_ui(new Ui::GmailConfigDialog) + , m_subscriptionsChanged(false) + , m_shouldClearCache(false) +{ + m_parentResource->settings()->setWinId(parent); + + m_ui->setupUi(mainWidget()); + m_folderArchiveSettingPage = new FolderArchiveSettingPage(resource->identifier()); + m_ui->tabWidget->addTab(m_folderArchiveSettingPage, i18n("Folder Archive")); + + m_ui->checkInterval->setSuffix( ki18np( " minute", " minutes" ) ); + m_ui->checkInterval->setRange( Akonadi::ResourceSettings::self()->minimumCheckInterval(), 10000, 1 ); + + + m_identityManager = new KPIMIdentities::IdentityManager( false, this, "mIdentityManager" ); + m_identityCombobox = new KPIMIdentities::IdentityCombo( m_identityManager, this ); + m_ui->identityLabel->setBuddy( m_identityCombobox ); + m_ui->identityLayout->addWidget( m_identityCombobox, 1 ); + m_ui->identityLabel->setBuddy( m_identityCombobox ); + + connect(m_ui->subscriptionEnabled, SIGNAL(toggled(bool)), + this, SLOT(slotSubcriptionCheckboxChanged()) ); + connect(m_ui->subscriptionButton, SIGNAL(clicked(bool)), + this, SLOT(slotManageSubscriptions())); + + connect(m_ui->authenticateButton, SIGNAL(clicked(bool)), + this, SLOT(slotAuthenticate())); + connect(m_ui->changeAuthButton, SIGNAL(clicked(bool)), + this, SLOT(slotAuthenticate())); + + connect(m_ui->useDefaultIdentityCheck, SIGNAL(toggled(bool)), + this, SLOT(slotIdentityCheckboxChanged())); + connect(m_ui->enableMailCheckBox, SIGNAL(toggled(bool)), + this, SLOT(slotMailCheckboxChanged())); + + connect(m_parentResource->settings(), SIGNAL(accountRequestCompleted(KGAPI2::AccountPtr,bool)), + this, SLOT(onAccountRequestCompleted(KGAPI2::AccountPtr,bool))); + + readSettings(); + slotComplete(); + slotSubcriptionCheckboxChanged(); + slotIdentityCheckboxChanged(); + + //connect(this, SIGNAL(applyClicked()), + //this, SLOT(applySettings()) ); + connect(this, SIGNAL(okClicked()), + this, SLOT(applySettings()) ); +} + +GmailConfigDialog::~GmailConfigDialog() +{ + delete m_ui; +} + +bool GmailConfigDialog::shouldClearCache() const +{ + return m_shouldClearCache; +} + +void GmailConfigDialog::slotSubcriptionCheckboxChanged() +{ + m_ui->subscriptionButton->setEnabled(m_ui->subscriptionEnabled->isChecked()); +} + +void GmailConfigDialog::slotIdentityCheckboxChanged() +{ + m_identityCombobox->setEnabled(!m_ui->useDefaultIdentityCheck->isChecked()); +} + +void GmailConfigDialog::slotMailCheckboxChanged() +{ + m_ui->checkInterval->setEnabled(m_ui->enableMailCheckBox->isChecked()); +} + +void GmailConfigDialog::applySettings() +{ + m_folderArchiveSettingPage->writeSettings(); + m_parentResource->setName(m_ui->usernameLabel->text()); + + GmailSettings *settings = static_cast(m_parentResource->settings()); + settings->setImapServer(QLatin1String("imap.gmail.com")); + settings->setImapPort(993); + settings->setUserName(m_account->accountName()); + settings->setPassword(m_account->accessToken()); + settings->setRefreshToken(m_account->refreshToken()); + settings->setSafety(QLatin1String("SSL")); + settings->setSubscriptionEnabled(m_ui->subscriptionEnabled->isChecked()); + settings->setIntervalCheckTime(m_ui->checkInterval->value()); + settings->setDisconnectedModeEnabled(m_ui->disconnectedModeEnabled->isChecked()); + + /* Gmail does not support sieve */ + settings->setSieveSupport(false); + + settings->setAutomaticExpungeEnabled(m_ui->autoExpungeCheck->isChecked()); + settings->setUseDefaultIdentity(m_ui->useDefaultIdentityCheck->isChecked()); + if (!m_ui->useDefaultIdentityCheck->isChecked()) { + settings->setAccountIdentity(m_identityCombobox->currentIdentity()); + } + + settings->setIntervalCheckEnabled(m_ui->enableMailCheckBox->isChecked()); + if (m_ui->enableMailCheckBox->isChecked()) { + settings->setIntervalCheckTime( m_ui->checkInterval->value() ); + } + + settings->writeConfig(); + + if (m_oldResourceName != m_account->accountName() && !m_account->accountName().isEmpty()) { + settings->renameRootCollection(m_account->accountName()); + } +} + +void GmailConfigDialog::readSettings() +{ + m_folderArchiveSettingPage->loadSettings(); + m_ui->usernameLabel->setText(m_parentResource->name()); + m_oldResourceName = m_parentResource->name(); + + GmailSettings *settings = static_cast(m_parentResource->settings()); + + m_account = KGAPI2::AccountPtr(new KGAPI2::Account); + if (!m_parentResource->name().startsWith(m_parentResource->defaultName())) { + m_account->setAccountName(m_parentResource->name()); + m_ui->usernameLabel->setText(m_account->accountName()); + m_ui->authenticateButton->setVisible(false); + m_ui->currentAccountBox->setVisible(true); + + bool rejected = false; + const QString accessToken = settings->password(&rejected); + const QString refreshToken = settings->refreshToken(&rejected); + if ( rejected ) { + //m_ui->password->setEnabled( false ); + KMessageBox::information(0, i18n("Could not access KWallet. If you want to use Gmail resource, you have to activate it.")); + } else { + m_account->setAccessToken(accessToken); + m_account->setRefreshToken(refreshToken); + } + } else { + m_ui->currentAccountBox->setVisible(false); + m_ui->authenticateButton->setVisible(true); + } + + m_ui->subscriptionEnabled->setChecked(settings->subscriptionEnabled()); + + m_ui->enableMailCheckBox->setChecked(settings->intervalCheckEnabled()); + m_ui->checkInterval->setEnabled(m_ui->enableMailCheckBox->isChecked()); + m_ui->checkInterval->setValue(settings->intervalCheckTime()); + m_ui->disconnectedModeEnabled->setChecked(settings->disconnectedModeEnabled()); + + m_ui->useDefaultIdentityCheck->setChecked(settings->useDefaultIdentity()); + if (!m_ui->useDefaultIdentityCheck->isChecked()) + m_identityCombobox->setCurrentIdentity(settings->accountIdentity()); + + + m_ui->autoExpungeCheck->setChecked(settings->automaticExpungeEnabled()); +} + +void GmailConfigDialog::slotComplete() +{ + const bool ok = (m_account || !m_account->accountName().isEmpty()); + button(KDialog::Ok)->setEnabled(ok); +} + +void GmailConfigDialog::slotManageSubscriptions() +{ + ImapAccount account; + + account.setServer(QLatin1String("imap.gmail.com")); + account.setPort(993); + account.setUserName(m_account->accountName()); + account.setSubscriptionEnabled(m_ui->subscriptionEnabled->isChecked()); + + account.setEncryptionMode(KIMAP::LoginJob::SslV3); + account.setAuthenticationMode(KIMAP::LoginJob::XOAuth2); + + QPointer subscriptions = new SubscriptionDialog( this ); + subscriptions->setCaption(i18n("Serverside Subscription")); + subscriptions->setWindowIcon(KIcon(QLatin1String("network-server"))); + subscriptions->connectAccount(account, m_account->accessToken()); + m_subscriptionsChanged = subscriptions->isSubscriptionChanged(); + + subscriptions->exec(); + delete subscriptions; + + m_ui->subscriptionEnabled->setChecked(account.isSubscriptionEnabled()); +} + +void GmailConfigDialog::slotAuthenticate() +{ + GmailSettings *settings = static_cast(m_parentResource->settings()); + settings->clearCachedPassword(); + settings->storeAccount(KGAPI2::AccountPtr()); + settings->requestAccount(true); + m_shouldClearCache = true; +} + +void GmailConfigDialog::onAccountRequestCompleted(const KGAPI2::AccountPtr &account, bool userRejected) +{ + if (userRejected || account.isNull()) { + m_account = KGAPI2::AccountPtr(); + + m_ui->currentAccountBox->setVisible(false); + m_ui->authenticateButton->setVisible(true); + } else { + m_account = account; + m_ui->currentAccountBox->setVisible(true); + m_ui->usernameLabel->setText(m_account->accountName()); + m_ui->authenticateButton->setVisible(false); + } + + GmailSettings *settings = static_cast(m_parentResource->settings()); + settings->storeAccount(m_account); + slotComplete(); +} + diff --git a/kdepim-runtime/resources/gmail/gmailconfigdialog.h b/kdepim-runtime/resources/gmail/gmailconfigdialog.h new file mode 100644 index 00000000..467a6de9 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailconfigdialog.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILSETUPSERVER_H +#define GMAILSETUPSERVER_H + +#include + +#include + +namespace Ui +{ +class GmailConfigDialog; +} + +namespace KPIMIdentities +{ +class IdentityCombo; +class IdentityManager; +} + +class GmailResource; +class FolderArchiveSettingPage; +class GmailConfigDialog : public KDialog +{ + Q_OBJECT + +public: + explicit GmailConfigDialog(GmailResource *resource, WId parent); + virtual ~GmailConfigDialog(); + + bool shouldClearCache() const; + +private Q_SLOTS: + /** + * Call this if you want the settings saved from this page. + */ + void applySettings(); + void slotIdentityCheckboxChanged(); + void slotMailCheckboxChanged(); + void slotManageSubscriptions(); + void slotSubcriptionCheckboxChanged(); + void slotComplete(); + + void slotAuthenticate(); + void onAccountRequestCompleted(const KGAPI2::AccountPtr &account, bool userRejected); + +private: + void readSettings(); + + GmailResource *m_parentResource; + Ui::GmailConfigDialog *m_ui; + bool m_subscriptionsChanged; + bool m_shouldClearCache; + KPIMIdentities::IdentityManager *m_identityManager; + KPIMIdentities::IdentityCombo *m_identityCombobox; + QString m_oldResourceName; + KGAPI2::AccountPtr m_account; + FolderArchiveSettingPage *m_folderArchiveSettingPage; + +}; + +#endif // GMAILSETUPSERVER_H diff --git a/kdepim-runtime/resources/gmail/gmailconfigdialog.ui b/kdepim-runtime/resources/gmail/gmailconfigdialog.ui new file mode 100644 index 00000000..05b9a404 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailconfigdialog.ui @@ -0,0 +1,243 @@ + + + GmailConfigDialog + + + + 0 + 0 + 449 + 460 + + + + + + + 0 + + + + General + + + + + + + + Account + + + + + + Authenticated as: + + + + + + + + 75 + true + + + + + + + + + + + Change... + + + + + + + + + + + + Authenticate + + + + + + + Mail Checking Options + + + + + + &Download all messages for offline use + + + true + + + + + + + Enable &interval mail checking + + + true + + + + + + + + + Check mail interval: + + + + + + + 15 + + + 5 + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Advanced + + + + + + IMAP Settings + + + + + + &Synchronize only selected folders + + + + + + + Select folders to synchronize... + + + + + + + Automatical&ly compact folders (expunge deleted messages) + + + true + + + + + + + + + + Identity Settings + + + + + + Use &default identity + + + true + + + + + + + + + Identity: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+ + KIntNumInput + QWidget +
knuminput.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/gmail/gmaillabelattribute.cpp b/kdepim-runtime/resources/gmail/gmaillabelattribute.cpp new file mode 100644 index 00000000..37c1c807 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmaillabelattribute.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmaillabelattribute.h" + +GmailLabelAttribute::GmailLabelAttribute() + : Akonadi::Attribute() +{ +} + +GmailLabelAttribute::GmailLabelAttribute(const QByteArray &label) + : Akonadi::Attribute() + , mLabel(label) +{ +} + + +GmailLabelAttribute::~GmailLabelAttribute() +{ +} + +QByteArray GmailLabelAttribute::label() const +{ + return mLabel; +} + +void GmailLabelAttribute::setLabel(const QByteArray &label) +{ + mLabel = label; +} + +void GmailLabelAttribute::deserialize(const QByteArray &data) +{ + mLabel = data; +} + +QByteArray GmailLabelAttribute::serialized() const +{ + return mLabel; +} + +Akonadi::Attribute *GmailLabelAttribute::clone() const +{ + return new GmailLabelAttribute(mLabel); +} + +QByteArray GmailLabelAttribute::type() const +{ + return "GmailLabel"; +} + +bool GmailLabelAttribute::isAllMail() const +{ + return mLabel == "\\All"; +} + +bool GmailLabelAttribute::isDrafts() const +{ + return mLabel == "\\Draft"; +} + +bool GmailLabelAttribute::isFlagged() const +{ + return mLabel == "\\Flagged"; +} + +bool GmailLabelAttribute::isImportant() const +{ + return mLabel == "\\Important"; +} + +bool GmailLabelAttribute::isInbox() const +{ + return mLabel == "\\Inbox"; +} + +bool GmailLabelAttribute::isJunk() const +{ + return mLabel == "\\Junk"; +} + +bool GmailLabelAttribute::isSent() const +{ + return mLabel == "\\Sent"; +} + +bool GmailLabelAttribute::isTrash() const +{ + return mLabel == "\\Trash"; +} diff --git a/kdepim-runtime/resources/gmail/gmaillabelattribute.h b/kdepim-runtime/resources/gmail/gmaillabelattribute.h new file mode 100644 index 00000000..4fedd228 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmaillabelattribute.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILLABELATTRIBUTE_H +#define GMAILLABELATTRIBUTE_H + +#include +#include + +class GmailLabelAttribute : public Akonadi::Attribute +{ +public: + explicit GmailLabelAttribute(); + explicit GmailLabelAttribute(const QByteArray &label); + virtual ~GmailLabelAttribute(); + + QByteArray label() const; + void setLabel(const QByteArray &label); + + bool isDrafts() const; + bool isTrash() const; + bool isImportant() const; + bool isSent() const; + bool isJunk() const; + bool isFlagged() const; + bool isInbox() const; + bool isAllMail() const; + + void deserialize(const QByteArray &data); + QByteArray serialized() const; + Akonadi::Attribute* clone() const; + QByteArray type() const; + +private: + QByteArray mLabel; +}; + +#endif // GMAILLABELATTRIBUTE_H diff --git a/kdepim-runtime/resources/gmail/gmaillinkitemstask.cpp b/kdepim-runtime/resources/gmail/gmaillinkitemstask.cpp new file mode 100644 index 00000000..53ff0077 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmaillinkitemstask.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmaillinkitemstask.h" +#include "gmailresource.h" +#include "gmailretrieveitemstask.h" +#include "gmailsettings.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define LABEL_PROPERTY "LabelProperty" +#define COLLECTION_NAME_PROPERTY "CollectionNameProperty" +#define COLLECTION_PROPERTY "CollectionProperty" + +GmailLinkItemsTask::GmailLinkItemsTask(GmailRetrieveItemsTask *retrieveTask, GmailResource *parent) + : QObject(parent) + , mResource(parent) +{ + connect(retrieveTask, SIGNAL(linkItem(QString,QVector)), + this, SLOT(linkItem(QString,QVector))); + connect(retrieveTask, SIGNAL(destroyed(QObject*)), + this, SLOT(onRetrievalDone())); + +} + +GmailLinkItemsTask::~GmailLinkItemsTask() +{ + +} + +void GmailLinkItemsTask::emitDone() +{ + Q_EMIT done(); + Q_EMIT status(Akonadi::AgentBase::Idle); + deleteLater(); +} + + +void GmailLinkItemsTask::linkItem(const QString &remoteId, const QVector &labels) +{ + mLinkMap.insert(remoteId, labels); + Q_FOREACH (const QByteArray &label, labels) { + if (!mLabels.contains(label)) { + mLabels << label; + } + } +} + + +void GmailLinkItemsTask::onRetrievalDone() +{ + Q_EMIT status(Akonadi::AgentBase::Running, i18n("Linking emails to labels")); + if (!mLabels.isEmpty()) { + resolveNextLabel(); + } else { + emitDone(); + } +} + +// Step 1: Resolve all labels to collections +void GmailLinkItemsTask::resolveNextLabel() +{ + const QByteArray label = mLabels.takeFirst(); + + QString realCollectionName; + if (label == "\\Inbox") { + realCollectionName = QLatin1String("/INBOX"); + } else if (label == "\\Drafts" || label == "\\Draft") { + realCollectionName = QLatin1String("/Drafts"); + } else if (label == "\\Important") { + realCollectionName = QLatin1String("/Important"); + } else if (label == "\\Sent") { + realCollectionName = QLatin1String("/Sent Mail"); + } else if (label == "\\Junk" || label == "\\Spam") { + realCollectionName = QLatin1String("/Spam"); + } else if (label == "\\Flagged" || label == "\\Starred") { + realCollectionName = QLatin1String("/Starred"); + } else if (label == "\\Trash") { + realCollectionName = QLatin1String("/Trash"); + } else if (label[0] == '/') { + realCollectionName = QString::fromLatin1(label); + } else { + realCollectionName = QLatin1Char('/') + QString::fromLatin1(label); + } + + Akonadi::Collection rootCollection; + rootCollection.setRemoteId(mResource->settings()->rootRemoteId()); + Akonadi::CollectionPathResolver *resolver + = new Akonadi::CollectionPathResolver(realCollectionName, rootCollection, this); + resolver->setProperty(COLLECTION_NAME_PROPERTY, realCollectionName); + resolver->setProperty(LABEL_PROPERTY, label); + connect(resolver, SIGNAL(finished(KJob*)), + this, SLOT(onLabelResolved(KJob*))); +} + +// Step 2: Continue resolving until all is resolved, then go to step 3 +void GmailLinkItemsTask::onLabelResolved(KJob *job) +{ + Akonadi::CollectionPathResolver *resolver + = qobject_cast(job); + const QString collectionName = resolver->property(COLLECTION_NAME_PROPERTY).toString(); + const QByteArray label = resolver->property(LABEL_PROPERTY).toByteArray(); + if (resolver->error() && resolver->collection() < 0) { + kWarning() << "Failed to resolve collection ID for path" << collectionName << ":" << resolver->errorString(); + return; + } + const Akonadi::Collection collection(resolver->collection()); + mLabelCollectionMap.insert(label, collection); + + if (!mLabels.isEmpty()) { + resolveNextLabel(); + } else { + retrieveVirtualReferences(); + } +} + +// Step 3: Retrieve virtual references ot all our items +void GmailLinkItemsTask::retrieveVirtualReferences() +{ + Akonadi::Collection allMailCollection; + allMailCollection.setRemoteId(QLatin1String("/[Gmail]/All Mail")); + + Akonadi::Item::List items; + Q_FOREACH (const QString &remoteId, mLinkMap.uniqueKeys()) { + Akonadi::Item item; + item.setRemoteId(remoteId); + items << item; + } + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(items, this); + fetchJob->setCollection(allMailCollection); + fetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::None); + fetchJob->fetchScope().setCacheOnly(true); + fetchJob->fetchScope().setFetchModificationTime(false); + fetchJob->fetchScope().setFetchRemoteIdentification(true); + fetchJob->fetchScope().setFetchVirtualReferences(true); + connect(fetchJob, SIGNAL(finished(KJob*)), + this, SLOT(onVirtualReferencesRetrieved(KJob*))); +} + +// Step 4: Compare existing virtual references and our current labels, link and +// unlink as necessary +void GmailLinkItemsTask::onVirtualReferencesRetrieved(KJob *job) +{ + if (job->error()) { + // TODO: Error handling + emitDone(); + return; + } + + Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); + + QMap toLink; + QMap toUnlink; + Q_FOREACH (const Akonadi::Item &item, fetchJob->items()) { + Akonadi::Collection::List existingReferences = item.virtualReferences(); + const QVector newLabels = mLinkMap[item.remoteId()]; + Akonadi::Collection::List newReferences; + Q_FOREACH (const QByteArray &label, newLabels) { + const Akonadi::Collection newRef = mLabelCollectionMap[label]; + if (!existingReferences.contains(newRef)) { + Akonadi::Item::List &list = toLink[newRef]; + list << item; + } else { + existingReferences.removeOne(newRef); + } + } + if (!existingReferences.isEmpty()) { + Q_FOREACH (const Akonadi::Collection &ref, existingReferences) { + Akonadi::Item::List &list = toUnlink[ref]; + list << item; + } + } + } + + QMap::ConstIterator iter; + for (iter = toLink.constBegin(); iter != toLink.constEnd(); ++iter) { + new Akonadi::LinkJob(iter.key(), iter.value()); + } + for (iter = toUnlink.constBegin(); iter != toUnlink.constEnd(); ++iter) { + new Akonadi::UnlinkJob(iter.key(), iter.value()); + } + + emitDone(); +} + diff --git a/kdepim-runtime/resources/gmail/gmaillinkitemstask.h b/kdepim-runtime/resources/gmail/gmaillinkitemstask.h new file mode 100644 index 00000000..a26e7b71 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmaillinkitemstask.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILLINKITEMSTASK_H +#define GMAILLINKITEMSTASK_H + +#include +#include + +#include + +class GmailResource; +class GmailRetrieveItemsTask; +class KJob; + +class GmailLinkItemsTask : public QObject +{ + Q_OBJECT + +public: + explicit GmailLinkItemsTask(GmailRetrieveItemsTask *retrieveTask, GmailResource *parent0); + virtual ~GmailLinkItemsTask(); + +Q_SIGNALS: + void done(); + void status(int status, const QString &message = QString()); + +private Q_SLOTS: + void linkItem(const QString &remoteId, const QVector &labels); + + void onRetrievalDone(); + void resolveNextLabel(); + void onLabelResolved(KJob *job); + void retrieveVirtualReferences(); + void onVirtualReferencesRetrieved(KJob *job); + +private: + void emitDone(); + + QHash > mLinkMap; + QList mLabels; + QMap mLabelCollectionMap; + + GmailResource *mResource; +}; + +#endif // GMAILLINKITEMSTASK_H diff --git a/kdepim-runtime/resources/gmail/gmailmessagehelper.cpp b/kdepim-runtime/resources/gmail/gmailmessagehelper.cpp new file mode 100644 index 00000000..84cdfcb6 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailmessagehelper.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmailmessagehelper.h" +#include "gmailretrieveitemstask.h" + +GmailMessageHelper::GmailMessageHelper(const Akonadi::Collection &collection, ResourceTask *currentTask) + : MessageHelper() + , mCollection(collection) + , mTask(currentTask) +{ + +} + + +Akonadi::Item GmailMessageHelper::createItemFromMessage(KMime::Message::Ptr message, + const qint64 uid, + const qint64 size, + const QList &attrs, + const QList &flags, + const KIMAP::FetchJob::FetchScope &scope, + bool &ok) const +{ + Akonadi::Item item = MessageHelper::createItemFromMessage(message, uid, size, attrs, flags, scope, ok); + if (!ok) { + kWarning() << "Failed to read imap message"; + return item; + } + + Q_FOREACH (const KIMAP::MessageAttribute &attr, attrs) { + if (attr.first == "X-GM-LABELS") { + if (mTask) { + QVector labels; + + QByteArray labelStr = attr.second.toByteArray(); + int lastPos = 0; + bool isQuoted = false; + int i = 0; + while (i < labelStr.size()) { + if (labelStr[i] == '(') { + lastPos = i; + i++; + continue; + } + + if (labelStr[i] == '\"') { + lastPos = i; + isQuoted = true; + i++; + } + + if (isQuoted) { + while (labelStr[i] != '\"') { + if (i == labelStr.size()) { + // huh? Broken string? + break; + } + i++; + } + QByteArray mid = labelStr.mid(lastPos + 1, i - lastPos - 1).trimmed(); + if (!mid.isEmpty() && mid != "\\\\All") { + labels.append(mid.replace("\\\\", "\\")); + } + isQuoted = false; + lastPos = i; + } else { + if (labelStr[i] == ' ' || labelStr[i] == ')') { + QByteArray mid = labelStr.mid(lastPos + 1, i - lastPos - 1).trimmed(); + if (!mid.isEmpty() && mid != "\\\\All") { + labels.append(mid.replace("\\\\", "\\")); + } + lastPos = i; + } + } + ++i; + } + if (!labels.isEmpty()) { + GmailRetrieveItemsTask *task = qobject_cast(mTask); + Q_ASSERT(task); + task->linkItem(item.remoteId(), labels); + } + } + } else if (attr.first == "X-GM-THRID") { + // TODO: Store thread information + } else if (attr.first == "X-GM-MSGID") { + item.setGid(attr.second.toString()); + } + } + + return item; +} diff --git a/kdepim-runtime/resources/gmail/gmailmessagehelper.h b/kdepim-runtime/resources/gmail/gmailmessagehelper.h new file mode 100644 index 00000000..98ecea01 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailmessagehelper.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILMESSAGEHELPER_H +#define GMAILMESSAGEHELPER_H + +#include + +#include + +class ResourceTask; + +class GmailMessageHelper : public MessageHelper +{ +public: + GmailMessageHelper(const Akonadi::Collection &collection, ResourceTask *currentTask); + + virtual Akonadi::Item createItemFromMessage (KMime::Message::Ptr message, + const qint64 uid, + const qint64 size, + const QList &attrs, + const QList &flags, + const KIMAP::FetchJob::FetchScope &scope, + bool &ok) const; + +private: + Akonadi::Collection mCollection; + ResourceTask *mTask; +}; + +#endif // GMAILMESSAGEHELPER_H diff --git a/kdepim-runtime/resources/gmail/gmailpasswordrequester.cpp b/kdepim-runtime/resources/gmail/gmailpasswordrequester.cpp new file mode 100644 index 00000000..e6297e2b --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailpasswordrequester.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmailpasswordrequester.h" +#include "gmailsettings.h" +#include "gmailresource.h" + +#include +#include + +#include + +#include + +/** + * See https://developers.google.com/gmail/xoauth2_protocol for protocol documentation + */ + +GmailPasswordRequester::GmailPasswordRequester(GmailResource *resource, QObject *parent) + : PasswordRequesterInterface(parent) + , mResource(resource) +{ +} + +GmailPasswordRequester::~GmailPasswordRequester() +{ +} + +void GmailPasswordRequester::requestPassword(PasswordRequesterInterface::RequestType request, const QString &serverError) +{ + if (request == PasswordRequesterInterface::WrongPasswordRequest) { + connect(mResource->settings(), SIGNAL(accountRequestCompleted(KGAPI2::AccountPtr,bool)), + this, SLOT(onAuthFinished(KGAPI2::AccountPtr,bool)), + Qt::UniqueConnection); + static_cast(mResource->settings())->requestAccount(true); + } else { + QMetaObject::invokeMethod(this, "done", Qt::QueuedConnection, + Q_ARG(int, PasswordRetrieved), + Q_ARG(QString, mResource->settings()->password())); + } +} + +bool GmailPasswordRequester::isTokenExpired(const QString &serverError) +{ + QJson::Parser parser; + + QString base64Error = serverError.mid(7, serverError.length() - 9); + const QByteArray decoded = QByteArray::fromBase64(base64Error.toLatin1()); + bool ok = false; + const QVariant json = parser.parse(decoded, &ok); + if (!ok) { + return false; + } + + const QVariantMap map = json.toMap(); + if (map[QLatin1String("status")].toString().toInt() == KGAPI2::Unauthorized) { + return true; + } + + kDebug() << "Gmail Auth error:" << json; + return false; +} + +void GmailPasswordRequester::onAuthFinished(const KGAPI2::AccountPtr &account, bool userRejected) +{ + if (userRejected) { + done(UserRejected); + return; + } + + if (!account) { + // Really?? + done(ReconnectNeeded); + return; + } + + // Access Token is not really a password, but meh... + done(PasswordRetrieved, account->accessToken()); +} diff --git a/kdepim-runtime/resources/gmail/gmailpasswordrequester.h b/kdepim-runtime/resources/gmail/gmailpasswordrequester.h new file mode 100644 index 00000000..8e444c88 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailpasswordrequester.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILPASSWORDREQUESTER_H +#define GMAILPASSWORDREQUESTER_H + +#include + +#include + +class GmailResource; +class GmailPasswordRequester : public PasswordRequesterInterface +{ + Q_OBJECT + +public: + GmailPasswordRequester(GmailResource *resource, QObject *parent); + virtual ~GmailPasswordRequester(); + + virtual void requestPassword(RequestType request = StandardRequest, const QString &serverError = QString()); + +private: + bool isTokenExpired(const QString &serverError); + QString encodePassword(const QString &accountName, const QString &accessToken) const; + +private Q_SLOTS: + void onAuthFinished(const KGAPI2::AccountPtr &account, bool userRejected); + +private: + GmailResource *mResource; +}; + +#endif // GMAILPASSWORDREQUESTER_H diff --git a/kdepim-runtime/resources/gmail/gmailresource.cpp b/kdepim-runtime/resources/gmail/gmailresource.cpp new file mode 100644 index 00000000..0b04952a --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailresource.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "gmailresource.h" +#include "gmailretrievecollectionstask.h" +#include "gmailretrieveitemstask.h" +#include "gmailresourcestate.h" +#include "gmailpasswordrequester.h" +#include "gmailconfigdialog.h" +#include "gmailsettings.h" +#include "gmaillinkitemstask.h" +#include "gmaillabelattribute.h" +#include "gmailchangeitemslabelstask.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +GmailResource::GmailResource(const QString &id) + : ImapResourceBase(id) + , m_settings(0) +{ + KGlobal::locale()->insertCatalog(QLatin1String("akonadi_imap_resource")); + setSeparatorCharacter(QLatin1Char('/')); + + m_pool->setPasswordRequester(new GmailPasswordRequester(this, m_pool)); + m_pool->setSessionUiProxy(SessionUiProxy::Ptr(new SessionUiProxy)); + + Akonadi::AttributeFactory::registerAttribute(); +} + +GmailResource::~GmailResource() +{ +} + +Settings *GmailResource::settings() const +{ + if (m_settings == 0) { + m_settings = new GmailSettings; + } + + return m_settings; +} + + +QString GmailResource::defaultName() const +{ + return i18n("Gmail Resource"); +} + +KDialog *GmailResource::createConfigureDialog(WId windowId) +{ + GmailConfigDialog *dlg = new GmailConfigDialog(this, windowId); + KWindowSystem::setMainWindow(dlg, windowId); + dlg->setWindowIcon(KIcon(QLatin1String("network-server"))); + connect(dlg, SIGNAL(finished(int)), this, SLOT(onConfigurationDone(int)));; + return dlg; +} + +void GmailResource::onConfigurationDone(int result) +{ + GmailConfigDialog *dlg = qobject_cast(sender()); + if (result) { + if ( dlg->shouldClearCache() ) { + clearCache(); + } + settings()->writeConfig(); + } + dlg->deleteLater(); +} + +Akonadi::Collection GmailResource::allMailCollection() const +{ + Akonadi::Collection c; + c.setRemoteId(QLatin1String("/[Gmail]/All Mail")); + return c; +} + +Akonadi::Collection GmailResource::rootCollection() const +{ + Akonadi::Collection c; + c.setRemoteId(settings()->rootRemoteId()); + return c; +} + +ResourceStateInterface::Ptr GmailResource::createResourceState(const TaskArguments &args) +{ + return ResourceStateInterface::Ptr(new GmailResourceState(this, args)); +} + +void GmailResource::retrieveCollections() +{ + emit status(AgentBase::Running, i18nc("@info:status", "Retrieving folders")); + + ResourceTask *task = new GmailRetrieveCollectionsTask(createResourceState(TaskArguments()), this); + if (settings()->trashCollection() == -1) { + connect(task, SIGNAL(destroyed(QObject*)), + this, SLOT(updateTrashFolder())); + } + task->start(m_pool); + queueTask(task); +} + +void GmailResource::updateTrashFolder() +{ + Akonadi::CollectionFetchJob *fetch + = new Akonadi::CollectionFetchJob(rootCollection(), Akonadi::CollectionFetchJob::FirstLevel, this); + connect(fetch, SIGNAL(finished(KJob*)), + this, SLOT(onUpdateTrashFolderCollectionsRetrieved(KJob*))); +} + +void GmailResource::onUpdateTrashFolderCollectionsRetrieved(KJob *job) +{ + Akonadi::CollectionFetchJob *fetch = qobject_cast(job); + if (job->error()) { + kError() << fetch->errorString(); + return; + } + + Akonadi::Collection::List cols = fetch->collections(); + Q_FOREACH (const Akonadi::Collection &col, cols) { + GmailLabelAttribute *attr = col.attribute(); + if (!attr) { + continue; + } + + if (attr->isTrash()) { + settings()->setTrashCollection(col.id()); + Akonadi::SpecialCollections::setSpecialCollectionType("trash", col); + return; + } + } + + kWarning() << "Failed to detect the Trash folder...!?"; +} + +void GmailResource::retrieveItems(const Akonadi::Collection &col) +{ + kDebug() << col.id() << col.remoteId() << col.name(); + // We can't sync the virtual collections - instead we get ID of "All Mail" and + // we schedule it's sync + // + // TODO: Don't resync the All Mail collections X times in a row for each virtual + // collection, instead uset a timer or something + if (col.isVirtual()) { + Akonadi::CollectionFetchJob *fetch + = new Akonadi::CollectionFetchJob(allMailCollection(), Akonadi::CollectionFetchJob::Base, this); + connect(fetch, SIGNAL(finished(KJob*)), + this, SLOT(onRetrieveItemsCollectionRetrieved(KJob*))); + return; + } + + setItemStreamingEnabled(true); + + GmailRetrieveItemsTask *task = new GmailRetrieveItemsTask(createResourceState(TaskArguments(col)), this); + connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); + connect(this, SIGNAL(retrieveNextItemSyncBatch(int)), task, SLOT(onReadyForNextBatch(int))); + + GmailLinkItemsTask *linkTask = new GmailLinkItemsTask(task, this); + connect(linkTask, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); + + startTask(task); + scheduleCustomTask(this, "triggerCollectionExtraInfoJobs", QVariant::fromValue(col), ResourceBase::Append); +} + +void GmailResource::onRetrieveItemsCollectionRetrieved(KJob *job) +{ + if (job->error()) { + cancelTask(job->errorString()); + return; + } + + Akonadi::CollectionFetchJob *fetch = qobject_cast(job); + if (fetch->collections().count() != 1) { + kWarning() << "Got" << fetch->collections().count() << "collections, expected only one!"; + cancelTask(); + return; + } + + synchronizeCollection(fetch->collections().first().id()); + + itemsRetrievalDone(); +} + +void GmailResource::itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) +{ + if (!collection.hasAttribute()) { + kWarning() << "Collection is missing GmailLabelAttribute! IMPOSSIBRU!"; + cancelTask(); + return; + } + + const QByteArray label = collection.attribute()->label(); + TaskArguments args(items, QSet() << label, QSet()); + GmailChangeItemsLabelsTask *task = new GmailChangeItemsLabelsTask(createResourceState(args), this); + startTask(task); +} + +void GmailResource::itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) +{ + if (!collection.hasAttribute()) { + kWarning() << "Collection is missing GmailLabelAttribute! IMPOSSIBRU!"; + cancelTask(); + return; + } + + const QByteArray label = collection.attribute()->label(); + TaskArguments args(items, QSet(), QSet() << label); + GmailChangeItemsLabelsTask *task = new GmailChangeItemsLabelsTask(createResourceState(args), this); + startTask(task); +} + +AKONADI_RESOURCE_MAIN(GmailResource) diff --git a/kdepim-runtime/resources/gmail/gmailresource.desktop b/kdepim-runtime/resources/gmail/gmailresource.desktop new file mode 100644 index 00000000..60d69adc --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailresource.desktop @@ -0,0 +1,64 @@ +[Desktop Entry] +Name=Gmail +Name[ca]=Gmail +Name[da]=Gmail +Name[de]=Gmail +Name[en_GB]=Gmail +Name[es]=Gmail +Name[et]=Gmail +Name[fi]=Gmail +Name[fr]=Gmail +Name[hu]=Gmail +Name[it]=Gmail +Name[ko]=GMail +Name[nb]=Gmail +Name[nds]=GMail +Name[nl]=Gmail +Name[pl]=Gmail +Name[pt]=Gmail +Name[pt_BR]=Gmail +Name[ru]=Gmail +Name[sk]=Gmail +Name[sr]=Г‑мејл +Name[sr@ijekavian]=Г‑мејл +Name[sr@ijekavianlatin]=GMail +Name[sr@latin]=GMail +Name[sv]=Gmail +Name[uk]=Gmail +Name[x-test]=xxGmailxx +Name[zh_TW]=Gmail +Comment=Connects to your Gmail account +Comment[ca]=Connecta amb el vostre compte de Gmail +Comment[da]=Forbinder til din Gmail-konto +Comment[de]=Verbindet mit Ihrem Gmail-Konto +Comment[en_GB]=Connects to your Gmail account +Comment[es]=Conecta a su cuenta Gmail +Comment[et]=Ãœhendumine Gmaili kontoga +Comment[fi]=Yhdistää Gmail-tiliin +Comment[fr]=Se connecte à votre compte Gmail +Comment[hu]=Csatlakozik a Gmail fiókjához +Comment[it]=Connette al tuo account Gmail +Comment[ko]=ë‚´ GMail ê³„ì •ì— ì—°ê²° +Comment[nb]=Kobler til Gmail-kontoen din +Comment[nds]=Koppelt sik Dien GMail-Konto to +Comment[nl]=Verbindt met uw Gmail account +Comment[pl]=ÅÄ…czy z kontem Gmail +Comment[pt]=Liga-se à sua conta de Gmail +Comment[pt_BR]=Conecta-o a sua conta do Gmail +Comment[ru]=Подключение к учётной запиÑи Gmail +Comment[sk]=Pripojí k vášmu Gmail úÄtu +Comment[sr]=Повезивање Ñа налогом на Г‑мејлу +Comment[sr@ijekavian]=Повезивање Ñа налогом на Г‑мејлу +Comment[sr@ijekavianlatin]=Povezivanje sa nalogom na GMailu +Comment[sr@latin]=Povezivanje sa nalogom na GMailu +Comment[sv]=Ansluter till ditt Gmail-konto +Comment[uk]=Ð’Ñтановлює Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· вашим обліковим запиÑом Gmail +Comment[x-test]=xxConnects to your Gmail accountxx +Comment[zh_TW]=連線到您的 Gmail 帳號 +Type=AkonadiResource +Exec=akonadi_gmail_resource +Icon=im-google + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_gmail_resource diff --git a/kdepim-runtime/resources/gmail/gmailresource.h b/kdepim-runtime/resources/gmail/gmailresource.h new file mode 100644 index 00000000..d4c7ca18 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailresource.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef GMAILRESOURCE_H +#define GMAILRESOURCE_H + +#include + +#include + +class GmailSettings; + +class GmailResource : public ImapResourceBase +{ + Q_OBJECT + + using Akonadi::AgentBase::ObserverV3; + +public: + explicit GmailResource(const QString &id); + ~GmailResource(); + + Akonadi::Collection allMailCollection() const; + Akonadi::Collection rootCollection() const; + + KDialog *createConfigureDialog (WId windowId); + QString defaultName() const; + + ResourceStateInterface::Ptr createResourceState (const TaskArguments &args); + + void retrieveCollections(); + void retrieveItems(const Akonadi::Collection &col); + + void itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); + void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); + + Settings *settings() const; + +private Q_SLOTS: + void updateTrashFolder(); + void onUpdateTrashFolderCollectionsRetrieved(KJob *job); + + void onConfigurationDone(int result); + void onRetrieveItemsCollectionRetrieved(KJob *job); + +private: + mutable GmailSettings *m_settings; +}; + + + +#endif // GMAILRESOURCE_H diff --git a/kdepim-runtime/resources/gmail/gmailresource.kcfg b/kdepim-runtime/resources/gmail/gmailresource.kcfg new file mode 100644 index 00000000..b399efb8 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailresource.kcfg @@ -0,0 +1,116 @@ + + + + + + + imap.gmail.com + + + + 993 + + + + + + + SSL + + + + + + + + 1 + + + + false + + + 30 + + + + + + false + + + + true + + + + 5 + + + + true + + + + true + + + + -1 + + + + false + + + + true + + + + + + + + + + + + + + + + + false + + + + true + + + + 4190 + + + + + + + kmail-vacation.siv + + + + + + + + ImapUserPassword + + + diff --git a/kdepim-runtime/resources/gmail/gmailresourcestate.cpp b/kdepim-runtime/resources/gmail/gmailresourcestate.cpp new file mode 100644 index 00000000..8b59b426 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailresourcestate.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmailresourcestate.h" +#include "gmailmessagehelper.h" +#include "gmailretrieveitemstask.h" +#include "gmailresource.h" + +#include + +GmailResourceState::GmailResourceState(GmailResource *resource, const TaskArguments &arguments) + : ResourceState(resource, arguments) + , mResource(resource) + , mTask(0) +{ +} + +void GmailResourceState::setCurrentTask(ResourceTask *task) +{ + mTask = task; +} + +ResourceTask *GmailResourceState::currentTask() const +{ + return mTask; +} + +MessageHelper::Ptr GmailResourceState::messageHelper() const +{ + return MessageHelper::Ptr(new GmailMessageHelper(collection(), + qobject_cast(mTask))); +} diff --git a/kdepim-runtime/resources/gmail/gmailresourcestate.h b/kdepim-runtime/resources/gmail/gmailresourcestate.h new file mode 100644 index 00000000..8fe68516 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailresourcestate.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GMAILRESOURCESTATE_H +#define GMAILRESOURCESTATE_H + +#include + +class GmailResource; +class ResourceTask; + +class GmailResourceState : public ResourceState +{ +public: + explicit GmailResourceState (GmailResource *resource, const TaskArguments &arguments); + + void setCurrentTask(ResourceTask *task); + ResourceTask *currentTask() const; + +protected: + virtual MessageHelper::Ptr messageHelper() const; + +private: + GmailResource *mResource; + ResourceTask *mTask; +}; + +#endif // GMAILRESOURCESTATE_H diff --git a/kdepim-runtime/resources/gmail/gmailretrievecollectionstask.cpp b/kdepim-runtime/resources/gmail/gmailretrievecollectionstask.cpp new file mode 100644 index 00000000..b22cb7c0 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailretrievecollectionstask.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "gmailretrievecollectionstask.h" +#include "gmaillabelattribute.h" + +#include +#include + +#include +#include +#include +#include + +#include + + +#include + +GmailRetrieveCollectionsTask::GmailRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, + QObject *parent) + : RetrieveCollectionsTask(resource, parent) +{ +} + +void GmailRetrieveCollectionsTask::onMailBoxesReceived(const QList &descriptors, + const QList > &flags) +{ + Akonadi::Collection &rootCollection = m_reportedCollections[QString()]; + Akonadi::EntityDisplayAttribute *attr = rootCollection.attribute(Akonadi::Entity::AddIfMissing); + attr->setIconName(QLatin1String("im-google")); + attr->setDisplayName(rootCollection.name()); + + QStringList contentTypes; + contentTypes << KMime::Message::mimeType(); + + for (int i = 0, dscCnt = descriptors.size(); i < dscCnt; ++i) { + const KIMAP::MailBoxDescriptor descriptor = descriptors[i]; + + // skip phantom mailboxes contained in LSUB but not LIST + if (isSubscriptionEnabled() && !m_fullReportedCollections.contains(descriptor.name)) { + kDebug() << "Got phantom mailbox: " << descriptor.name; + continue; + } + + const QString boxName = descriptor.name.endsWith(separatorCharacter()) + ? descriptor.name.left(descriptor.name.size() - 1) + : descriptor.name; + + /* FIXME: The Chats folder contains logs of Hangouts chats, which is rather + * useless in Kmail, and also it breaks sync, because it is not a "special" + * folder. We should re-enable it at some point so that people can't + * complain, but until then the folder will not be synced. + */ + if (boxName == QLatin1String("[Gmail]/Chats")) { + continue; + } + + const QStringList pathParts = boxName.split(separatorCharacter()); + + QString parentPath; + QString currentPath; + + for (int j = 0, partsCnt = pathParts.count(); j < partsCnt; ++j ) { + const bool isDummy = j != pathParts.size() - 1; + const QString pathPart = pathParts.at(j); + currentPath += separatorCharacter() + pathPart; + + if (m_reportedCollections.contains(currentPath)) { + if (m_dummyCollections.contains(currentPath) && !isDummy) { + kDebug() << "Received the real collection for a dummy one : " << currentPath; + //set the correct attributes for the collection, eg. noselect needs to be removed + Akonadi::Collection c = m_reportedCollections.value(currentPath); + c.setContentMimeTypes(contentTypes); + c.setRights(Akonadi::Collection::AllRights); + c.removeAttribute(); + m_dummyCollections.remove(currentPath); + m_reportedCollections.insert(currentPath, c); + } + parentPath = currentPath; + continue; + } + + const QList currentFlags = isDummy ? (QList() << "\\noselect") : flags[i]; + + Akonadi::Collection c; + c.setName(pathPart); + c.setRemoteId(separatorCharacter() + pathPart); + const Akonadi::Collection parentCollection = m_reportedCollections.value(parentPath); + c.setParentCollection(parentCollection); + c.setContentMimeTypes(contentTypes); + c.setVirtual(true); // All collections are virtual + c.setRights(Akonadi::Collection::CanChangeCollection | + Akonadi::Collection::CanDeleteCollection | + Akonadi::Collection::CanCreateCollection | + Akonadi::Collection::CanLinkItem | + Akonadi::Collection::CanUnlinkItem | + Akonadi::Collection::CanCreateItem | + Akonadi::Collection::CanDeleteItem | + Akonadi::Collection::CanChangeItem); + + Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Entity::AddIfMissing); + if (currentFlags.contains("\\trash")) { + attr->setIconName(QLatin1String("user-trash")); + } else if (currentFlags.contains("\\sent")) { + attr->setIconName(QLatin1String("mail-folder-sent")); + } else if (currentFlags.contains("\\inbox")) { + attr->setIconName(QLatin1String("mail-folder-inbox")); + } else { + attr->setIconName(QLatin1String("folder")); + } + attr->setDisplayName(pathPart); + + // If the folder is the Inbox, make some special settings. + if (currentPath.compare(separatorCharacter() + QLatin1String("INBOX") , Qt::CaseInsensitive) == 0) { + Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); + attr->setDisplayName(i18n("Inbox")); + attr->setIconName(QLatin1String("mail-folder-inbox")); + } + + // "All Mail", "Trash" and "Spam" are the only non-virtual collections + // that will store the actual emails. + if (currentFlags.contains("\\all") || currentFlags.contains("\\trash") || currentFlags.contains("\\junk")) { + c.setVirtual(false); + c.addAttribute(new NoSelectAttribute); + c.addAttribute(new NoInferiorsAttribute); + c.setParentCollection(m_reportedCollections.value(QString())); + c.setRemoteId(currentPath); + c.setLocalListPreference(Akonadi::Collection::ListSync, Akonadi::Collection::ListDefault); + + // Hide "All mail" collection and mark it as IDLE + if (currentFlags.contains("\\all")) { + c.setEnabled(false); + c.setLocalListPreference(Akonadi::Collection::ListDisplay, Akonadi::Collection::ListDisabled); + c.setLocalListPreference(Akonadi::Collection::ListIndex, Akonadi::Collection::ListDisabled); + c.setLocalListPreference(Akonadi::Collection::ListSync, Akonadi::Collection::ListEnabled); + + // This mean that we will automatically pick up changes in + // all labels - YAY! + setIdleCollection(c); + } + c.setRights(Akonadi::Collection::CanCreateItem | + Akonadi::Collection::CanChangeItem | + Akonadi::Collection::CanDeleteItem); + } + + // If this folder is a noselect folder, make some special settings. + if (currentFlags.contains("\\noselect")) { + kDebug() << "Dummy collection created: " << currentPath; + c.addAttribute(new NoSelectAttribute(true)); + c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType()); + c.setRights(Akonadi::Collection::ReadOnly); + } else { + // remove the noselect attribute explicitly, in case we had set it before (eg. for non-subscribed non-leaf folders) + c.removeAttribute(); + } + + // If this folder is a noinferiors folder, it is not allowed to create subfolders inside. + if (currentFlags.contains("\\noinferiors")) { + //kDebug() << "Noinferiors: " << currentPath; + c.addAttribute(new NoInferiorsAttribute(true)); + c.setRights(c.rights() & ~Akonadi::Collection::CanCreateCollection); + } + + kDebug() << currentPath << currentFlags; + // Special treating of Gmail system collections (and INBOX) + if (currentPath == QLatin1String("/INBOX") || + currentFlags.contains("\\drafts") || + currentFlags.contains("\\important") || + currentFlags.contains("\\sent") || + currentFlags.contains("\\flagged")) + { + // Keep [Gmail] in remoteID, so that we can reference them correctly + // even though they have different parent in Akonadi + c.setRemoteId(currentPath); + // Move them from the [Gmail] subfolder + c.setParentCollection(m_reportedCollections.value(QString())); + // None of these can actually have subcollections, cannot be modified and + // cannot be removed. + c.setRights( c.rights() & ~Akonadi::Collection::CanDeleteCollection + & ~Akonadi::Collection::CanChangeCollection + & ~Akonadi::Collection::CanCreateCollection ); + } + + // I am the king of non-generic code! + if (currentFlags.contains("\\inbox") || pathPart == QLatin1String("INBOX")) { + c.addAttribute(new GmailLabelAttribute("\\Inbox")); + } else if (currentFlags.contains("\\drafts")) { + // This is not a typo, they actually use "\Draft" in X-GM-LABEL + c.addAttribute(new GmailLabelAttribute("\\Draft")); + } else if (currentFlags.contains("\\important")) { + c.addAttribute(new GmailLabelAttribute("\\Important")); + } else if (currentFlags.contains("\\sent")) { + c.addAttribute(new GmailLabelAttribute("\\Sent")); + } else if (currentFlags.contains("\\junk")) { + c.addAttribute(new GmailLabelAttribute("\\Junk")); + } else if (currentFlags.contains("\\flagged")) { + c.addAttribute(new GmailLabelAttribute("\\Flagged")); + } else if (currentFlags.contains("\\trash")) { + c.addAttribute(new GmailLabelAttribute("\\Trash")); + } else if (currentFlags.contains("\\all")) { + // Ignore + } else { + // For non-gmail flags, store the actual path without opening "/", + // which is actually Gmail label + c.addAttribute(new GmailLabelAttribute(currentPath.mid(1).toUtf8())); + } + + // Add special mimetype to non-system virtual collections to allow + // creating subfolders + if (c.isVirtual() && c.rights() & Akonadi::Collection::CanCreateCollection) { + c.setContentMimeTypes(c.contentMimeTypes() + << Akonadi::Collection::virtualMimeType()); + } + + m_reportedCollections.insert(currentPath, c); + + if (isDummy) { + m_dummyCollections.insert(currentPath, c); + } + + parentPath = currentPath; + } + } + + // Remove the [Gmail] folder. We inserted it only to get remoteIDs for it's subcollections right + // FIXME GMAIL: Don't hardcode this, try to have some detection or at least a constant + m_reportedCollections.remove( separatorCharacter() + QLatin1String( "[Gmail]" ) ); +} + + diff --git a/kdepim-runtime/resources/gmail/gmailretrievecollectionstask.h b/kdepim-runtime/resources/gmail/gmailretrievecollectionstask.h new file mode 100644 index 00000000..9e553ee7 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailretrievecollectionstask.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef GMAILRETRIEVECOLLECTIONSTASK_H +#define GMAILRETRIEVECOLLECTIONSTASK_H + +#include + +class GmailRetrieveCollectionsTask : public RetrieveCollectionsTask +{ + Q_OBJECT +public: + explicit GmailRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0); + +protected Q_SLOTS: + void onMailBoxesReceived(const QList &descriptors, const QList > &flags); + +}; + +#endif // GMAILRETRIEVECOLLECTIONSTASK_H diff --git a/kdepim-runtime/resources/gmail/gmailretrieveitemstask.cpp b/kdepim-runtime/resources/gmail/gmailretrieveitemstask.cpp new file mode 100644 index 00000000..80631396 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailretrieveitemstask.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "gmailretrieveitemstask.h" +#include "gmailresourcestate.h" + +#include + +#include +#include + + +GmailRetrieveItemsTask::GmailRetrieveItemsTask(ResourceStateInterface::Ptr resource, QObject *parent) + : RetrieveItemsTask(resource, parent) +{ + kDebug(); + dynamic_cast(resource.get())->setCurrentTask(this); +} + +GmailRetrieveItemsTask::~GmailRetrieveItemsTask() +{ + +} + +BatchFetcher *GmailRetrieveItemsTask::createBatchFetcher(MessageHelper::Ptr messageHelper, + const KIMAP::ImapSet &set, + const KIMAP::FetchJob::FetchScope &scope, + int batchSize, + KIMAP::Session *session) +{ + kDebug(); + KIMAP::FetchJob::FetchScope gmailScope = scope; + BatchFetcher *batchFetcher = new BatchFetcher(messageHelper, set, gmailScope, batchSize, session); + batchFetcher->setGmailExtensionsEnabled(true); + return batchFetcher; +} + +bool GmailRetrieveItemsTask::serverSupportsCondstore() const +{ + return true; +} diff --git a/kdepim-runtime/resources/gmail/gmailretrieveitemstask.h b/kdepim-runtime/resources/gmail/gmailretrieveitemstask.h new file mode 100644 index 00000000..3ab54b06 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailretrieveitemstask.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef GMAILRETRIEVEITEMSTASK_H +#define GMAILRETRIEVEITEMSTASK_H + +#include + +#include + +class GmailRetrieveItemsTask : public RetrieveItemsTask +{ + Q_OBJECT +public: + explicit GmailRetrieveItemsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0); + ~GmailRetrieveItemsTask(); + + virtual bool serverSupportsCondstore() const; + +Q_SIGNALS: + void linkItem(const QString &remoteId, const QVector &labels); + +protected: + BatchFetcher *createBatchFetcher(MessageHelper::Ptr messageHelper, + const KIMAP::ImapSet &set, + const KIMAP::FetchJob::FetchScope &scope, + int batchSize, + KIMAP::Session *session); + +private: + // Allow GmailMessageHelper to emit linkItem() for us + friend class GmailMessageHelper; +}; + +#endif // GMAILRETRIEVEITEMSTASK_H diff --git a/kdepim-runtime/resources/gmail/gmailsettings.cpp b/kdepim-runtime/resources/gmail/gmailsettings.cpp new file mode 100644 index 00000000..f4409580 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailsettings.cpp @@ -0,0 +1,305 @@ +/* + * + * Copyright (C) 2014 Daniel Vrátil + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gmailsettings.h" +//#include "settingsadaptor.h" + +#include "imapaccount.h" + +#include +using KWallet::Wallet; + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +GmailSettings::GmailSettings(WId winId) + : Settings(winId) + , mActiveAuthJob(0) +{ + // Try to initialize mAccount + requestAccount(false); + /* + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/GmailSettings" ), this, + QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); + */ +} + +QString GmailSettings::apiKey() const +{ + return QLatin1String("554041944266.apps.googleusercontent.com"); +} + +QString GmailSettings::secretKey() const +{ + return QLatin1String("mdT1DjzohxN3npUUzkENT0gO"); +} + + +void GmailSettings::clearCachedPassword() +{ + mAccount = KGAPI2::AccountPtr(); +} + +void GmailSettings::cleanup() +{ + Wallet* wallet = Wallet::openWallet(Wallet::NetworkWallet(), m_winId); + if (wallet && wallet->isOpen()) { + if (wallet->hasFolder(QLatin1String("gmail"))) { + wallet->setFolder(QLatin1String("gmail")); + wallet->removeEntry(config()->name()); + } + delete wallet; + } +} + +void GmailSettings::requestPassword() +{ + requestAccount(false); + if (mAccount) { + Q_EMIT passwordRequestCompleted(mAccount->accessToken(), false); + } else { + Q_EMIT passwordRequestCompleted(QString(), false); + } +} + +void GmailSettings::requestAccount(bool authenticate) +{ + bool userRejected = false; + loadAccountFromKWallet(&userRejected); + if (userRejected) { + Q_EMIT accountRequestCompleted(KGAPI2::AccountPtr(), true); + return; + } + + if (authenticate) { + if (mActiveAuthJob) { + return; + } + + if (!mAccount) { + mAccount = KGAPI2::AccountPtr(new KGAPI2::Account()); + mAccount->addScope(QUrl(QLatin1String("https://mail.google.com"))); + } + + KGAPI2::AuthJob *authJob = new KGAPI2::AuthJob(mAccount, apiKey(), secretKey(), this); + connect(authJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(onAuthFinished(KGAPI2::Job*))); + mActiveAuthJob = authJob; + } else { + if (mAccount) { + setImapServer(QLatin1String("imap.gmail.com")); + } else { + setImapServer(QString()); + } + Q_EMIT accountRequestCompleted(mAccount, false); + } +} + +void GmailSettings::onAuthFinished(KGAPI2::Job *job) +{ + mActiveAuthJob = 0; + + if (job->error()) { + Q_EMIT accountRequestCompleted(KGAPI2::AccountPtr(), job->error() == KGAPI2::AuthCancelled); + return; + } + + KGAPI2::AuthJob *auth = qobject_cast(job); + const KGAPI2::AccountPtr account = auth->account(); + storeAccount(account); + setImapServer(QLatin1String("imap.gmail.com")); + + Q_EMIT accountRequestCompleted(account, false); +} + + +void GmailSettings::onWalletOpened(bool success) +{ + if (!success) { + emit passwordRequestCompleted(QString(), true); + } else { + Wallet *wallet = qobject_cast( sender() ); + bool passwordNotStoredInWallet = true; + if (wallet && wallet->hasFolder(QLatin1String("gmail"))) { + loadAccountFromKWallet(); + passwordNotStoredInWallet = false; + } + if (passwordNotStoredInWallet || !mAccount) { + /* FIXME: Manual auth */ + //requestManualAuth(); + } else { + emit passwordRequestCompleted(mAccount->accessToken(), passwordNotStoredInWallet); + } + + if (wallet) { + wallet->deleteLater(); + } + } +} + +void GmailSettings::loadAccountFromKWallet(bool *userRejected) const +{ + if (userRejected != 0) { + *userRejected = false; + } + + Wallet* wallet = Wallet::openWallet(Wallet::NetworkWallet(), m_winId); + if (wallet && wallet->isOpen()) { + if (wallet->hasFolder(QLatin1String("gmail"))) { + wallet->setFolder(QLatin1String("gmail")); + QMap map; + wallet->readMap(config()->name(), map); + mAccount = KGAPI2::AccountPtr(new KGAPI2::Account(map[QLatin1String("accountName")], + map[QLatin1String("accessToken")], + map[QLatin1String("refreshToken")], + QList() << QUrl(QLatin1String("https://mail.google.com")) + << KGAPI2::Account::accountInfoScopeUrl() + << KGAPI2::Account::accountInfoEmailScopeUrl())); + } else { + wallet->createFolder(QLatin1String("gmail")); + mAccount = KGAPI2::AccountPtr(); + } + } else { + mAccount = KGAPI2::AccountPtr(); + if (userRejected != 0) { + *userRejected = true; + } + } +} + +void GmailSettings::saveAccountToKWallet() +{ + Wallet* wallet = Wallet::openWallet(Wallet::NetworkWallet(), m_winId); + if (wallet && wallet->isOpen()) { + if (!wallet->hasFolder(QLatin1String("gmail"))) { + wallet->createFolder(QLatin1String("gmail")); + } + wallet->setFolder(QLatin1String("gmail")); + QMap map; + map[QLatin1String("accountName")] = mAccount->accountName(); + map[QLatin1String("accessToken")] = mAccount->accessToken(); + map[QLatin1String("refreshToken")] = mAccount->refreshToken(); + wallet->writeMap(config()->name(), map); + kDebug() << "Wallet save: " << wallet->sync(); + } + delete wallet; +} + + +QString GmailSettings::accountName(bool *userRejected) const +{ + if (!mAccount) { + loadAccountFromKWallet(userRejected); + } + + return mAccount->accountName(); +} + +void GmailSettings::setAccountName(const QString &accountName) +{ + if (accountName == mAccount->accountName()) { + return; + } + + mAccount->setAccountName(accountName); + saveAccountToKWallet(); +} + +QString GmailSettings::password(bool *userRejected) const +{ + if (!mAccount) { + loadAccountFromKWallet(userRejected); + } + if (mAccount) + return mAccount->accessToken(); + return QString(); +} + +void GmailSettings::setPassword(const QString &accessToken) +{ + if (accessToken == mAccount->accessToken()) { + return; + } + + mAccount->setAccessToken(accessToken); + saveAccountToKWallet(); +} + +QString GmailSettings::refreshToken(bool *userRejected) const +{ + if (!mAccount) { + loadAccountFromKWallet(userRejected); + } + + return mAccount->refreshToken(); +} + +void GmailSettings::setRefreshToken(const QString &refreshToken) +{ + if (refreshToken == mAccount->refreshToken()) { + return; + } + + mAccount->setRefreshToken(refreshToken); + saveAccountToKWallet(); +} + + +void GmailSettings::loadAccount(ImapAccount *account) const +{ + kDebug() << userName(); + account->setServer(QLatin1String("imap.gmail.com")); + account->setPort(993); + + account->setUserName(userName()); + account->setSubscriptionEnabled(subscriptionEnabled()); + + account->setEncryptionMode(KIMAP::LoginJob::SslV3); + account->setAuthenticationMode(KIMAP::LoginJob::XOAuth2); + + account->setTimeout( sessionTimeout() ); +} + +void GmailSettings::storeAccount(const KGAPI2::AccountPtr &account) +{ + if (!account) { + cleanup(); + return; + } + + mAccount = account; + saveAccountToKWallet(); +} + +QString GmailSettings::rootRemoteId() const +{ + return QLatin1String("imap://") + userName() + QLatin1Char('@') + imapServer() + QLatin1Char('/'); +} diff --git a/kdepim-runtime/resources/gmail/gmailsettings.h b/kdepim-runtime/resources/gmail/gmailsettings.h new file mode 100644 index 00000000..8f313c46 --- /dev/null +++ b/kdepim-runtime/resources/gmail/gmailsettings.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef GMAILSETTINGS_H +#define GMAILSETTINGS_H + +#include + +#include + +namespace KGAPI2 { +class Job; +class AuthJob; +} + +class ImapAccount; +class KJob; + +class GmailSettings : public Settings +{ + Q_OBJECT + +public: + explicit GmailSettings(WId wid = 0); + + void requestPassword(); + void requestAccount(bool authenticate = false); + + void loadAccount(ImapAccount *account) const; + void storeAccount(const KGAPI2::AccountPtr &account); + + /* FIXME: I have serious doubts about this methods...they should be in the + * Resource, not here. */ + QString rootRemoteId() const; + + // Actually cleans tokens + void clearCachedPassword(); + void cleanup(); + + QString apiKey() const; + QString secretKey() const; + +Q_SIGNALS: + void passwordRequestCompleted(const QString &password, bool userRejected); + void accountRequestCompleted(const KGAPI2::AccountPtr &account, bool userRejected); + +public Q_SLOTS: + Q_SCRIPTABLE QString accountName(bool *userRejected = 0) const; + Q_SCRIPTABLE void setAccountName(const QString &accountName); + + Q_SCRIPTABLE QString password(bool *userRejected = 0) const; + Q_SCRIPTABLE void setPassword(const QString &accessToken); + + Q_SCRIPTABLE QString refreshToken(bool *userRejected = 0) const; + Q_SCRIPTABLE void setRefreshToken(const QString &refreshToken); + +private Q_SLOTS: + void onWalletOpened(bool success); + + void loadAccountFromKWallet(bool *userRejected = 0) const; + void saveAccountToKWallet(); + + void onAuthFinished(KGAPI2::Job *job); + +private: + mutable KGAPI2::AccountPtr mAccount; + KGAPI2::AuthJob *mActiveAuthJob; + +}; + +#endif diff --git a/kdepim-runtime/resources/gmail/saslplugin/CMakeLists.txt b/kdepim-runtime/resources/gmail/saslplugin/CMakeLists.txt new file mode 100644 index 00000000..982c2c4d --- /dev/null +++ b/kdepim-runtime/resources/gmail/saslplugin/CMakeLists.txt @@ -0,0 +1,18 @@ +find_package(Sasl2) +set_package_properties(Sasl2 PROPERTIES DESCRIPTION "cyrus-sasl" URL "http://asg.web.cmu.edu/sasl/sasl-library.html" TYPE REQUIRED PURPOSE "Login authentication for Gmail resource") + +add_definitions(-D_POSIX_SOURCE) + +include_directories(${SASL2_INCLUDE_DIR} + ${SASL2_INCLUDE_DIR}/sasl) + +set(kdexoauth2sasl_SRCS + plugin_common.c + xoauth2plugin.c + xoauth2plugin_init.c +) + +kde4_add_library(kdexoauth2 SHARED ${kdexoauth2sasl_SRCS}) +set_target_properties(kdexoauth2 PROPERTIES SOVERSION 3 VERSION 3.0.0) + +install(TARGETS kdexoauth2 DESTINATION ${LIB_INSTALL_DIR}/sasl2) diff --git a/kdepim-runtime/resources/gmail/saslplugin/config.h b/kdepim-runtime/resources/gmail/saslplugin/config.h new file mode 100644 index 00000000..4c1faf9a --- /dev/null +++ b/kdepim-runtime/resources/gmail/saslplugin/config.h @@ -0,0 +1,584 @@ +/* config.h. Generated by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + + +/* acconfig.h - autoheader configuration input */ +/* + * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Office of Technology Transfer + * Carnegie Mellon University + * 5000 Forbes Avenue + * Pittsburgh, PA 15213-3890 + * (412) 268-4387, fax: (412) 268-7395 + * tech-transfer@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + + +/* Runtime config file location */ +#define CONFIGDIR "/usr/lib/sasl2:/etc/sasl2" + +/* Do we need a leading _ for dlsym? */ +/* #undef DLSYM_NEEDS_UNDERSCORE */ + +/* Should we build a shared plugin (via dlopen) library? */ +#define DO_DLOPEN + +/* should we support sasl_checkapop? */ +#define DO_SASL_CHECKAPOP + +/* should we support setpass() for SRP? */ +/* #undef DO_SRP_SETPASS */ + +/* should we mutex-wrap calls into the GSS library? */ +#define GSS_USE_MUTEXES + +/* Enable 'alwaystrue' password verifier? */ +/* #undef HAVE_ALWAYSTRUE */ + +/* Include support for Courier's authdaemond? */ +#define HAVE_AUTHDAEMON + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DES_H */ + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the `dns_lookup' function. */ +/* #undef HAVE_DNS_LOOKUP */ + +/* Define to 1 if you have the `dn_expand' function. */ +#define HAVE_DN_EXPAND 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Do we have a getaddrinfo? */ +#define HAVE_GETADDRINFO + +/* Define to 1 if you have the `getdomainname' function. */ +#define HAVE_GETDOMAINNAME 1 + +/* Define to 1 if you have the `gethostname' function. */ +#define HAVE_GETHOSTNAME 1 + +/* Do we have a getnameinfo() function? */ +#define HAVE_GETNAMEINFO + +/* Define to 1 if you have the `getpwnam' function. */ +#define HAVE_GETPWNAM 1 + +/* Define to 1 if you have the `getspnam' function. */ +#define HAVE_GETSPNAM 1 + +/* do we have getsubopt()? */ +#define HAVE_GETSUBOPT + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define if you have the gssapi.h header file */ +#define HAVE_GSSAPI_H + +/* Define to 1 if you have the `gsskrb5_register_acceptor_identity' function. + */ +/* #undef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY */ + +/* Define if your GSSAPI implimentation defines GSS_C_NT_HOSTBASED_SERVICE */ +#define HAVE_GSS_C_NT_HOSTBASED_SERVICE + +/* Define if your GSSAPI implimentation defines GSS_C_NT_USER_NAME */ +#define HAVE_GSS_C_NT_USER_NAME + +/* Define to 1 if you have the `inet_aton' function. */ +#define HAVE_INET_ATON 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `jrand48' function. */ +#define HAVE_JRAND48 1 + +/* Do we have Kerberos 4 Support? */ +/* #undef HAVE_KRB */ + +/* Define to 1 if you have the `krb_get_err_text' function. */ +/* #undef HAVE_KRB_GET_ERR_TEXT */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LBER_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LDAP_H */ + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MALLOC_H 1 + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkdir' function. */ +#define HAVE_MKDIR 1 + +/* Do we have mysql support? */ +/* #undef HAVE_MYSQL */ + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Do we have OpenSSL? */ +#define HAVE_OPENSSL + +/* Use OPIE for server-side OTP? */ +/* #undef HAVE_OPIE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PAM_PAM_APPL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_PATHS_H 1 + +/* Do we have Postgres support? */ +/* #undef HAVE_PGSQL */ + +/* Include Support for pwcheck daemon? */ +/* #undef HAVE_PWCHECK */ + +/* Include support for saslauthd? */ +#define HAVE_SASLAUTHD + +/* Define to 1 if you have the header file. */ +#define HAVE_SECURITY_PAM_APPL_H 1 + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Does the system have snprintf()? */ +#define HAVE_SNPRINTF + +/* Does sockaddr have an sa_len? */ +/* #undef HAVE_SOCKADDR_SA_LEN */ + +/* Define to 1 if you have the `socket' function. */ +#define HAVE_SOCKET 1 + +/* Do we have a socklen_t? */ +#define HAVE_SOCKLEN_T + +/* Do we have SQLite support? */ +/* #undef HAVE_SQLITE */ + +/* Is there an ss_family in sockaddr_storage? */ +#define HAVE_SS_FAMILY + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strchr' function. */ +#define HAVE_STRCHR 1 + +/* Define to 1 if you have the `strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strspn' function. */ +#define HAVE_STRSPN 1 + +/* Define to 1 if you have the `strstr' function. */ +#define HAVE_STRSTR 1 + +/* Define to 1 if you have the `strtol' function. */ +#define HAVE_STRTOL 1 + +/* Do we have struct sockaddr_stroage? */ +#define HAVE_STRUCT_SOCKADDR_STORAGE + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSEXITS_H 1 + +/* Define to 1 if you have the `syslog' function. */ +#define HAVE_SYSLOG 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UIO_H 1 + +/* Define to 1 if you have that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_VARARGS_H */ + +/* Does the system have vsnprintf()? */ +#define HAVE_VSNPRINTF + +/* define if your compiler has __attribute__ */ +/* #undef HAVE___ATTRIBUTE__ */ + +/* Should we keep handle to Berkeley DB open in SASLDB plugin? */ +/* #undef KEEP_DB_OPEN */ + +/* Ignore IP Address in Kerberos 4 tickets? */ +/* #undef KRB4_IGNORE_IP_ADDRESS */ + +/* Name of package */ +#define PACKAGE "cyrus-sasl" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Where do we look for Courier authdaemond's socket? */ +#define PATH_AUTHDAEMON_SOCKET "/dev/null" + +/* Where do we look for saslauthd's socket? */ +#define PATH_SASLAUTHD_RUNDIR "/var/state/saslauthd" + +/* Runtime plugin location */ +#define PLUGINDIR "/usr/lib/sasl2" + +/* Force a preferred mechanism */ +/* #undef PREFER_MECH */ + +/* Location of pwcheck socket */ +/* #undef PWCHECKDIR */ + +/* Define as the return type of signal handlers (`int' or `void'). */ +#define RETSIGTYPE void + +/* Use BerkeleyDB for SASLdb */ +#define SASL_BERKELEYDB + +/* Path to default SASLdb database */ +#define SASL_DB_PATH "/etc/sasldb2" + +/* File to use for source of randomness */ +#define SASL_DEV_RANDOM "/dev/random" + +/* Use GDBM for SASLdb */ +/* #undef SASL_GDBM */ + +/* Use NDBM for SASLdb */ +/* #undef SASL_NDBM */ + +/* The size of a `long', as computed by sizeof. */ +#define SIZEOF_LONG 4 + +/* Link ANONYMOUS Staticly */ +/* #undef STATIC_ANONYMOUS */ + +/* Link CRAM-MD5 Staticly */ +/* #undef STATIC_CRAMMD5 */ + +/* Link DIGEST-MD5 Staticly */ +/* #undef STATIC_DIGESTMD5 */ + +/* Link GSSAPI Staticly */ +#define STATIC_GSSAPIV2 + +/* User KERBEROS_V4 Staticly */ +/* #undef STATIC_KERBEROS4 */ + +/* Link ldapdb plugin Staticly */ +/* #undef STATIC_LDAPDB */ + +/* Link LOGIN Staticly */ +/* #undef STATIC_LOGIN */ + +/* Link NTLM Staticly */ +/* #undef STATIC_NTLM */ + +/* Link OTP Staticly */ +/* #undef STATIC_OTP */ + +/* Link PASSDSS Staticly */ +/* #undef STATIC_PASSDSS */ + +/* Link PLAIN Staticly */ +/* #undef STATIC_PLAIN */ + +/* Link OAUTH Staticly */ +/* #undef STATIC_OAUTH */ + +/* Link SASLdb Staticly */ +/* #undef STATIC_SASLDB */ + +/* Link SQL plugin staticly */ +/* #undef STATIC_SQL */ + +/* Link SRP Staticly */ +/* #undef STATIC_SRP */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Should we try to dlopen() plugins while staticly compiled? */ +/* #undef TRY_DLOPEN_WHEN_STATIC */ + +/* use the doors IPC API for saslauthd? */ +/* #undef USE_DOORS */ + +/* Version number of package */ +#define VERSION "2.1.23" + +/* Use DES */ +#define WITH_DES + +/* Linking against dmalloc? */ +/* #undef WITH_DMALLOC */ + +/* Use internal RC4 implementation? */ +#define WITH_RC4 + +/* Use OpenSSL DES Implementation */ +#define WITH_SSL_DES + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define as `__inline' if that's what the C compiler calls it, or to nothing + if it is not supported. */ +/* #undef inline */ + +/* Define to `int' if does not define. */ +/* #undef mode_t */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + + + + +/* Create a struct iovec if we need one */ +#if !defined(_WIN32) && !defined(HAVE_SYS_UIO_H) +/* (win32 is handled in sasl.h) */ +struct iovec { + char *iov_base; + long iov_len; +}; +#else +#include +#include +#endif + +/* location of the random number generator */ +#ifdef DEV_RANDOM +/* #undef DEV_RANDOM */ +#endif +#define DEV_RANDOM SASL_DEV_RANDOM + +/* if we've got krb_get_err_txt, we might as well use it; + especially since krb_err_txt isn't in some newer distributions + (MIT Kerb for Mac 4 being a notable example). If we don't have + it, we fall back to the krb_err_txt array */ +#ifdef HAVE_KRB_GET_ERR_TEXT +#define get_krb_err_txt krb_get_err_text +#else +#define get_krb_err_txt(X) (krb_err_txt[(X)]) +#endif + +/* Make Solaris happy... */ +#ifndef __EXTENSIONS__ +#define __EXTENSIONS__ +#endif + +/* Make Linux happy... */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef HAVE___ATTRIBUTE__ +/* Can't use attributes... */ +#define __attribute__(foo) +#endif + +#define SASL_PATH_ENV_VAR "SASL_PATH" +#define SASL_CONF_PATH_ENV_VAR "SASL_CONF_PATH" + +#include +#include +#include +#ifndef WIN32 +# include +# ifdef HAVE_SYS_PARAM_H +# include +# endif +#else /* WIN32 */ +# include +#endif /* WIN32 */ +#include + +#include + +#ifndef HAVE_SOCKLEN_T +typedef unsigned int socklen_t; +#endif /* HAVE_SOCKLEN_T */ + +#ifndef HAVE_STRUCT_SOCKADDR_STORAGE +#define _SS_MAXSIZE 128 /* Implementation specific max size */ +#define _SS_PADSIZE (_SS_MAXSIZE - sizeof (struct sockaddr)) + +struct sockaddr_storage { + struct sockaddr ss_sa; + char __ss_pad2[_SS_PADSIZE]; +}; +# define ss_family ss_sa.sa_family +#endif /* !HAVE_STRUCT_SOCKADDR_STORAGE */ + +#ifndef AF_INET6 +/* Define it to something that should never appear */ +#define AF_INET6 AF_MAX +#endif + +#ifndef HAVE_GETADDRINFO +#define getaddrinfo sasl_getaddrinfo +#define freeaddrinfo sasl_freeaddrinfo +#define gai_strerror sasl_gai_strerror +#endif + +#ifndef HAVE_GETNAMEINFO +#define getnameinfo sasl_getnameinfo +#endif + +#if !defined(HAVE_GETNAMEINFO) || !defined(HAVE_GETADDRINFO) +#include "gai.h" +#endif + +#ifndef AI_NUMERICHOST /* support glibc 2.0.x */ +#define AI_NUMERICHOST 4 +#define NI_NUMERICHOST 2 +#define NI_NAMEREQD 4 +#define NI_NUMERICSERV 8 +#endif + +/* Defined in RFC 1035. max strlen is only 253 due to length bytes. */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 255 +#endif + +#ifndef HAVE_SYSEXITS_H +#include "exits.h" +#else +#include "sysexits.h" +#endif + +/* Get the correct time.h */ +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#ifndef HIER_DELIMITER +#define HIER_DELIMITER '/' +#endif + +#endif /* CONFIG_H */ + diff --git a/kdepim-runtime/resources/gmail/saslplugin/plugin_common.c b/kdepim-runtime/resources/gmail/saslplugin/plugin_common.c new file mode 100644 index 00000000..0b2750a8 --- /dev/null +++ b/kdepim-runtime/resources/gmail/saslplugin/plugin_common.c @@ -0,0 +1,925 @@ +/* Generic SASL plugin utility functions + * Rob Siemborski + * $Id: plugin_common.c,v 1.22 2011/09/01 14:12:18 mel Exp $ + */ +/* + * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Office of Technology Transfer + * Carnegie Mellon University + * 5000 Forbes Avenue + * Pittsburgh, PA 15213-3890 + * (412) 268-4387, fax: (412) 268-7395 + * tech-transfer@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#ifndef macintosh +#ifdef WIN32 +# include +#else +# include +# include +# include +# include +# include +# include +#endif /* WIN32 */ +#endif /* macintosh */ +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_INTTYPES_H +#include +#endif + +#include "plugin_common.h" + +/* translate IPv4 mapped IPv6 address to IPv4 address */ +static void sockaddr_unmapped( +#ifdef IN6_IS_ADDR_V4MAPPED + struct sockaddr *sa, socklen_t *len +#else + struct sockaddr *sa __attribute__((unused)), + socklen_t *len __attribute__((unused)) +#endif +) +{ +#ifdef IN6_IS_ADDR_V4MAPPED + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin4; + uint32_t addr; + int port; + + if (sa->sa_family != AF_INET6) + return; + sin6 = (struct sockaddr_in6 *)sa; + if (!IN6_IS_ADDR_V4MAPPED((&sin6->sin6_addr))) + return; + sin4 = (struct sockaddr_in *)sa; + addr = *(uint32_t *)&sin6->sin6_addr.s6_addr[12]; + port = sin6->sin6_port; + memset(sin4, 0, sizeof(struct sockaddr_in)); + sin4->sin_addr.s_addr = addr; + sin4->sin_port = port; + sin4->sin_family = AF_INET; +#ifdef HAVE_SOCKADDR_SA_LEN + sin4->sin_len = sizeof(struct sockaddr_in); +#endif + *len = sizeof(struct sockaddr_in); +#else + return; +#endif +} + +int _plug_ipfromstring(const sasl_utils_t *utils, const char *addr, + struct sockaddr *out, socklen_t outlen) +{ + int i, j; + socklen_t len; + struct sockaddr_storage ss; + struct addrinfo hints, *ai = NULL; + char hbuf[NI_MAXHOST]; + + if(!utils || !addr || !out) { + if(utils) PARAMERROR( utils ); + return SASL_BADPARAM; + } + + /* Parse the address */ + for (i = 0; addr[i] != '\0' && addr[i] != ';'; i++) { + if (i >= NI_MAXHOST) { + if(utils) PARAMERROR( utils ); + return SASL_BADPARAM; + } + hbuf[i] = addr[i]; + } + hbuf[i] = '\0'; + + if (addr[i] == ';') + i++; + /* XXX/FIXME: Do we need this check? */ + for (j = i; addr[j] != '\0'; j++) + if (!isdigit((int)(addr[j]))) { + PARAMERROR( utils ); + return SASL_BADPARAM; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; + + if (getaddrinfo(hbuf, &addr[i], &hints, &ai) != 0) { + PARAMERROR( utils ); + return SASL_BADPARAM; + } + + len = ai->ai_addrlen; + memcpy(&ss, ai->ai_addr, len); + freeaddrinfo(ai); + sockaddr_unmapped((struct sockaddr *)&ss, &len); + if (outlen < len) { + PARAMERROR( utils ); + return SASL_BUFOVER; + } + + memcpy(out, &ss, len); + + return SASL_OK; +} + +int _plug_iovec_to_buf(const sasl_utils_t *utils, const struct iovec *vec, + unsigned numiov, buffer_info_t **output) +{ + unsigned i; + int ret; + buffer_info_t *out; + char *pos; + + if(!utils || !vec || !output) { + if(utils) PARAMERROR( utils ); + return SASL_BADPARAM; + } + + if(!(*output)) { + *output = utils->malloc(sizeof(buffer_info_t)); + if(!*output) { + MEMERROR(utils); + return SASL_NOMEM; + } + memset(*output,0,sizeof(buffer_info_t)); + } + + out = *output; + + out->curlen = 0; + for(i=0; icurlen += vec[i].iov_len; + + ret = _plug_buf_alloc(utils, &out->data, &out->reallen, out->curlen); + + if(ret != SASL_OK) { + MEMERROR(utils); + return SASL_NOMEM; + } + + memset(out->data, 0, out->reallen); + pos = out->data; + + for(i=0; imalloc(newlen); + if (*rwbuf == NULL) { + *curlen = 0; + MEMERROR(utils); + return SASL_NOMEM; + } + *curlen = newlen; + } else if(*rwbuf && *curlen < newlen) { + size_t needed = 2*(*curlen); + + while(needed < newlen) + needed *= 2; + + *rwbuf = utils->realloc(*rwbuf, needed); + if (*rwbuf == NULL) { + *curlen = 0; + MEMERROR(utils); + return SASL_NOMEM; + } + *curlen = needed; + } + + return SASL_OK; +} + +/* copy a string */ +int _plug_strdup(const sasl_utils_t * utils, const char *in, + char **out, int *outlen) +{ + size_t len = strlen(in); + + if(!utils || !in || !out) { + if(utils) PARAMERROR(utils); + return SASL_BADPARAM; + } + + *out = utils->malloc(len + 1); + if (!*out) { + MEMERROR(utils); + return SASL_NOMEM; + } + + strcpy((char *) *out, in); + + if (outlen) + *outlen = len; + + return SASL_OK; +} + +void _plug_free_string(const sasl_utils_t *utils, char **str) +{ + size_t len; + + if (!utils || !str || !(*str)) return; + + len = strlen(*str); + + utils->erasebuffer(*str, len); + utils->free(*str); + + *str=NULL; +} + +void _plug_free_secret(const sasl_utils_t *utils, sasl_secret_t **secret) +{ + if(!utils || !secret || !(*secret)) return; + + utils->erasebuffer((char *)(*secret)->data, (*secret)->len); + utils->free(*secret); + *secret = NULL; +} + +/* + * Trys to find the prompt with the lookingfor id in the prompt list + * Returns it if found. NULL otherwise + */ +sasl_interact_t *_plug_find_prompt(sasl_interact_t **promptlist, + unsigned int lookingfor) +{ + sasl_interact_t *prompt; + + if (promptlist && *promptlist) { + for (prompt = *promptlist; prompt->id != SASL_CB_LIST_END; ++prompt) { + if (prompt->id==lookingfor) + return prompt; + } + } + + return NULL; +} + +/* + * Retrieve the simple string given by the callback id. + */ +int _plug_get_simple(const sasl_utils_t *utils, unsigned int id, int required, + const char **result, sasl_interact_t **prompt_need) +{ + + int ret = SASL_FAIL; + sasl_getsimple_t *simple_cb; + void *simple_context; + sasl_interact_t *prompt; + + *result = NULL; + + /* see if we were given the result in the prompt */ + prompt = _plug_find_prompt(prompt_need, id); + if (prompt != NULL) { + /* We prompted, and got.*/ + + if (required && !prompt->result) { + SETERROR(utils, "Unexpectedly missing a prompt result"); + return SASL_BADPARAM; + } + + *result = prompt->result; + return SASL_OK; + } + + /* Try to get the callback... */ + ret = utils->getcallback(utils->conn, id, (sasl_callback_ft *)&simple_cb, &simple_context); + + if (ret == SASL_FAIL && !required) + return SASL_OK; + + if (ret == SASL_OK && simple_cb) { + ret = simple_cb(simple_context, id, result, NULL); + if (ret != SASL_OK) + return ret; + + if (required && !*result) { + PARAMERROR(utils); + return SASL_BADPARAM; + } + } + + return ret; +} + +/* + * Retrieve the user password. + */ +int _plug_get_password(const sasl_utils_t *utils, sasl_secret_t **password, + unsigned int *iscopy, sasl_interact_t **prompt_need) +{ + int ret = SASL_FAIL; + sasl_getsecret_t *pass_cb; + void *pass_context; + sasl_interact_t *prompt; + + *password = NULL; + *iscopy = 0; + + /* see if we were given the password in the prompt */ + prompt = _plug_find_prompt(prompt_need, SASL_CB_PASS); + if (prompt != NULL) { + /* We prompted, and got.*/ + + if (!prompt->result) { + SETERROR(utils, "Unexpectedly missing a prompt result"); + return SASL_BADPARAM; + } + + /* copy what we got into a secret_t */ + *password = (sasl_secret_t *) utils->malloc(sizeof(sasl_secret_t) + + prompt->len + 1); + if (!*password) { + MEMERROR(utils); + return SASL_NOMEM; + } + + (*password)->len=prompt->len; + memcpy((*password)->data, prompt->result, prompt->len); + (*password)->data[(*password)->len]=0; + + *iscopy = 1; + + return SASL_OK; + } + + /* Try to get the callback... */ + ret = utils->getcallback(utils->conn, SASL_CB_PASS, + (sasl_callback_ft *)&pass_cb, &pass_context); + + if (ret == SASL_OK && pass_cb) { + ret = pass_cb(utils->conn, pass_context, SASL_CB_PASS, password); + if (ret != SASL_OK) + return ret; + + if (!*password) { + PARAMERROR(utils); + return SASL_BADPARAM; + } + } + + return ret; +} + +/* + * Retrieve the string given by the challenge prompt id. + */ +int _plug_challenge_prompt(const sasl_utils_t *utils, unsigned int id, + const char *challenge, const char *promptstr, + const char **result, sasl_interact_t **prompt_need) +{ + int ret = SASL_FAIL; + sasl_chalprompt_t *chalprompt_cb; + void *chalprompt_context; + sasl_interact_t *prompt; + + *result = NULL; + + /* see if we were given the password in the prompt */ + prompt = _plug_find_prompt(prompt_need, id); + if (prompt != NULL) { + /* We prompted, and got.*/ + + if (!prompt->result) { + SETERROR(utils, "Unexpectedly missing a prompt result"); + return SASL_BADPARAM; + } + + *result = prompt->result; + return SASL_OK; + } + + /* Try to get the callback... */ + ret = utils->getcallback(utils->conn, id, + (sasl_callback_ft *)&chalprompt_cb, &chalprompt_context); + + if (ret == SASL_OK && chalprompt_cb) { + ret = chalprompt_cb(chalprompt_context, id, + challenge, promptstr, NULL, result, NULL); + if (ret != SASL_OK) + return ret; + + if (!*result) { + PARAMERROR(utils); + return SASL_BADPARAM; + } + } + + return ret; +} + +/* + * Retrieve the client realm. + */ +int _plug_get_realm(const sasl_utils_t *utils, const char **availrealms, + const char **realm, sasl_interact_t **prompt_need) +{ + int ret = SASL_FAIL; + sasl_getrealm_t *realm_cb; + void *realm_context; + sasl_interact_t *prompt; + + *realm = NULL; + + /* see if we were given the result in the prompt */ + prompt = _plug_find_prompt(prompt_need, SASL_CB_GETREALM); + if (prompt != NULL) { + /* We prompted, and got.*/ + + if (!prompt->result) { + SETERROR(utils, "Unexpectedly missing a prompt result"); + return SASL_BADPARAM; + } + + *realm = prompt->result; + return SASL_OK; + } + + /* Try to get the callback... */ + ret = utils->getcallback(utils->conn, SASL_CB_GETREALM, + (sasl_callback_ft *)&realm_cb, &realm_context); + + if (ret == SASL_OK && realm_cb) { + ret = realm_cb(realm_context, SASL_CB_GETREALM, availrealms, realm); + if (ret != SASL_OK) + return ret; + + if (!*realm) { + PARAMERROR(utils); + return SASL_BADPARAM; + } + } + + return ret; +} + +/* + * Make the requested prompts. (prompt==NULL means we don't want it) + */ +int _plug_make_prompts(const sasl_utils_t *utils, + sasl_interact_t **prompts_res, + const char *user_prompt, const char *user_def, + const char *auth_prompt, const char *auth_def, + const char *pass_prompt, const char *pass_def, + const char *echo_chal, + const char *echo_prompt, const char *echo_def, + const char *realm_chal, + const char *realm_prompt, const char *realm_def) +{ + int num = 1; + int alloc_size; + sasl_interact_t *prompts; + + if (user_prompt) num++; + if (auth_prompt) num++; + if (pass_prompt) num++; + if (echo_prompt) num++; + if (realm_prompt) num++; + + if (num == 1) { + SETERROR( utils, "make_prompts() called with no actual prompts" ); + return SASL_FAIL; + } + + alloc_size = sizeof(sasl_interact_t)*num; + prompts = utils->malloc(alloc_size); + if (!prompts) { + MEMERROR( utils ); + return SASL_NOMEM; + } + memset(prompts, 0, alloc_size); + + *prompts_res = prompts; + + if (user_prompt) { + (prompts)->id = SASL_CB_USER; + (prompts)->challenge = "Authorization Name"; + (prompts)->prompt = user_prompt; + (prompts)->defresult = user_def; + + prompts++; + } + + if (auth_prompt) { + (prompts)->id = SASL_CB_AUTHNAME; + (prompts)->challenge = "Authentication Name"; + (prompts)->prompt = auth_prompt; + (prompts)->defresult = auth_def; + + prompts++; + } + + if (pass_prompt) { + (prompts)->id = SASL_CB_PASS; + (prompts)->challenge = "Password"; + (prompts)->prompt = pass_prompt; + (prompts)->defresult = pass_def; + + prompts++; + } + + if (echo_prompt) { + (prompts)->id = SASL_CB_ECHOPROMPT; + (prompts)->challenge = echo_chal; + (prompts)->prompt = echo_prompt; + (prompts)->defresult = echo_def; + + prompts++; + } + + if (realm_prompt) { + (prompts)->id = SASL_CB_GETREALM; + (prompts)->challenge = realm_chal; + (prompts)->prompt = realm_prompt; + (prompts)->defresult = realm_def; + + prompts++; + } + + /* add the ending one */ + (prompts)->id = SASL_CB_LIST_END; + (prompts)->challenge = NULL; + (prompts)->prompt = NULL; + (prompts)->defresult = NULL; + + return SASL_OK; +} + +void _plug_decode_init(decode_context_t *text, + const sasl_utils_t *utils, unsigned int in_maxbuf) +{ + memset(text, 0, sizeof(decode_context_t)); + + text->utils = utils; + text->needsize = 4; + text->in_maxbuf = in_maxbuf; +} + +/* + * Decode as much of the input as possible (possibly none), + * using decode_pkt() to decode individual packets. + */ +int _plug_decode(decode_context_t *text, + const char *input, unsigned inputlen, + char **output, /* output buffer */ + unsigned *outputsize, /* current size of output buffer */ + unsigned *outputlen, /* length of data in output buffer */ + int (*decode_pkt)(void *rock, + const char *input, unsigned inputlen, + char **output, unsigned *outputlen), + void *rock) +{ + unsigned int tocopy; + unsigned diff; + char *tmp; + unsigned tmplen; + int ret; + + *outputlen = 0; + + while (inputlen) { /* more input */ + if (text->needsize) { /* need to get the rest of the 4-byte size */ + + /* copy as many bytes (up to 4) as we have into size buffer */ + tocopy = (inputlen > text->needsize) ? text->needsize : inputlen; + memcpy(text->sizebuf + 4 - text->needsize, input, tocopy); + text->needsize -= tocopy; + + input += tocopy; + inputlen -= tocopy; + + if (!text->needsize) { /* we have the entire 4-byte size */ + memcpy(&(text->size), text->sizebuf, 4); + text->size = ntohl(text->size); + + if (!text->size) /* should never happen */ + return SASL_FAIL; + + if (text->size > text->in_maxbuf) { + text->utils->log(NULL, SASL_LOG_ERR, + "encoded packet size too big (%d > %d)", + text->size, text->in_maxbuf); + return SASL_FAIL; + } + + if (!text->buffer) + text->buffer = text->utils->malloc(text->in_maxbuf); + if (text->buffer == NULL) return SASL_NOMEM; + + text->cursize = 0; + } else { + /* We do NOT have the entire 4-byte size... + * wait for more data */ + return SASL_OK; + } + } + + diff = text->size - text->cursize; /* bytes needed for full packet */ + + if (inputlen < diff) { /* not a complete packet, need more input */ + memcpy(text->buffer + text->cursize, input, inputlen); + text->cursize += inputlen; + return SASL_OK; + } + + /* copy the rest of the packet */ + memcpy(text->buffer + text->cursize, input, diff); + input += diff; + inputlen -= diff; + + /* decode the packet (no need to free tmp) */ + ret = decode_pkt(rock, text->buffer, text->size, &tmp, &tmplen); + if (ret != SASL_OK) return ret; + + /* append the decoded packet to the output */ + ret = _plug_buf_alloc(text->utils, output, outputsize, + *outputlen + tmplen + 1); /* +1 for NUL */ + if (ret != SASL_OK) return ret; + + memcpy(*output + *outputlen, tmp, tmplen); + *outputlen += tmplen; + + /* protect stupid clients */ + *(*output + *outputlen) = '\0'; + + /* reset for the next packet */ + text->needsize = 4; + } + + return SASL_OK; +} + +void _plug_decode_free(decode_context_t *text) +{ + if (text->buffer) text->utils->free(text->buffer); +} + +/* returns the realm we should pretend to be in */ +int _plug_parseuser(const sasl_utils_t *utils, + char **user, char **realm, const char *user_realm, + const char *serverFQDN, const char *input) +{ + int ret; + char *r; + + if(!user || !serverFQDN) { + PARAMERROR( utils ); + return SASL_BADPARAM; + } + + r = strchr(input, '@'); + if (!r) { + /* hmmm, the user didn't specify a realm */ + if(user_realm && user_realm[0]) { + ret = _plug_strdup(utils, user_realm, realm, NULL); + } else { + /* Default to serverFQDN */ + ret = _plug_strdup(utils, serverFQDN, realm, NULL); + } + + if (ret == SASL_OK) { + ret = _plug_strdup(utils, input, user, NULL); + } + } else { + r++; + ret = _plug_strdup(utils, r, realm, NULL); + *--r = '\0'; + *user = utils->malloc(r - input + 1); + if (*user) { + strncpy(*user, input, r - input +1); + } else { + MEMERROR( utils ); + ret = SASL_NOMEM; + } + *r = '@'; + } + + return ret; +} + +int _plug_make_fulluser(const sasl_utils_t *utils, + char **fulluser, + const char * useronly, + const char *realm) +{ + if(!fulluser || !useronly || !realm) { + PARAMERROR( utils ); + return (SASL_BADPARAM); + } + + *fulluser = utils->malloc (strlen(useronly) + strlen(realm) + 2); + if (*fulluser == NULL) { + MEMERROR( utils ); + return (SASL_NOMEM); + } + + strcpy (*fulluser, useronly); + strcat (*fulluser, "@"); + strcat (*fulluser, realm); + + return (SASL_OK); +} + +char * _plug_get_error_message (const sasl_utils_t *utils, +#ifdef WIN32 + DWORD error +#else + int error +#endif + ) +{ + char * return_value; +#ifdef WIN32 + LPVOID lpMsgBuf; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + if (_plug_strdup (utils, lpMsgBuf, &return_value, NULL) != SASL_OK) { + return_value = NULL; + } + + LocalFree( lpMsgBuf ); +#else /* !WIN32 */ + if (_plug_strdup (utils, strerror(error), &return_value, NULL) != SASL_OK) { + return_value = NULL; + } +#endif /* WIN32 */ + return (return_value); +} + +void _plug_snprintf_os_info (char * osbuf, int osbuf_len) +{ +#ifdef WIN32 + OSVERSIONINFOEX versioninfo; + char *sysname; + +/* : + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + TCHAR szCSDVersion[ 128 ]; +//Only NT SP 6 and later + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + */ + + versioninfo.dwOSVersionInfoSize = sizeof (versioninfo); + sysname = "Unknown Windows"; + + if (GetVersionEx ((OSVERSIONINFO *) &versioninfo) == FALSE) { + snprintf(osbuf, osbuf_len, "%s", sysname); + goto SKIP_OS_INFO; + } + + switch (versioninfo.dwPlatformId) { + case VER_PLATFORM_WIN32s: /* Win32s on Windows 3.1 */ + sysname = "Win32s on Windows 3.1"; +/* I can't test if dwBuildNumber has any meaning on Win32s */ + break; + + case VER_PLATFORM_WIN32_WINDOWS: /* 95/98/ME */ + switch (versioninfo.dwMinorVersion) { + case 0: + sysname = "Windows 95"; + break; + case 10: + sysname = "Windows 98"; + break; + case 90: + sysname = "Windows Me"; + break; + default: + sysname = "Unknown Windows 9X/ME series"; + break; + } +/* Clear the high order word, as it contains major/minor version */ + versioninfo.dwBuildNumber &= 0xFFFF; + break; + + case VER_PLATFORM_WIN32_NT: /* NT/2000/XP/.NET */ + if (versioninfo.dwMinorVersion > 99) { + } else { + switch (versioninfo.dwMajorVersion * 100 + versioninfo.dwMinorVersion) { + case 351: + sysname = "Windows NT 3.51"; + break; + case 400: + sysname = "Windows NT 4.0"; + break; + case 500: + sysname = "Windows 2000"; + break; + case 501: + sysname = "Windows XP/.NET"; /* or Windows .NET Server */ + break; + default: + sysname = "Unknown Windows NT series"; + break; + } + } + break; + + default: + break; + } + + snprintf(osbuf, osbuf_len, + "%s %s (Build %u)", + sysname, + versioninfo.szCSDVersion, + versioninfo.dwBuildNumber + ); + +SKIP_OS_INFO: + ; + +#else /* !WIN32 */ + struct utsname os; + + uname(&os); + snprintf(osbuf, osbuf_len, "%s %s", os.sysname, os.release); +#endif /* WIN32 */ +} + +#if defined(WIN32) +unsigned int plug_sleep (unsigned int seconds) +{ + long dwSec = seconds*1000; + Sleep (dwSec); + return 0; +} +#endif diff --git a/kdepim-runtime/resources/gmail/saslplugin/plugin_common.h b/kdepim-runtime/resources/gmail/saslplugin/plugin_common.h new file mode 100644 index 00000000..0f758975 --- /dev/null +++ b/kdepim-runtime/resources/gmail/saslplugin/plugin_common.h @@ -0,0 +1,222 @@ + +/* Generic SASL plugin utility functions + * Rob Siemborski + * $Id: plugin_common.h,v 1.21 2006/01/17 12:18:21 mel Exp $ + */ +/* + * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Office of Technology Transfer + * Carnegie Mellon University + * 5000 Forbes Avenue + * Pittsburgh, PA 15213-3890 + * (412) 268-4387, fax: (412) 268-7395 + * tech-transfer@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _PLUGIN_COMMON_H_ +#define _PLUGIN_COMMON_H_ + +#include + +#ifndef macintosh +#ifdef WIN32 +# include +#else +# include +# include +# include +# include +#endif /* WIN32 */ +#endif /* macintosh */ + +#include +#include +#include + +#ifdef WIN32 +#define PLUG_API __declspec(dllexport) +#else +#define PLUG_API extern +#endif + +#define SASL_CLIENT_PLUG_INIT( x ) \ +extern sasl_client_plug_init_t x##_client_plug_init; \ +PLUG_API int sasl_client_plug_init(const sasl_utils_t *utils, \ + int maxversion, int *out_version, \ + sasl_client_plug_t **pluglist, \ + int *plugcount) { \ + return x##_client_plug_init(utils, maxversion, out_version, \ + pluglist, plugcount); \ +} + +#define SASL_SERVER_PLUG_INIT( x ) \ +extern sasl_server_plug_init_t x##_server_plug_init; \ +PLUG_API int sasl_server_plug_init(const sasl_utils_t *utils, \ + int maxversion, int *out_version, \ + sasl_server_plug_t **pluglist, \ + int *plugcount) { \ + return x##_server_plug_init(utils, maxversion, out_version, \ + pluglist, plugcount); \ +} + +#define SASL_AUXPROP_PLUG_INIT( x ) \ +extern sasl_auxprop_init_t x##_auxprop_plug_init; \ +PLUG_API int sasl_auxprop_plug_init(const sasl_utils_t *utils, \ + int maxversion, int *out_version, \ + sasl_auxprop_plug_t **plug, \ + const char *plugname) {\ + return x##_auxprop_plug_init(utils, maxversion, out_version, \ + plug, plugname); \ +} + +#define SASL_CANONUSER_PLUG_INIT( x ) \ +extern sasl_canonuser_init_t x##_canonuser_plug_init; \ +PLUG_API int sasl_canonuser_init(const sasl_utils_t *utils, \ + int maxversion, int *out_version, \ + sasl_canonuser_plug_t **plug, \ + const char *plugname) {\ + return x##_canonuser_plug_init(utils, maxversion, out_version, \ + plug, plugname); \ +} + +/* note: msg cannot include additional variables, so if you want to + * do a printf-format string, then you need to call seterror yourself */ +#define SETERROR( utils, msg ) (utils)->seterror( (utils)->conn, 0, (msg) ) + +#ifndef MEMERROR +#define MEMERROR( utils ) \ + (utils)->seterror( (utils)->conn, 0, \ + "Out of Memory in " __FILE__ " near line %d", __LINE__ ) +#endif + +#ifndef PARAMERROR +#define PARAMERROR( utils ) \ + (utils)->seterror( (utils)->conn, 0, \ + "Parameter Error in " __FILE__ " near line %d", __LINE__ ) +#endif + +#ifndef SASLINT_H +typedef struct buffer_info +{ + char *data; + unsigned curlen; /* Current length of data in buffer */ + unsigned reallen; /* total length of buffer (>= curlen) */ +} buffer_info_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int _plug_ipfromstring(const sasl_utils_t *utils, const char *addr, + struct sockaddr *out, socklen_t outlen); +int _plug_iovec_to_buf(const sasl_utils_t *utils, const struct iovec *vec, + unsigned numiov, buffer_info_t **output); +int _plug_buf_alloc(const sasl_utils_t *utils, char **rwbuf, + unsigned *curlen, unsigned newlen); +int _plug_strdup(const sasl_utils_t * utils, const char *in, + char **out, int *outlen); +void _plug_free_string(const sasl_utils_t *utils, char **str); +void _plug_free_secret(const sasl_utils_t *utils, sasl_secret_t **secret); + +#define _plug_get_userid(utils, result, prompt_need) \ + _plug_get_simple(utils, SASL_CB_USER, 0, result, prompt_need) +#define _plug_get_authid(utils, result, prompt_need) \ + _plug_get_simple(utils, SASL_CB_AUTHNAME, 1, result, prompt_need) +int _plug_get_simple(const sasl_utils_t *utils, unsigned int id, int required, + const char **result, sasl_interact_t **prompt_need); + +int _plug_get_password(const sasl_utils_t *utils, sasl_secret_t **secret, + unsigned int *iscopy, sasl_interact_t **prompt_need); + +int _plug_challenge_prompt(const sasl_utils_t *utils, unsigned int id, + const char *challenge, const char *promptstr, + const char **result, sasl_interact_t **prompt_need); + +int _plug_get_realm(const sasl_utils_t *utils, const char **availrealms, + const char **realm, sasl_interact_t **prompt_need); + +int _plug_make_prompts(const sasl_utils_t *utils, + sasl_interact_t **prompts_res, + const char *user_prompt, const char *user_def, + const char *auth_prompt, const char *auth_def, + const char *pass_prompt, const char *pass_def, + const char *echo_chal, + const char *echo_prompt, const char *echo_def, + const char *realm_chal, + const char *realm_prompt, const char *realm_def); + +typedef struct decode_context { + const sasl_utils_t *utils; + unsigned int needsize; /* How much of the 4-byte size do we need? */ + char sizebuf[4]; /* Buffer to accumulate the 4-byte size */ + unsigned int size; /* Absolute size of the encoded packet */ + char *buffer; /* Buffer to accumulate an encoded packet */ + unsigned int cursize; /* Amount of packet data in the buffer */ + unsigned int in_maxbuf; /* Maximum allowed size of an incoming encoded packet */ +} decode_context_t; + +void _plug_decode_init(decode_context_t *text, + const sasl_utils_t *utils, unsigned int in_maxbuf); + +int _plug_decode(decode_context_t *text, + const char *input, unsigned inputlen, + char **output, unsigned *outputsize, unsigned *outputlen, + int (*decode_pkt)(void *rock, + const char *input, unsigned inputlen, + char **output, unsigned *outputlen), + void *rock); + +void _plug_decode_free(decode_context_t *text); + +int _plug_parseuser(const sasl_utils_t *utils, + char **user, char **realm, const char *user_realm, + const char *serverFQDN, const char *input); + +int _plug_make_fulluser(const sasl_utils_t *utils, + char **fulluser, const char * useronly, const char *realm); + +char * _plug_get_error_message (const sasl_utils_t *utils, +#ifdef WIN32 + DWORD error +#else + int error +#endif + ); +void _plug_snprintf_os_info (char * osbuf, int osbuf_len); + +#ifdef __cplusplus +} +#endif + +#endif /* _PLUGIN_COMMON_H_ */ diff --git a/kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin.c b/kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin.c new file mode 100644 index 00000000..c44b8cb3 --- /dev/null +++ b/kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin.c @@ -0,0 +1,243 @@ +/* + Copyright (c) 2014 Daniel Vrátil + + 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. +*/ + +/** + * This is a very simple and single-purpose implementation of XOAUTH2 protocol + * for SASL used by KDE's KIMAP and Gmail resource in order to support OAuth + * Gmail authentication. + * + * This plugin does not even have a server-side part, and the client-side makes + * assumptions about it's use related to how KIMAP's LoginJob is implemented, + * so it's unsuitable for general-purpose use. + * + * The plugin is called libkdexoauth2.so to avoid conflict in case anyone ever + * writes a proper XOAUTH2 plugin for SASL. + */ + +#include +#include +#include +#include +#include + +#include "plugin_common.h" + +/***************************** Common Section *****************************/ + +static const char plugin_id[] = "$Id: plain.c,v 1.64 2004/09/08 11:06:11 mel Exp $"; + + +/***************************** Client Section *****************************/ + +typedef struct client_context { + char *out_buf; + unsigned out_buf_len; +} client_context_t; + +static int xoauth2_client_mech_new(void *glob_context, + sasl_client_params_t *params, + void **conn_context) +{ + client_context_t *context; + + /* Q_UNUSED */ + (void)glob_context; + + /* holds state are in */ + context = params->utils->malloc(sizeof(client_context_t)); + if (context == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + memset(context, 0, sizeof(client_context_t)); + + *conn_context = context; + + return SASL_OK; +} + +static int xoauth2_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + client_context_t *context = (client_context_t *) conn_context; + const sasl_utils_t *utils = params->utils; + const char *authid = NULL, *token = NULL; + int auth_result = SASL_OK; + int token_result = SASL_OK; + int result; + *clientout = NULL; + *clientoutlen = 0; + + /* Q_UNUSED */ + (void) serverin; + (void) serverinlen; + + if (params->props.min_ssf > params->external_ssf) { + SETERROR(params->utils, "SSF requested of XOAUTH2 plugin"); + return SASL_TOOWEAK; + } + + /* try to get the authid */ + if (oparams->authid == NULL) { + auth_result = _plug_get_authid(utils, &authid, prompt_need); + if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) { + return auth_result; + } + } + + /* try to get oauth token */ + if (token == NULL) { + /* We don't use _plug_get_password because we don't really care much about + safety of the OAuth token */ + token_result = _plug_get_simple(utils, SASL_CB_PASS, 1, &token, prompt_need); + if ((token_result != SASL_OK) && (token_result != SASL_INTERACT)) { + return token_result; + } + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if ((auth_result == SASL_INTERACT) ||(token_result == SASL_INTERACT)) { + /* make the prompt list */ + result = + _plug_make_prompts(utils, prompt_need, + NULL, NULL, + auth_result == SASL_INTERACT ? + "Please enter your authentication name" : NULL, + NULL, + token_result == SASL_INTERACT ? + "Please enter OAuth token" : NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + if (result != SASL_OK) { + return result; + } + + return SASL_INTERACT; + } + + /* FIXME: Fail if username is missing too? */ + if (!token) { + PARAMERROR(params->utils); + return SASL_BADPARAM; + } + + result = params->canon_user(utils->conn, authid, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) { + return result; + } + + /* https://developers.google.com/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism */ + *clientoutlen = 5 /* user=*/ + + ((authid && *authid) ? strlen(authid) : 0) /* %s */ + + 1 /* \001 */ + + 12 /* auth=Bearer{space} */ + + ((token && *token) ? strlen(token) : 0) /* %s */ + + 2; /* \001\001 */ + + /* remember the extra NUL on the end for stupid clients */ + result = _plug_buf_alloc(params->utils, &(context->out_buf), + &(context->out_buf_len), *clientoutlen + 1); + if (result != SASL_OK) { + return result; + } + + snprintf(context->out_buf, context->out_buf_len, "user=%s\1auth=Bearer %s\1\1", authid, token); + + *clientout = context->out_buf; + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return SASL_OK; +} + +static void xoauth2_client_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *context = (client_context_t *) conn_context; + if (!context) { + return; + } + + if (context->out_buf) { + printf("%s", context->out_buf); + utils->free(context->out_buf); + } + utils->free(context); +} + +static sasl_client_plug_t xoauth2_client_plugins[] = +{ + { + "XOAUTH2", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS + | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY, /* features */ + NULL, /* required_prompts */ + NULL, /* glob_context */ + &xoauth2_client_mech_new, /* mech_new */ + &xoauth2_client_mech_step, /* mech_step */ + &xoauth2_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int xoauth2_client_plug_init(sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR(utils, "XOAUTH2 version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = xoauth2_client_plugins; + *plugcount = 1; + + return SASL_OK; +} diff --git a/kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin_init.c b/kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin_init.c new file mode 100644 index 00000000..d36da606 --- /dev/null +++ b/kdepim-runtime/resources/gmail/saslplugin/xoauth2plugin_init.c @@ -0,0 +1,58 @@ +/* + Copyright (c) 2014 Daniel Vrátil + + 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 "config.h" + +#include +#include +#include +#ifndef macintosh +#include +#endif +#include +#include + +#include +#include +#include + +#include "plugin_common.h" + +#ifdef macintosh +#include +#endif + +#ifdef WIN32 +BOOL APIENTRY DllMain( HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#endif + +SASL_CLIENT_PLUG_INIT( xoauth2 ) diff --git a/kdepim-runtime/resources/gmail/settingsbase.kcfgc b/kdepim-runtime/resources/gmail/settingsbase.kcfgc new file mode 100644 index 00000000..2c1ec470 --- /dev/null +++ b/kdepim-runtime/resources/gmail/settingsbase.kcfgc @@ -0,0 +1,6 @@ +File=gmailresource.kcfg +ClassName=SettingsBase +Mutators=true +SetUserTexts=true +Singleton=false +GlobalEnums=true diff --git a/kdepim-runtime/resources/google/CMakeLists.txt b/kdepim-runtime/resources/google/CMakeLists.txt new file mode 100644 index 00000000..92b8be83 --- /dev/null +++ b/kdepim-runtime/resources/google/CMakeLists.txt @@ -0,0 +1,15 @@ +include_directories(${LibKGAPI2_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + + +if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + include_directories(${ACCOUNTSQT_INCLUDE_DIRS} ${SIGNONQT_INCLUDE_DIRS} ../) + add_definitions(-DHAVE_ACCOUNTS) + set(accounts_SRCS ../../shared/getcredentialsjob.cpp) +endif() + +macro_optional_add_subdirectory(calendar) +macro_optional_add_subdirectory(contacts) + diff --git a/kdepim-runtime/resources/google/calendar/CMakeLists.txt b/kdepim-runtime/resources/google/calendar/CMakeLists.txt new file mode 100644 index 00000000..ac5e7d17 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/CMakeLists.txt @@ -0,0 +1,77 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +include_directories(${QJSON_INCLUDE_DIR} + ${qjson_INCLUDE_DIR} +) + +set(calendarresource_SRCS + calendarresource.cpp + defaultreminderattribute.cpp + settings.cpp + settingsdialog.cpp + ../common/googleresource.cpp + ../common/googleaccountmanager.cpp + ../common/googlesettings.cpp + ../common/googlesettingsdialog.cpp + ${accounts_SRCS} +) + +kde4_add_kcfg_files(calendarresource_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfgc) + +kcfg_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfg + org.kde.Akonadi.GoogleCalendar.Settings +) + +qt4_add_dbus_adaptor(calendarresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.GoogleCalendar.Settings.xml + ${CMAKE_CURRENT_SOURCE_DIR}/settings.h Settings +) + +kde4_add_executable(akonadi_googlecalendar_resource RUN_UNINSTALLED ${calendarresource_SRCS}) + +if(Q_WS_MAC) + set_target_properties(akonadi_googlecalendar_resource PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../../Info.plist.template + ) + set_target_properties(akonadi_googlecalendar_resource PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.googlecalendar" + ) + set_target_properties(akonadi_googlecalendar_resource PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Google Calendar Resource" + ) +endif() + + + + +target_link_libraries(akonadi_googlecalendar_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_CALENDAR_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEWEBKIT_LIBS} + ${QJSON_LIBRARIES} + ${QJSON_LIBRARY} # for Mac OSX + ${qjson_LIBRARY} # for Debian + ${LibKGAPI2_LIBRARY} +) + +if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + target_link_libraries(akonadi_googlecalendar_resource + ${ACCOUNTSQT_LIBRARIES} + ${SIGNONQT_LIBRARIES}) +endif() + + +install(TARGETS akonadi_googlecalendar_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install( + FILES googlecalendarresource.desktop + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" +) diff --git a/kdepim-runtime/resources/google/calendar/Messages.sh b/kdepim-runtime/resources/google/calendar/Messages.sh new file mode 100644 index 00000000..bd80228c --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_googlecalendar_resource.pot +$XGETTEXT ../common/*.cpp -j -o $podir/akonadi_googlecalendar_resource.pot diff --git a/kdepim-runtime/resources/google/calendar/calendarresource.cpp b/kdepim-runtime/resources/google/calendar/calendarresource.cpp new file mode 100644 index 00000000..e6e286bb --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/calendarresource.cpp @@ -0,0 +1,766 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "calendarresource.h" +#include "defaultreminderattribute.h" +#include "settings.h" +#include "settingsdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ROOT_COLLECTION_REMOTEID QLatin1String("RootCollection") +#define CALENDARS_PROPERTY "_KGAPI2CalendarPtr" +#define TASK_PROPERTY "_KGAPI2::TaskPtr" + +Q_DECLARE_METATYPE( KGAPI2::ObjectsList ) +Q_DECLARE_METATYPE( KGAPI2::TaskPtr ) + +using namespace Akonadi; +using namespace KGAPI2; + +CalendarResource::CalendarResource( const QString &id ): + GoogleResource( id ) +{ + AttributeFactory::registerAttribute< DefaultReminderAttribute >(); + KGlobal::locale()->insertCatalog( QLatin1String("akonadi_google_resource") ); + updateResourceName(); +} + +CalendarResource::~CalendarResource() +{ +} + +GoogleSettings *CalendarResource::settings() const +{ + return Settings::self(); +} + +int CalendarResource::runConfigurationDialog( WId windowId ) +{ + QScopedPointer settingsDialog( new SettingsDialog( accountManager(), windowId, this ) ); + settingsDialog->setWindowIcon( KIcon( QLatin1String("im-google") ) ); + + return settingsDialog->exec(); +} + +void CalendarResource::updateResourceName() +{ + const QString accountName = Settings::self()->account(); + setName( i18nc( "%1 is account name (user@gmail.com)", "Google Calendars and Tasks (%1)", accountName.isEmpty() ? i18n( "not configured" ) : accountName ) ); +} + +QList< QUrl > CalendarResource::scopes() const +{ + QList scopes; + scopes << Account::calendarScopeUrl() + << Account::tasksScopeUrl(); + + return scopes; +} + + +void CalendarResource::retrieveItems( const Akonadi::Collection &collection ) +{ + if ( !canPerformTask() ) { + return; + } + + // https://bugs.kde.org/show_bug.cgi?id=308122: we can only request changes in + // max. last 25 days, otherwise we get an error. + int lastSyncDelta = -1; + if ( !collection.remoteRevision().isEmpty() ) { + lastSyncDelta = QDateTime::currentDateTimeUtc().toTime_t() - collection.remoteRevision().toUInt(); + } + + KGAPI2::Job *job = 0; + if ( collection.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) ) { + EventFetchJob *fetchJob = new EventFetchJob( collection.remoteId(), account(), this ); + if ( lastSyncDelta > -1 && lastSyncDelta < 25 * 24 * 3600 ) { + fetchJob->setFetchOnlyUpdated( collection.remoteRevision().toULongLong() ); + } + if ( !Settings::self()->eventsSince().isEmpty() ) { + const QDate date = QDate::fromString( Settings::self()->eventsSince(), Qt::ISODate ); + fetchJob->setTimeMin( QDateTime( date ).toTime_t() ); + } + job = fetchJob; + } else if ( collection.contentMimeTypes().contains( KCalCore::Todo::todoMimeType() ) ) { + TaskFetchJob *fetchJob = new TaskFetchJob( collection.remoteId(), account(), this ); + if ( lastSyncDelta > -1 && lastSyncDelta < 25 * 25 * 3600 ) { + fetchJob->setFetchOnlyUpdated( collection.remoteRevision().toULongLong() ); + } + job = fetchJob; + } else { + itemsRetrieved( Item::List() ); + return; + } + + job->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( job, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotItemsRetrieved(KGAPI2::Job*)) ); +} + +void CalendarResource::retrieveCollections() +{ + if ( !canPerformTask() ) { + return; + } + + CalendarFetchJob *fetchJob = new CalendarFetchJob( account(), this ); + connect( fetchJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCalendarsRetrieved(KGAPI2::Job*)) ); +} + +void CalendarResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + if ( ( !collection.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) && + !collection.contentMimeTypes().contains( KCalCore::Todo::todoMimeType() ) ) || + ( !canPerformTask( item, KCalCore::Event::eventMimeType() ) && + !canPerformTask( item, KCalCore::Todo::todoMimeType() ) ) ) { + return; + } + + if ( collection.parentCollection() == Akonadi::Collection::root() ) { + cancelTask( i18n( "The top-level collection cannot contain any tasks or events" ) ); + return; + } + + KGAPI2::Job *job = 0; + if ( item.hasPayload() ) { + KCalCore::Event::Ptr event = item.payload(); + EventPtr kevent( new Event( *event ) ); + kevent->setUid( QLatin1String("") ); + + job = new EventCreateJob( kevent, collection.remoteId(), account(), this ); + + } else if ( item.hasPayload() ) { + KCalCore::Todo::Ptr todo = item.payload(); + TaskPtr ktodo( new Task( *todo ) ); + ktodo->setUid( QLatin1String("") ); + + if ( !ktodo->relatedTo( KCalCore::Incidence::RelTypeParent ).isEmpty() ) { + Akonadi::Item parentItem; + parentItem.setGid( ktodo->relatedTo( KCalCore::Incidence::RelTypeParent ) ); + + ItemFetchJob *fetchJob = new ItemFetchJob( parentItem, this ); + fetchJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + fetchJob->setProperty( TASK_PROPERTY, QVariant::fromValue( ktodo ) ); + + connect( fetchJob, SIGNAL(finished(KJob*)), + this, SLOT(slotTaskAddedSearchFinished(KJob*)) ); + return; + } else { + job = new TaskCreateJob( ktodo, collection.remoteId(), account(), this ); + } + } else { + cancelTask( i18n( "Invalid payload type" ) ); + return; + } + + job->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCreateJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::itemChanged( const Akonadi::Item &item, + const QSet< QByteArray > &partIdentifiers ) +{ + Q_UNUSED( partIdentifiers ); + + if ( !canPerformTask( item, KCalCore::Event::eventMimeType() ) && + !canPerformTask( item, KCalCore::Todo::todoMimeType() ) ) { + return; + } + + KGAPI2::Job *job = 0; + if ( item.hasPayload() ) { + KCalCore::Event::Ptr event = item.payload(); + EventPtr kevent( new Event( *event ) ); + kevent->setUid( item.remoteId() ); + + job = new EventModifyJob( kevent, item.parentCollection().remoteId(), account(), this ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); + + } else if ( item.hasPayload() ) { + KCalCore::Todo::Ptr todo = item.payload(); + TaskPtr ktodo( new Task( *todo ) ); + QString parentUid = todo->relatedTo( KCalCore::Incidence::RelTypeParent ); + job = new TaskMoveJob( item.remoteId(), item.parentCollection().remoteId(), parentUid, account(), this ); + job->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotModifyTaskReparentFinished(KGAPI2::Job*)) ); + } else { + cancelTask( i18n( "Invalid payload type" ) ); + return; + } + + job->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); +} + +void CalendarResource::itemRemoved( const Akonadi::Item &item ) +{ + if ( !canPerformTask() ) { + return; + } + + if ( item.mimeType() == KCalCore::Event::eventMimeType() ) { + KGAPI2::Job *job = new EventDeleteJob( item.remoteId(), item.parentCollection().remoteId(), account(), this ); + job->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); + + } else if ( item.mimeType() == KCalCore::Todo::todoMimeType() ) { + /* Google always automatically removes tasks with all their subtasks. In KOrganizer + * by default we only remove the item we are given. For this reason we have to first + * fetch all tasks, find all sub-tasks for the task being removed and detach them + * from the task. Only then the task can be safely removed. */ + ItemFetchJob *fetchJob = new ItemFetchJob( item.parentCollection() ); + fetchJob->setAutoDelete( true ); + fetchJob->fetchScope().fetchFullPayload( true ); + fetchJob->setProperty( ITEM_PROPERTY, qVariantFromValue( item ) ); + connect( fetchJob, SIGNAL(finished(KJob*)), + this, SLOT(slotRemoveTaskFetchJobFinished(KJob*)) ); + fetchJob->start(); + + } else { + cancelTask( i18n( "Invalid payload type. Expected event or todo, got %1", item.mimeType() ) ); + } +} + +void CalendarResource::itemMoved( const Item &item, + const Collection &collectionSource, + const Collection &collectionDestination ) +{ + if ( !canPerformTask() ) { + return; + } + + if ( collectionDestination.parentCollection() == Akonadi::Collection::root() ) { + cancelTask( i18n( "The top-level collection cannot contain any tasks or events" ) ); + return; + } + + if ( item.mimeType() != KCalCore::Event::eventMimeType() ) { + cancelTask( i18n( "Moving tasks between task lists is not supported" ) ); + return; + } + + KGAPI2::Job *job = new EventMoveJob( item.remoteId(), collectionSource.remoteId(), + collectionDestination.remoteId(), account(), + this ); + job->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::collectionAdded( const Collection &collection, const Collection &parent ) +{ + Q_UNUSED( parent ) + + if ( !canPerformTask() ) { + return; + } + + KGAPI2::Job *job; + if ( collection.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) ) { + CalendarPtr calendar( new Calendar() ); + calendar->setTitle( collection.displayName() ); + calendar->setEditable( true ); + job = new CalendarCreateJob( calendar, account(), this ); + + } if ( collection.contentMimeTypes().contains( KCalCore::Todo::todoMimeType() ) ) { + TaskListPtr taskList( new TaskList() ); + taskList->setTitle( collection.displayName() ); + + job = new TaskListCreateJob( taskList, account(), this ); + } else { + cancelTask( i18n( "Unknown collection mimetype" ) ); + return; + } + + job->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::collectionChanged( const Collection &collection ) +{ + if ( !canPerformTask() ) { + return; + } + + KGAPI2::Job *job; + if ( collection.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) ) { + CalendarPtr calendar( new Calendar() ); + calendar->setUid( collection.remoteId() ); + calendar->setTitle( collection.displayName() ); + calendar->setEditable( true ); + job = new CalendarModifyJob( calendar, account(), this ); + + } if ( collection.contentMimeTypes().contains( KCalCore::Todo::todoMimeType() ) ) { + TaskListPtr taskList( new TaskList() ); + taskList->setUid( collection.remoteId() ); + taskList->setTitle( collection.displayName() ); + job = new TaskListModifyJob( taskList, account(), this ); + } else { + cancelTask( i18n( "Unknown collection mimetype" ) ); + return; + } + + job->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::collectionRemoved( const Collection &collection ) +{ + if ( !canPerformTask() ) { + return; + } + + KGAPI2::Job *job; + if ( collection.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) ) { + job = new CalendarDeleteJob( collection.remoteId(), account(), this ); + + } if ( collection.contentMimeTypes().contains( KCalCore::Todo::todoMimeType() ) ) { + job = new TaskListDeleteJob( collection.remoteId(), account(), this ); + + } else { + cancelTask( i18n( "Unknown collection mimetype" ) ); + return; + } + + job->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::slotCalendarsRetrieved( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + CalendarFetchJob *calendarJob = qobject_cast(job); + ObjectsList objects = calendarJob->items(); + calendarJob->deleteLater(); + + TaskListFetchJob *fetchJob = new TaskListFetchJob( job->account(), this ); + fetchJob->setProperty( CALENDARS_PROPERTY, QVariant::fromValue( objects ) ); + connect( fetchJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCollectionsRetrieved(KGAPI2::Job*)) ); +} + + +void CalendarResource::slotCollectionsRetrieved( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + TaskListFetchJob *fetchJob = qobject_cast( job ); + ObjectsList calendars = fetchJob->property( CALENDARS_PROPERTY ).value(); + ObjectsList taskLists = fetchJob->items(); + + CachePolicy cachePolicy; + if ( Settings::self()->enableIntervalCheck() ) { + cachePolicy.setInheritFromParent( false ); + cachePolicy.setIntervalCheckTime( Settings::self()->intervalCheckTime() ); + } + + m_rootCollection = Collection(); + m_rootCollection.setContentMimeTypes( QStringList() << Collection::mimeType() ); + m_rootCollection.setRemoteId( ROOT_COLLECTION_REMOTEID ); + m_rootCollection.setName( fetchJob->account()->accountName() ); + m_rootCollection.setParentCollection( Collection::root() ); + m_rootCollection.setRights( Collection::CanCreateCollection ); + m_rootCollection.setCachePolicy( cachePolicy ); + + EntityDisplayAttribute *attr = m_rootCollection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( fetchJob->account()->accountName() ); + attr->setIconName( QLatin1String( "im-google" ) ); + + m_collections[ ROOT_COLLECTION_REMOTEID ] = m_rootCollection; + + const QStringList activeCalendars = Settings::self()->calendars(); + Q_FOREACH( const ObjectPtr &object, calendars ) { + const CalendarPtr &calendar = object.dynamicCast(); + + if ( !activeCalendars.contains( calendar->uid() ) ) { + continue; + } + + Collection collection; + collection.setContentMimeTypes( QStringList() << KCalCore::Event::eventMimeType() ); + collection.setName( calendar->uid() ); + collection.setParentCollection( m_rootCollection ); + collection.setRemoteId( calendar->uid() ); + if ( calendar->editable() ) { + collection.setRights( Collection::CanChangeCollection | + Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem ); + } else { + collection.setRights( 0 ); + } + + EntityDisplayAttribute *attr = collection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( calendar->title() ); + attr->setIconName( QLatin1String("view-calendar") ); + + DefaultReminderAttribute *reminderAttr = collection.attribute( Entity::AddIfMissing ); + reminderAttr->setReminders( calendar->defaultReminders() ); + + // Block email reminders, since Google sends them for us + BlockAlarmsAttribute *blockAlarms = collection.attribute( Entity::AddIfMissing ); + blockAlarms->blockAlarmType( KCalCore::Alarm::Audio, false ); + blockAlarms->blockAlarmType( KCalCore::Alarm::Display, false ); + blockAlarms->blockAlarmType( KCalCore::Alarm::Procedure, false ); + + m_collections[ collection.remoteId() ] = collection; + } + + const QStringList activeTaskLists = Settings::self()->taskLists(); + Q_FOREACH( const ObjectPtr &object, taskLists ) { + const TaskListPtr &taskList = object.dynamicCast(); + + if ( !activeTaskLists.contains( taskList->uid() ) ) { + continue; + } + + Collection collection; + collection.setContentMimeTypes( QStringList() << KCalCore::Todo::todoMimeType() ); + collection.setName( taskList->uid() ); + collection.setParentCollection( m_rootCollection ); + collection.setRemoteId( taskList->uid() ); + collection.setRights( Collection::CanChangeCollection | + Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem ); + + EntityDisplayAttribute *attr = collection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( taskList->title() ); + attr->setIconName( QLatin1String("view-pim-tasks") ); + + m_collections[ collection.remoteId() ] = collection; + } + + collectionsRetrieved( m_collections.values() ); + + job->deleteLater(); +} + +void CalendarResource::slotItemsRetrieved( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + Item::List changedItems, removedItems; + Collection collection = job->property( COLLECTION_PROPERTY ).value(); + DefaultReminderAttribute *attr = collection.attribute(); + bool isIncremental = false; + + ObjectsList objects = qobject_cast( job )->items(); + if ( collection.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) ) { + QMap< QString, EventPtr > recurrentEvents; + + isIncremental = ( qobject_cast( job )->fetchOnlyUpdated() > 0 ); + + /* Step 1: Find all recurrent events and move them to a separate map */ + int i = 0; + while (i < objects.length()) { + EventPtr event = objects.at(i).dynamicCast(); + if ( event->recurs() && !event->deleted() ) { + recurrentEvents.insert( event->uid(), event ); + objects.removeAt(i); + } else { + i++; + } + } + + /* Step 2: Process all remaining events */ + Q_FOREACH( const ObjectPtr &object, objects ) { + EventPtr event = object.dynamicCast(); + + /* If current event is related to a recurrent event stored in the map then + * take the original recurrent event, set date of the current event as an + * exception and continue. We will process content of the map later. */ + if ( recurrentEvents.contains( event->uid() ) ) { + EventPtr rEvent = recurrentEvents.value( event->uid() ); + rEvent->recurrence()->addExDate( event->dtStart().date() ); + continue; + } + + if ( event->useDefaultReminders() && attr ) { + KCalCore::Alarm::List alarms = attr->alarms( event.data() ); + Q_FOREACH ( KCalCore::Alarm::Ptr alarm, alarms ) { + event->addAlarm( alarm ); + } + } + + Item item; + item.setMimeType( KCalCore::Event::eventMimeType() ); + item.setParentCollection( collection ); + item.setRemoteId( event->uid() ); + item.setRemoteRevision( event->etag() ); + item.setPayload( event.dynamicCast() ); + + if ( event->deleted() ) { + removedItems << item; + } else { + changedItems << item; + } + } + + /* Step 3: Now process the recurrent events */ + Q_FOREACH ( const EventPtr &event, recurrentEvents.values() ) { + + Item item; + item.setRemoteId( event->uid() ); + item.setRemoteRevision( event->etag() ); + item.setPayload< KCalCore::Event::Ptr >( event.dynamicCast() ); + item.setMimeType( KCalCore::Event::eventMimeType() ); + item.setParentCollection( collection ); + + changedItems << item; + } + + } else if ( collection.contentMimeTypes().contains( KCalCore::Todo::todoMimeType() ) ) { + + isIncremental = ( qobject_cast( job )->fetchOnlyUpdated() > 0 ); + + Q_FOREACH( const ObjectPtr &object, objects ) { + TaskPtr task = object.dynamicCast(); + + Item item; + item.setMimeType( KCalCore::Todo::todoMimeType() ); + item.setParentCollection( collection ); + item.setRemoteId( task->uid() ); + item.setRemoteRevision( task->etag() ); + item.setPayload( task.dynamicCast() ); + + if ( task->deleted() ) { + removedItems << item; + } else { + changedItems << item; + } + } + } + + if ( isIncremental ) { + itemsRetrievedIncremental( changedItems, removedItems ); + } else { + itemsRetrieved( changedItems ); + } + + collection.setRemoteRevision( QString::number( KDateTime::currentUtcDateTime().toTime_t() ) ); + new CollectionModifyJob( collection, this ); + + job->deleteLater(); +} + +void CalendarResource::slotModifyTaskReparentFinished( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + Item item = job->property( ITEM_PROPERTY ).value(); + KCalCore::Todo::Ptr todo = item.payload(); + TaskPtr ktodo( new Task( *todo.data() ) ); + + job = new TaskModifyJob( ktodo, item.parentCollection().remoteId(), job->account(), this ); + job->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( job, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::slotRemoveTaskFetchJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( i18n( "Failed to delete task: %1", job->errorString() ) ); + return; + } + + ItemFetchJob *fetchJob = qobject_cast( job ); + Item removedItem = fetchJob->property( ITEM_PROPERTY ).value(); + + Item::List detachItems; + + Item::List items = fetchJob->items(); + Q_FOREACH ( Item item, items ) { //krazy:exclude=foreach + if( !item.hasPayload() ) { + kDebug() << "Item " << item.remoteId() << " does not have Todo payload"; + continue; + } + + KCalCore::Todo::Ptr todo = item.payload(); + /* If this item is child of the item we want to remove then add it to detach list */ + if ( todo->relatedTo( KCalCore::Incidence::RelTypeParent ) == removedItem.remoteId() ) { + todo->setRelatedTo( QString(), KCalCore::Incidence::RelTypeParent ); + item.setPayload( todo ); + detachItems << item; + } + } + + /* If there are no items do detach, then delete the task right now */ + if ( detachItems.isEmpty() ) { + slotDoRemoveTask( job ); + return; + } + + /* Send modify request to detach all the sub-tasks from the task that is about to be + * removed. */ + ItemModifyJob *modifyJob = new ItemModifyJob( detachItems ); + modifyJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( removedItem ) ); + modifyJob->setAutoDelete( true ); + connect( modifyJob, SIGNAL(finished(KJob*)), + this, SLOT(slotDoRemoveTask(KJob*)) ); +} + +void CalendarResource::slotDoRemoveTask( KJob *job ) +{ + if ( job->error() ) { + cancelTask( i18n( "Failed to delete task: %1", job->errorString() ) ); + return; + } + + // Make sure account is still valid + if ( !canPerformTask() ) { + return; + } + + Item item = job->property( ITEM_PROPERTY ).value< Item >(); + + /* Now finally we can safely remove the task we wanted to */ + TaskDeleteJob *deleteJob = new TaskDeleteJob( item.remoteId(), item.parentCollection().remoteId(), account(), this); + deleteJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( deleteJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void CalendarResource::slotTaskAddedSearchFinished( KJob *job ) +{ + ItemFetchJob *fetchJob = qobject_cast( job ); + Item item = job->property( ITEM_PROPERTY ).value(); + TaskPtr task = job->property( TASK_PROPERTY ).value(); + + Item::List items = fetchJob->items(); + kDebug() << "Parent query returned" << items.count() << "results"; + + const QString tasksListId = item.parentCollection().remoteId(); + + // Make sure account is still valid + if ( !canPerformTask() ) { + return; + } + + KGAPI2::Job *newJob; + // The parent is not known, so give up and just store the item in Google + // without the information about parent. + if ( items.count() == 0 ) { + task->setRelatedTo( QString(), KCalCore::Incidence::RelTypeParent ); + newJob = new TaskCreateJob( task, tasksListId, account(), this ); + } else { + Item matchedItem = items.first(); + + task->setRelatedTo( matchedItem.remoteId(), KCalCore::Incidence::RelTypeParent ); + TaskCreateJob *createJob = new TaskCreateJob( task, tasksListId, account(), this ); + createJob->setParentItem( matchedItem.remoteId() ); + newJob = createJob; + } + + newJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( newJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCreateJobFinished(KGAPI2::Job*)) ); +} + + +void CalendarResource::slotCreateJobFinished( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + Item item = job->property( ITEM_PROPERTY ).value(); + + CreateJob *createJob = qobject_cast(job); + ObjectsList objects = createJob->items(); + Q_ASSERT( objects.count() > 0 ); + + if ( item.mimeType() == KCalCore::Event::eventMimeType() ) { + EventPtr event = objects.first().dynamicCast(); + item.setRemoteId( event->uid() ); + item.setRemoteRevision( event->etag() ); + item.setGid( event->uid() ); + changeCommitted( item ); + item.setPayload( event.dynamicCast() ); + new ItemModifyJob( item, this ); + } else if ( item.mimeType() == KCalCore::Todo::todoMimeType() ) { + TaskPtr task = objects.first().dynamicCast(); + item.setRemoteId( task->uid() ); + item.setRemoteRevision( task->etag() ); + item.setGid( task->uid() ); + changeCommitted( item ); + item.setPayload( task.dynamicCast() ); + new ItemModifyJob( item, this ); + } +} + +AKONADI_RESOURCE_MAIN( CalendarResource ) diff --git a/kdepim-runtime/resources/google/calendar/calendarresource.h b/kdepim-runtime/resources/google/calendar/calendarresource.h new file mode 100644 index 00000000..f02dac74 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/calendarresource.h @@ -0,0 +1,71 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CALENDAR_CALENDARRESOURCE_H +#define GOOGLE_CALENDAR_CALENDARRESOURCE_H + +#include "common/googleresource.h" + +#include +#include + +class CalendarResource : public GoogleResource +{ + Q_OBJECT + public: + explicit CalendarResource( const QString &id ); + ~CalendarResource(); + + public: + virtual GoogleSettings *settings() const; + virtual QList< QUrl > scopes() const; + + protected Q_SLOTS: + virtual void retrieveCollections(); + virtual void retrieveItems( const Akonadi::Collection &collection ); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet< QByteArray >& partIdentifiers ); + virtual void itemRemoved( const Akonadi::Item &item ); + virtual void itemMoved( const Akonadi::Item &item, + const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection ); + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + void slotItemsRetrieved( KGAPI2::Job *job ); + void slotCollectionsRetrieved( KGAPI2::Job *job ); + void slotCalendarsRetrieved( KGAPI2::Job *job ); + void slotRemoveTaskFetchJobFinished( KJob *job ); + void slotDoRemoveTask( KJob *job ); + void slotModifyTaskReparentFinished( KGAPI2::Job *job ); + void slotTaskAddedSearchFinished( KJob * ); + void slotCreateJobFinished( KGAPI2::Job *job ); + + protected: + virtual int runConfigurationDialog( WId windowId ); + virtual void updateResourceName(); + + private: + QMap m_collections; + Akonadi::Collection m_rootCollection; + +}; + +#endif // CALENDARRESOURCE_H diff --git a/kdepim-runtime/resources/google/calendar/defaultreminderattribute.cpp b/kdepim-runtime/resources/google/calendar/defaultreminderattribute.cpp new file mode 100644 index 00000000..5fa009d2 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/defaultreminderattribute.cpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "defaultreminderattribute.h" + +#include + +#include +#include + +#include + +using namespace KGAPI2; + +DefaultReminderAttribute::DefaultReminderAttribute() +{ +} + +Akonadi::Attribute *DefaultReminderAttribute::clone() const +{ + DefaultReminderAttribute *attr = new DefaultReminderAttribute(); + attr->setReminders( m_reminders ); + + return attr; +} + +void DefaultReminderAttribute::setReminders( const RemindersList &reminders ) +{ + m_reminders = reminders; +} + +void DefaultReminderAttribute::deserialize( const QByteArray &data ) +{ + QJson::Parser parser; + QVariantList list; + + list = parser.parse( data ).toList(); + Q_FOREACH( const QVariant & l, list ) { + QVariantMap reminder = l.toMap(); + + KGAPI2::ReminderPtr rem( new KGAPI2::Reminder ); + + if ( reminder[QLatin1String("type")].toString() == QLatin1String("display") ) { + rem->setType( KCalCore::Alarm::Display ); + } else if ( reminder[QLatin1String("type")].toString() == QLatin1String("email") ) { + rem->setType( KCalCore::Alarm::Email ); + } + + KCalCore::Duration offset( reminder[QLatin1String("time")].toInt(), KCalCore::Duration::Seconds ); + rem->setStartOffset( offset ); + + m_reminders << rem; + } +} + +QByteArray DefaultReminderAttribute::serialized() const +{ + QVariantList list; + + Q_FOREACH( const ReminderPtr & rem, m_reminders ) { + QVariantMap reminder; + + if ( rem->type() == KCalCore::Alarm::Display ) { + reminder[QLatin1String("type")] = QLatin1String("display"); + } else if ( rem->type() == KCalCore::Alarm::Email ) { + reminder[QLatin1String("type")] = QLatin1String("email"); + } + + reminder[QLatin1String("time")] = rem->startOffset().asSeconds(); + + list << reminder; + } + + QJson::Serializer serializer; + return serializer.serialize( list ); +} + +KCalCore::Alarm::List DefaultReminderAttribute::alarms( KCalCore::Incidence *incidence ) const +{ + KCalCore::Alarm::List alarms; + + Q_FOREACH( const ReminderPtr & reminder, m_reminders ) { + KCalCore::Alarm::Ptr alarm( new KCalCore::Alarm( incidence ) ); + + alarm->setType( reminder->type() ); + alarm->setTime( incidence->dtStart() ); + alarm->setStartOffset( reminder->startOffset() ); + alarm->setEnabled( true ); + + alarms << alarm; + } + + return alarms; +} + +QByteArray DefaultReminderAttribute::type() const +{ + return "defaultReminders"; +} + diff --git a/kdepim-runtime/resources/google/calendar/defaultreminderattribute.h b/kdepim-runtime/resources/google/calendar/defaultreminderattribute.h new file mode 100644 index 00000000..8cd86454 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/defaultreminderattribute.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CALENDAR_DEFAULTREMINDERATTRIBUTE_H +#define GOOGLE_CALENDAR_DEFAULTREMINDERATTRIBUTE_H + +#include + +#include + +#include +#include + +class DefaultReminderAttribute : public Akonadi::Attribute +{ + public: + explicit DefaultReminderAttribute( ); + + Attribute *clone() const; + void deserialize( const QByteArray &data ); + QByteArray serialized() const; + QByteArray type() const; + + void setReminders( const KGAPI2::RemindersList &reminders ); + KCalCore::Alarm::List alarms( KCalCore::Incidence *incidence ) const; + + private: + KGAPI2::RemindersList m_reminders; +}; + +#endif // DEFAULTREMINDERATTRIBUTE_H diff --git a/kdepim-runtime/resources/google/calendar/googlecalendarresource.desktop b/kdepim-runtime/resources/google/calendar/googlecalendarresource.desktop new file mode 100644 index 00000000..b5a1a078 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/googlecalendarresource.desktop @@ -0,0 +1,88 @@ +[Desktop Entry] +Name=Google Calendars and Tasks +Name[bs]=Google kalendari i zadaće +Name[ca]=Calendaris i tasques de Google +Name[ca@valencia]=Calendaris i tasques de Google +Name[cs]=Kalendáře a úkoly Google +Name[da]=Google kalendere og opgaver +Name[de]=Google-Kalender und -Tasks +Name[el]=Google ημεÏολόγια και εÏγασίες +Name[en_GB]=Google Calendars and Tasks +Name[es]=Calendarios Google y tareas +Name[et]=Google'i kalendrid ja ülesanded +Name[fi]=Google-kalenterit ja -tehtävät +Name[fr]=Agendas et tâches Google +Name[gl]=Google Calendars and Tasks +Name[hu]=Google naptárak és feladatok +Name[ia]=Calendarios e cargas de Google +Name[it]=Calendari e Attività Google +Name[kk]=Google күнтізбелер мен тапÑырмалар +Name[km]=ភារកិច្ច និង​ប្រážáž·áž‘áž·áž“ Google +Name[ko]=Google ìº˜ë¦°ë” ë° í•  ì¼ +Name[lt]=Google kalendoriai ir užduotys +Name[lv]=Google kalendÄri un uzdevumi +Name[nb]=Google kalendere og gjøremÃ¥l +Name[nds]=Google-Kalenners un -Opgaven +Name[nl]=Google agenda's en taken +Name[pl]=Kalendarze i zadania Google +Name[pt]=Calendários e Tarefas do Google +Name[pt_BR]=Calendários e tarefas do Google +Name[ru]=Задачи и календари Google +Name[sk]=Google kalendáre a úlohy +Name[sl]=Koledarji in opravila Google +Name[sr]=Гуглови календари и задаци +Name[sr@ijekavian]=Гуглови календари и задаци +Name[sr@ijekavianlatin]=Googleovi kalendari i zadaci +Name[sr@latin]=Googleovi kalendari i zadaci +Name[sv]=Google kalendrar och uppgifter +Name[tr]=Google Takvimleri ve Görevleri +Name[uk]=Календарі та запиÑи завдань Google +Name[x-test]=xxGoogle Calendars and Tasksxx +Name[zh_CN]=Google 日历和任务 +Name[zh_TW]=Google 行事曆與工作 +Comment=Access your Google Calendars and Tasks from KDE +Comment[bs]=Pristupite svojim Google kalendaru i zadacima iz KDE +Comment[ca]=Accediu als calendaris i tasques de Google des del KDE +Comment[ca@valencia]=Accediu als calendaris i tasques de Google des del KDE +Comment[da]=TilgÃ¥ dine Google kalendere og opgaver fra KDE +Comment[de]=Greifen Sie in KDE auf Ihre Google-Kalender und -Tasks zu +Comment[el]=Αποκτήστε Ï€Ïόσβαση στα Google ημεÏολόγια και τις εÏγασίες σας από το KDE +Comment[en_GB]=Access your Google Calendars and Tasks from KDE +Comment[es]=Acceda a sus calendarios Google y tareas desde KDE +Comment[et]=Oma Google'i kalendrite ja ülesannete kasutamine otse KDE-st +Comment[fi]=Google-kalentereihin ja -tehtäviin pääsy KDE:sta +Comment[fr]=Accès à vos agendas et listes de tâches Google depuis KDE +Comment[gl]=Acceda aos seus calendarios e tarefas de Google desde KDE. +Comment[hu]=A Google naptárának és feladatainak elérése a KDE-bÅ‘l +Comment[ia]=Accede a tu Calendarios e Cargas de Google ab KDE +Comment[it]=Accedi ai tuoi calendari e attività Google da KDE +Comment[kk]=Google күнтізбелер мен тапÑырмаларға KDE-ден қатынау +Comment[km]=ដំណើរការ​ប្រážáž·áž‘áž·áž“ Google របស់​អ្នក និង​ភារកិច្ច​ពី KDE +Comment[ko]=KDEì—ì„œ Google ìº˜ë¦°ë” ë° í•  ì¼ì— 접근하기 +Comment[lt]=Pasiekite savo Google kalendorius ir užduotis iÅ¡ KDE +Comment[lv]=Piekļūstiet saviem Google kalendÄriem un uzdevumiem no KDE +Comment[nb]=Bruk dine Google-kalendere og gjøremÃ¥l fra KDE +Comment[nds]=Ut KDE op Dien Google-Kalenners un -Opgaven togriepen +Comment[nl]=Heb toegang tot uw Google agenda's en taken vanuit KDE +Comment[pl]=Uzyskaj dostÄ™p do Kalendarzy i zadaÅ„ Google z KDE +Comment[pt]=Aceda aos seus calendários e tarefas da Google a partir do KDE +Comment[pt_BR]=Acesse seus calendários e tarefas do Google a partir do KDE +Comment[ru]=ДоÑтуп к задачам и календарÑм Google из KDE +Comment[sk]=Pristupuje k vaÅ¡im Google kalendárom a úlohám z KDE +Comment[sl]=Dostopajte do svojih koledarjev in opravil Google +Comment[sr]=ПриÑтупите Ñвојим календарима и задацима на Гуглу из КДЕ‑а +Comment[sr@ijekavian]=ПриÑтупите Ñвојим календарима и задацима на Гуглу из КДЕ‑а +Comment[sr@ijekavianlatin]=Pristupite svojim kalendarima i zadacima na Googleu iz KDE‑a +Comment[sr@latin]=Pristupite svojim kalendarima i zadacima na Googleu iz KDE‑a +Comment[sv]=Kom Ã¥t Google kalendrar och uppgifter frÃ¥n KDE +Comment[tr]=Google Takvimlerinize ve Görevlerinize KDE'den eriÅŸin +Comment[uk]=ДоÑтуп до ваших календарів Ñ– запиÑів завдань Google з KDE +Comment[x-test]=xxAccess your Google Calendars and Tasks from KDExx +Comment[zh_CN]=在 KDE 中访问您的 Google 日历和任务 +Comment[zh_TW]=用 KDE å­˜å–您的 Google 行事曆與工作 +Type=AkonadiResource +Exec=akonadi_googlecalendar_resource +X-Akonadi-MimeTypes=text/calendar,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_googlecalendar_resource +Icon=im-google diff --git a/kdepim-runtime/resources/google/calendar/settings.cpp b/kdepim-runtime/resources/google/calendar/settings.cpp new file mode 100644 index 00000000..820aee98 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/settings.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2011, 2012 Dan Vratil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "settings.h" +#include "settingsadaptor.h" + +#include +#include + +#include + +class SettingsHelper +{ + public: + SettingsHelper() : q( 0 ) + { + } + + ~SettingsHelper() + { + delete q; + q = 0; + } + + Settings *q; +}; + +K_GLOBAL_STATIC( SettingsHelper, s_globalSettings ) + +Settings::Settings(): + GoogleSettings() +{ + Q_ASSERT( !s_globalSettings->q ); + s_globalSettings->q = this; + + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), this, + QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); +} + +Settings *Settings::self() +{ + if ( !s_globalSettings->q ) { + new Settings; + s_globalSettings->q->readConfig(); + } + + return s_globalSettings->q; + +} diff --git a/kdepim-runtime/resources/google/calendar/settings.h b/kdepim-runtime/resources/google/calendar/settings.h new file mode 100644 index 00000000..263ae8a5 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/settings.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2011, 2012 Dan Vratil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CALENDAR_SETTINGS_H +#define GOOGLE_CALENDAR_SETTINGS_H + +#include "common/googlesettings.h" + +class Settings: public GoogleSettings +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.GoogleCalendar.ExtendedSettings" ) + public: + Settings(); + static Settings *self(); + +}; + +#endif // SETTINGS_H diff --git a/kdepim-runtime/resources/google/calendar/settingsbase.kcfg b/kdepim-runtime/resources/google/calendar/settingsbase.kcfg new file mode 100644 index 00000000..ac58395d --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/settingsbase.kcfg @@ -0,0 +1,37 @@ + + + + + + + + + + + 0 + + + + + + + + + + + + + + + false + + + 60 + + + diff --git a/kdepim-runtime/resources/google/calendar/settingsbase.kcfgc b/kdepim-runtime/resources/google/calendar/settingsbase.kcfgc new file mode 100644 index 00000000..8eda3d31 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/settingsbase.kcfgc @@ -0,0 +1,7 @@ +File=settingsbase.kcfg +ClassName=SettingsBase +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +GlobalEnums=true \ No newline at end of file diff --git a/kdepim-runtime/resources/google/calendar/settingsdialog.cpp b/kdepim-runtime/resources/google/calendar/settingsdialog.cpp new file mode 100644 index 00000000..612318ca --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/settingsdialog.cpp @@ -0,0 +1,238 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "settingsdialog.h" +#include "settings.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace KGAPI2; + + +SettingsDialog::SettingsDialog( GoogleAccountManager *accountManager, WId windowId, GoogleResource *parent ): + GoogleSettingsDialog( accountManager, windowId, parent ) +{ + connect( this, SIGNAL(accepted()), + this, SLOT(saveSettings()) ); + connect( this, SIGNAL(currentAccountChanged(QString)), + this, SLOT(slotCurrentAccountChanged(QString)) ); + + + m_calendarsBox = new QGroupBox( i18n( "Calendars" ), this ); + mainWidget()->layout()->addWidget( m_calendarsBox ); + + QVBoxLayout *vbox = new QVBoxLayout( m_calendarsBox ); + + m_calendarsList = new KListWidget( m_calendarsBox ); + vbox->addWidget( m_calendarsList, 1 ); + + m_reloadCalendarsBtn = new KPushButton( KIcon( QLatin1String("view-refresh") ), i18n( "Reload" ), m_calendarsBox ); + vbox->addWidget( m_reloadCalendarsBtn ); + connect( m_reloadCalendarsBtn, SIGNAL(clicked(bool)), + this, SLOT(slotReloadCalendars()) ); + + QHBoxLayout *hbox = new QHBoxLayout; + vbox->addLayout( hbox ); + + m_eventsLimitLabel = new QLabel( i18nc( "Followed by a date picker widget", "&Fetch only new events since" ), this ); + hbox->addWidget( m_eventsLimitLabel ); + + m_eventsLimitCombo = new KDateComboBox( this ); + m_eventsLimitLabel->setBuddy( m_eventsLimitCombo ); + m_eventsLimitCombo->setMaximumDate( QDate::currentDate() ); + m_eventsLimitCombo->setMinimumDate( QDate::fromString( QLatin1String( "2000-01-01" ), Qt::ISODate ) ); + m_eventsLimitCombo->setOptions( KDateComboBox::EditDate | KDateComboBox::SelectDate | + KDateComboBox::DatePicker | KDateComboBox::WarnOnInvalid ); + if( Settings::self()->eventsSince().isEmpty() ) { + const QString ds = QString::fromLatin1( "%1-01-01" ).arg( QString::number( QDate::currentDate().year() - 3 ) ); + m_eventsLimitCombo->setDate( QDate::fromString( ds, Qt::ISODate ) ); + } else { + m_eventsLimitCombo->setDate( QDate::fromString( Settings::self()->eventsSince(), Qt::ISODate ) ); + } + hbox->addWidget( m_eventsLimitCombo ); + + m_taskListsBox = new QGroupBox( i18n( "Tasklists" ), this ); + mainWidget()->layout()->addWidget( m_taskListsBox ); + + vbox = new QVBoxLayout( m_taskListsBox ); + + m_taskListsList = new KListWidget( m_taskListsBox ); + vbox->addWidget( m_taskListsList, 1 ); + + m_reloadTaskListsBtn = new KPushButton( KIcon( QLatin1String("view-refresh") ), i18n( "Reload" ), m_taskListsBox ); + vbox->addWidget( m_reloadTaskListsBtn ); + connect( m_reloadTaskListsBtn, SIGNAL(clicked(bool)), + this, SLOT(slotReloadTaskLists()) ); +} + +SettingsDialog::~SettingsDialog() +{ +} + +void SettingsDialog::saveSettings() +{ + const AccountPtr account = currentAccount(); + if ( !currentAccount() ) { + Settings::self()->setAccount( QString() ); + Settings::self()->setCalendars( QStringList() ); + Settings::self()->setTaskLists( QStringList() ); + Settings::self()->setEventsSince( QString() ); + Settings::self()->writeConfig(); + return; + } + + Settings::self()->setAccount( account->accountName() ); + + QStringList calendars; + for ( int i = 0; i < m_calendarsList->count(); i++ ) { + QListWidgetItem *item = m_calendarsList->item( i ); + + if ( item->checkState() == Qt::Checked ) { + calendars.append( item->data( Qt::UserRole ).toString() ); + } + } + Settings::self()->setCalendars( calendars ); + if ( m_eventsLimitCombo->isValid() ) { + Settings::self()->setEventsSince( m_eventsLimitCombo->date().toString( Qt::ISODate ) ); + } + + QStringList taskLists; + for ( int i = 0; i < m_taskListsList->count(); i++ ) { + QListWidgetItem *item = m_taskListsList->item( i ); + + if ( item->checkState() == Qt::Checked ) { + taskLists.append( item->data( Qt::UserRole ).toString() ); + } + } + Settings::self()->setTaskLists( taskLists ); + + + Settings::self()->writeConfig(); +} + +void SettingsDialog::slotCurrentAccountChanged( const QString &accountName ) +{ + if ( accountName.isEmpty() ) { + return; + } + + slotReloadCalendars(); + slotReloadTaskLists(); +} + +void SettingsDialog::slotReloadCalendars() +{ + const AccountPtr account = currentAccount(); + if ( !account ) { + return; + } + + CalendarFetchJob *fetchJob = new CalendarFetchJob( account, this ); + connect( fetchJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCalendarsRetrieved(KGAPI2::Job*)) ); + + m_calendarsBox->setDisabled( true ); + m_calendarsList->clear(); +} + +void SettingsDialog::slotReloadTaskLists() +{ + const AccountPtr account = currentAccount(); + if ( !account ) { + return; + } + + TaskListFetchJob *fetchJob = new TaskListFetchJob( account, this ); + connect( fetchJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotTaskListsRetrieved(KGAPI2::Job*)) ); + + m_taskListsBox->setDisabled( true ); + m_taskListsList->clear(); +} + +void SettingsDialog::slotCalendarsRetrieved( Job *job ) +{ + if ( !handleError( job ) || !currentAccount() ) { + m_calendarsBox->setEnabled( true ); + return; + } + + FetchJob *fetchJob = qobject_cast(job); + ObjectsList objects = fetchJob->items(); + + QStringList activeCalendars; + if ( currentAccount()->accountName() == Settings::self()->account() ) { + activeCalendars = Settings::self()->calendars(); + } + Q_FOREACH( const ObjectPtr &object, objects ) { + CalendarPtr calendar = object.dynamicCast(); + + QListWidgetItem *item = new QListWidgetItem( calendar->title() ); + item->setData( Qt::UserRole, calendar->uid() ); + item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable ); + item->setCheckState( ( activeCalendars.isEmpty() || activeCalendars.contains( calendar->uid() ) ) ? Qt::Checked : Qt::Unchecked ); + m_calendarsList->addItem( item ); + + } + + m_calendarsBox->setEnabled( true ); +} + +void SettingsDialog::slotTaskListsRetrieved( Job *job ) +{ + if ( !handleError( job ) || !currentAccount() ) { + m_taskListsBox->setEnabled( true ); + return; + } + + FetchJob *fetchJob = qobject_cast(job); + ObjectsList objects = fetchJob->items(); + + QStringList activeTaskLists; + if ( currentAccount()->accountName() == Settings::self()->account()) { + activeTaskLists = Settings::self()->taskLists(); + } + Q_FOREACH( const ObjectPtr &object, objects ) { + TaskListPtr taskList = object.dynamicCast(); + + QListWidgetItem *item = new QListWidgetItem( taskList->title() ); + item->setData( Qt::UserRole, taskList->uid() ); + item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable ); + item->setCheckState( ( activeTaskLists.isEmpty() || activeTaskLists.contains( taskList->uid() ) ) ? Qt::Checked : Qt::Unchecked ); + m_taskListsList->addItem( item ); + } + + m_taskListsBox->setEnabled( true ); +} diff --git a/kdepim-runtime/resources/google/calendar/settingsdialog.h b/kdepim-runtime/resources/google/calendar/settingsdialog.h new file mode 100644 index 00000000..574c35a7 --- /dev/null +++ b/kdepim-runtime/resources/google/calendar/settingsdialog.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CALENDAR_SETTINGSDIALOG_H +#define GOOGLE_CALENDAR_SETTINGSDIALOG_H + +#include "common/googlesettingsdialog.h" + +class KListWidget; +class QLabel; +class KDateComboBox; + +class SettingsDialog : public GoogleSettingsDialog +{ + Q_OBJECT + public: + explicit SettingsDialog( GoogleAccountManager *accountManager, WId windowId, GoogleResource *parent ); + ~SettingsDialog(); + + private Q_SLOTS: + void slotReloadCalendars(); + void slotReloadTaskLists(); + void slotCurrentAccountChanged( const QString &accountName ); + + void slotTaskListsRetrieved( KGAPI2::Job *job ); + void slotCalendarsRetrieved( KGAPI2::Job *job ); + + void saveSettings(); + + private: + QGroupBox *m_calendarsBox; + KListWidget *m_calendarsList; + KPushButton *m_reloadCalendarsBtn; + QLabel *m_eventsLimitLabel; + KDateComboBox *m_eventsLimitCombo; + + QGroupBox *m_taskListsBox; + KListWidget *m_taskListsList; + KPushButton *m_reloadTaskListsBtn; + +}; + +#endif // SETTINGSDIALOG_H diff --git a/kdepim-runtime/resources/google/common/googleaccountmanager.cpp b/kdepim-runtime/resources/google/common/googleaccountmanager.cpp new file mode 100644 index 00000000..2e715b6d --- /dev/null +++ b/kdepim-runtime/resources/google/common/googleaccountmanager.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "googleaccountmanager.h" + +#include + +#include +#include +#include + +#include + +#define WALLET_FOLDER QLatin1String("Akonadi Google") + +using namespace KGAPI2; + +GoogleAccountManager::GoogleAccountManager( QObject* parent ): + QObject( parent ), + m_isReady(false) +{ + QMetaObject::invokeMethod(this, "initManager", Qt::QueuedConnection); +} + +GoogleAccountManager::~GoogleAccountManager() +{ + delete m_wallet; +} + +bool GoogleAccountManager::isReady() const +{ + return m_isReady; +} + +void GoogleAccountManager::initManager() +{ + delete m_wallet; + + // FIXME: Don't use synchronous wallet + // With asynchronous wallet however we are unable to read any data from it + // in when slotWalletOpened() is called on walletOpened() signal + m_wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), + 0, KWallet::Wallet::Synchronous ); + slotWalletOpened( m_wallet != 0 ); +// connect( m_wallet, SIGNAL(walletOpened(bool)), +// this, SLOT(slotWalletOpened(bool)) ); + if ( m_wallet ) { + connect( m_wallet, SIGNAL(folderUpdated(QString)), + this, SLOT(slotFolderUpdated(QString)) ); + connect( m_wallet, SIGNAL(walletClosed()), + this, SLOT(slotWalletClosed()) ); + } +} + +void GoogleAccountManager::slotWalletOpened( bool success ) +{ + if ( !success ) { + kWarning() << "Failed to open wallet"; + Q_EMIT managerReady( false ); + return; + } + + if ( !m_wallet->hasFolder( WALLET_FOLDER ) ) { + if ( !m_wallet->createFolder( WALLET_FOLDER ) ) { + kWarning() << "Failed to create KWallet folder " << WALLET_FOLDER; + Q_EMIT managerReady( false ); + return; + } + } + + if ( !m_wallet->setFolder( WALLET_FOLDER ) ) { + kWarning() << "Failed to open KWallet folder" << WALLET_FOLDER; + Q_EMIT managerReady( false ); + return; + } + + // Populate the cache now + QStringList accountNames = m_wallet->entryList(); + Q_FOREACH( const QString &accountName, accountNames ) { + m_accounts[accountName] = findAccountInWallet( accountName ); + } + + m_isReady = true; + Q_EMIT managerReady( true ); +} + +void GoogleAccountManager::slotWalletClosed() +{ + m_isReady = false; + delete m_wallet; +} + +void GoogleAccountManager::slotFolderUpdated(const QString& folder) +{ + // We are interested only in the "Akonadi Google" folder + if ( folder != WALLET_FOLDER ) { + return; + } + + QStringList walletEntries = m_wallet->entryList(); + + Q_FOREACH( const AccountPtr &account, m_accounts ) { + AccountPtr changedAccount = findAccountInWallet( account->accountName() ); + if ( changedAccount.isNull() ) { + walletEntries.removeOne( account->accountName() ); + m_accounts.remove( account->accountName() ); + Q_EMIT accountRemoved( account->accountName() ); + continue; + } + + if (( account->accessToken() != changedAccount->accessToken() ) || + ( account->refreshToken() != changedAccount->refreshToken() ) || + ( account->scopes() != changedAccount->scopes() )) { + + walletEntries.removeOne( account->accountName() ); + m_accounts[account->accountName()] = changedAccount; + Q_EMIT accountChanged( changedAccount ); + } + } + + Q_FOREACH( const QString &accountName, walletEntries ) { + const AccountPtr newAccount = findAccountInWallet( accountName ); + + m_accounts[newAccount->accountName()] = newAccount; + Q_EMIT accountAdded( newAccount ); + } +} + +AccountPtr GoogleAccountManager::findAccount( const QString& accountName ) const +{ + if ( !m_isReady ) { + kWarning() << "Manager is not ready!"; + return AccountPtr(); + } + + if ( m_accounts.contains( accountName ) ) { + return m_accounts[accountName]; + } + + AccountPtr account = findAccountInWallet( accountName ); + if ( account.isNull() ) { + return AccountPtr(); + } + + m_accounts[accountName] = account; + return account; +} + +AccountPtr GoogleAccountManager::findAccountInWallet(const QString& accountName) const +{ + if ( !m_wallet->entryList().contains( accountName ) ) { + kDebug() << "Account" << accountName << "not found in KWallet"; + return AccountPtr(); + } + + QMap map; + m_wallet->readMap( accountName, map ); + + const QStringList scopes = map[QLatin1String( "scopes" )].split( QLatin1Char(','), QString::SkipEmptyParts ); + QList scopeUrls; + Q_FOREACH( const QString &scope, scopes ) { + scopeUrls << QUrl( scope ); + } + AccountPtr account( new Account( accountName, + map[QLatin1String( "accessToken" )], + map[QLatin1String( "refreshToken" )], + scopeUrls ) ); + + return account; +} + +bool GoogleAccountManager::storeAccount(const AccountPtr& account) +{ + if ( !m_isReady ) { + kWarning() << "Manager is not ready!"; + return false; + } + + QStringList scopes; + const QList urlScopes = account->scopes(); + Q_FOREACH(const QUrl &url, urlScopes) { + scopes << url.toString(); + } + + QMap map; + map[QLatin1String( "accessToken" )] = account->accessToken(); + map[QLatin1String( "refreshToken" )] = account->refreshToken(); + map[QLatin1String( "scopes" )] = scopes.join(QLatin1String(",")); + + if ( m_wallet->writeMap( account->accountName(), map) == 0 ) { + m_accounts[account->accountName()] = account; + return true; + } + + return false; +} + +bool GoogleAccountManager::removeAccount(const QString& accountName) +{ + if ( !m_isReady ) { + kWarning() << "Manager is not ready"; + return false; + } + + if ( !m_accounts.contains( accountName ) ) { + return true; + } + + if (m_wallet->removeEntry( accountName ) != 0) { + kWarning() << "Failed to remove account from KWallet"; + return false; + } + + m_accounts.remove( accountName ); + return true; +} + +AccountsList GoogleAccountManager::listAccounts() const +{ + if ( !m_isReady ) { + kWarning() << "Manager is not ready"; + return AccountsList(); + } + + return m_accounts.values(); +} + +void GoogleAccountManager::cleanup(const QString &accountName) +{ + removeAccount(accountName); +} + diff --git a/kdepim-runtime/resources/google/common/googleaccountmanager.h b/kdepim-runtime/resources/google/common/googleaccountmanager.h new file mode 100644 index 00000000..514f6667 --- /dev/null +++ b/kdepim-runtime/resources/google/common/googleaccountmanager.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef GOOGLEACCOUNTMANAGER_H +#define GOOGLEACCOUNTMANAGER_H + +#include +#include +#include + +#include + +namespace KWallet { + class Wallet; +} + +class GoogleAccountManager : public QObject +{ + Q_OBJECT + + public: + explicit GoogleAccountManager( QObject* parent = 0 ); + virtual ~GoogleAccountManager(); + + bool isReady() const; + + bool storeAccount( const KGAPI2::AccountPtr &account ); + KGAPI2::AccountPtr findAccount( const QString &accountName ) const; + bool removeAccount( const QString &accountName ); + KGAPI2::AccountsList listAccounts() const; + + void cleanup(const QString &accountName); + + Q_SIGNALS: + void managerReady( bool ready ); + void accountAdded( const KGAPI2::AccountPtr &account ); + void accountChanged( const KGAPI2::AccountPtr &account ); + void accountRemoved( const QString &accountName ); + + private Q_SLOTS: + void initManager(); + void slotWalletOpened( bool success ); + void slotWalletClosed(); + void slotFolderUpdated( const QString &folder ); + KGAPI2::AccountPtr findAccountInWallet( const QString &accountName ) const; + + private: + bool m_isReady; + QPointer m_wallet; + mutable QMap m_accounts; +}; + +#endif // GOOGLEACCOUNTMANAGER_H diff --git a/kdepim-runtime/resources/google/common/googleresource.cpp b/kdepim-runtime/resources/google/common/googleresource.cpp new file mode 100644 index 00000000..80d570e2 --- /dev/null +++ b/kdepim-runtime/resources/google/common/googleresource.cpp @@ -0,0 +1,444 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "googleresource.h" +#include "googlesettings.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#ifdef HAVE_ACCOUNTS +#include "shared/getcredentialsjob.h" +#endif + +#define ACCESS_TOKEN_PROPERTY "AccessToken" + +Q_DECLARE_METATYPE( KGAPI2::Job* ) + +using namespace KGAPI2; +using namespace Akonadi; + +GoogleResource::GoogleResource( const QString &id ): + ResourceBase( id ), + AgentBase::ObserverV2(), + m_isConfiguring(false) +{ + KGlobal::locale()->insertCatalog( QLatin1String("akonadi_google_resource") ); + connect( this, SIGNAL(abortRequested()), + this, SLOT(slotAbortRequested()) ); + connect( this, SIGNAL(reloadConfiguration()), + this, SLOT(reloadConfig()) ); + + setNeedsNetwork( true ); + + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All ); + changeRecorder()->fetchCollection( true ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + + m_accountMgr = new GoogleAccountManager( this ); + connect( m_accountMgr, SIGNAL(accountChanged(KGAPI2::AccountPtr)), + this, SLOT(slotAccountChanged(KGAPI2::AccountPtr)) ); + connect( m_accountMgr, SIGNAL(accountRemoved(QString)), + this, SLOT(slotAccountRemoved(QString)) ); + connect( m_accountMgr, SIGNAL(managerReady(bool)), + this, SLOT(slotAccountManagerReady(bool)) ); + + emit status( NotConfigured, i18n( "Waiting for KWallet..." ) ); +} + +GoogleResource::~GoogleResource() +{ +} + +void GoogleResource::cleanup() +{ + accountManager()->cleanup(settings()->account()); +} + +AccountPtr GoogleResource::account() const +{ + return m_account; +} + +GoogleAccountManager *GoogleResource::accountManager() const +{ + return m_accountMgr; +} + +void GoogleResource::aboutToQuit() +{ + slotAbortRequested(); +} + +void GoogleResource::abort() +{ + cancelTask( i18n( "Aborted" ) ); +} + +void GoogleResource::slotAbortRequested() +{ + abort(); +} + +void GoogleResource::configure( WId windowId ) +{ + if ( !m_accountMgr->isReady() || m_isConfiguring ) { + emit configurationDialogAccepted(); + return; + } + + m_isConfiguring = true; + if ( runConfigurationDialog( windowId ) == KDialog::Accepted ) { + updateResourceName(); + + emit configurationDialogAccepted(); + + m_account = accountManager()->findAccount( settings()->account() ); + if ( m_account.isNull() ) { + emit status( NotConfigured, i18n( "Configured account does not exist" ) ); + m_isConfiguring = false; + return; + } + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + synchronize(); + } else { + updateResourceName(); + + emit configurationDialogRejected(); + } + m_isConfiguring = false; +} + +void GoogleResource::updateAccountToken( const AccountPtr &account, KGAPI2::Job *restartJob ) +{ + if ( accountId() > 0 ) { + configureKAccounts( accountId(), restartJob ); + } else if ( !settings()->account().isEmpty() ) { + AuthJob *authJob = new AuthJob( account, settings()->clientId(), settings()->clientSecret(), this ); + authJob->setProperty( JOB_PROPERTY, QVariant::fromValue( restartJob ) ); + connect( authJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotAuthJobFinished(KGAPI2::Job*)) ); + } +} + + +void GoogleResource::reloadConfig() +{ + const QString accountName = settings()->account(); + + if ( accountId() > 0 ) { + if ( !configureKAccounts( accountId() ) ) { + emit status( Broken ); + return; + } + } else if ( !accountName.isEmpty() ) { + if ( !configureKGAPIAccount( m_accountMgr->findAccount( accountName ) ) ) { + emit status( NotConfigured, i18n( "Configured account does not exist" ) ); + return; + } + } else { + emit status( NotConfigured ); + return; + } + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); +} + +bool GoogleResource::configureKAccounts( int accountId, KGAPI2::Job *restartJob ) +{ + if ( accountId == 0 ) { + return false; + } +#ifdef HAVE_ACCOUNTS + GetCredentialsJob *gc = new GetCredentialsJob( accountId, this ); + gc->setProperty( JOB_PROPERTY, QVariant::fromValue( restartJob ) ); + connect( gc, SIGNAL(finished(KJob*)), this, SLOT(slotKAccountsCredentialsReceived(KJob*)) ); + gc->start(); + // SUCKS! + return true; +#else + Q_UNUSED( restartJob ); + return false; +#endif +} + +#ifdef HAVE_ACCOUNTS +void GoogleResource::slotKAccountsCredentialsReceived( KJob *job ) +{ + if ( job->error() ) { + emit status( Broken ); + // FIXME: Fallback to KGAPI account? + return; + } + + GetCredentialsJob *gc = qobject_cast( job ); + const QVariantMap data = gc->credentialsData(); + const QString accessToken = data.value( QLatin1String( "AccessToken" ) ).toString(); + + // Createa temporary account that we use to fetch full user name + KGAPI2::AccountPtr account( new KGAPI2::Account ); + account->setAccessToken( accessToken ); + account->setScopes( scopes() ); + + KGAPI2::Job *otherJob = 0; + if ( !job->property( JOB_PROPERTY ).isNull() ) { + otherJob = job->property( JOB_PROPERTY ).value(); + } + + if ( settings()->accountName().isEmpty() ) { + account->setAccountName( i18n( "Unknown Account" ) ); + AccountInfoFetchJob *aiJob = new AccountInfoFetchJob( account, this ); + aiJob->setProperty( ACCESS_TOKEN_PROPERTY, accessToken ); + if ( otherJob ) { + aiJob->setProperty( JOB_PROPERTY, QVariant::fromValue( otherJob ) ); + } + connect( aiJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotKAccountsAccountInfoReceived(KGAPI2::Job*)) ); + } else { + m_account = AccountPtr( new Account( settings()->accountName(), + accessToken ) ); + finishKAccountsAuthentication( otherJob ); + } +} + +void GoogleResource::slotKAccountsAccountInfoReceived( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + emit error( job->errorString() ); + cancelTask( i18n( "Failed to refresh tokens") ); + return; + } + + AccountInfoFetchJob *aiJob = qobject_cast( job ); + Q_ASSERT( aiJob ); + aiJob->deleteLater(); + + const AccountPtr account = job->account(); + + if ( aiJob->items().count() != 1 ) { + kWarning() << "AccountInfoFetchJob returned unexpected amount of results"; + emit error( i18n( "Invalid reply" ) ); + cancelTask( i18n( "Failed to refresh tokens") ); + return; + } + + AccountInfoPtr info = aiJob->items().first().dynamicCast(); + settings()->setAccountName( info->email() ); + m_account = AccountPtr( new Account( info->email(), + aiJob->property( ACCESS_TOKEN_PROPERTY ).toString() ) ); + settings()->writeConfig(); + + KGAPI2::Job *otherJob = 0; + if ( job->property( JOB_PROPERTY ).isNull() ) { + otherJob = job->property( JOB_PROPERTY ).value(); + } + + finishKAccountsAuthentication( otherJob ); +} + +void GoogleResource::finishKAccountsAuthentication( KGAPI2::Job *job ) +{ + updateResourceName(); + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + + if ( job ) { + job->setAccount( m_account ); + job->restart(); + } else { + synchronize(); + } +} +#endif // HAVE_ACCOUNTS + +bool GoogleResource::configureKGAPIAccount( const AccountPtr &account ) +{ + m_account = account; + return !m_account.isNull(); +} + +void GoogleResource::slotAccountManagerReady( bool ready ) +{ + // If the resource have already been configured for KAccounts, then use that + if ( accountId() > 0 ) { + return; + } + + kDebug() << ready; + if ( !ready ) { + emit status( Broken, i18n( "Can't access KWallet" ) ); + return; + } + + const QString accountName = settings()->account(); + if ( accountName.isEmpty() ) { + emit status( NotConfigured ); + return; + } + + m_account = m_accountMgr->findAccount( accountName ); + if ( m_account.isNull() ) { + emit status( NotConfigured, i18n( "Configured account does not exist" ) ); + return; + } + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + synchronize(); +} + +void GoogleResource::slotAccountChanged( const AccountPtr &account ) +{ + // We don't care when using KAccounts + if ( accountId() > 0 ) { + return; + } + + m_account = account; +} + +void GoogleResource::slotAccountRemoved( const QString &accountName ) +{ + // We don't care when using KAccounts + if ( accountId() > 0 ) { + return; + } + + if ( m_account && m_account->accountName() != accountName ) { + return; + } + + emit status( NotConfigured, i18n( "Configured account has been removed" ) ); + m_account.clear(); + settings()->setAccount(QString()); +} + +bool GoogleResource::handleError( KGAPI2::Job *job ) +{ + if (( job->error() == KGAPI2::NoError ) || ( job->error() == KGAPI2::OK )) { + return true; + } + + if ( job->error() == KGAPI2::Unauthorized ) { + kDebug() << job << job->errorString(); + + const QList resourceScopes = scopes(); + Q_FOREACH(const QUrl &scope, resourceScopes) { + if ( !m_account->scopes().contains( scope ) ) { + m_account->addScope( scope ); + } + } + + updateAccountToken( m_account, job ); + return false; + } + + cancelTask( job->errorString() ); + job->deleteLater(); + return false; +} + +bool GoogleResource::canPerformTask() +{ + if ( !m_account && accountId() == 0 ) { + cancelTask( i18nc( "@info:status", "Resource is not configured" ) ); + emit status( NotConfigured, i18nc( "@info:status", "Resource is not configured" ) ); + return false; + } + + return true; +} + +void GoogleResource::slotAuthJobFinished( KGAPI2::Job *job ) +{ + kDebug(); + + if ( job->error() != KGAPI2::NoError ) { + cancelTask( i18n( "Failed to refresh tokens" ) ); + return; + } + + AuthJob *authJob = qobject_cast( job ); + m_account = authJob->account(); + if ( !m_accountMgr->storeAccount( m_account ) ) { + kWarning() << "Failed to store account in KWallet"; + } + + KGAPI2::Job *otherJob = job->property( JOB_PROPERTY ).value(); + if ( otherJob ) { + otherJob->setAccount(m_account); + otherJob->restart(); + } + + job->deleteLater(); +} + +void GoogleResource::slotGenericJobFinished( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + const Item item = job->property( ITEM_PROPERTY ).value(); + const Collection collection = job->property( COLLECTION_PROPERTY ).value(); + if ( item.isValid() ) { + changeCommitted( item ); + } else if ( collection.isValid() ) { + changeCommitted( collection ); + } else { + taskDone(); + } + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + + job->deleteLater(); +} + +void GoogleResource::emitPercent( KGAPI2::Job *job, int processedItems, int totalItems ) +{ + Q_UNUSED( job ); + + emit percent( ( ( float ) processedItems / ( float ) totalItems ) * 100 ); +} + +bool GoogleResource::retrieveItem( const Item &item, const QSet< QByteArray > &parts ) +{ + Q_UNUSED( parts ); + + /* We don't support fetching parts, the item is already fully stored. */ + itemRetrieved( item ); + + return true; +} + +int GoogleResource::accountId() const +{ +#ifdef HAVE_ACCOUNTS + return settings()->accountId(); +#else + return 0; +#endif +} diff --git a/kdepim-runtime/resources/google/common/googleresource.h b/kdepim-runtime/resources/google/common/googleresource.h new file mode 100644 index 00000000..4938164e --- /dev/null +++ b/kdepim-runtime/resources/google/common/googleresource.h @@ -0,0 +1,130 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLERESOURCE_H +#define GOOGLERESOURCE_H + +#include +#include + +#include + +#include "googleaccountmanager.h" + +#include + +#include + +#define ITEM_PROPERTY "_AkonadiItem" +#define ITEMLIST_PROPERTY "_AkonadiItemList" +#define COLLECTION_PROPERTY "_AkonadiCollection" +#define JOB_PROPERTY "_KGAPI2Job" + +namespace KGAPI2 +{ +class Job; +} + +class GoogleSettings; + +class GoogleResource : public Akonadi::ResourceBase, + public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + explicit GoogleResource( const QString &id ); + virtual ~GoogleResource(); + + virtual GoogleSettings* settings() const = 0; + virtual QList scopes() const = 0; + + void cleanup(); + +public Q_SLOTS: + virtual void configure( WId windowId ); + + void reloadConfig(); + + protected Q_SLOTS: + virtual bool retrieveItem( const Akonadi::Item &item, const QSet< QByteArray > &parts ); + + bool handleError( KGAPI2::Job *job ); + + virtual void slotAuthJobFinished( KGAPI2::Job *job ); + virtual void slotGenericJobFinished( KGAPI2::Job *job ); + + void emitPercent( KGAPI2::Job* job, int processedCount, int totalCount ); + + virtual void slotAbortRequested(); + virtual void slotAccountManagerReady( bool success ); + virtual void slotAccountChanged( const KGAPI2::AccountPtr &account ); + virtual void slotAccountRemoved( const QString &accountName ); + +#ifdef HAVE_ACCOUNTS + void slotKAccountsCredentialsReceived( KJob *job ); + void slotKAccountsAccountInfoReceived( KGAPI2::Job *job ); + void finishKAccountsAuthentication( KGAPI2::Job* job ); +#endif // HAVE_ACCOUNTS + + protected: + bool configureKAccounts( int accountId, KGAPI2::Job *restartJob = 0 ); + bool configureKGAPIAccount( const KGAPI2::AccountPtr &account ); + void updateAccountToken( const KGAPI2::AccountPtr &account, KGAPI2::Job *restartJob = 0 ); + + template + bool canPerformTask( const Akonadi::Item &item, const QString &mimeType = QString() ) { + if ( item.isValid() && !item.hasPayload()) { + cancelTask( i18n( "Invalid item payload." ) ); + return false; + } else if ( item.isValid() && mimeType != item.mimeType() ) { + cancelTask( i18n( "Invalid payload mimetype. Expected %1, found %2", mimeType, item.mimeType() ) ); + return false; + } + + return canPerformTask(); + } + + bool canPerformTask(); + + KGAPI2::AccountPtr account() const; + /** + * KAccounts support abstraction. + * + * Returns 0 when compiled without KAccounts or not configured for KAccounts + */ + int accountId() const; + + + GoogleAccountManager* accountManager() const; + + virtual void aboutToQuit(); + + + virtual int runConfigurationDialog( WId windowId ) = 0; + virtual void updateResourceName() = 0; + + private: + void abort(); + + bool m_isConfiguring; + GoogleAccountManager *m_accountMgr; + KGAPI2::AccountPtr m_account; + +}; + +#endif // GOOGLERESOURCE_H diff --git a/kdepim-runtime/resources/google/common/googlesettings.cpp b/kdepim-runtime/resources/google/common/googlesettings.cpp new file mode 100644 index 00000000..2152fc14 --- /dev/null +++ b/kdepim-runtime/resources/google/common/googlesettings.cpp @@ -0,0 +1,55 @@ +/* + Copyright (C) 2011-2013 Dan Vratil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "googlesettings.h" +#include "settingsbase.h" + +GoogleSettings::GoogleSettings(): + m_winId(0) +{ +} + +QString GoogleSettings::clientId() const +{ + return QLatin1String("554041944266.apps.googleusercontent.com"); +} + +QString GoogleSettings::clientSecret() const +{ + return QLatin1String("mdT1DjzohxN3npUUzkENT0gO"); +} + +void GoogleSettings::setWindowId( WId id ) +{ + m_winId = id; +} + +void GoogleSettings::setResourceId( const QString &resourceIdentificator ) +{ + m_resourceId = resourceIdentificator; +} + +QString GoogleSettings::account() const +{ + return SettingsBase::account(); +} + +void GoogleSettings::setAccount( const QString &account ) +{ + SettingsBase::setAccount( account ); +} + diff --git a/kdepim-runtime/resources/google/common/googlesettings.h b/kdepim-runtime/resources/google/common/googlesettings.h new file mode 100644 index 00000000..f9486cdd --- /dev/null +++ b/kdepim-runtime/resources/google/common/googlesettings.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef GOOGLESETTINGS_H +#define GOOGLESETTINGS_H + +#include "settingsbase.h" + +#include +#include + +/** + * @brief Settings object + * + * Provides read-only access to application clientId and + * clientSecret and read-write access to accessToken and + * refreshToken. + */ +class GoogleSettings: public SettingsBase +{ + Q_OBJECT + + public: + GoogleSettings(); + void setWindowId( WId id ); + void setResourceId( const QString &resourceIdentifier ); + + QString appId() const; + + QString clientId() const; + QString clientSecret() const; + + virtual QString account() const; + virtual void setAccount(const QString &account); + + private: + WId m_winId; + QString m_resourceId; + +}; + + +#endif // GOOGLESETTINGS_H diff --git a/kdepim-runtime/resources/google/common/googlesettingsdialog.cpp b/kdepim-runtime/resources/google/common/googlesettingsdialog.cpp new file mode 100644 index 00000000..b0cd1364 --- /dev/null +++ b/kdepim-runtime/resources/google/common/googlesettingsdialog.cpp @@ -0,0 +1,252 @@ +/* + Copyright (C) 2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "googlesettingsdialog.h" +#include "googleaccountmanager.h" +#include "googlesettings.h" +#include "googleresource.h" +#include "settings.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +Q_DECLARE_METATYPE( KGAPI2::Job* ) + +using namespace KGAPI2; + +GoogleSettingsDialog::GoogleSettingsDialog( GoogleAccountManager *accountManager, WId wId, GoogleResource *parent ): + KDialog(), + m_parentResource( parent ), + m_accountManager( accountManager ) +{ + KWindowSystem::setMainWindow( this, wId ); + setButtons( Ok | Cancel ); + + connect( this, SIGNAL(accepted()), + this, SLOT(slotSaveSettings()) ); + + QWidget *widget = new QWidget( this ); + QVBoxLayout *mainLayout = new QVBoxLayout( widget ); + setMainWidget( widget ); + + m_accGroupBox = new QGroupBox( i18n( "Accounts" ), this ); + mainLayout->addWidget( m_accGroupBox ); + QHBoxLayout *accLayout = new QHBoxLayout( m_accGroupBox ); + + m_accComboBox = new KComboBox( m_accGroupBox ); + accLayout->addWidget( m_accComboBox, 1 ); + connect( m_accComboBox, SIGNAL(currentIndexChanged(QString)), + this, SIGNAL(currentAccountChanged(QString)) ); + + m_addAccButton = new KPushButton( KIcon( QLatin1String("list-add-user") ), i18n( "&Add" ), m_accGroupBox ); + accLayout->addWidget( m_addAccButton ); + connect( m_addAccButton, SIGNAL(clicked(bool)), + this, SLOT(slotAddAccountClicked()) ); + + m_removeAccButton = new KPushButton( KIcon( QLatin1String("list-remove-user") ), i18n( "&Remove" ), m_accGroupBox ); + accLayout->addWidget( m_removeAccButton ); + connect( m_removeAccButton, SIGNAL(clicked(bool)), + this, SLOT(slotRemoveAccountClicked()) ); + + QGroupBox *refreshBox = new QGroupBox( i18n( "Refresh" ), this ); + mainLayout->addWidget( refreshBox ); + QGridLayout *refreshLayout = new QGridLayout( refreshBox ); + + m_enableRefresh = new QCheckBox( i18n( "Enable interval refresh" ), refreshBox ); + m_enableRefresh->setChecked( Settings::self()->enableIntervalCheck() ); + refreshLayout->addWidget( m_enableRefresh, 0, 0, 1, 2 ); + + QLabel *label = new QLabel( i18n( "Refresh interval:" ) ); + refreshLayout->addWidget( label, 1, 0 ); + m_refreshSpinBox = new KIntSpinBox( 10, 720, 1, 30, this, 10 ); + m_refreshSpinBox->setSuffix( ki18np( " minute", " minutes" ) ); + m_refreshSpinBox->setEnabled( Settings::self()->enableIntervalCheck() ); + refreshLayout->addWidget( m_refreshSpinBox, 1, 1 ); + connect( m_enableRefresh, SIGNAL(toggled(bool)), + m_refreshSpinBox, SLOT(setEnabled(bool)) ); + + if ( m_enableRefresh->isEnabled() ) { + m_refreshSpinBox->setValue( Settings::self()->intervalCheckTime() ); + } + + QMetaObject::invokeMethod( this, "reloadAccounts", Qt::QueuedConnection ); +} + +GoogleSettingsDialog::~GoogleSettingsDialog() +{ +} + +GoogleAccountManager *GoogleSettingsDialog::accountManager() const +{ + return m_accountManager; +} + +KGAPI2::AccountPtr GoogleSettingsDialog::currentAccount() const +{ + return m_accountManager->findAccount( m_accComboBox->currentText() ); +} + +void GoogleSettingsDialog::reloadAccounts() +{ + disconnect( m_accComboBox, SIGNAL(currentIndexChanged(QString)), + this, SIGNAL(currentAccountChanged(QString)) ); + + m_accComboBox->clear(); + + const AccountsList accounts = m_accountManager->listAccounts(); + Q_FOREACH( const AccountPtr &account, accounts ) { + m_accComboBox->addItem( account->accountName() ); + } + + int index = m_accComboBox->findText( m_parentResource->settings()->account(), Qt::MatchExactly ); + if ( index > -1 ) { + m_accComboBox->setCurrentIndex( index ); + } + + disconnect( m_accComboBox, SIGNAL(currentIndexChanged(QString)), + this, SIGNAL(currentAccountChanged(QString)) ); + + Q_EMIT currentAccountChanged( m_accComboBox->currentText() ); +} + +void GoogleSettingsDialog::slotAddAccountClicked() +{ + AccountPtr account( new Account() ); + // FIXME: We need a proper API for this + account->addScope( Account::contactsScopeUrl() ); + account->addScope( Account::calendarScopeUrl() ); + account->addScope( Account::tasksScopeUrl() ); + account->addScope( Account::accountInfoEmailScopeUrl() ); + account->addScope( Account::accountInfoScopeUrl() ); + + AuthJob *authJob = new AuthJob( account, + m_parentResource->settings()->clientId(), + m_parentResource->settings()->clientSecret() ); + connect( authJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotAccountAuthenticated(KGAPI2::Job*)) ); +} + +void GoogleSettingsDialog::slotRemoveAccountClicked() +{ + const AccountPtr account = currentAccount(); + if ( !account ) { + return; + } + + if ( KMessageBox::warningYesNo( + this, + i18n( "Do you really want to revoke access to account %1?" + "

This will revoke access to all resources using this account!

", + account->accountName() ), + i18n( "Revoke Access?" ), + KStandardGuiItem::yes(), + KStandardGuiItem::no(), + QString(), + KMessageBox::Dangerous ) != KMessageBox::Yes ) { + + return; + } + + m_accountManager->removeAccount( account->accountName() ); + reloadAccounts(); +} + +void GoogleSettingsDialog::slotAccountAuthenticated( Job *job ) +{ + AuthJob *authJob = qobject_cast(job); + const AccountPtr account = authJob->account(); + + if ( !m_accountManager->storeAccount( account ) ) { + kWarning() << "Failed to add account to KWallet"; + } + + reloadAccounts(); +} + +bool GoogleSettingsDialog::handleError( Job *job ) +{ + if (( job->error() == KGAPI2::NoError ) || ( job->error() == KGAPI2::OK )) { + return true; + } + + if ( job->error() == KGAPI2::Unauthorized ) { + kDebug() << job << job->errorString(); + const AccountPtr account = currentAccount(); + const QList resourceScopes = m_parentResource->scopes(); + Q_FOREACH(const QUrl &scope, resourceScopes) { + if ( !account->scopes().contains( scope ) ) { + account->addScope( scope ); + } + } + + AuthJob *authJob = new AuthJob( account, m_parentResource->settings()->clientId(), + m_parentResource->settings()->clientSecret(), this ); + authJob->setProperty( JOB_PROPERTY, QVariant::fromValue( job ) ); + connect( authJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotAuthJobFinished(KGAPI2::Job*)) ); + + return false; + } + + KMessageBox::sorry( this, job->errorString() ); + job->deleteLater(); + return false; +} + +void GoogleSettingsDialog::slotAuthJobFinished( Job *job ) +{ + kDebug(); + + if ( job->error() != KGAPI2::NoError ) { + KMessageBox::sorry( this, job->errorString() ); + return; + } + + AuthJob *authJob = qobject_cast( job ); + const AccountPtr account = authJob->account(); + if ( !m_accountManager->storeAccount( account ) ) { + kWarning() << "Failed to store account in KWallet"; + } + + KGAPI2::Job *otherJob = job->property( JOB_PROPERTY ).value(); + otherJob->setAccount( account ); + otherJob->restart(); + + job->deleteLater(); +} + +void GoogleSettingsDialog::slotSaveSettings() +{ + Settings::self()->setEnableIntervalCheck( m_enableRefresh->isChecked() ); + Settings::self()->setIntervalCheckTime( m_refreshSpinBox->value() ); + + saveSettings(); +} diff --git a/kdepim-runtime/resources/google/common/googlesettingsdialog.h b/kdepim-runtime/resources/google/common/googlesettingsdialog.h new file mode 100644 index 00000000..aaa16394 --- /dev/null +++ b/kdepim-runtime/resources/google/common/googlesettingsdialog.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLESETTINGSDIALOG_H +#define GOOGLESETTINGSDIALOG_H + +#include + +#include + +namespace KGAPI2 +{ +class Job; +} + +class GoogleResource; +class GoogleSettings; +class GoogleAccountManager; + +class QGroupBox; +class KComboBox; +class QCheckBox; +class KIntSpinBox; + + +class GoogleSettingsDialog : public KDialog +{ + Q_OBJECT + + public: + explicit GoogleSettingsDialog( GoogleAccountManager *accountManager, WId wId, GoogleResource *parent ); + virtual ~GoogleSettingsDialog(); + + GoogleAccountManager* accountManager() const; + KGAPI2::AccountPtr currentAccount() const; + + public Q_SLOTS: + void reloadAccounts(); + + Q_SIGNALS: + void currentAccountChanged( const QString &accountName ); + + protected: + bool handleError( KGAPI2::Job *job ); + virtual void saveSettings() = 0; + + private Q_SLOTS: + void slotSaveSettings(); + void slotAddAccountClicked(); + void slotRemoveAccountClicked(); + void slotAuthJobFinished( KGAPI2::Job *job ); + void slotAccountAuthenticated( KGAPI2::Job *job ); + + private: + GoogleResource *m_parentResource; + GoogleAccountManager *m_accountManager; + + QGroupBox *m_accGroupBox; + KPushButton *m_addAccButton; + KPushButton *m_removeAccButton; + KComboBox *m_accComboBox; + QCheckBox *m_enableRefresh; + KIntSpinBox *m_refreshSpinBox; + +}; + +#endif // GOOGLESETTINGSDIALOG_H + diff --git a/kdepim-runtime/resources/google/contacts/CMakeLists.txt b/kdepim-runtime/resources/google/contacts/CMakeLists.txt new file mode 100644 index 00000000..14be1996 --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/CMakeLists.txt @@ -0,0 +1,63 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +set(contactsresource_SRCS + contactsresource.cpp + settings.cpp + settingsdialog.cpp + ../common/googlesettings.cpp + ../common/googleresource.cpp + ../common/googleaccountmanager.cpp + ../common/googlesettingsdialog.cpp + ${accounts_SRCS} +) + +kde4_add_kcfg_files(contactsresource_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfgc) + +kcfg_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfg + org.kde.Akonadi.GoogleContacts.Settings +) + +qt4_add_dbus_adaptor(contactsresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.GoogleContacts.Settings.xml + ${CMAKE_CURRENT_SOURCE_DIR}/settings.h Settings +) + +kde4_add_executable(akonadi_googlecontacts_resource RUN_UNINSTALLED ${contactsresource_SRCS}) + +if(Q_WS_MAC) + set_target_properties(akonadi_googlecontacts_resource PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../../Info.plist.template + ) + set_target_properties(akonadi_googlecontacts_resource PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.googlecontacts" + ) + set_target_properties(akonadi_googlecontacts_resource PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Google Contacts Resource" + ) +endif() + + +target_link_libraries(akonadi_googlecontacts_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${LibKGAPI2_LIBRARY} +) + +if(${AccountsQt_FOUND} AND ${SignOnQt_FOUND}) + target_link_libraries(akonadi_googlecontacts_resource + ${ACCOUNTSQT_LIBRARIES} + ${SIGNONQT_LIBRARIES}) +endif() + +install(TARGETS akonadi_googlecontacts_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install( + FILES googlecontactsresource.desktop + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" +) diff --git a/kdepim-runtime/resources/google/contacts/Messages.sh b/kdepim-runtime/resources/google/contacts/Messages.sh new file mode 100644 index 00000000..87602e5e --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_googlecontacts_resource.pot +$XGETTEXT ../common/*.cpp -j -o $podir/akonadi_googlecontacts_resource.pot diff --git a/kdepim-runtime/resources/google/contacts/contactsresource.cpp b/kdepim-runtime/resources/google/contacts/contactsresource.cpp new file mode 100644 index 00000000..ac051ba9 --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/contactsresource.cpp @@ -0,0 +1,577 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "contactsresource.h" +#include "settingsdialog.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MYCONTACTS_REMOTEID QLatin1String( "MyContacts" ) +#define OTHERCONTACTS_REMOTEID QLatin1String( "OtherContacts" ) + + +Q_DECLARE_METATYPE( KGAPI2::Job * ) +Q_DECLARE_METATYPE( QList ) + +using namespace Akonadi; +using namespace KGAPI2; + +ContactsResource::ContactsResource( const QString &id ): + GoogleResource( id ) +{ + updateResourceName(); +} + +ContactsResource::~ContactsResource() +{ +} + +GoogleSettings *ContactsResource::settings() const +{ + return Settings::self(); +} + +int ContactsResource::runConfigurationDialog( WId windowId ) +{ + + QScopedPointer settingsDialog( new SettingsDialog( accountManager(), windowId, this ) ); + settingsDialog->setWindowIcon( KIcon( QLatin1String("im-google") ) ); + + return settingsDialog->exec(); +} + +void ContactsResource::updateResourceName() +{ + const QString accountName = Settings::self()->account(); + setName( i18nc( "%1 is account name (user@gmail.com)", "Google Contacts (%1)", accountName.isEmpty() ? i18n( "not configured" ) : accountName ) ); +} + +QList< QUrl > ContactsResource::scopes() const +{ + QList< QUrl > scopes; + scopes << Account::contactsScopeUrl() + << Account::accountInfoScopeUrl(); + return scopes; +} + +void ContactsResource::retrieveItems( const Collection &collection ) +{ + if ( !canPerformTask() ) { + return; + } + + // All items are only in top-level collection and Other Contacts collection + if ( ( collection.remoteId() != m_rootCollection.remoteId() ) && + ( collection.remoteId() != OTHERCONTACTS_REMOTEID ) ) { + itemsRetrievalDone(); + return; + } + + ContactFetchJob *fetchJob = new ContactFetchJob( account(), this ); + fetchJob->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + fetchJob->setFetchDeleted( true ); + if ( !collection.remoteRevision().isEmpty() ) { + fetchJob->setFetchOnlyUpdated( collection.remoteRevision().toLongLong() ); + } + connect( fetchJob, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( fetchJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotItemsRetrieved(KGAPI2::Job*)) ); +} + +void ContactsResource::retrieveContactsPhotos( const QVariant &arguments ) +{ + if ( !canPerformTask() ) { + return; + } + + const QVariantMap map = arguments.toMap(); + const Collection collection = map[ QLatin1String("collection") ].value(); + ItemFetchJob *itemFetchJob = new ItemFetchJob( collection, this ); + itemFetchJob->setProperty( "modifiedItems", map[ QLatin1String("modifiedItems") ] ); + itemFetchJob->fetchScope().fetchFullPayload(true); + connect( itemFetchJob, SIGNAL(finished(KJob*)), + this, SLOT(slotUpdatePhotosItemsRetrieved(KJob*)) ); + emit status( Running, i18nc( "@info:status", "Retrieving photos" ) ); +} + +void ContactsResource::retrieveCollections() +{ + if ( !canPerformTask() ) { + return; + } + + ContactsGroupFetchJob *fetchJob = new ContactsGroupFetchJob( account(), this ); + connect( fetchJob, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( fetchJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCollectionsRetrieved(KGAPI2::Job*)) ); +} + +void ContactsResource::itemAdded( const Item &item, const Collection &collection ) +{ + if ( !canPerformTask( item, KABC::Addressee::mimeType() ) ) { + return; + } + + KABC::Addressee addressee = item.payload< KABC::Addressee >(); + ContactPtr contact( new Contact( addressee ) ); + + /* If the contact has been moved into My Contacts group then modify the membership */ + if ( collection.remoteId() == MYCONTACTS_REMOTEID ) { + contact->addGroup( QString::fromLatin1( "http://www.google.com/m8/feeds/groups/%1/base/6" ).arg( QString::fromLatin1( QUrl::toPercentEncoding( account()->accountName() ) ) ) ); + } + + /* If the contact has been moved to Other Contacts then remove all groups */ + if ( collection.remoteId() == OTHERCONTACTS_REMOTEID ) { + contact->clearGroups(); + } + + ContactCreateJob *createJob = new ContactCreateJob( contact, account(), this ); + createJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( createJob, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( createJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCreateJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::itemChanged( const Item &item, const QSet< QByteArray > &partIdentifiers ) +{ + Q_UNUSED( partIdentifiers ); + + if ( !canPerformTask( item, KABC::Addressee::mimeType() ) ) { + return; + } + + KABC::Addressee addressee = item.payload< KABC::Addressee >(); + ContactPtr contact( new Contact( addressee ) ); + + if ( item.parentCollection().remoteId() == MYCONTACTS_REMOTEID ) { + contact->addGroup( QString::fromLatin1( "http://www.google.com/m8/feeds/groups/%1/base/6" ).arg( QString::fromLatin1( QUrl::toPercentEncoding( account()->accountName() ) ) ) ); + } + + ContactModifyJob *modifyJob = new ContactModifyJob( contact, account(), this ); + modifyJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( modifyJob, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( modifyJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::itemMoved( const Item &item, const Collection &collectionSource, + const Collection &collectionDestination ) +{ + if ( !canPerformTask( item, KABC::Addressee::mimeType() ) ) { + return; + } + + KABC::Addressee addressee = item.payload< KABC::Addressee >(); + ContactPtr contact( new Contact( addressee ) ); + + // MyContacts -> OtherContacts + if ( collectionSource.remoteId() == MYCONTACTS_REMOTEID && + collectionDestination.remoteId() == OTHERCONTACTS_REMOTEID ) { + contact->removeGroup( QString::fromLatin1( "http://www.google.com/m8/feeds/groups/%1/base/6" ).arg( QString::fromLatin1( QUrl::toPercentEncoding( account()->accountName() ) ) ) ); + + // OtherContacts -> MyContacts + } else if ( collectionSource.remoteId() == OTHERCONTACTS_REMOTEID && + collectionDestination.remoteId() == MYCONTACTS_REMOTEID ) { + contact->addGroup( QString::fromLatin1( "http://www.google.com/m8/feeds/groups/%1/base/6" ).arg( QString::fromLatin1( QUrl::toPercentEncoding( account()->accountName() ) ) ) ); + + } else { + cancelTask( i18n( "Invalid source or destination collection" ) ); + return; + } + + ContactModifyJob *modifyJob = new ContactModifyJob( contact, account(), this ); + modifyJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( modifyJob, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( modifyJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::itemRemoved( const Item &item ) +{ + if ( !canPerformTask() ) { + return; + } + + ContactDeleteJob *deleteJob = new ContactDeleteJob( item.remoteId(), account(), this ); + deleteJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( deleteJob, SIGNAL(progress(KGAPI2::Job*,int,int)), + this, SLOT(emitPercent(KGAPI2::Job*,int,int)) ); + connect( deleteJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); + + emit status( Running, i18nc( "@info:status", "Removing contact" ) ); +} + +void ContactsResource::itemLinked( const Item &item, const Collection &collection ) +{ + if ( !canPerformTask( item, KABC::Addressee::mimeType() ) ) { + return; + } + + KABC::Addressee addressee = item.payload(); + ContactPtr contact( new Contact( addressee ) ); + + contact->addGroup( collection.remoteId() ); + + ContactModifyJob *modifyJob = new ContactModifyJob( contact, account(), this ); + connect( modifyJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::itemUnlinked( const Item &item, const Collection &collection ) +{ + if ( !canPerformTask( item, KABC::Addressee::mimeType() ) ) { + return; + } + + KABC::Addressee addressee = item.payload(); + ContactPtr contact( new Contact( addressee ) ); + + contact->removeGroup( collection.remoteId() ); + + ContactModifyJob *modifyJob = new ContactModifyJob( contact, account(), this ); + modifyJob->setProperty( ITEM_PROPERTY, QVariant::fromValue( item ) ); + connect( modifyJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::collectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ) +{ + Q_UNUSED( parent ); + + if ( !canPerformTask() ) { + return; + } + + ContactsGroupPtr group( new ContactsGroup ); + group->setTitle( collection.name() ); + group->setIsSystemGroup( false ); + + ContactsGroupCreateJob *createJob = new ContactsGroupCreateJob( group, account(), this ); + createJob->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( createJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotCreateJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::collectionChanged( const Akonadi::Collection &collection ) +{ + if ( !canPerformTask() ) { + return; + } + + ContactsGroupPtr group( new ContactsGroup() ); + group->setId( collection.remoteId() ); + + group->setTitle( collection.displayName() ); + + ContactsGroupModifyJob *modifyJob = new ContactsGroupModifyJob( group, account(), this ); + modifyJob->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( modifyJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + if ( !canPerformTask() ) { + return; + } + + ContactsGroupDeleteJob *deleteJob = new ContactsGroupDeleteJob( collection.remoteId(), account(), this ); + deleteJob->setProperty( COLLECTION_PROPERTY, QVariant::fromValue( collection ) ); + connect( deleteJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + + +void ContactsResource::slotCollectionsRetrieved( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + ContactsGroupFetchJob *fetchJob = qobject_cast( job ); + const ObjectsList objects = fetchJob->items(); + + CachePolicy cachePolicy; + if ( Settings::self()->enableIntervalCheck() ) { + cachePolicy.setInheritFromParent( false ); + cachePolicy.setIntervalCheckTime( Settings::self()->intervalCheckTime() ); + } + + m_rootCollection = Collection(); + m_rootCollection.setContentMimeTypes( QStringList() << Collection::virtualMimeType() + << KABC::Addressee::mimeType() ); + m_rootCollection.setRemoteId( MYCONTACTS_REMOTEID ); + m_rootCollection.setName( fetchJob->account()->accountName() ); + m_rootCollection.setParentCollection( Collection::root() ); + m_rootCollection.setCachePolicy( cachePolicy ); + m_rootCollection.setRights( Collection::CanCreateCollection | + Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem); + + EntityDisplayAttribute *attr = m_rootCollection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( fetchJob->account()->accountName() ); + attr->setIconName( QLatin1String( "im-google" ) ); + + m_collections[ MYCONTACTS_REMOTEID ] = m_rootCollection; + + foreach( const ObjectPtr & object, objects ) { + const ContactsGroupPtr group = object.dynamicCast(); + + QString realName = group->title(); + + if ( group->isSystemGroup() ) { + if ( group->title().contains( QLatin1String( "Coworkers" ) ) ) { + realName = i18nc( "Name of a group of contacts", "Coworkers" ); + } else if ( group->title().contains( QLatin1String( "Friends" ) ) ) { + realName = i18nc( "Name of a group of contacts", "Friends" ); + } else if ( group->title().contains( QLatin1String( "Family" ) ) ) { + realName = i18nc( "Name of a group of contacts", "Family" ); + } else if ( group->title().contains( QLatin1String( "My Contacts" ) ) ) { + // Yes, skip My Contacts group, we store "My Contacts" in root collection + continue; + } + } else { + if ( group->title().contains( QLatin1String( "Other Contacts" ) ) ) { + realName = i18nc( "Name of a group of contacts", "Other Contacts" ); + } + } + + Collection collection; + collection.setContentMimeTypes( QStringList() << KABC::Addressee::mimeType() ); + collection.setName( group->id() ); + collection.setParentCollection( m_rootCollection ); + collection.setRights( Collection::CanLinkItem | + Collection::CanUnlinkItem | + Collection::CanChangeItem ); + if ( !group->isSystemGroup() ) { + collection.setRights( collection.rights() | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + } + collection.setRemoteId( group->id() ); + collection.setVirtual( true ); + + EntityDisplayAttribute *attr = collection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( realName ); + attr->setIconName( QLatin1String("view-pim-contacts") ); + + m_collections[ collection.remoteId() ] = collection; + } + + Collection otherCollection; + otherCollection.setContentMimeTypes( QStringList() << KABC::Addressee::mimeType() ); + otherCollection.setName( i18n( "Other Contacts" ) ); + otherCollection.setParentCollection( m_rootCollection ); + otherCollection.setRights( Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem ); + otherCollection.setRemoteId( OTHERCONTACTS_REMOTEID ); + + attr = otherCollection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( i18n( "Other Contacts" ) ); + attr->setIconName( QLatin1String("view-pim-contacts") ); + m_collections[ OTHERCONTACTS_REMOTEID ] = otherCollection; + + collectionsRetrieved( m_collections.values() ); + job->deleteLater(); +} + +void ContactsResource::slotItemsRetrieved( KGAPI2::Job *job ) +{ + if ( !handleError( job ) ) { + return; + } + + ContactFetchJob *fetchJob = qobject_cast( job ); + const ObjectsList objects = fetchJob->items(); + + Collection collection = fetchJob->property( COLLECTION_PROPERTY ).value(); + + Item::List changedItems, removedItems; + QMap groupsMap; + QList changedPhotos; + foreach( const ObjectPtr & object, objects ) { + const ContactPtr contact = object.dynamicCast(); + + if ( ( ( collection.remoteId() == OTHERCONTACTS_REMOTEID ) && !contact->groups().isEmpty() ) || + ( ( collection.remoteId() == MYCONTACTS_REMOTEID ) && contact->groups().isEmpty() ) ) { + continue; + } + + Item item; + item.setMimeType( KABC::Addressee::mimeType() ); + item.setParentCollection( m_collections[MYCONTACTS_REMOTEID] ); + item.setRemoteId( contact->uid() ); + item.setRemoteRevision( contact->etag() ); + item.setPayload( *contact.dynamicCast() ); + + if ( contact->deleted() ) { + removedItems << item; + } else { + changedItems << item; + changedPhotos << contact->uid(); + } + + const QStringList groups = contact->groups(); + foreach( const QString & group, groups ) { + groupsMap[group] << item; + } + } + + itemsRetrievedIncremental( changedItems, removedItems ); + + QMap::ConstIterator iter; + + for ( iter = groupsMap.constBegin(); iter != groupsMap.constEnd(); ++iter ) { + new LinkJob( m_collections[iter.key()], iter.value(), this ); + } + + QVariantMap map; + map[QLatin1String("collection")] = QVariant::fromValue(collection); + map[QLatin1String("modifiedItems")] = QVariant::fromValue(changedPhotos); + scheduleCustomTask( this, "retrieveContactsPhotos", map ); + + collection.setRemoteRevision( QString::number( KDateTime::currentUtcDateTime().toTime_t() ) ); + new CollectionModifyJob( collection, this ); + + job->deleteLater(); +} + +void ContactsResource::slotUpdatePhotosItemsRetrieved( KJob *job ) +{ + ItemFetchJob *fetchJob = qobject_cast(job); + const Item::List items = fetchJob->items(); + const QList modifiedItems = fetchJob->property( "modifiedItems" ).value< QList >(); + ContactsList contacts; + + foreach( const Item &item, items ) { + if ( modifiedItems.contains( item.remoteId() )) { + const KABC::Addressee addressee = item.payload(); + const ContactPtr contact( new Contact( addressee ) ); + contacts << contact; + } + } + + // Make sure account is still valid + if ( !canPerformTask() ) { + return; + } + + ContactFetchPhotoJob *photoJob = new ContactFetchPhotoJob( contacts, account(), this ); + photoJob->setProperty( ITEMLIST_PROPERTY, QVariant::fromValue( items ) ); + photoJob->setProperty( "processedItems", 0 ); + connect( photoJob, SIGNAL(photoFetched(KGAPI2::Job*,KGAPI2::ContactPtr)), + this, SLOT(slotUpdatePhotoFinished(KGAPI2::Job*,KGAPI2::ContactPtr)) ); + connect( photoJob, SIGNAL(finished(KGAPI2::Job*)), + this, SLOT(slotGenericJobFinished(KGAPI2::Job*)) ); +} + +void ContactsResource::slotUpdatePhotoFinished( KGAPI2::Job *job, const ContactPtr &contact ) +{ + Item::List items = job->property( ITEMLIST_PROPERTY ).value(); + + int processedItems = job->property( "processedItems" ).toInt(); + processedItems++; + job->setProperty( "processedItems", processedItems ); + emitPercent( job, processedItems, items.count() ); + + foreach( Item item, items ) { + if ( item.remoteId() == contact->uid() ) { + item.setPayload( *contact.dynamicCast() ); + new ItemModifyJob( item, this ); + return; + } + } +} + +void ContactsResource::slotCreateJobFinished( KGAPI2::Job* job ) +{ + if ( !handleError( job ) ) { + return; + } + + Item item = job->property( ITEM_PROPERTY ).value(); + Collection collection = job->property( COLLECTION_PROPERTY ).value(); + if ( item.isValid() ) { + ContactCreateJob *createJob = qobject_cast( job ); + Q_ASSERT( createJob->items().count() == 1); + ContactPtr contact = createJob->items().first().dynamicCast(); + + item.setRemoteId( contact->uid() ); + item.setRemoteRevision( contact->etag() ); + changeCommitted( item ); + } else if ( collection.isValid() ) { + ContactsGroupCreateJob *createJob = qobject_cast( job ); + Q_ASSERT( createJob->items().count() == 1); + ContactsGroupPtr group = createJob->items().first().dynamicCast(); + + collection.setRemoteId( group->id() ); + collection.setContentMimeTypes( QStringList() << KABC::Addressee::mimeType() ); + + EntityDisplayAttribute *attr = collection.attribute( Entity::AddIfMissing ); + attr->setDisplayName( group->title() ); + attr->setIconName( QLatin1String("view-pim-contacts") ); + + m_collections[ collection.remoteId() ] = collection; + + changeCommitted( collection ); + } + + job->deleteLater(); +} + + +AKONADI_RESOURCE_MAIN( ContactsResource ) diff --git a/kdepim-runtime/resources/google/contacts/contactsresource.h b/kdepim-runtime/resources/google/contacts/contactsresource.h new file mode 100644 index 00000000..dd41510f --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/contactsresource.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2011, 2012 Dan Vratil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CONTACTS_CONTACTSRESOURCE_H +#define GOOGLE_CONTACTS_CONTACTSRESOURCE_H + +#include "common/googleresource.h" + +#include +#include + +class GoogleSettings; +namespace KGAPI2 +{ +class Job; +} +class KJob; + +class ContactsResource: public GoogleResource +{ + Q_OBJECT + + public: + explicit ContactsResource( const QString &id ); + + ~ContactsResource(); + + protected Q_SLOTS: + virtual void retrieveCollections(); + + virtual void retrieveItems( const Akonadi::Collection &collection ); + virtual void retrieveContactsPhotos( const QVariant &argument ); + + virtual void itemRemoved( const Akonadi::Item &item ); + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers ); + virtual void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection ); + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + virtual void itemLinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemUnlinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); + + void slotItemsRetrieved( KGAPI2::Job *job ); + void slotCollectionsRetrieved( KGAPI2::Job *job ); + + void slotUpdatePhotosItemsRetrieved( KJob *job ); + void slotUpdatePhotoFinished( KGAPI2::Job *job, const KGAPI2::ContactPtr &contact ); + + void slotCreateJobFinished( KGAPI2::Job *job ); + + virtual GoogleSettings *settings() const; + virtual int runConfigurationDialog( WId windowId ); + virtual void updateResourceName(); + virtual QList< QUrl > scopes() const; + + private: + + QMap m_collections; + Akonadi::Collection m_rootCollection; + +}; + +#endif // CONTACTSRESOURCE_H diff --git a/kdepim-runtime/resources/google/contacts/googlecontactsresource.desktop b/kdepim-runtime/resources/google/contacts/googlecontactsresource.desktop new file mode 100644 index 00000000..6d199711 --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/googlecontactsresource.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Name=Google Contacts +Name[bs]=Google kontakti +Name[ca]=Contactes de Google +Name[ca@valencia]=Contactes de Google +Name[cs]=Kontakty Google +Name[da]=Google-kontakter +Name[de]=Google-Kontakte +Name[el]=Google Επαφές +Name[en_GB]=Google Contacts +Name[es]=Contactos Google +Name[et]=Google'i kontaktid +Name[fi]=Google-yhteystiedot +Name[fr]=Contacts Google +Name[ga]=Teagmhálacha Google +Name[gl]=Google Contacts +Name[hu]=Google névjegyek +Name[ia]=Contactos de Google +Name[it]=Contatti Google +Name[kk]=Google контакттары +Name[km]=ទំនាក់ទំនង Google +Name[ko]=Google ì—°ë½ì²˜ +Name[lt]=Google kontaktai +Name[lv]=Google kontakti +Name[nb]=Google-kontakter +Name[nds]=Google-Kontakten +Name[nl]=Google contactpersonen +Name[pl]=Kontakty Google +Name[pt]=Contactos do Google +Name[pt_BR]=Contatos do Google +Name[ru]=Контакты Google +Name[sk]=Google kontakty +Name[sl]=Stiki Google +Name[sr]=Гуглови контакти +Name[sr@ijekavian]=Гуглови контакти +Name[sr@ijekavianlatin]=Googleovi kontakti +Name[sr@latin]=Googleovi kontakti +Name[sv]=Google kontakter +Name[tr]=Google KiÅŸileri +Name[ug]=Google ئالاقەداشلىرى +Name[uk]=Контакти Google +Name[x-test]=xxGoogle Contactsxx +Name[zh_CN]=Google è”系人 +Name[zh_TW]=Google è¯çµ¡äºº +Comment=Access your Google Contacts from KDE +Comment[bs]=Pristupite svojim Google kontaktima iz KDE +Comment[ca]=Accediu als contactes de Google des del KDE +Comment[ca@valencia]=Accediu als contactes de Google des del KDE +Comment[da]=TilgÃ¥ dine Google-kontakter fra KDE +Comment[de]=Greifen Sie in KDE auf Google-Kontakte zu +Comment[el]=Αποκτήστε Ï€Ïόσβαση στις Google επαφές σας από το KDE +Comment[en_GB]=Access your Google Contacts from KDE +Comment[es]=Acceda a sus contactos Google desde KDE +Comment[et]=Oma Google'i kontaktide kasutamine otse KDE-st +Comment[fi]=Google-yhteystietoihin pääsy KDE:sta +Comment[fr]=Accès à vos contacts Google depuis KDE +Comment[gl]=Acceda aos seus contactos de Google desde KDE. +Comment[hu]=A Google névjegyeinek elérése a KDE-bÅ‘l +Comment[ia]=Accede a tu Contactos de Google ab KDE +Comment[it]=Accedi ai tuoi contatti Google da KDE +Comment[kk]=Google контакттарына KDE-ден қатынау +Comment[km]=ចូល​ដំណើរការ​ទំនាក់ទំនង Google របស់​អ្នក​ពី KDE +Comment[ko]=KDEì—ì„œ Google ì—°ë½ì²˜ì— 접근하기 +Comment[lt]=Pasiekite savo Google kontaktus iÅ¡ KDE +Comment[lv]=Piekļūstiet saviem Google kontaktiem no KDE +Comment[nb]=Bruk dine Google-kontakter fra KDE +Comment[nds]=Ut KDE op Dien Google-Kontakten togriepen +Comment[nl]=Heb toegang tot uw Google contactpersonen vanuit KDE +Comment[pl]=Uzyskaj dostÄ™p do Kontaktów Google z KDE +Comment[pt]=Aceda aos seus contactos da Google a partir do KDE +Comment[pt_BR]=Acesse seus contatos do Google a partir do KDE +Comment[ru]=ДоÑтуп к контактам Google из KDE +Comment[sk]=Pristupuje k vaÅ¡im Google kontaktom z KDE +Comment[sl]=Dostopajte do svojih stikov Google +Comment[sr]=ПриÑтупите Ñвојим контактима на Гуглу из КДЕ‑а +Comment[sr@ijekavian]=ПриÑтупите Ñвојим контактима на Гуглу из КДЕ‑а +Comment[sr@ijekavianlatin]=Pristupite svojim kontaktima na Googleu iz KDE‑a +Comment[sr@latin]=Pristupite svojim kontaktima na Googleu iz KDE‑a +Comment[sv]=Kom Ã¥t Google kontakter frÃ¥n KDE +Comment[tr]=Google KiÅŸilerinize KDE'den eriÅŸin +Comment[uk]=ДоÑтуп до ваших запиÑів контактів Google з KDE +Comment[x-test]=xxAccess your Google Contacts from KDExx +Comment[zh_CN]=在 KDE 中访问您的 Google è”系人 +Comment[zh_TW]=用 KDE å­˜å–您的 Google è¯çµ¡äºº +Type=AkonadiResource +Exec=akonadi_googlecontacts_resource +X-Akonadi-MimeTypes=text/directory, +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_googlecontacts_resource +X-Akonadi-Custom-KAccounts=google-contacts,google-calendar +Icon=im-google diff --git a/kdepim-runtime/resources/google/contacts/settings.cpp b/kdepim-runtime/resources/google/contacts/settings.cpp new file mode 100644 index 00000000..1fc2e69a --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/settings.cpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "settings.h" +#include "settingsadaptor.h" + +#include + +#include + +class SettingsHelper +{ + public: + SettingsHelper() : q( 0 ) + { + } + + ~SettingsHelper() + { + delete q; + q = 0; + } + + Settings *q; +}; + +K_GLOBAL_STATIC( SettingsHelper, s_globalSettings ) + +Settings::Settings(): + GoogleSettings() +{ + Q_ASSERT( !s_globalSettings->q ); + s_globalSettings->q = this; + + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), this, + QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); +} + +Settings *Settings::self() +{ + if ( !s_globalSettings->q ) { + new Settings; + s_globalSettings->q->readConfig(); + } + + return s_globalSettings->q; + +} diff --git a/kdepim-runtime/resources/google/contacts/settings.h b/kdepim-runtime/resources/google/contacts/settings.h new file mode 100644 index 00000000..1273b0a6 --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/settings.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2011-2012 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CONTACTS_SETTINGS_H +#define GOOGLE_CONTACTS_SETTINGS_H + +#include "common/googlesettings.h" + +class Settings: public GoogleSettings +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.GoogleContacts.ExtendedSettings" ) + public: + Settings(); + static Settings *self(); + +}; + +#endif // SETTINGS_H diff --git a/kdepim-runtime/resources/google/contacts/settingsbase.kcfg b/kdepim-runtime/resources/google/contacts/settingsbase.kcfg new file mode 100644 index 00000000..c66d0b3f --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/settingsbase.kcfg @@ -0,0 +1,26 @@ + + + + + + + + + + + 0 + + + + false + + + 60 + + + diff --git a/kdepim-runtime/resources/google/contacts/settingsbase.kcfgc b/kdepim-runtime/resources/google/contacts/settingsbase.kcfgc new file mode 100644 index 00000000..8eda3d31 --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/settingsbase.kcfgc @@ -0,0 +1,7 @@ +File=settingsbase.kcfg +ClassName=SettingsBase +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +GlobalEnums=true \ No newline at end of file diff --git a/kdepim-runtime/resources/google/contacts/settingsdialog.cpp b/kdepim-runtime/resources/google/contacts/settingsdialog.cpp new file mode 100644 index 00000000..2096497b --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/settingsdialog.cpp @@ -0,0 +1,52 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "settingsdialog.h" +#include "settings.h" + +#include +#include +#include + +#include +#include + +using namespace KGAPI2; + +SettingsDialog::SettingsDialog( GoogleAccountManager *accountMgr, WId windowId, GoogleResource *parent ): + GoogleSettingsDialog( accountMgr, windowId, parent ) +{ + connect( this, SIGNAL(accepted()), + this, SLOT(saveSettings()) ); +} + +SettingsDialog::~SettingsDialog() +{ +} + +void SettingsDialog::saveSettings() +{ + const AccountPtr account = currentAccount(); + if ( !account ) { + Settings::self()->setAccount( QString() ); + Settings::self()->writeConfig(); + return; + } + + Settings::self()->setAccount( account->accountName() ); + Settings::self()->writeConfig(); +} diff --git a/kdepim-runtime/resources/google/contacts/settingsdialog.h b/kdepim-runtime/resources/google/contacts/settingsdialog.h new file mode 100644 index 00000000..c0e2e1dd --- /dev/null +++ b/kdepim-runtime/resources/google/contacts/settingsdialog.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2011-2013 Daniel Vrátil + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef GOOGLE_CONTACTS_SETTINGSDIALOG_H +#define GOOGLE_CONTACTS_SETTINGSDIALOG_H + +#include "common/googlesettingsdialog.h" + +class GoogleAccountManager; + +class SettingsDialog : public GoogleSettingsDialog +{ + Q_OBJECT + public: + explicit SettingsDialog( GoogleAccountManager *accountMgr, WId windowId, GoogleResource *parent ); + ~SettingsDialog(); + + private Q_SLOTS: + void saveSettings(); + +}; + +#endif // SETTINGSDIALOG_H diff --git a/kdepim-runtime/resources/ical/CMakeLists.txt b/kdepim-runtime/resources/ical/CMakeLists.txt new file mode 100644 index 00000000..e457d983 --- /dev/null +++ b/kdepim-runtime/resources/ical/CMakeLists.txt @@ -0,0 +1,44 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/shared +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +add_subdirectory( wizard ) +add_subdirectory( notes ) +add_subdirectory( tests ) + +########### next target ############### +add_definitions( -DSETTINGS_NAMESPACE=Akonadi_ICal_Resource ) + +set( icalresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES} + shared/icalresourcebase.cpp + shared/icalresource.cpp + icalresourceplugin.cpp +) + +install( FILES icalresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(icalresource_SRCS ${AKONADI_SINGLEFILERESOURCE_SHARED_UI}) +kde4_add_kcfg_files(icalresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/icalresource.kcfg org.kde.Akonadi.ICal.Settings) +qt4_add_dbus_adaptor(icalresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.ICal.Settings.xml settings.h Akonadi_ICal_Resource::Settings icalsettingsadaptor ICalSettingsAdaptor +) + +kde4_add_plugin(akonadi_ical_resource ${icalresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_ical_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_ical_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.ICal") + set_target_properties(akonadi_ical_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi ICal Resource") +endif () + +target_link_libraries(akonadi_ical_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTDBUS_LIBRARY} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KCALCORE_LIBS}) + +install(TARGETS akonadi_ical_resource DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/ical/Messages.sh b/kdepim-runtime/resources/ical/Messages.sh new file mode 100644 index 00000000..6361b6f0 --- /dev/null +++ b/kdepim-runtime/resources/ical/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.kcfg -o -name \*.ui` >> rc.cpp || exit 11 +$XGETTEXT `find . -name \*.cpp -o -name \*.h` -o $podir/akonadi_ical_resource.pot +rm -f rc.cpp diff --git a/kdepim-runtime/resources/ical/icalresource.desktop b/kdepim-runtime/resources/ical/icalresource.desktop new file mode 100644 index 00000000..78ca2650 --- /dev/null +++ b/kdepim-runtime/resources/ical/icalresource.desktop @@ -0,0 +1,105 @@ +[Desktop Entry] +Name=ICal Calendar File +Name[ar]=مل٠تقويم ICal +Name[bg]=Календарен файл iCal +Name[bs]=ICal kalendar datoteka +Name[ca]=Fitxer de calendari ICal +Name[ca@valencia]=Fitxer de calendari ICal +Name[cs]=Soubor s kalendářem iCal +Name[da]=iCal-kalenderfil +Name[de]=ICal-Kalenderdatei +Name[el]=ΑÏχείο ημεÏολογίου ICal +Name[en_GB]=ICal Calendar File +Name[es]=Archivo de calendario ICal +Name[et]=ICali kalendrifail +Name[fi]=iCal-kalenteritiedosto +Name[fr]=Fichier d'agenda au format « ICal » +Name[ga]=Comhad Féilire ICal +Name[gl]=Ficheiro de Calendario de ICal +Name[hu]=iCal-naptárfájl +Name[ia]=File iCal de calendario +Name[it]=Calendario iCal +Name[ja]=iCal カレンダーファイル +Name[kk]=ICal күнтізбе файлы +Name[km]=ឯកសារ​ប្រážáž·áž‘áž·áž“ ICal +Name[ko]=ICal 달력 íŒŒì¼ +Name[lt]=ICal kalendoriaus failas +Name[lv]=ICal kalendÄra fails +Name[nb]=ICal kalenderfil +Name[nds]=ICal-Kalennerdatei +Name[nl]=ICal-agendabestand +Name[nn]=iCal-kalenderfil +Name[pa]=ICal ਕੈਲੰਡਰ ਫਾਇਲ +Name[pl]=Plik kalendarza ICal +Name[pt]=Ficheiro de Calendário ICal +Name[pt_BR]=Arquivo de calendário ICal +Name[ro]=FiÈ™ier-calendar ICal +Name[ru]=Файл ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ iCal +Name[sk]=Súbor kalendára iCal +Name[sl]=Koledarska datoteka iCal +Name[sr]=И‑календарÑки фајл +Name[sr@ijekavian]=И‑календарÑки фајл +Name[sr@ijekavianlatin]=I‑kalendarski fajl +Name[sr@latin]=I‑kalendarski fajl +Name[sv]=ICal-kalenderfil +Name[tr]=ICal Takvim Dosyası +Name[uk]=Файл ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ ICal +Name[x-test]=xxICal Calendar Filexx +Name[zh_CN]=ICal 日历文件 +Name[zh_TW]=ICal 行事曆檔案 +Comment=Loads data from an iCal file +Comment[ar]=تحميل البيانات من مل٠ICal +Comment[bg]=Зареждане на данни от файл iCal +Comment[bs]=UÄitava podatke iz iCal datoteke +Comment[ca]=Carrega les dades des d'un fitxer iCal +Comment[ca@valencia]=Carregar dades des d'un fitxer iCal +Comment[cs]=NaÄítá data z iCal souboru +Comment[da]=Indlæser data fra en iCal-fil +Comment[de]=Daten werden aus einer lokalen iCal-Datei geladen +Comment[el]=ΦόÏτωση δεδομένων από ένα αÏχείο iCal +Comment[en_GB]=Loads data from an iCal file +Comment[es]=Carga datos de un archivo iCal local +Comment[et]=Andmete laadimine iCal-failist +Comment[fi]=Noutaa tietoa iCal-tiedostosta +Comment[fr]=Charge des données depuis un fichier au format « iCal » +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad iCal +Comment[gl]=Carga datos desde un ficheiro iCal +Comment[hu]=Adatbetöltés iCal formátumú naptárból +Comment[ia]=Lege datos de un file iCal +Comment[it]=Carica dati da un file iCal +Comment[ja]=iCal ファイルã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=ICal күнтізбе файлынан деректі алып беру +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ឯកសារ iCal +Comment[ko]=로컬 iCal 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 가져오기 +Comment[lt]=Ä®kelia duomenis iÅ¡ iCal failo +Comment[lv]=IelÄdÄ“ datus no iCal faila +Comment[nb]=Laster data fra en iCal-fil +Comment[nds]=Laadt Daten ut en ICal-Datei +Comment[nl]=Laadt gegevens van een iCal-bestand +Comment[nn]=Lastar data frÃ¥ ei iCal-fil +Comment[pa]=iCal ਫਾਇਲ ਤੋਂ ਡਾਟਾ ਡਾਊਨਲੋਡ ਕਰੋ +Comment[pl]=Wczytuje dane z pliku ICal +Comment[pt]=Carrega os dados de um ficheiro iCal +Comment[pt_BR]=Carrega os dados de um arquivo iCal +Comment[ro]=ÃŽncarcă date dintr-un fiÈ™ier iCal +Comment[ru]=Загрузка данных из файла ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ iCal +Comment[sk]=NaÄíta dáta zo súboru iCal +Comment[sl]=Naloži podatke iz datoteke iCal +Comment[sr]=Учитава податке из и‑календарÑког фајла +Comment[sr@ijekavian]=Учитава податке из и‑календарÑког фајла +Comment[sr@ijekavianlatin]=UÄitava podatke iz i‑kalendarskog fajla +Comment[sr@latin]=UÄitava podatke iz i‑kalendarskog fajla +Comment[sv]=Laddar data frÃ¥n en iCal-fil +Comment[tr]=iCal dosyasından veri yükler +Comment[uk]=Завантажує дані з файла iCal +Comment[x-test]=xxLoads data from an iCal filexx +Comment[zh_CN]=从 iCal æ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從 iCal 檔載入資料 +Type=AkonadiResource +Exec=akonadi_ical_resource +Icon=text-calendar + +X-Akonadi-MimeTypes=text/calendar,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_ical_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/ical/icalresource.kcfg b/kdepim-runtime/resources/ical/icalresource.kcfg new file mode 100644 index 00000000..972d3486 --- /dev/null +++ b/kdepim-runtime/resources/ical/icalresource.kcfg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + false + + + + true + + + diff --git a/kdepim-runtime/resources/ical/icalresourceplugin.cpp b/kdepim-runtime/resources/ical/icalresourceplugin.cpp new file mode 100644 index 00000000..609fbc8b --- /dev/null +++ b/kdepim-runtime/resources/ical/icalresourceplugin.cpp @@ -0,0 +1,32 @@ +/* + Copyright (c) 2010 Bertjan Broeksema + + 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 "icalresource.h" + +#include + +#include + +/** We need to put this in a separate file because the notes plugin inherits the + ical plugin and we'd get a multiple definition of `qt_plugin_instance' + if we put the line below in icalresource.cpp. Now we can compile it only for + the ical resource + */ +AKONADI_AGENT_FACTORY( ICalResource, akonadi_ical_resource ) + diff --git a/kdepim-runtime/resources/ical/notes/CMakeLists.txt b/kdepim-runtime/resources/ical/notes/CMakeLists.txt new file mode 100644 index 00000000..8a9536f2 --- /dev/null +++ b/kdepim-runtime/resources/ical/notes/CMakeLists.txt @@ -0,0 +1,35 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../shared + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +add_definitions( -DSETTINGS_NAMESPACE=Akonadi_Aknotes_Resource ) + +set( notesresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES} + ../shared/icalresourcebase.cpp + ../shared/icalresource.cpp + notesresource.cpp +) + +install( FILES notesresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(notesresource_SRCS ${AKONADI_SINGLEFILERESOURCE_SHARED_UI}) +kde4_add_kcfg_files(notesresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/notesresource.kcfg org.kde.Akonadi.Notes.Settings) +qt4_add_dbus_adaptor(notesresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Notes.Settings.xml settings.h Akonadi_Aknotes_Resource::Settings icalsettingsadaptor ICalSettingsAdaptor +) + +kde4_add_plugin(akonadi_notes_resource ${notesresource_SRCS}) + +target_link_libraries(akonadi_notes_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTDBUS_LIBRARY} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KCALCORE_LIBS}) + +install(TARGETS akonadi_notes_resource DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/ical/notes/notesresource.cpp b/kdepim-runtime/resources/ical/notes/notesresource.cpp new file mode 100644 index 00000000..2ecc2187 --- /dev/null +++ b/kdepim-runtime/resources/ical/notes/notesresource.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2009 David Jarvie + + 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 "notesresource.h" + +#include + +#include + +#include +#include +#include +#include + +using namespace Akonadi; +using namespace KCalCore; + +static QLatin1String sNotesType( "application/x-vnd.kde.notes" ); + +NotesResource::NotesResource( const QString &id ) + : ICalResource( id, allMimeTypes(), QLatin1String("knotes") ) +{ + KConfigSkeleton::ItemPath *item = static_cast( mSettings->findItem( QLatin1String("Path") ) ); + if ( item ) { + item->setDefaultValue( KGlobal::dirs()->saveLocation( "data", QLatin1String("knotes/") ) + QLatin1String("notes.ics") ); + } +} + +NotesResource::~NotesResource() +{ +} + +QStringList NotesResource::allMimeTypes() const +{ + return QStringList() << sNotesType; +} + +QString NotesResource::mimeType( const KCalCore::IncidenceBase::Ptr & ) const +{ + return sNotesType; +} + +AKONADI_AGENT_FACTORY( NotesResource, akonadi_notes_resource ) + diff --git a/kdepim-runtime/resources/ical/notes/notesresource.desktop b/kdepim-runtime/resources/ical/notes/notesresource.desktop new file mode 100644 index 00000000..6b1f6ab5 --- /dev/null +++ b/kdepim-runtime/resources/ical/notes/notesresource.desktop @@ -0,0 +1,129 @@ +[Desktop Entry] +Name=Notes +Name[af]=Notas +Name[ar]=ملاحظات +Name[be]=Заметкі +Name[bg]=Бележки +Name[br]=Notennoù +Name[bs]=BiljeÅ¡ke +Name[ca]=Notes +Name[ca@valencia]=Notes +Name[cs]=Poznámky +Name[cy]=Nodiadau +Name[da]=Noter +Name[de]=Notizen +Name[el]=Σημειώσεις +Name[en_GB]=Notes +Name[eo]=Notoj +Name[es]=Notas +Name[et]=Sedelid +Name[eu]=Oharrak +Name[fa]=یادداشتها +Name[fi]=Muistiinpanot +Name[fr]=Notes +Name[fy]=Notysjes +Name[ga]=Nótaí +Name[gl]=Notas +Name[he]=×¤×ª×§×™× +Name[hu]=Feljegyzések +Name[ia]=Notas +Name[is]=Minnismiðar +Name[it]=Note +Name[ja]=メモ +Name[ka]=ჩáƒáƒœáƒ˜áƒ¨áƒ•áƒœáƒ”ბი +Name[kk]=Жазбалар +Name[km]=ចំណាំ +Name[ko]=노트 +Name[lt]=UžraÅ¡ai +Name[lv]=PiezÄ«mes +Name[mai]=टिपà¥à¤ªà¤£à¥€ +Name[mk]=Белешки +Name[ms]=Nota +Name[nb]=Notater +Name[nds]=Notizen +Name[ne]=टिपोट +Name[nl]=Notities +Name[nn]=Notat +Name[oc]=Nòtas +Name[pa]=ਨੋਟਿਸ +Name[pl]=Notatki +Name[pt]=Notas +Name[pt_BR]=Notas +Name[ro]=NotiÈ›e +Name[ru]=Заметки +Name[se]=Nohtat +Name[sk]=Poznámky +Name[sl]=Notice +Name[sq]=Shënimet +Name[sr]=Белешке +Name[sr@ijekavian]=Биљешке +Name[sr@ijekavianlatin]=BiljeÅ¡ke +Name[sr@latin]=BeleÅ¡ke +Name[sv]=Anteckningar +Name[ta]=கà¯à®±à®¿à®ªà¯à®ªà¯à®•à®³à¯ +Name[tg]=Ðхборот +Name[th]=บันทึà¸à¸¢à¹ˆà¸­ +Name[tr]=Notlar +Name[ug]=ئىزاھ +Name[uk]=Примітки +Name[uz]=Yozma xotira +Name[uz@cyrillic]=Ðзма хотира +Name[wa]=Notes +Name[x-test]=xxNotesxx +Name[zh_CN]=便笺 +Name[zh_TW]=備忘錄 +Comment=Loads data from a notes file +Comment[ar]=تحميل البيانات من مل٠الملاحظات +Comment[bg]=Зареждане на данни от файл Ñ Ð±ÐµÐ»ÐµÐ¶ÐºÐ¸ +Comment[bs]=UÄitava podatke iz datoteka biljeÅ¡ki +Comment[ca]=Carrega les dades des d'un fitxer de notes +Comment[ca@valencia]=Carrega dades des d'un fitxer de notes +Comment[da]=Indlæser data fra en notes-fil +Comment[de]=Lädt Daten aus einer Notizen-Datei +Comment[el]=ΦόÏτωση δεδομένων από ένα αÏχείο σημειώσεων +Comment[en_GB]=Loads data from a notes file +Comment[es]=Carga datos desde un archivo de notas +Comment[et]=Andmete laadimine sedelite failist +Comment[fi]=Noutaa tietoa muistiinpanotiedostosta +Comment[fr]=Charge des données depuis un fichier de notes +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad nótaí +Comment[gl]=Carga datos desde un ficheiro de notas +Comment[hu]=Adatbetöltés feljegyzésfájlból +Comment[ia]=Lege datos de un file de notas +Comment[it]=Carica dati da un file contenente note +Comment[ja]=メモファイルã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=Жазба файлынан деректі алып берді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ឯកសារ​ចំណាំ +Comment[ko]=로컬 노트 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 가져오기 +Comment[lt]=Ä®kelia duomenis iÅ¡ lipnių lapelių failų +Comment[lv]=IelÄdÄ“ datus no piezÄ«mju faila +Comment[nb]=Laster data fra en notat-fil +Comment[nds]=Laadt Daten ut en Notizen-Datei +Comment[nl]=Laadt gegevens van een notitiesbestand +Comment[nn]=Lastar data frÃ¥ ei notatfil +Comment[pa]=ਨੋਟਿਸ ਫਾਇਲ ਤੋਂ ਡਾਟਾ ਡਾਊਨਲੋਡ ਕਰੋ +Comment[pl]=Wczytuje dane z pliku notatek +Comment[pt]=Carrega os dados de um ficheiro de notas +Comment[pt_BR]=Carrega os dados de um arquivo de notas +Comment[ro]=ÃŽncarcă date dintr-un fiÈ™ier cu note +Comment[ru]=Загрузка данных из файла заметок +Comment[sk]=NaÄíta dáta zo súboru poznámok +Comment[sl]=Naloži podatke iz datoteke z noticami +Comment[sr]=Учитава податке из фајла белешки +Comment[sr@ijekavian]=Учитава податке из фајла биљешки +Comment[sr@ijekavianlatin]=UÄitava podatke iz fajla biljeÅ¡ki +Comment[sr@latin]=UÄitava podatke iz fajla beleÅ¡ki +Comment[sv]=Laddar data frÃ¥n en anteckningsfil +Comment[tr]=Bir not dosyasından veri yükler +Comment[uk]=Завантажує дані з файла нотаток +Comment[x-test]=xxLoads data from a notes filexx +Comment[zh_CN]=ä»Žä¾¿ç¬ºæ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從 notes 檔載入資料 +Type=AkonadiResource +Exec=akonadi_notes_resource +Icon=view-pim-notes + +X-Akonadi-MimeTypes=application/x-vnd.kde.notes +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_notes_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/ical/notes/notesresource.h b/kdepim-runtime/resources/ical/notes/notesresource.h new file mode 100644 index 00000000..85a30eaf --- /dev/null +++ b/kdepim-runtime/resources/ical/notes/notesresource.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2009 David Jarvie + + 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 NOTESRESOURCE_H +#define NOTESRESOURCE_H + +#include "icalresource.h" + +class NotesResource : public ICalResource +{ + Q_OBJECT + + public: + explicit NotesResource( const QString &id ); + ~NotesResource(); + + protected: + /** + Returns the Akonadi specific @c text/calendar sub MIME type of the given @p incidence. + */ + virtual QString mimeType( const KCalCore::IncidenceBase::Ptr &incidence ) const; + + /** + Returns a list of all calendar component sub MIME types. + */ + virtual QStringList allMimeTypes() const; +}; + +#endif diff --git a/kdepim-runtime/resources/ical/notes/notesresource.kcfg b/kdepim-runtime/resources/ical/notes/notesresource.kcfg new file mode 100644 index 00000000..1ed20ec5 --- /dev/null +++ b/kdepim-runtime/resources/ical/notes/notesresource.kcfg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + false + + + + 1 + + + + true + + + diff --git a/kdepim-runtime/resources/ical/notes/settings.kcfgc b/kdepim-runtime/resources/ical/notes/settings.kcfgc new file mode 100644 index 00000000..325e38ff --- /dev/null +++ b/kdepim-runtime/resources/ical/notes/settings.kcfgc @@ -0,0 +1,9 @@ +File=notesresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true +Namespace=Akonadi_Aknotes_Resource diff --git a/kdepim-runtime/resources/ical/settings.kcfgc b/kdepim-runtime/resources/ical/settings.kcfgc new file mode 100644 index 00000000..d024977a --- /dev/null +++ b/kdepim-runtime/resources/ical/settings.kcfgc @@ -0,0 +1,10 @@ +File=icalresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true +Namespace=Akonadi_ICal_Resource + diff --git a/kdepim-runtime/resources/ical/shared/icalresource.cpp b/kdepim-runtime/resources/ical/shared/icalresource.cpp new file mode 100644 index 00000000..200c322a --- /dev/null +++ b/kdepim-runtime/resources/ical/shared/icalresource.cpp @@ -0,0 +1,154 @@ +/* + Copyright (c) 2006 Till Adam + Copyright (c) 2009 David Jarvie + + 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 "icalresource.h" + +#include +#include + +#include +#include + +using namespace Akonadi; +using namespace KCalCore; + +ICalResource::ICalResource( const QString &id ) + : ICalResourceBase( id ) +{ + QStringList mimeTypes; + mimeTypes << QLatin1String( "text/calendar" ); + mimeTypes += allMimeTypes(); + initialise( mimeTypes, QLatin1String("office-calendar") ); +} + +ICalResource::ICalResource( const QString &id, const QStringList &mimeTypes, const QString& icon ) + : ICalResourceBase( id ) +{ + initialise( mimeTypes, icon ); +} + +ICalResource::~ICalResource() +{ +} + +bool ICalResource::doRetrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( parts ); + const QString rid = item.remoteId(); + Incidence::Ptr incidence = calendar()->instance( rid ); + if ( !incidence ) { + kError() << "akonadi_ical_resource: Can't find incidence with uid " + << rid << "; item.id() = " << item.id(); + emit error( i18n( "Incidence with uid '%1' not found.", rid ) ); + return false; + } + + Incidence::Ptr incidencePtr( incidence->clone() ); + + Item i = item; + i.setMimeType( incidencePtr->mimeType() ); + i.setPayload( incidencePtr ); + itemRetrieved( i ); + return true; +} + +void ICalResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& ) +{ + if ( !checkItemAddedChanged( item, CheckForAdded ) ) { + return; + } + + Incidence::Ptr i = item.payload(); + if ( !calendar()->addIncidence( Incidence::Ptr( i->clone() ) ) ) { + kError() << "akonadi_ical_resource: Error adding incidence with uid " + << i->uid() << "; item.id() " << item.id() << i->recurrenceId(); + cancelTask(); + return; + } + + Item it( item ); + it.setRemoteId( i->instanceIdentifier() ); + scheduleWrite(); + changeCommitted( it ); +} + +void ICalResource::itemChanged( const Akonadi::Item &item, + const QSet &parts ) +{ + Q_UNUSED( parts ) + + if ( !checkItemAddedChanged( item, CheckForChanged ) ) { + return; + } + + Incidence::Ptr payload = item.payload(); + Incidence::Ptr incidence = calendar()->instance( item.remoteId() ); + if ( !incidence ) { + // not in the calendar yet, should not happen -> add it + calendar()->addIncidence( Incidence::Ptr( payload->clone() ) ); + } else { + // make sure any observer the resource might have installed gets properly notified + incidence->startUpdates(); + + if ( incidence->type() == payload->type() ) { + // IncidenceBase::operator= calls virtual method assign, so it's safe. + *incidence.staticCast().data() = *payload.data(); + incidence->updated(); + incidence->endUpdates(); + } else { + incidence->endUpdates(); + kWarning() << "akonadi_ical_resource: Item changed incidence type. Replacing it."; + + calendar()->deleteIncidence( incidence ); + calendar()->addIncidence( Incidence::Ptr( payload->clone() ) ); + } + } + scheduleWrite(); + changeCommitted( item ); +} + +void ICalResource::doRetrieveItems( const Akonadi::Collection & col ) +{ + Q_UNUSED( col ); + Incidence::List incidences = calendar()->incidences(); + Item::List items; + foreach ( const Incidence::Ptr &incidence, incidences ) { + Item item ( incidence->mimeType() ); + item.setRemoteId( incidence->instanceIdentifier() ); + item.setPayload( Incidence::Ptr( incidence->clone() ) ); + items << item; + } + itemsRetrieved( items ); +} + +QStringList ICalResource::allMimeTypes() const +{ + return QStringList() << KCalCore::Event::eventMimeType() + << KCalCore::Todo::todoMimeType() + << KCalCore::Journal::journalMimeType() + << KCalCore::FreeBusy::freeBusyMimeType(); +} + +QString ICalResource::mimeType( const IncidenceBase::Ptr &incidence ) const +{ + return incidence->mimeType(); +} + + diff --git a/kdepim-runtime/resources/ical/shared/icalresource.h b/kdepim-runtime/resources/ical/shared/icalresource.h new file mode 100644 index 00000000..5a292a7e --- /dev/null +++ b/kdepim-runtime/resources/ical/shared/icalresource.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2006 Till Adam + Copyright (c) 2009 David Jarvie + + 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 ICALRESOURCE_H +#define ICALRESOURCE_H + +#include "icalresourcebase.h" + +#include + +class ICalResource : public ICalResourceBase +{ + Q_OBJECT + + public: + explicit ICalResource( const QString &id ); + ~ICalResource(); + + protected: + /** + * Constructor for derived classes. + * @param mimeTypes mimeTypes to be handled by the resource. + * @param icon icon name to use. + */ + ICalResource( const QString &id, const QStringList &mimeTypes, const QString& icon ); + + virtual bool doRetrieveItem( const Akonadi::Item &item, const QSet &parts ); + virtual void doRetrieveItems( const Akonadi::Collection &col ); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection& ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + + /** + Returns the Akonadi specific @c text/calendar sub MIME type of the given @p incidence. + */ + virtual QString mimeType( const KCalCore::IncidenceBase::Ptr &incidence ) const; + + /** + Returns a list of all calendar component sub MIME types. + */ + virtual QStringList allMimeTypes() const; +}; + +#endif diff --git a/kdepim-runtime/resources/ical/shared/icalresourcebase.cpp b/kdepim-runtime/resources/ical/shared/icalresourcebase.cpp new file mode 100644 index 00000000..e26c337b --- /dev/null +++ b/kdepim-runtime/resources/ical/shared/icalresourcebase.cpp @@ -0,0 +1,175 @@ +/* + Copyright (c) 2006 Till Adam + Copyright (c) 2009 David Jarvie + + 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 "icalresourcebase.h" +#include "icalsettingsadaptor.h" +#include "singlefileresourceconfigdialog.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace Akonadi; +using namespace KCalCore; +using namespace SETTINGS_NAMESPACE; + +ICalResourceBase::ICalResourceBase( const QString &id ) + : SingleFileResource( id ) +{ + KGlobal::locale()->insertCatalog( QLatin1String("akonadi_ical_resource") ); +} + +void ICalResourceBase::initialise( const QStringList &mimeTypes, const QString &icon ) +{ + setSupportedMimetypes( mimeTypes, icon ); + new ICalSettingsAdaptor( mSettings ); + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + mSettings, QDBusConnection::ExportAdaptors ); +} + +ICalResourceBase::~ICalResourceBase() +{ +} + +bool ICalResourceBase::retrieveItem( const Akonadi::Item &item, + const QSet &parts ) +{ + kDebug( 5251 ) << "Item:" << item.url(); + + if ( !mCalendar ) { + kError() << "akonadi_ical_resource: Calendar not loaded"; + emit error( i18n( "Calendar not loaded.") ); + return false; + } + + return doRetrieveItem( item, parts ); +} + +void ICalResourceBase::aboutToQuit() +{ + if ( !mSettings->readOnly() ) { + writeFile(); + } + mSettings->writeConfig(); +} + +void ICalResourceBase::customizeConfigDialog( SingleFileResourceConfigDialog *dlg ) +{ +#ifndef KDEPIM_MOBILE_UI + dlg->setFilter( QLatin1String("text/calendar") ); +#else + dlg->setFilter( QLatin1String("*.ics *.vcs") ); +#endif + dlg->setCaption( i18n( "Select Calendar" ) ); +} + +bool ICalResourceBase::readFromFile( const QString &fileName ) +{ + mCalendar = KCalCore::MemoryCalendar::Ptr( new KCalCore::MemoryCalendar( QLatin1String( "UTC" ) ) ); + mFileStorage = KCalCore::FileStorage::Ptr( new KCalCore::FileStorage( mCalendar, fileName, + new KCalCore::ICalFormat() ) ); + const bool result = mFileStorage->load(); + if ( !result ) { + kError() << "akonadi_ical_resource: Error loading file " << fileName; + } + + return result; +} + +void ICalResourceBase::itemRemoved( const Akonadi::Item &item ) +{ + if ( !mCalendar ) { + kError() << "akonadi_ical_resource: mCalendar is 0!"; + cancelTask( i18n( "Calendar not loaded." ) ); + return; + } + + Incidence::Ptr i = mCalendar->instance( item.remoteId() ); + if ( i ) { + if ( !mCalendar->deleteIncidence( i ) ) { + kError() << "akonadi_ical_resource: Can't delete incidence with instance identifier " + << item.remoteId() << "; item.id() = " << item.id(); + cancelTask(); + return; + } + } else { + kError() << "akonadi_ical_resource: itemRemoved(): Can't find incidence with instance identifier " + << item.remoteId() << "; item.id() = " << item.id(); + } + scheduleWrite(); + changeProcessed(); +} + +void ICalResourceBase::retrieveItems( const Akonadi::Collection &col ) +{ + reloadFile(); + if ( mCalendar ) { + doRetrieveItems( col ); + } else { + kError() << "akonadi_ical_resource: retrieveItems(): mCalendar is 0!"; + } +} + +bool ICalResourceBase::writeToFile( const QString &fileName ) +{ + if ( !mCalendar ) { + kError() << "akonadi_ical_resource: writeToFile() mCalendar is 0!"; + return false; + } + + KCalCore::FileStorage *fileStorage = mFileStorage.data(); + if ( fileName != mFileStorage->fileName() ) { + fileStorage = new KCalCore::FileStorage( mCalendar, + fileName, + new KCalCore::ICalFormat() ); + } + + bool success = true; + if ( !fileStorage->save() ) { + kError() << QLatin1String("akonadi_ical_resource: Failed to save calendar to file ") + fileName; + emit error( i18n( "Failed to save calendar file to %1", fileName ) ); + success = false; + } + + if ( fileStorage != mFileStorage.data() ) { + delete fileStorage; + } + + return success; +} + +KCalCore::MemoryCalendar::Ptr ICalResourceBase::calendar() const +{ + return mCalendar; +} + +KCalCore::FileStorage::Ptr ICalResourceBase::fileStorage() const +{ + return mFileStorage; +} + + diff --git a/kdepim-runtime/resources/ical/shared/icalresourcebase.h b/kdepim-runtime/resources/ical/shared/icalresourcebase.h new file mode 100644 index 00000000..91f2ca7f --- /dev/null +++ b/kdepim-runtime/resources/ical/shared/icalresourcebase.h @@ -0,0 +1,111 @@ +/* + Copyright (c) 2006 Till Adam + Copyright (c) 2009 David Jarvie + + 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 ICALRESOURCEBASE_H +#define ICALRESOURCEBASE_H + +#include "singlefileresource.h" +#include "settings.h" + +#include +#include + + +class ICalResourceBase : public Akonadi::SingleFileResource +{ + Q_OBJECT + + public: + explicit ICalResourceBase( const QString &id ); + ~ICalResourceBase(); + + protected Q_SLOTS: + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + void retrieveItems( const Akonadi::Collection &col ); + + protected: + enum CheckType { CheckForAdded, CheckForChanged }; + + void initialise( const QStringList &mimeTypes, const QString &icon ); + bool readFromFile( const QString &fileName ); + bool writeToFile( const QString &fileName ); + + /** + * Customize the configuration dialog before it is displayed. + */ + virtual void customizeConfigDialog( Akonadi::SingleFileResourceConfigDialog* dlg ); + + virtual void aboutToQuit(); + + /** + * Retrieve an incidence from the calendar, and set it into a new item's payload. + * Retrieval of the item should be signalled by calling @p itemRetrieved(). + * @param item the incidence ID to retrieve is provided by @c item.remoteId() + * @return true if item retrieved, false if not. + */ + virtual bool doRetrieveItem( const Akonadi::Item &item, const QSet &parts ) = 0; + + /** + * Retrieve all incidences from the calendar, and set each into a new item's payload. + * Retrieval of the items should be signalled by calling @p itemsRetrieved(). + */ + virtual void doRetrieveItems( const Akonadi::Collection &col ) = 0; + + /** + * To be called at the start of derived class implementations of itemAdded() + * or itemChanged() to verify that required conditions are true. + * @param type the type of change to perform the checks for. + * @return true if all checks are successful, and processing can continue; + * false if a check failed, in which case itemAdded() or itemChanged() + * should stop processing. + */ + template bool checkItemAddedChanged( const Akonadi::Item &item, CheckType type ); + + virtual void itemRemoved( const Akonadi::Item &item ); + + /** Return the local calendar. */ + KCalCore::MemoryCalendar::Ptr calendar() const; + + /** Return the calendar file storage. */ + KCalCore::FileStorage::Ptr fileStorage() const; + + private: + KCalCore::MemoryCalendar::Ptr mCalendar; + KCalCore::FileStorage::Ptr mFileStorage; +}; + +template +bool ICalResourceBase::checkItemAddedChanged( const Akonadi::Item &item, CheckType type ) +{ + if ( !mCalendar ) { + cancelTask( i18n( "Calendar not loaded." ) ); + return false; + } + if ( !item.hasPayload() ) { + QString msg = ( type == CheckForAdded ) + ? i18n( "Unable to retrieve added item %1.", item.id() ) + : i18n( "Unable to retrieve modified item %1.", item.id() ); + cancelTask( msg ); + return false; + } + return true; +} + +#endif diff --git a/kdepim-runtime/resources/ical/tests/CMakeLists.txt b/kdepim-runtime/resources/ical/tests/CMakeLists.txt new file mode 100644 index 00000000..1a338cad --- /dev/null +++ b/kdepim-runtime/resources/ical/tests/CMakeLists.txt @@ -0,0 +1,4 @@ +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ical-empty.xml ${CMAKE_CURRENT_BINARY_DIR}/ical-empty.xml COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ical-step1.xml ${CMAKE_CURRENT_BINARY_DIR}/ical-step1.xml COPYONLY) + +akonadi_add_resourcetest( ical icaltest.es ) diff --git a/kdepim-runtime/resources/ical/tests/event.ical b/kdepim-runtime/resources/ical/tests/event.ical new file mode 100644 index 00000000..a7844eea --- /dev/null +++ b/kdepim-runtime/resources/ical/tests/event.ical @@ -0,0 +1,26 @@ +BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN +VERSION:2.0 +BEGIN:VEVENT +DTSTAMP:20070109T100625Z +ORGANIZER;CN=Volker Krause:MAILTO:vkrause@kde.org +CREATED:20070109T100553Z +UID:libkcal-1135684253.945 +SEQUENCE:1 +LAST-MODIFIED:20070109T100625Z +SUMMARY:Test event +LOCATION:here +CLASS:PUBLIC +PRIORITY:5 +CATEGORIES:KDE +DTSTART:20070109T183000Z +DTEND:20070109T225900Z +TRANSP:OPAQUE +BEGIN:VALARM +DESCRIPTION: +ACTION:DISPLAY +TRIGGER;VALUE=DURATION:-PT45M +END:VALARM +END:VEVENT +END:VCALENDAR + diff --git a/kdepim-runtime/resources/ical/tests/ical-empty.xml b/kdepim-runtime/resources/ical/tests/ical-empty.xml new file mode 100644 index 00000000..a1e2d7b3 --- /dev/null +++ b/kdepim-runtime/resources/ical/tests/ical-empty.xml @@ -0,0 +1,6 @@ + + + ("newical.ics" "office-calendar") + wcdW + + diff --git a/kdepim-runtime/resources/ical/tests/ical-step1.xml b/kdepim-runtime/resources/ical/tests/ical-step1.xml new file mode 100644 index 00000000..1e7b9b85 --- /dev/null +++ b/kdepim-runtime/resources/ical/tests/ical-step1.xml @@ -0,0 +1,52 @@ + + + ("newical.ics" "office-calendar") + wcdW + + BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN +VERSION:2.0 +BEGIN:VEVENT +DTSTAMP:20090412T123141Z +ORGANIZER;CN="Volker Krause":MAILTO:vkrause@kde.org +CREATED:20070109T100553Z +UID:libkcal-1135684253.945 +SEQUENCE:1 +LAST-MODIFIED:20070109T100625Z +SUMMARY:Test event +LOCATION:here +PRIORITY:5 +CATEGORIES:KDE +DTSTART:20070109T183000Z +DTEND:20070109T225900Z +TRANSP:OPAQUE +BEGIN:VALARM +DESCRIPTION: +ACTION:DISPLAY +TRIGGER;VALUE=DURATION:-PT45M +END:VALARM +END:VEVENT + +END:VCALENDAR + + + BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN +VERSION:2.0 +BEGIN:VTODO +DTSTAMP:20090412T123141Z +ORGANIZER:MAILTO:vkrause@kde.org +CREATED:20040505T094143Z +UID:libkcal-1506191911.958 +LAST-MODIFIED:20040512T133925Z +SUMMARY:Add a demo task to this file +PRIORITY:3 +DUE;VALUE=DATE:20090101 +COMPLETED:20090101T133925Z +PERCENT-COMPLETE:100 +END:VTODO + +END:VCALENDAR + + + diff --git a/kdepim-runtime/resources/ical/tests/icaltest.es b/kdepim-runtime/resources/ical/tests/icaltest.es new file mode 100644 index 00000000..06c2687a --- /dev/null +++ b/kdepim-runtime/resources/ical/tests/icaltest.es @@ -0,0 +1,31 @@ +Resource.setType( "akonadi_ical_resource" ); +Resource.setPathOption( "Path", "newical.ics" ); +Resource.create(); + +XmlOperations.setXmlFile( "ical-empty.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.setCollectionKey( "None" ); +XmlOperations.ignoreCollectionField( "Name" ); // name is the resource identifier and thus unpredictable +XmlOperations.setNormalizeRemoteIds( true ); +XmlOperations.assertEqual(); + +// item creation +var i1 = ItemTest.newInstance(); +i1.setParentCollection( Resource.identifier() ); +i1.setMimeType( "text/calendar" ); +i1.setPayloadFromFile( "event.ical" ); +i1.create(); + +var i2 = ItemTest.newInstance(); +i2.setParentCollection( Resource.identifier() ); +i2.setMimeType( "text/calendar" ); +i2.setPayloadFromFile( "task.ical" ); +i2.create(); + +Resource.recreate(); + +XmlOperations.setXmlFile( "ical-step1.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.ignoreCollectionField( "None" ); +XmlOperations.assertEqual(); + diff --git a/kdepim-runtime/resources/ical/tests/task.ical b/kdepim-runtime/resources/ical/tests/task.ical new file mode 100644 index 00000000..cc51d6b9 --- /dev/null +++ b/kdepim-runtime/resources/ical/tests/task.ical @@ -0,0 +1,16 @@ +BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN +VERSION:2.0 +BEGIN:VTODO +DTSTAMP:20090101T154017Z +ORGANIZER:MAILTO:vkrause@kde.org +CREATED:20040505T094143Z +UID:libkcal-1506191911.958 +LAST-MODIFIED:20040512T133925Z +SUMMARY:Add a demo task to this file +PRIORITY:3 +DUE;VALUE=DATE:20090101 +COMPLETED:20090101T133925Z +PERCENT-COMPLETE:100 +END:VTODO +END:VCALENDAR diff --git a/kdepim-runtime/resources/ical/wizard/CMakeLists.txt b/kdepim-runtime/resources/ical/wizard/CMakeLists.txt new file mode 100644 index 00000000..e887ca97 --- /dev/null +++ b/kdepim-runtime/resources/ical/wizard/CMakeLists.txt @@ -0,0 +1,4 @@ +set(ICAL_FILE_DEFAULT_PATH "$HOME/.local/share/korganizer/calendar.ics") + +configure_file(icalwizard.es.cmake ${CMAKE_CURRENT_BINARY_DIR}/icalwizard.es) +install ( FILES icalwizard.desktop ${CMAKE_CURRENT_BINARY_DIR}/icalwizard.es icalwizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/ical ) diff --git a/kdepim-runtime/resources/ical/wizard/Messages.sh b/kdepim-runtime/resources/ical/wizard/Messages.sh new file mode 100644 index 00000000..6121338d --- /dev/null +++ b/kdepim-runtime/resources/ical/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_ical.pot +$XGETTEXT -kqsTr *.es.cmake -j -o $podir/accountwizard_ical.pot diff --git a/kdepim-runtime/resources/ical/wizard/icalwizard.desktop b/kdepim-runtime/resources/ical/wizard/icalwizard.desktop new file mode 100644 index 00000000..d8069bb7 --- /dev/null +++ b/kdepim-runtime/resources/ical/wizard/icalwizard.desktop @@ -0,0 +1,105 @@ +[Desktop Entry] +Name=ICal Calendar File +Name[ar]=مل٠تقويم ICal +Name[bg]=Календарен файл iCal +Name[bs]=ICal kalendar datoteka +Name[ca]=Fitxer de calendari ICal +Name[ca@valencia]=Fitxer de calendari ICal +Name[cs]=Soubor s kalendářem iCal +Name[da]=iCal-kalenderfil +Name[de]=ICal-Kalenderdatei +Name[el]=ΑÏχείο ημεÏολογίου ICal +Name[en_GB]=ICal Calendar File +Name[es]=Archivo de calendario ICal +Name[et]=ICali kalendrifail +Name[fi]=iCal-kalenteritiedosto +Name[fr]=Fichier d'agenda au format « ICal » +Name[ga]=Comhad Féilire ICal +Name[gl]=Ficheiro de Calendario de ICal +Name[hu]=iCal-naptárfájl +Name[ia]=File iCal de calendario +Name[it]=Calendario iCal +Name[ja]=iCal カレンダーファイル +Name[kk]=ICal күнтізбе файлы +Name[km]=ឯកសារ​ប្រážáž·áž‘áž·áž“ ICal +Name[ko]=ICal 달력 íŒŒì¼ +Name[lt]=ICal kalendoriaus failas +Name[lv]=ICal kalendÄra fails +Name[nb]=ICal kalenderfil +Name[nds]=ICal-Kalennerdatei +Name[nl]=ICal-agendabestand +Name[nn]=iCal-kalenderfil +Name[pa]=ICal ਕੈਲੰਡਰ ਫਾਇਲ +Name[pl]=Plik kalendarza ICal +Name[pt]=Ficheiro de Calendário ICal +Name[pt_BR]=Arquivo de calendário ICal +Name[ro]=FiÈ™ier-calendar ICal +Name[ru]=Файл ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ iCal +Name[sk]=Súbor kalendára iCal +Name[sl]=Koledarska datoteka iCal +Name[sr]=И‑календарÑки фајл +Name[sr@ijekavian]=И‑календарÑки фајл +Name[sr@ijekavianlatin]=I‑kalendarski fajl +Name[sr@latin]=I‑kalendarski fajl +Name[sv]=ICal-kalenderfil +Name[tr]=ICal Takvim Dosyası +Name[uk]=Файл ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ ICal +Name[x-test]=xxICal Calendar Filexx +Name[zh_CN]=ICal 日历文件 +Name[zh_TW]=ICal 行事曆檔案 +Icon=text-calendar +Comment=Loads data from an iCal file +Comment[ar]=تحميل البيانات من مل٠ICal +Comment[bg]=Зареждане на данни от файл iCal +Comment[bs]=UÄitava podatke iz iCal datoteke +Comment[ca]=Carrega les dades des d'un fitxer iCal +Comment[ca@valencia]=Carregar dades des d'un fitxer iCal +Comment[cs]=NaÄítá data z iCal souboru +Comment[da]=Indlæser data fra en iCal-fil +Comment[de]=Daten werden aus einer lokalen iCal-Datei geladen +Comment[el]=ΦόÏτωση δεδομένων από ένα αÏχείο iCal +Comment[en_GB]=Loads data from an iCal file +Comment[es]=Carga datos de un archivo iCal local +Comment[et]=Andmete laadimine iCal-failist +Comment[fi]=Noutaa tietoa iCal-tiedostosta +Comment[fr]=Charge des données depuis un fichier au format « iCal » +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad iCal +Comment[gl]=Carga datos desde un ficheiro iCal +Comment[hu]=Adatbetöltés iCal formátumú naptárból +Comment[ia]=Lege datos de un file iCal +Comment[it]=Carica dati da un file iCal +Comment[ja]=iCal ファイルã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=ICal күнтізбе файлынан деректі алып беру +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ឯកសារ iCal +Comment[ko]=로컬 iCal 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 가져오기 +Comment[lt]=Ä®kelia duomenis iÅ¡ iCal failo +Comment[lv]=IelÄdÄ“ datus no iCal faila +Comment[nb]=Laster data fra en iCal-fil +Comment[nds]=Laadt Daten ut en ICal-Datei +Comment[nl]=Laadt gegevens van een iCal-bestand +Comment[nn]=Lastar data frÃ¥ ei iCal-fil +Comment[pa]=iCal ਫਾਇਲ ਤੋਂ ਡਾਟਾ ਡਾਊਨਲੋਡ ਕਰੋ +Comment[pl]=Wczytuje dane z pliku ICal +Comment[pt]=Carrega os dados de um ficheiro iCal +Comment[pt_BR]=Carrega os dados de um arquivo iCal +Comment[ro]=ÃŽncarcă date dintr-un fiÈ™ier iCal +Comment[ru]=Загрузка данных из файла ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ iCal +Comment[sk]=NaÄíta dáta zo súboru iCal +Comment[sl]=Naloži podatke iz datoteke iCal +Comment[sr]=Учитава податке из и‑календарÑког фајла +Comment[sr@ijekavian]=Учитава податке из и‑календарÑког фајла +Comment[sr@ijekavianlatin]=UÄitava podatke iz i‑kalendarskog fajla +Comment[sr@latin]=UÄitava podatke iz i‑kalendarskog fajla +Comment[sv]=Laddar data frÃ¥n en iCal-fil +Comment[tr]=iCal dosyasından veri yükler +Comment[uk]=Завантажує дані з файла iCal +Comment[x-test]=xxLoads data from an iCal filexx +Comment[zh_CN]=从 iCal æ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從 iCal 檔載入資料 + +[Wizard] +Type=text/calendar +Script=icalwizard.es + +[Translate] +Filename=accountwizard_ical diff --git a/kdepim-runtime/resources/ical/wizard/icalwizard.es.cmake b/kdepim-runtime/resources/ical/wizard/icalwizard.es.cmake new file mode 100644 index 00000000..c2771bb1 --- /dev/null +++ b/kdepim-runtime/resources/ical/wizard/icalwizard.es.cmake @@ -0,0 +1,43 @@ +/* + Copyright (c) 2010 Till Adam + + 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. +*/ + +var page = Dialog.addPage( "icalwizard.ui", qsTr("Settings") ); + +page.widget().lineEdit.text = "${ICAL_FILE_DEFAULT_PATH}"; + +function validateInput() +{ + if ( page.widget().lineEdit.text == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var icalRes = SetupManager.createResource( "akonadi_ical_resource" ); + icalRes.setOption( "Path", page.widget().lineEdit.text ); + icalRes.setName( qsTr("Default Calendar") ); + SetupManager.execute(); +} + +page.widget().lineEdit.textChanged.connect( validateInput ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/ical/wizard/icalwizard.ui b/kdepim-runtime/resources/ical/wizard/icalwizard.ui new file mode 100644 index 00000000..7307df22 --- /dev/null +++ b/kdepim-runtime/resources/ical/wizard/icalwizard.ui @@ -0,0 +1,45 @@ + + + icalWizard + + + + 0 + 0 + 400 + 300 + + + + + + + + + Filename: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + diff --git a/kdepim-runtime/resources/icaldir/CMakeLists.txt b/kdepim-runtime/resources/icaldir/CMakeLists.txt new file mode 100644 index 00000000..3ca6c5e4 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/CMakeLists.txt @@ -0,0 +1,42 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( icaldirresource_SRCS + icaldirresource.cpp + ../shared/dirsettingsdialog.cpp +) + +kde4_add_ui_files(icaldirresource_SRCS settingsdialog.ui) +kde4_add_kcfg_files(icaldirresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/icaldirresource.kcfg org.kde.Akonadi.ICalDirectory.Settings) +qt4_add_dbus_adaptor(icaldirresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.ICalDirectory.Settings.xml settings.h Settings +) + +install( FILES icaldirresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_executable(akonadi_icaldir_resource ${icaldirresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_icaldir_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_icaldir_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.ICalDirectory") + set_target_properties(akonadi_icaldir_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi ICalDirectory Resource") +endif () + + +target_link_libraries(akonadi_icaldir_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDE4_KIO_LIBS} +) + +install(TARGETS akonadi_icaldir_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/icaldir/Messages.sh b/kdepim-runtime/resources/icaldir/Messages.sh new file mode 100644 index 00000000..43295120 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_icaldir_resource.pot diff --git a/kdepim-runtime/resources/icaldir/icaldirresource.cpp b/kdepim-runtime/resources/icaldir/icaldirresource.cpp new file mode 100644 index 00000000..8f369d43 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/icaldirresource.cpp @@ -0,0 +1,306 @@ +/* + Copyright (c) 2008 Tobias Koenig + Copyright (c) 2008 Bertjan Broeksema + Copyright (c) 2012 Sérgio Martins + + 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 "icaldirresource.h" + +#include "settingsadaptor.h" +#include "../shared/dirsettingsdialog.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace Akonadi; +using namespace KCalCore; + +static Incidence::Ptr readFromFile( const QString &fileName, const QString &expectedIdentifier ) +{ + MemoryCalendar::Ptr calendar = MemoryCalendar::Ptr( new MemoryCalendar( QLatin1String( "UTC" ) ) ); + FileStorage::Ptr fileStorage = FileStorage::Ptr( new FileStorage( calendar, fileName, new ICalFormat() ) ); + + Incidence::Ptr incidence; + if ( fileStorage->load() ) { + Incidence::List incidences = calendar->incidences(); + if ( incidences.count() == 1 && incidences.first()->instanceIdentifier() == expectedIdentifier ) + incidence = incidences.first(); + } else { + kError() << "Error loading file " << fileName; + } + + return incidence; +} + +static bool writeToFile( const QString &fileName, Incidence::Ptr &incidence ) +{ + if ( !incidence ) { + kError() << "incidence is 0!"; + return false; + } + + MemoryCalendar::Ptr calendar = MemoryCalendar::Ptr( new MemoryCalendar( QLatin1String( "UTC" ) ) ); + FileStorage::Ptr fileStorage = FileStorage::Ptr( new FileStorage( calendar, fileName, new ICalFormat() ) ); + calendar->addIncidence( incidence ); + Q_ASSERT( calendar->incidences().count() == 1 ); + + const bool success = fileStorage->save(); + if ( !success ) { + kError() << "Failed to save calendar to file " + fileName; + } + + return success; +} + +ICalDirResource::ICalDirResource( const QString &id ) + : ResourceBase( id ) +{ + // setup the resource + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + changeRecorder()->itemFetchScope().fetchFullPayload(); +} + +ICalDirResource::~ICalDirResource() +{ +} + +void ICalDirResource::aboutToQuit() +{ + Settings::self()->writeConfig(); +} + +void ICalDirResource::configure( WId windowId ) +{ + SettingsDialog dlg( windowId ); + dlg.setWindowIcon( KIcon( "text-calendar" ) ); + if ( dlg.exec() ) { + initializeICalDirectory(); + loadIncidences(); + + synchronize(); + + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } +} + +bool ICalDirResource::loadIncidences() +{ + mIncidences.clear(); + + QDirIterator it( iCalDirectoryName() ); + while ( it.hasNext() ) { + it.next(); + if ( it.fileName() != "." && it.fileName() != ".." && it.fileName() != "WARNING_README.txt" ) { + const KCalCore::Incidence::Ptr incidence = readFromFile( it.filePath(), it.fileName() ); + if ( incidence ) { + mIncidences.insert( incidence->instanceIdentifier(), incidence ); + } + } + } + + emit status( Idle ); + return true; +} + +bool ICalDirResource::retrieveItem( const Akonadi::Item &item, const QSet& ) +{ + const QString remoteId = item.remoteId(); + if ( !mIncidences.contains( remoteId ) ) { + emit error( i18n( "Incidence with uid '%1' not found.", remoteId ) ); + return false; + } + + Item newItem( item ); + newItem.setPayload( mIncidences.value( remoteId ) ); + itemRetrieved( newItem ); + + return true; +} + +void ICalDirResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& ) +{ + if ( Settings::self()->readOnly() ) { + emit error( i18n( "Trying to write to a read-only directory: '%1'", iCalDirectoryName() ) ); + cancelTask(); + return; + } + + KCalCore::Incidence::Ptr incidence; + if ( item.hasPayload() ) + incidence = item.payload(); + + if ( incidence ) { + // add it to the cache... + mIncidences.insert( incidence->instanceIdentifier(), incidence ); + + // ... and write it through to the file system + const bool success = writeToFile( iCalDirectoryFileName( incidence->instanceIdentifier() ), incidence ); + + if ( success ) { + // report everything ok + Item newItem( item ); + newItem.setRemoteId( incidence->instanceIdentifier() ); + changeCommitted( newItem ); + } else { + cancelTask(); + } + } else { + changeProcessed(); + } +} + +void ICalDirResource::itemChanged( const Akonadi::Item &item, const QSet& ) +{ + if ( Settings::self()->readOnly() ) { + emit error( i18n( "Trying to write to a read-only directory: '%1'", iCalDirectoryName() ) ); + cancelTask(); + return; + } + + KCalCore::Incidence::Ptr incidence; + if ( item.hasPayload() ) + incidence = item.payload(); + + if ( incidence ) { + // change it in the cache... + mIncidences.insert( incidence->instanceIdentifier(), incidence ); + + // ... and write it through to the file system + const bool success = writeToFile( iCalDirectoryFileName( incidence->instanceIdentifier() ), incidence ); + + if ( success ) { + Item newItem( item ); + newItem.setRemoteId( incidence->instanceIdentifier() ); + changeCommitted( newItem ); + } else { + cancelTask(); + } + } else { + changeProcessed(); + } +} + +void ICalDirResource::itemRemoved( const Akonadi::Item &item ) +{ + if ( Settings::self()->readOnly() ) { + emit error( i18n( "Trying to write to a read-only directory: '%1'", iCalDirectoryName() ) ); + cancelTask(); + return; + } + + // remove it from the cache... + if ( mIncidences.contains( item.remoteId() ) ) + mIncidences.remove( item.remoteId() ); + + // ... and remove it from the file system + QFile::remove( iCalDirectoryFileName( item.remoteId() ) ); + + changeProcessed(); +} + +void ICalDirResource::retrieveCollections() +{ + Collection c; + c.setParentCollection( Collection::root() ); + c.setRemoteId( iCalDirectoryName() ); + c.setName( name() ); + + QStringList mimetypes; + mimetypes << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType() << KCalCore::Journal::journalMimeType() << "text/calendar"; + c.setContentMimeTypes( mimetypes ); + + if ( Settings::self()->readOnly() ) { + c.setRights( Collection::CanChangeCollection ); + } else { + Collection::Rights rights = Collection::ReadOnly; + rights |= Collection::CanChangeItem; + rights |= Collection::CanCreateItem; + rights |= Collection::CanDeleteItem; + rights |= Collection::CanChangeCollection; + c.setRights( rights ); + } + + EntityDisplayAttribute* attr = c.attribute( Collection::AddIfMissing ); + attr->setDisplayName( i18n( "Calendar Folder" ) ); + attr->setIconName( "office-calendar" ); + + Collection::List list; + list << c; + collectionsRetrieved( list ); +} + +void ICalDirResource::retrieveItems( const Akonadi::Collection& ) +{ + loadIncidences(); + Item::List items; + + foreach ( const KCalCore::Incidence::Ptr &incidence, mIncidences ) { + Item item; + item.setRemoteId( incidence->instanceIdentifier() ); + item.setMimeType( incidence->mimeType() ); + items.append( item ); + } + + itemsRetrieved( items ); +} + +QString ICalDirResource::iCalDirectoryName() const +{ + return Settings::self()->path(); +} + +QString ICalDirResource::iCalDirectoryFileName( const QString &file ) const +{ + return Settings::self()->path() + QDir::separator() + file; +} + +void ICalDirResource::initializeICalDirectory() const +{ + QDir dir( iCalDirectoryName() ); + + // if folder does not exists, create it + if ( !dir.exists() ) + QDir::root().mkpath( dir.absolutePath() ); + + // check whether warning file is in place... + QFile file( dir.absolutePath() + QDir::separator() + "WARNING_README.txt" ); + if ( !file.exists() ) { + // ... if not, create it + file.open( QIODevice::WriteOnly ); + file.write( "Important Warning!!!\n\n" + "Don't create or copy files inside this folder manually, they are managed by the Akonadi framework!\n" ); + file.close(); + } +} + +AKONADI_RESOURCE_MAIN( ICalDirResource ) + diff --git a/kdepim-runtime/resources/icaldir/icaldirresource.desktop b/kdepim-runtime/resources/icaldir/icaldirresource.desktop new file mode 100644 index 00000000..71a1f627 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/icaldirresource.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +Name=ICal Calendar Folder +Name[bs]=ICal kalendarska datoteka +Name[ca]=Carpeta de calendari ICal +Name[ca@valencia]=Carpeta de calendari ICal +Name[cs]=Složka iCal kalendáře +Name[da]=iCal-kalendermappe +Name[de]=ICal-Kalenderordner +Name[el]=Φάκελος ημεÏολογίου ICal +Name[en_GB]=ICal Calendar Folder +Name[es]=Carpeta de calendario ICal +Name[et]=ICali kalendrikataloog +Name[fi]=iCal-kalenterikansio +Name[fr]=Dossier d'agendas au format « ICal » +Name[ga]=Fillteán Féilire ICal +Name[gl]=Cartafol de calendario iCal +Name[hu]=ICal naptármappa +Name[ia]=Dossier de calendario iCal +Name[it]=Cartella di calendario iCal +Name[kk]=ICal күнтізбе қапшығы +Name[ko]=ICal 달력 í´ë” +Name[lt]=ICal kalendoriaus aplankas +Name[nb]=ICal kalendermappe +Name[nds]=ICal-Kalennerorner +Name[nl]=ICal-agendamap +Name[pl]=Folder kalendarza ICal +Name[pt]=Pasta de Calendários ICal +Name[pt_BR]=Pasta de calendário ICal +Name[ru]=Каталог ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ iCal +Name[sk]=PrieÄinok kalendára iCal +Name[sl]=Koledarska mapa iCal +Name[sr]=И‑календарÑки фаÑцикла +Name[sr@ijekavian]=И‑календарÑки фаÑцикла +Name[sr@ijekavianlatin]=I‑kalendarski fascikla +Name[sr@latin]=I‑kalendarski fascikla +Name[sv]=ICal-kalenderkatalog +Name[tr]=ICal Takvim Klasörü +Name[uk]=Тека ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ ICal +Name[x-test]=xxICal Calendar Folderxx +Name[zh_CN]=ICal 日历文件夹 +Name[zh_TW]=ICal 行事曆資料夾 +Comment="Provides access to calendar items, each stored in a single file, in a given directory" +Comment[bs]="Puža pristup stavkama kalendara, svaka u pojedinoh datoteci u datom direktoriju" +Comment[ca]=«Proporciona accés als elements d'un calendari, cadascun emmagatzemat en un fitxer individual, en un directori proporcionat» +Comment[ca@valencia]=«Proporciona l'accés als elements d'un calendari, cada un emmagatzemat en un fitxer individual, en un directori proporcionat» +Comment[cs]="Poskytuje přístup k položkám kalendáře, každé uložené v jednom souboru v daném adresáři" +Comment[da]="Giver adgang til en kalenderelementer, hver især gemt i en enkelt fil i en given mappe" +Comment[de]="Ermöglicht Zugriff auf Kalender, die jeweils in einzelnen Dateien in einem vorgegebenen Ordner gespeichert sind" +Comment[el]="ΠαÏέχει Ï€Ïόσβαση σε αντικείμενα ημεÏολογίου, με το καθένα αποθηκευμένο σε ένα αÏχείο, σε δοσμένο κατάλογο" +Comment[en_GB]="Provides access to calendar items, each stored in a single file, in a given directory" +Comment[es]=«Proporciona acceso a elementos de calendario, cada uno almacenado en un solo archivo, en un directorio dado» +Comment[et]="Võimaldab kasutada eraldi failidesse salvestatud kalendrielemente määratud kataloogis" +Comment[fi]="Tarjoaa pääsyn määräkansion yksittäisiin tiedostoihin tallennettuihin kalenterimerkintöihin" +Comment[fr]=« Fournit l'accès aux éléments d'agenda, chacun stocké dans un fichier distinct, dans un dossier donné » +Comment[gl]=«Fornece acceso a elementos de calendario, cada un almacenado nun ficheiro de seu, nun directorio dado.» +Comment[hu]=„Hozzáférést biztosít a naptárelemekhez, mindegyiket külön fájlban tárolva a megadott könyvtárban†+Comment[ia]="Il provide accesso a elementos de calendario, cata un immagazinate in un file singule, in un date directorio" +Comment[it]="Fornisce accesso a voci di calendario, ciascuna memorizzata in un singolo file in una data cartella" +Comment[kk]="КөрÑетілген қапшықта, бөлек файлдарда Ñақталған күнтізбенің жазуларына қатынау мүмкіндігін береді" +Comment[ko]="지정한 ë””ë ‰í„°ë¦¬ì˜ ë‹¨ì¼ ë¡œì»¬ 파ì¼ì— 저장ë˜ì–´ 있는 달력 í•­ëª©ì— ì ‘ê·¼í•˜ê¸°" +Comment[lt]=„Suteikia prieigÄ… prie kalendoriaus įrašų, kurie saugomi individualiai nurodytame kataloge“ +Comment[nb]=«Gir tilgang til kalenderelementer, hver lagret i én enkelt fil, i en gitt mappe» +Comment[nds]=„Stellt Togriep op Kalennerindrääg praat, elkeen binnen een Datei binnen en angeven Orner“ +Comment[nl]="Geeft toegang tot agenda-items, elk opgeslagen in een enkel bestand in een opgegeven map" +Comment[pl]="Daje dostÄ™p do plików obiektów kalendarza, każdy w oddzielnym pliku, w podanym katalogu" +Comment[pt]=Oferece o acesso aos itens do calendário, estando cada um guardado num único ficheiro de uma dada pasta +Comment[pt_BR]="Fornece acesso aos itens do calendário, cada um armazenado em um único arquivo na pasta indicada" +Comment[ru]="ОбеÑпечивает доÑтуп к Ñлементам календарÑ, каждый из которых хранитÑÑ Ð² отдельном файле в указанном каталоге" +Comment[sk]="Poskytuje prístup do položiek kalendára, každá uložená v jednom súbore v danom adresári" +Comment[sr]="Омогућава приÑтуп календарÑким Ñтавкама, Ñкладиштеним у по једном фајлу у датој фаÑцикли" +Comment[sr@ijekavian]="Омогућава приÑтуп календарÑким Ñтавкама, Ñкладиштеним у по једном фајлу у датој фаÑцикли" +Comment[sr@ijekavianlatin]="Omogućava pristup kalendarskim stavkama, skladiÅ¡tenim u po jednom fajlu u datoj fascikli" +Comment[sr@latin]="Omogućava pristup kalendarskim stavkama, skladiÅ¡tenim u po jednom fajlu u datoj fascikli" +Comment[sv]="Ger tillgÃ¥ng till kalenderobjekt, vart och ett lagrat i en enda fil i en angiven katalog" +Comment[tr]="Belirtilen dizinde tek bir yerel dosyada depolanmış bir alarm takvimine eriÅŸim saÄŸlar" +Comment[uk]="Ðадає доÑтуп до запиÑів календарÑ, кожен з Ñких зберігаєтьÑÑ Ñƒ окремому файлі у вказаному каталозі" +Comment[x-test]=xx"Provides access to calendar items, each stored in a single file, in a given directory"xx +Comment[zh_CN]=“æ供日历内容的访问支æŒï¼Œæ¯ä¸ªæ—¥åŽ†å†…容都存储在指定目录的å•ç‹¬æ–‡ä»¶ä¸­â€ +Comment[zh_TW]="æ供存å–行事曆的項目。æ¯å€‹é …目都以單一檔案存放在指定的目錄中" + +Type=AkonadiResource +Exec=akonadi_icaldir_resource +Icon=text-calendar + +X-Akonadi-MimeTypes=application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_icaldir_resource diff --git a/kdepim-runtime/resources/icaldir/icaldirresource.h b/kdepim-runtime/resources/icaldir/icaldirresource.h new file mode 100644 index 00000000..8fe77335 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/icaldirresource.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2008 Tobias Koenig + Copyright (c) 2012 Sérgio Martins + + 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 ICALDIRRESOURCE_H +#define ICALDIRRESOURCE_H + +#include + +#include + +#include + +class ICalDirResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + explicit ICalDirResource( const QString &id ); + ~ICalDirResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + virtual void aboutToQuit(); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + private: + bool loadIncidences(); + QString iCalDirectoryName() const; + QString iCalDirectoryFileName( const QString &file ) const; + void initializeICalDirectory() const; + + private: + QHash mIncidences; +}; + +#endif diff --git a/kdepim-runtime/resources/icaldir/icaldirresource.kcfg b/kdepim-runtime/resources/icaldir/icaldirresource.kcfg new file mode 100644 index 00000000..de6b3d67 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/icaldirresource.kcfg @@ -0,0 +1,22 @@ + + + + + + + + + + + 5 + + + + false + + + diff --git a/kdepim-runtime/resources/icaldir/settings.kcfgc b/kdepim-runtime/resources/icaldir/settings.kcfgc new file mode 100644 index 00000000..4fea847e --- /dev/null +++ b/kdepim-runtime/resources/icaldir/settings.kcfgc @@ -0,0 +1,7 @@ +File=icaldirresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +GlobalEnums=true diff --git a/kdepim-runtime/resources/icaldir/settingsdialog.ui b/kdepim-runtime/resources/icaldir/settingsdialog.ui new file mode 100644 index 00000000..f41ab578 --- /dev/null +++ b/kdepim-runtime/resources/icaldir/settingsdialog.ui @@ -0,0 +1,221 @@ + + SettingsDialog + + + + 0 + 0 + 547 + 386 + + + + + + + 0 + + + + Directory + + + + + + Directory Name + + + + + + + + &Directory: + + + kcfg_Path + + + + + + + + + + + + Select the directory whose contents should be represented by this resource. If the directory does not exist, it will be created. + + + true + + + + + + + + + + Access Rights + + + + + + Read only + + + + + + + If read-only mode is enabled, no changes will be written to the directory selected above. Read-only mode will be automatically enabled if you do not have write access to the directory. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + + Tuning + + + + + + The options on this page allow you to change parameters that balance data safety and consistency against performance. In general you should be careful with changing anything here, the defaults are good enough in most cases. + + + true + + + + + + + + + Autosave delay: + + + + + + + 1 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + + + kcfg_ReadOnly + toggled(bool) + kcfg_AutosaveInterval + setDisabled(bool) + + + 273 + 205 + + + 157 + 101 + + + + + kcfg_ReadOnly + toggled(bool) + autosaveLabel + setDisabled(bool) + + + 273 + 205 + + + 56 + 101 + + + + +
diff --git a/kdepim-runtime/resources/imap/CMakeLists.txt b/kdepim-runtime/resources/imap/CMakeLists.txt new file mode 100644 index 00000000..3949c2e9 --- /dev/null +++ b/kdepim-runtime/resources/imap/CMakeLists.txt @@ -0,0 +1,123 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +option(IMAPRESOURCE_NO_SOLID "Disable the use of solid in the imap resource" FALSE) +if(IMAPRESOURCE_NO_SOLID) + add_definitions( -DIMAPRESOURCE_NO_SOLID=1 ) +endif() + +########### next target ############### + +set( imapresource_LIB_SRCS + addcollectiontask.cpp + additemtask.cpp + batchfetcher.cpp + changecollectiontask.cpp + changeitemsflagstask.cpp + changeitemtask.cpp + expungecollectiontask.cpp + highestmodseqattribute.cpp + imapaccount.cpp + imapflags.cpp + imapresourcebase.cpp + messagehelper.cpp + movecollectiontask.cpp + moveitemstask.cpp + noselectattribute.cpp + noinferiorsattribute.cpp + passwordrequesterinterface.cpp + removecollectionrecursivetask.cpp + resourcestateinterface.cpp + resourcetask.cpp + retrievecollectionmetadatatask.cpp + retrievecollectionstask.cpp + retrieveitemtask.cpp + retrieveitemstask.cpp + searchtask.cpp + sessionpool.cpp + timestampattribute.cpp + uidvalidityattribute.cpp + uidnextattribute.cpp + settings.cpp + subscriptiondialog.cpp + imapidlemanager.cpp + resourcestate.cpp + ${AKONADI_COLLECTIONATTRIBUTES_SHARED_SOURCES} + ${AKONADI_IMAPATTRIBUTES_SHARED_SOURCES} +) + +kcfg_generate_dbus_interface( ${CMAKE_CURRENT_SOURCE_DIR}/imapresource.kcfg org.kde.Akonadi.Imap.Settings ) +kde4_add_kcfg_files(imapresource_LIB_SRCS settingsbase.kcfgc) + +qt4_add_dbus_adaptor( imapresource_LIB_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Imap.Settings.xml settings.h Settings + ) + + + +qt4_generate_dbus_interface( ${CMAKE_CURRENT_SOURCE_DIR}/imapresourcebase.h org.kde.Akonadi.Imap.Resource.xml OPTIONS -a ) +qt4_add_dbus_adaptor( imapresource_LIB_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Imap.Resource.xml + imapresourcebase.h ImapResourceBase + ) + +kde4_add_library(imapresource STATIC ${imapresource_LIB_SRCS}) + +target_link_libraries(imapresource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${KDEPIMLIBS_KIMAP_LIBS} ${KDEPIMLIBS_MAILTRANSPORT_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS}) + +if( NOT IMAPRESOURCE_NO_SOLID) + target_link_libraries(imapresource ${KDE4_SOLID_LIBS}) +endif() +########### next target ############### + +set( akonadi_imap_resource_SRCS + main.cpp + imapresource.cpp + resourcestate.cpp + settingspasswordrequester.cpp + setupserver.cpp + serverinfodialog.cpp +) + +install( FILES imapresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +if (KDEPIM_MOBILE_UI) +kde4_add_ui_files(akonadi_imap_resource_SRCS setupserverview_mobile.ui) +else () +kde4_add_ui_files(akonadi_imap_resource_SRCS setupserverview_desktop.ui) +endif () +kde4_add_ui_files(akonadi_imap_resource_SRCS serverinfo.ui) + + + +if (RUNTIME_PLUGINS_STATIC) + add_definitions(-DMAIL_SERIALIZER_PLUGIN_STATIC) +endif () + +kde4_add_executable(akonadi_imap_resource ${akonadi_imap_resource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_imap_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_imap_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.Imap") + set_target_properties(akonadi_imap_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi IMAP Resource") +endif () + +target_link_libraries(akonadi_imap_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${KDEPIMLIBS_KIMAP_LIBS} ${KDEPIMLIBS_MAILTRANSPORT_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} imapresource folderarchivesettings) + +if (RUNTIME_PLUGINS_STATIC) + target_link_libraries(akonadi_imap_resource akonadi_serializer_mail) +endif () + +if( NOT IMAPRESOURCE_NO_SOLID) +target_link_libraries(akonadi_imap_resource ${KDE4_SOLID_LIBS}) +endif() + +install(TARGETS akonadi_imap_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) + +add_subdirectory( wizard ) +add_subdirectory( tests ) diff --git a/kdepim-runtime/resources/imap/Messages.sh b/kdepim-runtime/resources/imap/Messages.sh new file mode 100755 index 00000000..a1ec35a7 --- /dev/null +++ b/kdepim-runtime/resources/imap/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_imap_resource.pot diff --git a/kdepim-runtime/resources/imap/addcollectiontask.cpp b/kdepim-runtime/resources/imap/addcollectiontask.cpp new file mode 100644 index 00000000..7e8e8e99 --- /dev/null +++ b/kdepim-runtime/resources/imap/addcollectiontask.cpp @@ -0,0 +1,156 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "addcollectiontask.h" + +#include "collectionannotationsattribute.h" + +#include +#include + +#include +#include +#include +#include + +#include + +AddCollectionTask::AddCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( DeferIfNoSession, resource, parent ), m_pendingJobs( 0 ), m_session(0) +{ +} + +AddCollectionTask::~AddCollectionTask() +{ +} + +void AddCollectionTask::doStart( KIMAP::Session *session ) +{ + if ( parentCollection().remoteId().isEmpty() ) { + emitError( i18n( "Cannot add IMAP folder '%1' for a non-existing parent folder '%2'.", + collection().name(), + parentCollection().name() ) ); + changeProcessed(); + return; + } + + const QChar separator = separatorCharacter(); + m_pendingJobs = 0; + m_session = session; + m_collection = collection(); + m_collection.setName( m_collection.name().replace( separator, QString() ) ); + m_collection.setRemoteId( separator + m_collection.name() ); + + QString newMailBox = mailBoxForCollection( parentCollection() ); + + if ( !newMailBox.isEmpty() ) + newMailBox += separator; + + newMailBox += m_collection.name(); + + kDebug( 5327 ) << "New folder: " << newMailBox; + + KIMAP::CreateJob *job = new KIMAP::CreateJob( session ); + job->setMailBox( newMailBox ); + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onCreateDone(KJob*)) ); + + job->start(); +} + + +void AddCollectionTask::onCreateDone( KJob *job ) +{ + if ( job->error() ) { + emitError( i18n( "Failed to create the folder '%1' on the IMAP server. ", + m_collection.name() ) ); + cancelTask( job->errorString() ); + } else { + // Automatically subscribe to newly created mailbox + KIMAP::CreateJob *create = static_cast( job ); + + KIMAP::SubscribeJob *subscribe = new KIMAP::SubscribeJob( create->session() ); + subscribe->setMailBox( create->mailBox() ); + + connect( subscribe, SIGNAL(result(KJob*)), + this, SLOT(onSubscribeDone(KJob*)) ); + + subscribe->start(); + } +} + +void AddCollectionTask::onSubscribeDone( KJob *job ) +{ + if ( job->error() && isSubscriptionEnabled() ) { + emitWarning( i18n( "Failed to subscribe to the folder '%1' on the IMAP server. " + "It will disappear on next sync. Use the subscription dialog to overcome that", + m_collection.name() ) ); + } + + const Akonadi::CollectionAnnotationsAttribute *attribute = m_collection.attribute(); + if ( !attribute || !serverSupportsAnnotations() ) { + // we are finished + changeCommitted( m_collection ); + synchronizeCollectionTree(); + return; + } + + const QMap annotations = attribute->annotations(); + + foreach ( const QByteArray &entry, annotations.keys() ) { //krazy:exclude=foreach + KIMAP::SetMetaDataJob *job = new KIMAP::SetMetaDataJob( m_session ); + if ( serverCapabilities().contains( QLatin1String("METADATA") ) ) { + job->setServerCapability( KIMAP::MetaDataJobBase::Metadata ); + } else { + job->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore ); + } + job->setMailBox( mailBoxForCollection( m_collection ) ); + + if ( !entry.startsWith( "/shared" ) && !entry.startsWith( "/private" ) ) { + //Support for legacy annotations that don't include the prefix + job->addMetaData( QByteArray("/shared") + entry, annotations[entry] ); + } else { + job->addMetaData( entry, annotations[entry] ); + } + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onSetMetaDataDone(KJob*)) ); + + m_pendingJobs++; + + job->start(); + } +} + +void AddCollectionTask::onSetMetaDataDone( KJob *job ) +{ + if ( job->error() ) { + emitWarning( i18n( "Failed to write some annotations for '%1' on the IMAP server. %2", + collection().name(), job->errorText() ) ); + } + + m_pendingJobs--; + + if ( m_pendingJobs == 0 ) + changeCommitted( m_collection ); +} + diff --git a/kdepim-runtime/resources/imap/addcollectiontask.h b/kdepim-runtime/resources/imap/addcollectiontask.h new file mode 100644 index 00000000..de836830 --- /dev/null +++ b/kdepim-runtime/resources/imap/addcollectiontask.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 ADDCOLLECTIONTASK_H +#define ADDCOLLECTIONTASK_H + +#include "resourcetask.h" + +namespace KIMAP { +class Session; +} + +class AddCollectionTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit AddCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~AddCollectionTask(); + +private slots: + void onCreateDone( KJob *job ); + void onSubscribeDone( KJob *job ); + void onSetMetaDataDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + Akonadi::Collection m_collection; + uint m_pendingJobs; + KIMAP::Session *m_session; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/additemtask.cpp b/kdepim-runtime/resources/imap/additemtask.cpp new file mode 100644 index 00000000..8be675d6 --- /dev/null +++ b/kdepim-runtime/resources/imap/additemtask.cpp @@ -0,0 +1,214 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "additemtask.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "uidnextattribute.h" + +AddItemTask::AddItemTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( DeferIfNoSession, resource, parent ) +{ + +} + +AddItemTask::~AddItemTask() +{ +} + +void AddItemTask::doStart( KIMAP::Session *session ) +{ + if ( !item().hasPayload() ) { + changeProcessed(); + return; + } + + const QString mailBox = mailBoxForCollection( collection() ); + if ( mailBox.isEmpty() ) { + kWarning() << "Trying to append message to invalid mailbox, this will fail. Id: " << parentCollection().id(); + } + + kDebug( 5327 ) << "Got notification about item added for local id " << item().id() << " and remote id " << item().remoteId(); + + // save message to the server. + KMime::Message::Ptr msg = item().payload(); + m_messageId = msg->messageID()->asUnicodeString().toUtf8(); + + KIMAP::AppendJob *job = new KIMAP::AppendJob( session ); + job->setMailBox( mailBox ); + job->setContent( msg->encodedContent( true ) ); + job->setFlags( fromAkonadiToSupportedImapFlags( item().flags().toList(), collection() ) ); + job->setInternalDate( msg->date()->dateTime() ); + connect( job, SIGNAL(result(KJob*)), SLOT(onAppendMessageDone(KJob*)) ); + job->start(); +} + + +void AddItemTask::onAppendMessageDone( KJob *job ) +{ + KIMAP::AppendJob *append = qobject_cast( job ); + + if ( append->error() ) { + kWarning() << append->errorString(); + cancelTask( append->errorString() ); + return; + } + + qint64 uid = append->uid(); + + if ( uid > 0 ) { + // We got it directly if UIDPLUS is supported... + applyFoundUid( uid ); + + } else { + // ... otherwise prepare searching for the message + KIMAP::Session *session = append->session(); + const QString mailBox = append->mailBox(); + + if ( session->selectedMailBox() != mailBox ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( session ); + select->setMailBox( mailBox ); + + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onPreSearchSelectDone(KJob*)) ); + + select->start(); + + } else { + triggerSearchJob( session ); + } + } +} + +void AddItemTask::onPreSearchSelectDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << job->errorString(); + cancelTask( job->errorString() ); + } else { + KIMAP::SelectJob *select = static_cast( job ); + triggerSearchJob( select->session() ); + } +} + +void AddItemTask::triggerSearchJob( KIMAP::Session *session ) +{ + KIMAP::SearchJob *search = new KIMAP::SearchJob( session ); + + search->setUidBased( true ); + search->setSearchLogic( KIMAP::SearchJob::And ); + + if ( !m_messageId.isEmpty() ) { + QByteArray header = "Message-ID "; + header+= m_messageId; + + search->addSearchCriteria( KIMAP::SearchJob::Header, header ); + } else { + search->addSearchCriteria( KIMAP::SearchJob::New ); + + UidNextAttribute *uidNext = collection().attribute(); + if ( !uidNext ) { + cancelTask( i18n( "Could not determine the UID for the newly created message on the server" ) ); + search->deleteLater(); + return; + } + KIMAP::ImapInterval interval( uidNext->uidNext() ); + + search->addSearchCriteria( KIMAP::SearchJob::Uid, interval.toImapSequence() ); + } + + connect( search, SIGNAL(result(KJob*)), + this, SLOT(onSearchDone(KJob*)) ); + + search->start(); +} + +void AddItemTask::onSearchDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << job->errorString(); + cancelTask( job->errorString() ); + return; + } + + KIMAP::SearchJob *search = static_cast( job ); + + qint64 uid = 0; + if ( search->results().count() == 1 ) + uid = search->results().first(); + + applyFoundUid( uid ); +} + +void AddItemTask::applyFoundUid( qint64 uid ) +{ + Akonadi::Item i = item(); + + // if we didn't manage to get a valid UID from the server, use a random RID instead + // this will make ItemSync clean up the mess during the next sync (while empty RIDs are protected as not yet existing on the server) + if ( uid > 0 ) + i.setRemoteId( QString::number( uid ) ); + else + i.setRemoteId( QUuid::createUuid() ); + kDebug( 5327 ) << "Setting remote ID to " << i.remoteId() << " for item with local id " << i.id(); + + changeCommitted( i ); + + Akonadi::Collection c = collection(); + + // Get the current uid next value and store it + UidNextAttribute *uidAttr = 0; + int oldNextUid = 0; + if ( c.hasAttribute( "uidnext" ) ) { + uidAttr = static_cast( c.attribute( "uidnext" ) ); + oldNextUid = uidAttr->uidNext(); + } + + // If the uid we just got back is the expected next one of the box + // then update the property to the probable next uid to keep the cache in sync. + // If not something happened in our back, so we don't update and a refetch will + // happen at some point. + if ( uid==oldNextUid ) { + if ( uidAttr==0 ) { + uidAttr = new UidNextAttribute( uid+1 ); + c.addAttribute( uidAttr ); + } else { + uidAttr->setUidNext( uid+1 ); + } + + applyCollectionChanges( c ); + } +} + + + diff --git a/kdepim-runtime/resources/imap/additemtask.h b/kdepim-runtime/resources/imap/additemtask.h new file mode 100644 index 00000000..e28ad7de --- /dev/null +++ b/kdepim-runtime/resources/imap/additemtask.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 ADDITEMTASK_H +#define ADDITEMTASK_H + +#include "resourcetask.h" + +class AddItemTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit AddItemTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~AddItemTask(); + +private slots: + void onAppendMessageDone( KJob *job ); + void onPreSearchSelectDone( KJob *job ); + void onSearchDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void triggerSearchJob( KIMAP::Session *session ); + void applyFoundUid( qint64 uid ); + + QByteArray m_messageId; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/batchfetcher.cpp b/kdepim-runtime/resources/imap/batchfetcher.cpp new file mode 100644 index 00000000..64004be8 --- /dev/null +++ b/kdepim-runtime/resources/imap/batchfetcher.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2014 Christian Mollekopf + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "batchfetcher.h" + +#include + +BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, + const KIMAP::ImapSet &set, + const KIMAP::FetchJob::FetchScope &scope, + int batchSize, + KIMAP::Session *session) + : KJob(session), + m_currentSet(set), + m_scope(scope), + m_session(session), + m_batchSize(batchSize), + m_uidBased(false), + m_fetchedItemsInCurrentBatch(0), + m_messageHelper(messageHelper), + m_fetchInProgress(false), + m_continuationRequested(false), + m_gmailEnabled(false), + m_searchInChunks(false) +{ +} + +BatchFetcher::~BatchFetcher() +{ +} + +void BatchFetcher::setUidBased(bool uidBased) +{ + m_uidBased = uidBased; +} + +void BatchFetcher::setSearchUids(const KIMAP::ImapInterval &intervall) +{ + m_searchUidInterval = intervall; + + //We look up the UIDs ourselves + m_currentSet = KIMAP::ImapSet(); + + //MS Exchange can't handle big results so we have to split the search into small chunks + m_searchInChunks = m_session->serverGreeting().contains("Microsoft Exchange"); +} + +void BatchFetcher::setGmailExtensionsEnabled(bool enable) +{ + m_gmailEnabled = enable; +} + +static const int maxAmountOfUidToSearchInOneTime = 2000; + +void BatchFetcher::start() +{ + if (m_searchUidInterval.size()) { + //Search in chunks also Exchange can handle + const KIMAP::ImapInterval::Id firstUidToSearch = m_searchUidInterval.begin(); + const KIMAP::ImapInterval::Id lastUidToSearch = m_searchInChunks + ? qMin(firstUidToSearch + maxAmountOfUidToSearchInOneTime - 1, m_searchUidInterval.end()) + : m_searchUidInterval.end(); + + //Prepare next chunk + const KIMAP::ImapInterval::Id intervalBegin = lastUidToSearch + 1; + //Or are we already done? + if (intervalBegin > m_searchUidInterval.end()) { + m_searchUidInterval = KIMAP::ImapInterval(); + } else { + m_searchUidInterval.setBegin(intervalBegin); + } + + //Resolve the uid to sequence numbers + KIMAP::SearchJob *search = new KIMAP::SearchJob(m_session); + search->setUidBased(true); + search->setTerm(KIMAP::Term(KIMAP::Term::Uid, KIMAP::ImapSet(firstUidToSearch, lastUidToSearch))); + connect(search, SIGNAL(result(KJob*)), this, SLOT(onUidSearchDone(KJob*))); + search->start(); + } else { + fetchNextBatch(); + } +} + +void BatchFetcher::onUidSearchDone(KJob* job) +{ + if (job->error()) { + kWarning() << "Search job failed: " << job->errorString(); + setError(KJob::UserDefinedError); + emitResult(); + return; + } + + KIMAP::SearchJob *search = static_cast(job); + m_uidBased = search->isUidBased(); + m_currentSet.add(search->results()); + + //More to search? + start(); +} + +void BatchFetcher::fetchNextBatch() +{ + if (m_fetchInProgress) { + m_continuationRequested = true; + return; + } + m_continuationRequested = false; + Q_ASSERT(m_batchSize > 0); + if (m_currentSet.isEmpty()) { + kDebug(5327) << "fetch complete"; + emitResult(); + return; + } + + KIMAP::FetchJob *fetch = new KIMAP::FetchJob(m_session); + if (m_scope.changedSince != 0) { + kDebug(5327) << "Fetching all messages in one batch."; + fetch->setSequenceSet(m_currentSet); + m_currentSet = KIMAP::ImapSet(); + } else { + KIMAP::ImapSet toFetch; + qint64 counter = 0; + KIMAP::ImapSet newSet; + + //Take a chunk from the set + Q_FOREACH (const KIMAP::ImapInterval &interval, m_currentSet.intervals()) { + if (!interval.hasDefinedEnd()) { + //If we get an interval without a defined end we simply fetch everything + kDebug(5327) << "Received interval without defined end, fetching everything in one batch"; + toFetch.add(interval); + newSet = KIMAP::ImapSet(); + break; + } + const qint64 wantedItems = m_batchSize - counter; + if (counter < m_batchSize) { + if (interval.size() <= wantedItems) { + counter += interval.size(); + toFetch.add(interval); + } else { + counter += wantedItems; + toFetch.add(KIMAP::ImapInterval(interval.begin(), interval.begin() + wantedItems - 1)); + newSet.add(KIMAP::ImapInterval(interval.begin() + wantedItems, interval.end())); + } + } else { + newSet.add(interval); + } + } + kDebug(5327) << "Fetching " << toFetch.intervals().size() << " intervals"; + fetch->setSequenceSet(toFetch); + m_currentSet = newSet; + } + + fetch->setUidBased(m_uidBased); + fetch->setScope(m_scope); + fetch->setGmailExtensionsEnabled(m_gmailEnabled); + connect(fetch, SIGNAL(headersReceived(QString, + QMap, + QMap, + QMap, + QMap, + QMap)), + this, SLOT(onHeadersReceived(QString, + QMap, + QMap, + QMap, + QMap, + QMap)) ); + connect(fetch, SIGNAL(result(KJob*)), + this, SLOT(onHeadersFetchDone(KJob*))); + m_fetchInProgress = true; + fetch->start(); +} + +void BatchFetcher::onHeadersReceived(const QString &mailBox, + const QMap &uids, + const QMap &sizes, + const QMap &attrs, + const QMap &flags, + const QMap &messages) +{ + KIMAP::FetchJob *fetch = static_cast( sender() ); + Q_ASSERT( fetch ); + + Akonadi::Item::List addedItems; + foreach (qint64 number, uids.keys()) { //krazy:exclude=foreach + //kDebug( 5327 ) << "Flags: " << i.flags(); + bool ok; + const Akonadi::Item item = m_messageHelper->createItemFromMessage(messages[number], uids[number], sizes[number], attrs.values(number), flags[number], fetch->scope(), ok); + if (ok) { + m_fetchedItemsInCurrentBatch++; + addedItems << item; + } + } +// kDebug() << addedItems.size(); + if (!addedItems.isEmpty()) { + emit itemsRetrieved(addedItems); + } +} + +void BatchFetcher::onHeadersFetchDone( KJob *job ) +{ + m_fetchInProgress = false; + if (job->error()) { + kWarning() << "Fetch job failed " << job->errorString(); + setError(KJob::UserDefinedError); + emitResult(); + return; + } + if (m_currentSet.isEmpty()) { + emitResult(); + return; + } + //Fetch more if we didn't deliver enough yet. + //This can happen because no message is in the fetched uid range, or if the translation failed + if (m_fetchedItemsInCurrentBatch < m_batchSize) { + fetchNextBatch(); + } else { + m_fetchedItemsInCurrentBatch = 0; + //Also fetch more if we already got a continuation request during the fetch. + //This can happen if we deliver too many items during a previous batch (after using ) + //Note that m_fetchedItemsInCurrentBatch will be off by the items that we delivered already. + if (m_continuationRequested) { + fetchNextBatch(); + } + } +} + diff --git a/kdepim-runtime/resources/imap/batchfetcher.h b/kdepim-runtime/resources/imap/batchfetcher.h new file mode 100644 index 00000000..68f45d70 --- /dev/null +++ b/kdepim-runtime/resources/imap/batchfetcher.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014 Christian Mollekopf + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BATCHFETCHER_H +#define BATCHFETCHER_H + +#include +#include +#include +#include + +#include "messagehelper.h" + +/** + * A job that retrieves a set of messages in reverse-ordered batches. + * After each batch fetchNextBatch() needs to be called (for throttling the download speed) + */ +class BatchFetcher : public KJob { + Q_OBJECT +public: + BatchFetcher(MessageHelper::Ptr messageHelper, + const KIMAP::ImapSet &set, + const KIMAP::FetchJob::FetchScope &scope, + int batchSize, + KIMAP::Session *session); + virtual ~BatchFetcher(); + virtual void start(); + void fetchNextBatch(); + void setUidBased(bool); + void setSearchUids(const KIMAP::ImapInterval &); + void setGmailExtensionsEnabled(bool enable); + +Q_SIGNALS: + void itemsRetrieved(Akonadi::Item::List); + +private Q_SLOTS: + void onHeadersReceived(const QString &mailBox, + const QMap &uids, + const QMap &sizes, + const QMap &attrs, + const QMap &flags, + const QMap &messages); + void onHeadersFetchDone(KJob *job); + void onUidSearchDone(KJob* job); + +private: + //Batch fetching + KIMAP::ImapSet m_currentSet; + KIMAP::FetchJob::FetchScope m_scope; + KIMAP::Session *m_session; + int m_batchSize; + bool m_uidBased; + int m_fetchedItemsInCurrentBatch; + const MessageHelper::Ptr m_messageHelper; + bool m_fetchInProgress; + bool m_continuationRequested; + KIMAP::ImapInterval m_searchUidInterval; + bool m_gmailEnabled; + bool m_searchInChunks; +}; + + +#endif // BATCHFETCHER_H diff --git a/kdepim-runtime/resources/imap/changecollectiontask.cpp b/kdepim-runtime/resources/imap/changecollectiontask.cpp new file mode 100644 index 00000000..43720200 --- /dev/null +++ b/kdepim-runtime/resources/imap/changecollectiontask.cpp @@ -0,0 +1,294 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "changecollectiontask.h" + +#include +#include +#include +#include +#include + +#include "collectionannotationsattribute.h" +#include "imapaclattribute.h" +#include "imapquotaattribute.h" + +#include + +ChangeCollectionTask::ChangeCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( DeferIfNoSession, resource, parent ), m_pendingJobs(0) +{ +} + +ChangeCollectionTask::~ChangeCollectionTask() +{ +} + +void ChangeCollectionTask::doStart( KIMAP::Session *session ) +{ + if ( collection().remoteId().isEmpty() ) { + emitError( i18n( "Cannot modify IMAP folder '%1', it does not exist on the server.", + collection().name() ) ); + changeProcessed(); + return; + } + + m_collection = collection(); + m_pendingJobs = 0; + + if ( parts().contains( "AccessRights" ) ) { + Akonadi::ImapAclAttribute *aclAttribute = collection().attribute(); + + if ( aclAttribute==0 ) { + emitWarning( i18n( "ACLs for '%1' need to be retrieved from the IMAP server first. Skipping ACL change", + collection().name() ) ); + } else { + KIMAP::Acl::Rights imapRights = aclAttribute->rights()[userName().toUtf8()]; + Akonadi::Collection::Rights newRights = collection().rights(); + + if ( newRights & Akonadi::Collection::CanChangeItem ) { + imapRights|= KIMAP::Acl::Write; + } else { + imapRights&= ~KIMAP::Acl::Write; + } + + if ( newRights & Akonadi::Collection::CanCreateItem ) { + imapRights|= KIMAP::Acl::Insert; + } else { + imapRights&= ~KIMAP::Acl::Insert; + } + + if ( newRights & Akonadi::Collection::CanDeleteItem ) { + imapRights|= KIMAP::Acl::DeleteMessage; + } else { + imapRights&= ~KIMAP::Acl::DeleteMessage; + } + + if ( newRights & ( Akonadi::Collection::CanChangeCollection | Akonadi::Collection::CanCreateCollection ) ) { + imapRights|= KIMAP::Acl::CreateMailbox; + imapRights|= KIMAP::Acl::Create; + } else { + imapRights&= ~KIMAP::Acl::CreateMailbox; + imapRights&= ~KIMAP::Acl::Create; + } + + if ( newRights & Akonadi::Collection::CanDeleteCollection ) { + imapRights|= KIMAP::Acl::DeleteMailbox; + } else { + imapRights&= ~KIMAP::Acl::DeleteMailbox; + } + + if ( ( newRights & Akonadi::Collection::CanDeleteItem ) + && ( newRights & Akonadi::Collection::CanDeleteCollection ) ) { + imapRights|= KIMAP::Acl::Delete; + } else { + imapRights&= ~KIMAP::Acl::Delete; + } + + kDebug( 5327 ) << "imapRights:" << imapRights + << "newRights:" << newRights; + + KIMAP::SetAclJob *job = new KIMAP::SetAclJob( session ); + job->setMailBox( mailBoxForCollection( collection() ) ); + job->setRights( KIMAP::SetAclJob::Change, imapRights ); + job->setIdentifier( userName().toUtf8() ); + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onSetAclDone(KJob*)) ); + + job->start(); + + m_pendingJobs++; + } + } + + if ( parts().contains( "collectionannotations" ) && serverSupportsAnnotations() ) { + Akonadi::CollectionAnnotationsAttribute *annotationsAttribute = + collection().attribute(); + + if ( annotationsAttribute ) { // No annotations it seems... server is lieing to us? + QMap annotations = annotationsAttribute->annotations(); + kDebug( 5327 ) << "All annotations: " << annotations; + + foreach ( const QByteArray &entry, annotations.keys() ) { //krazy:exclude=foreach + KIMAP::SetMetaDataJob *job = new KIMAP::SetMetaDataJob( session ); + if ( serverCapabilities().contains( QLatin1String("METADATA") ) ) { + job->setServerCapability( KIMAP::MetaDataJobBase::Metadata ); + } else { + job->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore ); + } + job->setMailBox( mailBoxForCollection( collection() ) ); + + if ( !entry.startsWith( "/shared" ) && !entry.startsWith( "/private" ) ) { + //Support for legacy annotations that don't include the prefix + job->addMetaData( QByteArray("/shared") + entry, annotations[entry] ); + } else { + job->addMetaData( entry, annotations[entry] ); + } + + kDebug( 5327 ) << "Job got entry:" << entry << "value:" << annotations[entry]; + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onSetMetaDataDone(KJob*)) ); + + job->start(); + + m_pendingJobs++; + } + } + } + + if ( parts().contains( "imapacl" ) ) { + Akonadi::ImapAclAttribute *aclAttribute = collection().attribute(); + + if ( aclAttribute ) { + const QMap rights = aclAttribute->rights(); + const QMap oldRights = aclAttribute->oldRights(); + const QList oldIds = oldRights.keys(); + const QList ids = rights.keys(); + + // remove all ACL entries that have been deleted + foreach ( const QByteArray &oldId, oldIds ) { + if ( !ids.contains( oldId ) ) { + KIMAP::SetAclJob *job = new KIMAP::SetAclJob( session ); + job->setMailBox( mailBoxForCollection( collection() ) ); + job->setIdentifier( oldId ); + job->setRights( KIMAP::SetAclJob::Remove, oldRights[oldId] ); + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onSetAclDone(KJob*)) ); + + job->start(); + + m_pendingJobs++; + } + } + + foreach ( const QByteArray &id, ids ) { + KIMAP::SetAclJob *job = new KIMAP::SetAclJob( session ); + job->setMailBox( mailBoxForCollection( collection() ) ); + job->setIdentifier( id ); + job->setRights( KIMAP::SetAclJob::Change, rights[id] ); + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onSetAclDone(KJob*)) ); + + job->start(); + + m_pendingJobs++; + } + } + } + + // Check if we need to rename the mailbox + // This one goes last on purpose, we don't want the previous jobs + // we triggered to act on the wrong mailbox name + if ( parts().contains( "NAME" ) ) { + const QChar separator = separatorCharacter(); + m_collection.setName( m_collection.name().replace( separator, QString() ) ); + m_collection.setRemoteId( separator + m_collection.name() ); + + const QString oldMailBox = mailBoxForCollection( collection() ); + const QString newMailBox = mailBoxForCollection( m_collection ); + + if ( oldMailBox != newMailBox ) { + KIMAP::RenameJob *renameJob = new KIMAP::RenameJob( session ); + renameJob->setSourceMailBox( oldMailBox ); + renameJob->setDestinationMailBox( newMailBox ); + connect( renameJob, SIGNAL(result(KJob*)), + this, SLOT(onRenameDone(KJob*)) ); + + renameJob->start(); + + m_pendingJobs++; + } + } + + // we scheduled no change on the server side, probably we got only + // unsupported part, so just declare the task done + if ( m_pendingJobs == 0 ) { + changeCommitted( collection() ); + } +} + +void ChangeCollectionTask::onRenameDone( KJob *job ) +{ + if ( job->error() ) { + const QString prevRid = collection().remoteId(); + Q_ASSERT( !prevRid.isEmpty() ); + + emitWarning( i18n( "Failed to rename the folder, restoring folder list." ) ); + + m_collection.setName( prevRid.mid( 1 ) ); + m_collection.setRemoteId( prevRid ); + + endTaskIfNeeded(); + } else { + KIMAP::RenameJob *renameJob = static_cast( job ); + KIMAP::SubscribeJob *subscribeJob = new KIMAP::SubscribeJob( renameJob->session() ); + subscribeJob->setMailBox( renameJob->destinationMailBox() ); + connect( subscribeJob, SIGNAL(result(KJob*)), + this, SLOT(onSubscribeDone(KJob*)) ); + subscribeJob->start(); + } +} + +void ChangeCollectionTask::onSubscribeDone( KJob *job ) +{ + if ( job->error() && isSubscriptionEnabled() ) { + emitWarning( i18n( "Failed to subscribe to the renamed folder '%1' on the IMAP server. " + "It will disappear on next sync. Use the subscription dialog to overcome that", + m_collection.name() ) ); + } + + endTaskIfNeeded(); +} + +void ChangeCollectionTask::onSetAclDone( KJob *job ) +{ + if ( job->error() ) { + emitWarning( i18n( "Failed to write some ACLs for '%1' on the IMAP server. %2", + collection().name(), job->errorText() ) ); + } + + endTaskIfNeeded(); +} + +void ChangeCollectionTask::onSetMetaDataDone( KJob *job ) +{ + if ( job->error() ) { + emitWarning( i18n( "Failed to write some annotations for '%1' on the IMAP server. %2", + collection().name(), job->errorText() ) ); + } + + endTaskIfNeeded(); +} + +void ChangeCollectionTask::endTaskIfNeeded() +{ + if ( --m_pendingJobs == 0 ) { + // the others have ended, we're done, the next one can go + changeCommitted( m_collection ); + } +} + + + diff --git a/kdepim-runtime/resources/imap/changecollectiontask.h b/kdepim-runtime/resources/imap/changecollectiontask.h new file mode 100644 index 00000000..923590ba --- /dev/null +++ b/kdepim-runtime/resources/imap/changecollectiontask.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 CHANGECOLLECTIONTASK_H +#define CHANGECOLLECTIONTASK_H + +#include "resourcetask.h" + +class ChangeCollectionTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit ChangeCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~ChangeCollectionTask(); + +private slots: + void onRenameDone( KJob *job ); + void onSubscribeDone( KJob *job ); + void onSetAclDone( KJob *job ); + void onSetMetaDataDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void endTaskIfNeeded(); + + int m_pendingJobs; + Akonadi::Collection m_collection; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/changeitemsflagstask.cpp b/kdepim-runtime/resources/imap/changeitemsflagstask.cpp new file mode 100644 index 00000000..461ea37d --- /dev/null +++ b/kdepim-runtime/resources/imap/changeitemsflagstask.cpp @@ -0,0 +1,153 @@ +/* + Copyright (c) 2013 Daniel Vrátil + + 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 "changeitemsflagstask.h" + +#include +#include +#include + +ChangeItemsFlagsTask::ChangeItemsFlagsTask( ResourceStateInterface::Ptr resource, QObject* parent ): + ResourceTask( ResourceTask::DeferIfNoSession, resource, parent ), + m_processedItems(0) +{ + +} + +ChangeItemsFlagsTask::~ChangeItemsFlagsTask() +{ +} + +void ChangeItemsFlagsTask::doStart(KIMAP::Session* session) +{ + const QString mailBox = mailBoxForCollection( items().first().parentCollection() ); + kDebug(5327) << mailBox; + + if ( session->selectedMailBox() != mailBox ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( session ); + select->setMailBox( mailBox ); + + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onSelectDone(KJob*)) ); + + select->start(); + + } else { + if ( !addedFlags().isEmpty() ) { + triggerAppendFlagsJob( session ); + } else if ( !removedFlags().isEmpty() ) { + triggerRemoveFlagsJob( session ); + } else { + kDebug(5327) << "nothing to do"; + changeProcessed(); + } + } +} + +void ChangeItemsFlagsTask::onSelectDone(KJob* job) +{ + if ( job->error() ) { + kWarning() << "Select failed: " << job->errorString(); + cancelTask( job->errorString() ); + } else { + KIMAP::SelectJob *select = static_cast( job ); + kDebug(5327) << addedFlags(); + if ( !addedFlags().isEmpty() ) { + triggerAppendFlagsJob( select->session() ); + } else if ( !removedFlags().isEmpty() ) { + triggerRemoveFlagsJob( select->session() ); + } else { + kDebug(5327) << "nothing to do"; + changeProcessed(); + } + } +} + +KIMAP::StoreJob* ChangeItemsFlagsTask::prepareJob( KIMAP::Session *session ) +{ + KIMAP::ImapSet set; + const Akonadi::Item::List &allItems = items(); + // Split the request to multiple smaller requests of 2000 UIDs each - various IMAP + // servers have various limits on maximum size of a request + // 2000 is a random number that sounds like a good compromise between performance + // and functionality (i.e. 2000 UIDs should be supported by any server out there) + for (int i = 0, count = qMin(2000, allItems.count() - m_processedItems); i < count; ++i) { + set.add( allItems[m_processedItems].remoteId().toLong() ); + ++m_processedItems; + } + + KIMAP::StoreJob *store = new KIMAP::StoreJob( session ); + store->setUidBased( true ); + store->setSequenceSet( set ); + + return store; +} + +void ChangeItemsFlagsTask::triggerAppendFlagsJob(KIMAP::Session* session) +{ + KIMAP::StoreJob *store = prepareJob( session ); + store->setFlags( fromAkonadiToSupportedImapFlags( addedFlags().toList(), items().first().parentCollection() ) ); + store->setMode( KIMAP::StoreJob::AppendFlags ); + connect( store, SIGNAL(result(KJob*)), SLOT(onAppendFlagsDone(KJob*)) ); + store->start(); +} + +void ChangeItemsFlagsTask::triggerRemoveFlagsJob(KIMAP::Session* session) +{ + KIMAP::StoreJob *store = prepareJob( session ); + store->setFlags( fromAkonadiToSupportedImapFlags( removedFlags().toList(), items().first().parentCollection() ) ); + store->setMode( KIMAP::StoreJob::RemoveFlags ); + connect( store, SIGNAL(result(KJob*)), SLOT(onRemoveFlagsDone(KJob*)) ); + store->start(); +} + +void ChangeItemsFlagsTask::onAppendFlagsDone(KJob* job) +{ + if ( job->error() ) { + kWarning() << "Flag append failed: " << job->errorString(); + cancelTask( job->errorString() ); + } else { + KIMAP::Session *session = qobject_cast(job)->session(); + if ( m_processedItems < items().count() ) { + triggerAppendFlagsJob( session ); + } else if ( removedFlags().isEmpty() ) { + changeProcessed(); + } else { + kDebug(5327) << removedFlags(); + m_processedItems = 0; + triggerRemoveFlagsJob( session ); + } + } +} + +void ChangeItemsFlagsTask::onRemoveFlagsDone(KJob* job) +{ + if ( job->error() ) { + kWarning() << "Flag remove failed: " << job->errorString(); + cancelTask( job->errorString() ); + } else { + if ( m_processedItems < items().count() ) { + triggerRemoveFlagsJob( qobject_cast(job)->session() ); + } else { + changeProcessed(); + } + } +} + diff --git a/kdepim-runtime/resources/imap/changeitemsflagstask.h b/kdepim-runtime/resources/imap/changeitemsflagstask.h new file mode 100644 index 00000000..19261a96 --- /dev/null +++ b/kdepim-runtime/resources/imap/changeitemsflagstask.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2013 Daniel Vrátil + + 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 CHANGEITEMSFLAGSTASK_H +#define CHANGEITEMSFLAGSTASK_H + +#include "resourcetask.h" + +namespace KIMAP { +class StoreJob; +} + +class ChangeItemsFlagsTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit ChangeItemsFlagsTask( ResourceStateInterface::Ptr resource, QObject* parent = 0 ); + virtual ~ChangeItemsFlagsTask(); + +protected Q_SLOTS: + void onSelectDone( KJob *job ); + void onAppendFlagsDone( KJob *job ); + void onRemoveFlagsDone( KJob *job ); + +protected: + KIMAP::StoreJob* prepareJob( KIMAP::Session *session ); + + virtual void doStart( KIMAP::Session* session ); + + virtual void triggerAppendFlagsJob( KIMAP::Session *session ); + virtual void triggerRemoveFlagsJob( KIMAP::Session *session ); + +protected: + int m_processedItems; + +}; + +#endif diff --git a/kdepim-runtime/resources/imap/changeitemtask.cpp b/kdepim-runtime/resources/imap/changeitemtask.cpp new file mode 100644 index 00000000..2c103d4d --- /dev/null +++ b/kdepim-runtime/resources/imap/changeitemtask.cpp @@ -0,0 +1,303 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "changeitemtask.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "imapflags.h" +#include "uidnextattribute.h" + +ChangeItemTask::ChangeItemTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( DeferIfNoSession, resource, parent ), m_session( 0 ), m_oldUid( 0 ), m_newUid( 0 ) +{ + +} + +ChangeItemTask::~ChangeItemTask() +{ +} + +void ChangeItemTask::doStart( KIMAP::Session *session ) +{ + m_session = session; + + const QString mailBox = mailBoxForCollection( item().parentCollection() ); + m_oldUid = item().remoteId().toLongLong(); + kDebug(5327) << mailBox << m_oldUid << parts(); + + if ( parts().contains( "PLD:RFC822" ) ) { + if ( !item().hasPayload() ) { + kWarning() << "Payload changed, no payload available."; + changeProcessed(); + return; + } + + // save message to the server. + KMime::Message::Ptr msg = item().payload(); + m_messageId = msg->messageID()->asUnicodeString().toUtf8(); + + KIMAP::AppendJob *job = new KIMAP::AppendJob( session ); + + job->setMailBox( mailBox ); + job->setContent( msg->encodedContent( true ) ); + const QList flags = fromAkonadiToSupportedImapFlags( item().flags().toList(), item().parentCollection() ); + job->setFlags( flags ); + kDebug(5327) << "Appending new message: " << flags; + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onAppendMessageDone(KJob*)) ); + + job->start(); + + } else if ( parts().contains( "FLAGS" ) ) { + + if ( session->selectedMailBox() != mailBox ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( session ); + select->setMailBox( mailBox ); + + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onPreStoreSelectDone(KJob*)) ); + + select->start(); + + } else { + triggerStoreJob(); + } + + } else { + kDebug(5327) << "Nothing to do"; + changeProcessed(); + } +} + +void ChangeItemTask::onPreStoreSelectDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Select failed: " << job->errorString(); + cancelTask( job->errorString() ); + } else { + triggerStoreJob(); + } +} + +void ChangeItemTask::triggerStoreJob() +{ + QList flags = fromAkonadiToSupportedImapFlags( item().flags().toList(), item().parentCollection() ); + kDebug(5327) << flags; + + KIMAP::StoreJob *store = new KIMAP::StoreJob( m_session ); + + store->setUidBased( true ); + store->setSequenceSet( KIMAP::ImapSet( m_oldUid ) ); + store->setFlags( flags ); + store->setMode( KIMAP::StoreJob::SetFlags ); + + connect( store, SIGNAL(result(KJob*)), + this, SLOT(onStoreFlagsDone(KJob*)) ); + + store->start(); +} + +void ChangeItemTask::onStoreFlagsDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Flag store failed: " << job->errorString(); + cancelTask( job->errorString() ); + } else { + changeProcessed(); + } +} + +void ChangeItemTask::onAppendMessageDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Append failed: " << job->errorString(); + cancelTask( job->errorString() ); + return; + } + + KIMAP::AppendJob *append = qobject_cast( job ); + + m_newUid = append->uid(); + + // OK it's a content change, so we've to mark the old version as deleted + // remember, you can't modify messages in IMAP mailboxes so that's really + // add+remove all the time. + + // APPEND does not require a SELECT, so we could be anywhere right now + if ( m_session->selectedMailBox() != append->mailBox() ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( m_session ); + select->setMailBox( append->mailBox() ); + + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onPreDeleteSelectDone(KJob*)) ); + + select->start(); + + } else { + if ( m_newUid > 0 ) { + triggerDeleteJob(); + } else { + triggerSearchJob(); + } + } +} + +void ChangeItemTask::onPreDeleteSelectDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "PreDelete select failed: " << job->errorString(); + if ( m_newUid > 0 ) { + recordNewUid(); + } else { + cancelTask( job->errorString() ); + } + } else { + if ( m_newUid > 0 ) { + triggerDeleteJob(); + } else { + triggerSearchJob(); + } + } +} + +void ChangeItemTask::triggerSearchJob() +{ + KIMAP::SearchJob *search = new KIMAP::SearchJob( m_session ); + + search->setUidBased( true ); + search->setSearchLogic( KIMAP::SearchJob::And ); + + if ( !m_messageId.isEmpty() ) { + QByteArray header = "Message-ID "; + header+= m_messageId; + + search->addSearchCriteria( KIMAP::SearchJob::Header, header ); + } else { + search->addSearchCriteria( KIMAP::SearchJob::New ); + + UidNextAttribute *uidNext = item().parentCollection().attribute(); + if ( !uidNext ) { + kWarning() << "Failed to determine new uid."; + cancelTask( i18n( "Could not determine the UID for the newly created message on the server" ) ); + search->deleteLater(); + return; + } + KIMAP::ImapInterval interval( uidNext->uidNext() ); + + search->addSearchCriteria( KIMAP::SearchJob::Uid, interval.toImapSequence() ); + } + + connect( search, SIGNAL(result(KJob*)), + this, SLOT(onSearchDone(KJob*)) ); + + search->start(); +} + +void ChangeItemTask::onSearchDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Search failed: " << job->errorString(); + cancelTask( job->errorString() ); + return; + } + + KIMAP::SearchJob *search = static_cast( job ); + + if ( search->results().count()!=1 ) { + kWarning() << "Failed to determine new uid."; + cancelTask( i18n( "Could not determine the UID for the newly created message on the server" ) ); + return; + } + + m_newUid = search->results().first(); + triggerDeleteJob(); +} + +void ChangeItemTask::triggerDeleteJob() +{ + KIMAP::StoreJob *store = new KIMAP::StoreJob( m_session ); + + store->setUidBased( true ); + store->setSequenceSet( KIMAP::ImapSet( m_oldUid ) ); + store->setFlags( QList() << ImapFlags::Deleted ); + store->setMode( KIMAP::StoreJob::AppendFlags ); + + connect( store, SIGNAL(result(KJob*)), + this, SLOT(onDeleteDone(KJob*)) ); + + store->start(); +} + +void ChangeItemTask::onDeleteDone( KJob */*job*/ ) +{ + recordNewUid(); +} + +void ChangeItemTask::recordNewUid() +{ + Q_ASSERT( m_newUid > 0 ); + + Akonadi::Item i = item(); + Akonadi::Collection c = i.parentCollection(); + + // Get the current uid next value and store it + UidNextAttribute *uidAttr = 0; + int oldNextUid = 0; + if ( c.hasAttribute( "uidnext" ) ) { + uidAttr = static_cast( c.attribute( "uidnext" ) ); + oldNextUid = uidAttr->uidNext(); + } + + // If the uid we just got back is the expected next one of the box + // then update the property to the probable next uid to keep the cache in sync. + // If not something happened in our back, so we don't update and a refetch will + // happen at some point. + if ( m_newUid==oldNextUid ) { + if ( uidAttr==0 ) { + uidAttr = new UidNextAttribute( m_newUid+1 ); + c.addAttribute( uidAttr ); + } else { + uidAttr->setUidNext( m_newUid+1 ); + } + + applyCollectionChanges( c ); + } + + const QString remoteId = QString::number( m_newUid ); + kDebug( 5327 ) << "Setting remote ID to " << remoteId << " for item with local id " << i.id(); + i.setRemoteId( remoteId ); + + changeCommitted( i ); +} + + + diff --git a/kdepim-runtime/resources/imap/changeitemtask.h b/kdepim-runtime/resources/imap/changeitemtask.h new file mode 100644 index 00000000..708b7ac4 --- /dev/null +++ b/kdepim-runtime/resources/imap/changeitemtask.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 CHANGEITEMTASK_H +#define CHANGEITEMTASK_H + +#include "resourcetask.h" + +class ChangeItemTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit ChangeItemTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~ChangeItemTask(); + +private slots: + void onAppendMessageDone( KJob *job ); + + void onPreStoreSelectDone( KJob *job ); + void onStoreFlagsDone( KJob *job ); + + void onPreDeleteSelectDone( KJob *job ); + void onSearchDone( KJob *job ); + void onDeleteDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void triggerStoreJob(); + void triggerSearchJob(); + void triggerDeleteJob(); + + void recordNewUid(); + + KIMAP::Session *m_session; + QByteArray m_messageId; + qint64 m_oldUid; + qint64 m_newUid; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/expungecollectiontask.cpp b/kdepim-runtime/resources/imap/expungecollectiontask.cpp new file mode 100644 index 00000000..563a74df --- /dev/null +++ b/kdepim-runtime/resources/imap/expungecollectiontask.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "expungecollectiontask.h" + +#include +#include + +#include +#include +#include + +#include "noselectattribute.h" + +ExpungeCollectionTask::ExpungeCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( CancelIfNoSession, resource, parent ) +{ + +} + +ExpungeCollectionTask::~ExpungeCollectionTask() +{ +} + +void ExpungeCollectionTask::doStart( KIMAP::Session *session ) +{ + // Prevent expunging items from noselect folders. + if ( collection().hasAttribute( "noselect" ) ) { + NoSelectAttribute* noselect = static_cast( collection().attribute( "noselect" ) ); + if ( noselect->noSelect() ) { + kDebug( 5327 ) << "No Select folder"; + taskDone(); + return; + } + } + + const QString mailBox = mailBoxForCollection( collection() ); + + if ( session->selectedMailBox() != mailBox ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( session ); + select->setMailBox( mailBox ); + + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onSelectDone(KJob*)) ); + + select->start(); + + } else { + triggerExpungeJob( session ); + } +} + +void ExpungeCollectionTask::onSelectDone( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } else { + KIMAP::SelectJob *select = static_cast( job ); + triggerExpungeJob( select->session() ); + } +} + +void ExpungeCollectionTask::triggerExpungeJob( KIMAP::Session *session ) +{ + KIMAP::ExpungeJob *expunge = new KIMAP::ExpungeJob( session ); + + connect( expunge, SIGNAL(result(KJob*)), + this, SLOT(onExpungeDone(KJob*)) ); + + expunge->start(); +} + +void ExpungeCollectionTask::onExpungeDone( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } else { + taskDone(); + } +} + + + diff --git a/kdepim-runtime/resources/imap/expungecollectiontask.h b/kdepim-runtime/resources/imap/expungecollectiontask.h new file mode 100644 index 00000000..2b1cd120 --- /dev/null +++ b/kdepim-runtime/resources/imap/expungecollectiontask.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 EXPUNGECOLLECTIONTASK_H +#define EXPUNGECOLLECTIONTASK_H + +#include "resourcetask.h" + +class ExpungeCollectionTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit ExpungeCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~ExpungeCollectionTask(); + +private slots: + void onSelectDone( KJob *job ); + void onExpungeDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void triggerExpungeJob( KIMAP::Session *session ); +}; + +#endif diff --git a/kdepim-runtime/resources/imap/highestmodseqattribute.cpp b/kdepim-runtime/resources/imap/highestmodseqattribute.cpp new file mode 100644 index 00000000..af166fd9 --- /dev/null +++ b/kdepim-runtime/resources/imap/highestmodseqattribute.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "highestmodseqattribute.h" + +#include + +HighestModSeqAttribute::HighestModSeqAttribute( qint64 highestModSequence ): + Akonadi::Attribute(), + m_highestModSeq( highestModSequence ) +{ +} + +void HighestModSeqAttribute::setHighestModSeq( qint64 highestModSequence ) +{ + m_highestModSeq = highestModSequence; +} + +qint64 HighestModSeqAttribute::highestModSequence() const +{ + return m_highestModSeq; +} + +Akonadi::Attribute* HighestModSeqAttribute::clone() const +{ + return new HighestModSeqAttribute( m_highestModSeq ); +} + +QByteArray HighestModSeqAttribute::type() const +{ + return "highestmodseq"; +} + +void HighestModSeqAttribute::deserialize( const QByteArray &data ) +{ + m_highestModSeq = data.toLongLong(); +} + +QByteArray HighestModSeqAttribute::serialized() const +{ + return QByteArray::number( m_highestModSeq ); +} diff --git a/kdepim-runtime/resources/imap/highestmodseqattribute.h b/kdepim-runtime/resources/imap/highestmodseqattribute.h new file mode 100644 index 00000000..58d6da50 --- /dev/null +++ b/kdepim-runtime/resources/imap/highestmodseqattribute.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef HIGHESTMODSEQATTRIBUTE_H +#define HIGHESTMODSEQATTRIBUTE_H + +#include + +class HighestModSeqAttribute : public Akonadi::Attribute +{ + public: + explicit HighestModSeqAttribute( qint64 highestModSequence = -1 ); + void setHighestModSeq( qint64 highestModSequence ); + qint64 highestModSequence() const; + + virtual void deserialize(const QByteArray& data); + virtual QByteArray serialized() const; + virtual Akonadi::Attribute* clone() const; + virtual QByteArray type() const; + + private: + qint64 m_highestModSeq; +}; + +#endif // HIGHESTMODSEQATTRIBUTE_H diff --git a/kdepim-runtime/resources/imap/imapaccount.cpp b/kdepim-runtime/resources/imap/imapaccount.cpp new file mode 100644 index 00000000..0ca46207 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapaccount.cpp @@ -0,0 +1,109 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "imapaccount.h" + +ImapAccount::ImapAccount() + : m_port( 0 ), + m_timeout( 30 ), + m_encryption( KIMAP::LoginJob::Unencrypted ), + m_authentication( KIMAP::LoginJob::ClearText ), + m_subscriptionEnabled( false ) +{ +} + +ImapAccount::~ImapAccount() +{ +} + +void ImapAccount::setServer( const QString &server ) +{ + m_server = server; +} + +QString ImapAccount::server() const +{ + return m_server; +} + +void ImapAccount::setPort( quint16 port ) +{ + m_port = port; +} + +quint16 ImapAccount::port() const +{ + return m_port; +} + +void ImapAccount::setUserName( const QString &userName ) +{ + m_userName = userName; +} + +QString ImapAccount::userName() const +{ + return m_userName; +} + +void ImapAccount::setEncryptionMode( KIMAP::LoginJob::EncryptionMode mode) +{ + m_encryption = mode; +} + +KIMAP::LoginJob::EncryptionMode ImapAccount::encryptionMode() const +{ + return m_encryption; +} + +void ImapAccount::setAuthenticationMode( KIMAP::LoginJob::AuthenticationMode mode) +{ + m_authentication = mode; +} + +KIMAP::LoginJob::AuthenticationMode ImapAccount::authenticationMode() const +{ + return m_authentication; +} + +void ImapAccount::setSubscriptionEnabled( bool enabled ) +{ + m_subscriptionEnabled = enabled; +} + +bool ImapAccount::isSubscriptionEnabled() const +{ + return m_subscriptionEnabled; +} + +void ImapAccount::setTimeout(int timeout) +{ + m_timeout = timeout; +} + +int ImapAccount::timeout() const +{ + return m_timeout; +} diff --git a/kdepim-runtime/resources/imap/imapaccount.h b/kdepim-runtime/resources/imap/imapaccount.h new file mode 100644 index 00000000..3930a357 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapaccount.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 IMAPACCOUNT_H +#define IMAPACCOUNT_H + +#include + +class ImapAccount +{ +public: + explicit ImapAccount(); + ~ImapAccount(); + + void setServer( const QString &server ); + QString server() const; + + void setPort( quint16 port ); + quint16 port() const; + + void setUserName( const QString &userName ); + QString userName() const; + + void setEncryptionMode( KIMAP::LoginJob::EncryptionMode mode ); + KIMAP::LoginJob::EncryptionMode encryptionMode() const; + + void setAuthenticationMode( KIMAP::LoginJob::AuthenticationMode mode ); + KIMAP::LoginJob::AuthenticationMode authenticationMode() const; + + void setSubscriptionEnabled( bool enabled ); + bool isSubscriptionEnabled() const; + + void setTimeout( int timeout ); + int timeout() const; + +private: + QString m_name; + QString m_server; + quint16 m_port; + QString m_userName; + int m_timeout; + KIMAP::LoginJob::EncryptionMode m_encryption; + KIMAP::LoginJob::AuthenticationMode m_authentication; + bool m_subscriptionEnabled; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/imapflags.cpp b/kdepim-runtime/resources/imap/imapflags.cpp new file mode 100644 index 00000000..7a483272 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapflags.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + * Copyright (c) 2010 Leo Franchi + * + * 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 "imapflags.h" + +const char* ImapFlags::Seen = "\\Seen"; +const char* ImapFlags::Deleted = "\\Deleted"; +const char* ImapFlags::Answered = "\\Answered"; +const char* ImapFlags::Flagged = "\\Flagged"; diff --git a/kdepim-runtime/resources/imap/imapflags.h b/kdepim-runtime/resources/imap/imapflags.h new file mode 100644 index 00000000..99530b3f --- /dev/null +++ b/kdepim-runtime/resources/imap/imapflags.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + * Copyright (c) 2010 Leo Franchi + * + * 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 IMAP_RESOURCE_IMAPFLAGS_H +#define IMAP_RESOURCE_IMAPFLAGS_H + +/** + * Contains constants for IMAP flags from KIMAP. + */ +namespace ImapFlags +{ + /** + * The flag for a message being seen (i.e. opened by user). + */ + extern const char* Seen; + + /** + * The flag for a message being deleted by the user. + */ + extern const char* Deleted; + + /** + * The flag for a message being replied to by the user. + */ + extern const char* Answered; + + /** + * The flag for a message being marked as flagged. + */ + extern const char* Flagged; + +} + +#endif diff --git a/kdepim-runtime/resources/imap/imapidlemanager.cpp b/kdepim-runtime/resources/imap/imapidlemanager.cpp new file mode 100644 index 00000000..db04b161 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapidlemanager.cpp @@ -0,0 +1,185 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "imapidlemanager.h" + +#include + +#include +#include +#include + +#include + +#include "imapresource.h" +#include "sessionpool.h" + +ImapIdleManager::ImapIdleManager( ResourceStateInterface::Ptr state, + SessionPool *pool, ImapResourceBase *parent ) + : QObject( parent ), m_sessionRequestId( 0 ), m_pool( pool ), m_session( 0 ), + m_idle( 0 ), m_resource( parent ), m_state( state ), + m_lastMessageCount( -1 ), m_lastRecentCount( -1 ) +{ + connect( pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)), + this, SLOT(onSessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + m_sessionRequestId = m_pool->requestSession(); +} + +ImapIdleManager::~ImapIdleManager() +{ + stop(); + if ( m_pool ) { + if ( m_sessionRequestId ) { + m_pool->cancelSessionRequest( m_sessionRequestId ); + } + if ( m_session ) { + m_pool->releaseSession( m_session ); + } + } +} + +void ImapIdleManager::stop() +{ + if ( m_idle ) { + m_idle->stop(); + disconnect(m_idle, 0, this, 0); + m_idle = 0; + } + if ( m_pool ) { + disconnect(m_pool, 0, this, 0); + } +} + +KIMAP::Session *ImapIdleManager::session() const +{ + return m_session; +} + +void ImapIdleManager::reconnect() +{ + kDebug() << "attempting to reconnect IDLE session"; + if ( m_session == 0 && m_pool->isConnected() && m_sessionRequestId == 0 ) + m_sessionRequestId = m_pool->requestSession(); +} + +void ImapIdleManager::onSessionRequestDone( qint64 requestId, KIMAP::Session *session, + int errorCode, const QString &/*errorString*/ ) +{ + if ( requestId!=m_sessionRequestId || session==0 || errorCode!=SessionPool::NoError ) { + return; + } + + m_session = session; + m_sessionRequestId = 0; + + connect( m_pool, SIGNAL(connectionLost(KIMAP::Session*)), + this, SLOT(onConnectionLost(KIMAP::Session*)) ); + connect( m_pool, SIGNAL(disconnectDone()), + this, SLOT(onPoolDisconnect()) ); + + + KIMAP::SelectJob *select = new KIMAP::SelectJob( m_session ); + select->setMailBox( m_state->mailBoxForCollection( m_state->collection() ) ); + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onSelectDone(KJob*)) ); + select->start(); + + m_idle = new KIMAP::IdleJob( m_session ); + connect( m_idle, SIGNAL(mailBoxStats(KIMAP::IdleJob*,QString,int,int)), + this, SLOT(onStatsReceived(KIMAP::IdleJob*,QString,int,int)) ); + connect( m_idle, SIGNAL(mailBoxMessageFlagsChanged(KIMAP::IdleJob*,qint64)), + this, SLOT(onFlagsChanged(KIMAP::IdleJob*)) ); + connect( m_idle, SIGNAL(result(KJob*)), + this, SLOT(onIdleStopped()) ); + m_idle->start(); +} + +void ImapIdleManager::onConnectionLost( KIMAP::Session *session ) +{ + if ( session == m_session ) { + // Our session becomes invalid, so get ride of + // the pointer, we don't need to release it once the + // task is done + m_session = 0; + QMetaObject::invokeMethod( this, "reconnect", Qt::QueuedConnection ); + } +} + +void ImapIdleManager::onPoolDisconnect() +{ + // All the sessions in the pool we used changed, + // so get ride of the pointer, we don't need to + // release our session anymore + m_pool = 0; +} + +void ImapIdleManager::onSelectDone( KJob *job ) +{ + KIMAP::SelectJob *select = static_cast( job ); + + m_lastMessageCount = select->messageCount(); + m_lastRecentCount = select->recentCount(); +} + +void ImapIdleManager::onIdleStopped() +{ + kDebug( 5327 ) << "IDLE dropped maybe we should reconnect?"; + m_idle = 0; + if ( m_session ) { + kDebug( 5327 ) << "Restarting the IDLE session!"; + m_idle = new KIMAP::IdleJob( m_session ); + connect( m_idle, SIGNAL(mailBoxStats(KIMAP::IdleJob*,QString,int,int)), + this, SLOT(onStatsReceived(KIMAP::IdleJob*,QString,int,int)) ); + connect( m_idle, SIGNAL(result(KJob*)), + this, SLOT(onIdleStopped()) ); + m_idle->start(); + } +} + +void ImapIdleManager::onStatsReceived(KIMAP::IdleJob *job, const QString &mailBox, + int messageCount, int recentCount) +{ + kDebug( 5327 ) << "IDLE stats received:" << job << mailBox << messageCount << recentCount; + kDebug( 5327 ) << "Cached information:" << m_state->collection().remoteId() << m_state->collection().id() + << m_lastMessageCount << m_lastRecentCount; + + // It seems we're not in sync with the cache, resync is needed + if ( messageCount!=m_lastMessageCount || recentCount!=m_lastRecentCount ) { + m_lastMessageCount = messageCount; + m_lastRecentCount = recentCount; + + kDebug( 5327 ) << "Resync needed for" << mailBox << m_state->collection().id(); + m_resource->synchronizeCollection( m_state->collection().id() ); + } +} + +void ImapIdleManager::onFlagsChanged( KIMAP::IdleJob *job ) +{ + kDebug( 5327 ) << "IDLE flags changed in" << m_session->selectedMailBox(); + m_resource->synchronizeCollection( m_state->collection().id() ); +} + + + diff --git a/kdepim-runtime/resources/imap/imapidlemanager.h b/kdepim-runtime/resources/imap/imapidlemanager.h new file mode 100644 index 00000000..2016b14f --- /dev/null +++ b/kdepim-runtime/resources/imap/imapidlemanager.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RESOURCES_IMAP_IMAPIDLEMANAGER_H +#define RESOURCES_IMAP_IMAPIDLEMANAGER_H + +#include "resourcestateinterface.h" + +#include + +#include +#include + +namespace KIMAP +{ + class IdleJob; + class Session; +} + +class ImapResourceBase; +class SessionPool; + +class KJob; + +class ImapIdleManager : public QObject +{ + Q_OBJECT + +public: + ImapIdleManager( ResourceStateInterface::Ptr state, SessionPool *pool, ImapResourceBase *parent ); + ~ImapIdleManager(); + void stop(); + + KIMAP::Session *session() const; + +private slots: + void onConnectionLost( KIMAP::Session *session ); + void onPoolDisconnect(); + + void onSessionRequestDone( qint64 requestId, KIMAP::Session *session, + int errorCode, const QString &errorString ); + void onSelectDone( KJob *job ); + void onIdleStopped(); + void onStatsReceived( KIMAP::IdleJob *job, const QString &mailBox, + int messageCount, int recentCount ); + void onFlagsChanged( KIMAP::IdleJob *job ); + void reconnect(); + +private: + qint64 m_sessionRequestId; + SessionPool *m_pool; + KIMAP::Session *m_session; + QPointer m_idle; + ImapResourceBase *m_resource; + ResourceStateInterface::Ptr m_state; + qint64 m_lastMessageCount; + qint64 m_lastRecentCount; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/imapresource.cpp b/kdepim-runtime/resources/imap/imapresource.cpp new file mode 100644 index 00000000..e78768a7 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapresource.cpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "imapresource.h" +#include "setupserver.h" +#include "settings.h" +#include "sessionpool.h" +#include "settingspasswordrequester.h" +#include "sessionuiproxy.h" + +#include +#include + +ImapResource::ImapResource( const QString &id ) + : ImapResourceBase( id ) +{ + m_pool->setPasswordRequester( new SettingsPasswordRequester( this, m_pool ) ); + m_pool->setSessionUiProxy( SessionUiProxy::Ptr( new SessionUiProxy ) ); +} + +ImapResource::~ImapResource() +{ +} + +QString ImapResource::defaultName() const +{ + return i18n( "IMAP Account" ); +} + + +KDialog* ImapResource::createConfigureDialog(WId windowId) +{ + SetupServer *dlg = new SetupServer( this, windowId ); + KWindowSystem::setMainWindow( dlg, windowId ); + dlg->setWindowIcon( KIcon( QLatin1String("network-server") ) ); + connect(dlg, SIGNAL(finished(int)), this, SLOT(onConfigurationDone(int)));; + return dlg; +} + +void ImapResource::onConfigurationDone(int result) +{ + SetupServer *dlg = qobject_cast(sender()); + if (result) { + if ( dlg->shouldClearCache() ) { + clearCache(); + } + settings()->writeConfig(); + } + dlg->deleteLater(); +} + +void ImapResource::cleanup() +{ + settings()->cleanup(); +} diff --git a/kdepim-runtime/resources/imap/imapresource.desktop b/kdepim-runtime/resources/imap/imapresource.desktop new file mode 100644 index 00000000..a6800e31 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapresource.desktop @@ -0,0 +1,101 @@ +[Desktop Entry] +Name=IMAP E-Mail Server +Name[bg]=ПощенÑки Ñървър IMAP +Name[bs]=IMAP server za e-poÅ¡tu +Name[ca]=Servidor de correu IMAP +Name[ca@valencia]=Servidor de correu IMAP +Name[cs]=E-mailový IMAP server +Name[da]=IMAP e-mail-server +Name[de]=IMAP-E-Mail-Server +Name[el]=EξυπηÏετητής ηλ.ταχυδÏομείου IMAP +Name[en_GB]=IMAP E-Mail Server +Name[es]=Servidor de correo IMAP +Name[et]=IMAP e-posti server +Name[fi]=IMAP-sähköpostipalvelin +Name[fr]=Serveur de courriers électroniques IMAP +Name[ga]=Freastalaí Ríomhphoist IMAP +Name[gl]=Servidor de Correo IMAP +Name[hu]=IMAP e-mail kiszolgáló +Name[ia]=Servitor IMAP de e-posta +Name[it]=Server di posta IMAP +Name[ja]=IMAP メールサーム+Name[kk]=IMAP Ñл.пошта Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ​អ៊ីមែល IMAP +Name[ko]=IMAP ì´ë©”ì¼ ì„œë²„ +Name[lt]=IMAP el. paÅ¡to serveris +Name[lv]=IMAP e-pasta serveris +Name[nb]=IMAP E-post-tjener +Name[nds]=IMAP-Nettpostserver +Name[nl]=IMAP e-mailserver +Name[nn]=IMAP-basert e-posttenar +Name[pa]=IMAP ਈਮੇਲ ਸਰਵਰ +Name[pl]=Serwer poczty IMAP +Name[pt]=Servidor de E-Mail IMAP +Name[pt_BR]=Servidor de e-mails IMAP +Name[ro]=Server de poÈ™tă IMAP +Name[ru]=Почтовый Ñервер IMAP +Name[sk]=IMAP poÅ¡tový server +Name[sl]=E-poÅ¡tni strežnik IMAP +Name[sr]=ИМÐП Ñервер е‑поште +Name[sr@ijekavian]=ИМÐП Ñервер е‑поште +Name[sr@ijekavianlatin]=IMAP server e‑poÅ¡te +Name[sr@latin]=IMAP server e‑poÅ¡te +Name[sv]=IMAP e-postserver +Name[tr]=IMAP E-posta Sunucusu +Name[uk]=Сервер пошти IMAP +Name[x-test]=xxIMAP E-Mail Serverxx +Name[zh_CN]=IMAP 邮件æœåŠ¡å™¨ +Name[zh_TW]=IMAP 信件伺æœå™¨ +Comment=Connects to an IMAP e-mail server +Comment[bg]=Свързване Ñ Ð¿Ð¾Ñ‰ÐµÐ½Ñки Ñървър IMAP +Comment[bs]=Povezivanje na IMAP e-mail server +Comment[ca]=Connecta a un servidor de correu IMAP +Comment[ca@valencia]=Connecta a un servidor de correu IMAP +Comment[cs]=PÅ™ipojí se na e-mailový IMAP server +Comment[da]=Forbinder til en IMAP e-mail-server +Comment[de]=Verbindet zu einem IMAP E-Mail-Server. +Comment[el]=Συνδέεται σε έναν εξυπηÏετητή ηλ.ταχυδÏομείου IMAP +Comment[en_GB]=Connects to an IMAP e-mail server +Comment[es]=Conecta a un servidor de correo IMAP +Comment[et]=Ãœhendumine IMAP e-posti serveriga +Comment[fi]=Yhdistää IMAP-sähköpostipalvelimeen +Comment[fr]=Se connecte à un serveur de courriers électroniques IMAP +Comment[gl]=Conéctase a un servidor de correo IMAP +Comment[hu]=Kapcsolódás egy IMAP e-mail kiszolgálóhoz +Comment[ia]=Connecte a un servitor IMAP de e-posta +Comment[it]=Si collega ad un server di posta IMAP +Comment[ja]=IMAP ã®ãƒ¡ãƒ¼ãƒ«ã‚µãƒ¼ãƒã«æŽ¥ç¶šã—ã¾ã™ã€‚ +Comment[kk]=IMAP Ñл.пошта Ñерверімен Ð±Ð°Ð¹Ð»Ð°Ð½Ñ‹Ñ Ò›Ò±Ñ€Ñƒ +Comment[km]=ážáž—្ជាប់​ទៅ​កាន់​ម៉ាស៊ីន​បម្រើ​អ៊ីមែល IMAP +Comment[ko]=IMAP ì´ë©”ì¼ ì„œë²„ì— ì—°ê²°í•¨ +Comment[lt]=Prisijungia prie IMAP el. paÅ¡to serverio. +Comment[lv]=PieslÄ“dzas IMAP e-pasta serverim +Comment[nb]=Kobler til en IMAP e-posttjener +Comment[nds]=Stellt en Verbinnen na en IMAP-Nettpostserver op. +Comment[nl]=Maakt verbinding met een IMAP-e-mailserver +Comment[nn]=Koplar til ein IMAP-basert e-posttenar +Comment[pa]=IMAP ਈਮੇਲ ਸਰਵਰ ਨਾਲ ਕà©à¨¨à©ˆà¨•à¨Ÿ ਕਰਦਾ ਹੈ। +Comment[pl]=ÅÄ…czy z serwerem poczty IMAP +Comment[pt]=Liga-se a um servidor de e-mail em IMAP +Comment[pt_BR]=Conecta a um servidor de e-mails IMAP +Comment[ro]=Se conectează la un server de poÈ™tă IMAP +Comment[ru]=Подключение к почтовому Ñерверу IMAP +Comment[sk]=Pripojí sa na IMAP poÅ¡tový server +Comment[sl]=Povezava z e-poÅ¡tnim strežnikom IMAP +Comment[sr]=Повезује Ñе на ИМÐП Ñервер е‑поште +Comment[sr@ijekavian]=Повезује Ñе на ИМÐП Ñервер е‑поште +Comment[sr@ijekavianlatin]=Povezuje se na IMAP server e‑poÅ¡te +Comment[sr@latin]=Povezuje se na IMAP server e‑poÅ¡te +Comment[sv]=Ansluter till en IMAP e-postserver +Comment[tr]=IMAP e-posta sunucusuna baÄŸlanır +Comment[uk]=Ð’Ñтановлює Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· поштовим Ñервером IMAP +Comment[x-test]=xxConnects to an IMAP e-mail serverxx +Comment[zh_CN]=连接到 IMAP 电å­é‚®ä»¶æœåŠ¡å™¨ +Comment[zh_TW]=連線到 IMAP 信件伺æœå™¨ +Type=AkonadiResource +Exec=akonadi_imap_resource +Icon=network-server + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_imap_resource diff --git a/kdepim-runtime/resources/imap/imapresource.h b/kdepim-runtime/resources/imap/imapresource.h new file mode 100644 index 00000000..e9ce7816 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapresource.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 IMAPRESOURCE_H +#define IMAPRESOURCE_H + +#include + +class ImapResource : public ImapResourceBase +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.Imap.Resource" ) + +public: + explicit ImapResource( const QString &id ); + virtual ~ImapResource(); + + virtual KDialog *createConfigureDialog ( WId windowId ); + virtual void cleanup(); + +protected: + virtual QString defaultName() const; + +private Q_SLOTS: + void onConfigurationDone( int result ); + +}; + +#endif // IMAPRESOURCE_H diff --git a/kdepim-runtime/resources/imap/imapresource.kcfg b/kdepim-runtime/resources/imap/imapresource.kcfg new file mode 100644 index 00000000..fbec2992 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapresource.kcfg @@ -0,0 +1,114 @@ + + + + + + + + + + 993 + + + + + + + SSL + + + + + + + 1 + + + + false + + + 30 + + + + + + false + + + + true + + + + 5 + + + + true + + + + true + + + + -1 + + + + false + + + + true + + + + + + + + + + + + + + + + + false + + + + true + + + + 4190 + + + + + + + kmail-vacation.siv + + + + + + + + ImapUserPassword + + + diff --git a/kdepim-runtime/resources/imap/imapresourcebase.cpp b/kdepim-runtime/resources/imap/imapresourcebase.cpp new file mode 100644 index 00000000..9631d87f --- /dev/null +++ b/kdepim-runtime/resources/imap/imapresourcebase.cpp @@ -0,0 +1,766 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "imapresourcebase.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "collectionannotationsattribute.h" +#include "collectionflagsattribute.h" +#include "imapaclattribute.h" +#include "imapquotaattribute.h" +#include "noselectattribute.h" +#include "timestampattribute.h" +#include "uidvalidityattribute.h" +#include "uidnextattribute.h" +#include "highestmodseqattribute.h" + +#include "settings.h" +#include "imapaccount.h" +#include "imapidlemanager.h" +#include "resourcestate.h" +#include "subscriptiondialog.h" + +#include "addcollectiontask.h" +#include "additemtask.h" +#include "changecollectiontask.h" +#include "changeitemsflagstask.h" +#include "changeitemtask.h" +#include "expungecollectiontask.h" +#include "movecollectiontask.h" +#include "moveitemstask.h" +#include "removecollectionrecursivetask.h" +#include "retrievecollectionmetadatatask.h" +#include "retrievecollectionstask.h" +#include "retrieveitemtask.h" +#include "retrieveitemstask.h" +#include "searchtask.h" + +#include "settingspasswordrequester.h" +#include "sessionpool.h" +#include "sessionuiproxy.h" +#include "imapflags.h" + +#include "resourceadaptor.h" + +#ifdef MAIL_SERIALIZER_PLUGIN_STATIC + +Q_IMPORT_PLUGIN(akonadi_serializer_mail) +#endif + +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QWeakPointer) + +using namespace Akonadi; + +ImapResourceBase::ImapResourceBase( const QString &id ) + : ResourceBase( id ), + m_pool( new SessionPool( 2, this ) ), + mSubscriptions( 0 ), + m_idle( 0 ), + m_settings( 0 ) +{ + QTimer::singleShot( 0, this, SLOT(updateResourceName()) ); + + connect( m_pool, SIGNAL(connectDone(int,QString)), + this, SLOT(onConnectDone(int,QString)) ); + connect( m_pool, SIGNAL(connectionLost(KIMAP::Session*)), + this, SLOT(onConnectionLost(KIMAP::Session*)) ); + + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + + // For QMetaObject::invokeMethod() + qRegisterMetaType >(); + + changeRecorder()->fetchCollection( true ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + changeRecorder()->collectionFetchScope().setIncludeStatistics( true ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All ); + changeRecorder()->itemFetchScope().setFetchModificationTime( false ); + //(Andras) disable now, as tokoe reported problems with it and the mail filter: changeRecorder()->fetchChangedOnly( true ); + + setHierarchicalRemoteIdentifiersEnabled( true ); + setItemTransactionMode( ItemSync::MultipleTransactions ); // we can recover from incomplete syncs, so we can use a faster mode + ItemFetchScope scope( changeRecorder()->itemFetchScope() ); + scope.fetchFullPayload( false ); + scope.setAncestorRetrieval( ItemFetchScope::None ); + setItemSynchronizationFetchScope( scope ); + setDisableAutomaticItemDeliveryDone( true ); + setItemSyncBatchSize( 100 ); + + connect( this, SIGNAL(reloadConfiguration()), SLOT(reconnect()) ); + + + m_statusMessageTimer = new QTimer( this ); + m_statusMessageTimer->setSingleShot( true ); + connect( m_statusMessageTimer, SIGNAL(timeout()), SLOT(clearStatusMessage()) ); + connect( this, SIGNAL(error(QString)), SLOT(showError(QString)) ); + + QMetaObject::invokeMethod( this, "delayedInit", Qt::QueuedConnection ); +} + +void ImapResourceBase::delayedInit() +{ + settings(); // make sure the D-Bus settings interface is up + new ImapResourceBaseAdaptor( this ); + setNeedsNetwork( needsNetwork() ); + + // Migration issue: trash folder had ID in config, but didn't have SpecialCollections attribute, fix that. + if (!settings()->trashCollectionMigrated()) { + const Akonadi::Collection::Id trashCollection = settings()->trashCollection(); + if (trashCollection != -1) { + Collection attributeCollection(trashCollection); + SpecialCollections::setSpecialCollectionType("trash", attributeCollection); + } + settings()->setTrashCollectionMigrated(true); + } +} + + +ImapResourceBase::~ImapResourceBase() +{ + //Destroy everything that could cause callbacks immediately, otherwise the callbacks can result in a crash. + + if ( m_idle ) { + delete m_idle; + m_idle = 0; + } + + Q_FOREACH (ResourceTask* task, m_taskList) { + delete task; + } + m_taskList.clear(); + + delete m_pool; + delete m_settings; +} + +void ImapResourceBase::aboutToQuit() +{ + //TODO the resource would ideally have to signal when it's done with logging out etc, before the destructor gets called + if ( m_idle ) { + m_idle->stop(); + } + + Q_FOREACH (ResourceTask* task, m_taskList) { + task->kill(); + } + + m_pool->disconnect(); +} + +void ImapResourceBase::updateResourceName() +{ + if ( name() == identifier() ) { + const QString agentType = AgentManager::self()->instance( identifier() ).type().identifier(); + const QString agentsrcFile = KGlobal::dirs()->localxdgconfdir() + QLatin1String("akonadi/agentsrc"); + + const QSettings agentsrc( agentsrcFile, QSettings::IniFormat ); + const int instanceCounter = agentsrc.value( + QString::fromLatin1( "InstanceCounters/%1/InstanceCounter" ).arg( agentType ), + -1 ).toInt(); + + if ( instanceCounter > 0 ) { + setName( QString("%1 %2").arg(defaultName()).arg(instanceCounter) ); + } else { + setName( defaultName() ); + } + } +} + + +// ----------------------------------------------------------------------------- + +void ImapResourceBase::configure( WId windowId ) +{ + if ( createConfigureDialog( windowId )->exec() == QDialog::Accepted ) { + emit configurationDialogAccepted(); + reconnect(); + } else { + emit configurationDialogRejected(); + } +} + +// ---------------------------------------------------------------------------------- + +void ImapResourceBase::startConnect( const QVariant& ) +{ + if ( settings()->imapServer().isEmpty() ) { + setOnline( false ); + emit status( NotConfigured, i18n( "No server configured yet." ) ); + taskDone(); + return; + } + + m_pool->disconnect(); // reset all state, delete any old account + ImapAccount *account = new ImapAccount; + settings()->loadAccount( account ); + + const bool result = m_pool->connect( account ); + Q_ASSERT( result ); + Q_UNUSED( result ); +} + +int ImapResourceBase::configureSubscription(qlonglong windowId) +{ + if (mSubscriptions) + return 0; + + if ( !m_pool->account() ) + return -2; + const QString password = settings()->password(); + if ( password.isEmpty() ) + return -1; + + mSubscriptions = new SubscriptionDialog( 0, SubscriptionDialog::AllowToEnableSubscription ); + if(windowId) { +#ifndef Q_WS_WIN + KWindowSystem::setMainWindow( mSubscriptions, windowId ); +#else + KWindowSystem::setMainWindow( mSubscriptions, (HWND)windowId ); +#endif + } + mSubscriptions->setCaption( i18nc( "@title:window", "Serverside Subscription" ) ); + mSubscriptions->setWindowIcon( KIcon( QLatin1String("network-server") ) ); + mSubscriptions->connectAccount( *m_pool->account(), password ); + mSubscriptions->setSubscriptionEnabled( settings()->subscriptionEnabled() ); + + if ( mSubscriptions->exec() ) { + settings()->setSubscriptionEnabled( mSubscriptions->subscriptionEnabled() ); + settings()->writeConfig(); + emit configurationDialogAccepted(); + reconnect(); + } + delete mSubscriptions; + + return 0; +} + +void ImapResourceBase::onConnectDone( int errorCode, const QString &errorString ) +{ + switch ( errorCode ) { + case SessionPool::NoError: + setOnline( true ); + taskDone(); + emit status( Idle, i18n( "Connection established." ) ); + + synchronizeCollectionTree(); + break; + + case SessionPool::PasswordRequestError: + case SessionPool::EncryptionError: + case SessionPool::LoginFailError: + case SessionPool::CapabilitiesTestError: + case SessionPool::IncompatibleServerError: + setOnline( false ); + emit status( Broken, errorString ); + cancelTask(); + return; + + case SessionPool::CouldNotConnectError: + emit status( Idle, i18n( "Server is not available." ) ); + deferTask(); + setTemporaryOffline((m_pool->account() && m_pool->account()->timeout() > 0) ? m_pool->account()->timeout() : 300); + return; + + case SessionPool::ReconnectNeededError: + reconnect(); + return; + + case SessionPool::NoAvailableSessionError: + kFatal() << "Shouldn't happen"; + return; + case SessionPool::CancelledError: + kWarning() << "Session login cancelled"; + return; + } +} + +void ImapResourceBase::onConnectionLost( KIMAP::Session */*session*/ ) +{ + if ( !m_pool->isConnected() ) { + reconnect(); + } +} + +ResourceStateInterface::Ptr ImapResourceBase::createResourceState(const TaskArguments &args) +{ + return ResourceStateInterface::Ptr(new ResourceState(this, args)); +} + +Settings *ImapResourceBase::settings() const +{ + if (m_settings == 0) { + m_settings = new Settings; + } + + return m_settings; +} + + +// ---------------------------------------------------------------------------------- + +bool ImapResourceBase::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + // The collection name is empty here... + //emit status( AgentBase::Running, i18nc( "@info:status", "Retrieving item in '%1'", item.parentCollection().name() ) ); + + RetrieveItemTask *task = new RetrieveItemTask( createResourceState(TaskArguments(item, parts)), this ); + task->start( m_pool ); + queueTask( task ); + return true; +} + +void ImapResourceBase::itemAdded( const Item &item, const Collection &collection ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Adding item in '%1'", collection.name() ) ); + + startTask(new AddItemTask( createResourceState(TaskArguments(item, collection)), this )); +} + +void ImapResourceBase::itemChanged( const Item &item, const QSet &parts ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Updating item in '%1'", item.parentCollection().name() ) ); + + startTask(new ChangeItemTask( createResourceState(TaskArguments(item, parts)), this )); +} + +void ImapResourceBase::itemsFlagsChanged( const Item::List& items, const QSet< QByteArray >& addedFlags, + const QSet< QByteArray >& removedFlags ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Updating items" ) ); + + startTask(new ChangeItemsFlagsTask( createResourceState(TaskArguments(items, addedFlags, removedFlags)), this )); +} + +void ImapResourceBase::itemsRemoved( const Akonadi::Item::List &items ) +{ + const QString mailBox = ResourceStateInterface::mailBoxForCollection( items.first().parentCollection(), false ); + if ( mailBox.isEmpty() ) { + // this item will be removed soon by its parent collection + changeProcessed(); + return; + } + + emit status( AgentBase::Running, i18nc( "@info:status", "Removing items" ) ); + + startTask(new ChangeItemsFlagsTask( createResourceState(TaskArguments(items, QSet() << ImapFlags::Deleted, QSet())), this )); +} + +void ImapResourceBase::itemsMoved( const Akonadi::Item::List &items, const Akonadi::Collection &source, + const Akonadi::Collection &destination ) +{ + if ( items.first().parentCollection() != destination ) { // should have been set by the server + kWarning() << "Collections don't match: destination=" << destination.id() + << "; items parent=" << items.first().parentCollection().id() + << "; source collection=" << source.id(); + //Q_ASSERT( false ); + //TODO: Find out why this happens + cancelTask(); + return; + } + + emit status( AgentBase::Running, i18nc( "@info:status", "Moving items from '%1' to '%2'", source.name(), destination.name() ) ); + + startTask(new MoveItemsTask( createResourceState(TaskArguments(items, source, destination)), this )); +} + + + +// ---------------------------------------------------------------------------------- + +void ImapResourceBase::retrieveCollections() +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Retrieving folders" ) ); + + startTask(new RetrieveCollectionsTask( createResourceState(TaskArguments()), this )); +} + +void ImapResourceBase::retrieveCollectionAttributes( const Akonadi::Collection &col ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Retrieving extra folder information for '%1'", col.name() ) ); + startTask(new RetrieveCollectionMetadataTask( createResourceState(TaskArguments(col)), this )); +} + +void ImapResourceBase::retrieveItems( const Collection &col ) +{ + synchronizeCollectionAttributes(col.id()); + + setItemStreamingEnabled( true ); + + RetrieveItemsTask *task = new RetrieveItemsTask( createResourceState(TaskArguments(col)), this); + connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); + connect(this, SIGNAL(retrieveNextItemSyncBatch(int)), task, SLOT(onReadyForNextBatch(int))); + startTask(task); +} + +void ImapResourceBase::collectionAdded( const Collection & collection, const Collection &parent ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Creating folder '%1'", collection.name() ) ); + startTask(new AddCollectionTask( createResourceState(TaskArguments(collection, parent)), this )); +} + +void ImapResourceBase::collectionChanged( const Collection &collection, const QSet &parts ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Updating folder '%1'", collection.name() ) ); + startTask(new ChangeCollectionTask( createResourceState(TaskArguments(collection, parts)), this )); +} + +void ImapResourceBase::collectionRemoved( const Collection &collection ) +{ + //TODO Move this to the task + const QString mailBox = ResourceStateInterface::mailBoxForCollection( collection, false ); + if ( mailBox.isEmpty() ) { + // this collection will be removed soon by its parent collection + changeProcessed(); + return; + } + emit status( AgentBase::Running, i18nc( "@info:status", "Removing folder '%1'", collection.name() ) ); + + startTask(new RemoveCollectionRecursiveTask( createResourceState(TaskArguments(collection)), this )); +} + +void ImapResourceBase::collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, + const Akonadi::Collection &destination ) +{ + emit status( AgentBase::Running, i18nc( "@info:status", "Moving folder '%1' from '%2' to '%3'", + collection.name(), source.name(), destination.name() ) ); + startTask(new MoveCollectionTask( createResourceState(TaskArguments(collection, source, destination)), this )); +} + + + +void ImapResourceBase::addSearch(const QString& query, const QString& queryLanguage, const Collection& resultCollection) +{ +} + +void ImapResourceBase::removeSearch(const Collection& resultCollection) +{ +} + +void ImapResourceBase::search( const QString &query, const Collection &collection ) +{ + QVariantMap arg; + arg[QLatin1String("query")] = query; + arg[QLatin1String("collection")] = QVariant::fromValue( collection ); + scheduleCustomTask( this, "doSearch", arg ); +} + +void ImapResourceBase::doSearch( const QVariant &arg ) +{ + const QVariantMap map = arg.toMap(); + const QString query = map[QLatin1String("query")].toString(); + const Collection collection = map[QLatin1String("collection")].value(); + + emit status( AgentBase::Running, i18nc( "@info:status", "Searching..." ) ); + startTask(new SearchTask( createResourceState(TaskArguments(collection)), query, this )); +} + + +// ----- + +// ---------------------------------------------------------------------------------- + +void ImapResourceBase::scheduleConnectionAttempt() +{ + // block all other tasks, until we are connected + scheduleCustomTask( this, "startConnect", QVariant(), ResourceBase::Prepend ); +} + +void ImapResourceBase::doSetOnline(bool online) +{ +#ifndef IMAPRESOURCE_NO_SOLID + kDebug() << "online=" << online; +#endif + if ( !online ) { + Q_FOREACH(ResourceTask* task, m_taskList) { + task->kill(); + delete task; + } + m_taskList.clear(); + m_pool->cancelPasswordRequests(); + if (m_pool->isConnected()) { + m_pool->disconnect(); + } + if (m_idle) { + m_idle->stop(); + delete m_idle; + m_idle = 0; + } + settings()->clearCachedPassword(); + } else if ( online && !m_pool->isConnected() ) { + scheduleConnectionAttempt(); + } + ResourceBase::doSetOnline( online ); +} + +QChar ImapResourceBase::separatorCharacter() const +{ + return m_separatorCharacter; +} + +void ImapResourceBase::setSeparatorCharacter( const QChar &separator ) +{ + m_separatorCharacter = separator; +} + +bool ImapResourceBase::needsNetwork() const +{ + const QString hostName = settings()->imapServer().section( QLatin1Char(':'), 0, 0 ); + // ### is there a better way to do this? + if ( hostName == QLatin1String( "127.0.0.1" ) || + hostName == QLatin1String( "localhost" ) || + hostName == QHostInfo::localHostName() ) { + return false; + } + return true; +} + +void ImapResourceBase::reconnect() +{ + setNeedsNetwork( needsNetwork() ); + setOnline( false ); // we are not connected initially + setOnline( true ); +} + + + +// ---------------------------------------------------------------------------------- + +void ImapResourceBase::startIdleIfNeeded() +{ + if ( !m_idle ) { + startIdle(); + } +} + +void ImapResourceBase::startIdle() +{ + delete m_idle; + m_idle = 0; + + if ( !m_pool->serverCapabilities().contains( QLatin1String("IDLE") ) ) + return; + + //Without password we don't even have to try + if (settings()->password().isEmpty()) { + return; + } + + const QStringList ridPath = settings()->idleRidPath(); + if ( ridPath.size() < 2 ) + return; + + Collection c, p; + p.setParentCollection( Collection::root() ); + for ( int i = ridPath.size() - 1; i > 0; --i ) { + p.setRemoteId( ridPath.at( i ) ); + c.setParentCollection( p ); + p = c; + } + c.setRemoteId( ridPath.first() ); + + Akonadi::CollectionFetchScope scope; + scope.setResource( identifier() ); + scope.setAncestorRetrieval( Akonadi::CollectionFetchScope::All ); + + Akonadi::CollectionFetchJob *fetch + = new Akonadi::CollectionFetchJob( c, Akonadi::CollectionFetchJob::Base, this ); + fetch->setFetchScope( scope ); + + connect( fetch, SIGNAL(result(KJob*)), + this, SLOT(onIdleCollectionFetchDone(KJob*)) ); +} + +void ImapResourceBase::onIdleCollectionFetchDone( KJob *job ) +{ + if (job->error()) { + kWarning() << "CollectionFetch for idling failed." + << "error=" << job->error() + << ", errorString=" << job->errorString(); + return; + } + Akonadi::CollectionFetchJob *fetch = static_cast(job); + //Can be empty if collection is not subscribed locally + if (!fetch->collections().isEmpty()) { + m_idle = new ImapIdleManager( createResourceState(TaskArguments(fetch->collections().first())), m_pool, this ); + } +} + + + +// ---------------------------------------------------------------------------------- + +void ImapResourceBase::requestManualExpunge( qint64 collectionId ) +{ + if ( !settings()->automaticExpungeEnabled() ) { + Collection collection( collectionId ); + + Akonadi::CollectionFetchScope scope; + scope.setResource( identifier() ); + scope.setAncestorRetrieval( Akonadi::CollectionFetchScope::All ); + scope.setIncludeUnsubscribed( true ); + + Akonadi::CollectionFetchJob *fetch + = new Akonadi::CollectionFetchJob( collection, + Akonadi::CollectionFetchJob::Base, + this ); + fetch->setFetchScope( scope ); + + connect( fetch, SIGNAL(result(KJob*)), + this, SLOT(onExpungeCollectionFetchDone(KJob*)) ); + } +} + +void ImapResourceBase::onExpungeCollectionFetchDone( KJob *job ) +{ + if ( job->error() == 0 ) { + Akonadi::CollectionFetchJob *fetch = static_cast( job ); + Akonadi::Collection collection = fetch->collections().first(); + + scheduleCustomTask( this, "triggerCollectionExpunge", + QVariant::fromValue( collection ) ); + + } else { + kWarning() << "CollectionFetch for expunge failed." + << "error=" << job->error() + << ", errorString=" << job->errorString(); + } +} + +void ImapResourceBase::triggerCollectionExpunge( const QVariant &collectionVariant ) +{ + const Collection collection = collectionVariant.value(); + + ExpungeCollectionTask *task = new ExpungeCollectionTask( createResourceState(TaskArguments(collection)), this ); + task->start( m_pool ); + queueTask( task ); +} + + + +// ---------------------------------------------------------------------------------- + +void ImapResourceBase::abortActivity() +{ + if ( !m_taskList.isEmpty() ) { + m_pool->disconnect( SessionPool::CloseSession ); + scheduleConnectionAttempt(); + } +} + +void ImapResourceBase::queueTask( ResourceTask *task ) +{ + connect( task, SIGNAL(destroyed(QObject*)), + this, SLOT(taskDestroyed(QObject*)) ); + m_taskList << task; +} + +void ImapResourceBase::startTask( ResourceTask* task ) +{ + task->start(m_pool); + queueTask(task); +} + +void ImapResourceBase::taskDestroyed( QObject *task ) +{ + m_taskList.removeAll( static_cast( task ) ); +} + + +QStringList ImapResourceBase::serverCapabilities() const +{ + return m_pool->serverCapabilities(); +} + +void ImapResourceBase::cleanup() +{ + settings()->cleanup(); +} + +QString ImapResourceBase::dumpResourceToString() const +{ + QString ret; + Q_FOREACH(ResourceTask* task, m_taskList) { + if (!ret.isEmpty()) + ret += QLatin1String(", "); + ret += QLatin1String(task->metaObject()->className()); + } + return QLatin1String("IMAP tasks: ") + ret; +} + +void ImapResourceBase::showError( const QString &message ) +{ + emit status( Akonadi::AgentBase::Idle, message ); + m_statusMessageTimer->start( 1000*10 ); +} + +void ImapResourceBase::clearStatusMessage() +{ + emit status( Akonadi::AgentBase::Idle, QString() ); +} + +void ImapResourceBase::modifyCollection(const Collection &col) +{ + Akonadi::CollectionModifyJob *modJob = new Akonadi::CollectionModifyJob(col, this); + connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionModifyDone(KJob*))); +} + +void ImapResourceBase::onCollectionModifyDone(KJob* job) +{ + if (job->error()) { + kWarning() << "Failed to modify collection: " << job->errorString(); + } +} + diff --git a/kdepim-runtime/resources/imap/imapresourcebase.h b/kdepim-runtime/resources/imap/imapresourcebase.h new file mode 100644 index 00000000..2b823cc8 --- /dev/null +++ b/kdepim-runtime/resources/imap/imapresourcebase.h @@ -0,0 +1,171 @@ +/* + Copyright (c) 2007 Till Adam + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Kevin Ottens + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RESOURCES_IMAP_IMAPRESOURCEBASE_H +#define RESOURCES_IMAP_IMAPRESOURCEBASE_H + +#include +#include +#include +#include +#include "resourcestateinterface.h" +#include "resourcestate.h" + +class QTimer; + +class ResourceTask; +namespace KIMAP +{ + class Session; +} + +class ImapIdleManager; +class SessionPool; +class ResourceState; +class SubscriptionDialog; +class Settings; + +class ImapResourceBase : public Akonadi::ResourceBase, + public Akonadi::AgentBase::ObserverV3, + public Akonadi::AgentSearchInterface +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.Akonadi.ImapResourceBase") +protected: + using Akonadi::AgentBase::Observer::collectionChanged; + +public: + explicit ImapResourceBase( const QString &id ); + ~ImapResourceBase(); + + virtual KDialog *createConfigureDialog( WId windowId ) = 0; + + QStringList serverCapabilities() const; + void cleanup(); + + virtual Settings* settings() const; + +public Q_SLOTS: + virtual void configure( WId windowId ); + + // DBus methods + Q_SCRIPTABLE void requestManualExpunge( qint64 collectionId ); + Q_SCRIPTABLE int configureSubscription( qlonglong windowId = 0 ); + + // pseudo-virtual called by ResourceBase + QString dumpResourceToString() const; + +protected Q_SLOTS: + void startIdleIfNeeded(); + void startIdle(); + + void abortActivity(); + + virtual void retrieveCollections(); + void retrieveCollectionAttributes( const Akonadi::Collection &col ); + + virtual void retrieveItems( const Akonadi::Collection &col ); + virtual bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + +protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemsFlagsChanged( const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags ); + virtual void itemsRemoved( const Akonadi::Item::List &items ); + virtual void itemsMoved( const Akonadi::Item::List &item, const Akonadi::Collection &source, + const Akonadi::Collection &destination ); + + + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection, const QSet &parts ); + virtual void collectionRemoved( const Akonadi::Collection &collection ); + virtual void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, + const Akonadi::Collection &destination ); + + virtual void addSearch( const QString &query, const QString &queryLanguage, const Akonadi::Collection &resultCollection ); + virtual void removeSearch( const Akonadi::Collection &resultCollection ); + virtual void search( const QString &query, const Akonadi::Collection &collection ); + + virtual void doSetOnline(bool online); + + QChar separatorCharacter() const; + void setSeparatorCharacter( const QChar &separator ); + + virtual void aboutToQuit(); + + virtual ResourceStateInterface::Ptr createResourceState(const TaskArguments &); + virtual QString defaultName() const = 0; + +private Q_SLOTS: + void doSearch( const QVariant &arg ); + + void reconnect(); + + void scheduleConnectionAttempt(); + void startConnect( const QVariant & ); // the parameter is necessary, since this method is used by the task scheduler + void onConnectDone( int errorCode, const QString &errorMessage ); + void onConnectionLost( KIMAP::Session *session ); + + + void onIdleCollectionFetchDone( KJob *job ); + + void onExpungeCollectionFetchDone( KJob *job ); + void triggerCollectionExpunge( const QVariant &collectionVariant ); + + + void taskDestroyed( QObject *task ); + + void showError( const QString &message ); + void clearStatusMessage(); + + void updateResourceName(); + + void onCollectionModifyDone( KJob *job ); + + void delayedInit(); + +protected: + //Starts and queues a task + void startTask( ResourceTask *task ); + void queueTask( ResourceTask *task ); + SessionPool *m_pool; + +private: + friend class ResourceState; + + bool needsNetwork() const; + void modifyCollection(const Akonadi::Collection &); + + friend class ImapIdleManager; + + QList m_taskList; //used to be able to kill tasks + QPointer mSubscriptions; + ImapIdleManager *m_idle; + QTimer *m_statusMessageTimer; + QChar m_separatorCharacter; + mutable Settings *m_settings; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/main.cpp b/kdepim-runtime/resources/imap/main.cpp new file mode 100644 index 00000000..406837e1 --- /dev/null +++ b/kdepim-runtime/resources/imap/main.cpp @@ -0,0 +1,22 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 "imapresource.h" + +AKONADI_RESOURCE_MAIN(ImapResource) diff --git a/kdepim-runtime/resources/imap/messagehelper.cpp b/kdepim-runtime/resources/imap/messagehelper.cpp new file mode 100644 index 00000000..cf893151 --- /dev/null +++ b/kdepim-runtime/resources/imap/messagehelper.cpp @@ -0,0 +1,82 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 "messagehelper.h" + +#include + +#include "resourcetask.h" + +MessageHelper::~MessageHelper() +{ + +} + +Akonadi::Item MessageHelper::createItemFromMessage(KMime::Message::Ptr message, + const qint64 uid, + const qint64 size, + const QList &attrs, + const QList &flags, + const KIMAP::FetchJob::FetchScope &scope, + bool &ok) const +{ + Q_UNUSED(attrs); + + Akonadi::Item i; + if (scope.mode == KIMAP::FetchJob::FetchScope::Flags) { + i.setRemoteId(QString::number(uid)); + i.setMimeType(KMime::Message::mimeType()); + i.setFlags(Akonadi::Item::Flags::fromList(ResourceTask::toAkonadiFlags(flags))); + } else { + if (!message) { + kWarning() << "Got empty message: " << uid; + ok = false; + return Akonadi::Item(); + } + // Sometimes messages might not have a body at all + if (message->body().isEmpty() && (scope.mode == KIMAP::FetchJob::FetchScope::Full || scope.mode == KIMAP::FetchJob::FetchScope::Content)) { + // In that case put a space in as body so that it gets cached + // otherwise we'll wrongly believe the body part is missing from the cache + message->setBody( " " ); + } + i.setRemoteId(QString::number(uid)); + i.setMimeType(KMime::Message::mimeType()); + i.setPayload(KMime::Message::Ptr(message)); + i.setSize(size); + + // update status flags + if (KMime::isSigned(message.get())) { + i.setFlag(Akonadi::MessageFlags::Signed); + } + if (KMime::isEncrypted(message.get())) { + i.setFlag(Akonadi::MessageFlags::Encrypted); + } + if (KMime::isInvitation(message.get())) { + i.setFlag(Akonadi::MessageFlags::HasInvitation); + } + if (KMime::hasAttachment(message.get())) { + i.setFlag(Akonadi::MessageFlags::HasAttachment); + } + + foreach (const QByteArray &flag, ResourceTask::toAkonadiFlags(flags)) { + i.setFlag(flag); + } + } + ok = true; + return i; +} diff --git a/kdepim-runtime/resources/imap/messagehelper.h b/kdepim-runtime/resources/imap/messagehelper.h new file mode 100644 index 00000000..43874a46 --- /dev/null +++ b/kdepim-runtime/resources/imap/messagehelper.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 MESSAGEHELPER_H +#define MESSAGEHELPER_H + +#include +#include +#include +#include + +class MessageHelper { +public: + typedef boost::shared_ptr Ptr; + + virtual ~MessageHelper(); + virtual Akonadi::Item createItemFromMessage(KMime::Message::Ptr message, + const qint64 uid, + const qint64 size, + const QList &attrs, + const QList &flags, + const KIMAP::FetchJob::FetchScope &scope, + bool &ok) const; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/movecollectiontask.cpp b/kdepim-runtime/resources/imap/movecollectiontask.cpp new file mode 100644 index 00000000..2e551921 --- /dev/null +++ b/kdepim-runtime/resources/imap/movecollectiontask.cpp @@ -0,0 +1,154 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "movecollectiontask.h" + +#include +#include + +#include +#include +#include +#include + +#include + +MoveCollectionTask::MoveCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( DeferIfNoSession, resource, parent ) +{ + +} + +MoveCollectionTask::~MoveCollectionTask() +{ +} + +void MoveCollectionTask::doStart( KIMAP::Session *session ) +{ + if ( collection().remoteId().isEmpty() ) { + emitError( i18n( "Cannot move IMAP folder '%1', it does not exist on the server.", + collection().name() ) ); + changeProcessed(); + return; + } + + if ( sourceCollection().remoteId().isEmpty() ) { + emitError( i18n( "Cannot move IMAP folder '%1' out of '%2', '%2' does not exist on the server.", + collection().name(), + sourceCollection().name() ) ); + changeProcessed(); + return; + } + + if ( targetCollection().remoteId().isEmpty() ) { + emitError( i18n( "Cannot move IMAP folder '%1' to '%2', '%2' does not exist on the server.", + collection().name(), + sourceCollection().name() ) ); + changeProcessed(); + return; + } + + if ( session->selectedMailBox() != mailBoxForCollection( collection() ) ) { + doRename( session ); + return; + } + + // Some IMAP servers don't allow moving an opened mailbox, so make sure + // it's not opened (https://bugs.kde.org/show_bug.cgi?id=324932) by examining + // a non-existent mailbox. We don't use CLOSE in order not to trigger EXPUNGE + KIMAP::SelectJob *examine = new KIMAP::SelectJob( session ); + examine->setOpenReadOnly( true ); // use EXAMINE instead of SELECT + examine->setMailBox( QString::fromLatin1( "IMAP Resource non existing folder %1" ).arg( QUuid::createUuid().toString() ) ); + connect( examine, SIGNAL(result(KJob*)), + this, SLOT(onExamineDone(KJob*)) ); + examine->start(); +} + +void MoveCollectionTask::onExamineDone( KJob* job ) +{ + // We deliberately ignore any error here, because the SelectJob will always fail + // when examining a non-existent mailbox + + KIMAP::SelectJob *examine = static_cast( job ); + doRename( examine->session() ); +} + +QString MoveCollectionTask::mailBoxForCollections( const Akonadi::Collection& parent, const Akonadi::Collection& child ) const +{ + const QString parentMailbox = mailBoxForCollection( parent ); + if ( parentMailbox.isEmpty() ) { + return child.remoteId().mid(1); //Strip separator on toplevel mailboxes + } + return parentMailbox + child.remoteId(); +} + +void MoveCollectionTask::doRename( KIMAP::Session *session ) +{ + // collection.remoteId() already includes the separator + const QString oldMailBox = mailBoxForCollections( sourceCollection(), collection() ); + const QString newMailBox = mailBoxForCollections( targetCollection(), collection() ); + + if ( oldMailBox != newMailBox ) { + KIMAP::RenameJob *job = new KIMAP::RenameJob( session ); + job->setSourceMailBox( oldMailBox ); + job->setDestinationMailBox( newMailBox ); + + connect( job, SIGNAL(result(KJob*)), + this, SLOT(onRenameDone(KJob*)) ); + + job->start(); + + } else { + changeProcessed(); + } +} + +void MoveCollectionTask::onRenameDone( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } else { + // Automatically subscribe to the new mailbox name + KIMAP::RenameJob *rename = static_cast( job ); + + KIMAP::SubscribeJob *subscribe = new KIMAP::SubscribeJob( rename->session() ); + subscribe->setMailBox( rename->destinationMailBox() ); + + connect( subscribe, SIGNAL(result(KJob*)), + this, SLOT(onSubscribeDone(KJob*)) ); + + subscribe->start(); + } +} + +void MoveCollectionTask::onSubscribeDone( KJob *job ) +{ + if ( job->error() ) { + emitWarning( i18n( "Failed to subscribe to the folder '%1' on the IMAP server. " + "It will disappear on next sync. Use the subscription dialog to overcome that", + collection().name() ) ); + } + + changeCommitted( collection() ); +} + + + diff --git a/kdepim-runtime/resources/imap/movecollectiontask.h b/kdepim-runtime/resources/imap/movecollectiontask.h new file mode 100644 index 00000000..c1abcb19 --- /dev/null +++ b/kdepim-runtime/resources/imap/movecollectiontask.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 MOVECOLLECTIONTASK_H +#define MOVECOLLECTIONTASK_H + +#include "resourcetask.h" + +class MoveCollectionTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit MoveCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~MoveCollectionTask(); + +private slots: + void onExamineDone( KJob *job ); + void onRenameDone( KJob *job ); + void onSubscribeDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void doRename( KIMAP::Session *session ); + QString mailBoxForCollections( const Akonadi::Collection &parent, const Akonadi::Collection &child ) const; + + Akonadi::Collection m_collection; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/moveitemstask.cpp b/kdepim-runtime/resources/imap/moveitemstask.cpp new file mode 100644 index 00000000..0cf4deda --- /dev/null +++ b/kdepim-runtime/resources/imap/moveitemstask.cpp @@ -0,0 +1,326 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "moveitemstask.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "imapflags.h" +#include "uidnextattribute.h" + +MoveItemsTask::MoveItemsTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( DeferIfNoSession, resource, parent ) +{ + +} + +MoveItemsTask::~MoveItemsTask() +{ +} + +void MoveItemsTask::doStart( KIMAP::Session *session ) +{ + if ( item().remoteId().isEmpty() ) { + kWarning() << "Failed: messages has no rid"; + emitError( i18n( "Cannot move message, it does not exist on the server." ) ); + changeProcessed(); + return; + } + + if ( sourceCollection().remoteId().isEmpty() ) { + kWarning() << "Failed: source collection has no rid"; + emitError( i18n( "Cannot move message out of '%1', '%1' does not exist on the server.", + sourceCollection().name() ) ); + changeProcessed(); + return; + } + + if ( targetCollection().remoteId().isEmpty() ) { + kWarning() << "Failed: target collection has no rid"; + emitError( i18n( "Cannot move message to '%1', '%1' does not exist on the server.", + targetCollection().name() ) ); + changeProcessed(); + return; + } + + const QString oldMailBox = mailBoxForCollection( sourceCollection() ); + const QString newMailBox = mailBoxForCollection( targetCollection() ); + + if ( oldMailBox == newMailBox ) { + kDebug() << "Nothing to do, same mailbox"; + changeProcessed(); + return; + } + + if ( session->selectedMailBox() != oldMailBox ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( session ); + + select->setMailBox( oldMailBox ); + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onSelectDone(KJob*)) ); + + select->start(); + } else { + triggerCopyJob( session ); + } +} + +void MoveItemsTask::onSelectDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Select failed: " << job->errorString(); + cancelTask( job->errorString() ); + + } else { + KIMAP::SelectJob *select = static_cast( job ); + triggerCopyJob( select->session() ); + } +} + +void MoveItemsTask::triggerCopyJob( KIMAP::Session *session ) +{ + const QString newMailBox = mailBoxForCollection( targetCollection() ); + + KIMAP::ImapSet set; + + // save message id, might be needed later to search for the + // resulting message uid. + foreach ( const Akonadi::Item &item, items() ) { + try { + KMime::Message::Ptr msg = item.payload(); + const QByteArray messageId = msg->messageID()->asUnicodeString().toUtf8(); + if ( !messageId.isEmpty() ) { + m_messageIds.insert( item.id(), messageId ); + } + + set.add( item.remoteId().toLong() ); + } catch ( Akonadi::PayloadException e ) { + kWarning() << "Copy failed, payload exception " << item.id() << item.remoteId(); + cancelTask( i18n( "Failed to copy item, it has no message payload. Remote id: %1", item.remoteId() ) ); + return; + } + } + + KIMAP::CopyJob *copy = new KIMAP::CopyJob( session ); + + copy->setUidBased( true ); + copy->setSequenceSet( set ); + copy->setMailBox( newMailBox ); + + connect( copy, SIGNAL(result(KJob*)), + this, SLOT(onCopyDone(KJob*)) ); + + copy->start(); + + m_oldSet = set; +} + +void MoveItemsTask::onCopyDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << job->errorString(); + cancelTask( job->errorString() ); + + } else { + KIMAP::CopyJob *copy = static_cast( job ); + + m_newUids = imapSetToList( copy->resultingUids() ); + + // Mark the old one ready for deletion + KIMAP::StoreJob *store = new KIMAP::StoreJob( copy->session() ); + + store->setUidBased( true ); + store->setSequenceSet( m_oldSet ); + store->setFlags( QList() << ImapFlags::Deleted ); + store->setMode( KIMAP::StoreJob::AppendFlags ); + + connect( store, SIGNAL(result(KJob*)), + this, SLOT(onStoreFlagsDone(KJob*)) ); + + store->start(); + } +} + +void MoveItemsTask::onStoreFlagsDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Failed to mark message as deleted on source server: " << job->errorString(); + emitWarning( i18n( "Failed to mark the message from '%1' for deletion on the IMAP server. " + "It will reappear on next sync.", + sourceCollection().name() ) ); + } + + if ( !m_newUids.isEmpty() ) { + recordNewUid(); + } else { + // Let's go for a search to find the new UID :-) + + // We did a copy we're very likely not in the right mailbox + KIMAP::StoreJob *store = static_cast( job ); + KIMAP::SelectJob *select = new KIMAP::SelectJob( store->session() ); + select->setMailBox( mailBoxForCollection( targetCollection() ) ); + + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onPreSearchSelectDone(KJob*)) ); + + select->start(); + } +} + +void MoveItemsTask::onPreSearchSelectDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Select failed: " << job->errorString(); + cancelTask( job->errorString() ); + return; + } + + KIMAP::SelectJob *select = static_cast( job ); + KIMAP::SearchJob *search = new KIMAP::SearchJob( select->session() ); + + search->setUidBased( true ); + + if ( !m_messageIds.isEmpty() ) { + search->setSearchLogic( KIMAP::SearchJob::Or ); + + foreach ( const QByteArray &messageId, m_messageIds ) { + QByteArray header = "Message-ID "; + header+= messageId; + search->addSearchCriteria( KIMAP::SearchJob::Header, header ); + } + } else { + search->setSearchLogic( KIMAP::SearchJob::And ); + search->addSearchCriteria( KIMAP::SearchJob::New ); + + UidNextAttribute *uidNext = targetCollection().attribute(); + if ( !uidNext ) { + cancelTask( i18n( "Could not determine the UID for the newly created message on the server" ) ); + search->deleteLater(); + return; + } + KIMAP::ImapInterval interval( uidNext->uidNext() ); + + search->addSearchCriteria( KIMAP::SearchJob::Uid, interval.toImapSequence() ); + } + + connect( search, SIGNAL(result(KJob*)), + this, SLOT(onSearchDone(KJob*)) ); + + search->start(); +} + +void MoveItemsTask::onSearchDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << "Search failed: " << job->errorString(); + cancelTask( job->errorString() ); + return; + } + + KIMAP::SearchJob *search = static_cast( job ); + m_newUids = search->results(); + + recordNewUid(); +} + +void MoveItemsTask::recordNewUid() +{ + // Create the item resulting of the operation, since at that point + // the first part of the move succeeded + QList oldUids = imapSetToList( m_oldSet ); + + Akonadi::Item::List newItems; + for (int i = 0; i < oldUids.count(); ++i) { + const QString oldUid = QString::number( oldUids.at( i ) ); + Akonadi::Item item; + Q_FOREACH ( const Akonadi::Item &it, items() ) { + if ( it.remoteId() == oldUid ) { + item = it; + break; + } + } + Q_ASSERT ( item.isValid() ); + + // Update the item content with the new UID from the copy + // if we didn't manage to get a valid UID from the server, use a random RID instead + // this will make ItemSync clean up the mess during the next sync (while empty RIDs are protected as not yet existing on the server) + if ( m_newUids.count() <= i ) { + item.setRemoteId( QUuid::createUuid() ); + } else { + item.setRemoteId( QString::number( m_newUids.at( i ) ) ); + } + newItems << item; + } + + changesCommitted( newItems ); + + Akonadi::Collection c = targetCollection(); + + // Get the current uid next value and store it + UidNextAttribute *uidAttr = 0; + int oldNextUid = 0; + if ( c.hasAttribute( "uidnext" ) ) { + uidAttr = static_cast( c.attribute( "uidnext" ) ); + oldNextUid = uidAttr->uidNext(); + } + + // If the uid we just got back is the expected next one of the box + // then update the property to the probable next uid to keep the cache in sync. + // If not something happened in our back, so we don't update and a refetch will + // happen at some point. + if ( !m_newUids.isEmpty() && m_newUids.last() == oldNextUid ) { + if ( uidAttr == 0 ) { + uidAttr = new UidNextAttribute( m_newUids.last() + 1 ); + c.addAttribute( uidAttr ); + } else { + uidAttr->setUidNext( m_newUids.last() + 1 ); + } + + applyCollectionChanges( c ); + } +} + +QList< qint64 > MoveItemsTask::imapSetToList(const KIMAP::ImapSet& set) +{ + QList list; + foreach ( const KIMAP::ImapInterval &interval, set.intervals() ) { + for (qint64 i = interval.begin(); i <= interval.end(); ++i) { + list << i; + } + } + + return list; +} + + + + diff --git a/kdepim-runtime/resources/imap/moveitemstask.h b/kdepim-runtime/resources/imap/moveitemstask.h new file mode 100644 index 00000000..bcb7e94f --- /dev/null +++ b/kdepim-runtime/resources/imap/moveitemstask.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 MOVEITEMSTASK_H +#define MOVEITEMSTASK_H + +#include "resourcetask.h" + +#include + +class MoveItemsTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit MoveItemsTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~MoveItemsTask(); + +private slots: + void onSelectDone( KJob *job ); + void onCopyDone( KJob *job ); + void onStoreFlagsDone( KJob *job ); + + void onPreSearchSelectDone( KJob *job ); + void onSearchDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void triggerCopyJob( KIMAP::Session *session ); + void recordNewUid(); + QList imapSetToList( const KIMAP::ImapSet &set ); + + KIMAP::ImapSet m_oldSet; + QList m_newUids; + QMap m_messageIds; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/noinferiorsattribute.cpp b/kdepim-runtime/resources/imap/noinferiorsattribute.cpp new file mode 100644 index 00000000..c506de3e --- /dev/null +++ b/kdepim-runtime/resources/imap/noinferiorsattribute.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2012 Christian Mollekopf + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "noinferiorsattribute.h" + +#include + +#include + +NoInferiorsAttribute::NoInferiorsAttribute( bool noInferiors ) +: mNoInferiors( noInferiors ) +{ +} + +void NoInferiorsAttribute::setNoInferiors( bool noInferiors ) +{ + mNoInferiors = noInferiors; +} + +bool NoInferiorsAttribute::noInferiors() const +{ + return mNoInferiors; +} + +QByteArray NoInferiorsAttribute::type() const +{ + return "noinferiors"; +} + +Akonadi::Attribute* NoInferiorsAttribute::clone() const +{ + return new NoInferiorsAttribute( mNoInferiors ); +} + +QByteArray NoInferiorsAttribute::serialized() const +{ + return mNoInferiors ? QByteArray::number( 1 ) : QByteArray::number( 0 ); +} + +void NoInferiorsAttribute::deserialize( const QByteArray &data ) +{ + mNoInferiors = ( data.toInt() == 0 ) ? false : true; +} diff --git a/kdepim-runtime/resources/imap/noinferiorsattribute.h b/kdepim-runtime/resources/imap/noinferiorsattribute.h new file mode 100644 index 00000000..5e30684f --- /dev/null +++ b/kdepim-runtime/resources/imap/noinferiorsattribute.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2012 Christian Mollekopf + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef NOINFERIORSATTRIBUTE_H +#define NOINFERIORSATTRIBUTE_H + +#include + +class NoInferiorsAttribute : public Akonadi::Attribute +{ +public: + explicit NoInferiorsAttribute( bool noInferiors = false ); + void setNoInferiors( bool noInferiors ); + bool noInferiors() const; + virtual QByteArray type() const; + virtual Attribute* clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + +private: + bool mNoInferiors; +}; + +#endif // NOINFERIORSATTRIBUTE_H diff --git a/kdepim-runtime/resources/imap/noselectattribute.cpp b/kdepim-runtime/resources/imap/noselectattribute.cpp new file mode 100644 index 00000000..849032b5 --- /dev/null +++ b/kdepim-runtime/resources/imap/noselectattribute.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 "noselectattribute.h" + +#include + +#include + +NoSelectAttribute::NoSelectAttribute( bool noSelect ) + : mNoSelect( noSelect ) +{ +} + +void NoSelectAttribute::setNoSelect( bool noSelect ) +{ + mNoSelect = noSelect; +} + +bool NoSelectAttribute::noSelect() const +{ + return mNoSelect; +} + +QByteArray NoSelectAttribute::type() const +{ + return "noselect"; +} + +Akonadi::Attribute* NoSelectAttribute::clone() const +{ + return new NoSelectAttribute( mNoSelect ); +} + +QByteArray NoSelectAttribute::serialized() const +{ + return mNoSelect ? QByteArray::number( 1 ) : QByteArray::number( 0 ); +} + +void NoSelectAttribute::deserialize( const QByteArray &data ) +{ + mNoSelect = ( data.toInt() == 0 ) ? false : true; +} diff --git a/kdepim-runtime/resources/imap/noselectattribute.h b/kdepim-runtime/resources/imap/noselectattribute.h new file mode 100644 index 00000000..fc217ef7 --- /dev/null +++ b/kdepim-runtime/resources/imap/noselectattribute.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 NOSELECTATTRIBUTE_H +#define NOSELECTATTRIBUTE_H + +#include + +class NoSelectAttribute : public Akonadi::Attribute +{ +public: + explicit NoSelectAttribute( bool noSelect = false ); + void setNoSelect( bool noSelect ); + bool noSelect() const; + virtual QByteArray type() const; + virtual Attribute* clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + +private: + bool mNoSelect; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/passwordrequesterinterface.cpp b/kdepim-runtime/resources/imap/passwordrequesterinterface.cpp new file mode 100644 index 00000000..6d50a5dd --- /dev/null +++ b/kdepim-runtime/resources/imap/passwordrequesterinterface.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "passwordrequesterinterface.h" + +PasswordRequesterInterface::PasswordRequesterInterface( QObject *parent ) + : QObject( parent ) +{ + +} + +void PasswordRequesterInterface::cancelPasswordRequests() +{ + +} diff --git a/kdepim-runtime/resources/imap/passwordrequesterinterface.h b/kdepim-runtime/resources/imap/passwordrequesterinterface.h new file mode 100644 index 00000000..5aad99ca --- /dev/null +++ b/kdepim-runtime/resources/imap/passwordrequesterinterface.h @@ -0,0 +1,57 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 PASSWORDREQUESTERINTERFACE_H +#define PASSWORDREQUESTERINTERFACE_H + +#include + +class PasswordRequesterInterface : public QObject +{ + Q_OBJECT + Q_ENUMS( ResultType RequestType ) + +public: + enum ResultType { + PasswordRetrieved, + ReconnectNeeded, + UserRejected, + EmptyPasswordEntered + }; + + enum RequestType { + StandardRequest, + WrongPasswordRequest + }; + +protected: + PasswordRequesterInterface( QObject *parent = 0 ); + +public: + virtual void requestPassword( RequestType request = StandardRequest, + const QString &serverError = QString() ) = 0; + virtual void cancelPasswordRequests(); + +signals: + void done( int resultType, const QString &password = QString() ); +}; + +#endif diff --git a/kdepim-runtime/resources/imap/removecollectionrecursivetask.cpp b/kdepim-runtime/resources/imap/removecollectionrecursivetask.cpp new file mode 100644 index 00000000..d53a2238 --- /dev/null +++ b/kdepim-runtime/resources/imap/removecollectionrecursivetask.cpp @@ -0,0 +1,170 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: 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. +*/ + +#include "removecollectionrecursivetask.h" + +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE( KIMAP::DeleteJob* ) + +RemoveCollectionRecursiveTask::RemoveCollectionRecursiveTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( CancelIfNoSession, resource, parent ), + mSession( 0 ), mFolderFound( false ) +{ +} + +RemoveCollectionRecursiveTask::~RemoveCollectionRecursiveTask() +{ +} + +void RemoveCollectionRecursiveTask::doStart( KIMAP::Session *session ) +{ + mSession = session; + + mFolderFound = false; + KIMAP::ListJob *listJob = new KIMAP::ListJob( session ); + listJob->setIncludeUnsubscribed( !isSubscriptionEnabled() ); + listJob->setQueriedNamespaces( serverNamespaces() ); + connect( listJob, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onMailBoxesReceived(QList,QList >)) ); + connect( listJob, SIGNAL(result(KJob*)), SLOT(onJobDone(KJob*)) ); + listJob->start(); +} + +void RemoveCollectionRecursiveTask::onMailBoxesReceived( const QList< KIMAP::MailBoxDescriptor > &descriptors, + const QList< QList >& ) +{ + const QString mailBox = mailBoxForCollection( collection() ); + + // We have to delete the deepest-nested folders first, so + // we use a map here that has the level of nesting as key. + QMultiMap foldersToDelete; + + for ( int i = 0; i < descriptors.size(); ++i ) { + const KIMAP::MailBoxDescriptor descriptor = descriptors[ i ]; + + if ( descriptor.name == mailBox || descriptor.name.startsWith( mailBox + descriptor.separator ) ) { // a sub folder to delete + const QStringList pathParts = descriptor.name.split( descriptor.separator ); + foldersToDelete.insert( pathParts.count(), descriptor ); + } + } + + if ( foldersToDelete.isEmpty() ) { + return; + } + + mFolderFound = true; + + // Now start the actual deletion work + mFolderIterator.reset( new QMapIterator( foldersToDelete ) ); + mFolderIterator->toBack(); // we start with largest nesting value first + + deleteNextMailbox(); +} + +void RemoveCollectionRecursiveTask::deleteNextMailbox() +{ + if ( !mFolderIterator->hasPrevious() ) { + changeProcessed(); // finish the job + return; + } + + mFolderIterator->previous(); + const KIMAP::MailBoxDescriptor &descriptor = mFolderIterator->value(); + kDebug() << descriptor.name; + + // first select the mailbox + KIMAP::SelectJob *selectJob = new KIMAP::SelectJob( mSession ); + selectJob->setMailBox( descriptor.name ); + connect( selectJob, SIGNAL(result(KJob*)), SLOT(onJobDone(KJob*)) ); + selectJob->start(); + + // mark all items as deleted + // This step shouldn't be required, but apparently some servers don't allow deleting, non empty mailboxes (although they should). + KIMAP::ImapSet allItems; + allItems.add( KIMAP::ImapInterval( 1, 0 ) ); // means 1:* + KIMAP::StoreJob *storeJob = new KIMAP::StoreJob( mSession ); + storeJob->setSequenceSet( allItems ); + storeJob->setFlags( KIMAP::MessageFlags() << Akonadi::MessageFlags::Deleted ); + storeJob->setMode( KIMAP::StoreJob::AppendFlags ); + // The result is explicitly ignored, since this can fail in the case of an empty folder + storeJob->start(); + + // Some IMAP servers don't allow deleting an opened mailbox, so make sure + // it's not opened (https://bugs.kde.org/show_bug.cgi?id=324932). CLOSE will + // also trigger EXPUNGE to take care of the messages deleted above + KIMAP::CloseJob *closeJob = new KIMAP::CloseJob( mSession ); + closeJob->setProperty( "folderDescriptor", descriptor.name ); + connect( closeJob, SIGNAL(result(KJob*)), SLOT(onCloseJobDone(KJob*)) ); + closeJob->start(); +} + +void RemoveCollectionRecursiveTask::onCloseJobDone( KJob* job ) +{ + if ( job->error() ) { + changeProcessed(); + kDebug( 5327 ) << "Failed to close the folder, resync the folder tree"; + emitWarning( i18n( "Failed to delete the folder, restoring folder list." ) ); + synchronizeCollectionTree(); + } else { + KIMAP::DeleteJob *deleteJob = new KIMAP::DeleteJob( mSession ); + deleteJob->setMailBox( job->property( "folderDescriptor" ).toString() ); + connect( deleteJob, SIGNAL(result(KJob*)), SLOT(onDeleteJobDone(KJob*)) ); + deleteJob->start(); + } +} + + +void RemoveCollectionRecursiveTask::onDeleteJobDone( KJob* job ) +{ + if ( job->error() ) { + changeProcessed(); + + kDebug( 5327 ) << "Failed to delete the folder, resync the folder tree"; + emitWarning( i18n( "Failed to delete the folder, restoring folder list." ) ); + synchronizeCollectionTree(); + } else { + deleteNextMailbox(); + } +} + +void RemoveCollectionRecursiveTask::onJobDone( KJob* job ) +{ + if ( job->error() ) { + changeProcessed(); + + kDebug( 5327 ) << "Failed to delete the folder, resync the folder tree"; + emitWarning( i18n( "Failed to delete the folder, restoring folder list." ) ); + synchronizeCollectionTree(); + } else if ( !mFolderFound ) { + changeProcessed(); + kDebug( 5327 ) << "Failed to find the folder to be deleted, resync the folder tree"; + emitWarning( i18n( "Failed to find the folder to be deleted, restoring folder list." ) ); + synchronizeCollectionTree(); + } +} + diff --git a/kdepim-runtime/resources/imap/removecollectionrecursivetask.h b/kdepim-runtime/resources/imap/removecollectionrecursivetask.h new file mode 100644 index 00000000..d759c8ca --- /dev/null +++ b/kdepim-runtime/resources/imap/removecollectionrecursivetask.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: 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. +*/ + +#ifndef REMOVECOLLECTIONRECURSIVETASK_H +#define REMOVECOLLECTIONRECURSIVETASK_H + +#include + +#include "resourcetask.h" + +class RemoveCollectionRecursiveTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit RemoveCollectionRecursiveTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~RemoveCollectionRecursiveTask(); + +private slots: + void onMailBoxesReceived( const QList &descriptors, + const QList< QList > &flags ); + void onCloseJobDone( KJob *job ); + void onDeleteJobDone( KJob *job ); + void onJobDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void deleteNextMailbox(); + + KIMAP::Session *mSession; + bool mFolderFound; + + QScopedPointer< QMapIterator > mFolderIterator; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/resourcestate.cpp b/kdepim-runtime/resources/imap/resourcestate.cpp new file mode 100644 index 00000000..d2ed2d22 --- /dev/null +++ b/kdepim-runtime/resources/imap/resourcestate.cpp @@ -0,0 +1,364 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "resourcestate.h" + +#include "imapaccount.h" +#include "imapresource.h" +#include "sessionpool.h" +#include "settings.h" +#include "noselectattribute.h" +#include "timestampattribute.h" + +#include +#include + +ResourceState::ResourceState( ImapResourceBase *resource, const TaskArguments &args ) + : m_resource( resource ), + m_arguments( args ) +{ + +} + +ResourceState::~ResourceState() +{ + +} + +QString ResourceState::userName() const +{ + return m_resource->m_pool->account()->userName(); +} + +QString ResourceState::resourceName() const +{ + return m_resource->name(); +} + +QStringList ResourceState::serverCapabilities() const +{ + return m_resource->m_pool->serverCapabilities(); +} + +QList ResourceState::serverNamespaces() const +{ + return m_resource->m_pool->serverNamespaces(); +} + +bool ResourceState::isAutomaticExpungeEnabled() const +{ + return m_resource->settings()->automaticExpungeEnabled(); +} + +bool ResourceState::isSubscriptionEnabled() const +{ + return m_resource->settings()->subscriptionEnabled(); +} + +bool ResourceState::isDisconnectedModeEnabled() const +{ + return m_resource->settings()->disconnectedModeEnabled(); +} + +int ResourceState::intervalCheckTime() const +{ + if ( m_resource->settings()->intervalCheckEnabled() ) + return m_resource->settings()->intervalCheckTime(); + else + return -1; // -1 for never +} + +Akonadi::Collection ResourceState::collection() const +{ + return m_arguments.collection; +} + +Akonadi::Item ResourceState::item() const +{ + if (m_arguments.items.count() > 1) { + kWarning() << "Called item() while state holds multiple items!"; + } + + return m_arguments.items.first(); +} + +Akonadi::Item::List ResourceState::items() const +{ + return m_arguments.items; +} + +Akonadi::Collection ResourceState::parentCollection() const +{ + return m_arguments.parentCollection; +} + +Akonadi::Collection ResourceState::sourceCollection() const +{ + return m_arguments.sourceCollection; +} + +Akonadi::Collection ResourceState::targetCollection() const +{ + return m_arguments.targetCollection; +} + +QSet ResourceState::parts() const +{ + return m_arguments.parts; +} + +QSet ResourceState::addedFlags() const +{ + return m_arguments.addedFlags; +} + +QSet ResourceState::removedFlags() const +{ + return m_arguments.removedFlags; +} + +QString ResourceState::rootRemoteId() const +{ + return m_resource->settings()->rootRemoteId(); +} + +void ResourceState::setIdleCollection( const Akonadi::Collection &collection ) +{ + QStringList ridPath; + + Akonadi::Collection curCol = collection; + while ( curCol != Akonadi::Collection::root() && !curCol.remoteId().isEmpty() ) { + ridPath.append( curCol.remoteId() ); + curCol = curCol.parentCollection(); + } + + m_resource->settings()->setIdleRidPath( ridPath ); + m_resource->settings()->writeConfig(); +} + +void ResourceState::applyCollectionChanges( const Akonadi::Collection &collection ) +{ + m_resource->modifyCollection(collection); +} + +void ResourceState::collectionAttributesRetrieved( const Akonadi::Collection &collection ) +{ + m_resource->collectionAttributesRetrieved( collection ); +} + +void ResourceState::itemRetrieved( const Akonadi::Item &item ) +{ + m_resource->itemRetrieved( item ); +} + +void ResourceState::itemsRetrieved( const Akonadi::Item::List &items ) +{ + m_resource->itemsRetrieved( items ); +} + +void ResourceState::itemsRetrievedIncremental( const Akonadi::Item::List &changed, const Akonadi::Item::List &removed ) +{ + m_resource->itemsRetrievedIncremental( changed, removed ); +} + +void ResourceState::itemsRetrievalDone() +{ + m_resource->itemsRetrievalDone(); + emitPercent(100); +} + +void ResourceState::setTotalItems(int items) +{ + m_resource->setTotalItems(items); +} + +void ResourceState::itemChangeCommitted( const Akonadi::Item &item ) +{ + m_resource->changeCommitted( item ); +} + +void ResourceState::itemsChangesCommitted(const Akonadi::Item::List& items) +{ + m_resource->changesCommitted( items ); +} + +void ResourceState::collectionsRetrieved( const Akonadi::Collection::List &collections ) +{ + m_resource->collectionsRetrieved( collections ); + + if ( m_resource->settings()->retrieveMetadataOnFolderListing() ) { + QStringList oldMailBoxes = m_resource->settings()->knownMailBoxes(); + QStringList newMailBoxes; + + foreach ( const Akonadi::Collection &c, collections ) { + const QString mailBox = mailBoxForCollection( c ); + + if ( !c.hasAttribute() + && !oldMailBoxes.contains( mailBox ) ) { + m_resource->synchronizeCollectionAttributes(c.id()); + } + + newMailBoxes << mailBox; + } + + m_resource->settings()->setKnownMailBoxes( newMailBoxes ); + } + + m_resource->startIdleIfNeeded(); +} + +void ResourceState::collectionChangeCommitted( const Akonadi::Collection &collection ) +{ + m_resource->changeCommitted( collection ); +} + +void ResourceState::changeProcessed() +{ + m_resource->changeProcessed(); +} + +void ResourceState::searchFinished( const QVector &result, bool isRid ) +{ + m_resource->searchFinished( result, isRid ? Akonadi::AgentSearchInterface::Rid : + Akonadi::AgentSearchInterface::Uid ); +} + + +void ResourceState::cancelTask( const QString &errorString ) +{ + m_resource->cancelTask( errorString ); + + // We get here in case of some error during the task. In such a case that can have + // been provoked by the fact that some of the metadata we had was wrong (most notably + // ACL and we took a wrong decision. + // So reset the timestamp of all the collections involved in the task, and also + // remove them from the "known mailboxes" list so that we get a chance to refresh + // the metadata about them ASAP. + + Akonadi::Collection::List collections; + collections << m_arguments.collection + << m_arguments.parentCollection + << m_arguments.sourceCollection + << m_arguments.targetCollection; + + foreach ( const Akonadi::Item &item, m_arguments.items ) { + if ( item.isValid() && item.parentCollection().isValid() ) { + collections << item.parentCollection(); + } + } + + if ( m_arguments.collection.isValid() && m_arguments.collection.parentCollection().isValid() ) { + collections << m_arguments.collection.parentCollection(); + } + + const QStringList oldMailBoxes = m_resource->settings()->knownMailBoxes(); + QStringList newMailBoxes = oldMailBoxes; + + foreach ( const Akonadi::Collection &collection, collections ) { + if ( collection.isValid() + && collection.hasAttribute() ) { + + Akonadi::Collection c = collection; + c.removeAttribute(); + + m_resource->modifyCollection( c ); + newMailBoxes.removeAll( mailBoxForCollection( c ) ); + } + } + + if ( oldMailBoxes.size()!=newMailBoxes.size() ) { + m_resource->settings()->setKnownMailBoxes( newMailBoxes ); + } +} + +void ResourceState::deferTask() +{ + m_resource->deferTask(); +} + +void ResourceState::restartItemRetrieval(Akonadi::Collection::Id col) +{ + //This ensures the collection fetch job is rerun (it isn't when using deferTask) + //The task will be appended + //TODO: deferTask should rerun the collectionfetchjob + m_resource->synchronizeCollection(col); + cancelTask("Restarting item retrieval."); +} + +void ResourceState::taskDone() +{ + m_resource->taskDone(); +} + +void ResourceState::emitError( const QString &message ) +{ + emit m_resource->error( message ); +} + +void ResourceState::emitWarning( const QString &message ) +{ + emit m_resource->warning( message ); +} + +void ResourceState::emitPercent( int percent ) +{ + emit m_resource->percent( percent ); +} + +void ResourceState::synchronizeCollection(Akonadi::Entity::Id id) +{ + m_resource->synchronizeCollection(id); +} + +void ResourceState::synchronizeCollectionTree() +{ + m_resource->synchronizeCollectionTree(); +} + +void ResourceState::scheduleConnectionAttempt() +{ + m_resource->scheduleConnectionAttempt(); +} + +QChar ResourceState::separatorCharacter() const +{ + return m_resource->separatorCharacter(); +} + +void ResourceState::setSeparatorCharacter( const QChar &separator ) +{ + m_resource->setSeparatorCharacter( separator ); +} + +void ResourceState::showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ) +{ + KMessageBox::information( 0, message, title, dontShowAgainName ); +} + +int ResourceState::batchSize() const +{ + return m_resource->itemSyncBatchSize(); +} + +MessageHelper::Ptr ResourceState::messageHelper() const +{ + return MessageHelper::Ptr(new MessageHelper()); +} diff --git a/kdepim-runtime/resources/imap/resourcestate.h b/kdepim-runtime/resources/imap/resourcestate.h new file mode 100644 index 00000000..4a61a111 --- /dev/null +++ b/kdepim-runtime/resources/imap/resourcestate.h @@ -0,0 +1,137 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RESOURCESTATE_H +#define RESOURCESTATE_H + +#include "resourcestateinterface.h" +#include "messagehelper.h" + +class ImapResourceBase; + +struct TaskArguments { + TaskArguments(){} + TaskArguments(const Akonadi::Item &_item): items(Akonadi::Item::List() << _item) {} + TaskArguments(const Akonadi::Item &_item, const Akonadi::Collection &_collection): collection(_collection), items(Akonadi::Item::List() << _item) {} + TaskArguments(const Akonadi::Item &_item, const QSet &_parts): items(Akonadi::Item::List() << _item), parts(_parts) {} + TaskArguments(const Akonadi::Item::List &_items): items(_items) {} + TaskArguments(const Akonadi::Item::List &_items, const QSet &_addedFlags, const QSet &_removedFlags): items(_items), addedFlags(_addedFlags), removedFlags(_removedFlags) {} + TaskArguments(const Akonadi::Item::List &_items, const Akonadi::Collection &_sourceCollection, const Akonadi::Collection &_targetCollection): items(_items), sourceCollection(_sourceCollection), targetCollection(_targetCollection){} + TaskArguments(const Akonadi::Collection &_collection): collection(_collection){} + TaskArguments(const Akonadi::Collection &_collection, const Akonadi::Collection &_parentCollection): collection(_collection), parentCollection(_parentCollection){} + TaskArguments(const Akonadi::Collection &_collection, const Akonadi::Collection &_sourceCollection, const Akonadi::Collection &_targetCollection): collection(_collection), sourceCollection(_sourceCollection), targetCollection(_targetCollection){} + TaskArguments(const Akonadi::Collection &_collection, const QSet &_parts): collection(_collection), parts(_parts){} + Akonadi::Collection collection; + Akonadi::Item::List items; + Akonadi::Collection parentCollection; //only used as parent of a collection + Akonadi::Collection sourceCollection; + Akonadi::Collection targetCollection; + QSet parts; + QSet addedFlags; + QSet removedFlags; +}; + +class ResourceState : public ResourceStateInterface +{ +public: + explicit ResourceState( ImapResourceBase *resource, const TaskArguments &arguments ); + +public: + ~ResourceState(); + + virtual QString userName() const; + virtual QString resourceName() const; + virtual QStringList serverCapabilities() const; + virtual QList serverNamespaces() const; + + virtual bool isAutomaticExpungeEnabled() const; + virtual bool isSubscriptionEnabled() const; + virtual bool isDisconnectedModeEnabled() const; + virtual int intervalCheckTime() const; + + virtual Akonadi::Collection collection() const; + virtual Akonadi::Item item() const; + virtual Akonadi::Item::List items() const; + + virtual Akonadi::Collection parentCollection() const; + + virtual Akonadi::Collection sourceCollection() const; + virtual Akonadi::Collection targetCollection() const; + + virtual QSet parts() const; + virtual QSet addedFlags() const; + virtual QSet removedFlags() const; + + virtual QString rootRemoteId() const; + + virtual void setIdleCollection( const Akonadi::Collection &collection ); + virtual void applyCollectionChanges( const Akonadi::Collection &collection ); + + virtual void collectionAttributesRetrieved( const Akonadi::Collection &collection ); + + virtual void itemRetrieved( const Akonadi::Item &item ); + + virtual void itemsRetrieved( const Akonadi::Item::List &items ); + virtual void itemsRetrievedIncremental( const Akonadi::Item::List &changed, const Akonadi::Item::List &removed ); + virtual void itemsRetrievalDone(); + + virtual void setTotalItems(int); + + virtual void itemChangeCommitted( const Akonadi::Item &item ); + virtual void itemsChangesCommitted(const Akonadi::Item::List& items); + + virtual void collectionsRetrieved( const Akonadi::Collection::List &collections ); + + virtual void collectionChangeCommitted( const Akonadi::Collection &collection ); + + virtual void changeProcessed(); + + virtual void searchFinished( const QVector &result, bool isRid = true ); + + virtual void cancelTask( const QString &errorString ); + virtual void deferTask(); + virtual void restartItemRetrieval(Akonadi::Collection::Id col); + virtual void taskDone(); + + virtual void emitError( const QString &message ); + virtual void emitWarning( const QString &message ); + + virtual void emitPercent( int percent ); + + virtual void synchronizeCollection(Akonadi::Collection::Id); + virtual void synchronizeCollectionTree(); + virtual void scheduleConnectionAttempt(); + + virtual QChar separatorCharacter() const; + virtual void setSeparatorCharacter( const QChar &separator ); + + virtual void showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ); + + virtual int batchSize() const; + + virtual MessageHelper::Ptr messageHelper() const; + +private: + ImapResourceBase *m_resource; + const TaskArguments m_arguments; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/resourcestateinterface.cpp b/kdepim-runtime/resources/imap/resourcestateinterface.cpp new file mode 100644 index 00000000..47846682 --- /dev/null +++ b/kdepim-runtime/resources/imap/resourcestateinterface.cpp @@ -0,0 +1,51 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "resourcestateinterface.h" + +ResourceStateInterface::~ResourceStateInterface() +{ + +} + +QString ResourceStateInterface::mailBoxForCollection( const Akonadi::Collection &collection, bool showWarnings ) +{ + if ( collection.remoteId().isEmpty() ) { //This should never happen, investigate why a collection without remoteId made it this far + if ( showWarnings ) + kWarning() << "Got incomplete ancestor chain due to empty remoteId:" << collection; + return QString(); + } + + if ( collection.parentCollection() == Akonadi::Collection::root() ) { + /*if ( showWarnings ) + kWarning( collection.remoteId() != rootRemoteId() ) << "RID mismatch, is " << collection.remoteId() << " expected " << rootRemoteId(); + */ + return QLatin1String( "" ); // see below, this intentionally not just QString()! + } + const QString parentMailbox = mailBoxForCollection( collection.parentCollection() ); + if ( parentMailbox.isNull() ) // invalid, != isEmpty() here! + return QString(); + + const QString mailbox = parentMailbox + collection.remoteId(); + if ( parentMailbox.isEmpty() ) + return mailbox.mid( 1 ); // strip of the separator on top-level mailboxes + return mailbox; +} diff --git a/kdepim-runtime/resources/imap/resourcestateinterface.h b/kdepim-runtime/resources/imap/resourcestateinterface.h new file mode 100644 index 00000000..704038e2 --- /dev/null +++ b/kdepim-runtime/resources/imap/resourcestateinterface.h @@ -0,0 +1,115 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RESOURCESTATEINTERFACE_H +#define RESOURCESTATEINTERFACE_H + +#include + +#include +#include + +#include + +#include +#include "messagehelper.h" + +class ResourceStateInterface +{ +public: + typedef boost::shared_ptr Ptr; + + virtual ~ResourceStateInterface(); + + virtual QString userName() const = 0; + virtual QString resourceName() const = 0; + virtual QStringList serverCapabilities() const = 0; + virtual QList serverNamespaces() const = 0; + + virtual bool isAutomaticExpungeEnabled() const = 0; + virtual bool isSubscriptionEnabled() const = 0; + virtual bool isDisconnectedModeEnabled() const = 0; + virtual int intervalCheckTime() const = 0; + + virtual Akonadi::Collection collection() const = 0; + virtual Akonadi::Item item() const = 0; + virtual Akonadi::Item::List items() const = 0; + + virtual Akonadi::Collection parentCollection() const = 0; + + virtual Akonadi::Collection sourceCollection() const = 0; + virtual Akonadi::Collection targetCollection() const = 0; + + virtual QSet parts() const = 0; + virtual QSet addedFlags() const = 0; + virtual QSet removedFlags() const = 0; + + virtual QString rootRemoteId() const = 0; + static QString mailBoxForCollection( const Akonadi::Collection &collection, bool showWarnings = true ); + + virtual void setIdleCollection( const Akonadi::Collection &collection ) = 0; + virtual void applyCollectionChanges( const Akonadi::Collection &collection ) = 0; + + virtual void itemRetrieved( const Akonadi::Item &item ) = 0; + + virtual void itemsRetrieved( const Akonadi::Item::List &items ) = 0; + virtual void itemsRetrievedIncremental( const Akonadi::Item::List &changed, + const Akonadi::Item::List &removed ) = 0; + virtual void itemsRetrievalDone() = 0; + + virtual void setTotalItems(int) = 0; + + virtual void itemChangeCommitted( const Akonadi::Item &item ) = 0; + virtual void itemsChangesCommitted( const Akonadi::Item::List &items ) = 0; + + virtual void collectionsRetrieved( const Akonadi::Collection::List &collections ) = 0; + virtual void collectionAttributesRetrieved( const Akonadi::Collection &collection ) = 0; + + virtual void collectionChangeCommitted( const Akonadi::Collection &collection ) = 0; + + virtual void changeProcessed() = 0; + + virtual void searchFinished( const QVector &result, bool isRid = true ) = 0; + + virtual void cancelTask( const QString &errorString ) = 0; + virtual void deferTask() = 0; + virtual void restartItemRetrieval(Akonadi::Collection::Id col) = 0; + virtual void taskDone() = 0; + + virtual void emitError( const QString &message ) = 0; + virtual void emitWarning( const QString &message ) = 0; + virtual void emitPercent( int percent ) = 0; + + virtual void synchronizeCollectionTree() = 0; + virtual void scheduleConnectionAttempt() = 0; + + virtual QChar separatorCharacter() const = 0; + virtual void setSeparatorCharacter( const QChar &separator ) = 0; + + virtual void showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ) = 0; + + virtual int batchSize() const = 0; + + virtual MessageHelper::Ptr messageHelper() const = 0; + +}; + +#endif diff --git a/kdepim-runtime/resources/imap/resourcetask.cpp b/kdepim-runtime/resources/imap/resourcetask.cpp new file mode 100644 index 00000000..70564350 --- /dev/null +++ b/kdepim-runtime/resources/imap/resourcetask.cpp @@ -0,0 +1,539 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "resourcetask.h" + +#include + +#include + +#include "collectionflagsattribute.h" +#include "imapflags.h" +#include "sessionpool.h" +#include "resourcestateinterface.h" + +ResourceTask::ResourceTask( ActionIfNoSession action, ResourceStateInterface::Ptr resource, QObject *parent ) + : QObject( parent ), + m_pool( 0 ), + m_sessionRequestId( 0 ), + m_session( 0 ), + m_actionIfNoSession( action ), + m_resource( resource ), + mCancelled( false ) +{ + +} + +ResourceTask::~ResourceTask() +{ + if ( m_pool ) { + if ( m_sessionRequestId ) + m_pool->cancelSessionRequest( m_sessionRequestId ); + if ( m_session ) + m_pool->releaseSession( m_session ); + } +} + +void ResourceTask::start( SessionPool *pool ) +{ + m_pool = pool; + connect( m_pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)), + this, SLOT(onSessionRequested(qint64,KIMAP::Session*,int,QString)) ); + + m_sessionRequestId = m_pool->requestSession(); + + if ( m_sessionRequestId <= 0 ) { + m_sessionRequestId = 0; + + switch ( m_actionIfNoSession ) { + case CancelIfNoSession: + kDebug() << "Cancelling this request. Probably there is no connection."; + m_resource->cancelTask( i18n( "There is currently no connection to the IMAP server." ) ); + break; + + case DeferIfNoSession: + kDebug() << "Defering this request. Probably there is no connection."; + m_resource->deferTask(); + break; + } + + // In this case we were likely disconnect, try to get the resource online + m_resource->scheduleConnectionAttempt(); + deleteLater(); + } +} + +void ResourceTask::onSessionRequested( qint64 requestId, KIMAP::Session *session, + int errorCode, const QString &/*errorString*/ ) +{ + if ( requestId!=m_sessionRequestId ) { + // Not for us, ignore + return; + } + + disconnect( m_pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)), + this, SLOT(onSessionRequested(qint64,KIMAP::Session*,int,QString)) ); + m_sessionRequestId = 0; + + if ( errorCode!=SessionPool::NoError ) { + switch ( m_actionIfNoSession ) { + case CancelIfNoSession: + kDebug() << "Cancelling this request. Probably there is no more session available."; + m_resource->cancelTask( i18n( "There is currently no session to the IMAP server available." ) ); + break; + + case DeferIfNoSession: + kDebug() << "Defering this request. Probably there is no more session available."; + m_resource->deferTask(); + break; + } + + deleteLater(); + return; + } + + m_session = session; + + connect( m_pool, SIGNAL(connectionLost(KIMAP::Session*)), + this, SLOT(onConnectionLost(KIMAP::Session*)) ); + connect( m_pool, SIGNAL(disconnectDone()), + this, SLOT(onPoolDisconnect()) ); + + doStart( m_session ); +} + +void ResourceTask::onConnectionLost( KIMAP::Session *session ) +{ + if ( session == m_session ) { + // Our session becomes invalid, so get rid of + // the pointer, we don't need to release it once the + // task is done + m_session = 0; + cancelTask( i18n( "Connection lost" ) ); + } +} + +void ResourceTask::onPoolDisconnect() +{ + // All the sessions in the pool we used changed, + // so get rid of the pointer, we don't need to + // release our session anymore + m_pool = 0; + + cancelTask( i18n( "Connection lost" ) ); +} + +QString ResourceTask::userName() const +{ + return m_resource->userName(); +} + +QString ResourceTask::resourceName() const +{ + return m_resource->resourceName(); +} + +QStringList ResourceTask::serverCapabilities() const +{ + return m_resource->serverCapabilities(); +} + +QList ResourceTask::serverNamespaces() const +{ + return m_resource->serverNamespaces(); +} + +bool ResourceTask::isAutomaticExpungeEnabled() const +{ + return m_resource->isAutomaticExpungeEnabled(); +} + +bool ResourceTask::isSubscriptionEnabled() const +{ + return m_resource->isSubscriptionEnabled(); +} + +bool ResourceTask::isDisconnectedModeEnabled() const +{ + return m_resource->isDisconnectedModeEnabled(); +} + +int ResourceTask::intervalCheckTime() const +{ + return m_resource->intervalCheckTime(); +} + +static Akonadi::Collection detatchCollection(const Akonadi::Collection &collection) +{ + //HACK: Attributes are accessed via a const function, and the implicitly shared private pointer thus doesn't detach. + //We force a detach to avoid surprises. (RetrieveItemsTask used to write back the collection changes, even though the task was canceled) + //Once this is fixed this function can go away. + Akonadi::Collection col = collection; + col.setId(col.id()); + return col; +} + +Akonadi::Collection ResourceTask::collection() const +{ + return detatchCollection(m_resource->collection()); +} + +Akonadi::Item ResourceTask::item() const +{ + return m_resource->item(); +} + +Akonadi::Item::List ResourceTask::items() const +{ + return m_resource->items(); +} + +Akonadi::Collection ResourceTask::parentCollection() const +{ + return detatchCollection(m_resource->parentCollection()); +} + +Akonadi::Collection ResourceTask::sourceCollection() const +{ + return detatchCollection(m_resource->sourceCollection()); +} + +Akonadi::Collection ResourceTask::targetCollection() const +{ + return detatchCollection(m_resource->targetCollection()); +} + +QSet ResourceTask::parts() const +{ + return m_resource->parts(); +} + +QSet< QByteArray > ResourceTask::addedFlags() const +{ + return m_resource->addedFlags(); +} + +QSet< QByteArray > ResourceTask::removedFlags() const +{ + return m_resource->removedFlags(); +} + +QString ResourceTask::rootRemoteId() const +{ + return m_resource->rootRemoteId(); +} + +QString ResourceTask::mailBoxForCollection( const Akonadi::Collection &collection ) const +{ + return m_resource->mailBoxForCollection( collection ); +} + +void ResourceTask::setIdleCollection( const Akonadi::Collection &collection ) +{ + if (!mCancelled) { + m_resource->setIdleCollection( collection ); + } +} + +void ResourceTask::applyCollectionChanges( const Akonadi::Collection &collection ) +{ + if (!mCancelled) { + m_resource->applyCollectionChanges( collection ); + } +} + +void ResourceTask::itemRetrieved( const Akonadi::Item &item ) +{ + if (!mCancelled) { + m_resource->itemRetrieved( item ); + emitPercent(100); + } + deleteLater(); +} + +void ResourceTask::itemsRetrieved( const Akonadi::Item::List &items ) +{ + if (!mCancelled) { + m_resource->itemsRetrieved( items ); + } +} + +void ResourceTask::itemsRetrievedIncremental( const Akonadi::Item::List &changed, + const Akonadi::Item::List &removed ) +{ + if (!mCancelled) { + m_resource->itemsRetrievedIncremental( changed, removed ); + } +} + +void ResourceTask::itemsRetrievalDone() +{ + if (!mCancelled) { + m_resource->itemsRetrievalDone(); + } + deleteLater(); +} + +void ResourceTask::setTotalItems(int totalItems) +{ + if (!mCancelled) { + m_resource->setTotalItems(totalItems); + } +} + +void ResourceTask::changeCommitted( const Akonadi::Item &item ) +{ + if (!mCancelled) { + m_resource->itemChangeCommitted( item ); + } + deleteLater(); +} + +void ResourceTask::changesCommitted(const Akonadi::Item::List& items) +{ + if (!mCancelled) { + m_resource->itemsChangesCommitted( items ); + } + deleteLater(); +} + +void ResourceTask::searchFinished( const QVector &result, bool isRid ) +{ + if (!mCancelled) { + m_resource->searchFinished( result, isRid ); + } + deleteLater(); +} + +void ResourceTask::collectionsRetrieved( const Akonadi::Collection::List &collections ) +{ + if (!mCancelled) { + m_resource->collectionsRetrieved( collections ); + } + deleteLater(); +} + +void ResourceTask::collectionAttributesRetrieved(const Akonadi::Collection& col) +{ + if (!mCancelled) { + m_resource->collectionAttributesRetrieved( col ); + } + deleteLater(); +} + +void ResourceTask::changeCommitted( const Akonadi::Collection &collection ) +{ + if (!mCancelled) { + m_resource->collectionChangeCommitted( collection ); + } + deleteLater(); +} + +void ResourceTask::changeProcessed() +{ + if (!mCancelled) { + m_resource->changeProcessed(); + } + deleteLater(); +} + +void ResourceTask::cancelTask( const QString &errorString ) +{ + if (!mCancelled) { + mCancelled = true; + m_resource->cancelTask( errorString ); + } + deleteLater(); +} + +void ResourceTask::deferTask() +{ + if (!mCancelled) { + mCancelled = true; + m_resource->deferTask(); + } + deleteLater(); +} + +void ResourceTask::restartItemRetrieval(Akonadi::Entity::Id col) +{ + if (!mCancelled) { + m_resource->restartItemRetrieval(col); + } + deleteLater(); +} + +void ResourceTask::taskDone() +{ + m_resource->taskDone(); + deleteLater(); +} + +void ResourceTask::emitPercent( int percent ) +{ + m_resource->emitPercent( percent ); +} + +void ResourceTask::emitError( const QString &message ) +{ + m_resource->emitError( message ); +} + +void ResourceTask::emitWarning( const QString &message ) +{ + m_resource->emitWarning( message ); +} + +void ResourceTask::synchronizeCollectionTree() +{ + m_resource->synchronizeCollectionTree(); +} + +void ResourceTask::showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ) +{ + m_resource->showInformationDialog( message, title, dontShowAgainName ); +} + +QList ResourceTask::fromAkonadiToSupportedImapFlags( const QList &flags, + const Akonadi::Collection &collection ) +{ + QList imapFlags = fromAkonadiFlags( flags ); + + const Akonadi::CollectionFlagsAttribute *flagAttr = collection.attribute(); + // the server does not support arbitrary flags, so filter out those it can't handle + if ( flagAttr && !flagAttr->flags().isEmpty() && !flagAttr->flags().contains( "\\*" ) ) { + for ( QList< QByteArray >::iterator it = imapFlags.begin(); it != imapFlags.end(); ) { + if ( flagAttr->flags().contains( *it ) ) { + ++it; + } else { + kDebug() << "Server does not support flag" << *it; + it = imapFlags.erase( it ); + } + } + } + + return imapFlags; +} + +QList ResourceTask::fromAkonadiFlags( const QList &flags ) +{ + QList newFlags; + + foreach ( const QByteArray &oldFlag, flags ) { + if ( oldFlag == Akonadi::MessageFlags::Seen ) { + newFlags.append( ImapFlags::Seen ); + } else if ( oldFlag == Akonadi::MessageFlags::Deleted ) { + newFlags.append( ImapFlags::Deleted ); + } else if ( oldFlag == Akonadi::MessageFlags::Answered || oldFlag == Akonadi::MessageFlags::Replied ) { + newFlags.append( ImapFlags::Answered ); + } else if ( oldFlag == Akonadi::MessageFlags::Flagged ) { + newFlags.append( ImapFlags::Flagged ); + } else { + newFlags.append( oldFlag ); + } + } + + return newFlags; +} + +QList ResourceTask::toAkonadiFlags( const QList &flags ) +{ + QList newFlags; + + foreach ( const QByteArray &oldFlag, flags ) { + if ( oldFlag == ImapFlags::Seen ) { + newFlags.append( Akonadi::MessageFlags::Seen ); + } else if ( oldFlag == ImapFlags::Deleted ) { + newFlags.append( Akonadi::MessageFlags::Deleted ); + } else if ( oldFlag == ImapFlags::Answered ) { + newFlags.append( Akonadi::MessageFlags::Answered ); + } else if ( oldFlag == ImapFlags::Flagged ) { + newFlags.append( Akonadi::MessageFlags::Flagged ); + } else if ( oldFlag.isEmpty() ) { + // filter out empty flags, to avoid isNull/isEmpty confusions higher up + continue; + } else { + newFlags.append( oldFlag ); + } + } + + return newFlags; +} + +void ResourceTask::kill() +{ + kDebug(); + cancelTask(i18n("killed")); +} + +const QChar ResourceTask::separatorCharacter() const +{ + const QChar separator = m_resource->separatorCharacter(); + if ( !separator.isNull() ) { + return separator; + } else { + //If we request the separator before first folder listing, then try to guess + //the separator: + //If we create a toplevel folder, assume the separator to be '/'. This is not perfect, but detecting the right + //IMAP separator is not straightforward for toplevel folders, and fixes bug 292418 and maybe other, where + //subfolders end up with remote id's starting with "i" (the first letter of imap:// ...) + + QString remoteId; + // We don't always have parent collection set (for example for CollectionChangeTask), + // in such cases however we can use current collection's remoteId to get the separator + const Akonadi::Collection parent = parentCollection(); + if ( parent.isValid() ) { + remoteId = parent.remoteId(); + } else { + remoteId = collection().remoteId(); + } + return ( ( remoteId != rootRemoteId() ) && !remoteId.isEmpty() ) ? remoteId.at( 0 ) : QLatin1Char('/'); + } +} + +void ResourceTask::setSeparatorCharacter( const QChar& separator ) +{ + m_resource->setSeparatorCharacter( separator ); +} + +bool ResourceTask::serverSupportsAnnotations() const +{ + return serverCapabilities().contains( QLatin1String( "METADATA" ) ) + || serverCapabilities().contains( QLatin1String( "ANNOTATEMORE" ) ); +} + +bool ResourceTask::serverSupportsCondstore() const +{ + // Don't enable CONDSTORE for GMail (X-GM-EXT-1 is a GMail-specific capability) + // because it breaks changes synchronization when using labels. + return serverCapabilities().contains( QLatin1String( "CONDSTORE" ) ) && + !serverCapabilities().contains( QLatin1String( "X-GM-EXT-1" ) ); +} + +int ResourceTask::batchSize() const +{ + return m_resource->batchSize(); +} + +ResourceStateInterface::Ptr ResourceTask::resourceState() +{ + return m_resource; +} diff --git a/kdepim-runtime/resources/imap/resourcetask.h b/kdepim-runtime/resources/imap/resourcetask.h new file mode 100644 index 00000000..4b08f6f7 --- /dev/null +++ b/kdepim-runtime/resources/imap/resourcetask.h @@ -0,0 +1,164 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RESOURCETASK_H +#define RESOURCETASK_H + +#include + +#include +#include + +#include + +#include "resourcestateinterface.h" + +namespace KIMAP +{ + class Session; +} + +class SessionPool; + +class ResourceTask : public QObject +{ + Q_OBJECT + Q_ENUMS( ActionIfNoSession ) + +public: + enum ActionIfNoSession { + CancelIfNoSession, + DeferIfNoSession + }; + + explicit ResourceTask( ActionIfNoSession action, ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~ResourceTask(); + + void start( SessionPool *pool ); + + void kill(); + + static QList fromAkonadiToSupportedImapFlags( const QList &flags, const Akonadi::Collection &collection ); + + static QList toAkonadiFlags( const QList &flags ); + +Q_SIGNALS: + void status( int status, const QString &message = QString() ); + +protected: + virtual void doStart( KIMAP::Session *session ) = 0; + +protected: + QString userName() const; + QString resourceName() const; + QStringList serverCapabilities() const; + QList serverNamespaces() const; + + bool isAutomaticExpungeEnabled() const; + bool isSubscriptionEnabled() const; + bool isDisconnectedModeEnabled() const; + int intervalCheckTime() const; + + Akonadi::Collection collection() const; + Akonadi::Item item() const; + Akonadi::Item::List items() const; + + Akonadi::Collection parentCollection() const; + + Akonadi::Collection sourceCollection() const; + Akonadi::Collection targetCollection() const; + + QSet parts() const; + QSet addedFlags() const; + QSet removedFlags() const; + + QString rootRemoteId() const; + QString mailBoxForCollection( const Akonadi::Collection &collection ) const; + + void setIdleCollection( const Akonadi::Collection &collection ); + void applyCollectionChanges( const Akonadi::Collection &collection ); + + void itemRetrieved( const Akonadi::Item &item ); + + void itemsRetrieved( const Akonadi::Item::List &items ); + void itemsRetrievedIncremental( const Akonadi::Item::List &changed, + const Akonadi::Item::List &removed ); + void itemsRetrievalDone(); + + void setTotalItems(int); + + void changeCommitted( const Akonadi::Item &item ); + void changesCommitted( const Akonadi::Item::List &items ); + + void collectionsRetrieved( const Akonadi::Collection::List &collections ); + + void collectionAttributesRetrieved( const Akonadi::Collection &col ); + + void changeCommitted( const Akonadi::Collection &collection ); + + void changeProcessed(); + + void searchFinished( const QVector &result, bool isRid = true ); + + void cancelTask( const QString &errorString ); + void deferTask(); + void restartItemRetrieval(Akonadi::Collection::Id col); + void taskDone(); + void emitPercent( int percent ); + void emitError( const QString &message ); + void emitWarning( const QString &message ); + + void synchronizeCollectionTree(); + + void showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ); + + const QChar separatorCharacter() const; + void setSeparatorCharacter( const QChar &separator ); + + virtual bool serverSupportsAnnotations() const; + virtual bool serverSupportsCondstore() const; + + int batchSize() const; + + ResourceStateInterface::Ptr resourceState(); + +private: + + static QList fromAkonadiFlags( const QList &flags ); + +private slots: + void onSessionRequested( qint64 requestId, KIMAP::Session *session, + int errorCode, const QString &errorString ); + void onConnectionLost( KIMAP::Session *session ); + void onPoolDisconnect(); + + +private: + SessionPool *m_pool; + qint64 m_sessionRequestId; + + KIMAP::Session *m_session; + ActionIfNoSession m_actionIfNoSession; + ResourceStateInterface::Ptr m_resource; + bool mCancelled; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/retrievecollectionmetadatatask.cpp b/kdepim-runtime/resources/imap/retrievecollectionmetadatatask.cpp new file mode 100644 index 00000000..355bfb89 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrievecollectionmetadatatask.cpp @@ -0,0 +1,316 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "retrievecollectionmetadatatask.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "collectionannotationsattribute.h" +#include "imapaclattribute.h" +#include "imapquotaattribute.h" +#include "noselectattribute.h" +#include "timestampattribute.h" + +RetrieveCollectionMetadataTask::RetrieveCollectionMetadataTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( CancelIfNoSession, resource, parent ), + m_pendingMetaDataJobs( 0 ) +{ +} + +RetrieveCollectionMetadataTask::~RetrieveCollectionMetadataTask() +{ +} + +void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session ) +{ + kDebug( 5327 ) << collection().remoteId(); + + // Prevent fetching metadata from noselect folders. + if ( collection().hasAttribute( "noselect" ) ) { + NoSelectAttribute* noselect = static_cast( collection().attribute( "noselect" ) ); + if ( noselect->noSelect() ) { + kDebug( 5327 ) << "No Select folder"; + endTaskIfNeeded(); + return; + } + } + + m_collection = collection(); + const QString mailBox = mailBoxForCollection( m_collection ); + const QStringList capabilities = serverCapabilities(); + + m_pendingMetaDataJobs = 0; + + // First get the annotations from the mailbox if it's supported + if ( capabilities.contains( QLatin1String("METADATA") ) || capabilities.contains( QLatin1String("ANNOTATEMORE") ) ) { + KIMAP::GetMetaDataJob *meta = new KIMAP::GetMetaDataJob( session ); + meta->setMailBox( mailBox ); + if ( capabilities.contains( QLatin1String("METADATA") ) ) { + meta->setServerCapability( KIMAP::MetaDataJobBase::Metadata ); + meta->addRequestedEntry( "/shared" ); + meta->setDepth( KIMAP::GetMetaDataJob::AllLevels ); + } else { + meta->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore ); + meta->addEntry( "*", "value.shared" ); + } + connect( meta, SIGNAL(result(KJob*)), SLOT(onGetMetaDataDone(KJob*)) ); + m_pendingMetaDataJobs++; + meta->start(); + } + + // Get the ACLs from the mailbox if it's supported + if ( capabilities.contains( QLatin1String("ACL") ) ) { + KIMAP::GetAclJob *acl = new KIMAP::GetAclJob( session ); + acl->setMailBox( mailBox ); + connect( acl, SIGNAL(result(KJob*)), SLOT(onGetAclDone(KJob*)) ); + m_pendingMetaDataJobs++; + acl->start(); + + KIMAP::MyRightsJob *rights = new KIMAP::MyRightsJob( session ); + rights->setMailBox( mailBox ); + connect( rights, SIGNAL(result(KJob*)), SLOT(onRightsReceived(KJob*)) ); + m_pendingMetaDataJobs++; + rights->start(); + } + + // Get the QUOTA info from the mailbox if it's supported + if ( capabilities.contains( QLatin1String("QUOTA") ) ) { + KIMAP::GetQuotaRootJob *quota = new KIMAP::GetQuotaRootJob( session ); + quota->setMailBox( mailBox ); + connect( quota, SIGNAL(result(KJob*)), SLOT(onQuotasReceived(KJob*)) ); + m_pendingMetaDataJobs++; + quota->start(); + } + + // the server does not have any of the capabilities needed to get extra info, so this + // step is done here + if ( m_pendingMetaDataJobs == 0 ) { + endTaskIfNeeded(); + } +} + +void RetrieveCollectionMetadataTask::onGetMetaDataDone( KJob *job ) +{ + m_pendingMetaDataJobs--; + if ( job->error() ) { + kWarning() << "Get metadata failed: " << job->errorString(); + endTaskIfNeeded(); + return; // Well, no metadata for us then... + } + + KIMAP::GetMetaDataJob *meta = qobject_cast( job ); + QMap rawAnnotations = meta->allMetaData(); + + // filter out unused and annoying Cyrus annotation /vendor/cmu/cyrus-imapd/lastupdate + // which contains the current date and time and thus constantly changes for no good + // reason which triggers a change notification and thus a bunch of Akonadi operations + rawAnnotations.remove( "/shared/vendor/cmu/cyrus-imapd/lastupdate" ); + rawAnnotations.remove( "/private/vendor/cmu/cyrus-imapd/lastupdate" ); + + // Store the mailbox metadata + Akonadi::CollectionAnnotationsAttribute *annotationsAttribute = + m_collection.attribute( Akonadi::Collection::AddIfMissing ); + const QMap oldAnnotations = annotationsAttribute->annotations(); + if ( oldAnnotations != rawAnnotations ) { + annotationsAttribute->setAnnotations( rawAnnotations ); + } + + endTaskIfNeeded(); +} + +void RetrieveCollectionMetadataTask::onGetAclDone( KJob *job ) +{ + m_pendingMetaDataJobs--; + if ( job->error() ) { + kWarning() << "GetACL failed: " << job->errorString(); + endTaskIfNeeded(); + return; // Well, no metadata for us then... + } + + KIMAP::GetAclJob *acl = qobject_cast( job ); + + // Store the mailbox ACLs + Akonadi::ImapAclAttribute *aclAttribute + = m_collection.attribute( Akonadi::Collection::AddIfMissing ); + const QMap oldRights = aclAttribute->rights(); + if ( oldRights != acl->allRights() ) { + aclAttribute->setRights( acl->allRights() ); + } + + endTaskIfNeeded(); +} + +void RetrieveCollectionMetadataTask::onRightsReceived( KJob *job ) +{ + m_pendingMetaDataJobs--; + if ( job->error() ) { + kWarning() << "MyRights failed: " << job->errorString(); + endTaskIfNeeded(); + return; // Well, no metadata for us then... + } + + KIMAP::MyRightsJob *rightsJob = qobject_cast( job ); + + Akonadi::ImapAclAttribute * const parentAclAttribute = + collection().parentCollection().attribute(); + KIMAP::Acl::Rights parentRights = 0; + if ( parentAclAttribute ) { + parentRights = parentAclAttribute->rights()[userName().toUtf8()]; + } + + KIMAP::Acl::Rights imapRights = rightsJob->rights(); + Akonadi::Collection::Rights newRights = Akonadi::Collection::ReadOnly; + + // For renaming, the parent folder needs to have the CreateMailbox or Create permission. + // We map renaming to CanChangeCollection here, which is not entirely correct, but we have no + // CanRenameCollection flag. + // If the ACL of the parent folder hasn't been retrieved yet, allow changing, since we don't know + // better. If the parent folder is a noselect folder though, don't allow it, since for those we have + // no CreateMailbox right. + if ( ( !parentAclAttribute && !collection().parentCollection().hasAttribute( "noselect" ) ) || + parentRights & KIMAP::Acl::CreateMailbox || + parentRights & KIMAP::Acl::Create ) { + newRights|= Akonadi::Collection::CanChangeCollection; + } + + if ( imapRights & KIMAP::Acl::Write ) { + newRights|= Akonadi::Collection::CanChangeItem; + } + + if ( imapRights & KIMAP::Acl::Insert ) { + newRights|= Akonadi::Collection::CanCreateItem; + } + + if ( imapRights & ( KIMAP::Acl::DeleteMessage | KIMAP::Acl::Delete ) ) { + newRights|= Akonadi::Collection::CanDeleteItem; + } + + if ( !m_collection.hasAttribute( "noinferiors" ) && imapRights & ( KIMAP::Acl::CreateMailbox | KIMAP::Acl::Create ) ) { + newRights|= Akonadi::Collection::CanCreateCollection; + } + + if ( imapRights & ( KIMAP::Acl::DeleteMailbox | KIMAP::Acl::Delete ) ) { + newRights|= Akonadi::Collection::CanDeleteCollection; + } + +// kDebug( 5327 ) << collection.remoteId() +// << "imapRights:" << imapRights +// << "newRights:" << newRights +// << "oldRights:" << collection.rights(); + + const bool isNewCollection = !m_collection.hasAttribute(); + if ( (m_collection.rights() & Akonadi::Collection::CanCreateItem) && + !(newRights & Akonadi::Collection::CanCreateItem) && + !isNewCollection ) { + // write access revoked + const QString collectionName = m_collection.displayName(); + + showInformationDialog( i18n( "

Your access rights to folder %1 have been restricted, " + "it will no longer be possible to add messages to this folder.

", + collectionName ), + i18n( "Access rights revoked" ), QLatin1String("ShowRightsRevokedWarning") ); + } + + if ( newRights != m_collection.rights() ) { + m_collection.setRights( newRights ); + } + + endTaskIfNeeded(); +} + +void RetrieveCollectionMetadataTask::onQuotasReceived( KJob *job ) +{ + m_pendingMetaDataJobs--; + if ( job->error() ) { + kWarning() << "Quota retrieval failed: " << job->errorString(); + endTaskIfNeeded(); + return; // Well, no metadata for us then... + } + + KIMAP::GetQuotaRootJob *quotaJob = qobject_cast( job ); + const QString &mailBox = mailBoxForCollection( m_collection ); + + QList newRoots = quotaJob->roots(); + QList< QMap > newLimits; + QList< QMap > newUsages; + qint64 newCurrent = -1; + qint64 newMax = -1; + + foreach ( const QByteArray &root, newRoots ) { + newLimits << quotaJob->allLimits( root ); + newUsages << quotaJob->allUsages( root ); + + const QString &decodedRoot = QString::fromUtf8( KIMAP::decodeImapFolderName( root ) ); + + if ( newRoots.size() == 1 || decodedRoot == mailBox ) { + newCurrent = newUsages.last()["STORAGE"] * 1024; + newMax = newLimits.last()["STORAGE"] * 1024; + } + } + + // Store the mailbox IMAP Quotas + Akonadi::ImapQuotaAttribute *imapQuotaAttribute + = m_collection.attribute( Akonadi::Collection::AddIfMissing ); + const QList oldRoots = imapQuotaAttribute->roots(); + const QList< QMap > oldLimits = imapQuotaAttribute->limits(); + const QList< QMap > oldUsages = imapQuotaAttribute->usages(); + + if ( oldRoots != newRoots + || oldLimits != newLimits + || oldUsages != newUsages ) { + imapQuotaAttribute->setQuotas( newRoots, newLimits, newUsages ); + } + + // Store the collection Quota + Akonadi::CollectionQuotaAttribute *quotaAttribute + = m_collection.attribute( Akonadi::Collection::AddIfMissing ); + qint64 oldCurrent = quotaAttribute->currentValue(); + qint64 oldMax = quotaAttribute->maximumValue(); + + if ( oldCurrent != newCurrent + || oldMax != newMax ) { + quotaAttribute->setCurrentValue( newCurrent ); + quotaAttribute->setMaximumValue( newMax ); + } + + endTaskIfNeeded(); +} + +void RetrieveCollectionMetadataTask::endTaskIfNeeded() +{ + if ( m_pendingMetaDataJobs <= 0 ) { + const uint currentTimestamp = QDateTime::currentDateTime().toTime_t(); + TimestampAttribute *attr = m_collection.attribute( Akonadi::Collection::AddIfMissing ); + attr->setTimestamp( currentTimestamp ); + + collectionAttributesRetrieved( m_collection ); + } +} diff --git a/kdepim-runtime/resources/imap/retrievecollectionmetadatatask.h b/kdepim-runtime/resources/imap/retrievecollectionmetadatatask.h new file mode 100644 index 00000000..53eebfe1 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrievecollectionmetadatatask.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RETRIEVECOLLECTIONMETADATATASK_H +#define RETRIEVECOLLECTIONMETADATATASK_H + +#include "resourcetask.h" + +class RetrieveCollectionMetadataTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit RetrieveCollectionMetadataTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~RetrieveCollectionMetadataTask(); + +private slots: + void onGetMetaDataDone( KJob *job ); + void onGetAclDone( KJob *job ); + void onRightsReceived( KJob *job ); + void onQuotasReceived( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void endTaskIfNeeded(); + + int m_pendingMetaDataJobs; + + Akonadi::Collection m_collection; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/retrievecollectionstask.cpp b/kdepim-runtime/resources/imap/retrievecollectionstask.cpp new file mode 100644 index 00000000..8fc71f17 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrievecollectionstask.cpp @@ -0,0 +1,236 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "retrievecollectionstask.h" + +#include "noselectattribute.h" +#include "noinferiorsattribute.h" + +#include +#include +#include + +#include + +#include +#include + +RetrieveCollectionsTask::RetrieveCollectionsTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( CancelIfNoSession, resource, parent ) +{ +} + +RetrieveCollectionsTask::~RetrieveCollectionsTask() +{ +} + +void RetrieveCollectionsTask::doStart( KIMAP::Session *session ) +{ + Akonadi::Collection root; + root.setName( resourceName() ); + root.setRemoteId( rootRemoteId() ); + root.setContentMimeTypes( QStringList( Akonadi::Collection::mimeType() ) ); + root.setParentCollection( Akonadi::Collection::root() ); + root.addAttribute( new NoSelectAttribute( true ) ); + + Akonadi::CachePolicy policy; + policy.setInheritFromParent( false ); + policy.setSyncOnDemand( true ); + + QStringList localParts; + localParts << QLatin1String(Akonadi::MessagePart::Envelope) + << QLatin1String(Akonadi::MessagePart::Header); + int cacheTimeout = 60; + + if ( isDisconnectedModeEnabled() ) { + // For disconnected mode we also cache the body + // and we keep all data indifinitely + localParts << QLatin1String(Akonadi::MessagePart::Body); + cacheTimeout = -1; + } + + policy.setLocalParts( localParts ); + policy.setCacheTimeout( cacheTimeout ); + policy.setIntervalCheckTime( intervalCheckTime() ); + + root.setCachePolicy( policy ); + + m_reportedCollections.insert( QString(), root ); + + // this is ugly, but the result of LSUB is unfortunately not a sub-set of LIST + // it also contains subscribed but currently not available (eg. deleted) mailboxes + // so we need to use both and exclude mailboxes in LSUB but not in LIST + if ( isSubscriptionEnabled() ) { + KIMAP::ListJob *fullListJob = new KIMAP::ListJob( session ); + fullListJob->setIncludeUnsubscribed( true ); + fullListJob->setQueriedNamespaces( serverNamespaces() ); + connect( fullListJob, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onFullMailBoxesReceived(QList,QList >)) ); + connect( fullListJob, SIGNAL(result(KJob*)), SLOT(onFullMailBoxesReceiveDone(KJob*)) ); + fullListJob->start(); + } + + KIMAP::ListJob *listJob = new KIMAP::ListJob( session ); + listJob->setIncludeUnsubscribed( !isSubscriptionEnabled() ); + listJob->setQueriedNamespaces( serverNamespaces() ); + connect( listJob, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onMailBoxesReceived(QList,QList >)) ); + connect( listJob, SIGNAL(result(KJob*)), SLOT(onMailBoxesReceiveDone(KJob*)) ); + listJob->start(); +} + +void RetrieveCollectionsTask::onMailBoxesReceived( const QList< KIMAP::MailBoxDescriptor > &descriptors, + const QList< QList > &flags ) +{ + QStringList contentTypes; + contentTypes << KMime::Message::mimeType() << Akonadi::Collection::mimeType(); + + if ( !descriptors.isEmpty() ) { + // This is still not optimal way of getting the separator, but it's better + // than guessing every time from RID of parent collection + setSeparatorCharacter( descriptors.first().separator ); + } + + for ( int i=0; i(); + + m_dummyCollections.remove( currentPath ); + m_reportedCollections.remove( currentPath ); + m_reportedCollections.insert( currentPath, c ); + + } + parentPath = currentPath; + continue; + } + + const QList currentFlags = isDummy ? ( QList() << "\\noselect" ) : flags[i]; + + Akonadi::Collection c; + c.setName( pathPart ); + c.setRemoteId( separator + pathPart ); + const Akonadi::Collection parentCollection = m_reportedCollections.value( parentPath ); + c.setParentCollection( parentCollection ); + c.setContentMimeTypes( contentTypes ); + + // If the folder is the Inbox, make some special settings. + if ( currentPath.compare( separator + QLatin1String( "INBOX" ) , Qt::CaseInsensitive ) == 0 ) { + Akonadi::EntityDisplayAttribute *attr = c.attribute( Akonadi::Collection::AddIfMissing ); + attr->setDisplayName( i18n( "Inbox" ) ); + attr->setIconName( QLatin1String("mail-folder-inbox") ); + setIdleCollection( c ); + } + + // If the folder is the user top-level folder, mark it as well, even although it is not officially noted in the RFC + if ( currentPath == ( separator + QLatin1String( "user" ) ) && currentFlags.contains( "\\noselect" ) ) { + Akonadi::EntityDisplayAttribute *attr = c.attribute( Akonadi::Collection::AddIfMissing ); + attr->setDisplayName( i18n( "Shared Folders" ) ); + attr->setIconName( QLatin1String("x-mail-distribution-list") ); + } + + // If this folder is a noselect folder, make some special settings. + if ( currentFlags.contains( "\\noselect" ) ) { + kDebug() << "Dummy collection created: " << currentPath; + c.addAttribute( new NoSelectAttribute( true ) ); + c.setContentMimeTypes( QStringList() << Akonadi::Collection::mimeType() ); + c.setRights( Akonadi::Collection::ReadOnly ); + } else { + // remove the noselect attribute explicitly, in case we had set it before (eg. for non-subscribed non-leaf folders) + c.removeAttribute(); + } + + // If this folder is a noinferiors folder, it is not allowed to create subfolders inside. + if ( currentFlags.contains( "\\noinferiors" ) ) { + //kDebug() << "Noinferiors: " << currentPath; + c.addAttribute( new NoInferiorsAttribute( true ) ); + c.setRights( c.rights() & ~Akonadi::Collection::CanCreateCollection ); + } + + m_reportedCollections.insert( currentPath, c ); + + if ( isDummy ) { + m_dummyCollections.insert( currentPath, c ); + } + + parentPath = currentPath; + } + } +} + +void RetrieveCollectionsTask::onMailBoxesReceiveDone( KJob* job ) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } else { + collectionsRetrieved( m_reportedCollections.values() ); + } +} + +void RetrieveCollectionsTask::onFullMailBoxesReceived( const QList< KIMAP::MailBoxDescriptor >& descriptors, + const QList< QList< QByteArray > >& flags ) +{ + Q_UNUSED( flags ); + foreach ( const KIMAP::MailBoxDescriptor &descriptor, descriptors ) { + m_fullReportedCollections.insert( descriptor.name ); + } +} + +void RetrieveCollectionsTask::onFullMailBoxesReceiveDone(KJob* job) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } +} + + + diff --git a/kdepim-runtime/resources/imap/retrievecollectionstask.h b/kdepim-runtime/resources/imap/retrievecollectionstask.h new file mode 100644 index 00000000..11731059 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrievecollectionstask.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RETRIEVECOLLECTIONSTASK_H +#define RETRIEVECOLLECTIONSTASK_H + +#include + +#include + +#include "resourcetask.h" + +class RetrieveCollectionsTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit RetrieveCollectionsTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~RetrieveCollectionsTask(); + +private slots: + void onMailBoxesReceived( const QList &descriptors, + const QList< QList > &flags ); + void onMailBoxesReceiveDone( KJob *job ); + void onFullMailBoxesReceived( const QList &descriptors, const QList > &flags ); + void onFullMailBoxesReceiveDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +protected: + QHash m_reportedCollections; + QHash m_dummyCollections; + QSet m_fullReportedCollections; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/retrieveitemstask.cpp b/kdepim-runtime/resources/imap/retrieveitemstask.cpp new file mode 100644 index 00000000..8b54d501 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrieveitemstask.cpp @@ -0,0 +1,590 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + Copyright (c) 2014 Christian Mollekopf + + 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 "retrieveitemstask.h" + +#include "collectionflagsattribute.h" +#include "noselectattribute.h" +#include "uidvalidityattribute.h" +#include "uidnextattribute.h" +#include "highestmodseqattribute.h" +#include "messagehelper.h" +#include "batchfetcher.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +RetrieveItemsTask::RetrieveItemsTask(ResourceStateInterface::Ptr resource, QObject *parent) + : ResourceTask(CancelIfNoSession, resource, parent), + m_session(0), + m_fetchedMissingBodies(-1), + m_fetchMissingBodies(false), + m_batchFetcher(0), + m_uidBasedFetch(true), + m_flagsChanged(false) +{ + +} + +RetrieveItemsTask::~RetrieveItemsTask() +{ +} + +void RetrieveItemsTask::setFetchMissingItemBodies(bool enabled) +{ + m_fetchMissingBodies = enabled; +} + +void RetrieveItemsTask::doStart(KIMAP::Session *session) +{ + emitPercent(0); + // Prevent fetching items from noselect folders. + if (collection().hasAttribute("noselect")) { + NoSelectAttribute* noselect = static_cast(collection().attribute("noselect")); + if (noselect->noSelect()) { + kDebug(5327) << "No Select folder"; + itemsRetrievalDone(); + return; + } + } + + m_session = session; + + const Akonadi::Collection col = collection(); + if (m_fetchMissingBodies && col.cachePolicy() + .localParts().contains( QLatin1String(Akonadi::MessagePart::Body))) { //disconnected mode, make sure we really have the body cached + + Akonadi::Session *session = new Akonadi::Session(resourceName().toLatin1() + "_body_checker", this); + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(col, session); + fetchJob->fetchScope().setCheckForCachedPayloadPartsOnly(); + fetchJob->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Body); + fetchJob->fetchScope().setFetchModificationTime(false); + connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchItemsWithoutBodiesDone(KJob*))); + connect(fetchJob, SIGNAL(result(KJob*)), session, SLOT(deleteLater())); + } else { + startRetrievalTasks(); + } +} + +BatchFetcher* RetrieveItemsTask::createBatchFetcher(MessageHelper::Ptr messageHelper, + const KIMAP::ImapSet &set, + const KIMAP::FetchJob::FetchScope &scope, + int batchSize, KIMAP::Session* session) +{ + return new BatchFetcher(messageHelper, set, scope, batchSize, session); +} + +void RetrieveItemsTask::fetchItemsWithoutBodiesDone(KJob *job) +{ + QList uids; + if (job->error()) { + kWarning() << job->errorString(); + cancelTask(job->errorString()); + return; + } else { + int i = 0; + Akonadi::ItemFetchJob *fetch = static_cast(job); + Q_FOREACH(const Akonadi::Item &item, fetch->items()) { + if (!item.cachedPayloadParts().contains(Akonadi::MessagePart::Body)) { + kWarning() << "Item " << item.id() << " is missing the payload! Cached payloads: " << item.cachedPayloadParts(); + uids.append(item.remoteId().toInt()); + i++; + } + } + if (i > 0) { + kWarning() << "Number of items missing the body: " << i; + } + } + onFetchItemsWithoutBodiesDone(uids); +} + +void RetrieveItemsTask::onFetchItemsWithoutBodiesDone(const QList &items) +{ + m_messageUidsMissingBody = items; + startRetrievalTasks(); +} + + +void RetrieveItemsTask::startRetrievalTasks() +{ + const QString mailBox = mailBoxForCollection(collection()); + kDebug(5327) << "Starting retrieval for " << mailBox; + m_time.start(); + + // Now is the right time to expunge the messages marked \\Deleted from this mailbox. + // We assume that we can only expunge if we can delete items (correct would be to check for "e" ACL right). + if (isAutomaticExpungeEnabled() && (collection().rights() & Akonadi::Collection::CanDeleteItem)) { + if (m_session->selectedMailBox() != mailBox) { + triggerPreExpungeSelect(mailBox); + } else { + triggerExpunge(mailBox); + } + } else { + // Always select to get the stats updated + triggerFinalSelect(mailBox); + } +} + +void RetrieveItemsTask::triggerPreExpungeSelect(const QString &mailBox) +{ + KIMAP::SelectJob *select = new KIMAP::SelectJob(m_session); + select->setMailBox(mailBox); + select->setCondstoreEnabled(serverSupportsCondstore()); + connect(select, SIGNAL(result(KJob*)), + this, SLOT(onPreExpungeSelectDone(KJob*))); + select->start(); +} + +void RetrieveItemsTask::onPreExpungeSelectDone(KJob *job) +{ + if (job->error()) { + kWarning() << job->errorString(); + cancelTask(job->errorString()); + } else { + KIMAP::SelectJob *select = static_cast(job); + triggerExpunge(select->mailBox()); + } +} + +void RetrieveItemsTask::triggerExpunge(const QString &mailBox) +{ + KIMAP::ExpungeJob *expunge = new KIMAP::ExpungeJob(m_session); + connect(expunge, SIGNAL(result(KJob*)), + this, SLOT(onExpungeDone(KJob*))); + expunge->start(); +} + +void RetrieveItemsTask::onExpungeDone(KJob *job) +{ + // We can ignore the error, we just had a wrong expunge so some old messages will just reappear. + // TODO we should probably hide messages that are marked as deleted (skipping will not work because we rely on the message count) + if (job->error()) { + kWarning() << "Expunge failed: " << job->errorString(); + } + // Except for network errors. + if (job->error() && m_session->state() == KIMAP::Session::Disconnected) { + cancelTask( job->errorString() ); + return; + } + + // We have to re-select the mailbox to update all the stats after the expunge + // (the EXPUNGE command doesn't return enough for our needs) + triggerFinalSelect(m_session->selectedMailBox()); +} + +void RetrieveItemsTask::triggerFinalSelect(const QString &mailBox) +{ + KIMAP::SelectJob *select = new KIMAP::SelectJob(m_session); + select->setMailBox(mailBox); + select->setCondstoreEnabled(serverSupportsCondstore()); + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onFinalSelectDone(KJob*)) ); + select->start(); +} + +void RetrieveItemsTask::onFinalSelectDone(KJob *job) +{ + if (job->error()) { + kWarning() << job->errorString(); + cancelTask(job->errorString()); + return; + } + + KIMAP::SelectJob *select = qobject_cast(job); + + const QString mailBox = select->mailBox(); + const int messageCount = select->messageCount(); + const qint64 uidValidity = select->uidValidity(); + qint64 nextUid = select->nextUid(); + quint64 highestModSeq = select->highestModSequence(); + const QList flags = select->permanentFlags(); + + //This is known to happen with Courier IMAP. A better solution would be to issue STATUS in case UIDNEXT is not delivered on select, + //but that would have to be implemented in KIMAP first. See Bug 338186. + if (nextUid < 0) { + kWarning() << "Server bug: Your IMAP Server delivered an invalid UIDNEXT value. This is a known problem with Courier IMAP."; + nextUid = 0; + } + + //The select job retrieves highestmodseq whenever it's available, but in case of no CONDSTORE support we ignore it + if (!serverSupportsCondstore()) { + highestModSeq = 0; + } + + Akonadi::Collection col = collection(); + bool modifyNeeded = false; + + // Get the current uid validity value and store it + int oldUidValidity = 0; + if (!col.hasAttribute("uidvalidity")) { + UidValidityAttribute* currentUidValidity = new UidValidityAttribute(uidValidity); + col.addAttribute(currentUidValidity); + modifyNeeded = true; + } else { + UidValidityAttribute* currentUidValidity = + static_cast(col.attribute("uidvalidity" )); + oldUidValidity = currentUidValidity->uidValidity(); + if (oldUidValidity != uidValidity) { + currentUidValidity->setUidValidity(uidValidity); + modifyNeeded = true; + } + } + + // Get the current uid next value and store it + int oldNextUid = 0; + if (nextUid > 0) { //this can fail with faulty servers that don't deliver uidnext + if (UidNextAttribute* currentNextUid = col.attribute()) { + oldNextUid = currentNextUid->uidNext(); + if (oldNextUid != nextUid) { + currentNextUid->setUidNext(nextUid); + modifyNeeded = true; + } + } else { + col.attribute(Akonadi::Collection::AddIfMissing)->setUidNext(nextUid); + modifyNeeded = true; + } + } + + // Store the mailbox flags + if (!col.hasAttribute("collectionflags")) { + Akonadi::CollectionFlagsAttribute *flagsAttribute = new Akonadi::CollectionFlagsAttribute(flags); + col.addAttribute(flagsAttribute); + modifyNeeded = true; + } else { + Akonadi::CollectionFlagsAttribute *flagsAttribute = + static_cast(col.attribute("collectionflags")); + const QList oldFlags = flagsAttribute->flags(); + if (oldFlags != flags) { + flagsAttribute->setFlags(flags); + modifyNeeded = true; + } + } + + quint64 oldHighestModSeq = 0; + if (serverSupportsCondstore() && highestModSeq > 0) { + if (!col.hasAttribute("highestmodseq")) { + HighestModSeqAttribute *attr = new HighestModSeqAttribute(highestModSeq); + col.addAttribute(attr); + modifyNeeded = true; + } else { + HighestModSeqAttribute *attr = col.attribute(); + if (attr->highestModSequence() < highestModSeq) { + oldHighestModSeq = attr->highestModSequence(); + attr->setHighestModSeq(highestModSeq); + modifyNeeded = true; + } else if (attr->highestModSequence() == highestModSeq) { + oldHighestModSeq = attr->highestModSequence(); + } else if (attr->highestModSequence() > highestModSeq) { + // This situation should not happen. If it does, update the highestModSeq + // attribute, but rather do a full sync + attr->setHighestModSeq(highestModSeq); + modifyNeeded = true; + } + } + } + m_highestModseq = oldHighestModSeq; + + if ( modifyNeeded ) { + m_modifiedCollection = col; + } + + KIMAP::FetchJob::FetchScope scope; + scope.parts.clear(); + scope.mode = KIMAP::FetchJob::FetchScope::FullHeaders; + + if ( col.cachePolicy() + .localParts().contains( QLatin1String(Akonadi::MessagePart::Body) ) ) { + scope.mode = KIMAP::FetchJob::FetchScope::Full; + } + + const qint64 realMessageCount = col.statistics().count(); + + kDebug(5327) << "Starting message retrieval. Elapsed(ms): " << m_time.elapsed(); + kDebug(5327) << "MessageCount: " << messageCount << "Local message count: " << realMessageCount; + kDebug(5327) << "UidNext: " << nextUid << "Local UidNext: "<< oldNextUid; + kDebug(5327) << "HighestModSeq: " << highestModSeq << "Local HighestModSeq: "<< oldHighestModSeq; + + /* + * A synchronization has 3 mandatory steps: + * * If uidvalidity changed the local cache must be invalidated + * * New messages can be fetched usin uidNext and the last known fetched uid + * * flag changes and removals can be detected by listing all messages that weren't part of the previous step + * + * Everything else is optimizations. + * + * TODO: Note that the local message count can be larger than the remote message count although no messages + * have been deleted remotely, if we locally have messages that were not yet uploaded. + * We cannot differentiate that from remotely removed messages, so we have to do a full flag + * listing in that case. This can be optimized once we support QRESYNC and therefore have a way + * to determine wether messages have been removed. + */ + + if (messageCount == 0) { + //Shortcut: + //If no messages are present on the server, clear local cash and finish + m_incremental = false; + if (realMessageCount > 0) { + kDebug( 5327 ) << "No messages present so we are done, deleting local messages."; + itemsRetrieved(Akonadi::Item::List()); + } else { + kDebug( 5327 ) << "No messages present so we are done"; + } + taskComplete(); + } else if (oldUidValidity != uidValidity) { + //If uidvalidity has changed our local cache is worthless and has to be refetched completely + if (oldUidValidity != 0) { + kDebug( 5327 ) << "UIDVALIDITY check failed (" << oldUidValidity << "|" + << uidValidity << ") refetching " << mailBox; + } else { + kDebug( 5327 ) << "Fetching complete mailbox " << mailBox; + } + + setTotalItems(messageCount); + retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true); + } else if (nextUid <= 0) { + //This is a compatibilty codepath for Courier IMAP. It probably introduces problems, but at least it syncs. + //Since we don't have uidnext available, we simply use the messagecount. This will miss simultaneously added/removed messages. + kDebug() << "Running courier imap compatiblity codepath"; + if (messageCount > realMessageCount) { + //Get new messages + retrieveItems(KIMAP::ImapSet(realMessageCount + 1, messageCount), scope, false, false); + } else if (messageCount == realMessageCount) { + m_uidBasedFetch = false; + m_incremental = true; + setTotalItems(messageCount); + listFlagsForImapSet(KIMAP::ImapSet(1, messageCount)); + } else { + m_uidBasedFetch = false; + m_incremental = false; + setTotalItems(messageCount); + listFlagsForImapSet(KIMAP::ImapSet(1, messageCount)); + } + } else if (!m_messageUidsMissingBody.isEmpty()) { + //fetch missing uids + m_fetchedMissingBodies = 0; + setTotalItems(m_messageUidsMissingBody.size()); + KIMAP::ImapSet imapSet; + imapSet.add(m_messageUidsMissingBody); + retrieveItems(imapSet, scope, true, true); + } else if (nextUid > oldNextUid && ((realMessageCount + nextUid - oldNextUid) == messageCount) && realMessageCount > 0) { + //Optimization: + //New messages are available, but we know no messages have been removed. + //Fetch new messages, and then check for changed flags and removed messages + //We can make an incremental update and use modseq. + kDebug( 5327 ) << "Incrementally fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid << " message count " << messageCount << realMessageCount; + setTotalItems(qMax(1ll, messageCount - realMessageCount)); + m_flagsChanged = !(highestModSeq == oldHighestModSeq); + retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, true, true); + } else if (nextUid > oldNextUid && messageCount > (realMessageCount + nextUid - oldNextUid) && realMessageCount > 0) { + //Error recovery: + //New messages are available, but not enough to justify the difference between the local and remote message count. + //This can be triggered if we i.e. clear the local cache, but the keep the annotations. + //If we didn't catch this case, we end up inserting flags only for every missing message. + kWarning() << "Detected inconsistency in local cache, we're missing some messages. Server: " << messageCount << " Local: "<< realMessageCount; + kWarning() << "Refetching complete mailbox."; + setTotalItems(messageCount); + retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true); + } else if (nextUid > oldNextUid) { + //New messages are available. Fetch new messages, and then check for changed flags and removed messages + kDebug( 5327 ) << "Fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid; + setTotalItems(messageCount); + retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, false, true); + } else if (messageCount == realMessageCount && oldNextUid == nextUid) { + //Optimization: + //We know no messages were added or removed (if the message count and uidnext is still the same) + //We only check the flags incrementally and can make use of modseq + m_uidBasedFetch = true; + m_incremental = true; + m_flagsChanged = !(highestModSeq == oldHighestModSeq); + //Workaround: If the server doesn't support CONDSTORE we would end up syncing all flags during every sync. + //Instead we only sync flags when new messages are available or removed and skip this step. + //WARNING: This sacrifices consistency as we will not detect flag changes until a new message enters the mailbox. + if (m_incremental && !serverSupportsCondstore()) { + kDebug(5327) << "Avoiding flag sync due to missing CONDSTORE support"; + taskComplete(); + return; + } + setTotalItems(messageCount); + listFlagsForImapSet(KIMAP::ImapSet(1, nextUid)); + } else if (messageCount > realMessageCount) { + //Error recovery: + //We didn't detect any new messages based on the uid, but according to the message count there are new ones. + //Our local cache is invalid and has to be refetched. + kWarning() << "Detected inconsistency in local cache, we're missing some messages. Server: " << messageCount << " Local: "<< realMessageCount; + kWarning() << "Refetching complete mailbox."; + setTotalItems(messageCount); + retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true); + } else { + //Shortcut: + //No new messages are available. Directly check for changed flags and removed messages. + m_uidBasedFetch = true; + m_incremental = false; + setTotalItems(messageCount); + listFlagsForImapSet(KIMAP::ImapSet(1, nextUid)); + } +} + +void RetrieveItemsTask::retrieveItems(const KIMAP::ImapSet& set, const KIMAP::FetchJob::FetchScope &scope, bool incremental, bool uidBased) +{ + Q_ASSERT(set.intervals().size() == 1); + + m_incremental = incremental; + m_uidBasedFetch = uidBased; + + m_batchFetcher = createBatchFetcher(resourceState()->messageHelper(), set, scope, batchSize(), m_session); + m_batchFetcher->setUidBased(m_uidBasedFetch); + if (m_uidBasedFetch && set.intervals().size() == 1) { + m_batchFetcher->setSearchUids(set.intervals().front()); + } + m_batchFetcher->setProperty("alreadyFetched", set.intervals().first().begin()); + connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)), + this, SLOT(onItemsRetrieved(Akonadi::Item::List))); + connect(m_batchFetcher, SIGNAL(result(KJob*)), + this, SLOT(onRetrievalDone(KJob*))); + m_batchFetcher->start(); +} + +void RetrieveItemsTask::onReadyForNextBatch(int size) +{ + Q_UNUSED(size); + if (m_batchFetcher) { + m_batchFetcher->fetchNextBatch(); + } +} + +void RetrieveItemsTask::onItemsRetrieved(const Akonadi::Item::List &addedItems) +{ + if (m_incremental) { + itemsRetrievedIncremental(addedItems, Akonadi::Item::List()); + } else { + itemsRetrieved(addedItems); + } + + //m_fetchedMissingBodies is -1 if we fetch for other reason, but missing bodies + if (m_fetchedMissingBodies != -1) { + const QString mailBox = mailBoxForCollection(collection()); + m_fetchedMissingBodies += addedItems.count(); + emit status(Akonadi::AgentBase::Running, + i18nc( "@info:status", "Fetching missing mail bodies in %3: %1/%2", m_fetchedMissingBodies, m_messageUidsMissingBody.count(), mailBox)); + } +} + +void RetrieveItemsTask::onRetrievalDone(KJob *job) +{ + m_batchFetcher = 0; + if (job->error()) { + kWarning() << job->errorString(); + cancelTask(job->errorString()); + m_fetchedMissingBodies = -1; + return; + } + + //This is the lowest sequence number that we just fetched. + const KIMAP::ImapSet::Id alreadyFetchedBegin = job->property("alreadyFetched").value(); + + // If this is the first fetch of a folder, skip getting flags, we + // already have them all from the previous full fetch. This is not + // just an optimization, as incremental retrieval assumes nothing + // will be listed twice. + if (m_fetchedMissingBodies != -1 || alreadyFetchedBegin <= 1) { + taskComplete(); + return; + } + + // Fetch flags of all items that were not fetched by the fetchJob. After + // that /all/ items in the folder are synced. + listFlagsForImapSet(KIMAP::ImapSet(1, alreadyFetchedBegin - 1)); +} + +void RetrieveItemsTask::listFlagsForImapSet(const KIMAP::ImapSet& set) +{ + kDebug(5327) << "Listing flags " << set.intervals().first().begin() << set.intervals().first().end(); + kDebug(5327) << "Starting flag retrieval. Elapsed(ms): " << m_time.elapsed(); + + KIMAP::FetchJob::FetchScope scope; + scope.parts.clear(); + scope.mode = KIMAP::FetchJob::FetchScope::Flags; + // Only use changeSince when doing incremental listings, + // otherwise we would overwrite our local data with an incomplete dataset + if(m_incremental && serverSupportsCondstore()) { + scope.changedSince = m_highestModseq; + if (!m_flagsChanged) { + kDebug(5327) << "No flag changes."; + taskComplete(); + return; + } + } + + m_batchFetcher = createBatchFetcher(resourceState()->messageHelper(), set, scope, 10 * batchSize(), m_session); + m_batchFetcher->setUidBased(m_uidBasedFetch); + if (m_uidBasedFetch && scope.changedSince == 0 && set.intervals().size() == 1) { + m_batchFetcher->setSearchUids(set.intervals().front()); + } + connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)), + this, SLOT(onItemsRetrieved(Akonadi::Item::List))); + connect(m_batchFetcher, SIGNAL(result(KJob*)), + this, SLOT(onFlagsFetchDone(KJob*))); + m_batchFetcher->start(); +} + +void RetrieveItemsTask::onFlagsFetchDone(KJob *job) +{ + m_batchFetcher = 0; + if ( job->error() ) { + kWarning() << job->errorString(); + cancelTask(job->errorString()); + } else { + taskComplete(); + } +} + +void RetrieveItemsTask::taskComplete() +{ + if (m_modifiedCollection.isValid()) { + kDebug(5327) << "Applying collection changes"; + applyCollectionChanges(m_modifiedCollection); + } + if (m_incremental) { + // Calling itemsRetrievalDone() before previous call to itemsRetrievedIncremental() + // behaves like if we called itemsRetrieved(Items::List()), so make sure + // Akonadi knows we did incremental fetch that came up with no changes + itemsRetrievedIncremental(Akonadi::Item::List(), Akonadi::Item::List()); + } + kDebug(5327) << "Retrieval complete. Elapsed(ms): " << m_time.elapsed(); + itemsRetrievalDone(); +} + +#include "retrieveitemstask.moc" diff --git a/kdepim-runtime/resources/imap/retrieveitemstask.h b/kdepim-runtime/resources/imap/retrieveitemstask.h new file mode 100644 index 00000000..355e37b6 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrieveitemstask.h @@ -0,0 +1,86 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + Copyright (c) 2014 Christian Mollekopf + + 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 RETRIEVEITEMSTASK_H +#define RETRIEVEITEMSTASK_H + +#include + +#include "resourcetask.h" + +class BatchFetcher; +namespace Akonadi { + class Session; +} + +class RetrieveItemsTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit RetrieveItemsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0); + virtual ~RetrieveItemsTask(); + void setFetchMissingItemBodies(bool enabled); + +public slots: + void onFetchItemsWithoutBodiesDone(const QList &items); + void onReadyForNextBatch(int size); + +private slots: + void fetchItemsWithoutBodiesDone(KJob *job); + void onPreExpungeSelectDone(KJob *job); + void onExpungeDone(KJob *job); + void onFinalSelectDone(KJob *job); + void onItemsRetrieved(const Akonadi::Item::List &addedItems); + void onRetrievalDone(KJob *job); + void onFlagsFetchDone( KJob *job ); + +protected: + virtual void doStart(KIMAP::Session *session); + + virtual BatchFetcher* createBatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSet &set, + const KIMAP::FetchJob::FetchScope &scope, int batchSize, + KIMAP::Session *session); + +private: + void startRetrievalTasks(); + void triggerPreExpungeSelect(const QString &mailBox); + void triggerExpunge(const QString &mailBox); + void triggerFinalSelect(const QString &mailBox); + void retrieveItems(const KIMAP::ImapSet& set, const KIMAP::FetchJob::FetchScope &scope, bool incremental = false, bool uidBased = false); + void listFlagsForImapSet(const KIMAP::ImapSet& set); + void taskComplete(); + + KIMAP::Session *m_session; + QList m_messageUidsMissingBody; + int m_fetchedMissingBodies; + bool m_fetchMissingBodies; + bool m_incremental; + qint64 m_highestModseq; + BatchFetcher *m_batchFetcher; + Akonadi::Collection m_modifiedCollection; + bool m_uidBasedFetch; + bool m_flagsChanged; + QTime m_time; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/retrieveitemtask.cpp b/kdepim-runtime/resources/imap/retrieveitemtask.cpp new file mode 100644 index 00000000..661b6c83 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrieveitemtask.cpp @@ -0,0 +1,148 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "retrieveitemtask.h" +#include "messagehelper.h" + +#include +#include + +#include +#include +#include + +RetrieveItemTask::RetrieveItemTask( ResourceStateInterface::Ptr resource, QObject *parent ) + : ResourceTask( CancelIfNoSession, resource, parent ), m_session( 0 ), m_uid( 0 ), m_messageReceived( false ) +{ + +} + +RetrieveItemTask::~RetrieveItemTask() +{ +} + +void RetrieveItemTask::doStart( KIMAP::Session *session ) +{ + m_session = session; + + const QString mailBox = mailBoxForCollection( item().parentCollection() ); + m_uid = item().remoteId().toLongLong(); + + if ( m_uid == 0 ) { + kWarning() << "Remote id is " << item().remoteId(); + cancelTask( i18n("Remote id is empty or invalid") ); + return; + } + + if ( session->selectedMailBox() != mailBox ) { + KIMAP::SelectJob *select = new KIMAP::SelectJob( m_session ); + select->setMailBox( mailBox ); + connect( select, SIGNAL(result(KJob*)), + this, SLOT(onSelectDone(KJob*)) ); + select->start(); + } else { + triggerFetchJob(); + } +} + +void RetrieveItemTask::onSelectDone( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } else { + triggerFetchJob(); + } +} + +void RetrieveItemTask::triggerFetchJob() +{ + KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_session ); + KIMAP::FetchJob::FetchScope scope; + fetch->setUidBased( true ); + fetch->setSequenceSet( KIMAP::ImapSet( m_uid ) ); + scope.parts.clear();// = parts.toList(); + scope.mode = KIMAP::FetchJob::FetchScope::Content; + fetch->setScope( scope ); + connect( fetch, SIGNAL(messagesReceived(QString, + QMap, + QMap, + QMap)), + this, SLOT(onMessagesReceived(QString, + QMap, + QMap, + QMap)) ); + //TODO: Handle parts retrieval + //connect( fetch, SIGNAL(partsReceived(QString,QMap,QMap)), + // this, SLOT(onPartsReceived(QString,QMap,QMap)) ); + connect( fetch, SIGNAL(result(KJob*)), + this, SLOT(onContentFetchDone(KJob*)) ); + fetch->start(); +} + +void RetrieveItemTask::onMessagesReceived( const QString &mailBox, + const QMap &uids, + const QMap &attrs, + const QMap &messages ) +{ + Q_UNUSED( mailBox ); + + KIMAP::FetchJob *fetch = qobject_cast( sender() ); + Q_ASSERT( fetch!=0 ); + Q_ASSERT( uids.size()==1 ); + Q_ASSERT( messages.size()==1 ); + + Akonadi::Item i = item(); + + kDebug( 5327 ) << "MESSAGE from Imap server" << item().remoteId(); + Q_ASSERT( item().isValid() ); + + const qint64 number = uids.keys().first(); + bool ok; + const Akonadi::Item remoteItem = resourceState()->messageHelper()->createItemFromMessage(messages[number], uids[number], 0, attrs.values(number), QList(), fetch->scope(), ok); + if (!ok) { + kWarning() << "Failed to retrieve message " << uids[number]; + cancelTask( i18n( "No message retrieved, failed to read the message." ) ); + return; + } + i.setMimeType(remoteItem.mimeType()); + i.setPayload(remoteItem.payload()); + foreach (const QByteArray &flag, remoteItem.flags()) { + i.setFlag(flag); + } + + kDebug( 5327 ) << "Has Payload: " << i.hasPayload(); + + m_messageReceived = true; + itemRetrieved( i ); +} + +void RetrieveItemTask::onContentFetchDone( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorString() ); + } else if ( !m_messageReceived ) { + cancelTask( i18n( "No message retrieved, server reply was empty." ) ); + } +} + + + + diff --git a/kdepim-runtime/resources/imap/retrieveitemtask.h b/kdepim-runtime/resources/imap/retrieveitemtask.h new file mode 100644 index 00000000..e44fd1e2 --- /dev/null +++ b/kdepim-runtime/resources/imap/retrieveitemtask.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 RETRIEVEITEMTASK_H +#define RETRIEVEITEMTASK_H + +#include + +#include "resourcetask.h" + +class RetrieveItemTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit RetrieveItemTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 ); + virtual ~RetrieveItemTask(); + +private slots: + void onSelectDone( KJob *job ); + void onMessagesReceived( const QString &mailBox, + const QMap &uids, + const QMap &attrs, + const QMap &messages ); + void onContentFetchDone( KJob *job ); + +protected: + virtual void doStart( KIMAP::Session *session ); + +private: + void triggerFetchJob(); + + KIMAP::Session *m_session; + qint64 m_uid; + bool m_messageReceived; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/searchtask.cpp b/kdepim-runtime/resources/imap/searchtask.cpp new file mode 100644 index 00000000..830c1379 --- /dev/null +++ b/kdepim-runtime/resources/imap/searchtask.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "searchtask.h" +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE( KIMAP::Session* ) + +SearchTask::SearchTask( ResourceStateInterface::Ptr state, const QString &query, QObject *parent) + : ResourceTask( ResourceTask::DeferIfNoSession, state, parent) + , m_query( query ) +{ +} + +SearchTask::~SearchTask() +{ +} + +void SearchTask::doStart( KIMAP::Session *session ) +{ + kDebug() << collection().remoteId(); + + const QString mailbox = mailBoxForCollection( collection() ); + if ( session->selectedMailBox() == mailbox ) { + doSearch( session ); + return; + } + + KIMAP::SelectJob *select = new KIMAP::SelectJob( session ); + select->setMailBox( mailbox ); + connect( select, SIGNAL(finished(KJob*)), + this, SLOT(onSelectDone(KJob*)) ); + select->start(); +} + +void SearchTask::onSelectDone( KJob *job ) +{ + if ( job->error() ) { + searchFinished( QVector() ); + cancelTask( job->errorText() ); + return; + } + + doSearch( qobject_cast( job )->session() ); +} + +static KIMAP::Term::Relation mapRelation(Akonadi::SearchTerm::Relation relation) { + if (relation == Akonadi::SearchTerm::RelAnd){ + return KIMAP::Term::And; + } + return KIMAP::Term::Or; +} + +static KIMAP::Term recursiveEmailTermMapping(const Akonadi::SearchTerm &term) +{ + if (!term.subTerms().isEmpty()) { + QVector subterms; + Q_FOREACH (const Akonadi::SearchTerm &subterm, term.subTerms()) { + const KIMAP::Term newTerm = recursiveEmailTermMapping(subterm); + if (!newTerm.isNull()) { + subterms << newTerm; + } + } + return KIMAP::Term(mapRelation(term.relation()), subterms); + } else { + const Akonadi::EmailSearchTerm::EmailSearchField field = Akonadi::EmailSearchTerm::fromKey(term.key()); + switch (field) { + case Akonadi::EmailSearchTerm::Message: + return KIMAP::Term(KIMAP::Term::Text, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::Body: + return KIMAP::Term(KIMAP::Term::Body, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::Headers: + //FIXME +// return KIMAP::Term(KIMAP::Term::Header, term.value()).setNegated(term.isNegated()); + break; + case Akonadi::EmailSearchTerm::ByteSize: { + int value = term.value().toInt(); + switch (term.condition()) { + case Akonadi::SearchTerm::CondGreaterOrEqual: + value--; + case Akonadi::SearchTerm::CondGreaterThan: + return KIMAP::Term(KIMAP::Term::Larger, value).setNegated(term.isNegated()); + case Akonadi::SearchTerm::CondLessOrEqual: + value++; + case Akonadi::SearchTerm::CondLessThan: + return KIMAP::Term(KIMAP::Term::Smaller, value).setNegated(term.isNegated()); + case Akonadi::SearchTerm::CondEqual: + return KIMAP::Term(KIMAP::Term::And, QVector() << KIMAP::Term(KIMAP::Term::Smaller, value + 1) << KIMAP::Term(KIMAP::Term::Larger, value + 1)).setNegated(term.isNegated()); + case Akonadi::SearchTerm::CondContains: + kDebug()<<" invalid condition for ByteSize"; + break; + } + } + break; + case Akonadi::EmailSearchTerm::HeaderOnlyDate: + case Akonadi::EmailSearchTerm::HeaderDate: { + QDate value = term.value().toDateTime().date(); + switch (term.condition()) { + case Akonadi::SearchTerm::CondGreaterOrEqual: + value = value.addDays(-1); + case Akonadi::SearchTerm::CondGreaterThan: + return KIMAP::Term(KIMAP::Term::SentSince, value).setNegated(term.isNegated()); + case Akonadi::SearchTerm::CondLessOrEqual: + value = value.addDays(1); + case Akonadi::SearchTerm::CondLessThan: + return KIMAP::Term(KIMAP::Term::SentBefore, value).setNegated(term.isNegated()); + case Akonadi::SearchTerm::CondEqual: + return KIMAP::Term(KIMAP::Term::SentOn, value).setNegated(term.isNegated()); + case Akonadi::SearchTerm::CondContains: + kDebug()<<" invalid condition for Date"; + break; + } + } + case Akonadi::EmailSearchTerm::Subject: + return KIMAP::Term(KIMAP::Term::Subject, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::HeaderFrom: + return KIMAP::Term(KIMAP::Term::From, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::HeaderTo: + return KIMAP::Term(KIMAP::Term::To, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::HeaderCC: + return KIMAP::Term(KIMAP::Term::Cc, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::HeaderBCC: + return KIMAP::Term(KIMAP::Term::Bcc, term.value().toString()).setNegated(term.isNegated()); + case Akonadi::EmailSearchTerm::MessageStatus: + if (term.value().toString() == QString::fromLatin1(Akonadi::MessageFlags::Flagged)) { + return KIMAP::Term(KIMAP::Term::Flagged).setNegated(term.isNegated()); + } + if (term.value().toString() == QString::fromLatin1(Akonadi::MessageFlags::Deleted)) { + return KIMAP::Term(KIMAP::Term::Deleted).setNegated(term.isNegated()); + } + if (term.value().toString() == QString::fromLatin1(Akonadi::MessageFlags::Replied)) { + return KIMAP::Term(KIMAP::Term::Answered).setNegated(term.isNegated()); + } + if (term.value().toString() == QString::fromLatin1(Akonadi::MessageFlags::Seen)) { + return KIMAP::Term(KIMAP::Term::Seen).setNegated(term.isNegated()); + } + break; + case Akonadi::EmailSearchTerm::MessageTag: + break; + case Akonadi::EmailSearchTerm::HeaderReplyTo: + break; + case Akonadi::EmailSearchTerm::HeaderOrganization: + break; + case Akonadi::EmailSearchTerm::HeaderListId: + break; + case Akonadi::EmailSearchTerm::HeaderResentFrom: + break; + case Akonadi::EmailSearchTerm::HeaderXLoop: + break; + case Akonadi::EmailSearchTerm::HeaderXMailingList: + break; + case Akonadi::EmailSearchTerm::HeaderXSpamFlag: + break; + case Akonadi::EmailSearchTerm::Unknown: + default: + kWarning() << "unknown term " << term.key(); + } + } + return KIMAP::Term(); +} + +void SearchTask::doSearch( KIMAP::Session *session ) +{ + kDebug() << m_query; + + Akonadi::SearchQuery query = Akonadi::SearchQuery::fromJSON( m_query.toLatin1() ); + KIMAP::SearchJob *searchJob = new KIMAP::SearchJob( session ); + searchJob->setUidBased( true ); + + KIMAP::Term term = recursiveEmailTermMapping(query.term()); + if (term.isNull()) { + kWarning() << "failed to translate query " << m_query; + searchFinished( QVector() ); + cancelTask( "Invalid search" ); + return; + } + searchJob->setTerm(term); + + connect( searchJob, SIGNAL(finished(KJob*)), + this, SLOT(onSearchDone(KJob*)) ); + searchJob->start(); +} + +void SearchTask::onSearchDone( KJob* job ) +{ + if ( job->error() ) { + kWarning() << "Failed to execute search " << job->errorString(); + kDebug() << m_query; + searchFinished( QVector() ); + cancelTask( job->errorString() ); + return; + } + + KIMAP::SearchJob *searchJob = qobject_cast( job ); + const QList result = searchJob->results(); + kDebug() << result.count() << "matches"; + + searchFinished( result.toVector() ); + taskDone(); +} diff --git a/kdepim-runtime/resources/imap/searchtask.h b/kdepim-runtime/resources/imap/searchtask.h new file mode 100644 index 00000000..dec6e983 --- /dev/null +++ b/kdepim-runtime/resources/imap/searchtask.h @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef SEARCHTASK_H +#define SEARCHTASK_H + +#include "resourcetask.h" + +class SearchTask : public ResourceTask +{ + Q_OBJECT + public: + SearchTask( ResourceStateInterface::Ptr state, const QString &query, QObject *parent ); + ~SearchTask(); + + protected: + virtual void doStart( KIMAP::Session *session ); + + private Q_SLOTS: + void onSelectDone( KJob *job ); + void onSearchDone( KJob *job ); + + private: + void doSearch( KIMAP::Session *session ); + + QString m_query; + +}; + +#endif // SEARCHTASK_H diff --git a/kdepim-runtime/resources/imap/serverinfo.ui b/kdepim-runtime/resources/imap/serverinfo.ui new file mode 100644 index 00000000..66a0c708 --- /dev/null +++ b/kdepim-runtime/resources/imap/serverinfo.ui @@ -0,0 +1,24 @@ + + + ServerInfo + + + + 0 + 0 + 283 + 322 + + + + Server Info + + + + + + + + + + diff --git a/kdepim-runtime/resources/imap/serverinfodialog.cpp b/kdepim-runtime/resources/imap/serverinfodialog.cpp new file mode 100644 index 00000000..140bfc05 --- /dev/null +++ b/kdepim-runtime/resources/imap/serverinfodialog.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + 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 "serverinfodialog.h" +#include "imapresource.h" +#include "ui_serverinfo.h" + +#include + +ServerInfoDialog::ServerInfoDialog(ImapResourceBase *parentResource, QWidget *parent) + : KDialog(parent) +{ + setCaption( + i18nc( "@title:window Dialog title for dialog showing information about a server", + "Server Info" ) ); + setButtons( KDialog::Close ); + setAttribute( Qt::WA_DeleteOnClose ); + + mServerInfoWidget = new Ui::ServerInfo(); + mServerInfoWidget->setupUi( this ); + setMainWidget( mServerInfoWidget->serverInfo ); + mServerInfoWidget->serverInfo->setPlainText( + parentResource->serverCapabilities().join( QLatin1String( "\n" ) ) ); +} + +ServerInfoDialog::~ServerInfoDialog() +{ + delete mServerInfoWidget; +} + diff --git a/kdepim-runtime/resources/imap/serverinfodialog.h b/kdepim-runtime/resources/imap/serverinfodialog.h new file mode 100644 index 00000000..32cee08d --- /dev/null +++ b/kdepim-runtime/resources/imap/serverinfodialog.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + 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 SERVERINFODIALOG_H +#define SERVERINFODIALOG_H + +#include + +class ImapResourceBase; +namespace Ui { +class ServerInfo; +} + +class ServerInfoDialog : public KDialog +{ + Q_OBJECT +public: + explicit ServerInfoDialog(ImapResourceBase *parentResource, QWidget *parent); + ~ServerInfoDialog(); +private: + Ui::ServerInfo *mServerInfoWidget; +}; + +#endif // SERVERINFODIALOG_H diff --git a/kdepim-runtime/resources/imap/sessionpool.cpp b/kdepim-runtime/resources/imap/sessionpool.cpp new file mode 100644 index 00000000..73050a22 --- /dev/null +++ b/kdepim-runtime/resources/imap/sessionpool.cpp @@ -0,0 +1,533 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "sessionpool.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include "imapaccount.h" +#include "passwordrequesterinterface.h" + +qint64 SessionPool::m_requestCounter = 0; + +SessionPool::SessionPool( int maxPoolSize, QObject *parent ) + : QObject( parent ), + m_maxPoolSize( maxPoolSize ), + m_account( 0 ), + m_passwordRequester( 0 ), + m_initialConnectDone( false ), + m_pendingInitialSession( 0 ) +{ +} + +SessionPool::~SessionPool() +{ + disconnect( CloseSession ); +} + +PasswordRequesterInterface *SessionPool::passwordRequester() const +{ + return m_passwordRequester; +} + +void SessionPool::setPasswordRequester( PasswordRequesterInterface *requester ) +{ + delete m_passwordRequester; + + m_passwordRequester = requester; + m_passwordRequester->setParent( this ); + QObject::connect( m_passwordRequester, SIGNAL(done(int,QString)), + this, SLOT(onPasswordRequestDone(int,QString)) ); +} + +void SessionPool::cancelPasswordRequests() +{ + m_passwordRequester->cancelPasswordRequests(); +} + +KIMAP::SessionUiProxy::Ptr SessionPool::sessionUiProxy() const +{ + return m_sessionUiProxy; +} + +void SessionPool::setSessionUiProxy( KIMAP::SessionUiProxy::Ptr proxy ) +{ + m_sessionUiProxy = proxy; +} + +bool SessionPool::isConnected() const +{ + return m_initialConnectDone; +} + +bool SessionPool::connect( ImapAccount *account ) +{ + if ( m_account ) { + return false; + } + + m_account = account; + if ( m_account->authenticationMode() == KIMAP::LoginJob::GSSAPI ) { + // for GSSAPI we don't have to ask for username/password, because it uses session wide tickets + QMetaObject::invokeMethod( this, "onPasswordRequestDone", Qt::QueuedConnection, + Q_ARG( int, PasswordRequesterInterface::PasswordRetrieved ), + Q_ARG( QString, QString() ) ); + } else { + m_passwordRequester->requestPassword(); + } + + return true; +} + +void SessionPool::disconnect( SessionTermination termination ) +{ + if ( !m_account ) { + return; + } + + foreach ( KIMAP::Session *s, m_unusedPool + m_reservedPool + m_connectingPool ) { + killSession( s, termination ); + } + m_unusedPool.clear(); + m_reservedPool.clear(); + m_connectingPool.clear(); + m_pendingInitialSession = 0; + m_passwordRequester->cancelPasswordRequests(); + + delete m_account; + m_account = 0; + m_namespaces.clear(); + m_capabilities.clear(); + + m_initialConnectDone = false; + emit disconnectDone(); +} + +qint64 SessionPool::requestSession() +{ + if ( !m_initialConnectDone ) { + return -1; + } + + qint64 requestNumber = ++m_requestCounter; + + // The queue was empty, so trigger the processing + if ( m_pendingRequests.isEmpty() ) { + QTimer::singleShot( 0, this, SLOT(processPendingRequests()) ); + } + + m_pendingRequests << requestNumber; + + return requestNumber; +} + +void SessionPool::cancelSessionRequest( qint64 id ) +{ + Q_ASSERT( id > 0 ); + m_pendingRequests.removeAll( id ); +} + +void SessionPool::releaseSession( KIMAP::Session *session ) +{ + if ( m_reservedPool.contains( session ) ) { + m_reservedPool.removeAll( session ); + m_unusedPool << session; + } +} + +ImapAccount *SessionPool::account() const +{ + return m_account; +} + +QStringList SessionPool::serverCapabilities() const +{ + return m_capabilities; +} + +QList SessionPool::serverNamespaces() const +{ + return m_namespaces; +} + +void SessionPool::killSession( KIMAP::Session *session, SessionTermination termination ) +{ + QObject::disconnect( session, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)), + this, SLOT(onSessionStateChanged(KIMAP::Session::State,KIMAP::Session::State)) ); + m_unusedPool.removeAll( session ); + m_reservedPool.removeAll( session ); + m_connectingPool.removeAll( session ); + + if ( session->state() != KIMAP::Session::Disconnected && termination == LogoutSession ) { + KIMAP::LogoutJob *logout = new KIMAP::LogoutJob( session ); + QObject::connect( logout, SIGNAL(result(KJob*)), + session, SLOT(deleteLater()) ); + logout->start(); + } else { + session->close(); + session->deleteLater(); + } +} + +void SessionPool::declareSessionReady( KIMAP::Session *session ) +{ + //This can happen if we happen to disconnect while capabilities and namespace are being retrieved, + //resulting in us keeping a dangling pointer to a deleted session + if (!m_connectingPool.contains( session )) { + kWarning() << "Tried to declare a removed session ready"; + return; + } + + m_pendingInitialSession = 0; + + if ( !m_initialConnectDone ) { + m_initialConnectDone = true; + emit connectDone(); + } + + m_connectingPool.removeAll( session ); + + if ( m_pendingRequests.isEmpty() ) { + m_unusedPool << session; + } else { + m_reservedPool << session; + emit sessionRequestDone( m_pendingRequests.takeFirst(), session ); + + if ( !m_pendingRequests.isEmpty() ) { + QTimer::singleShot( 0, this, SLOT(processPendingRequests()) ); + } + } +} + +void SessionPool::cancelSessionCreation( KIMAP::Session *session, int errorCode, + const QString &errorMessage ) +{ + m_pendingInitialSession = 0; + + QString msg; + if ( m_account ) { + msg = i18n( "Could not connect to the IMAP-server %1.\n%2", m_account->server(), errorMessage ); + } else { + // Can happen when we lose all ready connections while trying to establish + // a new connection, for example. + msg = i18n( "Could not connect to the IMAP server.\n%1", errorMessage ); + } + + if ( !m_initialConnectDone ) { + disconnect(); // kills all sessions, including \a session + } else { + killSession( session, LogoutSession ); + if ( !m_pendingRequests.isEmpty() ) { + emit sessionRequestDone( m_pendingRequests.takeFirst(), 0, errorCode, errorMessage ); + if ( !m_pendingRequests.isEmpty() ) { + QTimer::singleShot( 0, this, SLOT(processPendingRequests()) ); + } + } + } + //Always emit this at the end. This can call SessionPool::disconnect via ImapResource. + emit connectDone( errorCode, msg ); +} + +void SessionPool::processPendingRequests() +{ + if ( !m_unusedPool.isEmpty() ) { + // We have a session ready to give out + KIMAP::Session *session = m_unusedPool.takeFirst(); + m_reservedPool << session; + if ( !m_pendingRequests.isEmpty() ) { + emit sessionRequestDone( m_pendingRequests.takeFirst(), session ); + if ( !m_pendingRequests.isEmpty() ) { + QTimer::singleShot( 0, this, SLOT(processPendingRequests()) ); + } + } + } else if ( m_unusedPool.size() + m_reservedPool.size() < m_maxPoolSize ) { + // We didn't reach the max pool size yet so create a new one + m_passwordRequester->requestPassword(); + + } else { + // No session available, and max pool size reached + if ( !m_pendingRequests.isEmpty() ) { + emit sessionRequestDone( + m_pendingRequests.takeFirst(), 0, NoAvailableSessionError, + i18n( "Could not create another extra connection to the IMAP-server %1.", + m_account->server() ) ); + if ( !m_pendingRequests.isEmpty() ) { + QTimer::singleShot( 0, this, SLOT(processPendingRequests()) ); + } + } + } +} + +void SessionPool::onPasswordRequestDone( int resultType, const QString &password ) +{ + QString errorMessage; + + if ( !m_account ) { + // it looks like the connection was lost while we were waiting + // for the password, we should fail all the pending requests and stop there + foreach ( int request, m_pendingRequests ) { + emit sessionRequestDone( request, 0, + LoginFailError, i18n( "Disconnected from server during login." ) ); + } + return; + } + + switch ( resultType ) { + case PasswordRequesterInterface::PasswordRetrieved: + // All is fine + break; + case PasswordRequesterInterface::ReconnectNeeded: + Q_ASSERT( m_pendingInitialSession != 0 ); + cancelSessionCreation( m_pendingInitialSession, ReconnectNeededError, errorMessage ); + return; + case PasswordRequesterInterface::UserRejected: + errorMessage = i18n( "Could not read the password: user rejected wallet access" ); + if ( m_pendingInitialSession ) { + cancelSessionCreation( m_pendingInitialSession, LoginFailError, errorMessage ); + } else { + emit connectDone( PasswordRequestError, errorMessage ); + } + return; + case PasswordRequesterInterface::EmptyPasswordEntered: + errorMessage = i18n( "Empty password" ); + if ( m_pendingInitialSession ) { + cancelSessionCreation( m_pendingInitialSession, LoginFailError, errorMessage ); + } else { + emit connectDone( PasswordRequestError, errorMessage ); + } + return; + } + + if ( m_account->encryptionMode() != KIMAP::LoginJob::Unencrypted && !QSslSocket::supportsSsl() ) { + kWarning() << "Crypto not supported!"; + emit connectDone( EncryptionError, + i18n( "You requested TLS/SSL to connect to %1, but your " + "system does not seem to be set up for that.", m_account->server() ) ); + disconnect(); + return; + } + + KIMAP::Session *session = 0; + if ( m_pendingInitialSession ) { + session = m_pendingInitialSession; + } else { + session = new KIMAP::Session( m_account->server(), m_account->port(), this ); + QObject::connect(session, SIGNAL(destroyed(QObject*)), this, SLOT(onSessionDestroyed(QObject*))); + session->setUiProxy( m_sessionUiProxy ); + session->setTimeout( m_account->timeout() ); + m_connectingPool << session; + } + + QObject::connect( session, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)), + this, SLOT(onSessionStateChanged(KIMAP::Session::State,KIMAP::Session::State)) ); + + KIMAP::LoginJob *loginJob = new KIMAP::LoginJob( session ); + loginJob->setUserName( m_account->userName() ); + loginJob->setPassword( password ); + loginJob->setEncryptionMode( m_account->encryptionMode() ); + loginJob->setAuthenticationMode( m_account->authenticationMode() ); + + QObject::connect( loginJob, SIGNAL(result(KJob*)), + this, SLOT(onLoginDone(KJob*)) ); + loginJob->start(); +} + +void SessionPool::onLoginDone( KJob *job ) +{ + KIMAP::LoginJob *login = static_cast( job ); + //Can happen if we disonnected meanwhile + if (!m_connectingPool.contains(login->session())) { + emit connectDone( CancelledError, i18n( "Disconnected from server during login.") ); + return; + } + + if ( job->error() == 0 ) { + if ( m_initialConnectDone ) { + declareSessionReady( login->session() ); + } else { + // On initial connection we ask for capabilities + KIMAP::CapabilitiesJob *capJob = new KIMAP::CapabilitiesJob( login->session() ); + QObject::connect( capJob, SIGNAL(result(KJob*)), SLOT(onCapabilitiesTestDone(KJob*)) ); + capJob->start(); + } + } else { + if ( job->error() == KIMAP::LoginJob::ERR_COULD_NOT_CONNECT ) { + if ( m_account ) { + cancelSessionCreation( login->session(), + CouldNotConnectError, + i18n( "Could not connect to the IMAP-server %1.\n%2", + m_account->server(), job->errorString() ) ); + } else { + // Can happen when we loose all ready connections while trying to login. + cancelSessionCreation( login->session(), + CouldNotConnectError, + i18n( "Could not connect to the IMAP-server.\n%1", + job->errorString() ) ); + } + } else { + // Connection worked, but login failed -> ask for a different password or ssl settings. + m_pendingInitialSession = login->session(); + m_passwordRequester->requestPassword( PasswordRequesterInterface::WrongPasswordRequest, + job->errorString() ); + } + } +} + +void SessionPool::onCapabilitiesTestDone( KJob *job ) +{ + KIMAP::CapabilitiesJob *capJob = qobject_cast( job ); + //Can happen if we disonnected meanwhile + if (!m_connectingPool.contains(capJob->session())) { + emit connectDone( CancelledError, i18n( "Disconnected from server during login.") ); + return; + } + + if ( job->error() ) { + if ( m_account ) { + cancelSessionCreation( capJob->session(), + CapabilitiesTestError, + i18n( "Could not test the capabilities supported by the " + "IMAP server %1.\n%2", + m_account->server(), job->errorString() ) ); + } else { + // Can happen when we loose all ready connections while trying to check capabilities. + cancelSessionCreation( capJob->session(), + CapabilitiesTestError, + i18n( "Could not test the capabilities supported by the " + "IMAP server.\n%1", job->errorString() ) ); + } + return; + } + + m_capabilities = capJob->capabilities(); + QStringList expected; + expected << QLatin1String("IMAP4REV1"); + + QStringList missing; + foreach ( const QString &capability, expected ) { + if ( !m_capabilities.contains( capability ) ) { + missing << capability; + } + } + + if ( !missing.isEmpty() ) { + cancelSessionCreation( capJob->session(), + IncompatibleServerError, + i18n( "Cannot use the IMAP server %1, " + "some mandatory capabilities are missing: %2. " + "Please ask your sysadmin to upgrade the server.", + m_account->server(), + missing.join( QLatin1String(", ") ) ) ); + return; + } + + // If the extension is supported, grab the namespaces from the server + if ( m_capabilities.contains( QLatin1String("NAMESPACE") ) ) { + KIMAP::NamespaceJob *nsJob = new KIMAP::NamespaceJob( capJob->session() ); + QObject::connect( nsJob, SIGNAL(result(KJob*)), SLOT(onNamespacesTestDone(KJob*)) ); + nsJob->start(); + return; + } else { + declareSessionReady( capJob->session() ); + } +} + +void SessionPool::onNamespacesTestDone( KJob *job ) +{ + KIMAP::NamespaceJob *nsJob = qobject_cast( job ); + // Can happen if we disconnect meanwhile + if (!m_connectingPool.contains(nsJob->session())) { + emit connectDone( CancelledError, i18n( "Disconnected from server during login.") ); + return; + } + + if ( nsJob->containsEmptyNamespace() ) { + // When we got the empty namespace here, we assume that the other + // ones can be freely ignored and that the server will give us all + // the mailboxes if we list from the empty namespace itself... + + m_namespaces.clear(); + + } else { + // ... otherwise we assume that we have to list explicitly each + // namespace + + m_namespaces = nsJob->personalNamespaces() + + nsJob->userNamespaces() + + nsJob->sharedNamespaces(); + } + + declareSessionReady( nsJob->session() ); +} + +void SessionPool::onSessionStateChanged(KIMAP::Session::State newState, KIMAP::Session::State oldState) +{ + if (newState == KIMAP::Session::Disconnected && oldState != KIMAP::Session::Disconnected) { + onConnectionLost(); + } +} + +void SessionPool::onConnectionLost() +{ + KIMAP::Session *session = static_cast( sender() ); + + m_unusedPool.removeAll( session ); + m_reservedPool.removeAll( session ); + m_connectingPool.removeAll( session ); + + if ( m_unusedPool.isEmpty() && m_reservedPool.isEmpty() ) { + m_passwordRequester->cancelPasswordRequests(); + delete m_account; + m_account = 0; + m_namespaces.clear(); + m_capabilities.clear(); + + m_initialConnectDone = false; + } + + emit connectionLost( session ); + + session->deleteLater(); + if ( session == m_pendingInitialSession ) + m_pendingInitialSession = 0; +} + +void SessionPool::onSessionDestroyed(QObject *object) +{ + //Safety net for bugs that cause dangling session pointers + KIMAP::Session *session = static_cast(object); + if (m_unusedPool.contains(session) || m_reservedPool.contains(session) || m_connectingPool.contains(session)) { + kWarning() << "Session destroyed while still in pool" << session; + m_unusedPool.removeAll(session); + m_reservedPool.removeAll(session); + m_connectingPool.removeAll(session); + Q_ASSERT(false); + } +} + diff --git a/kdepim-runtime/resources/imap/sessionpool.h b/kdepim-runtime/resources/imap/sessionpool.h new file mode 100644 index 00000000..e5d1cc94 --- /dev/null +++ b/kdepim-runtime/resources/imap/sessionpool.h @@ -0,0 +1,130 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 SESSIONPOOL_H +#define SESSIONPOOL_H + +#include +#include + +#include +#include +#include + +namespace KIMAP +{ + class MailBoxDescriptor; +} + +class ImapAccount; +class PasswordRequesterInterface; + +class SessionPool : public QObject +{ + Q_OBJECT + Q_ENUMS( ConnectError ) + +public: + enum ErrorCodes { + NoError, + PasswordRequestError, + ReconnectNeededError, + EncryptionError, + LoginFailError, + CapabilitiesTestError, + IncompatibleServerError, + NoAvailableSessionError, + CouldNotConnectError, + CancelledError + }; + + enum SessionTermination { + LogoutSession, + CloseSession + }; + + explicit SessionPool( int maxPoolSize, QObject *parent = 0 ); + ~SessionPool(); + + PasswordRequesterInterface *passwordRequester() const; + void setPasswordRequester( PasswordRequesterInterface *requester ); + void cancelPasswordRequests(); + + KIMAP::SessionUiProxy::Ptr sessionUiProxy() const; + void setSessionUiProxy( KIMAP::SessionUiProxy::Ptr proxy ); + + bool isConnected() const; + bool connect( ImapAccount *account ); + void disconnect( SessionTermination termination = LogoutSession ); + + qint64 requestSession(); + void cancelSessionRequest( qint64 id ); + void releaseSession( KIMAP::Session *session ); + + ImapAccount *account() const; + QStringList serverCapabilities() const; + QList serverNamespaces() const; + +signals: + void connectionLost( KIMAP::Session *session ); + + void sessionRequestDone( qint64 requestNumber, KIMAP::Session *session, + int errorCode = NoError, const QString &errorString = QString() ); + void connectDone( int errorCode = NoError, const QString &errorString = QString() ); + void disconnectDone(); + +private slots: + void processPendingRequests(); + + void onPasswordRequestDone(int resultType, const QString &password); + void onLoginDone( KJob *job ); + void onCapabilitiesTestDone( KJob *job ); + void onNamespacesTestDone( KJob *job ); + + void onSessionStateChanged(KIMAP::Session::State newState, KIMAP::Session::State oldState); + void onSessionDestroyed(QObject*); + +private: + void onConnectionLost(); + void killSession( KIMAP::Session *session, SessionTermination termination ); + void declareSessionReady( KIMAP::Session *session ); + void cancelSessionCreation( KIMAP::Session *session, int errorCode, const QString &errorString ); + + static qint64 m_requestCounter; + + int m_maxPoolSize; + ImapAccount *m_account; + PasswordRequesterInterface *m_passwordRequester; + KIMAP::SessionUiProxy::Ptr m_sessionUiProxy; + + bool m_initialConnectDone; + KIMAP::Session *m_pendingInitialSession; + + QList m_pendingRequests; + QList m_connectingPool; // in preparation + QList m_unusedPool; // ready to be used + QList m_reservedPool; // currently used + + QStringList m_capabilities; + QList m_namespaces; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/sessionuiproxy.h b/kdepim-runtime/resources/imap/sessionuiproxy.h new file mode 100644 index 00000000..d60e7b75 --- /dev/null +++ b/kdepim-runtime/resources/imap/sessionuiproxy.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 SESSIONUIPROXY_H +#define SESSIONUIPROXY_H + +#include +#include + + +class SessionUiProxy : public KIMAP::SessionUiProxy { + public: + bool ignoreSslError( const KSslErrorUiData &errorData ) { + if ( KIO::SslUi::askIgnoreSslErrors( errorData, KIO::SslUi::RecallAndStoreRules) ) { + return true; + } else { + return false; + } + } +}; + +#endif diff --git a/kdepim-runtime/resources/imap/settings.cpp b/kdepim-runtime/resources/imap/settings.cpp new file mode 100644 index 00000000..2671a8c4 --- /dev/null +++ b/kdepim-runtime/resources/imap/settings.cpp @@ -0,0 +1,329 @@ +/* + Copyright (c) 2008 Volker Krause + Copyright (c) 2008 Omat Holding B.V. + + 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 "settings.h" +#include "settingsadaptor.h" + +#include "imapaccount.h" + +#include +using KWallet::Wallet; + +#include +#include + +#include + +#include +#include +#include + +/** + * Maps the enum used to represent authentication in MailTransport (kdepimlibs) + * to the one used by the imap resource. + * @param authType the MailTransport auth enum value + * @return the corresponding KIMAP auth value. + * @note will cause fatal error if there is no mapping, so be careful not to pass invalid auth options (e.g., APOP) to this function. + */ +KIMAP::LoginJob::AuthenticationMode Settings::mapTransportAuthToKimap( MailTransport::Transport::EnumAuthenticationType::type authType ) +{ + // typedef these for readability + typedef MailTransport::Transport::EnumAuthenticationType MTAuth; + typedef KIMAP::LoginJob KIAuth; + switch ( authType ) { + case MTAuth::ANONYMOUS: + return KIAuth::Anonymous; + case MTAuth::PLAIN: + return KIAuth::Plain; + case MTAuth::NTLM: + return KIAuth::NTLM; + case MTAuth::LOGIN: + return KIAuth::Login; + case MTAuth::GSSAPI: + return KIAuth::GSSAPI; + case MTAuth::DIGEST_MD5: + return KIAuth::DigestMD5; + case MTAuth::CRAM_MD5: + return KIAuth::CramMD5; + case MTAuth::CLEAR: + return KIAuth::ClearText; + default: + kFatal() << "mapping from Transport::EnumAuthenticationType -> KIMAP::LoginJob::AuthenticationMode not possible"; + } + return KIAuth::ClearText; // dummy value, shouldn't get here. +} + +Settings::Settings( WId winId ) : SettingsBase(), m_winId( winId ) +{ + readConfig(); + + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), this, QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); +} + +void Settings::setWinId( WId winId ) +{ + m_winId = winId; +} + +void Settings::clearCachedPassword() +{ + m_password.clear(); +} + +void Settings::cleanup() +{ + Wallet* wallet = Wallet::openWallet( Wallet::NetworkWallet(), m_winId ); + if ( wallet && wallet->isOpen() ) { + if ( wallet->hasFolder( QLatin1String("imap") ) ) { + wallet->setFolder( QLatin1String("imap") ); + wallet->removeEntry( config()->name() ); + } + delete wallet; + } +} + +void Settings::requestPassword() +{ + if ( !m_password.isEmpty() || + ( mapTransportAuthToKimap( (MailTransport::TransportBase::EnumAuthenticationType::type)authentication() ) == KIMAP::LoginJob::GSSAPI ) ) { + emit passwordRequestCompleted( m_password, false ); + } else { + Wallet *wallet = Wallet::openWallet( Wallet::NetworkWallet(), m_winId, Wallet::Asynchronous ); + if ( wallet ) { + connect( wallet, SIGNAL(walletOpened(bool)), + this, SLOT(onWalletOpened(bool)) ); + } else { + QMetaObject::invokeMethod( this, "onWalletOpened", Qt::QueuedConnection, Q_ARG(bool, true) ); + } + } +} + +void Settings::onWalletOpened( bool success ) +{ + if ( !success ) { + emit passwordRequestCompleted( QString(), true ); + } else { + Wallet *wallet = qobject_cast( sender() ); + bool passwordNotStoredInWallet = true; + if ( wallet && wallet->hasFolder( QLatin1String("imap") ) ) { + wallet->setFolder( QLatin1String("imap") ); + wallet->readPassword( config()->name(), m_password ); + passwordNotStoredInWallet = false; + } + if ( passwordNotStoredInWallet || m_password.isEmpty() ) + requestManualAuth(); + else + emit passwordRequestCompleted( m_password, passwordNotStoredInWallet ); + + if ( wallet ) { + wallet->deleteLater(); + } + } +} + +void Settings::requestManualAuth() +{ + KPasswordDialog *dlg = new KPasswordDialog( 0 ); + dlg->setModal( true ); + dlg->setPrompt( i18n( "Please enter password for user '%1' on IMAP server '%2'.", + userName(), imapServer() ) ); + dlg->setAttribute( Qt::WA_DeleteOnClose ); + connect( dlg, SIGNAL(finished(int)), this, SLOT(onDialogFinished(int)) ); + dlg->show(); +} + +void Settings::onDialogFinished( int result ) +{ + if ( result == QDialog::Accepted ) { + KPasswordDialog *dlg = qobject_cast( sender() ); + setPassword( dlg->password() ); + emit passwordRequestCompleted( dlg->password(), false ); + } else { + emit passwordRequestCompleted( QString(), true ); + } +} + +QString Settings::password(bool *userRejected) const +{ + if ( userRejected != 0 ) { + *userRejected = false; + } + + if ( !m_password.isEmpty() || + ( mapTransportAuthToKimap( (MailTransport::TransportBase::EnumAuthenticationType::type)authentication() ) == KIMAP::LoginJob::GSSAPI ) ) + return m_password; + Wallet* wallet = Wallet::openWallet( Wallet::NetworkWallet(), m_winId ); + if ( wallet && wallet->isOpen() ) { + if ( wallet->hasFolder( QLatin1String("imap") ) ) { + wallet->setFolder( QLatin1String("imap") ); + wallet->readPassword( config()->name(), m_password ); + } else { + wallet->createFolder( QLatin1String("imap") ); + } + } else if ( userRejected != 0 ) { + *userRejected = true; + } + delete wallet; + return m_password; +} + +QString Settings::sieveCustomPassword(bool *userRejected) const +{ + if ( userRejected != 0 ) { + *userRejected = false; + } + + if ( !m_customSievePassword.isEmpty() ) + return m_customSievePassword; + + Wallet* wallet = Wallet::openWallet( Wallet::NetworkWallet(), m_winId ); + if ( wallet && wallet->isOpen() ) { + if ( wallet->hasFolder( QLatin1String("imap") ) ) { + wallet->setFolder( QLatin1String("imap") ); + wallet->readPassword( QLatin1String("custom_sieve_") + config()->name(), m_customSievePassword ); + } else { + wallet->createFolder( QLatin1String("imap") ); + } + } else if ( userRejected != 0 ) { + *userRejected = true; + } + delete wallet; + return m_customSievePassword; +} + +void Settings::setSieveCustomPassword(const QString & password) +{ + if (m_customSievePassword == password) + return; + m_customSievePassword = password; + Wallet* wallet = Wallet::openWallet( Wallet::NetworkWallet(), m_winId ); + if ( wallet && wallet->isOpen() ) { + if ( !wallet->hasFolder( QLatin1String("imap") ) ) + wallet->createFolder( QLatin1String("imap") ); + wallet->setFolder( QLatin1String("imap") ); + wallet->writePassword( QLatin1String("custom_sieve_") + config()->name(), password ); + kDebug() << "Wallet save: " << wallet->sync(); + } + delete wallet; +} + +void Settings::setPassword( const QString & password ) +{ + if ( password == m_password ) + return; + + if ( mapTransportAuthToKimap( (MailTransport::TransportBase::EnumAuthenticationType::type)authentication() ) == KIMAP::LoginJob::GSSAPI ) + return; + + m_password = password; + Wallet* wallet = Wallet::openWallet( Wallet::NetworkWallet(), m_winId ); + if ( wallet && wallet->isOpen() ) { + if ( !wallet->hasFolder( QLatin1String("imap") ) ) + wallet->createFolder( QLatin1String("imap") ); + wallet->setFolder( QLatin1String("imap") ); + wallet->writePassword( config()->name(), password ); + kDebug() << "Wallet save: " << wallet->sync(); + } + delete wallet; +} + +void Settings::loadAccount( ImapAccount *account ) const +{ + account->setServer( imapServer() ); + if ( imapPort()>=0 ) { + account->setPort( imapPort() ); + } + + account->setUserName( userName() ); + account->setSubscriptionEnabled( subscriptionEnabled() ); + + const QString encryption = safety(); + if ( encryption == QLatin1String("SSL") ) { + account->setEncryptionMode( KIMAP::LoginJob::AnySslVersion ); + } else if ( encryption == QLatin1String("STARTTLS") ) { + //KIMAP confused TLS and STARTTLS, TlsV1 really means "use STARTTLS" + account->setEncryptionMode( KIMAP::LoginJob::TlsV1 ); + } else { + account->setEncryptionMode( KIMAP::LoginJob::Unencrypted ); + } + + //Some SSL Server fail to advertise an ssl version they support (AnySslVersion), + //we therefore allow overriding this in the config + //(so we don't have to make the UI unnecessarily complex for properly working servers). + const QString overrideEncryptionMode = overrideEncryption(); + if (!overrideEncryptionMode.isEmpty()) { + kWarning() << "Overriding encryption mode with: " << overrideEncryptionMode; + if ( overrideEncryptionMode == QLatin1String("SSLV2") ) { + account->setEncryptionMode( KIMAP::LoginJob::SslV2 ); + } else if ( overrideEncryptionMode == QLatin1String("SSLV3") ) { + account->setEncryptionMode( KIMAP::LoginJob::SslV3 ); + } else if ( overrideEncryptionMode == QLatin1String("TLSV1") ) { + account->setEncryptionMode( KIMAP::LoginJob::SslV3_1 ); + } else if ( overrideEncryptionMode == QLatin1String("SSL") ) { + account->setEncryptionMode( KIMAP::LoginJob::AnySslVersion ); + } else if ( overrideEncryptionMode == QLatin1String("STARTTLS") ) { + account->setEncryptionMode( KIMAP::LoginJob::TlsV1 ); + } else if ( overrideEncryptionMode == QLatin1String("UNENCRYPTED") ) { + account->setEncryptionMode( KIMAP::LoginJob::Unencrypted ); + } else { + kWarning() << "Tried to force invalid encryption mode: " << overrideEncryptionMode; + } + } + + account->setAuthenticationMode( + mapTransportAuthToKimap( + (MailTransport::TransportBase::EnumAuthenticationType::type) authentication() + ) + ); + + account->setTimeout( sessionTimeout() ); + +} + +QString Settings::rootRemoteId() const +{ + return QLatin1String("imap://") + userName() + QLatin1Char('@') + imapServer() + QLatin1Char('/'); +} + +void Settings::renameRootCollection( const QString &newName ) +{ + Akonadi::Collection rootCollection; + rootCollection.setRemoteId( rootRemoteId() ); + Akonadi::CollectionFetchJob *fetchJob = + new Akonadi::CollectionFetchJob( rootCollection, Akonadi::CollectionFetchJob::Base ); + fetchJob->setProperty( "collectionName", newName ); + connect( fetchJob, SIGNAL(result(KJob*)), + this, SLOT(onRootCollectionFetched(KJob*)) ); +} + +void Settings::onRootCollectionFetched( KJob *job ) +{ + const QString newName = job->property( "collectionName" ).toString(); + Q_ASSERT( !newName.isEmpty() ); + Akonadi::CollectionFetchJob *fetchJob = static_cast( job ); + if ( fetchJob->collections().size() == 1 ) { + Akonadi::Collection rootCollection = fetchJob->collections().first(); + rootCollection.setName( newName ); + new Akonadi::CollectionModifyJob( rootCollection ); + // We don't care about the result here, nothing we can/should do if the renaming fails + } +} + diff --git a/kdepim-runtime/resources/imap/settings.h b/kdepim-runtime/resources/imap/settings.h new file mode 100644 index 00000000..31d17f9a --- /dev/null +++ b/kdepim-runtime/resources/imap/settings.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2008 Volker Krause + Copyright (c) 2008 Omat Holding B.V. + + 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 SETTINGS_H +#define SETTINGS_H + +#include "settingsbase.h" + +#include + +#include + +class ImapAccount; + +class Settings : public SettingsBase +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.Imap.Wallet" ) +public: + static KIMAP::LoginJob::AuthenticationMode mapTransportAuthToKimap( MailTransport::Transport::EnumAuthenticationType::type authType ); + + explicit Settings( WId = 0 ); + void setWinId( WId ); + + virtual void requestPassword(); + virtual void requestManualAuth(); + + virtual void loadAccount( ImapAccount *account ) const; + + QString rootRemoteId() const; + virtual void renameRootCollection( const QString &newName ); + + virtual void clearCachedPassword(); + virtual void cleanup(); + +signals: + void passwordRequestCompleted( const QString &password, bool userRejected ); + +public slots: + Q_SCRIPTABLE virtual QString password( bool *userRejected = 0 ) const; + Q_SCRIPTABLE virtual void setPassword( const QString &password ); + Q_SCRIPTABLE virtual void setSieveCustomPassword(const QString & password); + Q_SCRIPTABLE virtual QString sieveCustomPassword( bool *userRejected = 0 ) const; + +protected slots: + virtual void onWalletOpened( bool success ); + virtual void onDialogFinished( int result ); + + void onRootCollectionFetched( KJob *job ); + +protected: + WId m_winId; + mutable QString m_password; + mutable QString m_customSievePassword; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/settingsbase.kcfgc b/kdepim-runtime/resources/imap/settingsbase.kcfgc new file mode 100644 index 00000000..57b9d5f6 --- /dev/null +++ b/kdepim-runtime/resources/imap/settingsbase.kcfgc @@ -0,0 +1,6 @@ +File=imapresource.kcfg +ClassName=SettingsBase +Mutators=true +SetUserTexts=true +Singleton=false +GlobalEnums=true diff --git a/kdepim-runtime/resources/imap/settingspasswordrequester.cpp b/kdepim-runtime/resources/imap/settingspasswordrequester.cpp new file mode 100644 index 00000000..b7959286 --- /dev/null +++ b/kdepim-runtime/resources/imap/settingspasswordrequester.cpp @@ -0,0 +1,142 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "settingspasswordrequester.h" + +#include +#include +#include + +#include +#include + +#include "imapresourcebase.h" +#include "settings.h" + +SettingsPasswordRequester::SettingsPasswordRequester( ImapResourceBase *resource, QObject *parent ) + : PasswordRequesterInterface( parent ), m_resource( resource ), m_requestDialog( 0 ), m_settingsDialog( 0 ) +{ + +} + +SettingsPasswordRequester::~SettingsPasswordRequester() +{ + cancelPasswordRequests(); +} + +void SettingsPasswordRequester::requestPassword( RequestType request, const QString &serverError ) +{ + if ( request == WrongPasswordRequest ) { + QMetaObject::invokeMethod( this, "askUserInput", Qt::QueuedConnection, Q_ARG(QString, serverError) ); + } else { + connect( m_resource->settings(), SIGNAL(passwordRequestCompleted(QString,bool)), + this, SLOT(onPasswordRequestCompleted(QString,bool)) ); + m_resource->settings()->requestPassword(); + } +} + +void SettingsPasswordRequester::askUserInput( const QString &serverError ) +{ + // the credentials were not ok, allow to retry or change password + if ( m_requestDialog ) { + kDebug() << "Password request dialog is already open"; + return; + } + QWidget *parent = QWidget::find(m_resource->winIdForDialogs()); + QString text = i18n( "The server for account \"%2\" refused the supplied username and password. " + "Do you want to go to the settings, have another attempt " + "at logging in, or do nothing?\n\n" + "%1", serverError, m_resource->name() ); + KDialog *dialog = new KDialog(parent, Qt::Dialog); + dialog->setCaption(i18n( "Could Not Authenticate" )); + dialog->setButtons(KDialog::Yes|KDialog::No|KDialog::Cancel); + dialog->setDefaultButton(KDialog::Yes); + dialog->setButtonText(KDialog::Yes, i18n( "Account Settings" )); + dialog->setButtonText(KDialog::No, i18nc( "Input username/password manually and not store them", "Try Again" )); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, SIGNAL(buttonClicked(KDialog::ButtonCode)), this, SLOT(onButtonClicked(KDialog::ButtonCode))); + connect(dialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDialogDestroyed())); + m_requestDialog = dialog; + KWindowSystem::setMainWindow(dialog, m_resource->winIdForDialogs()); + bool checkboxResult = false; + KMessageBox::createKMessageBox(dialog, QMessageBox::Information, + text, QStringList(), + QString(), + &checkboxResult, KMessageBox::NoExec); + dialog->show(); +} + +void SettingsPasswordRequester::onDialogDestroyed() +{ + m_requestDialog = 0; +} + +void SettingsPasswordRequester::onButtonClicked(KDialog::ButtonCode result) +{ + if ( result == KDialog::Yes ) { + if (!m_settingsDialog) { + KDialog *dialog = m_resource->createConfigureDialog(m_resource->winIdForDialogs()); + connect(dialog, SIGNAL(finished(int)), this, SLOT(onSettingsDialogFinished(int))); + m_settingsDialog = dialog; + dialog->show(); + } + } else if ( result == KDialog::No ) { + connect( m_resource->settings(), SIGNAL(passwordRequestCompleted(QString,bool)), + this, SLOT(onPasswordRequestCompleted(QString,bool)) ); + m_resource->settings()->requestManualAuth(); + } else { + emit done( UserRejected ); + } + m_requestDialog = 0; +} + +void SettingsPasswordRequester::onSettingsDialogFinished(int result) +{ + m_settingsDialog = 0; + if (result == QDialog::Accepted) { + emit done( ReconnectNeeded ); + } else { + emit done( UserRejected ); + } +} + +void SettingsPasswordRequester::cancelPasswordRequests() +{ + if (m_requestDialog) { + if (m_requestDialog->close()) { + m_requestDialog = 0; + } + } +} + +void SettingsPasswordRequester::onPasswordRequestCompleted( const QString &password, bool userRejected ) +{ + disconnect( m_resource->settings(), SIGNAL(passwordRequestCompleted(QString,bool)), + this, SLOT(onPasswordRequestCompleted(QString,bool)) ); + + if ( userRejected ) { + emit done( UserRejected ); + } else if ( password.isEmpty() && (m_resource->settings()->authentication() != MailTransport::Transport::EnumAuthenticationType::GSSAPI) ) { + emit done( EmptyPasswordEntered ); + } else { + emit done( PasswordRetrieved, password ); + } +} diff --git a/kdepim-runtime/resources/imap/settingspasswordrequester.h b/kdepim-runtime/resources/imap/settingspasswordrequester.h new file mode 100644 index 00000000..9f9791b7 --- /dev/null +++ b/kdepim-runtime/resources/imap/settingspasswordrequester.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 SETTINGSPASSWORDREQUESTER_H +#define SETTINGSPASSWORDREQUESTER_H + +#include +#include + +class ImapResourceBase; + +class SettingsPasswordRequester : public PasswordRequesterInterface +{ + Q_OBJECT + +public: + explicit SettingsPasswordRequester( ImapResourceBase *resource, QObject *parent = 0 ); + virtual ~SettingsPasswordRequester(); + + virtual void requestPassword( RequestType request = StandardRequest, + const QString &serverError = QString() ); + virtual void cancelPasswordRequests(); + +private slots: + void askUserInput( const QString &serverError ); + void onPasswordRequestCompleted( const QString &password, bool userRejected ); + void onButtonClicked(KDialog::ButtonCode); + void onDialogDestroyed(); + void onSettingsDialogFinished(int result); + +private: + ImapResourceBase *m_resource; + KDialog *m_requestDialog; + KDialog *m_settingsDialog; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/setupserver.cpp b/kdepim-runtime/resources/imap/setupserver.cpp new file mode 100644 index 00000000..26653f69 --- /dev/null +++ b/kdepim-runtime/resources/imap/setupserver.cpp @@ -0,0 +1,660 @@ +/* This file is part of the KDE project + + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + Copyright (C) 2010 Casey Link + Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + Copyright (C) 2009 Kevin Ottens + Copyright (C) 2006-2008 Omat Holding B.V. + Copyright (C) 2006 Frode M. Døving + + Original copied from showfoto: + Copyright 2005 by Gilles Caulier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "setupserver.h" +#include "settings.h" +#include "imapresource.h" +#include "serverinfodialog.h" +#include "resources/folderarchivesettings/folderarchivesettingpage.h" + + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef IMAPRESOURCE_NO_SOLID +#include +#endif + +#include +#include + +#include "imapaccount.h" +#include "subscriptiondialog.h" + +#ifdef KDEPIM_MOBILE_UI +#include "ui_setupserverview_mobile.h" +#else +#include "ui_setupserverview_desktop.h" +#endif +#include "ui_serverinfo.h" + +/** static helper functions **/ +static QString authenticationModeString( MailTransport::Transport::EnumAuthenticationType::type mode ) +{ + switch ( mode ) { + case MailTransport::Transport::EnumAuthenticationType::LOGIN: + return QLatin1String("LOGIN"); + case MailTransport::Transport::EnumAuthenticationType::PLAIN: + return QLatin1String("PLAIN"); + case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5: + return QLatin1String("CRAM-MD5"); + case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5: + return QLatin1String("DIGEST-MD5"); + case MailTransport::Transport::EnumAuthenticationType::GSSAPI: + return QLatin1String("GSSAPI"); + case MailTransport::Transport::EnumAuthenticationType::NTLM: + return QLatin1String("NTLM"); + case MailTransport::Transport::EnumAuthenticationType::CLEAR: + return i18nc( "Authentication method", "Clear text" ); + case MailTransport::Transport::EnumAuthenticationType::ANONYMOUS: + return i18nc( "Authentication method", "Anonymous" ); + default: + break; + } + return QString(); +} + +static void setCurrentAuthMode( QComboBox* authCombo, MailTransport::Transport::EnumAuthenticationType::type authtype ) +{ + kDebug() << "setting authcombo: " << authenticationModeString( authtype ); + int index = authCombo->findData( authtype ); + if ( index == -1 ) + kWarning() << "desired authmode not in the combo"; + kDebug() << "found corresponding index: " << index << "with data" << authenticationModeString( (MailTransport::Transport::EnumAuthenticationType::type) authCombo->itemData( index ).toInt() ); + authCombo->setCurrentIndex( index ); + MailTransport::Transport::EnumAuthenticationType::type t = (MailTransport::Transport::EnumAuthenticationType::type) authCombo->itemData( authCombo->currentIndex() ).toInt(); + kDebug() << "selected auth mode:" << authenticationModeString( t ); + Q_ASSERT( t == authtype ); +} + +static MailTransport::Transport::EnumAuthenticationType::type getCurrentAuthMode( QComboBox* authCombo ) +{ + MailTransport::Transport::EnumAuthenticationType::type authtype = (MailTransport::Transport::EnumAuthenticationType::type) authCombo->itemData( authCombo->currentIndex() ).toInt(); + kDebug() << "current auth mode: " << authenticationModeString( authtype ); + return authtype; +} + +static void addAuthenticationItem( QComboBox* authCombo, MailTransport::Transport::EnumAuthenticationType::type authtype ) +{ + kDebug() << "adding auth item " << authenticationModeString( authtype ); + authCombo->addItem( authenticationModeString( authtype ), QVariant( authtype ) ); +} + +SetupServer::SetupServer( ImapResourceBase *parentResource, WId parent ) + : KDialog(), m_parentResource( parentResource ), m_ui(new Ui::SetupServerView), m_serverTest(0), + m_subscriptionsChanged(false), m_shouldClearCache(false), mValidator( this ) +{ + m_parentResource->settings()->setWinId( parent ); + m_ui->setupUi( mainWidget() ); + m_folderArchiveSettingPage = new FolderArchiveSettingPage(m_parentResource->identifier()); + m_ui->tabWidget->addTab(m_folderArchiveSettingPage, i18n("Folder Archive")); + m_ui->safeImapGroup->setId( m_ui->noRadio, KIMAP::LoginJob::Unencrypted ); + m_ui->safeImapGroup->setId( m_ui->sslRadio, KIMAP::LoginJob::AnySslVersion ); + m_ui->safeImapGroup->setId( m_ui->tlsRadio, KIMAP::LoginJob::TlsV1 ); + + connect( m_ui->noRadio, SIGNAL(toggled(bool)), + this, SLOT(slotSafetyChanged()) ); + connect( m_ui->sslRadio, SIGNAL(toggled(bool)), + this, SLOT(slotSafetyChanged()) ); + connect( m_ui->tlsRadio, SIGNAL(toggled(bool)), + this, SLOT(slotSafetyChanged()) ); + + m_ui->testInfo->hide(); + m_ui->testProgress->hide(); + m_ui->accountName->setFocus(); + m_ui->checkInterval->setSuffix( ki18np( " minute", " minutes" ) ); + m_ui->checkInterval->setRange( Akonadi::ResourceSettings::self()->minimumCheckInterval(), 10000, 1 ); + + // regex for evaluating a valid server name/ip + mValidator.setRegExp( QRegExp( QLatin1String("[A-Za-z0-9-_:.]*") ) ); + m_ui->imapServer->setValidator( &mValidator ); + + m_ui->folderRequester->setMimeTypeFilter( + QStringList() << KMime::Message::mimeType() ); + m_ui->folderRequester->setAccessRightsFilter( Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanCreateItem | Akonadi::Collection::CanDeleteItem ); + m_ui->folderRequester->changeCollectionDialogOptions( Akonadi::CollectionDialog::AllowToCreateNewChildCollection ); + m_identityManager = new KPIMIdentities::IdentityManager( false, this, "mIdentityManager" ); + m_identityCombobox = new KPIMIdentities::IdentityCombo( m_identityManager, this ); + m_ui->identityLabel->setBuddy( m_identityCombobox ); + m_ui->identityLayout->addWidget( m_identityCombobox, 1 ); + m_ui->identityLabel->setBuddy( m_identityCombobox ); + + + connect( m_ui->testButton, SIGNAL(pressed()), SLOT(slotTest()) ); + + connect( m_ui->imapServer, SIGNAL(textChanged(QString)), + SLOT(slotTestChanged()) ); + connect( m_ui->imapServer, SIGNAL(textChanged(QString)), + SLOT(slotComplete()) ); + connect( m_ui->userName, SIGNAL(textChanged(QString)), + SLOT(slotComplete()) ); + connect( m_ui->subscriptionEnabled, SIGNAL(toggled(bool)), this, SLOT(slotSubcriptionCheckboxChanged()) ); + connect( m_ui->subscriptionButton, SIGNAL(pressed()), SLOT(slotManageSubscriptions()) ); + + connect( m_ui->managesieveCheck, SIGNAL(toggled(bool)), + SLOT(slotEnableWidgets()) ); + connect( m_ui->sameConfigCheck, SIGNAL(toggled(bool)), + SLOT(slotEnableWidgets()) ); + + + connect( m_ui->useDefaultIdentityCheck, SIGNAL(toggled(bool)), this, SLOT(slotIdentityCheckboxChanged()) ); + connect( m_ui->enableMailCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotMailCheckboxChanged()) ); + connect( m_ui->safeImapGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotEncryptionRadioChanged()) ); + connect( m_ui->customSieveGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCustomSieveChanged()) ); + connect( m_ui->showServerInfo, SIGNAL(pressed()), this, SLOT(slotShowServerInfo()) ); + + readSettings(); + slotTestChanged(); + slotComplete(); + slotCustomSieveChanged(); +#ifndef IMAPRESOURCE_NO_SOLID + connect( Solid::Networking::notifier(), + SIGNAL(statusChanged(Solid::Networking::Status)), + SLOT(slotTestChanged()) ); +#endif + connect( this, SIGNAL(applyClicked()), + SLOT(applySettings()) ); + connect( this, SIGNAL(okClicked()), + SLOT(applySettings()) ); +} + +SetupServer::~SetupServer() +{ + delete m_ui; +} + +bool SetupServer::shouldClearCache() const +{ + return m_shouldClearCache; +} + +void SetupServer::slotSubcriptionCheckboxChanged() +{ + m_ui->subscriptionButton->setEnabled( m_ui->subscriptionEnabled->isChecked() ); +} + +void SetupServer::slotIdentityCheckboxChanged() +{ + m_identityCombobox->setEnabled( !m_ui->useDefaultIdentityCheck->isChecked() ); +} + +void SetupServer::slotMailCheckboxChanged() +{ + m_ui->checkInterval->setEnabled( m_ui->enableMailCheckBox->isChecked() ); +} + +void SetupServer::slotEncryptionRadioChanged() +{ + // TODO these really should be defined somewhere else + switch ( m_ui->safeImapGroup->checkedId() ) { + case KIMAP::LoginJob::Unencrypted: + case KIMAP::LoginJob::TlsV1: + m_ui->portSpin->setValue( 143 ); + break; + case KIMAP::LoginJob::AnySslVersion: + m_ui->portSpin->setValue( 993 ); + break; + default: + kFatal() << "Shouldn't happen"; + } +} + +void SetupServer::slotCustomSieveChanged() +{ + QAbstractButton *checkedButton = m_ui->customSieveGroup->checkedButton(); + + if (checkedButton == m_ui->imapUserPassword || + checkedButton == m_ui->noAuthentification ) { + m_ui->customUsername->setEnabled(false); + m_ui->customPassword->setEnabled(false); + } else if (checkedButton == m_ui->customUserPassword) { + m_ui->customUsername->setEnabled(true); + m_ui->customPassword->setEnabled(true); + } +} + +void SetupServer::applySettings() +{ + m_folderArchiveSettingPage->writeSettings(); + m_shouldClearCache = ( m_parentResource->settings()->imapServer() != m_ui->imapServer->text() ) + || ( m_parentResource->settings()->userName() != m_ui->userName->text() ); + + m_parentResource->setName( m_ui->accountName->text() ); + + m_parentResource->settings()->setImapServer( m_ui->imapServer->text() ); + m_parentResource->settings()->setImapPort( m_ui->portSpin->value() ); + m_parentResource->settings()->setUserName( m_ui->userName->text() ); + QString encryption; + switch ( m_ui->safeImapGroup->checkedId() ) { + case KIMAP::LoginJob::Unencrypted : + encryption = QLatin1String("None"); + break; + case KIMAP::LoginJob::AnySslVersion: + encryption = QLatin1String("SSL"); + break; + case KIMAP::LoginJob::TlsV1: + encryption = QLatin1String("STARTTLS"); + break; + default: + kFatal() << "Shouldn't happen"; + } + m_parentResource->settings()->setSafety( encryption ); + MailTransport::Transport::EnumAuthenticationType::type authtype = getCurrentAuthMode( m_ui->authenticationCombo ); + kDebug() << "saving IMAP auth mode: " << authenticationModeString( authtype ); + m_parentResource->settings()->setAuthentication( authtype ); + m_parentResource->settings()->setPassword( m_ui->password->text() ); + m_parentResource->settings()->setSubscriptionEnabled( m_ui->subscriptionEnabled->isChecked() ); + m_parentResource->settings()->setIntervalCheckTime( m_ui->checkInterval->value() ); + m_parentResource->settings()->setDisconnectedModeEnabled( m_ui->disconnectedModeEnabled->isChecked() ); + + m_parentResource->settings()->setSieveSupport( m_ui->managesieveCheck->isChecked() ); + m_parentResource->settings()->setSieveReuseConfig( m_ui->sameConfigCheck->isChecked() ); + m_parentResource->settings()->setSievePort( m_ui->sievePortSpin->value() ); + m_parentResource->settings()->setSieveAlternateUrl( m_ui->alternateURL->text() ); + m_parentResource->settings()->setSieveVacationFilename( m_vacationFileName ); + + m_parentResource->settings()->setTrashCollection( m_ui->folderRequester->collection().id() ); + Akonadi::Collection trash = m_ui->folderRequester->collection(); + Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::Trash, trash); + Akonadi::EntityDisplayAttribute *attribute = trash.attribute( Akonadi::Entity::AddIfMissing ); + attribute->setIconName( QLatin1String( "user-trash" ) ); + new Akonadi::CollectionModifyJob( trash ); + if (mOldTrash != trash) { + Akonadi::SpecialMailCollections::self()->unregisterCollection(mOldTrash); + } + + m_parentResource->settings()->setAutomaticExpungeEnabled( m_ui->autoExpungeCheck->isChecked() ); + m_parentResource->settings()->setUseDefaultIdentity( m_ui->useDefaultIdentityCheck->isChecked() ); + + if ( !m_ui->useDefaultIdentityCheck->isChecked() ) + m_parentResource->settings()->setAccountIdentity( m_identityCombobox->currentIdentity() ); + + m_parentResource->settings()->setIntervalCheckEnabled( m_ui->enableMailCheckBox->isChecked() ); + if ( m_ui->enableMailCheckBox->isChecked() ) + m_parentResource->settings()->setIntervalCheckTime( m_ui->checkInterval->value() ); + + m_parentResource->settings()->setSieveCustomUsername(m_ui->customUsername->text()); + + QAbstractButton *checkedButton = m_ui->customSieveGroup->checkedButton(); + + if (checkedButton == m_ui->imapUserPassword) { + m_parentResource->settings()->setSieveCustomAuthentification(QLatin1String("ImapUserPassword")); + } else if (checkedButton == m_ui->noAuthentification) { + m_parentResource->settings()->setSieveCustomAuthentification(QLatin1String("NoAuthentification")); + } else if (checkedButton == m_ui->customUserPassword) { + m_parentResource->settings()->setSieveCustomAuthentification(QLatin1String("CustomUserPassword")); + } + + m_parentResource->settings()->setSieveCustomPassword( m_ui->customPassword->text() ); + + m_parentResource->settings()->writeConfig(); + kDebug() << "wrote" << m_ui->imapServer->text() << m_ui->userName->text() << m_ui->safeImapGroup->checkedId(); + + if ( m_oldResourceName != m_ui->accountName->text() && !m_ui->accountName->text().isEmpty() ) { + m_parentResource->settings()->renameRootCollection( m_ui->accountName->text() ); + } +} + +void SetupServer::readSettings() +{ + m_folderArchiveSettingPage->loadSettings(); + m_ui->accountName->setText( m_parentResource->name() ); + m_oldResourceName = m_ui->accountName->text(); + + KUser* currentUser = new KUser(); + KEMailSettings esetting; + + m_ui->imapServer->setText( + !m_parentResource->settings()->imapServer().isEmpty() ? m_parentResource->settings()->imapServer() : + esetting.getSetting( KEMailSettings::InServer ) ); + + m_ui->portSpin->setValue( m_parentResource->settings()->imapPort() ); + + m_ui->userName->setText( + !m_parentResource->settings()->userName().isEmpty() ? m_parentResource->settings()->userName() : + currentUser->loginName() ); + + const QString safety = m_parentResource->settings()->safety(); + int i = 0; + if ( safety == QLatin1String( "SSL" ) ) + i = KIMAP::LoginJob::AnySslVersion; + else if ( safety == QLatin1String( "STARTTLS" ) ) + i = KIMAP::LoginJob::TlsV1; + else + i = KIMAP::LoginJob::Unencrypted; + + QAbstractButton* safetyButton = m_ui->safeImapGroup->button( i ); + if ( safetyButton ) + safetyButton->setChecked( true ); + + populateDefaultAuthenticationOptions(); + i = m_parentResource->settings()->authentication(); + kDebug() << "read IMAP auth mode: " << authenticationModeString( (MailTransport::Transport::EnumAuthenticationType::type) i ); + setCurrentAuthMode( m_ui->authenticationCombo, (MailTransport::Transport::EnumAuthenticationType::type) i ); + + bool rejected = false; + const QString password = m_parentResource->settings()->password( &rejected ); + if ( rejected ) { + m_ui->password->setEnabled( false ); + KMessageBox::information( 0, i18n( "Could not access KWallet. " + "If you want to store the password permanently then you have to " + "activate it. If you do not " + "want to use KWallet, check the box below, but note that you will be " + "prompted for your password when needed." ), + i18n( "Do not use KWallet" ), QLatin1String("warning_kwallet_disabled") ); + } else { + m_ui->password->insert( password ); + } + + m_ui->subscriptionEnabled->setChecked( m_parentResource->settings()->subscriptionEnabled() ); + + m_ui->checkInterval->setValue( m_parentResource->settings()->intervalCheckTime() ); + m_ui->disconnectedModeEnabled->setChecked( m_parentResource->settings()->disconnectedModeEnabled() ); + + m_ui->managesieveCheck->setChecked( m_parentResource->settings()->sieveSupport() ); + m_ui->sameConfigCheck->setChecked( m_parentResource->settings()->sieveReuseConfig() ); + m_ui->sievePortSpin->setValue( m_parentResource->settings()->sievePort() ); + m_ui->alternateURL->setText( m_parentResource->settings()->sieveAlternateUrl() ); + m_vacationFileName = m_parentResource->settings()->sieveVacationFilename(); + + + Akonadi::Collection trashCollection( m_parentResource->settings()->trashCollection() ); + if ( trashCollection.isValid() ) { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob( trashCollection,Akonadi::CollectionFetchJob::Base,this ); + connect( fetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), + this, SLOT(targetCollectionReceived(Akonadi::Collection::List)) ); + } else { + Akonadi::SpecialMailCollectionsRequestJob *requestJob = new Akonadi::SpecialMailCollectionsRequestJob( this ); + connect ( requestJob, SIGNAL(result(KJob*)), + this, SLOT(localFolderRequestJobFinished(KJob*)) ); + requestJob->requestDefaultCollection( Akonadi::SpecialMailCollections::Trash ); + requestJob->start(); + } + + m_ui->useDefaultIdentityCheck->setChecked( m_parentResource->settings()->useDefaultIdentity() ); + if ( !m_ui->useDefaultIdentityCheck->isChecked() ) + m_identityCombobox->setCurrentIdentity( m_parentResource->settings()->accountIdentity() ); + + m_ui->enableMailCheckBox->setChecked( m_parentResource->settings()->intervalCheckEnabled() ); + if ( m_ui->enableMailCheckBox->isChecked() ) + m_ui->checkInterval->setValue( m_parentResource->settings()->intervalCheckTime() ); + else + m_ui->checkInterval->setEnabled( false ); + + m_ui->autoExpungeCheck->setChecked( m_parentResource->settings()->automaticExpungeEnabled() ); + + if ( m_vacationFileName.isEmpty() ) + m_vacationFileName = QLatin1String("kmail-vacation.siv"); + + m_ui->customUsername->setText(m_parentResource->settings()->sieveCustomUsername()); + + m_ui->customPassword->setText(m_parentResource->settings()->sieveCustomPassword()); + + + const QString sieverCustomAuth(m_parentResource->settings()->sieveCustomAuthentification()); + if (sieverCustomAuth == QLatin1String("ImapUserPassword")) + m_ui->imapUserPassword->setChecked(true); + else if (sieverCustomAuth == QLatin1String("NoAuthentification")) + m_ui->noAuthentification->setChecked(true); + else if (sieverCustomAuth == QLatin1String("CustomUserPassword")) + m_ui->customUserPassword->setChecked(true); + + delete currentUser; +} + +void SetupServer::slotTest() +{ + kDebug() << m_ui->imapServer->text(); + + m_ui->testButton->setEnabled( false ); + m_ui->safeImap->setEnabled( false ); + m_ui->authenticationCombo->setEnabled( false ); + + m_ui->testInfo->clear(); + m_ui->testInfo->hide(); + + delete m_serverTest; + m_serverTest = new MailTransport::ServerTest( this ); +#ifndef QT_NO_CURSOR + qApp->setOverrideCursor( Qt::BusyCursor ); +#endif + + const QString server = m_ui->imapServer->text(); + const int port = m_ui->portSpin->value(); + kDebug() << "server: " << server << "port: " << port; + + m_serverTest->setServer( server ); + + if ( port != 143 && port != 993 ) { + m_serverTest->setPort( MailTransport::Transport::EnumEncryption::None, port ); + m_serverTest->setPort( MailTransport::Transport::EnumEncryption::SSL, port ); + } + + m_serverTest->setProtocol( QLatin1String("imap") ); + m_serverTest->setProgressBar( m_ui->testProgress ); + connect( m_serverTest, SIGNAL(finished(QList)), + SLOT(slotFinished(QList)) ); + enableButtonOk( false ); + m_serverTest->start(); +} + +void SetupServer::slotFinished( const QList &testResult ) +{ + kDebug() << testResult; + +#ifndef QT_NO_CURSOR + qApp->restoreOverrideCursor(); +#endif + enableButtonOk( true ); + + using namespace MailTransport; + + if ( !m_serverTest->isNormalPossible() && !m_serverTest->isSecurePossible() ) + KMessageBox::sorry( this, i18n( "Unable to connect to the server, please verify the server address." ) ); + + m_ui->testInfo->show(); + + m_ui->sslRadio->setEnabled( testResult.contains( Transport::EnumEncryption::SSL ) ); + m_ui->tlsRadio->setEnabled( testResult.contains( Transport::EnumEncryption::TLS ) ); + m_ui->noRadio->setEnabled( testResult.contains( Transport::EnumEncryption::None ) ); + + QString text; + if ( testResult.contains( Transport::EnumEncryption::TLS ) ) { + m_ui->tlsRadio->setChecked( true ); + text = i18n( "TLS is supported and recommended." ); + } else if ( testResult.contains( Transport::EnumEncryption::SSL ) ) { + m_ui->sslRadio->setChecked( true ); + text = i18n( "SSL is supported and recommended." ); + } else if ( testResult.contains( Transport::EnumEncryption::None ) ) { + m_ui->noRadio->setChecked( true ); + text = i18n( "No security is supported. It is not " + "recommended to connect to this server." ); + } else { + text = i18n( "It is not possible to use this server." ); + } + m_ui->testInfo->setText( text ); + + m_ui->testButton->setEnabled( true ); + m_ui->safeImap->setEnabled( true ); + m_ui->authenticationCombo->setEnabled( true ); + slotEncryptionRadioChanged(); + slotSafetyChanged(); +} + +void SetupServer::slotTestChanged() +{ + delete m_serverTest; + m_serverTest = 0; + slotSafetyChanged(); + + // do not use imapConnectionPossible, as the data is not yet saved. + m_ui->testButton->setEnabled( true /* TODO Global::connectionPossible() || + m_ui->imapServer->text() == "localhost"*/ ); +} + +void SetupServer::slotEnableWidgets() +{ + const bool haveSieve = m_ui->managesieveCheck->isChecked(); + const bool reuseConfig = m_ui->sameConfigCheck->isChecked(); + + m_ui->sameConfigCheck->setEnabled( haveSieve ); + m_ui->sievePortSpin->setEnabled( haveSieve && reuseConfig ); + m_ui->alternateURL->setEnabled( haveSieve && !reuseConfig ); + m_ui->authentification->setEnabled( haveSieve && !reuseConfig ); +} + +void SetupServer::slotComplete() +{ + const bool ok = !m_ui->imapServer->text().isEmpty() && !m_ui->userName->text().isEmpty(); + button( KDialog::Ok )->setEnabled( ok ); +} + +void SetupServer::slotSafetyChanged() +{ + if ( m_serverTest == 0 ) { + kDebug() << "serverTest null"; + m_ui->noRadio->setEnabled( true ); + m_ui->sslRadio->setEnabled( true ); + m_ui->tlsRadio->setEnabled( true ); + + m_ui->authenticationCombo->setEnabled( true ); + return; + } + + QList protocols; + + switch ( m_ui->safeImapGroup->checkedId() ) { + case KIMAP::LoginJob::Unencrypted : + kDebug() << "safeImapGroup: unencrypted"; + protocols = m_serverTest->normalProtocols(); + break; + case KIMAP::LoginJob::AnySslVersion: + protocols = m_serverTest->secureProtocols(); + kDebug() << "safeImapGroup: SSL"; + break; + case KIMAP::LoginJob::TlsV1: + protocols = m_serverTest->tlsProtocols(); + kDebug() << "safeImapGroup: starttls"; + break; + default: + kFatal() << "Shouldn't happen"; + } + + m_ui->authenticationCombo->clear(); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::CLEAR ); + foreach ( int prot, protocols ) { + addAuthenticationItem( m_ui->authenticationCombo, (MailTransport::Transport::EnumAuthenticationType::type) prot ); + } + if ( protocols.size() > 0 ) + setCurrentAuthMode( m_ui->authenticationCombo, (MailTransport::Transport::EnumAuthenticationType::type) protocols.first() ); + else + kDebug() << "no authmodes found"; +} + +void SetupServer::slotManageSubscriptions() +{ + kDebug() << "manage subscripts"; + ImapAccount account; + + account.setServer( m_ui->imapServer->text() ); + account.setPort( m_ui->portSpin->value() ); + + account.setUserName( m_ui->userName->text() ); + account.setSubscriptionEnabled( m_ui->subscriptionEnabled->isChecked() ); + + account.setEncryptionMode( + static_cast( m_ui->safeImapGroup->checkedId() ) + ); + + account.setAuthenticationMode( Settings::mapTransportAuthToKimap( getCurrentAuthMode( m_ui->authenticationCombo ) ) ); + + QPointer subscriptions = new SubscriptionDialog( this ); + subscriptions->setCaption( i18n( "Serverside Subscription" ) ); + subscriptions->setWindowIcon( KIcon( QLatin1String("network-server") ) ); + subscriptions->connectAccount( account, m_ui->password->text() ); + m_subscriptionsChanged = subscriptions->isSubscriptionChanged(); + + subscriptions->exec(); + delete subscriptions; + + m_ui->subscriptionEnabled->setChecked( account.isSubscriptionEnabled() ); +} + +void SetupServer::slotShowServerInfo() +{ + ServerInfoDialog *dialog = new ServerInfoDialog(m_parentResource, this); + dialog->show(); +} + +void SetupServer::targetCollectionReceived( const Akonadi::Collection::List &collections ) +{ + m_ui->folderRequester->setCollection( collections.first() ); + mOldTrash = collections.first(); +} + +void SetupServer::localFolderRequestJobFinished( KJob *job ) +{ + if ( !job->error() ) { + Akonadi::Collection targetCollection = Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Trash ); + Q_ASSERT( targetCollection.isValid() ); + m_ui->folderRequester->setCollection( targetCollection ); + mOldTrash = targetCollection; + } +} + +void SetupServer::populateDefaultAuthenticationOptions() +{ + m_ui->authenticationCombo->clear(); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::CLEAR); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::LOGIN ); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::PLAIN ); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::CRAM_MD5 ); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5 ); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::NTLM ); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::GSSAPI ); + addAuthenticationItem( m_ui->authenticationCombo, MailTransport::Transport::EnumAuthenticationType::ANONYMOUS ); +} + + + + diff --git a/kdepim-runtime/resources/imap/setupserver.h b/kdepim-runtime/resources/imap/setupserver.h new file mode 100644 index 00000000..c5650f35 --- /dev/null +++ b/kdepim-runtime/resources/imap/setupserver.h @@ -0,0 +1,113 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Casey Link + Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + Copyright (C) 2009 Kevin Ottens + Copyright (C) 2006-2008 Omat Holding B.V. + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef SETUPSERVER_H +#define SETUPSERVER_H + +#include +#include +#include +#include + +#include + +namespace Ui +{ +class SetupServerView; +} + +namespace MailTransport +{ +class ServerTest; +} +namespace KPIMIdentities +{ +class IdentityCombo; +class IdentityManager; +} +class FolderArchiveSettingPage; +class ImapResourceBase; + +/** + * @class SetupServer + * These contain the account settings + * @author Tom Albers + */ +class SetupServer : public KDialog +{ + Q_OBJECT + +public: + /** + * Constructor + * @param parentResource The resource this dialog belongs to + * @param parent Parent WId + */ + SetupServer( ImapResourceBase *parentResource, WId parent ); + + /** + * Destructor + */ + ~SetupServer(); + + bool shouldClearCache() const; + +private slots: + /** + * Call this if you want the settings saved from this page. + */ + void applySettings(); + void slotIdentityCheckboxChanged(); + void slotMailCheckboxChanged(); + void slotEncryptionRadioChanged(); + void slotSubcriptionCheckboxChanged(); + void slotShowServerInfo(); +private: + void readSettings(); + void populateDefaultAuthenticationOptions(); + + ImapResourceBase *m_parentResource; + Ui::SetupServerView *m_ui; + MailTransport::ServerTest *m_serverTest; + bool m_subscriptionsChanged; + bool m_shouldClearCache; + QString m_vacationFileName; + KPIMIdentities::IdentityManager *m_identityManager; + KPIMIdentities::IdentityCombo *m_identityCombobox; + QString m_oldResourceName; + QRegExpValidator mValidator; + Akonadi::Collection mOldTrash; + FolderArchiveSettingPage *m_folderArchiveSettingPage; + +private slots: + void slotTest(); + void slotFinished(const QList &testResult ); + void slotCustomSieveChanged(); + + void slotTestChanged(); + void slotComplete(); + void slotSafetyChanged(); + void slotManageSubscriptions(); + void slotEnableWidgets(); + void targetCollectionReceived(const Akonadi::Collection::List &collections ); + void localFolderRequestJobFinished( KJob *job ); +}; + +#endif diff --git a/kdepim-runtime/resources/imap/setupserverview_desktop.ui b/kdepim-runtime/resources/imap/setupserverview_desktop.ui new file mode 100644 index 00000000..92a35abf --- /dev/null +++ b/kdepim-runtime/resources/imap/setupserverview_desktop.ui @@ -0,0 +1,653 @@ + + + SetupServerView + + + + 0 + 0 + 454 + 518 + + + + + + + 0 + + + + General + + + + + + Account Information + + + + + + Account Name: + + + + + + + true + + + + + + + Indicate the IMAP server. If you want to connect to a non-standard port for a specific encryption scheme, you can add ":port" to indicate that. For example: "imap.foo.com:144". + + + IMAP Server: + + + imapServer + + + + + + + true + + + + + + + The username. + + + Username: + + + userName + + + + + + + true + + + + + + + The password. + + + Password: + + + password + + + + + + + QLineEdit::Password + + + true + + + + + + + + + + + Mail Checking Options + + + + + + &Download all messages for offline use + + + + + + + Enable &interval mail checking + + + + + + + + + Check mail interval: + + + + + + + 1 + + + 0 + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Filtering + + + + + + Server supports Sieve + + + + + + + Reuse host and login configuration + + + + + + + The server port changed when ManageSieve turned into a full RFC Standard. Old server implementations still use port 2000, while newer standard conform server can only be accessed via port 4190. + + + true + + + + + + + + + Managesieve port: + + + + + + + 65535 + + + + + + + + + + + Alternate URL: + + + + + + + true + + + + + + + + + + Authentication + + + + + + QLineEdit::Password + + + true + + + + + + + No authentication + + + customSieveGroup + + + + + + + Password: + + + + + + + Username: + + + + + + + Username and Password + + + customSieveGroup + + + + + + + true + + + + + + + IMAP Username and Password + + + true + + + customSieveGroup + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Advanced + + + + + + IMAP Settings + + + + + + + + Enable server-side subscriptions + + + + + + + false + + + Serverside Subscription... + + + + + + + + + Automaticall&y compact folders (expunges deleted messages) + + + + + + + + + Trash folder: + + + + + + + + + + + + Identity Settings + + + + + + Use the default identity for this account + + + Use &default identity + + + + + + + + + Select the KMail identity used for this account + + + Identity: + + + + + + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'HandelGotDLig'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">SSL/TLS</span><span style=" font-family:'Sans Serif';"> is safe IMAP over port 993;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">STARTTLS</span><span style=" font-family:'Sans Serif';"> will operate on port 143 and switch to a secure connection directly after connecting;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">None</span><span style=" font-family:'Sans Serif';"> will connect to port 143 but not switch to a secure connection. This setting is not recommended.</span></p></body></html> + + + Connection Settings + + + + + + Auto Detect + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Encryption: + + + + + + + + + None + + + true + + + safeImapGroup + + + + + + + SSL/TLS + + + safeImapGroup + + + + + + + STARTTLS + + + safeImapGroup + + + + + + + + + Port: + + + + + + + 1 + + + 65534 + + + 143 + + + + + + + Authentication: + + + + + + + + Clear text + + + + + LOGIN + + + + + PLAIN + + + + + CRAM-MD5 + + + + + DIGEST-MD5 + + + + + NTLM + + + + + GSSAPI + + + + + Anonymous + + + + + + + + + + + + + + 75 + true + + + + Empty + + + Qt::AlignCenter + + + true + + + + + + + 24 + + + + + + + Server Info + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+ 1 +
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KIntSpinBox + QSpinBox +
knuminput.h
+
+ + KIntNumInput + QWidget +
knuminput.h
+
+ + Akonadi::CollectionRequester + QFrame +
Akonadi/CollectionRequester
+ 1 +
+
+ + + + + + +
diff --git a/kdepim-runtime/resources/imap/setupserverview_mobile.ui b/kdepim-runtime/resources/imap/setupserverview_mobile.ui new file mode 100644 index 00000000..4760fec8 --- /dev/null +++ b/kdepim-runtime/resources/imap/setupserverview_mobile.ui @@ -0,0 +1,514 @@ + + + SetupServerView + + + + 0 + 0 + 307 + 774 + + + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 307 + 774 + + + + + + + + + Account Name: + + + + + + + + + + Indicate the IMAP server. If you want to connect to a non-standard port for a specific encryption scheme, you can add ":port" to indicate that. For example: "imap.foo.com:144". + + + IMAP server: + + + imapServer + + + + + + + + + + + + + 75 + true + + + + Empty + + + Qt::AlignCenter + + + + + + + 24 + + + + + + + Auto Detect + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">SSL</span> is safe IMAP over port 993;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">TLS</span> will operate on port 143 and switch to a secure connection directly after connecting;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">None</span> will connect to port 143 but not switch to a secure connection. This setting is not recommended.</p></body></html> + + + Encryption + + + true + + + + + + None + + + true + + + safeImapGroup + + + + + + + SSL + + + safeImapGroup + + + + + + + TLS + + + safeImapGroup + + + + + + + + + + + + The username. + + + Username: + + + userName + + + + + + + + + + The password. + + + Password: + + + password + + + + + + + QLineEdit::Password + + + + + + + Port: + + + portSpin + + + + + + + 65536 + + + 143 + + + + + + + + + Authentication Method + + + true + + + + + + + Clear text + + + + + LOGIN + + + + + PLAIN + + + + + CRAM-MD5 + + + + + DIGEST-MD5 + + + + + NTLM + + + + + GSSAPI + + + + + Anonymous + + + + + + + + + + + Enable interval mail checking + + + + + + + + + Check interval: + + + + + + + 1 + + + 0 + + + minutes + + + + + + + + + + + + &Download all messages for offline use + + + + + + + Enable Server-Side Subscriptions + + + + + + + false + + + Serverside Subscription... + + + + + + + Automaticall&y expunge deleted messages + + + + + + + + + Trash folder: + + + + + + + + + + + + Use the default identity for this account + + + Use &default identity + + + + + + + + + + true + + + + + + + + Select the KMail identity used for this account + + + Identity: + + + + + + + + + + + + Server supports Sieve + + + + + + + Reuse host and login configuration + + + + + + + + + Alternate URL: + + + alternateURL + + + + + + + + + + Managesieve port: + + + sievePortSpin + + + + + + + 65535 + + + + + + + + + Server Info + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KLineEdit + QLineEdit +
klineedit.h
+ 1 +
+ + KIntNumInput + QWidget +
knuminput.h
+
+ + Akonadi::CollectionRequester + QFrame +
Akonadi/CollectionRequester
+ 1 +
+
+ + + + subscriptionEnabled + toggled(bool) + subscriptionButton + setEnabled(bool) + + + 131 + 431 + + + 331 + 432 + + + + + + + +
diff --git a/kdepim-runtime/resources/imap/subscriptiondialog.cpp b/kdepim-runtime/resources/imap/subscriptiondialog.cpp new file mode 100644 index 00000000..98897002 --- /dev/null +++ b/kdepim-runtime/resources/imap/subscriptiondialog.cpp @@ -0,0 +1,466 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "subscriptiondialog.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "imapaccount.h" +#include "sessionuiproxy.h" + +#ifndef KDEPIM_MOBILE_UI +#include +#include +#include +#else +#include +#include +#include + +class CheckableFilterProxyModel : public QSortFilterProxyModel +{ +public: + CheckableFilterProxyModel( QObject *parent = 0 ) + : QSortFilterProxyModel( parent ) { } + +protected: + /*reimp*/ bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const + { + QModelIndex sourceIndex = sourceModel()->index( sourceRow, 0, sourceParent ); + return sourceModel()->flags(sourceIndex) & Qt::ItemIsUserCheckable; + } +}; + + +#endif + + + +SubscriptionDialog::SubscriptionDialog( QWidget *parent, SubscriptionDialog::SubscriptionDialogOptions option ) + : KDialog( parent ), + m_session( 0 ), + m_subscriptionChanged( false ), + m_lineEdit( 0 ), + m_filter( new SubscriptionFilterProxyModel( this ) ), + m_model( new QStandardItemModel( this ) ) +{ + setModal( true ); + setButtons( Ok | Cancel | User1 ); + + setButtonText( User1, i18nc( "@action:button", "Reload &List" ) ); + enableButton( User1, false ); + connect( this, SIGNAL(user1Clicked()), + this, SLOT(onReloadRequested()) ); + + QWidget *mainWidget = new QWidget( this ); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainWidget->setLayout( mainLayout ); + setMainWidget( mainWidget ); + + m_enableSubscription = new QCheckBox( i18nc( "@option:check", + "Enable server-side subscriptions" ) ); + mainLayout->addWidget( m_enableSubscription ); + + QHBoxLayout *filterBarLayout = new QHBoxLayout; + mainLayout->addLayout( filterBarLayout ); + +#ifndef KDEPIM_MOBILE_UI + filterBarLayout->addWidget( new QLabel( i18nc( "@label search for a subscription", + "Search:" ) ) ); +#endif + + m_lineEdit = new KLineEdit( mainWidget ); + m_lineEdit->setClearButtonShown( true ); + connect( m_lineEdit, SIGNAL(textChanged(QString)), + this, SLOT(slotSearchPattern(QString)) ); + filterBarLayout->addWidget( m_lineEdit ); + m_lineEdit->setFocus(); + +#ifndef KDEPIM_MOBILE_UI + QCheckBox *checkBox = new QCheckBox( i18nc( "@option:check", "Subscribed only" ), mainWidget ); + connect( checkBox, SIGNAL(stateChanged(int)), + m_filter, SLOT(setIncludeCheckedOnly(int)) ); + filterBarLayout->addWidget( checkBox ); + + m_treeView = new QTreeView( mainWidget ); + m_treeView->header()->hide(); + m_filter->setSourceModel( m_model ); + m_treeView->setModel( m_filter ); + mainLayout->addWidget( m_treeView ); +#else + m_lineEdit->hide(); + connect( m_lineEdit, SIGNAL(textChanged(QString)), + this, SLOT(onMobileLineEditChanged(QString)) ); + + m_listView = new QListView( mainWidget ); + + KDescendantsProxyModel *flatModel = new KDescendantsProxyModel( m_listView ); + flatModel->setDisplayAncestorData( true ); + flatModel->setAncestorSeparator( QLatin1String("/") ); + flatModel->setSourceModel( m_model ); + + CheckableFilterProxyModel *checkableModel = new CheckableFilterProxyModel( m_listView ); + checkableModel->setSourceModel( flatModel ); + + m_filter->setSourceModel( checkableModel ); + + m_listView->setModel( m_filter ); + mainLayout->addWidget( m_listView ); + + // We want to get all the keyboard input all the time + grabKeyboard(); +#endif + + connect( m_model, SIGNAL(itemChanged(QStandardItem*)), + this, SLOT(onItemChanged(QStandardItem*)) ); + + if ( option & SubscriptionDialog::AllowToEnableSubscription ) { +#ifndef KDEPIM_MOBILE_UI + connect( m_enableSubscription, SIGNAL(clicked(bool)), m_treeView, SLOT(setEnabled(bool)) ); +#else + connect( m_enableSubscription, SIGNAL(clicked(bool)), m_listView, SLOT(setEnabled(bool)) ); +#endif + } else { + m_enableSubscription->hide(); + } + readConfig(); +} + +SubscriptionDialog::~SubscriptionDialog() +{ + writeConfig(); +} + +void SubscriptionDialog::slotSearchPattern(const QString &pattern) +{ +#ifndef KDEPIM_MOBILE_UI + m_treeView->expandAll(); +#endif + m_filter->setSearchPattern(pattern); +} + +void SubscriptionDialog::readConfig() +{ + KConfigGroup group( KGlobal::config(), "SubscriptionDialog" ); + + const QSize size = group.readEntry( "Size", QSize() ); + if ( size.isValid() ) { + resize( size ); + } else { + resize( 500, 300 ); + } +} + +void SubscriptionDialog::writeConfig() +{ + KConfigGroup group( KGlobal::config(), "SubscriptionDialog" ); + group.writeEntry( "Size", size() ); + group.sync(); +} + + +void SubscriptionDialog::setSubscriptionEnabled( bool enabled ) +{ + m_enableSubscription->setChecked( enabled ); +#ifndef KDEPIM_MOBILE_UI + m_treeView->setEnabled( enabled ); +#else + m_listView->setEnabled( enabled ); +#endif +} + +bool SubscriptionDialog::subscriptionEnabled() const +{ + return m_enableSubscription->isChecked(); +} + + +void SubscriptionDialog::connectAccount( const ImapAccount &account, const QString &password ) +{ + m_session = new KIMAP::Session( account.server(), account.port(), this ); + m_session->setUiProxy( SessionUiProxy::Ptr( new SessionUiProxy ) ); + + KIMAP::LoginJob *login = new KIMAP::LoginJob( m_session ); + login->setUserName( account.userName() ); + login->setPassword( password ); + login->setEncryptionMode( account.encryptionMode() ); + login->setAuthenticationMode( account.authenticationMode() ); + + connect( login, SIGNAL(result(KJob*)), + this, SLOT(onLoginDone(KJob*)) ); + login->start(); +} + +bool SubscriptionDialog::isSubscriptionChanged() const +{ + return m_subscriptionChanged; +} + +void SubscriptionDialog::onLoginDone( KJob *job ) +{ + if ( !job->error() ) { + onReloadRequested(); + } +} + +void SubscriptionDialog::onReloadRequested() +{ + enableButton( User1, false ); + m_itemsMap.clear(); + m_model->clear(); + + // we need a connection + if ( !m_session + || m_session->state() != KIMAP::Session::Authenticated ) { + kWarning() << "SubscriptionDialog - got no connection"; + enableButton( User1, true ); + return; + } + + KIMAP::ListJob *list = new KIMAP::ListJob( m_session ); + list->setIncludeUnsubscribed( true ); + connect( list, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onMailBoxesReceived(QList,QList >)) ); + connect( list, SIGNAL(result(KJob*)), this, SLOT(onFullListingDone(KJob*)) ); + list->start(); +} + +void SubscriptionDialog::onMailBoxesReceived( const QList &mailBoxes, + const QList< QList > &flags ) +{ + const int numberOfMailBoxes( mailBoxes.size() ); + for ( int i = 0; isetCheckable( isCheckable ); + } + + } else if ( !parentPath.isEmpty() ) { + Q_ASSERT( m_itemsMap.contains( parentPath ) ); + + QStandardItem *parentItem = m_itemsMap[parentPath]; + + QStandardItem *item = new QStandardItem( pathPart ); + item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); + item->setCheckable( isCheckable ); + item->setData( currentPath.mid( 1 ), PathRole ); + parentItem->appendRow( item ); + m_itemsMap[currentPath] = item; + + } else { + QStandardItem *item = new QStandardItem( pathPart ); + item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); + item->setCheckable( isCheckable ); + item->setData( currentPath.mid( 1 ), PathRole ); + m_model->appendRow( item ); + m_itemsMap[currentPath] = item; + } + + parentPath = currentPath; + } + } +} + +void SubscriptionDialog::onFullListingDone( KJob *job ) +{ + if ( job->error() ) { + enableButton( User1, true ); + return; + } + + KIMAP::ListJob *list = new KIMAP::ListJob( m_session ); + list->setIncludeUnsubscribed( false ); + connect( list, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onSubscribedMailBoxesReceived(QList,QList >)) ); + connect( list, SIGNAL(result(KJob*)), this, SLOT(onReloadDone(KJob*)) ); + list->start(); +} + +void SubscriptionDialog::onSubscribedMailBoxesReceived( const QList &mailBoxes, + const QList< QList > &flags ) +{ + Q_UNUSED( flags ); + const int numberOfMailBoxes( mailBoxes.size() ); + for ( int i = 0; isetCheckState( Qt::Checked ); + item->setData( Qt::Checked, InitialStateRole ); + } + } +} + +void SubscriptionDialog::onReloadDone( KJob *job ) +{ + Q_UNUSED( job ); + enableButton( User1, true ); +} + +void SubscriptionDialog::onItemChanged( QStandardItem *item ) +{ + QFont font = item->font(); + font.setBold( item->checkState()!=item->data( InitialStateRole ).toInt() ); + item->setFont( font ); +} + +void SubscriptionDialog::slotButtonClicked( int button ) +{ + if ( button == KDialog::Ok ) { + applyChanges(); + accept(); + } else { + KDialog::slotButtonClicked( button ); + } +} + +void SubscriptionDialog::applyChanges() +{ + QList items = m_itemsMap.values(); + + while ( !items.isEmpty() ) { + QStandardItem *item = items.takeFirst(); + + if ( item->checkState()!=item->data( InitialStateRole ).toInt() ) { + if ( item->checkState() == Qt::Checked ) { + kDebug() << "Subscribing" << item->data( PathRole ); + KIMAP::SubscribeJob *subscribe = new KIMAP::SubscribeJob( m_session ); + subscribe->setMailBox( item->data( PathRole ).toString() ); + subscribe->exec(); + } else { + kDebug() << "Unsubscribing" << item->data( PathRole ); + KIMAP::UnsubscribeJob *unsubscribe = new KIMAP::UnsubscribeJob( m_session ); + unsubscribe->setMailBox( item->data( PathRole ).toString() ); + unsubscribe->exec(); + } + + m_subscriptionChanged = true; + } + } +} + +SubscriptionFilterProxyModel::SubscriptionFilterProxyModel( QObject* parent ) + : KRecursiveFilterProxyModel( parent ), m_checkedOnly( false ) +{ + +} + +void SubscriptionFilterProxyModel::setSearchPattern( const QString &pattern ) +{ + if(m_pattern != pattern) { + m_pattern = pattern; + invalidate(); + } +} + +void SubscriptionFilterProxyModel::setIncludeCheckedOnly( bool checkedOnly ) +{ + if (m_checkedOnly != checkedOnly) { + m_checkedOnly = checkedOnly; + invalidate(); + } +} + +void SubscriptionFilterProxyModel::setIncludeCheckedOnly( int checkedOnlyState ) +{ + m_checkedOnly = (checkedOnlyState == Qt::Checked); + invalidate(); +} + +bool SubscriptionFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex sourceIndex = sourceModel()->index( sourceRow, 0, sourceParent ); + + const bool checked = sourceIndex.data(Qt::CheckStateRole).toInt()==Qt::Checked; + + if ( m_checkedOnly && !checked ) { + return false; + } else if ( !m_pattern.isEmpty() ) { + const QString text = sourceIndex.data(Qt::DisplayRole).toString(); + return text.contains( m_pattern, Qt::CaseInsensitive ); + } else { + return true; + } +} + +void SubscriptionDialog::onMobileLineEditChanged( const QString &text ) +{ + if ( !text.isEmpty() && !m_lineEdit->isVisible() ) { + m_lineEdit->show(); + m_lineEdit->setFocus(); + m_lineEdit->grabKeyboard(); // Now the line edit runs the show + } else if ( text.isEmpty() && m_lineEdit->isVisible() ) { + m_lineEdit->hide(); + grabKeyboard(); // Line edit gone, so let's get all the events for us again + } +} + +void SubscriptionDialog::keyPressEvent( QKeyEvent *event ) +{ +#ifndef KDEPIM_MOBILE_UI + KDialog::keyPressEvent( event ); +#else + static bool isSendingEvent = false; + + if ( !isSendingEvent + && !event->text().isEmpty() + && !m_lineEdit->isVisible() ) { + isSendingEvent = true; + QCoreApplication::sendEvent( m_lineEdit, event ); + isSendingEvent = false; + } else { + KDialog::keyPressEvent( event ); + } +#endif +} + diff --git a/kdepim-runtime/resources/imap/subscriptiondialog.h b/kdepim-runtime/resources/imap/subscriptiondialog.h new file mode 100644 index 00000000..6142342a --- /dev/null +++ b/kdepim-runtime/resources/imap/subscriptiondialog.h @@ -0,0 +1,124 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 SUBSCRIPTIONDIALOG_H +#define SUBSCRIPTIONDIALOG_H + +#include + +#include +#include + +#include + +class QKeyEvent; +class QStandardItemModel; +class QStandardItem; + +class KLineEdit; +class QCheckBox; +class ImapAccount; +class QTreeView; +class QListView; + + +class SubscriptionFilterProxyModel : public KRecursiveFilterProxyModel +{ + Q_OBJECT +public: + explicit SubscriptionFilterProxyModel( QObject* parent = 0 ); + +public slots: + void setSearchPattern( const QString &pattern ); + void setIncludeCheckedOnly( bool checkedOnly ); + void setIncludeCheckedOnly( int checkedOnlyState ); + +protected: + /*reimp*/ bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const; + +private: + QString m_pattern; + bool m_checkedOnly; +}; + + +class SubscriptionDialog : public KDialog +{ + Q_OBJECT +public: + enum Roles { + InitialStateRole = Qt::UserRole + 1, + PathRole + }; + enum SubscriptionDialogOption { + None = 0, + AllowToEnableSubscription = 1 + }; + Q_DECLARE_FLAGS( SubscriptionDialogOptions, SubscriptionDialogOption ) + + explicit SubscriptionDialog( QWidget *parent = 0, SubscriptionDialog::SubscriptionDialogOptions option = SubscriptionDialog::None ); + ~SubscriptionDialog(); + + void connectAccount( const ImapAccount &account, const QString &password ); + bool isSubscriptionChanged() const; + void setSubscriptionEnabled( bool enabled ); + bool subscriptionEnabled() const; + +private slots: + void onLoginDone( KJob *job ); + void onReloadRequested(); + void onMailBoxesReceived( const QList &mailBoxes, + const QList< QList > &flags ); + void onFullListingDone( KJob *job ); + void onSubscribedMailBoxesReceived( const QList &mailBoxes, + const QList< QList > &flags ); + void onReloadDone( KJob *job ); + void onItemChanged( QStandardItem *item ); + void onMobileLineEditChanged( const QString &text ); + + void slotSearchPattern(const QString &pattern); +protected: + /* reimp */ void keyPressEvent( QKeyEvent *event ); + +protected slots: + void slotButtonClicked( int button ); +private: + void readConfig(); + void writeConfig(); + void applyChanges(); + + KIMAP::Session *m_session; + bool m_subscriptionChanged; + +#ifndef KDEPIM_MOBILE_UI + QTreeView *m_treeView; +#else + QListView* m_listView; +#endif + + KLineEdit *m_lineEdit; + QCheckBox *m_enableSubscription; + SubscriptionFilterProxyModel *m_filter; + QStandardItemModel *m_model; + QMap m_itemsMap; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/tests/CMakeLists.txt b/kdepim-runtime/resources/imap/tests/CMakeLists.txt new file mode 100644 index 00000000..9f4fe18f --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/CMakeLists.txt @@ -0,0 +1,53 @@ +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) + +# if kdepimlibs was built without -DKDE4_BUILD_TESTS, kimaptest doesn't exist. +find_path(KIMAPTEST_INCLUDE_DIR NAMES kimaptest/fakeserver.h) +find_library(KIMAPTEST_LIBRARY NAMES kimaptest) + +if(KIMAPTEST_INCLUDE_DIR AND KIMAPTEST_LIBRARY) + MACRO(IMAP_RESOURCE_UNIT_TESTS) + FOREACH(_testname ${ARGN}) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/..) + kde4_add_unit_test(${_testname} TESTNAME imap-${_testname} NOGUI ${_testname}.cpp + dummypasswordrequester.cpp + dummyresourcestate.cpp + imaptestbase.cpp + ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_KIMAP_LIBS} ${KDEPIMLIBS_KIMAP_LIBS} ${KIMAPTEST_LIBRARY} ${QT_QTTEST_LIBRARY} imapresource) + add_definitions(-DTEST_DATA="\\"${CMAKE_CURRENT_SOURCE_DIR}\\"") + ENDFOREACH(_testname) + ENDMACRO(IMAP_RESOURCE_UNIT_TESTS) + + IMAP_RESOURCE_UNIT_TESTS( + testresourcetask + testsessionpool + + testaddcollectiontask + testadditemtask + testchangecollectiontask + testchangeitemtask + testexpungecollectiontask + testmovecollectiontask + testmoveitemstask + testremovecollectionrecursivetask + testretrievecollectionmetadatatask + testretrievecollectionstask + testretrieveitemtask + testretrieveitemstask + ) + +endif() + +set(testsubscriptiondialog_SRCS + testsubscriptiondialog.cpp + ../imapaccount.cpp + ../subscriptiondialog.cpp +) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/..) +kde4_add_executable(testsubscriptiondialog ${testsubscriptiondialog_SRCS}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") +target_link_libraries(testsubscriptiondialog ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KIMAP_LIBS} ${KDEPIMLIBS_KMIME_LIBS}) + diff --git a/kdepim-runtime/resources/imap/tests/dummypasswordrequester.cpp b/kdepim-runtime/resources/imap/tests/dummypasswordrequester.cpp new file mode 100644 index 00000000..f4f04b94 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/dummypasswordrequester.cpp @@ -0,0 +1,83 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "dummypasswordrequester.h" + +#include + +#include + +DummyPasswordRequester::DummyPasswordRequester( QObject *parent) + : PasswordRequesterInterface(parent) +{ + for ( int i=0; i<10; ++i ) { + m_expectedCalls << StandardRequest; + m_results << PasswordRetrieved; + } +} + +QString DummyPasswordRequester::password() const +{ + return m_password; +} + +void DummyPasswordRequester::setPassword( const QString &password ) +{ + m_password = password; +} + +void DummyPasswordRequester::setScenario( const QList &expectedCalls, + const QList &results ) +{ + Q_ASSERT( expectedCalls.size() == results.size() ); + + m_expectedCalls = expectedCalls; + m_results = results; +} + +void DummyPasswordRequester::setDelays( const QList &delays ) +{ + m_delays = delays; +} + +void DummyPasswordRequester::requestPassword( RequestType request, + const QString &/*serverError*/ ) +{ + QVERIFY2( !m_expectedCalls.isEmpty(), QString::fromLatin1( "Got unexpected call: %1" ).arg( request ).toUtf8().constData() ); + QCOMPARE( (int)request, (int)m_expectedCalls.takeFirst() ); + + int delay = 20; + if ( !m_delays.isEmpty() ) { + delay = m_delays.takeFirst(); + } + + QTimer::singleShot( delay, this, SLOT(emitResult()) ); +} + +void DummyPasswordRequester::emitResult() +{ + ResultType result = m_results.takeFirst(); + + if ( result == PasswordRetrieved ) { + emit done( result, m_password ); + } else { + emit done( result ); + } +} + diff --git a/kdepim-runtime/resources/imap/tests/dummypasswordrequester.h b/kdepim-runtime/resources/imap/tests/dummypasswordrequester.h new file mode 100644 index 00000000..395218df --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/dummypasswordrequester.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef DUMMYPASSWORDREQUESTER_H +#define DUMMYPASSWORDREQUESTER_H + +#include "passwordrequesterinterface.h" + +class DummyPasswordRequester : public PasswordRequesterInterface +{ + Q_OBJECT +public: + DummyPasswordRequester( QObject *parent = 0 ); + + QString password() const; + void setPassword( const QString &password ); + + void setScenario( const QList &expectedCalls, + const QList &results ); + void setDelays( const QList &delays ); + +public: + virtual void requestPassword( RequestType request = StandardRequest, + const QString &serverError = QString() ); + +private slots: + void emitResult(); + +private: + QString m_password; + QList m_expectedCalls; + QList m_results; + QList m_delays; +}; + +#endif + diff --git a/kdepim-runtime/resources/imap/tests/dummyresourcestate.cpp b/kdepim-runtime/resources/imap/tests/dummyresourcestate.cpp new file mode 100644 index 00000000..32dad6ca --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/dummyresourcestate.cpp @@ -0,0 +1,352 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "dummyresourcestate.h" + +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QVector) + +DummyResourceState::DummyResourceState() + : m_automaticExpunge( true ), m_subscriptionEnabled( true ), + m_disconnectedMode( true ), m_intervalCheckTime( -1 ) +{ + qRegisterMetaType >(); + qRegisterMetaType >(); +} + +DummyResourceState::~DummyResourceState() +{ + +} + +void DummyResourceState::setUserName( const QString &name ) +{ + m_userName = name; +} + +QString DummyResourceState::userName() const +{ + return m_userName; +} + +void DummyResourceState::setResourceName( const QString &name ) +{ + m_resourceName = name; +} + +QString DummyResourceState::resourceName() const +{ + return m_resourceName; +} + +void DummyResourceState::setServerCapabilities( const QStringList &capabilities ) +{ + m_capabilities = capabilities; +} + +QStringList DummyResourceState::serverCapabilities() const +{ + return m_capabilities; +} + +void DummyResourceState::setServerNamespaces( const QList &namespaces ) +{ + m_namespaces = namespaces; +} + +QList DummyResourceState::serverNamespaces() const +{ + return m_namespaces; +} + +void DummyResourceState::setAutomaticExpungeEnagled( bool enabled ) +{ + m_automaticExpunge = enabled; +} + +bool DummyResourceState::isAutomaticExpungeEnabled() const +{ + return m_automaticExpunge; +} + +void DummyResourceState::setSubscriptionEnabled( bool enabled ) +{ + m_subscriptionEnabled = enabled; +} + +bool DummyResourceState::isSubscriptionEnabled() const +{ + return m_subscriptionEnabled; +} + +void DummyResourceState::setDisconnectedModeEnabled( bool enabled ) +{ + m_disconnectedMode = enabled; +} + +bool DummyResourceState::isDisconnectedModeEnabled() const +{ + return m_disconnectedMode; +} + +void DummyResourceState::setIntervalCheckTime( int interval ) +{ + m_intervalCheckTime = interval; +} + +int DummyResourceState::intervalCheckTime() const +{ + return m_intervalCheckTime; +} + +void DummyResourceState::setCollection( const Akonadi::Collection &collection ) +{ + m_collection = collection; +} + +Akonadi::Collection DummyResourceState::collection() const +{ + return m_collection; +} + +void DummyResourceState::setItem( const Akonadi::Item &item ) +{ + m_items.clear(); + m_items << item; +} + +Akonadi::Item DummyResourceState::item() const +{ + return m_items.first(); +} + +Akonadi::Item::List DummyResourceState::items() const +{ + return m_items; +} + +void DummyResourceState::setParentCollection( const Akonadi::Collection &collection ) +{ + m_parentCollection = collection; +} + +Akonadi::Collection DummyResourceState::parentCollection() const +{ + return m_parentCollection; +} + +void DummyResourceState::setSourceCollection( const Akonadi::Collection &collection ) +{ + m_sourceCollection = collection; +} + +Akonadi::Collection DummyResourceState::sourceCollection() const +{ + return m_sourceCollection; +} + +void DummyResourceState::setTargetCollection( const Akonadi::Collection &collection ) +{ + m_targetCollection = collection; +} + +Akonadi::Collection DummyResourceState::targetCollection() const +{ + return m_targetCollection; +} + +void DummyResourceState::setParts( const QSet &parts ) +{ + m_parts = parts; +} + +QSet DummyResourceState::parts() const +{ + return m_parts; +} + +QString DummyResourceState::rootRemoteId() const +{ + return QLatin1String("root-id"); +} + +void DummyResourceState::setIdleCollection( const Akonadi::Collection &collection ) +{ + recordCall( "setIdleCollection", QVariant::fromValue( collection ) ); +} + +void DummyResourceState::applyCollectionChanges( const Akonadi::Collection &collection ) +{ + recordCall( "applyCollectionChanges", QVariant::fromValue( collection ) ); +} + +void DummyResourceState::collectionAttributesRetrieved( const Akonadi::Collection &collection ) +{ + recordCall( "collectionAttributesRetrieved", QVariant::fromValue( collection ) ); +} + +void DummyResourceState::itemRetrieved( const Akonadi::Item &item ) +{ + recordCall( "itemRetrieved", QVariant::fromValue(item) ); +} + +void DummyResourceState::itemsRetrieved( const Akonadi::Item::List &items ) +{ + recordCall( "itemsRetrieved", QVariant::fromValue( items ) ); +} + +void DummyResourceState::itemsRetrievedIncremental( const Akonadi::Item::List &changed, const Akonadi::Item::List &removed ) +{ + Q_UNUSED( removed ) + + recordCall( "itemsRetrievedIncremental", QVariant::fromValue( changed ) ); +} + +void DummyResourceState::itemsRetrievalDone() +{ + recordCall( "itemsRetrievalDone" ); +} + +void DummyResourceState::setTotalItems(int) +{ + +} + +QSet< QByteArray > DummyResourceState::addedFlags() const +{ + return QSet(); +} + +QSet< QByteArray > DummyResourceState::removedFlags() const +{ + return QSet(); +} + +void DummyResourceState::itemChangeCommitted( const Akonadi::Item &item ) +{ + recordCall( "itemChangeCommitted", QVariant::fromValue( item ) ); +} + +void DummyResourceState::itemsChangesCommitted( const Akonadi::Item::List &items ) +{ + recordCall( "itemsChangesCommitted", QVariant::fromValue( items ) ); +} + +void DummyResourceState::collectionsRetrieved( const Akonadi::Collection::List &collections ) +{ + recordCall( "collectionsRetrieved", QVariant::fromValue( collections ) ); +} + +void DummyResourceState::collectionChangeCommitted( const Akonadi::Collection &collection ) +{ + recordCall( "collectionChangeCommitted", QVariant::fromValue( collection ) ); +} + +void DummyResourceState::changeProcessed() +{ + recordCall( "changeProcessed" ); +} + +void DummyResourceState::searchFinished( const QVector &result, bool isRid ) +{ + recordCall( "searchFinished", QVariant::fromValue( result ) ); +} + +void DummyResourceState::cancelTask( const QString &errorString ) +{ + recordCall( "cancelTask", QVariant::fromValue(errorString) ); +} + +void DummyResourceState::deferTask() +{ + recordCall( "deferTask" ); +} + +void DummyResourceState::restartItemRetrieval(Akonadi::Entity::Id col) +{ + recordCall( "restartItemRetrieval", QVariant::fromValue(col) ); +} + +void DummyResourceState::taskDone() +{ + recordCall( "taskDone" ); +} + +void DummyResourceState::emitError( const QString &message ) +{ + recordCall( "emitError", QVariant::fromValue(message) ); +} + +void DummyResourceState::emitWarning( const QString &message ) +{ + recordCall( "emitWarning", QVariant::fromValue(message) ); +} + +void DummyResourceState::emitPercent( int percent ) +{ + // FIXME: Many tests need to be updated for this to be uncommented out. + // recordCall( "emitPercent", QVariant::fromValue(percent) ); +} + +void DummyResourceState::synchronizeCollectionTree() +{ + recordCall( "synchronizeCollectionTree" ); +} + +void DummyResourceState::scheduleConnectionAttempt() +{ + recordCall( "scheduleConnectionAttempt" ); +} + +void DummyResourceState::showInformationDialog( const QString &message, const QString&, const QString& ) +{ + recordCall( "showInformationDialog", QVariant::fromValue( message ) ); +} + +QList< QPair > DummyResourceState::calls() const +{ + return m_calls; +} + +QChar DummyResourceState::separatorCharacter() const +{ + return m_separator; +} + +void DummyResourceState::setSeparatorCharacter( const QChar &separator ) +{ + m_separator = separator; +} + +void DummyResourceState::recordCall( const QByteArray callName, const QVariant ¶meter ) +{ + m_calls << QPair( callName, parameter ); +} + +int DummyResourceState::batchSize() const +{ + return 10; +} + +MessageHelper::Ptr DummyResourceState::messageHelper() const +{ + return MessageHelper::Ptr(new MessageHelper()); +} diff --git a/kdepim-runtime/resources/imap/tests/dummyresourcestate.h b/kdepim-runtime/resources/imap/tests/dummyresourcestate.h new file mode 100644 index 00000000..9e77a746 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/dummyresourcestate.h @@ -0,0 +1,157 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 DUMMYRESOURCESTATE_H +#define DUMMYRESOURCESTATE_H + +#include +#include + +#include "resourcestateinterface.h" + +class DummyResourceState : public ResourceStateInterface +{ +public: + typedef boost::shared_ptr Ptr; + + explicit DummyResourceState(); + ~DummyResourceState(); + + void setUserName( const QString &name ); + virtual QString userName() const; + + void setResourceName( const QString &name ); + virtual QString resourceName() const; + + void setServerCapabilities( const QStringList &capabilities ); + virtual QStringList serverCapabilities() const; + + void setServerNamespaces( const QList &namespaces ); + virtual QList serverNamespaces() const; + + void setAutomaticExpungeEnagled( bool enabled ); + virtual bool isAutomaticExpungeEnabled() const; + + void setSubscriptionEnabled( bool enabled ); + virtual bool isSubscriptionEnabled() const; + void setDisconnectedModeEnabled( bool enabled ); + virtual bool isDisconnectedModeEnabled() const; + void setIntervalCheckTime( int interval ); + virtual int intervalCheckTime() const; + + + void setCollection( const Akonadi::Collection &collection ); + virtual Akonadi::Collection collection() const; + void setItem( const Akonadi::Item &item ); + virtual Akonadi::Item item() const; + virtual Akonadi::Item::List items() const; + + void setParentCollection( const Akonadi::Collection &collection ); + virtual Akonadi::Collection parentCollection() const; + + void setSourceCollection( const Akonadi::Collection &collection ); + virtual Akonadi::Collection sourceCollection() const; + void setTargetCollection( const Akonadi::Collection &collection ); + virtual Akonadi::Collection targetCollection() const; + + void setParts( const QSet &parts ); + virtual QSet parts() const; + + virtual QString rootRemoteId() const; + + virtual void setIdleCollection( const Akonadi::Collection &collection ); + virtual void applyCollectionChanges( const Akonadi::Collection &collection ); + + virtual void collectionAttributesRetrieved( const Akonadi::Collection &collection ); + + virtual void itemRetrieved( const Akonadi::Item &item ); + + virtual void itemsRetrieved( const Akonadi::Item::List &items ); + virtual void itemsRetrievedIncremental( const Akonadi::Item::List &changed, const Akonadi::Item::List &removed ); + virtual void itemsRetrievalDone(); + + virtual void setTotalItems(int); + + virtual QSet< QByteArray > addedFlags() const; + virtual QSet< QByteArray > removedFlags() const; + + virtual void itemChangeCommitted( const Akonadi::Item &item ); + virtual void itemsChangesCommitted(const Akonadi::Item::List& items); + + virtual void collectionsRetrieved( const Akonadi::Collection::List &collections ); + + virtual void collectionChangeCommitted( const Akonadi::Collection &collection ); + + virtual void searchFinished( const QVector &result, bool isRid = true ); + + virtual void changeProcessed(); + + virtual void cancelTask( const QString &errorString ); + virtual void deferTask(); + virtual void restartItemRetrieval(Akonadi::Collection::Id col); + virtual void taskDone(); + + virtual void emitError( const QString &message ); + virtual void emitWarning( const QString &message ); + virtual void emitPercent( int percent ); + + virtual void synchronizeCollectionTree(); + virtual void scheduleConnectionAttempt(); + + virtual QChar separatorCharacter() const; + virtual void setSeparatorCharacter( const QChar &separator ); + + virtual void showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ); + + virtual int batchSize() const; + + virtual MessageHelper::Ptr messageHelper() const; + + QList< QPair > calls() const; + +private: + void recordCall( const QByteArray callName, const QVariant ¶meter = QVariant() ); + + QString m_userName; + QString m_resourceName; + QStringList m_capabilities; + QList m_namespaces; + + bool m_automaticExpunge; + bool m_subscriptionEnabled; + bool m_disconnectedMode; + int m_intervalCheckTime; + QChar m_separator; + + Akonadi::Collection m_collection; + Akonadi::Item::List m_items; + + Akonadi::Collection m_parentCollection; + + Akonadi::Collection m_sourceCollection; + Akonadi::Collection m_targetCollection; + + QSet m_parts; + + QList< QPair > m_calls; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/tests/imaptestbase.cpp b/kdepim-runtime/resources/imap/tests/imaptestbase.cpp new file mode 100644 index 00000000..94625d86 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/imaptestbase.cpp @@ -0,0 +1,137 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +ImapTestBase::ImapTestBase( QObject *parent ) + : QObject( parent ) +{ + +} + +QString ImapTestBase::defaultUserName() const +{ + return QLatin1String("test@kdab.com"); +} + +QString ImapTestBase::defaultPassword() const +{ + return QLatin1String("foobar"); +} + +ImapAccount *ImapTestBase::createDefaultAccount() const +{ + ImapAccount *account = new ImapAccount; + + account->setServer( QLatin1String("127.0.0.1") ); + account->setPort( 5989 ); + account->setUserName( defaultUserName() ); + account->setSubscriptionEnabled( true ); + account->setEncryptionMode( KIMAP::LoginJob::Unencrypted ); + account->setAuthenticationMode( KIMAP::LoginJob::ClearText ); + + return account; +} + +DummyPasswordRequester *ImapTestBase::createDefaultRequester() +{ + DummyPasswordRequester *requester = new DummyPasswordRequester( this ); + requester->setPassword( defaultPassword() ); + return requester; +} + +void ImapTestBase::setupTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); +} + +QList ImapTestBase::defaultAuthScenario() const +{ + QList scenario; + + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in"; + + return scenario; +} + +QList ImapTestBase::defaultPoolConnectionScenario( const QList &customCapabilities ) const +{ + QList scenario; + + QByteArray caps = "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE"; + Q_FOREACH ( const QByteArray &cap, customCapabilities ) { + caps += " " + cap; + } + + scenario << defaultAuthScenario() + << "C: A000002 CAPABILITY" + << caps + << "S: A000002 OK Completed"; + + return scenario; +} + +bool ImapTestBase::waitForSignal( QObject *obj, const char *member, int timeout ) const +{ + QEventLoop loop; + QTimer timer; + + connect( &timer, SIGNAL(timeout()), &loop, SLOT(quit()) ); + + QSignalSpy spy( obj, member ); + connect( obj, member, &loop, SLOT(quit()) ); + + timer.setSingleShot( true ); + timer.start( timeout ); + loop.exec(); + timer.stop(); + + return spy.count()==1; +} + +Akonadi::Collection ImapTestBase::createCollectionChain( const QString &remoteId ) const +{ + QChar separator = remoteId.length() > 0 ? remoteId.at(0) : QLatin1Char('/'); + + Akonadi::Collection parent( 1 ); + parent.setRemoteId( QLatin1String("root-id") ); + parent.setParentCollection( Akonadi::Collection::root() ); + Akonadi::Entity::Id id = 2; + + Akonadi::Collection collection = parent; + + const QStringList collections = remoteId.split( separator, QString::SkipEmptyParts ); + Q_FOREACH ( const QString &colId, collections ) { + collection = Akonadi::Collection( id ); + collection.setRemoteId( separator + colId ); + collection.setParentCollection( parent ); + + parent = collection; + id++; + } + + return collection; +} + + diff --git a/kdepim-runtime/resources/imap/tests/imaptestbase.h b/kdepim-runtime/resources/imap/tests/imaptestbase.h new file mode 100644 index 00000000..cdb92c01 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/imaptestbase.h @@ -0,0 +1,95 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef IMAPTESTBASE_H +#define IMAPTESTBASE_H + +#include + +#include + +#include "dummypasswordrequester.h" +#include "dummyresourcestate.h" +#include "imapaccount.h" +#include "resourcetask.h" +#include "sessionpool.h" + +Q_DECLARE_METATYPE(ImapAccount*) +Q_DECLARE_METATYPE(DummyPasswordRequester*) +Q_DECLARE_METATYPE(DummyResourceState::Ptr) +Q_DECLARE_METATYPE(KIMAP::Session*) +Q_DECLARE_METATYPE(QVariant) + +class ImapTestBase : public QObject +{ + Q_OBJECT + +public: + ImapTestBase( QObject *parent = 0 ); + +protected: + QString defaultUserName() const; + QString defaultPassword() const; + ImapAccount *createDefaultAccount() const; + DummyPasswordRequester *createDefaultRequester(); + QList defaultAuthScenario() const; + QList defaultPoolConnectionScenario( const QList &customCapabilities = QList() ) const; + + bool waitForSignal( QObject *obj, const char *member, int timeout = 500 ) const; + + Akonadi::Collection createCollectionChain( const QString &remoteId ) const; + +private slots: + void setupTestCase(); +}; + +// Taken from Qt 5: +#if QT_VERSION < 0x050000 + +// Will try to wait for the expression to become true while allowing event processing +#define QTRY_VERIFY(__expr) \ +do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ( !( __expr ) ) { \ + QTest::qWait( 0 ); \ + } \ + for ( int __i = 0; __i < __timeout && !( __expr ); __i += __step ) { \ + QTest::qWait( __step ); \ + } \ + QVERIFY( __expr ); \ +} while ( 0 ) + +// Will try to wait for the comparison to become successful while allowing event processing +#define QTRY_COMPARE(__expr, __expected) \ +do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ( ( __expr ) != ( __expected ) ) { \ + QTest::qWait( 0 ); \ + } \ + for ( int __i = 0; __i < __timeout && ( ( __expr ) != ( __expected ) ); __i += __step ) { \ + QTest::qWait( __step ); \ + } \ + QCOMPARE( __expr, __expected ); \ +} while ( 0 ) + +#endif + +#endif diff --git a/kdepim-runtime/resources/imap/tests/testaddcollectiontask.cpp b/kdepim-runtime/resources/imap/tests/testaddcollectiontask.cpp new file mode 100644 index 00000000..8939b13e --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testaddcollectiontask.cpp @@ -0,0 +1,181 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "addcollectiontask.h" +#include + +class TestAddCollectionTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldCreateAndSubscribe_data() + { + QTest::addColumn( "parentCollection" ); + QTest::addColumn( "collection" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + QTest::addColumn( "collectionName" ); + QTest::addColumn( "remoteId" ); + + Akonadi::Collection parentCollection; + Akonadi::Collection collection; + QList scenario; + QStringList callNames; + + parentCollection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection = Akonadi::Collection( 4 ); + collection.setName( QLatin1String("Bar") ); + collection.setParentCollection( parentCollection ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 CREATE \"INBOX/Foo/Bar\"" + << "S: A000003 OK create done" + << "C: A000004 SUBSCRIBE \"INBOX/Foo/Bar\"" + << "S: A000004 OK subscribe done"; + + callNames.clear(); + callNames << QLatin1String("collectionChangeCommitted") << QLatin1String("synchronizeCollectionTree"); + + QTest::newRow( "trivial case" ) << parentCollection << collection << scenario << callNames + << collection.name() << "/Bar"; + + parentCollection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection = Akonadi::Collection( 4 ); + collection.setName( QLatin1String("Bar/Baz") ); + collection.setParentCollection( parentCollection ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 CREATE \"INBOX/Foo/BarBaz\"" + << "S: A000003 OK create done" + << "C: A000004 SUBSCRIBE \"INBOX/Foo/BarBaz\"" + << "S: A000004 OK subscribe done"; + + callNames.clear(); + callNames << QLatin1String("collectionChangeCommitted") << QLatin1String("synchronizeCollectionTree"); + + QTest::newRow( "folder with invalid separator" ) << parentCollection << collection << scenario + << callNames << "BarBaz" << "/BarBaz"; + + parentCollection = createCollectionChain( QLatin1String(".INBOX") ); + collection = Akonadi::Collection( 3 ); + collection.setName ( QLatin1String("Foo") ); + collection.setParentCollection( parentCollection ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 CREATE \"INBOX.Foo\"" + << "S: A000003 OK create done" + << "C: A000004 SUBSCRIBE \"INBOX.Foo\"" + << "S: A000004 OK subscribe done"; + callNames.clear(); + callNames << QLatin1String("collectionChangeCommitted") << QLatin1String("synchronizeCollectionTree"); + + QTest::newRow( "folder with non-standard separator") << parentCollection << collection << scenario + << callNames << "Foo" << ".Foo"; + + parentCollection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection = Akonadi::Collection( 4 ); + collection.setName( QLatin1String("Bar") ); + collection.setParentCollection( parentCollection ); + Akonadi::CollectionAnnotationsAttribute *attr = collection.attribute( Akonadi::Collection::AddIfMissing ); + QMap annotations; + annotations.insert( "/shared/vendor/foobar/foo", "value" ); + attr->setAnnotations( annotations ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 CREATE \"INBOX/Foo/Bar\"" + << "S: A000003 OK create done" + << "C: A000004 SUBSCRIBE \"INBOX/Foo/Bar\"" + << "S: A000004 OK subscribe done" + << "C: A000005 SETMETADATA \"INBOX/Foo/Bar\" (\"/shared/vendor/foobar/foo\" {5}\r\nvalue)" + << "S: A000005 OK SETMETADATA complete"; + + callNames.clear(); + callNames << QLatin1String("collectionChangeCommitted"); + + QTest::newRow( "folder with annotations" ) << parentCollection << collection << scenario << callNames + << collection.name() << "/Bar"; + } + + void shouldCreateAndSubscribe() + { + QFETCH( Akonadi::Collection, parentCollection ); + QFETCH( Akonadi::Collection, collection ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + QFETCH( QString, collectionName ); + QFETCH( QString, remoteId ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr(new DummyResourceState); + state->setParentCollection( parentCollection ); + state->setCollection( collection ); + if (collection.hasAttribute()) { + state->setServerCapabilities( QStringList() << "METADATA" ); + } + AddCollectionTask *task = new AddCollectionTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8( state->calls().at( i ).first ); + QVariant parameter = state->calls().at( i ).second; + + if ( command==QLatin1String("cancelTask") && callNames[i]!=QLatin1String("cancelTask") ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + if ( command == QLatin1String("collectionChangeCommitted") ) { + QCOMPARE( parameter.value().name(), collectionName ); + QCOMPARE( parameter.value().remoteId().right( collectionName.length() ), + collectionName ); + QCOMPARE( parameter.value().remoteId(), remoteId ); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == QLatin1String("cancelTask") ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestAddCollectionTask ) + +#include "testaddcollectiontask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testadditemtask.cpp b/kdepim-runtime/resources/imap/tests/testadditemtask.cpp new file mode 100644 index 00000000..250ad23a --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testadditemtask.cpp @@ -0,0 +1,184 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "additemtask.h" + +#include "uidnextattribute.h" + +#include + +class TestAddItemTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldAppendMessage_data() + { + QTest::addColumn( "item" ); + QTest::addColumn( "collection" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Collection collection; + Akonadi::Item item; + QString messageContent; + QList scenario; + QStringList callNames; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + UidNextAttribute *uidNext = new UidNextAttribute; + uidNext->setUidNext( 63 ); + collection.addAttribute( uidNext ); + + item = Akonadi::Item( 2 ); + item.setParentCollection( collection ); + + KMime::Message::Ptr message(new KMime::Message); + + messageContent = QLatin1String("From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."); + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {55}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done [ APPENDUID 1239890035 66 ]"; + + callNames.clear(); + callNames << QLatin1String("itemChangeCommitted"); + + QTest::newRow( "trivial case" ) << item << collection << scenario << callNames; + + + + message = KMime::Message::Ptr( new KMime::Message ); + + messageContent = QLatin1String("From: ervin\nTo: someone\nSubject: foo\nMessage-ID: <42.4242.foo@bar.org>\n\nSpeechless..."); + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {90}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done" + << "C: A000004 SELECT \"INBOX/Foo\"" + << "S: A000004 OK select done" + << "C: A000005 UID SEARCH HEADER Message-ID <42.4242.foo@bar.org>" + << "S: * SEARCH 66" + << "S: A000005 OK search done"; + + callNames.clear(); + callNames << QLatin1String("itemChangeCommitted"); + + QTest::newRow( "no APPENDUID, message contained Message-ID" ) << item << collection << scenario << callNames; + + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {90}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done" + << "C: A000004 SELECT \"INBOX/Foo\"" + << "S: A000004 OK select done" + << "C: A000005 UID SEARCH HEADER Message-ID <42.4242.foo@bar.org>" + << "S: * SEARCH 65 66" + << "S: A000005 OK search done"; + callNames.clear(); + callNames << QLatin1String("itemChangeCommitted"); + QTest::newRow( "no APPENDUID, message contained non-unique Message-ID" ) << item << collection << scenario << callNames; + + + + message = KMime::Message::Ptr( new KMime::Message ); + + messageContent = QLatin1String("From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."); + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload(message); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {55}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done" + << "C: A000004 SELECT \"INBOX/Foo\"" + << "S: A000004 OK select done" + << "C: A000005 UID SEARCH NEW UID 63:*" + << "S: * SEARCH 66" + << "S: A000005 OK search done"; + + callNames.clear(); + callNames << QLatin1String("itemChangeCommitted"); + + QTest::newRow( "no APPENDUID, message didn't contain Message-ID" ) << item << collection << scenario << callNames; + } + + void shouldAppendMessage() + { + QFETCH( Akonadi::Item, item ); + QFETCH( Akonadi::Collection, collection ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setCollection( collection ); + state->setItem( item ); + AddItemTask *task = new AddItemTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8( state->calls().at( i ).first ); + QVariant parameter = state->calls().at( i ).second; + + if ( command == QLatin1String("cancelTask") && callNames[i] != QLatin1String("cancelTask") ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == QLatin1String("cancelTask") ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestAddItemTask ) + +#include "testadditemtask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testchangecollectiontask.cpp b/kdepim-runtime/resources/imap/tests/testchangecollectiontask.cpp new file mode 100644 index 00000000..f0ba5ec6 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testchangecollectiontask.cpp @@ -0,0 +1,244 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "changecollectiontask.h" + +#include "collectionannotationsattribute.h" +#include "imapaclattribute.h" + +Q_DECLARE_METATYPE( QSet ) + +class TestChangeCollectionTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldUpdateMetadataAclAndName_data() + { + QTest::addColumn( "collection" ); + QTest::addColumn< QSet >( "parts" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + QTest::addColumn( "collectionName" ); + QTest::addColumn( "caps" ); + + Akonadi::Collection collection; + QSet parts; + QList scenario; + QStringList callNames; + QStringList caps; + + collection = createCollectionChain( QLatin1String("/Foo") ); + collection.setName( QLatin1String("Bar") ); + collection.setRights( Akonadi::Collection::AllRights ); + + Akonadi::ImapAclAttribute *acls = new Akonadi::ImapAclAttribute; + QMap rights; + // Old rights + rights["test@kdab.com"] = KIMAP::Acl::rightsFromString( "lrswipckxtda" ); + rights["foo@kde.org"] = KIMAP::Acl::rightsFromString( "lrswipcda" ); + acls->setRights( rights ); + + // New rights + rights["test@kdab.com"] = KIMAP::Acl::rightsFromString( "lrswipckxtda" ); + rights["foo@kde.org"] = KIMAP::Acl::rightsFromString( "lrswipcda" ); + acls->setRights( rights ); + collection.addAttribute( acls ); + + Akonadi::CollectionAnnotationsAttribute *annotationsAttr = new Akonadi::CollectionAnnotationsAttribute; + QMap annotations; + annotations["/vendor/kolab/folder-test"] = "false"; + annotations["/vendor/kolab/folder-test2"] = "true"; + annotationsAttr->setAnnotations( annotations ); + collection.addAttribute( annotationsAttr ); + + parts << "NAME" << "AccessRights" << "imapacl" << "collectionannotations"; + + caps << "ACL" << "ANNOTATEMORE"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SETACL \"Foo\" \"test@kdab.com\" \"lrswipckxtda\"" + << "S: A000003 OK acl changed" + << "C: A000004 SETANNOTATION \"Foo\" \"/vendor/kolab/folder-test\" (\"value.shared\" \"false\")" + << "S: A000004 OK annotations changed" + << "C: A000005 SETANNOTATION \"Foo\" \"/vendor/kolab/folder-test2\" (\"value.shared\" \"true\")" + << "S: A000005 OK annotations changed" + << "C: A000006 SETACL \"Foo\" \"foo@kde.org\" \"lrswipcda\"" + << "S: A000006 OK acl changed" + << "C: A000007 SETACL \"Foo\" \"test@kdab.com\" \"lrswipckxtda\"" + << "S: A000007 OK acl changed" + << "C: A000008 RENAME \"Foo\" \"Bar\"" + << "S: A000008 OK rename done" + << "C: A000009 SUBSCRIBE \"Bar\"" + << "S: A000009 OK mailbox subscribed"; + + callNames.clear(); + callNames << QLatin1String("collectionChangeCommitted"); + + QTest::newRow( "complete case" ) << collection << parts << scenario << callNames << collection.name() << caps; + + caps.clear(); + caps << "ACL"; + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SETACL \"Foo\" \"test@kdab.com\" \"lrswipckxtda\"" + << "S: A000003 OK acl changed" + << "C: A000004 SETACL \"Foo\" \"foo@kde.org\" \"lrswipcda\"" + << "S: A000004 OK acl changed" + << "C: A000005 SETACL \"Foo\" \"test@kdab.com\" \"lrswipckxtda\"" + << "S: A000005 OK acl changed" + << "C: A000006 RENAME \"Foo\" \"Bar\"" + << "S: A000006 OK rename done" + << "C: A000007 SUBSCRIBE \"Bar\"" + << "S: A000007 OK mailbox subscribed"; + QTest::newRow( "no ANNOTATEMORE support" ) << collection << parts << scenario << callNames << collection.name() << caps; + + collection = createCollectionChain( QLatin1String("/Foo") ); + collection.setName( QLatin1String("Bar/Baz") ); + caps.clear(); + caps << "ACL" << "ANNOTATEMORE"; + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"Foo\" \"BarBaz\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"BarBaz\"" + << "S: A000004 OK mailbox subscribed"; + parts.clear(); + parts << "NAME"; + callNames.clear(); + callNames << QLatin1String("collectionChangeCommitted"); + QTest::newRow( "rename with invalid separator" ) << collection << parts << scenario << callNames + << "BarBaz" << caps; + + collection = createCollectionChain( QLatin1String(".INBOX.Foo") ); + collection.setName( QLatin1String("Bar") ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"INBOX.Foo\" \"INBOX.Bar\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"INBOX.Bar\"" + << "S: A000004 OK mailbox subscribed"; + QTest::newRow( "rename with non-standard separator" ) << collection << parts << scenario << callNames + << "Bar" << caps; + + collection = createCollectionChain( QLatin1String("/Foo") ); + collection.setName( QLatin1String("Bar") ); + collection.setRights( Akonadi::Collection::AllRights ); + + acls = new Akonadi::ImapAclAttribute; + // Old rights + rights["test@kdab.com"] = KIMAP::Acl::rightsFromString( "lrswipckxtda" ); + rights["foo@kde.org"] = KIMAP::Acl::rightsFromString( "lrswipcda" ); + acls->setRights( rights ); + + // New rights + rights["test@kdab.com"] = KIMAP::Acl::rightsFromString( "lrswipckxtda" ); + rights["foo@kde.org"] = KIMAP::Acl::rightsFromString( "lrswipcda" ); + acls->setRights( rights ); + collection.addAttribute( acls ); + + annotationsAttr = new Akonadi::CollectionAnnotationsAttribute; + annotations["/vendor/kolab/folder-test"] = "false"; + annotations["/vendor/kolab/folder-test2"] = "true"; + annotationsAttr->setAnnotations( annotations ); + collection.addAttribute( annotationsAttr ); + + parts << "NAME" << "AccessRights" << "imapacl" << "collectionannotations"; + + caps << "ACL" << "METADATA"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SETACL \"Foo\" \"test@kdab.com\" \"lrswipckxtda\"" + << "S: A000003 OK acl changed" + << "C: A000004 SETMETADATA \"Foo\" (\"/shared/vendor/kolab/folder-test\" {5}\r\nfalse)" + << "S: A000004 OK SETMETADATA complete" + << "C: A000005 SETMETADATA \"Foo\" (\"/shared/vendor/kolab/folder-test2\" {4}\r\ntrue)" + << "S: A000005 OK SETMETADATA complete" + << "C: A000006 SETACL \"Foo\" \"foo@kde.org\" \"lrswipcda\"" + << "S: A000006 OK acl changed" + << "C: A000007 SETACL \"Foo\" \"test@kdab.com\" \"lrswipckxtda\"" + << "S: A000007 OK acl changed" + << "C: A000008 RENAME \"Foo\" \"Bar\"" + << "S: A000008 OK rename done" + << "C: A000009 SUBSCRIBE \"Bar\"" + << "S: A000009 OK mailbox subscribed"; + QTest::newRow( "complete case METADATA" ) << collection << parts << scenario << callNames << collection.name() << caps; + } + + void shouldUpdateMetadataAclAndName() + { + QFETCH( Akonadi::Collection, collection ); + QFETCH( QSet, parts ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + QFETCH( QString, collectionName ); + QFETCH( QStringList, caps ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setUserName( defaultUserName() ); + state->setServerCapabilities( caps ); + state->setCollection( collection ); + state->setParts( parts ); + ChangeCollectionTask *task = new ChangeCollectionTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == QLatin1String("cancelTask") && callNames[i] != QLatin1String("cancelTask") ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == QLatin1String("cancelTask") ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + if ( command == QLatin1String("collectionChangeCommitted") ) { + QCOMPARE( parameter.value().name(), collectionName ); + QCOMPARE( parameter.value().remoteId().right( collectionName.length() ), + collectionName ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestChangeCollectionTask ) + +#include "testchangecollectiontask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testchangeitemtask.cpp b/kdepim-runtime/resources/imap/tests/testchangeitemtask.cpp new file mode 100644 index 00000000..0ccacb7b --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testchangeitemtask.cpp @@ -0,0 +1,218 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "changeitemtask.h" +#include "uidnextattribute.h" + +#include + +Q_DECLARE_METATYPE(QSet) + +class TestChangeItemTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldAppendMessage_data() + { + QTest::addColumn( "item" ); + QTest::addColumn< QSet >( "parts" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Collection collection; + Akonadi::Item item; + QSet parts; + QString messageContent; + QList scenario; + QStringList callNames; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.addAttribute( new UidNextAttribute( 65 ) ); + item = Akonadi::Item( 2 ); + item.setParentCollection( collection ); + item.setRemoteId( "5" ); + + KMime::Message::Ptr message( new KMime::Message ); + + messageContent = "From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."; + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + parts.clear(); + parts << "PLD:RFC822"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {55}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done [ APPENDUID 1239890035 65 ]" + << "C: A000004 SELECT \"INBOX/Foo\"" + << "S: A000004 OK select done" + << "C: A000005 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000005 OK store done"; + + callNames.clear(); + callNames << "applyCollectionChanges" << "itemChangeCommitted"; + + QTest::newRow( "modifying mail content" ) << item << parts << scenario << callNames; + + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.addAttribute( new UidNextAttribute( 65 ) ); + item = Akonadi::Item( 2 ); + item.setParentCollection( collection ); + item.setRemoteId( "5" ); + + message = KMime::Message::Ptr( new KMime::Message ); + + messageContent = "From: ervin\nTo: someone\nSubject: foo\nMessage-ID: <42.4242.foo@bar.org>\n\nSpeechless..."; + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + parts.clear(); + parts << "PLD:RFC822"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {90}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done" + << "C: A000004 SELECT \"INBOX/Foo\"" + << "S: A000004 OK select done" + << "C: A000005 UID SEARCH HEADER Message-ID <42.4242.foo@bar.org>" + << "S: * SEARCH 65" + << "S: A000005 OK search done" + << "C: A000006 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000006 OK store done"; + + callNames.clear(); + callNames << "applyCollectionChanges" << "itemChangeCommitted"; + + QTest::newRow( "modifying mail content, no APPENDUID, message has Message-ID" ) << item << parts << scenario << callNames; + + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.addAttribute( new UidNextAttribute( 65 )); + item = Akonadi::Item( 2 ); + item.setParentCollection( collection ); + item.setRemoteId( "5" ); + + message = KMime::Message::Ptr( new KMime::Message ); + + messageContent = "From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."; + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + parts.clear(); + parts << "PLD:RFC822"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 APPEND \"INBOX/Foo\" {55}\r\n"+message->encodedContent(true) + << "S: A000003 OK append done" + << "C: A000004 SELECT \"INBOX/Foo\"" + << "S: A000004 OK select done" + << "C: A000005 UID SEARCH NEW UID 65:*" + << "S: * SEARCH 65" + << "S: A000005 OK search done" + << "C: A000006 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000006 OK store done"; + + callNames.clear(); + callNames << "applyCollectionChanges" << "itemChangeCommitted"; + + QTest::newRow( "modifying mail content, no APPENDUID, message has no Message-ID" ) << item << parts << scenario << callNames; + + + + // collection unchanged for this test + // item only gets a set of flags + item.setFlags( QSet() << "\\Foo" ); + + parts.clear(); + parts << "FLAGS"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID STORE 5 FLAGS (\\Foo)" + << "S: A000004 OK store done"; + + callNames.clear(); + callNames << "changeProcessed"; + + QTest::newRow( "modifying mail flags" ) << item << parts << scenario << callNames; + } + + void shouldAppendMessage() + { + QFETCH( Akonadi::Item, item ); + QFETCH( QSet, parts ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setParts( parts ); + state->setItem( item ); + ChangeItemTask *task = new ChangeItemTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestChangeItemTask ) + +#include "testchangeitemtask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testexpungecollectiontask.cpp b/kdepim-runtime/resources/imap/tests/testexpungecollectiontask.cpp new file mode 100644 index 00000000..bcf6b2e1 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testexpungecollectiontask.cpp @@ -0,0 +1,128 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "expungecollectiontask.h" + +class TestExpungeCollectionTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldDeleteMailBox_data() + { + QTest::addColumn( "collection" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Collection collection; + QSet parts; + QString messageContent; + QList scenario; + QStringList callNames; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done"; + + callNames.clear(); + callNames << "taskDone"; + + QTest::newRow( "normal case" ) << collection << scenario << callNames; + + + // We keep the same collection + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 NO select failed"; + + callNames.clear(); + callNames << "cancelTask"; + + QTest::newRow( "select failed" ) << collection << scenario << callNames; + + // We keep the same collection + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 NO expunge failed"; + + callNames.clear(); + callNames << "cancelTask"; + + QTest::newRow( "expunge failed" ) << collection << scenario << callNames; + } + + void shouldDeleteMailBox() + { + QFETCH( Akonadi::Collection, collection ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setCollection( collection ); + ExpungeCollectionTask *task = new ExpungeCollectionTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestExpungeCollectionTask ) + +#include "testexpungecollectiontask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testmovecollectiontask.cpp b/kdepim-runtime/resources/imap/tests/testmovecollectiontask.cpp new file mode 100644 index 00000000..53cb7455 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testmovecollectiontask.cpp @@ -0,0 +1,197 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "movecollectiontask.h" + +class TestMoveCollectionTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldRenameMailBox_data() + { + QTest::addColumn( "collection" ); + QTest::addColumn( "source" ); + QTest::addColumn( "target" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Collection root; + Akonadi::Collection inbox; + Akonadi::Collection collection; + Akonadi::Collection source; + Akonadi::Collection target; + QList scenario; + QStringList callNames; + + root = createCollectionChain( QString() ); + inbox = createCollectionChain( QLatin1String("/INBOX") ); + + source = Akonadi::Collection( 3 ); + source.setRemoteId( QLatin1String("/Foo") ); + source.setParentCollection( inbox ); + + collection = Akonadi::Collection( 10 ); + collection.setRemoteId( QLatin1String("/Baz") ); + collection.setParentCollection( source ); + + target = Akonadi::Collection( 4 ); + target.setRemoteId( QLatin1String("/Bar") ); + target.setParentCollection( inbox ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"INBOX/Foo/Baz\" \"INBOX/Bar/Baz\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"INBOX/Bar/Baz\"" + << "S: A000004 OK subscribe done"; + + callNames.clear(); + callNames << "collectionChangeCommitted"; + + QTest::newRow( "moving mailbox" ) << collection << source << target << scenario << callNames; + + { + const Akonadi::Collection toplevel = createCollectionChain( QLatin1String("/Bar") ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"Bar\" \"INBOX/Bar\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"INBOX/Bar\"" + << "S: A000004 OK subscribe done"; + + callNames.clear(); + callNames << "collectionChangeCommitted"; + + QTest::newRow( "move mailbox from toplevel" ) << toplevel << root << inbox << scenario << callNames; + } + + { + const Akonadi::Collection toplevel = createCollectionChain( QLatin1String("/INBOX/Bar") ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"INBOX/Bar\" \"Bar\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"Bar\"" + << "S: A000004 OK subscribe done"; + + callNames.clear(); + callNames << "collectionChangeCommitted"; + + QTest::newRow( "move mailbox to toplevel" ) << toplevel << inbox << root << scenario << callNames; + } + + // Same collections + // The scenario changes though + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"INBOX/Foo/Baz\" \"INBOX/Bar/Baz\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"INBOX/Bar/Baz\"" + << "S: A000004 NO subscribe failed"; + + callNames.clear(); + callNames << "emitWarning" << "collectionChangeCommitted"; + + QTest::newRow( "moving mailbox, subscribe fails" ) << collection << source << target << scenario << callNames; + + + + inbox = createCollectionChain( QLatin1String(".INBOX") ); + + source = Akonadi::Collection( 3 ); + source.setRemoteId( QLatin1String(".Foo") ); + source.setParentCollection( inbox ); + + collection = Akonadi::Collection( 10 ); + collection.setRemoteId( QLatin1String(".Baz") ); + collection.setParentCollection( source ); + + target = Akonadi::Collection( 4 ); + target.setRemoteId( QLatin1String(".Bar") ); + target.setParentCollection( inbox ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 RENAME \"INBOX.Foo.Baz\" \"INBOX.Bar.Baz\"" + << "S: A000003 OK rename done" + << "C: A000004 SUBSCRIBE \"INBOX.Bar.Baz\"" + << "S: A000004 OK subscribe done"; + + callNames.clear(); + callNames << "collectionChangeCommitted"; + + QTest::newRow( "moving mailbox with non-standard separators" ) << collection << source << target << scenario << callNames; + } + + void shouldRenameMailBox() + { + QFETCH( Akonadi::Collection, collection ); + QFETCH( Akonadi::Collection, source ); + QFETCH( Akonadi::Collection, target ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setCollection( collection ); + state->setSourceCollection( source ); + state->setTargetCollection( target ); + MoveCollectionTask *task = new MoveCollectionTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestMoveCollectionTask ) + +#include "testmovecollectiontask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testmoveitemstask.cpp b/kdepim-runtime/resources/imap/tests/testmoveitemstask.cpp new file mode 100644 index 00000000..ef5322c7 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testmoveitemstask.cpp @@ -0,0 +1,263 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "moveitemstask.h" +#include "uidnextattribute.h" + +#include + +Q_DECLARE_METATYPE(QSet) + +class TestMoveItemsTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldCopyAndDeleteMessage_data() + { + QTest::addColumn( "item" ); + QTest::addColumn( "source" ); + QTest::addColumn( "target" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Item item; + Akonadi::Collection inbox; + Akonadi::Collection source; + Akonadi::Collection target; + QList scenario; + QStringList callNames; + + item = Akonadi::Item( 1 ); + item.setRemoteId( "5" ); + + KMime::Message::Ptr message(new KMime::Message); + + QString messageContent = "From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."; + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload(message); + + inbox = createCollectionChain( QLatin1String("/INBOX") ); + source = Akonadi::Collection( 3 ); + source.setRemoteId( "/Foo" ); + source.setParentCollection( inbox ); + target = Akonadi::Collection( 4 ); + target.setRemoteId( "/Bar" ); + target.setParentCollection( inbox ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID COPY 5 \"INBOX/Bar\"" + << "S: A000004 OK copy [ COPYUID 1239890035 5 65 ]" + << "C: A000005 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000005 OK store done"; + + callNames.clear(); + callNames << "itemsChangesCommitted"; + + QTest::newRow( "moving mail" ) << item << source << target << scenario << callNames; + + + + + // Same item and collections + // The scenario changes though + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID COPY 5 \"INBOX/Bar\"" + << "S: A000004 OK copy [ COPYUID 1239890035 5 65 ]" + << "C: A000005 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000005 NO store failed"; + + callNames.clear(); + callNames << "emitWarning" << "itemsChangesCommitted"; + + QTest::newRow( "moving mail, store fails" ) << item << source << target << scenario << callNames; + + + + item = Akonadi::Item( 1 ); + item.setRemoteId( "5" ); + + message = KMime::Message::Ptr( new KMime::Message ); + + messageContent = "From: ervin\nTo: someone\nSubject: foo\nMessage-ID: <42.4242.foo@bar.org>\n\nSpeechless..."; + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + source = Akonadi::Collection( 3 ); + source.setRemoteId( "/Foo" ); + source.setParentCollection( inbox ); + source.addAttribute( new UidNextAttribute( 42 ) ); + target = Akonadi::Collection( 3 ); + target.setRemoteId( "/Bar" ); + target.setParentCollection( inbox ); + target.addAttribute( new UidNextAttribute( 65 ) ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID COPY 5 \"INBOX/Bar\"" + << "S: A000004 OK copy done" + << "C: A000005 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000005 OK store done" + << "C: A000006 SELECT \"INBOX/Bar\"" + << "S: A000006 OK select done" + << "C: A000007 UID SEARCH (HEADER Message-ID <42.4242.foo@bar.org>)" + << "S: * SEARCH 65" + << "S: A000007 OK search done"; + + callNames.clear(); + callNames << "itemsChangesCommitted" << "applyCollectionChanges"; + + QTest::newRow( "moving mail, no COPYUID, message had Message-ID" ) << item << source << target << scenario << callNames; + + + + + item = Akonadi::Item( 1 ); + item.setRemoteId( "5" ); + + message = KMime::Message::Ptr( new KMime::Message ); + + messageContent = "From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."; + + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + source = Akonadi::Collection( 3 ); + source.setRemoteId( "/Foo" ); + source.setParentCollection( inbox ); + source.addAttribute( new UidNextAttribute( 42 ) ); + target = Akonadi::Collection( 4 ); + target.setRemoteId( "/Bar" ); + target.setParentCollection( inbox ); + target.addAttribute( new UidNextAttribute( 65 ) ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID COPY 5 \"INBOX/Bar\"" + << "S: A000004 OK copy done" + << "C: A000005 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000005 OK store done" + << "C: A000006 SELECT \"INBOX/Bar\"" + << "S: A000006 OK select done" + << "C: A000007 UID SEARCH NEW UID 65:*" + << "S: * SEARCH 65" + << "S: A000007 OK search done"; + + callNames.clear(); + callNames << "itemsChangesCommitted" << "applyCollectionChanges"; + + QTest::newRow( "moving mail, no COPYUID, message didn't have Message-ID" ) << item << source << target << scenario << callNames; + + + item = Akonadi::Item( 1 ); + item.setRemoteId( "5" ); + message = KMime::Message::Ptr(new KMime::Message); + messageContent = "From: ervin\nTo: someone\nSubject: foo\nMessage-ID: <42.4242.foo@bar.org>\n\nSpeechless..."; + message->setContent( messageContent.toUtf8() ); + message->parse(); + item.setPayload( message ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID COPY 5 \"INBOX/Bar\"" + << "S: A000004 OK copy done" + << "C: A000005 UID STORE 5 +FLAGS (\\Deleted)" + << "S: A000005 OK store done" + << "C: A000006 SELECT \"INBOX/Bar\"" + << "S: A000006 OK select done" + << "C: A000007 UID SEARCH (HEADER Message-ID <42.4242.foo@bar.org>)" + << "S: * SEARCH 61 65" + << "S: A000007 OK search done"; + + callNames.clear(); + callNames << "itemsChangesCommitted" << "applyCollectionChanges"; + + QTest::newRow( "moving mail, no COPYUID, message didn't have unique Message-ID, but last one matches old uidnext" ) << item << source << target << scenario << callNames; + } + + void shouldCopyAndDeleteMessage() + { + QFETCH( Akonadi::Item, item ); + QFETCH( Akonadi::Collection, source ); + QFETCH( Akonadi::Collection, target ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr(new DummyResourceState); + state->setItem( item ); + state->setSourceCollection( source ); + state->setTargetCollection( target ); + MoveItemsTask *task = new MoveItemsTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestMoveItemsTask ) + +#include "testmoveitemstask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testremovecollectionrecursivetask.cpp b/kdepim-runtime/resources/imap/tests/testremovecollectionrecursivetask.cpp new file mode 100644 index 00000000..2b028712 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testremovecollectionrecursivetask.cpp @@ -0,0 +1,247 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "removecollectionrecursivetask.h" + +class TestRemoveCollectionRecursiveTask : public ImapTestBase +{ + Q_OBJECT + + void shouldDeleteMailBoxRecursive_data() + { + QTest::addColumn( "collection" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Collection collection; + QList scenario; + QStringList callNames; + + collection = createCollectionChain( QLatin1String("/INBOX/test1") ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LSUB \"\" *" + << "S: * LSUB ( \\HasChildren ) / INBOX" + << "S: * LSUB ( \\HasChildren ) / INBOX/test1" + << "S: * LSUB ( ) / INBOX/test1/test2" + << "S: A000003 OK Completed ( 0.000 secs 26 calls )" + << "C: A000004 SELECT \"INBOX/test1/test2\"" + << "S: * FLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen )" + << "S: * OK [ PERMANENTFLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen \\* ) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UNSEEN 1 ]" + << "S: * OK [ UIDVALIDITY 1292857898 ]" + << "S: * OK [ UIDNEXT 2 ]" + << "S: A000004 OK Completed [ READ-WRITE ]" + << "C: A000005 STORE 1:* +FLAGS (\\DELETED)" + << "S: * 1 FETCH ( FLAGS (\\Deleted) ) " + << "S: A000005 OK Completed" + << "C: A000006 EXPUNGE" + << "S: * 1 EXPUNGE" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000006 OK Completed" + << "C: A000007 CLOSE" + << "S: A000007 OK Completed" + << "C: A000008 DELETE \"INBOX/test1/test2\"" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000008 OK Completed" + << "C: A000009 SELECT \"INBOX/test1\"" + << "S: * FLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen )" + << "S: * OK [ PERMANENTFLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen \\* ) ]" + << "S: * 1 EXISTS" + << "S: * 1 RECENT" + << "S: * OK [ UIDVALIDITY 1292857888 ]" + << "S: * OK [ UIDNEXT 2 ]" + << "S: A000009 OK Completed [ READ-WRITE ]" + << "C: A000010 STORE 1:* +FLAGS (\\DELETED)" + << "S: * 1 FETCH ( FLAGS (\\Recent \\Deleted \\Seen) )" + << "S: A000010 OK Completed" + << "C: A000011 EXPUNGE" + << "S: * 1 EXPUNGE" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000011 OK Completed" + << "C: A000012 CLOSE" + << "S: A000012 OK Completed" + << "C: A000013 DELETE \"INBOX/test1\"" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000013 OK Completed"; + callNames.clear(); + callNames << "changeProcessed"; + + QTest::newRow( "normal case" ) << collection << scenario << callNames; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LSUB \"\" *" + << "S: * LSUB ( \\HasChildren ) / INBOX" + << "S: * LSUB ( \\HasChildren ) / INBOX/test1" + << "S: * LSUB ( ) / INBOX/test1/test2" + << "S: A000003 OK Completed ( 0.000 secs 26 calls )"; + collection.setRemoteId( "/test1" ); + collection.setParentCollection( Akonadi::Collection::root() ); + callNames.clear(); + callNames << "changeProcessed" << "emitWarning" << "synchronizeCollectionTree"; + QTest::newRow( "invalid collection" ) << collection << scenario << callNames; + + collection = createCollectionChain( QLatin1String(".INBOX.test1") ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LSUB \"\" *" + << "S: * LSUB ( \\HasChildren ) . INBOX" + << "S: * LSUB ( \\HasChildren ) . INBOX.test1" + << "S: * LSUB ( ) . INBOX.test1.test2" + << "S: A000003 OK Completed ( 0.000 secs 26 calls )" + << "C: A000004 SELECT \"INBOX.test1.test2\"" + << "S: * FLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen )" + << "S: * OK [ PERMANENTFLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen \\* ) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UNSEEN 1 ]" + << "S: * OK [ UIDVALIDITY 1292857898 ]" + << "S: * OK [ UIDNEXT 2 ]" + << "S: A000004 OK Completed [ READ-WRITE ]" + << "C: A000005 STORE 1:* +FLAGS (\\DELETED)" + << "S: * 1 FETCH ( FLAGS (\\Deleted) ) " + << "S: A000005 OK Completed" + << "C: A000006 EXPUNGE" + << "S: * 1 EXPUNGE" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000006 OK Completed" + << "C: A000007 CLOSE" + << "S: A000007 OK Completed" + << "C: A000008 DELETE \"INBOX.test1.test2\"" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000008 OK Completed" + << "C: A000009 SELECT \"INBOX.test1\"" + << "S: * FLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen )" + << "S: * OK [ PERMANENTFLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen \\* ) ]" + << "S: * 1 EXISTS" + << "S: * 1 RECENT" + << "S: * OK [ UIDVALIDITY 1292857888 ]" + << "S: * OK [ UIDNEXT 2 ]" + << "S: A000009 OK Completed [ READ-WRITE ]" + << "C: A000010 STORE 1:* +FLAGS (\\DELETED)" + << "S: * 1 FETCH ( FLAGS (\\Recent \\Deleted \\Seen) )" + << "S: A000010 OK Completed" + << "C: A000011 EXPUNGE" + << "S: * 1 EXPUNGE" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000011 OK Completed" + << "C: A000012 CLOSE" + << "S: A000012 OK Completed" + << "C: A000013 DELETE \"INBOX.test1\"" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000013 OK Completed"; + callNames.clear(); + callNames << "changeProcessed"; + QTest::newRow( "non-standard separator" ) << collection << scenario << callNames; + + collection = createCollectionChain( QLatin1String(".INBOX.test1") ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LSUB \"\" *" + << "S: * LSUB ( \\HasChildren ) . INBOX" + << "S: * LSUB ( \\HasChildren ) . INBOX.test1" + << "S: * LSUB ( ) . INBOX.test1.test2" + << "S: A000003 OK Completed ( 0.000 secs 26 calls )" + << "C: A000004 SELECT \"INBOX.test1.test2\"" + << "S: * FLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen )" + << "S: * OK [ PERMANENTFLAGS ( \\Answered \\Flagged \\Draft \\Deleted \\Seen \\* ) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UNSEEN 1 ]" + << "S: * OK [ UIDVALIDITY 1292857898 ]" + << "S: * OK [ UIDNEXT 2 ]" + << "S: A000004 OK Completed [ READ-WRITE ]" + << "C: A000005 STORE 1:* +FLAGS (\\DELETED)" + << "S: * 1 FETCH ( FLAGS (\\Deleted) ) " + << "S: A000005 OK Completed" + << "C: A000006 EXPUNGE" + << "S: * 1 EXPUNGE" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: A000006 OK Completed" + << "C: A000007 CLOSE" + << "S: A000007 NO Close failed"; + callNames.clear(); + callNames << "changeProcessed" << "emitWarning" << "synchronizeCollectionTree"; + QTest::newRow( "close failed" ) << collection << scenario << callNames; + } + + void shouldDeleteMailBoxRecursive() + { + QFETCH( Akonadi::Collection, collection ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setCollection( collection ); + RemoveCollectionRecursiveTask *task = new RemoveCollectionRecursiveTask( state ); + task->start( &pool ); + QEventLoop loop; + connect( task, SIGNAL(destroyed(QObject*)), &loop, SLOT(quit()) ); + loop.exec(); + + QCOMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestRemoveCollectionRecursiveTask ) + +#include "testremovecollectionrecursivetask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testresourcetask.cpp b/kdepim-runtime/resources/imap/tests/testresourcetask.cpp new file mode 100644 index 00000000..ebd1e6b1 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testresourcetask.cpp @@ -0,0 +1,178 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "resourcetask.h" + +Q_DECLARE_METATYPE( ResourceTask::ActionIfNoSession ) + +class DummyResourceTask : public ResourceTask +{ +public: + explicit DummyResourceTask( ActionIfNoSession action, ResourceStateInterface::Ptr resource, QObject *parent = 0 ) + : ResourceTask( action, resource, parent ) + { + + } + + void doStart( KIMAP::Session */*session*/ ) + { + cancelTask( "Dummy task" ); + } +}; + + +class TestResourceTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldRequestSession_data() + { + QTest::addColumn( "state" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "shouldConnect" ); + QTest::addColumn( "shouldRequestSession" ); + QTest::addColumn( "actionIfNoSession" ); + QTest::addColumn( "callNames" ); + QTest::addColumn( "firstCallParameter" ); + + DummyResourceState::Ptr state; + QList scenario; + QStringList callNames; + + state = DummyResourceState::Ptr( new DummyResourceState ); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000002 OK Completed"; + callNames.clear(); + callNames << "cancelTask"; + QTest::newRow( "normal case" ) << state << scenario + << true << false + << ResourceTask::DeferIfNoSession + << callNames << QVariant( "Dummy task" ); + + + state = DummyResourceState::Ptr( new DummyResourceState ); + callNames.clear(); + callNames << "deferTask"; + QTest::newRow( "all sessions allocated (defer)" ) << state << scenario + << true << true + << ResourceTask::DeferIfNoSession + << callNames << QVariant(); + + + state = DummyResourceState::Ptr( new DummyResourceState ); + callNames.clear(); + callNames << "cancelTask"; + QTest::newRow( "all sessions allocated (cancel)" ) << state << scenario + << true << true + << ResourceTask::CancelIfNoSession + << callNames << QVariant(); + + + state = DummyResourceState::Ptr( new DummyResourceState ); + scenario.clear(); + callNames.clear(); + callNames << "deferTask" << "scheduleConnectionAttempt"; + QTest::newRow( "disconnected pool (defer)" ) << state << scenario + << false << false + << ResourceTask::DeferIfNoSession + << callNames << QVariant(); + + + state = DummyResourceState::Ptr( new DummyResourceState ); + scenario.clear(); + callNames.clear(); + callNames << "cancelTask" << "scheduleConnectionAttempt"; + QTest::newRow( "disconnected pool (cancel)" ) << state << scenario + << false << false + << ResourceTask::CancelIfNoSession + << callNames << QVariant(); + } + + void shouldRequestSession() + { + QFETCH( DummyResourceState::Ptr, state ); + QFETCH( QList, scenario ); + QFETCH( bool, shouldConnect ); + QFETCH( bool, shouldRequestSession ); + QFETCH( ResourceTask::ActionIfNoSession, actionIfNoSession ); + QFETCH( QStringList, callNames ); + QFETCH( QVariant, firstCallParameter ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + if ( shouldConnect ) { + QSignalSpy poolSpy( &pool, SIGNAL(connectDone(int,QString)) ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + + QTRY_COMPARE( poolSpy.count(), 1 ); + QCOMPARE( poolSpy.at( 0 ).at( 0 ).toInt(), (int)SessionPool::NoError ); + } + + + if ( shouldRequestSession ) { + QSignalSpy requestSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + pool.requestSession(); + QTRY_COMPARE( requestSpy.count(), 1 ); + } + + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + DummyResourceTask *task = new DummyResourceTask( actionIfNoSession, state ); + task->start( &pool ); + + if ( shouldConnect ) { + QTRY_COMPARE( sessionSpy.count(), 1 ); + } else { + //We want to ensure the signal isn't emitted, so we have to wait + QTest::qWait( 500 ); + QCOMPARE( sessionSpy.count(), 0 ); + } + + QCOMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QCOMPARE( command, callNames[i] ); + } + + if ( firstCallParameter.toString() == "Dummy task" ) { + QCOMPARE( state->calls().first().second, firstCallParameter ); + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestResourceTask ) + +#include "testresourcetask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testretrievecollectionmetadatatask.cpp b/kdepim-runtime/resources/imap/tests/testretrievecollectionmetadatatask.cpp new file mode 100644 index 00000000..c6113f83 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testretrievecollectionmetadatatask.cpp @@ -0,0 +1,342 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "retrievecollectionmetadatatask.h" + +#include +#include +#include "collectionannotationsattribute.h" +#include "imapaclattribute.h" +#include "imapquotaattribute.h" +#include "noselectattribute.h" +#include "timestampattribute.h" +#include + +typedef QMap QBYTEARRAYMAP; + +Q_DECLARE_METATYPE( Akonadi::Collection::Rights ) +Q_DECLARE_METATYPE( QBYTEARRAYMAP ) + +class TestRetrieveCollectionMetadataTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + + void initTestCase() + { + Akonadi::AttributeFactory::registerAttribute(); + Akonadi::AttributeFactory::registerAttribute(); + } + + void shouldCollectionRetrieveMetadata_data() + { + QTest::addColumn( "collection" ); + QTest::addColumn( "capabilities" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + QTest::addColumn( "expectedRights" ); + QTest::addColumn( "expectedAnnotations" ); + + Akonadi::Collection collection; + QStringList capabilities; + QList scenario; + QStringList callNames; + QMap expectedAnnotations; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.setRights( 0 ); + collection.addAttribute( new TimestampAttribute( QDateTime::currentDateTime().toTime_t() ) ); + + capabilities.clear(); + capabilities << "ANNOTATEMORE" << "ACL" << "QUOTA"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 GETANNOTATION \"INBOX/Foo\" \"*\" \"value.shared\"" + << "S: * ANNOTATION INBOX/Foo /vendor/kolab/folder-test ( value.shared true )" + << "S: A000003 OK annotations retrieved" + << "C: A000004 GETACL \"INBOX/Foo\"" + << "S: * ACL INBOX/Foo foo@kde.org lrswipcda" + << "S: A000004 OK acl retrieved" + << "C: A000005 MYRIGHTS \"INBOX/Foo\"" + << "S: * MYRIGHTS \"INBOX/Foo\" lrswipkxtecda" + << "S: A000005 OK rights retrieved" + << "C: A000006 GETQUOTAROOT \"INBOX/Foo\"" + << "S: * QUOTAROOT INBOX/Foo user/foo" + << "S: * QUOTA user/foo ( )" + << "S: A000006 OK quota retrieved"; + + callNames.clear(); + callNames << "collectionAttributesRetrieved"; + + expectedAnnotations.clear(); + expectedAnnotations.insert( "/shared/vendor/kolab/folder-test", "true" ); + + Akonadi::Collection::Rights rights = Akonadi::Collection::AllRights; + QTest::newRow( "first listing, connected IMAP" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + + // + // Test that if the parent collection doesn't allow renaming in its ACL, the child mailbox + // can't be renamed, i.e. doesn't have the CanChangeCollection flag. + // + Akonadi::Collection parentCollection = createCollectionChain( QLatin1String("/INBOX") ); + QMap rightsMap; + rightsMap.insert( "Hans", KIMAP::Acl::Lookup | KIMAP::Acl::Read | KIMAP::Acl::KeepSeen | + KIMAP::Acl::Write | KIMAP::Acl::Insert | KIMAP::Acl::Post | + KIMAP::Acl::Delete ); + Akonadi::ImapAclAttribute *aclAttribute = new Akonadi::ImapAclAttribute(); + aclAttribute->setRights( rightsMap ); + parentCollection.addAttribute( aclAttribute ); + collection.setParentCollection( parentCollection ); + collection.removeAttribute(); + rights = Akonadi::Collection::AllRights; + rights &= ~Akonadi::Collection::CanChangeCollection; + QTest::newRow( "parent without create rights" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + + // + // Test that if the parent collection is a noselect folder, the child mailbox will not have + // rename (CanChangeCollection) permission. + // + parentCollection = createCollectionChain( QLatin1String("/INBOX") ); + NoSelectAttribute *noSelectAttribute = new NoSelectAttribute(); + parentCollection.addAttribute( noSelectAttribute ); + collection.setParentCollection( parentCollection ); + QTest::newRow( "parent wit noselect" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + parentCollection.removeAttribute(); + + // + // Test that the rights are properly set on the resulting collection if the mailbox doesn't + // have full rights. + // + collection.setParentCollection( createCollectionChain( QLatin1String("/INBOX") ) ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 GETANNOTATION \"INBOX/Foo\" \"*\" \"value.shared\"" + << "S: * ANNOTATION INBOX/Foo /vendor/kolab/folder-test ( value.shared true )" + << "S: A000003 OK annotations retrieved" + << "C: A000004 GETACL \"INBOX/Foo\"" + << "S: * ACL INBOX/Foo foo@kde.org wi" + << "S: A000004 OK acl retrieved" + << "C: A000005 MYRIGHTS \"INBOX/Foo\"" + << "S: * MYRIGHTS \"INBOX/Foo\" wi" + << "S: A000005 OK rights retrieved" + << "C: A000006 GETQUOTAROOT \"INBOX/Foo\"" + << "S: * QUOTAROOT INBOX/Foo user/foo" + << "S: * QUOTA user/foo ( )" + << "S: A000006 OK quota retrieved"; + rights = Akonadi::Collection::CanCreateItem | Akonadi::Collection::CanChangeItem | + Akonadi::Collection::CanChangeCollection; + QTest::newRow( "only some rights" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + + // + // Test that a warning is issued if the insert rights of a folder have been revoked on the server. + // + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.addAttribute( new TimestampAttribute( QDateTime::currentDateTime().toTime_t() ) ); + collection.setParentCollection( parentCollection ); + collection.setRights( Akonadi::Collection::CanCreateItem ); + + capabilities.clear(); + capabilities << "ANNOTATEMORE" << "ACL" << "QUOTA"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 GETANNOTATION \"INBOX/Foo\" \"*\" \"value.shared\"" + << "S: * ANNOTATION INBOX/Foo /vendor/kolab/folder-test ( value.shared true )" + << "S: A000003 OK annotations retrieved" + << "C: A000004 GETACL \"INBOX/Foo\"" + << "S: * ACL INBOX/Foo foo@kde.org wi" + << "S: A000004 OK acl retrieved" + << "C: A000005 MYRIGHTS \"INBOX/Foo\"" + << "S: * MYRIGHTS \"INBOX/Foo\" w" + << "S: A000005 OK rights retrieved" + << "C: A000006 GETQUOTAROOT \"INBOX/Foo\"" + << "S: * QUOTAROOT INBOX/Foo user/foo" + << "S: * QUOTA user/foo ( )" + << "S: A000006 OK quota retrieved"; + + callNames.clear(); + callNames << "showInformationDialog"; + callNames << "collectionAttributesRetrieved"; + + rights = Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanChangeCollection; + QTest::newRow( "revoked rights" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + + // + // Test that NoInferiors overrides acl rights and disallows creating new mailboxes + // + collection.setParentCollection( createCollectionChain( QString() ) ); + collection.setRemoteId( "/INBOX" ); + collection.setRights( Akonadi::Collection::AllRights ); + collection.addAttribute( new NoInferiorsAttribute( true ) ); + collection.removeAttribute(); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 GETANNOTATION \"INBOX\" \"*\" \"value.shared\"" + << "S: * ANNOTATION INBOX /vendor/kolab/folder-test ( value.shared true )" + << "S: A000003 OK annotations retrieved" + << "C: A000004 GETACL \"INBOX\"" + << "S: * ACL INBOX foo@kde.org wik" + << "S: A000004 OK acl retrieved" + << "C: A000005 MYRIGHTS \"INBOX\"" + << "S: * MYRIGHTS \"INBOX\" wk" + << "S: A000005 OK rights retrieved" + << "C: A000006 GETQUOTAROOT \"INBOX\"" + << "S: * QUOTAROOT INBOX user" + << "S: * QUOTA user ( )" + << "S: A000006 OK quota retrieved"; + + callNames.clear(); + callNames << "collectionAttributesRetrieved"; + + rights = Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanChangeCollection; + + QTest::newRow( "noinferiors" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.setRights( 0 ); + collection.removeAttribute(); + + capabilities.clear(); + capabilities << "METADATA" << "ACL" << "QUOTA"; + + expectedAnnotations.clear(); + expectedAnnotations.insert( "/shared/vendor/kolab/folder-test", "true" ); + expectedAnnotations.insert( "/shared/vendor/kolab/folder-test2", "" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 GETMETADATA (DEPTH infinity) \"INBOX/Foo\" (/shared)" + << "S: * METADATA \"INBOX/Foo\" (/shared/vendor/kolab/folder-test \"true\")" + << "S: * METADATA \"INBOX/Foo\" (/shared/vendor/kolab/folder-test2 \"NIL\")" + << "S: * METADATA \"INBOX/Foo\" (/shared/vendor/cmu/cyrus-imapd/lastupdate \"true\")" + << "S: A000003 OK GETMETADATA complete" + << "C: A000004 GETACL \"INBOX/Foo\"" + << "S: * ACL INBOX/Foo foo@kde.org lrswipcda" + << "S: A000004 OK acl retrieved" + << "C: A000005 MYRIGHTS \"INBOX/Foo\"" + << "S: * MYRIGHTS \"INBOX/Foo\" lrswipkxtecda" + << "S: A000005 OK rights retrieved" + << "C: A000006 GETQUOTAROOT \"INBOX/Foo\"" + << "S: * QUOTAROOT INBOX/Foo user/Foo" + << "S: * QUOTA user/Foo ( )" + << "S: A000006 OK quota retrieved"; + + callNames.clear(); + callNames << "collectionAttributesRetrieved"; + + rights = Akonadi::Collection::AllRights; + QTest::newRow( "METADATA" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.setRights( 0 ); + collection.addAttribute( new TimestampAttribute( QDateTime::currentDateTime().toTime_t() ) ); + + capabilities.clear(); + expectedAnnotations.clear(); + + callNames.clear(); + callNames << "collectionAttributesRetrieved"; + + rights = 0; + + scenario.clear(); + scenario << defaultPoolConnectionScenario(); + + QTest::newRow( "no capabilities" ) << collection << capabilities << scenario + << callNames << rights << expectedAnnotations; + } + + void shouldCollectionRetrieveMetadata() + { + QFETCH( Akonadi::Collection, collection ); + QFETCH( QStringList, capabilities ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + QFETCH( Akonadi::Collection::Rights, expectedRights ); + QFETCH( QBYTEARRAYMAP, expectedAnnotations ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setCollection( collection ); + state->setServerCapabilities( capabilities ); + state->setUserName( "Hans" ); + RetrieveCollectionMetadataTask *task = new RetrieveCollectionMetadataTask( state ); + + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + + if ( command == "collectionAttributesRetrieved" ) { + Akonadi::Collection collection = parameter.value(); + QCOMPARE( collection.rights(), expectedRights ); + QVERIFY( collection.hasAttribute() ); + + const qint64 timestamp = collection.attribute()->timestamp(); + const qint64 currentTimestamp = QDateTime::currentDateTime().toTime_t(); + QVERIFY( qAbs( currentTimestamp - timestamp ) < 5 ); + + if ( !expectedAnnotations.isEmpty() ) { + QVERIFY( collection.hasAttribute() ); + QCOMPARE( collection.attribute()->annotations(), expectedAnnotations ); + } + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestRetrieveCollectionMetadataTask ) + +#include "testretrievecollectionmetadatatask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testretrievecollectionstask.cpp b/kdepim-runtime/resources/imap/tests/testretrievecollectionstask.cpp new file mode 100644 index 00000000..0050052e --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testretrievecollectionstask.cpp @@ -0,0 +1,491 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "retrievecollectionstask.h" +#include "noselectattribute.h" +#include + +#include +#include +#include + +class TestRetrieveCollectionsTask : public ImapTestBase +{ + Q_OBJECT +public: + TestRetrieveCollectionsTask( QObject *parent = 0 ) + : ImapTestBase( parent ), m_nextCollectionId( 1 ) + { + } + +private slots: + void shouldListCollections_data() + { + QTest::addColumn( "expectedCollections" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + QTest::addColumn( "isSubscriptionEnabled" ); + QTest::addColumn( "isDisconnectedModeEnabled" ); + QTest::addColumn( "intervalCheckTime" ); + QTest::addColumn( "separator" ); + + Akonadi::Collection collection; + + Akonadi::Collection::List expectedCollections; + QList scenario; + QStringList callNames; + bool isSubscriptionEnabled; + bool isDisconnectedModeEnabled; + int intervalCheckTime; + + expectedCollections.clear(); + expectedCollections << createRootCollection() + << createCollection( "/", "INBOX" ) + << createCollection( "/", "INBOX/Calendar" ) + << createCollection( "/", "INBOX/Calendar/Private" ) + << createCollection( "/", "INBOX/Archives" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: * LIST ( \\HasChildren ) / INBOX/Calendar" + << "S: * LIST ( ) / INBOX/Calendar/Private" + << "S: * LIST ( ) / INBOX/Archives" + << "S: A000003 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "first listing, connected IMAP" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + + + expectedCollections.clear(); + expectedCollections << createRootCollection( true, 5 ) + << createCollection( "/", "INBOX" ) + << createCollection( "/", "INBOX/Calendar" ) + << createCollection( "/", "INBOX/Calendar/Private" ) + << createCollection( "/", "INBOX/Archives" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: * LIST ( \\HasChildren ) / INBOX/Calendar" + << "S: * LIST ( ) / INBOX/Calendar/Private" + << "S: * LIST ( ) / INBOX/Archives" + << "S: A000003 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = true; + intervalCheckTime = 5; + + QTest::newRow( "first listing, disconnected IMAP" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + + + expectedCollections.clear(); + expectedCollections << createRootCollection( true, 5 ) + << createCollection( "/", "INBOX" ) + << createCollection( "/", "INBOX/Archives" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: * LIST ( \\HasChildren ) / INBOX/" + << "S: * LIST ( \\HasChildren ) / INBOX/Archives" + << "S: A000003 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = true; + intervalCheckTime = 5; + + QTest::newRow( "first listing, spurious INBOX/ (BR: 25342)" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + + expectedCollections.clear(); + expectedCollections << createRootCollection() + << createCollection( "/", "INBOX" ) + << createCollection( "/", "INBOX/Calendar", true ) + << createCollection( "/", "INBOX/Calendar/Private" ) + << createCollection( "/", "INBOX/Archives" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: * LIST ( ) / INBOX/Calendar/Private" + << "S: * LIST ( ) / INBOX/Archives" + << "S: A000003 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "auto-insert missing nodes in the tree" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + + + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( ) / INBOX/Archives" + << "S: * LIST ( ) / INBOX/Calendar/Private" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: A000003 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "auto-insert missing nodes in the tree (reverse order)" ) + << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + + + expectedCollections.clear(); + expectedCollections << createRootCollection() + << createCollection( "/", "INBOX" ) + << createCollection( "/", "INBOX/Calendar" ) + << createCollection( "/", "INBOX/Calendar/Private" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( ) / INBOX/Unsubscribed" + << "S: * LIST ( ) / INBOX/Calendar" + << "S: * LIST ( ) / INBOX/Calendar/Private" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: A000003 OK list done" + << "C: A000004 LSUB \"\" *" + << "S: * LSUB ( \\HasChildren ) / INBOX" + << "S: * LSUB ( ) / INBOX/SubscribedButNotExisting" + << "S: * LSUB ( ) / INBOX/Calendar" + << "S: * LSUB ( ) / INBOX/Calendar/Private" + << "S: A000004 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = true; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "subscription enabled" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( ) / INBOX/Unsubscribed" + << "S: * LIST ( ) / INBOX/Calendar" + << "S: * LIST ( ) / INBOX/Calendar/Private" + << "S: * LIST ( \\HasChildren ) / INBOX" + << "S: A000003 OK list done" + << "C: A000004 LSUB \"\" *" + << "S: * LSUB ( \\HasChildren ) / Inbox" + << "S: * LSUB ( ) / Inbox/SubscribedButNotExisting" + << "S: * LSUB ( ) / Inbox/Calendar" + << "S: * LSUB ( ) / Inbox/Calendar/Private" + << "S: A000004 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = true; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "subscription enabled, case insensitive inbox" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + expectedCollections.clear(); + expectedCollections << createRootCollection() + << createCollection( "/", "INBOX", false, true ) + << createCollection( "/", "Archive" ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( \\Noinferiors ) / INBOX" + << "S: * LIST ( ) / Archive" + << "S: A000003 OK list done"; + + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "Noinferiors" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('/'); + + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 LIST \"\" *" + << "S: * LIST ( ) . INBOX" + << "S: * LIST ( ) . INBOX.Foo" + << "S: * LIST ( ) . INBOX.Bar" + << "S: A000003 OK list done"; + callNames.clear(); + callNames << "setIdleCollection" << "collectionsRetrieved"; + + expectedCollections.clear(); + expectedCollections << createRootCollection() + << createCollection( ".", "INBOX" ) + << createCollection( ".", "INBOX.Foo" ) + << createCollection( ".", "INBOX.Bar" ); + isSubscriptionEnabled = false; + isDisconnectedModeEnabled = false; + intervalCheckTime = -1; + + QTest::newRow( "non-standard separators" ) << expectedCollections << scenario << callNames + << isSubscriptionEnabled << isDisconnectedModeEnabled << intervalCheckTime + << QChar('.'); + } + + void shouldListCollections() + { + QFETCH( Akonadi::Collection::List, expectedCollections ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + QFETCH( bool, isSubscriptionEnabled ); + QFETCH( bool, isDisconnectedModeEnabled ); + QFETCH( int, intervalCheckTime ); + QFETCH( QChar, separator ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setResourceName( "resource" ); + state->setSubscriptionEnabled( isSubscriptionEnabled ); + state->setDisconnectedModeEnabled( isDisconnectedModeEnabled ); + state->setIntervalCheckTime( intervalCheckTime ); + + RetrieveCollectionsTask *task = new RetrieveCollectionsTask( state ); + task->start( &pool ); + + Akonadi::Collection::List collections; + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command=="cancelTask" && callNames[i]!="cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } else if ( command == "collectionsRetrieved" ) { + collections+= parameter.value(); + } + } + + QCOMPARE( state->separatorCharacter(), separator ); + + QVERIFY( server.isAllScenarioDone() ); + compareCollectionLists( collections, expectedCollections ); + + server.quit(); + } + +private: + qint64 m_nextCollectionId; + + Akonadi::Collection createRootCollection( bool isDisconnectedImap = false, int intervalCheck = -1 ) + { + // Root + Akonadi::Collection collection = Akonadi::Collection( m_nextCollectionId++ ); + collection.setName( "resource" ); + collection.setRemoteId( "root-id" ); + collection.setContentMimeTypes( QStringList( Akonadi::Collection::mimeType() ) ); + collection.setParentCollection( Akonadi::Collection::root() ); + collection.addAttribute( new NoSelectAttribute( true ) ); + + Akonadi::CachePolicy policy; + policy.setInheritFromParent( false ); + policy.setSyncOnDemand( true ); + + if ( isDisconnectedImap ) { + policy.setLocalParts( QStringList() << Akonadi::MessagePart::Envelope + << Akonadi::MessagePart::Header + << Akonadi::MessagePart::Body ); + policy.setCacheTimeout( -1 ); + } else { + policy.setLocalParts( QStringList() << Akonadi::MessagePart::Envelope + << Akonadi::MessagePart::Header ); + policy.setCacheTimeout( 60 ); + } + + policy.setIntervalCheckTime( intervalCheck ); + + collection.setCachePolicy( policy ); + + return collection; + } + + Akonadi::Collection createCollection( const QString &separator, const QString &path, bool isNoSelect = false, bool isNoInferiors = false ) + { + // No path? That's the root of this resource then + if ( path.isEmpty() ) { + return createRootCollection(); + } + + QStringList pathParts = path.split( separator ); + + const QString pathPart = pathParts.takeLast(); + const QString parentPath = pathParts.join( separator ); + + // Here we should likely reuse already produced collections if possible to be 100% accurate + // but in the tests we check only a limited amount of properties (namely remote id and name). + const Akonadi::Collection parentCollection = createCollection( separator, parentPath ); + + + Akonadi::Collection collection( m_nextCollectionId++ ); + collection.setName( pathPart ); + collection.setRemoteId( separator + pathPart ); + + collection.setParentCollection( parentCollection ); + collection.setContentMimeTypes( QStringList() << "message/rfc822" << Akonadi::Collection::mimeType() ); + + // If the folder is the Inbox, make some special settings. + if ( pathPart.compare( QLatin1String( "INBOX" ) , Qt::CaseInsensitive ) == 0 ) { + Akonadi::EntityDisplayAttribute *attr = new Akonadi::EntityDisplayAttribute; + attr->setDisplayName( i18n( "Inbox" ) ); + attr->setIconName( "mail-folder-inbox" ); + collection.addAttribute( attr ); + } + + // If the folder is the user top-level folder, mark it as well, even although it is not officially noted in the RFC + if ( ( pathPart.compare( QLatin1String( "user" ) , Qt::CaseInsensitive ) == 0) && isNoSelect ) { + Akonadi::EntityDisplayAttribute *attr = new Akonadi::EntityDisplayAttribute; + attr->setDisplayName( i18n( "Shared Folders" ) ); + attr->setIconName( "x-mail-distribution-list" ); + collection.addAttribute( attr ); + } + + // If this folder is a noselect folder, make some special settings. + if ( isNoSelect ) { + collection.addAttribute( new NoSelectAttribute( true ) ); + collection.setContentMimeTypes( QStringList() << Akonadi::Collection::mimeType() ); + collection.setRights( Akonadi::Collection::ReadOnly ); + } + + if ( isNoInferiors ) { + collection.addAttribute( new NoInferiorsAttribute( true ) ); + collection.setRights( collection.rights() & ~Akonadi::Collection::CanCreateCollection ); + } + + return collection; + } + + void compareCollectionLists( const Akonadi::Collection::List &resultList, + const Akonadi::Collection::List &expectedList ) + { + for ( int i = 0; i< expectedList.size(); i++ ) { + Akonadi::Collection expected = expectedList[i]; + bool found = false; + + for ( int j = 0; j < resultList.size(); j++ ) { + Akonadi::Collection result = resultList[j]; + + if ( result.remoteId() == expected.remoteId() ) { + found = true; + + QVERIFY( !result.name().isEmpty() ); + + QCOMPARE( result.name(), expected.name() ); + QCOMPARE( result.contentMimeTypes(), expected.contentMimeTypes() ); + QCOMPARE( result.rights(), expected.rights() ); + if ( expected.parentCollection() == Akonadi::Collection::root() ) { + QCOMPARE( result.parentCollection(), expected.parentCollection() ); + } else { + QCOMPARE( result.parentCollection().remoteId(), expected.parentCollection().remoteId() ); + } + + QCOMPARE( result.cachePolicy().inheritFromParent(), expected.cachePolicy().inheritFromParent() ); + QCOMPARE( result.cachePolicy().syncOnDemand(), expected.cachePolicy().syncOnDemand() ); + QCOMPARE( result.cachePolicy().localParts(), expected.cachePolicy().localParts() ); + QCOMPARE( result.cachePolicy().cacheTimeout(), expected.cachePolicy().cacheTimeout() ); + QCOMPARE( result.cachePolicy().intervalCheckTime(), expected.cachePolicy().intervalCheckTime() ); + + QCOMPARE( result.hasAttribute(), expected.hasAttribute() ); + QCOMPARE( result.hasAttribute(), expected.hasAttribute() ); + + break; + } + } + + QVERIFY2( found, QString( "%1 not found!" ).arg(expected.remoteId() ).toUtf8() ); + } + + QCOMPARE( resultList.size(), expectedList.size() ); + } +}; + +QTEST_KDEMAIN_CORE( TestRetrieveCollectionsTask ) + +#include "testretrievecollectionstask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testretrieveitemstask.cpp b/kdepim-runtime/resources/imap/tests/testretrieveitemstask.cpp new file mode 100644 index 00000000..bebb2a1a --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testretrieveitemstask.cpp @@ -0,0 +1,613 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "retrieveitemstask.h" +#include "uidnextattribute.h" +#include +#include + +#include +#include +#include + +class TestRetrieveItemsTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldIntrospectCollection_data() + { + QTest::addColumn( "collection" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callNames" ); + + Akonadi::Collection collection; + QList scenario; + QStringList callNames; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 1:9" + << "S: * SEARCH 1 2 3 4 5 6 7 8 9" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE " + "BODY.PEEK[HEADER] " + "FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[HEADER] {69}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + " )" + << "S: A000007 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone" ; + + + QTest::newRow( "first listing, connected IMAP" ) << collection << scenario << callNames; + + Akonadi::CachePolicy policy; + policy.setLocalParts( QStringList() << Akonadi::MessagePart::Envelope + << Akonadi::MessagePart::Header + << Akonadi::MessagePart::Body ); + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 1:9" + << "S: * SEARCH 1 2 3 4 5 6 7 8 9" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + << "S: A000007 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone"; + + QTest::newRow( "first listing, disconnected IMAP" ) << collection << scenario << callNames; + + + Akonadi::CollectionStatistics stats; + stats.setCount( 1 ); + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext(9); + collection.setCachePolicy( policy ); + collection.setStatistics( stats ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 1:9" + << "S: * SEARCH 1 2 3 4 5 6 7 8 9" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 1:9 (FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 )" + << "S: A000007 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone"; + + //Disabled test since the flag sync is disabled if CONDSTORE is not supported +// QTest::newRow( "second listing, checking for flag changes" ) << collection << scenario << callNames; + + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + stats.setCount( 1 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 0 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: A000005 OK select done"; + + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone"; + + QTest::newRow( "third listing, full sync, empty folder" ) << collection << scenario << callNames; + + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 8 ); + stats.setCount( 4 ); + collection.setStatistics( stats ); + collection.attribute( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 5 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: * OK [ HIGHESTMODSEQ 123456789 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 8:9" + << "S: * SEARCH 8 9" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 8:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 5 FETCH ( FLAGS (\\Seen) UID 9 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + << "S: A000007 OK fetch done" + << "C: A000008 UID SEARCH UID 1:7" + << "S: * SEARCH 1 2 3 4 5 6 7" + << "S: A000008 OK search done" + << "C: A000009 UID FETCH 1:7 (FLAGS UID)" + << "S: * 1 FETCH" + << "S: * 2 FETCH" + << "S: * 3 FETCH" + << "S: * 4 FETCH" + << "S: A000009 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental"<< "itemsRetrievalDone"; + + //We know no messages have been removed, so we can do an incremental update + QTest::newRow( "uidnext changed, fetch new messages incrementally" ) << collection << scenario << callNames; + + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 8 ); + stats.setCount( 5 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 5 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: * OK [ HIGHESTMODSEQ 123456789 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 8:9" + << "S: * SEARCH 8 9" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 8:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 4 FETCH ( FLAGS (\\Seen) UID 8 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + << "S: * 5 FETCH ( FLAGS (\\Seen) UID 9 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + << "S: A000007 OK fetch done" + << "C: A000008 UID SEARCH UID 1:7" + << "S: * SEARCH 1 2 3 4 5 6 7" + << "S: A000008 OK search done" + << "C: A000009 UID FETCH 1:7 (FLAGS UID)" + << "S: * 1 FETCH" + << "S: * 2 FETCH" + << "S: * 3 FETCH" + << "S: A000009 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone"; + + //A new message has been added and an old one removed, we can't do an incremental update + QTest::newRow( "uidnext changed, fetch new messages and list flags" ) << collection << scenario << callNames; + + + collection = createCollectionChain(QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 9 ); + collection.attribute( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456789 ); + stats.setCount( 5 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario( QList() << "CONDSTORE" ) + << "C: A000003 SELECT \"INBOX/Foo\" (CONDSTORE)" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge DONE" + << "C: A000005 SELECT \"INBOX/Foo\" (CONDSTORE)" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 5 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: * OK [ HIGHESTMODSEQ 123456789 ]" + << "S: A000005 OK select done"; + callNames.clear(); + callNames << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone"; + + //No flags have changed + QTest::newRow( "highestmodseq test" ) << collection << scenario << callNames; + + collection = createCollectionChain(QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 9 ); + collection.attribute( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 ); + stats.setCount( 5 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario( QList() << "CONDSTORE" ) + << "C: A000003 SELECT \"INBOX/Foo\" (CONDSTORE)" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge DONE" + << "C: A000005 SELECT \"INBOX/Foo\" (CONDSTORE)" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 5 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: * OK [ HIGHESTMODSEQ 123456789 ]" + << "S: A000005 OK select done" + << "C: A000006 UID FETCH 1:9 (FLAGS UID) (CHANGEDSINCE 123456788)" + << "S: * 5 FETCH ( UID 8 FLAGS () )" + << "S: A000006 OK fetch done"; + callNames.clear(); + callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone"; + + //fetch only changed flags + QTest::newRow( "changedsince test" ) << collection << scenario << callNames; + + collection = createCollectionChain(QLatin1String("/INBOX/Foo") ); + collection.setCachePolicy( policy ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 9 ); + collection.attribute( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 ); + stats.setCount( 5 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario( QList() << "XYMHIGHESTMODSEQ" ) + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge DONE" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 5 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: * OK [ HIGHESTMODSEQ 123456789 ]" + << "S: A000005 OK select done"; + //Disabled since the flag sync is disabled if CONDSTORE is not supported +// << "C: A000006 UID SEARCH UID 1:9" +// << "S: * SEARCH 1 2 3 4 5 6 7 8 9" +// << "S: A000006 OK search done" +// << "C: A000007 UID FETCH 1:9 (FLAGS UID)" +// << "S: * 5 FETCH ( UID 8 FLAGS () )" +// << "S: A000007 OK fetch done"; + callNames.clear(); + + //Disabled since the flag sync is disabled if CONDSTORE is not supported + callNames << /*"itemsRetrievedIncremental" << */"applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone"; + + //Don't rely on yahoos highestmodseq implementation + QTest::newRow( "yahoo highestmodseq test" ) << collection << scenario << callNames; + + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 9 ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(3); + collection.setCachePolicy( policy ); + stats.setCount( 1 ); + collection.setStatistics( stats ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 1 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 1:9" + << "S: * SEARCH 1 2 3 4 5 6 7 8 9" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + << "S: A000007 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone"; + + QTest::newRow( "uidvalidity changed" ) << collection << scenario << callNames; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Collection::AddIfMissing)->setUidNext(105); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + stats.setCount(104); + collection.setStatistics( stats ); + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 119 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 120 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 105:120" + //We asked for until 120 but only 119 is available (120 is uidnext) + << "S: * SEARCH 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 105:114 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 105 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + //9 more would follow but are excluded for clarity + << "S: A000007 OK fetch done" + << "C: A000008 UID FETCH 115:119 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 115 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + //4 more would follow but are excluded for clarity + << "S: A000008 OK fetch done" + << "C: A000009 UID SEARCH UID 1:104" + << "S: * SEARCH 1 2 99 100" + << "S: A000009 OK search done" + << "C: A000010 UID FETCH 1:2,99:100 (FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 1 )" + //3 more would follow but are excluded for clarity + << "S: A000010 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrievedIncremental" << "itemsRetrievedIncremental" << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone"; + + QTest::newRow( "test batch processing" ) << collection << scenario << callNames; + + collection = createCollectionChain(QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( 9 ); + collection.attribute( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456789 ); + stats.setCount( 5 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario( QList() << "CONDSTORE" ) + << "C: A000003 SELECT \"INBOX/Foo\" (CONDSTORE)" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge DONE" + << "C: A000005 SELECT \"INBOX/Foo\" (CONDSTORE)" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 4 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: * OK [ UIDNEXT 9 ]" + << "S: * OK [ HIGHESTMODSEQ 123456789 ]" + << "S: A000005 OK select done" + << "C: A000006 UID SEARCH UID 1:9" + << "S: * SEARCH 1 2 3 4" + << "S: A000006 OK search done" + << "C: A000007 UID FETCH 1:4 (FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 1 )" + << "S: * 2 FETCH ( FLAGS (\\Seen) UID 2 )" + << "S: * 3 FETCH ( FLAGS (\\Seen) UID 3 )" + << "S: * 4 FETCH ( FLAGS (\\Seen) UID 4 )" + << "S: A000007 OK fetch done"; + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone"; + + //fetch only changed flags + QTest::newRow( "remote message deleted" ) << collection << scenario << callNames; + + collection = createCollectionChain(QLatin1String("/INBOX/Foo") ); + collection.attribute(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135); + collection.setCachePolicy( policy ); + collection.attribute( Akonadi::Collection::AddIfMissing )->setUidNext( -1 ); + collection.attribute( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456789 ); + stats.setCount( 0 ); + collection.setStatistics( stats ); + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 EXPUNGE" + << "S: A000004 OK expunge done" + << "C: A000005 SELECT \"INBOX/Foo\"" + << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)" + << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]" + << "S: * 9 EXISTS" + << "S: * 0 RECENT" + << "S: * OK [ UIDVALIDITY 1149151135 ]" + << "S: A000005 OK select done" + << "C: A000006 FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)" + << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" " + "RFC822.SIZE 75 BODY[] {75}\r\n" + "From: Foo \r\n" + "To: Bar \r\n" + "Subject: Test Mail\r\n" + "\r\n" + "Test\r\n" + " )" + << "S: A000006 OK fetch done"; + + callNames.clear(); + callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone" ; + + QTest::newRow( "missing uidnext" ) << collection << scenario << callNames; + + } + + void shouldIntrospectCollection() + { + QFETCH( Akonadi::Collection, collection ); + QFETCH( QList, scenario ); + QFETCH( QStringList, callNames ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setServerCapabilities( pool.serverCapabilities() ); + state->setCollection( collection ); + + RetrieveItemsTask *task = new RetrieveItemsTask( state ); + task->setFetchMissingItemBodies( false ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), callNames.size() ); + kDebug() << state->calls(); + for ( int i = 0; i < callNames.size(); i++ ) { + QString command = QString::fromUtf8(state->calls().at( i ).first); + QVariant parameter = state->calls().at( i ).second; + + if ( command == "cancelTask" && callNames[i] != "cancelTask" ) { + kDebug() << "Got a cancel:" << parameter.toString(); + } + + QCOMPARE( command, callNames[i] ); + + if ( command == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestRetrieveItemsTask ) + +#include "testretrieveitemstask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testretrieveitemtask.cpp b/kdepim-runtime/resources/imap/tests/testretrieveitemtask.cpp new file mode 100644 index 00000000..3db48669 --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testretrieveitemtask.cpp @@ -0,0 +1,126 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include "retrieveitemtask.h" + +class TestRetrieveItemTask : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldFetchMessage_data() + { + QTest::addColumn( "item" ); + QTest::addColumn( "message" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "callName" ); + + Akonadi::Collection collection; + Akonadi::Item item; + QString message; + QList scenario; + + collection = createCollectionChain( QLatin1String("/INBOX/Foo") ); + item = Akonadi::Item( 2 ); + item.setParentCollection( collection ); + item.setRemoteId( "42" ); + + message = "From: ervin\nTo: someone\nSubject: foo\n\nSpeechless..."; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID FETCH 42 (BODY.PEEK[] UID)" + << "S: * 10 FETCH (UID 42 BODY[] \"From: ervin\nTo: someone\nSubject: foo\n\nSpeechless...\")" + << "S: A000004 OK fetch done"; + QTest::newRow( "normal case" ) << item << message << scenario << "itemRetrieved"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 NO select fail"; + QTest::newRow( "select fail" ) << item << message << scenario << "cancelTask"; + + scenario.clear(); + scenario << defaultPoolConnectionScenario() + << "C: A000003 SELECT \"INBOX/Foo\"" + << "S: A000003 OK select done" + << "C: A000004 UID FETCH 42 (BODY.PEEK[] UID)" + << "S: A000004 NO fetch failed"; + QTest::newRow( "fetch fail" ) << item << message << scenario << "cancelTask"; + } + + void shouldFetchMessage() + { + QFETCH( Akonadi::Item, item ); + QFETCH( QString, message ); + QFETCH( QList, scenario ); + QFETCH( QString, callName ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 1 ); + + pool.setPasswordRequester( createDefaultRequester() ); + QVERIFY( pool.connect( createDefaultAccount() ) ); + QVERIFY( waitForSignal( &pool, SIGNAL(connectDone(int,QString)) ) ); + + DummyResourceState::Ptr state = DummyResourceState::Ptr( new DummyResourceState ); + state->setItem( item ); + RetrieveItemTask *task = new RetrieveItemTask( state ); + task->start( &pool ); + + QTRY_COMPARE( state->calls().count(), 1 ); + + QString command = QString::fromUtf8(state->calls().first().first); + if ( command == "cancelTask" && callName != "cancelTask" ) { + kDebug() << "Got a cancel:" << state->calls().first().second.toString(); + } + QCOMPARE( command, callName ); + + QVariant parameter = state->calls().first().second; + + if ( callName == "itemRetrieved" ) { + QCOMPARE( parameter.value().id(), item.id() ); + QCOMPARE( parameter.value().remoteId(), item.remoteId() ); + + QString payload = parameter.value().payload()->encodedContent(); + + QCOMPARE( payload, message ); + + } else if ( callName == "cancelTask" ) { + QVERIFY( !parameter.toString().isEmpty() ); + } else { + QFAIL( QString( "Unexpected call type: %1" ).arg( callName ).toUtf8().constData() ); + } + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } +}; + +QTEST_KDEMAIN_CORE( TestRetrieveItemTask ) + +#include "testretrieveitemtask.moc" diff --git a/kdepim-runtime/resources/imap/tests/testsessionpool.cpp b/kdepim-runtime/resources/imap/tests/testsessionpool.cpp new file mode 100644 index 00000000..d4201e0d --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testsessionpool.cpp @@ -0,0 +1,794 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imaptestbase.h" + +#include + +class TestSessionPool : public ImapTestBase +{ + Q_OBJECT + +private slots: + void shouldPrepareFirstSessionOnConnect_data() + { + QTest::addColumn( "account" ); + QTest::addColumn( "requester" ); + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "password" ); + QTest::addColumn( "errorCode" ); + QTest::addColumn( "capabilities" ); + + ImapAccount *account = 0; + DummyPasswordRequester *requester = 0; + QList scenario; + QString password; + QStringList capabilities; + + account = createDefaultAccount(); + requester = createDefaultRequester(); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 NAMESPACE UIDPLUS IDLE" + << "S: A000002 OK Completed" + << "C: A000003 NAMESPACE" + << "S: * NAMESPACE ( (\"INBOX/\" \"/\") ) ( (\"user/\" \"/\") ) ( (\"\" \"/\") )" + << "S: A000003 OK Completed"; + password = "foobar"; + int errorCode = SessionPool::NoError; + capabilities.clear(); + capabilities << "IMAP4" << "IMAP4REV1" << "NAMESPACE" << "UIDPLUS" << "IDLE"; + QTest::newRow( "normal case" ) << account << requester << scenario + << password << errorCode << capabilities; + + + account = createDefaultAccount(); + requester = createDefaultRequester(); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000002 OK Completed"; + password = "foobar"; + errorCode = SessionPool::NoError; + capabilities.clear(); + capabilities << "IMAP4" << "IMAP4REV1" << "UIDPLUS" << "IDLE"; + QTest::newRow( "no NAMESPACE support" ) << account << requester << scenario + << password << errorCode << capabilities; + + + account = createDefaultAccount(); + requester = createDefaultRequester(); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IDLE" + << "S: A000002 OK Completed" + << "C: A000003 LOGOUT"; + password = "foobar"; + errorCode = SessionPool::IncompatibleServerError; + capabilities.clear(); + QTest::newRow( "incompatible server" ) << account << requester << scenario + << password << errorCode << capabilities; + + + QList requests; + QList results; + + account = createDefaultAccount(); + requester = createDefaultRequester(); + requests.clear(); + results.clear(); + requests << DummyPasswordRequester::StandardRequest << DummyPasswordRequester::WrongPasswordRequest; + results << DummyPasswordRequester::PasswordRetrieved << DummyPasswordRequester::UserRejected; + requester->setScenario( requests, results ); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 NO Login failed" + << "C: A000002 LOGOUT"; + password = "foobar"; + errorCode = SessionPool::LoginFailError; + capabilities.clear(); + QTest::newRow( "login fail, user reject password entry" ) << account << requester << scenario + << password << errorCode << capabilities; + + account = createDefaultAccount(); + requester = createDefaultRequester(); + requests.clear(); + results.clear(); + requests << DummyPasswordRequester::StandardRequest << DummyPasswordRequester::WrongPasswordRequest; + results << DummyPasswordRequester::PasswordRetrieved << DummyPasswordRequester::PasswordRetrieved; + requester->setScenario( requests, results ); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 NO Login failed" + << "C: A000002 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000002 OK Login succeeded" + << "C: A000003 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000003 OK Completed"; + password = "foobar"; + errorCode = SessionPool::NoError; + capabilities.clear(); + capabilities << "IMAP4" << "IMAP4REV1" << "UIDPLUS" << "IDLE"; + QTest::newRow( "login fail, user provide new password" ) << account << requester << scenario + << password << errorCode << capabilities; + + account = createDefaultAccount(); + requester = createDefaultRequester(); + requests.clear(); + results.clear(); + requests << DummyPasswordRequester::StandardRequest << DummyPasswordRequester::WrongPasswordRequest; + results << DummyPasswordRequester::PasswordRetrieved << DummyPasswordRequester::EmptyPasswordEntered; + requester->setScenario( requests, results ); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 NO Login failed" + << "C: A000002 LOGOUT"; + password = "foobar"; + errorCode = SessionPool::LoginFailError; + capabilities.clear(); + QTest::newRow( "login fail, user provided empty password" ) << account << requester << scenario + << password << errorCode << capabilities; + + account = createDefaultAccount(); + requester = createDefaultRequester(); + requests.clear(); + results.clear(); + requests << DummyPasswordRequester::StandardRequest << DummyPasswordRequester::WrongPasswordRequest; + results << DummyPasswordRequester::PasswordRetrieved << DummyPasswordRequester::ReconnectNeeded; + requester->setScenario( requests, results ); + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 NO Login failed" + << "C: A000002 LOGOUT"; + password = "foobar"; + errorCode = SessionPool::ReconnectNeededError; + capabilities.clear(); + QTest::newRow( "login fail, user change the settings" ) << account << requester << scenario + << password << errorCode << capabilities; + } + + + void shouldPrepareFirstSessionOnConnect() + { + QFETCH( ImapAccount*, account ); + QFETCH( DummyPasswordRequester*, requester ); + QFETCH( QList, scenario ); + QFETCH( QString, password ); + QFETCH( int, errorCode ); + QFETCH( QStringList, capabilities ); + + QSignalSpy requesterSpy( requester, SIGNAL(done(int,QString)) ); + + FakeServer server; + server.setScenario( scenario ); + server.startAndWait(); + + SessionPool pool( 2 ); + + QVERIFY( !pool.isConnected() ); + + QSignalSpy poolSpy( &pool, SIGNAL(connectDone(int,QString)) ); + + pool.setPasswordRequester( requester ); + QVERIFY( pool.connect( account ) ); + + QTest::qWait( 200 ); + QVERIFY( requesterSpy.count()>0 ); + if ( requesterSpy.count() == 1 ) { + QCOMPARE( requesterSpy.at( 0 ).at( 0 ).toInt(), 0 ); + QCOMPARE( requesterSpy.at( 0 ).at( 1 ).toString(), password ); + } + + QCOMPARE( poolSpy.count(), 1 ); + QCOMPARE( poolSpy.at( 0 ).at( 0 ).toInt(), errorCode ); + if ( errorCode == SessionPool::NoError ) { + QVERIFY( pool.isConnected() ); + } else { + QVERIFY( !pool.isConnected() ); + } + + QCOMPARE( pool.serverCapabilities(), capabilities ); + QVERIFY( pool.serverNamespaces().isEmpty() ); + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldManageSeveralSessions() + { + FakeServer server; + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 NAMESPACE UIDPLUS IDLE" + << "S: A000002 OK Completed" + << "C: A000003 NAMESPACE" + << "S: * NAMESPACE ( (\"INBOX/\" \"/\") ) ( (\"user/\" \"/\") ) ( (\"\" \"/\") )" + << "S: A000003 OK Completed" + ); + + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + ); + + server.startAndWait(); + + + + ImapAccount *account = createDefaultAccount(); + DummyPasswordRequester *requester = createDefaultRequester(); + + QSignalSpy requesterSpy( requester, SIGNAL(done(int,QString)) ); + + SessionPool pool( 2 ); + pool.setPasswordRequester( requester ); + + QSignalSpy connectSpy( &pool, SIGNAL(connectDone(int,QString)) ); + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + + + // Before connect we can't get any session + qint64 requestId = pool.requestSession(); + QCOMPARE( requestId, qint64(-1) ); + + + // Initial connect should trigger only a password request and a connect + QVERIFY( pool.connect( account ) ); + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 1 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 0 ); + + + // Requesting a first session shouldn't create a new one, + // only sessionRequestDone is emitted right away + requestId = pool.requestSession(); + QCOMPARE( requestId, qint64( 1 ) ); + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 1 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 1 ); + + QCOMPARE( sessionSpy.at( 0 ).at( 0 ).toLongLong(), requestId ); + QVERIFY( sessionSpy.at( 0 ).at( 1 ).value() != 0 ); + QCOMPARE( sessionSpy.at( 0 ).at( 2 ).toInt(), 0 ); + QCOMPARE( sessionSpy.at( 0 ).at( 3 ).toString(), QString() ); + + + // Requesting an extra session should create a new one + // So for instance password will be requested + requestId = pool.requestSession(); + QCOMPARE( requestId, qint64( 2 ) ); + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 2 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 2 ); + + QCOMPARE( sessionSpy.at( 1 ).at( 0 ).toLongLong(), requestId ); + QVERIFY( sessionSpy.at( 1 ).at( 1 ).value() != 0 ); + // Should be different sessions... + QVERIFY( sessionSpy.at( 0 ).at( 1 ).value() != sessionSpy.at( 1 ).at( 1 ).value() ); + QCOMPARE( sessionSpy.at( 1 ).at( 2 ).toInt(), 0 ); + QCOMPARE( sessionSpy.at( 1 ).at( 3 ).toString(), QString() ); + + + // Requesting yet another session should fail as we reached the + // maximum pool size, and they're all reserved + requestId = pool.requestSession(); + QCOMPARE( requestId, qint64( 3 ) ); + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 2 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 3 ); + + QCOMPARE( sessionSpy.at( 2 ).at( 0 ).toLongLong(), requestId ); + QVERIFY( sessionSpy.at( 2 ).at( 1 ).value()==0 ); + QCOMPARE( sessionSpy.at( 2 ).at( 2 ).toInt(), (int)SessionPool::NoAvailableSessionError ); + QVERIFY( !sessionSpy.at( 2 ).at( 3 ).toString().isEmpty() ); + + + // OTOH, if we release one now, and then request another one + // it should succeed without even creating a new session + KIMAP::Session *session = sessionSpy.at( 0 ).at( 1 ).value(); + pool.releaseSession( session ); + requestId = pool.requestSession(); + QCOMPARE( requestId, qint64( 4 ) ); + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 2 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 4 ); + + QCOMPARE( sessionSpy.at( 3 ).at( 0 ).toLongLong(), requestId ); + // Only one session was available, so that should be the one we get gack + QVERIFY( sessionSpy.at( 3 ).at( 1 ).value() == session ); + QCOMPARE( sessionSpy.at( 3 ).at( 2 ).toInt(), 0 ); + QCOMPARE( sessionSpy.at( 3 ).at( 3 ).toString(), QString() ); + + + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldNotifyConnectionLost() + { + FakeServer server; + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000002 OK Completed" + << "C: A000003 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "X" + ); + + server.startAndWait(); + + ImapAccount *account = createDefaultAccount(); + DummyPasswordRequester *requester = createDefaultRequester(); + + SessionPool pool( 1 ); + pool.setPasswordRequester( requester ); + + QSignalSpy connectSpy( &pool, SIGNAL(connectDone(int,QString)) ); + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + QSignalSpy lostSpy( &pool, SIGNAL(connectionLost(KIMAP::Session*)) ); + + + // Initial connect should trigger only a password request and a connect + QVERIFY( pool.connect( account ) ); + QTest::qWait( 100 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 0 ); + + qint64 requestId = pool.requestSession(); + QTest::qWait( 100 ); + QCOMPARE( sessionSpy.count(), 1 ); + + QCOMPARE( sessionSpy.at( 0 ).at( 0 ).toLongLong(), requestId ); + KIMAP::Session *s = sessionSpy.at( 0 ).at( 1 ).value(); + + KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob( s ); + job->start(); + QTest::qWait( 100 ); + QCOMPARE( lostSpy.count(), 1 ); + QCOMPARE( lostSpy.at( 0 ).at( 0 ).value(), s ); + + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldNotifyOnDisconnect_data() + { + QTest::addColumn< QList >( "scenario" ); + QTest::addColumn( "termination" ); + + QList scenario; + + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000002 OK Completed" + << "C: A000003 LOGOUT"; + + QTest::newRow( "logout session" ) << scenario << (int)SessionPool::LogoutSession; + + scenario.clear(); + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000002 OK Completed"; + + QTest::newRow( "close session" ) << scenario << (int)SessionPool::CloseSession; + } + + void shouldNotifyOnDisconnect() + { + QFETCH( QList, scenario ); + QFETCH( int, termination ); + + FakeServer server; + server.addScenario( scenario ); + + server.startAndWait(); + + ImapAccount *account = createDefaultAccount(); + DummyPasswordRequester *requester = createDefaultRequester(); + + SessionPool pool( 1 ); + pool.setPasswordRequester( requester ); + + QSignalSpy disconnectSpy( &pool, SIGNAL(disconnectDone()) ); + + + // Initial connect should trigger only a password request and a connect + QVERIFY( pool.connect( account ) ); + QTest::qWait( 100 ); + + QCOMPARE( disconnectSpy.count(), 0 ); + pool.disconnect( (SessionPool::SessionTermination) termination ); + QTest::qWait( 100 ); + QCOMPARE( disconnectSpy.count(), 1 ); + + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldCleanupOnClosingDuringLogin_data() + { + QTest::addColumn< QList >( "scenario" ); + + { + QList scenario; + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\""; + + QTest::newRow( "during login" ) << scenario; + } + { + QList scenario; + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY"; + + QTest::newRow( "during capability" ) << scenario; + } + { + QList scenario; + scenario << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 NAMESPACE UIDPLUS IDLE" + << "S: A000002 OK Completed" + << "C: A000003 NAMESPACE"; + QTest::newRow( "during namespace" ) << scenario; + } + } + + void shouldCleanupOnClosingDuringLogin() + { + QFETCH( QList, scenario ); + + FakeServer server; + server.addScenario(scenario); + + server.startAndWait(); + + ImapAccount *account = createDefaultAccount(); + DummyPasswordRequester *requester = createDefaultRequester(); + + SessionPool pool( 1 ); + pool.setPasswordRequester( requester ); + + QSignalSpy connectSpy( &pool, SIGNAL(connectDone(int,QString)) ); + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + QSignalSpy lostSpy( &pool, SIGNAL(connectionLost(KIMAP::Session*)) ); + + // Initial connect should trigger only a password request and a connect + QVERIFY( pool.connect( account ) ); + QTest::qWait( 100 ); + QCOMPARE( connectSpy.count(), 0 ); // Login not done yet + QWeakPointer session = qFindChild( &pool ); + QVERIFY( session.data() ); + QCOMPARE( sessionSpy.count(), 0 ); + + pool.disconnect( SessionPool::CloseSession ); + + QTest::qWait( 100 ); + QCOMPARE( connectSpy.count(), 1 ); // We're informed that connect failed + QCOMPARE( connectSpy.at( 0 ).at( 0 ).toInt(), int(SessionPool::CancelledError) ); + QCOMPARE( lostSpy.count(), 0 ); // We're not supposed to know the session pointer, so no connectionLost emitted + + // Make the session->deleteLater work, it can't happen in qWait (nested event loop) + QCoreApplication::sendPostedEvents( 0, QEvent::DeferredDelete); + + QVERIFY( session.isNull() ); + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldHonorCancelRequest() + { + FakeServer server; + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "S: A000002 OK Completed" + << "C: A000003 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "X" + ); + + server.startAndWait(); + + ImapAccount *account = createDefaultAccount(); + DummyPasswordRequester *requester = createDefaultRequester(); + + SessionPool pool( 1 ); + pool.setPasswordRequester( requester ); + + QSignalSpy connectSpy( &pool, SIGNAL(connectDone(int,QString)) ); + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + QSignalSpy lostSpy( &pool, SIGNAL(connectionLost(KIMAP::Session*)) ); + + // Initial connect should trigger only a password request and a connect + QVERIFY( pool.connect( account ) ); + QTest::qWait( 100 ); + QCOMPARE( connectSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 0 ); + + qint64 requestId = pool.requestSession(); + + // Cancel the request + pool.cancelSessionRequest( requestId ); + + // The request should not be processed anymore + QTest::qWait( 100 ); + QCOMPARE( sessionSpy.count(), 0 ); + } + + void shouldBeDisconnectedOnAllSessionLost() + { + FakeServer server; + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 IDLE" + << "S: A000002 OK Completed" + << "C: A000003 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "X" + ); + + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "X" + ); + + server.startAndWait(); + + ImapAccount *account = createDefaultAccount(); + DummyPasswordRequester *requester = createDefaultRequester(); + + SessionPool pool( 2 ); + pool.setPasswordRequester( requester ); + + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + + // Initial connect should trigger only a password request and a connect + QVERIFY( pool.connect( account ) ); + QTest::qWait( 100 ); + + // We should be connected now + QVERIFY( pool.isConnected() ); + + // Ask for a session + pool.requestSession(); + QTest::qWait( 100 ); + QCOMPARE( sessionSpy.count(), 1 ); + QVERIFY( sessionSpy.at( 0 ).at( 1 ).value() != 0 ); + + // Still connected obviously + QVERIFY( pool.isConnected() ); + + // Ask for a second session + pool.requestSession(); + QTest::qWait( 100 ); + QCOMPARE( sessionSpy.count(), 2 ); + QVERIFY( sessionSpy.at( 1 ).at( 1 ).value() != 0 ); + + // Still connected of course + QVERIFY( pool.isConnected() ); + + KIMAP::Session *session1 = sessionSpy.at( 0 ).at( 1 ).value(); + KIMAP::Session *session2 = sessionSpy.at( 1 ).at( 1 ).value(); + + // Prepare for session disconnects + QSignalSpy lostSpy( &pool, SIGNAL(connectionLost(KIMAP::Session*)) ); + + // Make the first session drop + KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob( session1 ); + job->start(); + QTest::qWait( 100 ); + QCOMPARE( lostSpy.count(), 1 ); + QCOMPARE( lostSpy.at( 0 ).at( 0 ).value(), session1 ); + + // We're still connected (one session being alive) + QVERIFY( pool.isConnected() ); + + // Make the second session drop + job = new KIMAP::CapabilitiesJob( session2 ); + job->start(); + QTest::qWait( 100 ); + QCOMPARE( lostSpy.count(), 2 ); + QCOMPARE( lostSpy.at( 1 ).at( 0 ).value(), session2 ); + + // We're not connected anymore! All sessions dropped! + QVERIFY( !pool.isConnected() ); + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldHandleDisconnectDuringPasswordRequest() + { + ImapAccount *account = createDefaultAccount(); + + // This requester will delay the second reply by a second + DummyPasswordRequester *requester = createDefaultRequester(); + requester->setDelays( QList() << 20 << 1000 ); + QSignalSpy requesterSpy( requester, SIGNAL(done(int,QString)) ); + + FakeServer server; + + server.addScenario( QList() + << FakeServer::greeting() + << "C: A000001 LOGIN \"test@kdab.com\" \"foobar\"" + << "S: A000001 OK User Logged in" + << "C: A000002 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 IDLE" + << "S: A000002 OK Completed" + << "C: A000003 CAPABILITY" + << "S: * CAPABILITY IMAP4 IMAP4rev1 UIDPLUS IDLE" + << "X" + ); + + server.startAndWait(); + + // The connect should work nicely + SessionPool pool( 2 ); + + QVERIFY( !pool.isConnected() ); + + QSignalSpy poolSpy( &pool, SIGNAL(connectDone(int,QString)) ); + QSignalSpy sessionSpy( &pool, SIGNAL(sessionRequestDone(qint64,KIMAP::Session*,int,QString)) ); + QSignalSpy lostSpy( &pool, SIGNAL(connectionLost(KIMAP::Session*)) ); + + pool.setPasswordRequester( requester ); + QVERIFY( pool.connect( account ) ); + + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 1 ); + + QCOMPARE( poolSpy.count(), 1 ); + QCOMPARE( poolSpy.at( 0 ).at( 0 ).toInt(), (int)SessionPool::NoError ); + QVERIFY( pool.isConnected() ); + + // Ask for the session we just created + pool.requestSession(); + QTest::qWait( 100 ); + QCOMPARE( sessionSpy.count(), 1 ); + QVERIFY( sessionSpy.at( 0 ).at( 1 ).value() != 0 ); + + KIMAP::Session *session = sessionSpy.at( 0 ).at( 1 ).value(); + + // Ask for the second session, the password requested will never reply + // and we'll get a disconnect in parallel (by triggering the capability + // job on the first session + // Done this way to simulate a disconnect during a password request + + // Ask for the extra session, and make sure the call is placed by waiting + // just a bit (but not too long so that the requester didn't reply yet, + // we set the reply timeout to 1000 earlier in this test) + pool.requestSession(); + QTest::qWait( 100 ); + QCOMPARE( requesterSpy.count(), 1 ); // Requester didn't reply yet + QCOMPARE( sessionSpy.count(), 1 ); + + // Make the first (and only) session drop while we wait for the requester + // to reply + KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob( session ); + job->start(); + QTest::qWait( 100 ); + QCOMPARE( lostSpy.count(), 1 ); + QCOMPARE( lostSpy.at( 0 ).at( 0 ).value(), session ); + + // The requester didn't reply yet + QCOMPARE( requesterSpy.count(), 1 ); + QCOMPARE( sessionSpy.count(), 1 ); + + // Now wait the remaining time to get the session creation to fail + QTest::qWait( 1000 ); + QCOMPARE( requesterSpy.count(), 2 ); + QCOMPARE( sessionSpy.count(), 2 ); + QCOMPARE( sessionSpy.at( 1 ).at( 2 ).toInt(), (int)SessionPool::LoginFailError ); + + QVERIFY( server.isAllScenarioDone() ); + + server.quit(); + } + + void shouldNotifyFailureToConnect() + { + // This tests what happens when we can't connect to the server, e.g. due to being offline. + // In this test we just use 0.0.0.0 as an invalid server IP, instead. + ImapAccount *account = createDefaultAccount(); + account->setServer( "0.0.0.0" ); // so that the connexion fails + DummyPasswordRequester *requester = createDefaultRequester(); + QList requests; + QList results; + // I don't want to see "WrongPasswordRequest". A password popup is annoying when we're offline or the server is down. + requests << DummyPasswordRequester::StandardRequest; + results << DummyPasswordRequester::PasswordRetrieved; + requester->setScenario( requests, results ); + + QSignalSpy requesterSpy( requester, SIGNAL(done(int,QString)) ); + SessionPool pool( 2 ); + QSignalSpy connectDoneSpy( &pool, SIGNAL(connectDone(int,QString)) ); + QSignalSpy lostSpy( &pool, SIGNAL(connectionLost(KIMAP::Session*)) ); + QVERIFY( !pool.isConnected() ); + pool.setPasswordRequester( requester ); + pool.connect( account ); + QVERIFY( !pool.isConnected() ); + QTRY_COMPARE( requesterSpy.count(), requests.count() ); + QTRY_COMPARE( connectDoneSpy.count(), 1 ); + QCOMPARE( connectDoneSpy.at( 0 ).at( 0 ).toInt(), (int)SessionPool::CouldNotConnectError ); + QCOMPARE( lostSpy.count(), 0 ); // don't want this, it makes the resource reconnect immediately (and fail, and reconnect, and so on...) + } + +}; + +QTEST_KDEMAIN_CORE( TestSessionPool ) + +#include "testsessionpool.moc" diff --git a/kdepim-runtime/resources/imap/tests/testsubscriptiondialog.cpp b/kdepim-runtime/resources/imap/tests/testsubscriptiondialog.cpp new file mode 100644 index 00000000..9653b76f --- /dev/null +++ b/kdepim-runtime/resources/imap/tests/testsubscriptiondialog.cpp @@ -0,0 +1,59 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include + +#include "imapaccount.h" +#include "subscriptiondialog.h" + +int main( int argc, char **argv ) +{ + QApplication app( argc, argv ); + + if ( app.arguments().size() < 5 ) { + qWarning( "Not enough parameters, expecting: " ); + return 1; + } + + QString server = app.arguments().at( 1 ); + int port = app.arguments().at( 2 ).toInt(); + QString user = app.arguments().at( 3 ); + QString password = app.arguments().at( 4 ); + + qDebug() << "Querying:" << server << port << user << password; + qDebug(); + + ImapAccount account; + account.setServer( server ); + account.setPort( port ); + account.setUserName( user ); + + SubscriptionDialog *dialog = new SubscriptionDialog(); + dialog->connectAccount( account, password ); + + dialog->show(); + + int retcode = app.exec(); + + qDebug() << "Subscription changed?" << dialog->isSubscriptionChanged(); + + return retcode; +} diff --git a/kdepim-runtime/resources/imap/timestampattribute.cpp b/kdepim-runtime/resources/imap/timestampattribute.cpp new file mode 100644 index 00000000..e1ab6f65 --- /dev/null +++ b/kdepim-runtime/resources/imap/timestampattribute.cpp @@ -0,0 +1,61 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 "timestampattribute.h" + +#include + +#include + +TimestampAttribute::TimestampAttribute( uint timestamp ) + : mTimestamp( timestamp ) +{ +} + +void TimestampAttribute::setTimestamp( uint timestamp ) +{ + mTimestamp = timestamp; +} + +uint TimestampAttribute::timestamp() const +{ + return mTimestamp; +} + +QByteArray TimestampAttribute::type() const +{ + return "timestamp"; +} + +Akonadi::Attribute *TimestampAttribute::clone() const +{ + return new TimestampAttribute( mTimestamp ); +} + +QByteArray TimestampAttribute::serialized() const +{ + return QByteArray::number( mTimestamp ); +} + +void TimestampAttribute::deserialize( const QByteArray &data ) +{ + mTimestamp = data.toInt(); +} diff --git a/kdepim-runtime/resources/imap/timestampattribute.h b/kdepim-runtime/resources/imap/timestampattribute.h new file mode 100644 index 00000000..1675acbc --- /dev/null +++ b/kdepim-runtime/resources/imap/timestampattribute.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + 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 TIMESTAMPATTRIBUTE_H +#define TIMESTAMPATTRIBUTE_H + +#include + +class TimestampAttribute : public Akonadi::Attribute +{ +public: + explicit TimestampAttribute( uint timestamp = 0 ); + void setTimestamp( uint timestamp ); + uint timestamp() const; + virtual QByteArray type() const; + virtual Attribute *clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + +private: + uint mTimestamp; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/uidnextattribute.cpp b/kdepim-runtime/resources/imap/uidnextattribute.cpp new file mode 100644 index 00000000..24a3f2e4 --- /dev/null +++ b/kdepim-runtime/resources/imap/uidnextattribute.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 "uidnextattribute.h" + +#include + +#include + +UidNextAttribute::UidNextAttribute( int uidnext ) + : mUidNext( uidnext ) +{ +} + +void UidNextAttribute::setUidNext( int uidnext ) +{ + mUidNext = uidnext; +} + +int UidNextAttribute::uidNext() const +{ + return mUidNext; +} + +QByteArray UidNextAttribute::type() const +{ + return "uidnext"; +} + +Akonadi::Attribute* UidNextAttribute::clone() const +{ + return new UidNextAttribute( mUidNext ); +} + +QByteArray UidNextAttribute::serialized() const +{ + return QByteArray::number( mUidNext ); +} + +void UidNextAttribute::deserialize( const QByteArray &data ) +{ + mUidNext = data.toInt(); +} diff --git a/kdepim-runtime/resources/imap/uidnextattribute.h b/kdepim-runtime/resources/imap/uidnextattribute.h new file mode 100644 index 00000000..ba761277 --- /dev/null +++ b/kdepim-runtime/resources/imap/uidnextattribute.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 UIDNEXTATTRIBUTE_H +#define UIDNEXTATTRIBUTE_H + +#include + +class UidNextAttribute : public Akonadi::Attribute +{ +public: + explicit UidNextAttribute( int uidnext = 0 ); + void setUidNext( int uidnext ); + int uidNext() const; + virtual QByteArray type() const; + virtual Attribute* clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + +private: + int mUidNext; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/uidvalidityattribute.cpp b/kdepim-runtime/resources/imap/uidvalidityattribute.cpp new file mode 100644 index 00000000..04bb2179 --- /dev/null +++ b/kdepim-runtime/resources/imap/uidvalidityattribute.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 "uidvalidityattribute.h" + +#include + +#include + +UidValidityAttribute::UidValidityAttribute( int uidvalidity ) + : mUidValidity( uidvalidity ) +{ +} + +void UidValidityAttribute::setUidValidity( int uidvalidity ) +{ + mUidValidity = uidvalidity; +} + +int UidValidityAttribute::uidValidity() const +{ + return mUidValidity; +} + +QByteArray UidValidityAttribute::type() const +{ + return "uidvalidity"; +} + +Akonadi::Attribute* UidValidityAttribute::clone() const +{ + return new UidValidityAttribute( mUidValidity ); +} + +QByteArray UidValidityAttribute::serialized() const +{ + return QByteArray::number( mUidValidity ); +} + +void UidValidityAttribute::deserialize( const QByteArray &data ) +{ + mUidValidity = data.toInt(); +} diff --git a/kdepim-runtime/resources/imap/uidvalidityattribute.h b/kdepim-runtime/resources/imap/uidvalidityattribute.h new file mode 100644 index 00000000..d168a067 --- /dev/null +++ b/kdepim-runtime/resources/imap/uidvalidityattribute.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 UIDVALIDITYATTRIBUTE_H +#define UIDVALIDITYATTRIBUTE_H + +#include + +class UidValidityAttribute : public Akonadi::Attribute +{ +public: + explicit UidValidityAttribute( int uidvalidity = 0 ); + void setUidValidity( int uidvalidity ); + int uidValidity() const; + virtual QByteArray type() const; + virtual Attribute* clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + +private: + int mUidValidity; +}; + +#endif diff --git a/kdepim-runtime/resources/imap/wizard/CMakeLists.txt b/kdepim-runtime/resources/imap/wizard/CMakeLists.txt new file mode 100644 index 00000000..b72f1c77 --- /dev/null +++ b/kdepim-runtime/resources/imap/wizard/CMakeLists.txt @@ -0,0 +1,2 @@ + +install ( FILES imapwizard.desktop imapwizard.es imapwizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/imap ) diff --git a/kdepim-runtime/resources/imap/wizard/Messages.sh b/kdepim-runtime/resources/imap/wizard/Messages.sh new file mode 100755 index 00000000..adcf81ed --- /dev/null +++ b/kdepim-runtime/resources/imap/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_imap.pot +$XGETTEXT -kqsTr *.es -j -o $podir/accountwizard_imap.pot diff --git a/kdepim-runtime/resources/imap/wizard/imapwizard.desktop b/kdepim-runtime/resources/imap/wizard/imapwizard.desktop new file mode 100644 index 00000000..62ea3b2e --- /dev/null +++ b/kdepim-runtime/resources/imap/wizard/imapwizard.desktop @@ -0,0 +1,101 @@ +[Desktop Entry] +Name=Generic IMAP Email Server +Name[bs]=GeneriÄki IMAP Email Server +Name[ca]=Servidor de correu IMAP genèric +Name[ca@valencia]=Servidor de correu IMAP genèric +Name[cs]=Obecný e-mailový IMAP server +Name[da]=Generisk IMAP e-mail-server +Name[de]=Allgemeiner IMAP-E-Mail-Server +Name[el]=Γενικός εξυπηÏετητής ηλ.ταχυδÏομείου IMAP +Name[en_GB]=Generic IMAP Email Server +Name[es]=Servidor de correo IMAP genérico +Name[et]=Ãœldine IMAP e-posti server +Name[fi]=Yleinen IMAP-sähköpostipalvelin +Name[fr]=Serveur générique de courriers électroniques IMAP +Name[ga]=Freastalaí Ríomhphoist IMAP Ginearálta +Name[gl]=Servidor de correo IMAP xenérico +Name[hu]=Ãltalános IMAP e-mail kiszolgáló +Name[ia]=Servitor generic de e-posta IMAP +Name[it]=Server di posta IMAP generico +Name[ja]=汎用 IMAP メールサーム+Name[kk]=Жалпы IMAP Ñл.пошта Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ​អ៊ីមែល IMAP ទូទៅ +Name[ko]=ì¼ë°˜ IMAP ì´ë©”ì¼ ì„œë²„ +Name[lt]=Pagrindinis IMAP paÅ¡to serveris +Name[lv]=VispÄrÄ«gs IMAP e-pasta serveris +Name[nb]=Generisk IMAP E-post-tjener +Name[nds]=Allgemeen IMAP-Nettpostserver +Name[nl]=Generieke IMAP e-mailserver +Name[pa]=ਆਮ IMAP ਈਮੇਲ ਸਰਵਰ +Name[pl]=ZwykÅ‚y serwer poczty IMAP +Name[pt]=Servidor Genérico de E-Mail IMAP +Name[pt_BR]=Servidor de e-mails IMAP genérico +Name[ro]=Server de poÈ™tă IMAP generic +Name[ru]=Почтовый Ñервер IMAP +Name[sk]=VÅ¡eobecný IMAP Email Server +Name[sl]=SploÅ¡ni e-poÅ¡tni strežnik IMAP +Name[sr]=Генерички ИМÐП Ñервер е‑поште +Name[sr@ijekavian]=Генерички ИМÐП Ñервер е‑поште +Name[sr@ijekavianlatin]=GeneriÄki IMAP server e‑poÅ¡te +Name[sr@latin]=GeneriÄki IMAP server e‑poÅ¡te +Name[sv]=Generell IMAP e-postserver +Name[tr]=Genel IMAP E-posta Sunucu +Name[uk]=Типовий Ñервер пошти IMAP +Name[x-test]=xxGeneric IMAP Email Serverxx +Name[zh_CN]=普通 IMAP 电å­é‚®ä»¶æœåŠ¡å™¨ +Name[zh_TW]=一般 IMAP 信件伺æœå™¨ +Icon=network-server +Comment=Imap account +Comment[bg]=Сметка IMAP +Comment[bs]=Imap nalog +Comment[ca]=Compte Imap +Comment[ca@valencia]=Compte Imap +Comment[cs]=ÚÄet IMAP +Comment[da]=IMAP-konto +Comment[de]=IMAP-Zugang +Comment[el]=ΛογαÏιασμός imap +Comment[en_GB]=Imap account +Comment[es]=Cuenta Imap +Comment[et]=IMAP konto +Comment[fi]=IMAP-tili +Comment[fr]=Compte IMAP +Comment[ga]=Cuntas IMAP +Comment[gl]=Conta de IMAP +Comment[hu]=IMAP azonosító +Comment[ia]=Conto de IMAP +Comment[it]=Account IMAP +Comment[ja]=Imap アカウント +Comment[kk]=Imap тіркелгіÑÑ– +Comment[km]=គណនី Imap +Comment[ko]=IMAP 계정 +Comment[lt]=Imap paskyra +Comment[lv]=IMAP konts +Comment[nb]=Imap-konto +Comment[nds]=Imap-Konto +Comment[nl]=Imap-account +Comment[nn]=IMAP-konto +Comment[pa]=Imap ਅਕਾਊਂਟ +Comment[pl]=Konto Imap +Comment[pt]=Conta de IMAP +Comment[pt_BR]=Conta IMAP +Comment[ro]=Cont Imap +Comment[ru]=Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ imap +Comment[sk]=Imap úÄet +Comment[sl]=RaÄun IMAP +Comment[sr]=ИМÐП налог +Comment[sr@ijekavian]=ИМÐП налог +Comment[sr@ijekavianlatin]=IMAP nalog +Comment[sr@latin]=IMAP nalog +Comment[sv]=Imap-konto +Comment[tr]=Imap hesabı +Comment[uk]=Обліковий Ð·Ð°Ð¿Ð¸Ñ IMAP +Comment[x-test]=xxImap accountxx +Comment[zh_CN]=IMAP 账户 +Comment[zh_TW]=Imap 帳號 + +[Wizard] +Type=message/rfc822 +Script=imapwizard.es + +[Translate] +Filename=accountwizard_imap diff --git a/kdepim-runtime/resources/imap/wizard/imapwizard.es b/kdepim-runtime/resources/imap/wizard/imapwizard.es new file mode 100644 index 00000000..3e4dd58d --- /dev/null +++ b/kdepim-runtime/resources/imap/wizard/imapwizard.es @@ -0,0 +1,128 @@ +/* + Copyright (c) 2009 Montel Laurent + + 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. +*/ + +// add this function to trim user input of whitespace when needed +String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); }; + +var page = Dialog.addPage( "imapwizard.ui", qsTr("Personal Settings") ); + +// try to guess some defaults +var emailAddr = SetupManager.email(); +var pos = emailAddr.indexOf( "@" ); +if ( pos >= 0 && (pos + 1) < emailAddr.length ) { + var server = emailAddr.slice( pos + 1, emailAddr.length ); + page.widget().incommingAddress.text = server; + page.widget().outgoingAddress.text = server; + // We must not strip the server from the user identifier. + // Otherwise the 'user' will be kdabtest1 instead of kdabtest1@demo.kolab.org + // which fails, + //var user = emailAddr.slice( 0, pos ); + page.widget().userName.text = emailAddr; +} + +function validateInput() +{ + if ( page.widget().incommingAddress.text.trim() == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +var stage = 1; +var identity; + +function setup() +{ + if ( stage == 1 ) { + identity = SetupManager.createIdentity(); + identity.setEmail( SetupManager.email() ); + identity.setRealName( SetupManager.name() ); + + ServerTest.test( page.widget().incommingAddress.text.trim(), "imap" ); + } else { + ServerTest.test( page.widget().outgoingAddress.text.trim(), "smtp" ); + } +} + +function testResultFail() +{ + testOk( -1 ); +} + +function testOk( arg ) +{ + if (stage == 1) { + SetupManager.openWallet(); + var imapRes = SetupManager.createResource( "akonadi_imap_resource" ); + imapRes.setOption( "ImapServer", page.widget().incommingAddress.text.trim() ); + imapRes.setOption( "UserName", page.widget().userName.text ); + imapRes.setOption( "Password", SetupManager.password() ); + imapRes.setOption( "DisconnectedModeEnabled", page.widget().disconnectedMode.checked ); + imapRes.setOption( "UseDefaultIdentity", false ); + imapRes.setOption( "AccountIdentity", identity.uoid() ); + imapRes.setOption( "Authentication", 7 ); // ClearText + if ( arg == "ssl" ) { + // The ENUM used for authentication (in the imap resource only) + imapRes.setOption( "Safety", "SSL"); // SSL/TLS + imapRes.setOption( "ImapPort", 993 ); + } else if ( arg == "tls" ) { // tls is really STARTTLS + imapRes.setOption( "Safety", "STARTTLS"); // STARTTLS + imapRes.setOption( "ImapPort", 143 ); + } else if ( arg == "none" ) { + imapRes.setOption( "Safety", "NONE" ); // No encryption + imapRes.setOption( "ImapPort", 143 ); + } else { + // safe default fallback when servertest failed + imapRes.setOption( "Safety", "STARTTLS"); + imapRes.setOption( "ImapPort", 143 ); + } + imapRes.setOption( "IntervalCheckTime", 60 ); + imapRes.setOption( "SubscriptionEnabled", true ); + + stage = 2; + setup(); + } else { + var smtp = SetupManager.createTransport( "smtp" ); + smtp.setName( page.widget().outgoingAddress.text.trim() ); + smtp.setHost( page.widget().outgoingAddress.text.trim() ); + if ( arg == "ssl" ) { + smtp.setEncryption( "SSL" ); + } else if ( arg == "tls" ) { + smtp.setEncryption( "TLS" ); + } else { + smtp.setEncryption( "None" ); + } + smtp.setUsername( page.widget().userName.text ); + smtp.setPassword( SetupManager.password() ); + identity.setTransport( smtp ); + SetupManager.execute(); + } +} + +try { + ServerTest.testFail.connect( testResultFail ); + ServerTest.testResult.connect( testOk ); + page.widget().incommingAddress.textChanged.connect( validateInput ); + page.pageLeftNext.connect( setup ); +} catch ( e ) { + print( e ); +} + +validateInput(); diff --git a/kdepim-runtime/resources/imap/wizard/imapwizard.ui b/kdepim-runtime/resources/imap/wizard/imapwizard.ui new file mode 100644 index 00000000..d45d31b3 --- /dev/null +++ b/kdepim-runtime/resources/imap/wizard/imapwizard.ui @@ -0,0 +1,97 @@ + + + imapWizard + + + + 0 + 0 + 400 + 300 + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Username: + + + userName + + + + + + + + + + &Incoming server: + + + incommingAddress + + + + + + + + + + &Outgoing server: + + + outgoingAddress + + + + + + + + + + + + + + &Download all messages for offline use + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + userName + incommingAddress + outgoingAddress + disconnectedMode + + + +
diff --git a/kdepim-runtime/resources/kabc/CMakeLists.txt b/kdepim-runtime/resources/kabc/CMakeLists.txt new file mode 100644 index 00000000..4b91ba57 --- /dev/null +++ b/kdepim-runtime/resources/kabc/CMakeLists.txt @@ -0,0 +1,41 @@ +add_definitions( + -DKRESOURCES_DEPRECATED= + -DKABC_DEPRECATED= + -DKCAL_DEPRECATED= +) +# This one won't be needed when CMake 2.8.13 is depended on. +add_definitions( + -DKRESOURCES_DEPRECATED_EXPORT=KRESOURCES_EXPORT + -DKABC_DEPRECATED_EXPORT=KABC_EXPORT + -DKCAL_DEPRECATED_EXPORT=KCAL_EXPORT +) + +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( kabcresource_SRCS + kabcresource.cpp + kresourceassistant.cpp +) + +install( FILES kabcresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + + +kde4_add_executable(akonadi_kabc_resource ${kabcresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_kabc_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_kabc_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.KABC") + set_target_properties(akonadi_kabc_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi KABC Resource") +endif () + +target_link_libraries(akonadi_kabc_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KABC_LIBS} ${KDE4_KIO_LIBS}) + +install(TARGETS akonadi_kabc_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/kabc/Messages.sh b/kdepim-runtime/resources/kabc/Messages.sh new file mode 100644 index 00000000..2c68072d --- /dev/null +++ b/kdepim-runtime/resources/kabc/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$XGETTEXT kabcresource.cpp -o $podir/akonadi_kabc_resource.pot +$XGETTEXT kresourceassistant.cpp -o $podir/akonadi_kresourceassistant.pot diff --git a/kdepim-runtime/resources/kabc/kabcresource.cpp b/kdepim-runtime/resources/kabc/kabcresource.cpp new file mode 100644 index 00000000..3b724558 --- /dev/null +++ b/kdepim-runtime/resources/kabc/kabcresource.cpp @@ -0,0 +1,896 @@ +/* + Copyright (c) 2008 Kevin Krammer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "kabcresource.h" + +#include "kresourceassistant.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +typedef QMap UidToResourceMap; + +using namespace Akonadi; + +using KABC::Resource; + +class KABCResource::ErrorHandler : public KABC::ErrorHandler +{ + public: + explicit ErrorHandler( KABCResource* parent ) + : mParent( parent ) {} + + virtual void error( const QString &message ) { + mLastError = message; + emit mParent->error( message ); + } + + public: + KABCResource *mParent; + + QString mLastError; +}; + +// workaround to access protected method +class KABCResource::AddressBook : public KABC::AddressBook +{ + public: + AddressBook() {} + + KRES::Manager *getResourceManager() + { + return resourceManager(); + } +}; + +KABCResource::KABCResource( const QString &id ) + : ResourceBase( id ), + mAddressBook( new AddressBook() ), + mBaseResource( 0 ), + mFolderResource( 0 ), + mErrorHandler( new ErrorHandler( this ) ), + mFullItemRetrieve( false ), + mDelayedSaveTimer( new QTimer( this ) ), + mContactGroupMimeChecker( new MimeTypeChecker() ) +{ + KGlobal::locale()->insertCatalog( QLatin1String( "akonadi_kresourceassistant" ) ); + + mAddressBook->setErrorHandler( mErrorHandler ); + connect( this, SIGNAL(reloadConfiguration()), SLOT(reloadConfiguration()) ); + + connect( mAddressBook, SIGNAL(addressBookChanged(AddressBook*)), + this, SLOT(addressBookChanged()) ); + + connect( mDelayedSaveTimer, SIGNAL(timeout()), + this, SLOT(delayedSaveAddressBook()) ); + + changeRecorder()->itemFetchScope().fetchFullPayload(); + changeRecorder()->fetchCollection( true ); + + mDelayedSaveTimer->setSingleShot( true ); + + mContactGroupMimeChecker->addWantedMimeType( KABC::ContactGroup::mimeType() ); +} + +KABCResource::~KABCResource() +{ + delete mAddressBook; + delete mContactGroupMimeChecker; +} + +void KABCResource::configure( WId windowId ) +{ + KRES::Manager *manager = mAddressBook->getResourceManager(); + + if ( mBaseResource != 0 ) { + emit status( Running, + i18nc( "@info:status", "Changing address book plugin configuration" ) ); + QPointer dlg = new KRES::ConfigDialog( 0, QLatin1String( "contact" ), mBaseResource ); + if ( windowId ) { + KWindowSystem::setMainWindow( dlg, windowId ); + } + dlg->setWindowIcon( KIcon( "text-directory" ) ); + int stat = dlg->exec(); + delete dlg; + + if ( stat == QDialog::Accepted ) { + setName( mBaseResource->resourceName() ); + manager->writeConfig( KGlobal::config().data() ); + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + + emit status( Idle, QString() ); + // TODO: need to react on name changes, but do we need to react on data changes? + // as a workaround lets sync the collection tree + synchronizeCollectionTree(); + return; + } + + emit status( Running, + i18nc( "@info:status", "Acquiring address book plugin configuration" ) ); + QPointer kresAssistant = new KResourceAssistant( QLatin1String( "Contact" ) ); + KWindowSystem::setMainWindow( kresAssistant, windowId ); + + connect( kresAssistant, SIGNAL(error(QString)), + this, SIGNAL(error(QString)) ); + + int stat = kresAssistant->exec(); + if ( stat != QDialog::Accepted ) { + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + emit configurationDialogRejected(); + delete kresAssistant; + return; + } + + emit configurationDialogAccepted(); + KABC::Resource *resource = dynamic_cast( kresAssistant->resource() ); + Q_ASSERT( resource != 0 ); + + delete kresAssistant; + setResourcePointers( resource ); + + mBaseResource->setAddressBook( mAddressBook ); + + manager->writeConfig( KGlobal::config().data() ); + + mAddressBook->addResource( mBaseResource ); + + if ( !openConfiguration() ) { + const QString message = + i18nc( "@info:status", + "Initialization based on newly created configuration failed." ); + emit error( message ); + emit status( NotConfigured, message ); + return; + } + + if ( !mAddressBook->asyncLoad() ) { + const QString message = i18nc( "@info:status", "Loading of address book failed." ); + emit error( message ); + emit status( Broken, message ); + return; + } + + emit status( Running, i18nc( "@info:status", "Loading address book" ) ); +} + +void KABCResource::retrieveCollections() +{ + kDebug(); + if ( mBaseResource == 0 ) { + kError() << "No KABC resource"; + + const QString message = i18nc( "@info:status", "No KDE address book plugin configured yet" ); + emit error( message ); + + emit status( NotConfigured, message ); + return; + } + + Collection topLevelCollection; + topLevelCollection.setParentCollection( Collection::root() ); + topLevelCollection.setRemoteId( mBaseResource->identifier() ); + topLevelCollection.setName( mBaseResource->resourceName() ); + + EntityDisplayAttribute* attr = + topLevelCollection.attribute( Collection::AddIfMissing ); + attr->setDisplayName( mBaseResource->resourceName() ); + attr->setIconName( QLatin1String( "office-address-book" ) ); + + QStringList mimeTypes; + mimeTypes << KABC::Addressee::mimeType(); + + QStringList topLevelMimeTypes = mimeTypes; + topLevelMimeTypes << KABC::ContactGroup::mimeType(); + + if ( mFolderResource != 0 ) { + topLevelMimeTypes << Collection::mimeType(); + } + + Collection::Rights readOnlyRights; + + Collection::Rights readWriteRights; + readWriteRights |= Collection::CanCreateItem; + readWriteRights |= Collection::CanChangeItem; + readWriteRights |= Collection::CanDeleteItem; + topLevelCollection.setContentMimeTypes( topLevelMimeTypes ); + topLevelCollection.setRights( mBaseResource->readOnly() ? readOnlyRights : readWriteRights ); + + Collection::List list; + list << topLevelCollection; + + if ( mFolderResource != 0 ) { + const QStringList subResources = mFolderResource->subresources(); + kDebug() << "subResources" << subResources; + foreach ( const QString &subResource, subResources ) { + Collection childCollection; + childCollection.setParentCollection( topLevelCollection ); + childCollection.setRemoteId( subResource ); + childCollection.setName( mFolderResource->subresourceLabel( subResource ) ); + childCollection.setContentMimeTypes( mimeTypes ); + bool readOnly = !mFolderResource->subresourceWritable( subResource ); + childCollection.setRights( readOnly ? readOnlyRights : readWriteRights ); + + attr = childCollection.attribute( Collection::AddIfMissing ); + attr->setDisplayName( childCollection.name() ); + attr->setIconName( QLatin1String( "office-address-book" ) ); + + list << childCollection; + } + } + + collectionsRetrieved( list ); +} + +void KABCResource::retrieveItems( const Akonadi::Collection &col ) +{ + kDebug() << "full items:" << mFullItemRetrieve; + const UidToResourceMap uidToResourceMap = + mFolderResource != 0 ? mFolderResource->uidToResourceMap() : UidToResourceMap(); + + Item::List items; + + // check for each addressee whether there is a sub resource mapping. + // if there is a mapping, skip it if the mapping does not equal the collection's + // remoteId. + // if there is none, skip it if the collection is not the top level collection + KABC::AddressBook::const_iterator addrIt = mAddressBook->constBegin(); + KABC::AddressBook::const_iterator addrEndIt = mAddressBook->constEnd(); + for ( ; addrIt != addrEndIt; ++addrIt ) { + UidToResourceMap::const_iterator findIt = uidToResourceMap.find( addrIt->uid() ); + if ( findIt != uidToResourceMap.end() ) { + if ( findIt.value() != col.remoteId() ) + continue; + } else { + if ( col.parentCollection() != Collection::root() ) + continue; + } + + Item item; + item.setRemoteId( addrIt->uid() ); + item.setMimeType( KABC::Addressee::mimeType() ); + if ( mFullItemRetrieve ) item.setPayload( *addrIt ); + items.append( item ); + } + + QList distLists = mBaseResource->allDistributionLists(); + foreach ( const KABC::DistributionList *list, distLists ) { + Item item; + item.setRemoteId( list->identifier() ); + item.setMimeType( KABC::ContactGroup::mimeType() ); + + if ( mFullItemRetrieve ) { + item.setPayload( contactGroupFromDistList( list ) ); + } + + items.append( item ); + } + + mFullItemRetrieve = false; + + itemsRetrieved( items ); +} + +bool KABCResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType() << "part=" << parts; + Q_UNUSED( parts ); + const QString rid = item.remoteId(); + + if ( mContactGroupMimeChecker->isWantedItem( item ) ) { + KABC::DistributionList *list = + mAddressBook->findDistributionListByIdentifier( rid ); + if ( list == 0 ) { + kError() << "No distributionlist with identifier" << rid; + emit error( i18nc( "@info:status", + "Request for data of a specific distribution list failed " + "because there is no such list" ) ); + return false; + } + + Item i( item ); + i.setPayload( contactGroupFromDistList( list ) ); + itemRetrieved( i ); + } else { + KABC::Addressee addressee = mAddressBook->findByUid( item.remoteId() ); + if ( addressee.isEmpty() ) { + kError() << "No addressee with uid" << rid; + emit error( i18nc( "@info:status", + "Request for data of a specific address book entry failed " + "because there is no such entry" ) ); + return false; + } + + Item i( item ); + i.setPayload( addressee ); + itemRetrieved( i ); + } + return true; +} + +void KABCResource::aboutToQuit() +{ + saveAddressBook(); +} + +void KABCResource::doSetOnline( bool online ) +{ + kDebug() << "online" << online << "resource" << (void*) mBaseResource; + + if ( online ) { + reloadConfiguration(); + } else { + closeConfiguration(); + } + + ResourceBase::doSetOnline( online ); +} + +void KABCResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& col ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType(); + // KABC::Resource only has one collection and + // KABC::ResourceABC does not have API for setting the storage sub resource + Q_UNUSED( col ); + + if ( mBaseResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + + kDebug() << "item.hasPayload() " << item.hasPayload(); + kDebug() << "item.hasPayload() " << item.hasPayload(); + if ( item.hasPayload() ) { + KABC::Addressee addressee = item.payload(); + + if ( addressee.uid().isEmpty() ) + addressee.setUid( KRandom::randomString( 10 ) ); + + addressee.setResource( mBaseResource ); + mAddressBook->insertAddressee( addressee ); + + // TODO: proper error reporting + if ( scheduleSaveAddressBook() ) { + Item i( item ); + i.setRemoteId( addressee.uid() ); + i.setPayload( addressee ); + + changeCommitted( i ); + return; + } + } else if ( item.hasPayload() ) { + KABC::ContactGroup contactGroup = item.payload(); + + if ( contactGroup.id().isEmpty() ) + contactGroup.setId( KRandom::randomString( 10 ) ); + + // also inserts list into resource + distListFromContactGroup( contactGroup ); + + // TODO: proper error reporting + if ( scheduleSaveAddressBook() ) { + Item i( item ); + i.setRemoteId( contactGroup.id() ); + i.setPayload( contactGroup ); + + changeCommitted( i ); + return; + } + } + + changeProcessed(); +} + +void KABCResource::itemChanged( const Akonadi::Item &item, const QSet& parts ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType(); + // we store the whole addressee/contactgroup anyway + Q_UNUSED( parts ); + + if ( mBaseResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + + kDebug() << "item.hasPayload() " << item.hasPayload(); + kDebug() << "item.hasPayload() " << item.hasPayload(); + if ( item.hasPayload() ) { + KABC::Addressee addressee = item.payload(); + Q_ASSERT( !addressee.uid().isEmpty() ); + + addressee.setResource( mBaseResource ); + mAddressBook->insertAddressee( addressee ); + + // TODO: proper error reporting + if ( scheduleSaveAddressBook() ) { + changeCommitted( item ); + return; + } + } else if ( item.hasPayload() ) { + KABC::ContactGroup contactGroup = item.payload(); + Q_ASSERT( !contactGroup.id().isEmpty() ); + + KABC::DistributionList *list = + mAddressBook->findDistributionListByIdentifier( contactGroup.id() ); + if ( list == 0 ) { + // TODO: rather report an error? + + // also inserts list into resource + distListFromContactGroup( contactGroup ); + } else { + // TODO: might be better to update the already existing instance + mBaseResource->removeDistributionList( list ); + delete list; + + // also inserts list into resource + distListFromContactGroup( contactGroup ); + } + + // TODO: proper error reporting + if ( scheduleSaveAddressBook() ) { + changeCommitted( item ); + return; + } + } + + changeProcessed(); +} + +void KABCResource::itemRemoved( const Akonadi::Item &item ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType(); + + if ( mBaseResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + + if ( mContactGroupMimeChecker->isWantedItem( item ) ) { + KABC::DistributionList *list = + mAddressBook->findDistributionListByIdentifier( item.remoteId() ); + if ( list != 0 ) { + mAddressBook->removeDistributionList( list ); + delete list; + + // TODO: proper error reporting + if ( scheduleSaveAddressBook() ) { + changeCommitted( item ); + return; + } + } + } else { + KABC::Addressee addressee = mAddressBook->findByUid( item.remoteId() ); + if ( !addressee.isEmpty() ) { + mAddressBook->removeAddressee( addressee ); + // TODO: proper error reporting + if ( scheduleSaveAddressBook() ) { + changeCommitted( item ); + return; + } + } + } + changeProcessed(); +} + +void KABCResource::collectionChanged( const Akonadi::Collection &collection ) +{ + kDebug() << "collection.name=" << collection.name() + << ", resource name=" << mBaseResource->resourceName(); + + if ( mBaseResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + const QString newName = collection.displayName(); + + if ( newName != mBaseResource->resourceName() ) { + mBaseResource->setResourceName( newName ); + setName( newName ); + mAddressBook->getResourceManager()->writeConfig( KGlobal::config().data() ); + } + } else + kWarning() << "Got collection change for a sub resource which we cannot change"; + + changeCommitted( collection ); +} + +void KABCResource::setResourcePointers( KABC::Resource *resource ) +{ + mBaseResource = resource; + mFolderResource = dynamic_cast( resource ); + + if ( mBaseResource != 0 ) + mBaseResource->setAddressBook( mAddressBook ); +} + +bool KABCResource::openConfiguration() +{ + if ( mBaseResource != 0 ) { + if ( !mBaseResource->isOpen() ) { + if ( !mBaseResource->open() ) { + kError() << "Opening resource" << mBaseResource->identifier() << "failed"; + return false; + } + } + + connect( mBaseResource, SIGNAL(loadingError(Resource*,QString)), + this, SLOT(loadingError(Resource*,QString)) ); + + connect( mBaseResource, SIGNAL(loadingFinished(Resource*)), + this, SLOT(initialLoadingFinished(Resource*)) ); + + if ( mFolderResource != 0 ) { + connect( mFolderResource, + SIGNAL(signalSubresourceAdded(KABC::ResourceABC*,QString,QString)), + this, SLOT(subResourceAdded(KABC::ResourceABC*,QString,QString)) ); + + connect( mFolderResource, + SIGNAL(signalSubresourceRemoved(KABC::ResourceABC*,QString,QString)), + this, SLOT(subResourceRemoved(KABC::ResourceABC*,QString,QString)) ); + + connect( mFolderResource, + SIGNAL(signalSubresourceChanged(KABC::ResourceABC*,QString,QString)), + this, SLOT(subResourceChanged(KABC::ResourceABC*,QString,QString)) ); + } + + + // do not react on addressbook changes until we have finished its initial loading + mAddressBook->blockSignals( true ); + + setName( mBaseResource->resourceName() ); + } + + return true; +} + +void KABCResource::closeConfiguration() +{ + mDelayedSaveTimer->stop(); + + // do not react on addressbook changes until we have finished its initial loading + mAddressBook->blockSignals( true ); + + if ( mBaseResource != 0 ) { + disconnect( mBaseResource, SIGNAL(loadingError(Resource*,QString)), + this, SLOT(loadingError(Resource*,QString)) ); + + disconnect( mBaseResource, SIGNAL(loadingFinished(Resource*)), + this, SLOT(initialLoadingFinished(Resource*)) ); + + if ( mFolderResource != 0 ) { + disconnect( mFolderResource, + SIGNAL(signalSubresourceAdded(KABC::ResourceABC*,QString,QString)), + this, SLOT(subResourceAdded(KABC::ResourceABC*,QString,QString)) ); + + disconnect( mFolderResource, + SIGNAL(signalSubresourceRemoved(KABC::ResourceABC*,QString,QString)), + this, SLOT(subResourceRemoved(KABC::ResourceABC*,QString,QString)) ); + + disconnect( mFolderResource, + SIGNAL(signalSubresourceChanged(KABC::ResourceABC*,QString,QString)), + this, SLOT(subResourceChanged(KABC::ResourceABC*,QString,QString)) ); + } + + if ( mBaseResource->isOpen() ) + mBaseResource->close(); + } +} + +bool KABCResource::saveAddressBook() +{ + mDelayedSaveTimer->stop(); + + if ( !mBaseResource || mBaseResource->readOnly() ) + return false; + + mErrorHandler->mLastError.clear(); + + KABC::Ticket *ticket = mAddressBook->requestSaveTicket(); + if ( ticket == 0 ) { + kError() << "Could not get address book save ticket"; + emit error( i18nc( "@info:status", + "Request for saving the address book failed. " + "Probably locked by another application" ) ); + return false; + } + + if ( !mAddressBook->save( ticket ) ) { + kError() << "Saving failed: " << mErrorHandler->mLastError; + mAddressBook->releaseSaveTicket( ticket ); + return false; + } + + kDebug() << "Saving succeeded"; + return true; +} + +bool KABCResource::scheduleSaveAddressBook() +{ + if ( !mBaseResource || mBaseResource->readOnly() ) + return false; + + if ( !mDelayedSaveTimer->isActive() ) + mDelayedSaveTimer->start( 5000 ); + + return true; +} + +void KABCResource::loadingError( KABC::Resource *resource, const QString &message ) +{ + Q_UNUSED( resource ); + + kError() << "Loading error: " << message; + emit error( message ); + emit status( Broken, message ); +} + +void KABCResource::initialLoadingFinished( KABC::Resource *resource ) +{ + kDebug() << resource; + Q_ASSERT( mBaseResource != 0 ); + Q_ASSERT( resource == mBaseResource ); + + + disconnect( mBaseResource, SIGNAL(loadingFinished(Resource*)), + this, SLOT(initialLoadingFinished(Resource*)) ); + + emit status( Idle, QString() ); + + mAddressBook->blockSignals( false ); + + // Let Akonadi retrieve all items but only the base information, no payloads yet + mFullItemRetrieve = false; + synchronize(); +} + +void KABCResource::addressBookChanged() +{ + kDebug(); + if ( mDelayedSaveTimer->isActive() ) { + // TODO should record changes for delayed saving + kError() << "Delayed saving scheduled when resource changed. We might have lost changes"; + mDelayedSaveTimer->stop(); + } + // FIXME: there must be a better way to do this + mFullItemRetrieve = true; + synchronize(); +} + +void KABCResource::subResourceAdded( KABC::ResourceABC *resource, + const QString &type, const QString &subResource ) +{ + Q_UNUSED( resource ); + Q_UNUSED( type ); + kDebug() << "subResource" << subResource; + Q_ASSERT( resource == mFolderResource ); + Q_ASSERT( type.toLower() == QLatin1String( "contact" ) ); + Q_ASSERT( !subResource.isEmpty() ); + + // synchronizeCollectionTree just in case the resource does not make + // the address book emit addressBookChanged() + synchronizeCollectionTree(); +} + +void KABCResource::subResourceRemoved( KABC::ResourceABC *resource, + const QString &type, const QString &subResource ) +{ + Q_UNUSED( resource ); + Q_UNUSED( type ); + kDebug() << "subResource" << subResource; + Q_ASSERT( resource == mFolderResource ); + Q_ASSERT( type.toLower() == QLatin1String( "contact" ) ); + Q_ASSERT( !subResource.isEmpty() ); + + // synchronizeCollectionTree just in case the resource does not make + // the address book emit addressBookChanged() + synchronizeCollectionTree(); +} + +void KABCResource::subResourceChanged( KABC::ResourceABC *resource, + const QString &type, const QString &subResource ) +{ + Q_UNUSED( resource ); + Q_UNUSED( type ); + kDebug() << "subResource" << subResource; + Q_ASSERT( resource == mFolderResource ); + Q_ASSERT( type.toLower() == QLatin1String( "contact" ) ); + Q_ASSERT( !subResource.isEmpty() ); + + // synchronizeCollectionTree just in case the resource does not make + // the address book emit addressBookChanged() + synchronizeCollectionTree(); +} + +void KABCResource::reloadConfiguration() +{ + if ( mDelayedSaveTimer->isActive() ) { + if ( !saveAddressBook() ) { + kError() << "Saving of address book failed:" << mErrorHandler->mLastError; + } + } + closeConfiguration(); + + KGlobal::config()->reparseConfiguration(); + + if ( KGlobal::config()->groupList().isEmpty() ) { + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + + Q_ASSERT( KGlobal::config().data() != 0); + + KRES::Manager *manager = mAddressBook->getResourceManager(); + manager->readConfig( KGlobal::config().data() ); + + KRES::Resource *resource = manager->standardResource(); + if ( resource != 0 ) { + if ( resource->type().toLower() == QLatin1String( "akonadi" ) ) { + kError() << "Resource config points to an Akonadi bridge resource"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + } + + setResourcePointers( manager->standardResource() ); + if ( mBaseResource == 0 ) { + emit status( NotConfigured, i18nc( "@info:status", "No KDE address book plugin configured yet" ) ); + return; + } + + if ( !isOnline() ) + return; + + if ( !openConfiguration() ) { + kError() << "openConfiguration() failed"; + + const QString message = i18nc( "@info:status", "Initialization based on stored configuration failed." ); + emit error( message ); + emit status( Broken, message ); + return; + } + + if ( !mAddressBook->asyncLoad() ) { + kError() << "asyncLoad() failed"; + + const QString message = i18nc( "@info:status", "Loading of address book failed." ); + emit error( message ); + emit status( Broken, message ); + return; + } + + emit status( Running, i18nc( "@info:status", "Loading address book" ) ); +} + +void KABCResource::delayedSaveAddressBook() +{ + if ( !saveAddressBook() ) { + kError() << "Saving failed, rescheduling delayed save. Error was: " + << mErrorHandler->mLastError; + if ( !scheduleSaveAddressBook() ) { + kError() << "Scheduling failed as well, giving up"; + } + } +} + +KABC::DistributionList *KABCResource::distListFromContactGroup( const KABC::ContactGroup& contactGroup ) +{ + KABC::DistributionList *list = + new KABC::DistributionList( mBaseResource, contactGroup.id(), contactGroup.name() ); + + for ( unsigned int refIndex = 0; refIndex < contactGroup.contactReferenceCount(); ++refIndex ) { + const KABC::ContactGroup::ContactReference &reference = contactGroup.contactReference( refIndex ); + + KABC::Addressee addressee = mBaseResource->findByUid( reference.uid() ); + if ( addressee.isEmpty() ) { + addressee.setUid( reference.uid() ); + // TODO any way to set a good name? + } + + // TODO how to handle ContactGroup::ContactReference custom fields? + + list->insertEntry( addressee, reference.preferredEmail() ); + } + + for ( unsigned int dataIndex = 0; dataIndex < contactGroup.dataCount(); ++dataIndex ) { + const KABC::ContactGroup::Data &data = contactGroup.data( dataIndex ); + + KABC::Addressee addressee; + addressee.setName( data.name() ); + addressee.insertEmail( data.email() ); + + // TODO how to handle ContactGroup::Data custom fields? + + list->insertEntry( addressee ); + } + + return list; +} + +KABC::ContactGroup KABCResource::contactGroupFromDistList( const KABC::DistributionList* list ) const +{ + kDebug() << "name=" << list->name() << ", identifier=" << list->identifier() + << ", entries.count=" << list->entries().count(); + + KABC::ContactGroup contactGroup( list->name() ); + contactGroup.setId( list->identifier() ); + + const KABC::DistributionList::Entry::List entries = list->entries(); + foreach ( const KABC::DistributionList::Entry &entry, entries ) { + const KABC::Addressee addressee = entry.addressee(); + const QString email = entry.email(); + if ( addressee.isEmpty() ) { + if ( email.isEmpty() ) + continue; + + KABC::ContactGroup::Data data( email, email ); + contactGroup.append( data ); + } else { + const KABC::Addressee baseAddressee = mBaseResource->findByUid( addressee.uid() ); + if ( baseAddressee.isEmpty() ) { + KABC::ContactGroup::Data data( email, email ); + // TODO: transer custom fields? + contactGroup.append( data ); + } else { + KABC::ContactGroup::ContactReference reference( addressee.uid() ); + reference.setPreferredEmail( email ); + // TODO: transer custom fields? + contactGroup.append( reference ); + } + } + } + + return contactGroup; +} + +AKONADI_RESOURCE_MAIN( KABCResource ) + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/kabc/kabcresource.desktop b/kdepim-runtime/resources/kabc/kabcresource.desktop new file mode 100644 index 00000000..27ecc672 --- /dev/null +++ b/kdepim-runtime/resources/kabc/kabcresource.desktop @@ -0,0 +1,100 @@ +[Desktop Entry] +Name=KDE Address Book (traditional) +Name[ar]=دÙتر عناوين كدي (التقليدي) +Name[bs]=KDE adresad (tradicionalno) +Name[ca]=Llibreta d'adreces del KDE (tradicional) +Name[ca@valencia]=Llibreta d'adreces del KDE (tradicional) +Name[cs]=Kniha adres KDE (tradiÄní) +Name[da]=KDE's adressebog (traditionel) +Name[de]=KDE-Adressbuch (herkömmlich) +Name[el]=Βιβλίο διευθÏνσεων του KDE (παÏαδοσιακό) +Name[en_GB]=KDE Address Book (traditional) +Name[es]=Libreta de direcciones de KDE (tradicional) +Name[et]=KDE aadressiraamat (traditsiooniline) +Name[fi]=KDE:n osoitekirja (perinteinen) +Name[fr]=Carnet d'adresses KDE (traditionnel) +Name[ga]=Leabhar Seoltaí KDE (traidisiúnta) +Name[gl]=Caderno de Enderezos do KDE (tradicional) +Name[hu]=KDE címjegyzék (hagyományos) +Name[ia]=Adressario KDE (traditional) +Name[it]=Rubrica indirizzi di KDE (tradizionale) +Name[ja]=KDE アドレス帳 (従æ¥ã®ã‚¿ã‚¤ãƒ—) +Name[kk]=KDE адреÑтік кітапшаÑÑ‹ (дәÑтүрлі) +Name[km]=សៀវភៅ​អាសយដ្ឋាន KDE (បុរាណ) +Name[ko]=KDE ì£¼ì†Œë¡ (ì´ì „ 버전) +Name[lt]=KDE adresų knygelÄ— (tradicinÄ—) +Name[lv]=KDE adreÅ¡u grÄmata (tradicionÄlÄ) +Name[nb]=KDE-adressebok (tradisjonell) +Name[nds]=KDE-Adressbook (normaal) +Name[nl]=Adresboek van KDE (traditioneel) +Name[nn]=KDE-adressebok (tradisjonell) +Name[pa]=KDE à¨à¨¡à¨°à©ˆà©±à¨¸ ਬà©à©±à¨• (ਪà©à¨°à¨¾à¨£à©€) +Name[pl]=Książka adresowa KDE (tradycyjna) +Name[pt]=Livro de Endereços do KDE (tradicional) +Name[pt_BR]=Livro de endereços do KDE (tradicional) +Name[ro]=Carte de adrese KDE (tradiÈ›ională) +Name[ru]=ÐдреÑÐ½Ð°Ñ ÐºÐ½Ð¸Ð³Ð° KDE (традиционный иÑточник) +Name[sk]=KDE adresár (tradiÄný) +Name[sl]=KDE-jev imenik (tradicionalni) +Name[sr]=КДЕ‑ов адреÑар (традиционални) +Name[sr@ijekavian]=КДЕ‑ов адреÑар (традиционални) +Name[sr@ijekavianlatin]=KDE‑ov adresar (tradicionalni) +Name[sr@latin]=KDE‑ov adresar (tradicionalni) +Name[sv]=KDE:s adressbok (traditionell) +Name[tr]=KDE Adres Defteri (geleneksel) +Name[uk]=ÐдреÑна книга KDE (традиційна) +Name[x-test]=xxKDE Address Book (traditional)xx +Name[zh_CN]=KDE 地å€ç°¿(传统) +Name[zh_TW]=KDE 通訊錄(傳統) +Comment=Loads data from a traditional KDE address book resource +Comment[ar]=تحمل البيانات من موارد دÙتر عنوان كدي التقليدي +Comment[bs]=UÄitava podatke iz tradicionalog KDE izvora adresara +Comment[ca]=Carrega les dades des d'un recurs de llibreta d'adreces tradicional del KDE +Comment[ca@valencia]=Carrega dades des d'un recurs de llibreta d'adreces tradicional del KDE +Comment[cs]=NaÄítá data ze zdroje tradiÄní Knihy adres KDE +Comment[da]=Indlæser data fra en traditionel KDE adressebogsressource +Comment[de]=Lädt Daten aus einer herkömmlichen KDE-Adressbuch-Ressource +Comment[el]=ΦόÏτωση δεδομένων από έναν παÏαδοσιακό πόÏο βιβλίου διευθÏνσεων του KDE +Comment[en_GB]=Loads data from a traditional KDE address book resource +Comment[es]=Carga datos desde un recurso tradicional de libreta de direcciones de KDE +Comment[et]=Andmete laadimine traditsioonilisest KDE aadressiraamatu ressursist +Comment[fi]=Noutaa tietoa KDE:n perinteisestä osoitekirjaresurssista +Comment[fr]=Charge des données depuis un carnet traditionnel d'adresses KDE +Comment[ga]=Breiseán a luchtaíonn sonraí ó acmhainn thraidisiúnta leabhair seoltaí KDE +Comment[gl]=Carga datos desde un recurso de caderno de enderezos do KDE +Comment[hu]=Adatbetöltés egy hagyományos KDE címjegyzékbÅ‘l +Comment[ia]=Lege datos de un ressource de un adressario KDE traditional +Comment[it]=Carica dati da una tradizionale risorsa rubrica di KDE +Comment[ja]=従æ¥ã® KDE アドレス帳リソースã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=ДәÑтүрлі KDE адреÑтік кітапшаÑынан деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ធនធាន​សៀវភៅ​អាសយដ្ឋាន KDE បុរាណ +Comment[ko]=ì´ì „ 버전 KDE ì£¼ì†Œë¡ ìžì›ì—ì„œ ë°ì´í„°ë¥¼ 불러옵니다 +Comment[lt]=Ä®kels duomenis iÅ¡ tradicinÄ—s KDE adresų knygos resurso +Comment[lv]=IelÄdÄ“ datus no tradicionÄlÄ KDE adreÅ¡u grÄmatas resursa +Comment[nb]=Laster data fra en tradisjonell KDE adressebok-ressurs +Comment[nds]=Laadt Daten ut en normaal KDE-Adressbookressource +Comment[nl]=Laadt gegevens van een traditionele KDE adresboekbron +Comment[nn]=Lastar data frÃ¥ tradisjonelle KDE-adressebokressursar +Comment[pl]=Wczytuje dane z tradycyjnych zasobów książki adresowej KDE +Comment[pt]=Carrega os dados de um recurso de livro de endereços tradicional do KDE +Comment[pt_BR]=Carrega os dados do tradicional recurso do livro de endereços do KDE +Comment[ru]=Загрузка данных из проÑтой адреÑной книги KDE +Comment[sk]=NaÄíta dáta z prechodného KDE zdroja adresára +Comment[sl]=Naloži podatke iz tradicionalnega KDE-jevega imenika +Comment[sr]=Учитава податке из традиционалног КДЕ‑овог реÑурÑа адреÑара +Comment[sr@ijekavian]=Учитава податке из традиционалног КДЕ‑овог реÑурÑа адреÑара +Comment[sr@ijekavianlatin]=UÄitava podatke iz tradicionalnog KDE‑ovog resursa adresara +Comment[sr@latin]=UÄitava podatke iz tradicionalnog KDE‑ovog resursa adresara +Comment[sv]=Laddar data frÃ¥n en traditionell adressboksresurs i KDE +Comment[tr]=Geleneksel KDE adres defteri kaynağından veri yükler +Comment[uk]=Завантажує дані зі звичайного реÑурÑу адреÑної книги Ð´Ð»Ñ KDE +Comment[x-test]=xxLoads data from a traditional KDE address book resourcexx +Comment[zh_CN]=从传统 KDE 地å€ç°¿èµ„æºè½½å…¥æ•°æ® +Comment[zh_TW]=從傳統 KDE 通訊錄資æºè¼‰å…¥è³‡æ–™ +Type=AkonadiResource +Exec=akonadi_kabc_resource +Icon=text-directory + +X-Akonadi-MimeTypes=text/directory,application/x-vnd.kde.contactgroup +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_kabc_resource diff --git a/kdepim-runtime/resources/kabc/kabcresource.h b/kdepim-runtime/resources/kabc/kabcresource.h new file mode 100644 index 00000000..73c96eb7 --- /dev/null +++ b/kdepim-runtime/resources/kabc/kabcresource.h @@ -0,0 +1,117 @@ +/* + Copyright (c) 2008 Kevin Krammer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef KABCRESOURCE +#define KABCRESOURCE + +#include + +namespace KABC { + class ContactGroup; + class DistributionList; + class Resource; + class ResourceABC; +} + +namespace Akonadi { + class MimeTypeChecker; +} + +class QTimer; + +class KABCResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + explicit KABCResource( const QString &id ); + virtual ~KABCResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void aboutToQuit(); + + virtual void doSetOnline( bool online ); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + virtual void collectionChanged( const Akonadi::Collection &collection ); + + private: + class AddressBook; + AddressBook *mAddressBook; + KABC::Resource *mBaseResource; + KABC::ResourceABC *mFolderResource; + + class ErrorHandler; + ErrorHandler *mErrorHandler; + + bool mFullItemRetrieve; + + QTimer *mDelayedSaveTimer; + + Akonadi::MimeTypeChecker *mContactGroupMimeChecker; + + private: + void setResourcePointers( KABC::Resource *resource ); + + bool openConfiguration(); + + void closeConfiguration(); + + bool saveAddressBook(); + + bool scheduleSaveAddressBook(); + + KABC::DistributionList *distListFromContactGroup( const KABC::ContactGroup& contactGroup ); + + KABC::ContactGroup contactGroupFromDistList( const KABC::DistributionList* list ) const; + + typedef KABC::Resource Resource; + + private Q_SLOTS: + void loadingError( Resource *resource, const QString &message ); + void initialLoadingFinished( Resource *resource ); + + void addressBookChanged(); + + void subResourceAdded( KABC::ResourceABC *resource, + const QString &type, const QString &subResource ); + void subResourceRemoved( KABC::ResourceABC *resource, + const QString &type, const QString &subResource ); + + void subResourceChanged( KABC::ResourceABC *resource, + const QString &type, const QString &subResource ); + + void reloadConfiguration(); + + void delayedSaveAddressBook(); +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/kabc/kresourceassistant.cpp b/kdepim-runtime/resources/kabc/kresourceassistant.cpp new file mode 100644 index 00000000..6c74cb8e --- /dev/null +++ b/kdepim-runtime/resources/kabc/kresourceassistant.cpp @@ -0,0 +1,400 @@ +/* + Copyright (c) 2008 Kevin Krammer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "kresourceassistant.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + + +static bool isBlackListed( const QString &name ) +{ + static const QSet blackList = QSet() << "akonadi" << "file" << "dir" << "net" << "localdir"; + return blackList.contains( name ); +} + +class CompatibilityIntroductionLabel : public QWidget +{ + public: + explicit CompatibilityIntroductionLabel( QWidget *parent ) + : QWidget( parent ) + { + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setSpacing( KDialog::spacingHint() ); + mainLayout->setMargin( KDialog::marginHint() ); + + const QString introduction = + i18nc( "@info", + "Introduction" + "This assistant will guide you through the necessary " + "steps to use a traditional KDE resource plugin to populate " + "a folder of your Akonadi personal information setup with data " + "otherwise not yet accessible through native Akonadi " + "resources." + "The setup process consists of three steps:" + "" + "Step 1: Selecting a plugin suitable for the kind of data " + "source you want to add;" + "Step 2: Providing the selected plugin with information on " + "where to find and how to access the data;" + "Step 3: Naming the resulting data source so you can easily " + "identify it in any application presenting you a choice of " + "which data to process." ); + + QLabel *label = new QLabel( this ); + label->setWordWrap( true ); + label->setText( introduction ); + + mainLayout->addWidget( label ); + mainLayout->addStretch(); + } +}; + +class KResourceDescriptionLabel : public QWidget +{ + public: + KResourceDescriptionLabel( const QString &type, const QString &desc, QWidget *parent) + : QWidget( parent ), mType( type ) + { + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setSpacing( KDialog::spacingHint() ); + + QLabel *label = new QLabel( desc, this ); + label->setWordWrap( true ); + mainLayout->addWidget( label ); + + mainLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding ) ); + } + + public: + const QString mType; +}; + +class KResourceCreationWidget : public QWidget +{ + public: + KResourceCreationWidget( const QString &familyName, KRES::Factory *factory, QWidget *parent ) + : QWidget( parent ), mFamilyName( familyName ), + mFactory( factory ), mResource( 0 ), mPageWidget( 0 ) + { + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setSpacing( KDialog::spacingHint() ); + mainLayout->setMargin( KDialog::marginHint() ); + + mPageWidget = new KPageWidget( this ); + mPageWidget->setFaceType( KPageView::Tree ); + + mainLayout->addWidget( mPageWidget ); + + mTypes = mFactory->typeNames(); + + // Filter-out types for which we have a native akonadi resource + foreach( const QString &type, mTypes ) { + if ( isBlackListed( type ) ) + mTypes.removeAll( type ); + } + + foreach ( const QString &type, mTypes ) { + QString description = mFactory->typeDescription( type ); + if ( description.isEmpty() ) + description = i18nc( "@info", "No description available" ); + + QWidget *label = new KResourceDescriptionLabel( type, description, mPageWidget ); + mPageWidget->addPage( label, mFactory->typeName( type ) ); + } + } + + void createResource() + { + KResourceDescriptionLabel *label = + static_cast( mPageWidget->currentPage()->widget() ); + + if ( mResource != 0 && mResource->type() == label->mType ) + return; + + delete mResource; + mResource = mFactory->resource( label->mType ); + + mResource->setResourceName( mFamilyName ); + } + + public: + QString mFamilyName; + QStringList mTypes; + + KRES::Factory *mFactory; + KRES::Resource *mResource; + + KPageWidget *mPageWidget; +}; + +class KResourcePluginConfigWidget : public QGroupBox +{ + public: + KResourcePluginConfigWidget( const QString &type, KRES::Factory *factory, QWidget *parent) + : QGroupBox( parent ), mPluginWidget( 0 ) + { + setTitle( i18nc( "@title:group", "%1 Plugin Settings", factory->typeName( type ) ) ); + + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mPluginWidget = factory->configWidget( type, this ); + if ( mPluginWidget == 0 ) { + kError() << "No plugin configuration widget for resource type" << type; + QLabel *label = new QLabel( i18nc( "@info", "No plugin specific configuration available" ), this ); + label->setAlignment( Qt::AlignHCenter ); + mainLayout->addWidget( label ); + } else + mainLayout->addWidget( mPluginWidget ); + + mainLayout->addStretch(); + } + + public: + KRES::ConfigWidget *mPluginWidget; +}; + +class KResourceConfigWidget : public QWidget +{ + public: + KResourceConfigWidget( const QStringList &types, KRES::Factory *factory, QWidget *parent ) + : QWidget( parent ), mStackWidget( 0 ) + { + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setSpacing( KDialog::spacingHint() ); + mainLayout->setMargin( KDialog::marginHint() ); + + mStackWidget = new QStackedWidget( this ); + + mainLayout->addWidget( mStackWidget ); + + foreach ( const QString &type, types ) { + KResourcePluginConfigWidget *configWidget = + new KResourcePluginConfigWidget( type, factory, mStackWidget ); + mStackWidget->addWidget( configWidget ); + mStackedWidgets.insert( type, configWidget ); + + if ( configWidget->mPluginWidget != 0 ) { + connect( configWidget->mPluginWidget, SIGNAL(setReadOnly(bool)), + parent, SLOT(setReadOnly(bool)) ); + } + } + } + + void loadSettings( KRES::Resource *resource ) + { + KResourcePluginConfigWidget *widget = mStackedWidgets[ resource->type() ]; + Q_ASSERT( widget != 0 ); + + if ( widget->mPluginWidget != 0 ) + widget->mPluginWidget->loadSettings( resource ); + mStackWidget->setCurrentWidget( widget ); + } + + void saveSettings( KRES::Resource *resource ) + { + KResourcePluginConfigWidget *widget = mStackedWidgets[ resource->type() ]; + Q_ASSERT( widget != 0 ); + + if ( widget->mPluginWidget != 0 ) + widget->mPluginWidget->saveSettings( resource ); + } + + public: + QStackedWidget *mStackWidget; + QMap mStackedWidgets; +}; + +class KResourceFolderConfigWidget : public QWidget +{ + public: + KResourceFolderConfigWidget( const QString &familyName, QWidget *parent ) + : QWidget( parent ), mName( 0 ), mReadOnly( 0 ) + { + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setSpacing( KDialog::spacingHint() ); + + // TODO probably add Akonadi related options + const QString helpText = + i18nc( "@info", + "The settings on this page allow you to customize how the " + "data from the plugin will fit into your personal information " + "setup." ); + + QLabel *helpLabel = new QLabel( this ); + helpLabel->setWordWrap( true ); + helpLabel->setText( helpText ); + + mainLayout->addWidget( helpLabel ); + + QGroupBox *generalGroup = new QGroupBox( this ); + QGridLayout *generalLayout = new QGridLayout( generalGroup ); + generalLayout->setMargin( KDialog::marginHint() ); + generalLayout->setSpacing( KDialog::spacingHint() ); + + generalGroup->setTitle( i18nc( "@title:group general resource settings", + "%1 Folder Settings", + familyName ) ); + + QLabel *nameLabel = new QLabel( i18nc( "@label resource name", "Name:" ), + generalGroup ); + generalLayout->addWidget( nameLabel, 0, 0 ); + + mName = new KLineEdit( generalGroup ); + generalLayout->addWidget( mName, 0, 1 ); + + mReadOnly = new QCheckBox( i18nc( "@option:check if resource is read-only", + "Read-only" ), + generalGroup ); + generalLayout->addWidget( mReadOnly, 1, 0, 1, 2 ); + + mReadOnly->setChecked( false ); + + mainLayout->addWidget( generalGroup ); + mainLayout->addStretch(); + + connect( mName, SIGNAL(textChanged(QString)), + parent, SLOT(slotNameChanged(QString)) ); + } + + public: + KLineEdit *mName; + QCheckBox *mReadOnly; +}; + +class KResourceAssistant::Private +{ + public: + explicit Private( KResourceAssistant *parent ) + : mParent( parent ), mFactory( 0 ), mCreationWidget( 0 ), + mConfigWidget( 0 ), mFolderWidget( 0 ), + mLastPage( 0 ) + { + } + + public: + KResourceAssistant *mParent; + + KRES::Factory *mFactory; + + KResourceCreationWidget *mCreationWidget; + KResourceConfigWidget *mConfigWidget; + KResourceFolderConfigWidget *mFolderWidget; + + KPageWidgetItem *mLastPage; + + public: + void setReadOnly( bool readOnly ) + { + mFolderWidget->mReadOnly->setChecked( readOnly ); + } + + void slotNameChanged( const QString &text ); +}; + +KResourceAssistant::KResourceAssistant( const QString& resourceFamily, QWidget *parent ) + : KAssistantDialog( parent ), d( new Private( this ) ) +{ + // TODO they are most likely already defined somewhere + QMap familyNames; + familyNames[ QLatin1String( "contact" ) ] = + i18nc( "@title user visible resource type", "Address Book" ); + familyNames[ QLatin1String( "calendar" ) ] = + i18nc( "@title user visible resource type", "Calendar" ); + + const QString familyName = familyNames[ resourceFamily.toLower() ]; + + setModal( true ); + setCaption( i18nc( "@title:window", "KDE Compatibility Assistant" ) ); + setWindowIcon( KIcon( "text-directory" ) ); + + QWidget *introPage = new CompatibilityIntroductionLabel( this ); + addPage( introPage, QLatin1String( " " ) ); + + d->mFactory = KRES::Factory::self( resourceFamily.toLower() ); + + d->mCreationWidget = new KResourceCreationWidget( familyName, d->mFactory, this ); + addPage( d->mCreationWidget, i18nc( "@title assistant dialog step", + "Step 1: Select a KDE resource plugin" ) ); + + d->mConfigWidget = + new KResourceConfigWidget( d->mCreationWidget->mTypes, d->mFactory, this ); + addPage( d->mConfigWidget, i18nc( "@title assistant dialog step", + "Step 2: Configure the selected KDE resource plugin" ) ); + + d->mFolderWidget = new KResourceFolderConfigWidget( familyName, this ); + d->mLastPage = addPage( d->mFolderWidget, i18nc( "@title assistant dialog step", + "Step 3: Choose target folder properties" ) ); +} + +KResourceAssistant::~KResourceAssistant() +{ + delete d; +} + +KRES::Resource *KResourceAssistant::resource() +{ + return d->mCreationWidget->mResource; +} + +void KResourceAssistant::back() +{ + KPageWidgetItem *item = currentPage(); + if ( item->widget() == d->mConfigWidget ) { + d->mConfigWidget->saveSettings( d->mCreationWidget->mResource ); + } + + KAssistantDialog::back(); +} + +void KResourceAssistant::next() +{ + KPageWidgetItem *item = currentPage(); + if ( item->widget() == d->mCreationWidget ) { + d->mCreationWidget->createResource(); + + d->mConfigWidget->loadSettings( d->mCreationWidget->mResource ); + } else if ( item->widget() == d->mConfigWidget ) { + d->mConfigWidget->saveSettings( d->mCreationWidget->mResource ); + d->setReadOnly( d->mCreationWidget->mResource->readOnly() ); + const QString resourceName = d->mCreationWidget->mResource->resourceName(); + d->mFolderWidget->mName->setText( resourceName ); + } + + KAssistantDialog::next(); +} + +void KResourceAssistant::Private::slotNameChanged( const QString &text ) +{ + + mParent->setValid( mLastPage, !text.isEmpty() ); + mCreationWidget->mResource->setResourceName( text ); +} + +#include "moc_kresourceassistant.cpp" +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/kabc/kresourceassistant.h b/kdepim-runtime/resources/kabc/kresourceassistant.h new file mode 100644 index 00000000..c90823b1 --- /dev/null +++ b/kdepim-runtime/resources/kabc/kresourceassistant.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2008 Kevin Krammer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef KRESOURCEASSISTANT +#define KRESOURCEASSISTANT + +#include + +namespace KRES { + class Resource; +} + +class KResourceAssistant : public KAssistantDialog +{ + Q_OBJECT + + public: + explicit KResourceAssistant( const QString& resourceFamily, QWidget *parent = 0 ); + + virtual ~KResourceAssistant(); + + KRES::Resource *resource(); + + Q_SIGNALS: + void error( const QString& message ); + + public Q_SLOTS: + virtual void back(); + virtual void next(); + + private: + class Private; + Private *d; + + Q_PRIVATE_SLOT( d, void setReadOnly( bool ) ) + Q_PRIVATE_SLOT( d, void slotNameChanged( const QString& ) ) +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/kalarm/CMakeLists.txt b/kdepim-runtime/resources/kalarm/CMakeLists.txt new file mode 100644 index 00000000..62546444 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(kalarm) +add_subdirectory(kalarmdir) diff --git a/kdepim-runtime/resources/kalarm/Messages.sh b/kdepim-runtime/resources/kalarm/Messages.sh new file mode 100755 index 00000000..6d8398f4 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.kcfg -o -name \*.ui` >> rc.cpp || exit 11 +$XGETTEXT `find . \( ! -path "./kdepim-runtime/*" \) -a \( -name "*.cpp" -o -name "*.h" \)` -o $podir/akonadi_kalarm_resource.pot +rm -f rc.cpp diff --git a/kdepim-runtime/resources/kalarm/kalarm/CMakeLists.txt b/kdepim-runtime/resources/kalarm/kalarm/CMakeLists.txt new file mode 100644 index 00000000..e38a620e --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarm/CMakeLists.txt @@ -0,0 +1,49 @@ +include_directories( + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../shared + ${CMAKE_CURRENT_SOURCE_DIR}/../../ical/shared +) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +########### next target ############### +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5952) +add_definitions(-DSETTINGS_NAMESPACE=Akonadi_KAlarm_Resource) + +set(kalarmresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../../ical/shared/icalresourcebase.cpp + kalarmresource.cpp + ../shared/kalarmresourcecommon.cpp + ../shared/alarmtyperadiowidget.cpp +) + +install(FILES kalarmresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") + +kde4_add_ui_files(kalarmresource_SRCS ../shared/alarmtyperadiowidget.ui ${AKONADI_SINGLEFILERESOURCE_SHARED_UI}) +kde4_add_kcfg_files(kalarmresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/kalarmresource.kcfg org.kde.Akonadi.KAlarm.Settings) +qt4_add_dbus_adaptor(kalarmresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.KAlarm.Settings.xml settings.h Akonadi_KAlarm_Resource::Settings icalsettingsadaptor ICalSettingsAdaptor) +add_custom_target(kalarm_resource_xml ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.KAlarm.Settings.xml) + +kde4_add_plugin(akonadi_kalarm_resource ${kalarmresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_kalarm_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_kalarm_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.KAlarm") + set_target_properties(akonadi_kalarm_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi KAlarm Resource") +endif () + +target_link_libraries(akonadi_kalarm_resource + ${KDEPIMLIBS_KALARMCAL_LIBS} + ${KDEPIMLIBS_AKONADI_KCAL_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDE4_KIO_LIBS} + ${QT_QTDBUS_LIBRARY} + ) + +install(TARGETS akonadi_kalarm_resource DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.cpp b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.cpp new file mode 100644 index 00000000..463dcf22 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.cpp @@ -0,0 +1,545 @@ +/* + * kalarmresource.cpp - Akonadi resource for KAlarm + * Program: kalarm + * Copyright © 2009-2014 by David Jarvie + * + * 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 "kalarmresource.h" +#include "kalarmresourcecommon.h" +#include "alarmtyperadiowidget.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace Akonadi; +using namespace Akonadi_KAlarm_Resource; +using namespace KAlarmCal; +using KAlarmResourceCommon::errorMessage; + +KAlarmResource::KAlarmResource(const QString& id) + : ICalResourceBase(id), + mCompatibility(KACalendar::Incompatible), + mFileCompatibility(KACalendar::Incompatible), + mVersion(KACalendar::MixedFormat), + mFileVersion(KACalendar::IncompatibleFormat), + mHaveReadFile(false), + mFetchedAttributes(false) +{ + kDebug() << id; + KAlarmResourceCommon::initialise(this); + initialise(mSettings->alarmTypes(), QLatin1String("kalarm")); + connect(mSettings, SIGNAL(configChanged()), SLOT(settingsChanged())); + + // Start a job to fetch the collection attributes + fetchCollection(SLOT(collectionFetchResult(KJob*))); +} + +KAlarmResource::~KAlarmResource() +{ +} + +/****************************************************************************** +* Customize the configuration dialog before it is displayed. +*/ +void KAlarmResource::customizeConfigDialog(SingleFileResourceConfigDialog* dlg) +{ + ICalResourceBase::customizeConfigDialog(dlg); +#ifdef KDEPIM_MOBILE_UI + dlg->setFilter("*.ics"); +#endif + mTypeSelector = new AlarmTypeRadioWidget(dlg); + const QStringList types = mSettings->alarmTypes(); + CalEvent::Type alarmType = CalEvent::ACTIVE; + if (!types.isEmpty()) + alarmType = CalEvent::type(types[0]); + mTypeSelector->setAlarmType(alarmType); + dlg->appendWidget(mTypeSelector); + dlg->setMonitorEnabled(false); + QString title; + switch (alarmType) + { + case CalEvent::ACTIVE: + title = i18nc("@title:window", "Select Active Alarm Calendar"); + break; + case CalEvent::ARCHIVED: + title = i18nc("@title:window", "Select Archived Alarm Calendar"); + break; + case CalEvent::TEMPLATE: + title = i18nc("@title:window", "Select Alarm Template Calendar"); + break; + default: + return; + } + dlg->setCaption(title); +} + +/****************************************************************************** +* Save extra settings after the configuration dialog has been accepted. +*/ +void KAlarmResource::configDialogAcceptedActions(SingleFileResourceConfigDialog*) +{ + mSettings->setAlarmTypes(CalEvent::mimeTypes(mTypeSelector->alarmType())); + mSettings->writeConfig(); +} + +/****************************************************************************** +* Reimplemented to fetch collection attributes after creating the collection. +*/ +void KAlarmResource::retrieveCollections() +{ + kDebug(); + mSupportedMimetypes = mSettings->alarmTypes(); + ICalResourceBase::retrieveCollections(); + fetchCollection(SLOT(collectionFetchResult(KJob*))); +} + +/****************************************************************************** +* Called when the collection fetch job completes. +* Check the calendar file's compatibility status if pending. +*/ +void KAlarmResource::collectionFetchResult(KJob* j) +{ + if (j->error()) + { + // An error occurred. Note that if it's a new resource, it will complain + // about an invalid collection if the collection has not yet been created. + kDebug() << "Error: " << j->errorString(); + } + else + { + bool firstTime = !mFetchedAttributes; + mFetchedAttributes = true; + CollectionFetchJob* job = static_cast(j); + const Collection::List collections = job->collections(); + if (collections.isEmpty()) + kDebug() << "Error: resource's collection not found"; + else + { + // Check whether calendar file format needs to be updated + kDebug() << "Fetched collection"; + const Collection& c(collections[0]); + if (firstTime && mSettings->path().isEmpty()) + { + // Initialising a resource which seems to have no stored + // settings config file. Recreate the settings. + static Collection::Rights writableRights = Collection::CanChangeItem | Collection::CanCreateItem | Collection::CanDeleteItem; + kDebug() << "Recreating config for remote id:" << c.remoteId(); + mSettings->setPath(c.remoteId()); + mSettings->setDisplayName(c.name()); + mSettings->setAlarmTypes(c.contentMimeTypes()); + mSettings->setReadOnly((c.rights() & writableRights) != writableRights); + mSettings->writeConfig(); + synchronize(); // tell the server to use the new config + } + checkFileCompatibility(c, true); + } + } +} + +/****************************************************************************** +* Reimplemented to read data from the given file. +* This is called every time the resource starts up (see SingleFileResourceBase +* constructor). +* Find the calendar file's compatibility with the current KAlarm format. +* The file is always local; loading from the network is done automatically if +* needed. +*/ +bool KAlarmResource::readFromFile(const QString& fileName) +{ + kDebug() << fileName; +//TODO Notify user if error occurs on next line + if (!ICalResourceBase::readFromFile(fileName)) + return false; + if (calendar()->incidences().isEmpty()) + { + // It's a new file. Set up the KAlarm custom property. + KACalendar::setKAlarmVersion(calendar()); + } + mFileCompatibility = KAlarmResourceCommon::getCompatibility(fileStorage(), mFileVersion); + mHaveReadFile = true; + + if (mFetchedAttributes) + { + // The old calendar file version and compatibility have been read from + // the database. Check if the file format needs to be converted. + checkFileCompatibility(); + } + return true; +} + +/****************************************************************************** +* To be called when the collection attributes have been fetched, or if they +* have changed. +* Check if the recorded calendar version and compatibility are different from +* the actual backend file, and if necessary convert the calendar in memory to +* the current version. +* If 'createAttribute' is true, the CompatibilityAttribute will be created if +* it does not already exist. +*/ +void KAlarmResource::checkFileCompatibility(const Collection& collection, bool createAttribute) +{ + if (collection.isValid() + && collection.hasAttribute()) + { + // Update our note of the calendar version and compatibility + const CompatibilityAttribute* attr = collection.attribute(); + mCompatibility = attr->compatibility(); + mVersion = attr->version(); + createAttribute = false; + } + if (mHaveReadFile + && (createAttribute + || mFileCompatibility != mCompatibility || mFileVersion != mVersion)) + { + // The actual file's version and compatibility are different from + // those in the Akonadi database, so update the database attributes. + mCompatibility = mFileCompatibility; + mVersion = mFileVersion; + const Collection c(collection); + if (c.isValid()) + KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); + else + fetchCollection(SLOT(setCompatibility(KJob*))); + } +} + +/****************************************************************************** +* Called when a collection fetch job completes. +* Set the compatibility attribute for the collection. +*/ +void KAlarmResource::setCompatibility(KJob* j) +{ + CollectionFetchJob* job = static_cast(j); + if (j->error()) + kDebug() << "Error: " << j->errorString(); + else if (job->collections().isEmpty()) + kDebug() << "Error: resource's collection not found"; + else + KAlarmResourceCommon::setCollectionCompatibility(job->collections()[0], mCompatibility, mVersion); +} + +/****************************************************************************** +* Reimplemented to write data to the given file. +* The file is always local. +*/ +bool KAlarmResource::writeToFile(const QString& fileName) +{ + kDebug() << fileName; + if (calendar()->incidences().isEmpty()) + { + // It's an empty file. Set up the KAlarm custom property. + KACalendar::setKAlarmVersion(calendar()); + } + return ICalResourceBase::writeToFile(fileName); +} + +/****************************************************************************** +* Retrieve an event from the calendar, whose uid and Akonadi id are given by +* 'item' (item.remoteId() and item.id() respectively). +* Set the event into a new item's payload, and signal its retrieval by calling +* itemRetrieved(newitem). +*/ +bool KAlarmResource::doRetrieveItem(const Akonadi::Item& item, const QSet& parts) +{ + Q_UNUSED(parts); + const QString rid = item.remoteId(); + const KCalCore::Event::Ptr kcalEvent = calendar()->event(rid); + if (!kcalEvent) + { + kWarning() << "Event not found:" << rid; + emit error(errorMessage(KAlarmResourceCommon::UidNotFound, rid)); + return false; + } + + if (kcalEvent->alarms().isEmpty()) + { + kWarning() << "KCalCore::Event has no alarms:" << rid; + emit error(errorMessage(KAlarmResourceCommon::EventNoAlarms, rid)); + return false; + } + + KAEvent event(kcalEvent); + const QString mime = CalEvent::mimeType(event.category()); + if (mime.isEmpty()) + { + kWarning() << "KAEvent has no alarms:" << rid; + emit error(errorMessage(KAlarmResourceCommon::EventNoAlarms, rid)); + return false; + } + event.setCompatibility(mCompatibility); + const Item newItem = KAlarmResourceCommon::retrieveItem(item, event); + itemRetrieved(newItem); + return true; +} + +/****************************************************************************** +* Called when the resource settings have changed. +* Update the supported mime types if the AlarmTypes setting has changed. +* Update the storage format if UpdateStorageFormat setting = true. +*/ +void KAlarmResource::settingsChanged() +{ + kDebug(); + const QStringList mimeTypes = mSettings->alarmTypes(); + if (mimeTypes != mSupportedMimetypes) + mSupportedMimetypes = mimeTypes; + + if (mSettings->updateStorageFormat()) + { + // This is a flag to request that the backend calendar storage format should + // be updated to the current KAlarm format. + kDebug() << "Update storage format"; + fetchCollection(SLOT(updateFormat(KJob*))); + } +} + +/****************************************************************************** +* Called when a collection fetch job completes. +* Update the backend calendar storage format to the current KAlarm format. +*/ +void KAlarmResource::updateFormat(KJob* j) +{ + CollectionFetchJob* job = static_cast(j); + if (j->error()) + kDebug() << "Error: " << j->errorString(); + else if (job->collections().isEmpty()) + kDebug() << "Error: resource's collection not found"; + else + { + const Collection c(job->collections()[0]); + if (c.hasAttribute()) + { + const CompatibilityAttribute* attr = c.attribute(); + if (attr->compatibility() != mCompatibility) + kDebug()<<"Compatibility changed:"<"<compatibility(); + } + switch (mCompatibility) + { + case KACalendar::Current: + kWarning() << "Already current storage format"; + break; + case KACalendar::Incompatible: + default: + kWarning() << "Incompatible storage format: compat=" << mCompatibility; + break; + case KACalendar::Converted: + case KACalendar::Convertible: + { + if (mSettings->readOnly()) + { + kWarning() << "Cannot update storage format for a read-only resource"; + break; + } + // Update the backend storage format to the current KAlarm format + const QString filename = fileStorage()->fileName(); + kDebug() << "Updating storage for" << filename; + KACalendar::setKAlarmVersion(fileStorage()->calendar()); + if (!writeToFile(filename)) + { + kWarning() << "Error updating calendar storage format"; + break; + } + // Prevent a new file read being triggered by writeToFile(), which + // would replace the current Collection by a new one. + mCurrentHash = calculateHash(filename); + + mCompatibility = mFileCompatibility = KACalendar::Current; + mVersion = mFileVersion = KACalendar::CurrentFormat; + KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, 0); + break; + } + } + mSettings->setUpdateStorageFormat(false); + mSettings->writeConfig(); + } +} + +/****************************************************************************** +* Called when an item has been added to the collection. +* Store the event in the calendar, and set its Akonadi remote ID to the +* KAEvent's UID. +*/ +void KAlarmResource::itemAdded(const Akonadi::Item& item, const Akonadi::Collection&) +{ + if (!checkItemAddedChanged(item, CheckForAdded)) + return; + if (mCompatibility != KACalendar::Current) + { + kWarning() << "Calendar not in current format"; + cancelTask(errorMessage(KAlarmResourceCommon::NotCurrentFormat)); + return; + } + const KAEvent event = item.payload(); + KCalCore::Event::Ptr kcalEvent(new KCalCore::Event); + event.updateKCalEvent(kcalEvent, KAEvent::UID_SET); + if (!calendar()->addIncidence(kcalEvent)) + { + kError() << "Error adding event with id" << event.id() << ", item id" << item.id(); + cancelTask(errorMessage(KAlarmResourceCommon::CalendarAdd, event.id())); + return; + } + + Item it(item); + it.setRemoteId(kcalEvent->uid()); + scheduleWrite(); + changeCommitted(it); +} + +/****************************************************************************** +* Called when an item has been changed. +* Store the changed event in the calendar, and delete the original event. +*/ +void KAlarmResource::itemChanged(const Akonadi::Item& item, const QSet& parts) +{ + Q_UNUSED(parts) + if (!checkItemAddedChanged(item, CheckForChanged)) + return; + QString errorMsg; + if (mCompatibility != KACalendar::Current) + { + kWarning() << "Calendar not in current format"; + cancelTask(errorMessage(KAlarmResourceCommon::NotCurrentFormat)); + return; + } + const KAEvent event = KAlarmResourceCommon::checkItemChanged(item, errorMsg); + if (!event.isValid()) + { + if (errorMsg.isEmpty()) + changeProcessed(); + else + cancelTask(errorMsg); + return; + } + + KCalCore::Incidence::Ptr incidence = calendar()->incidence(item.remoteId()); + if (incidence) + { + if (incidence->isReadOnly()) + { + kWarning() << "Event is read only:" << event.id(); + cancelTask(errorMessage(KAlarmResourceCommon::EventReadOnly, event.id())); + return; + } + if (incidence->type() == KCalCore::Incidence::TypeEvent) + { + calendar()->deleteIncidence(incidence); // it's not an Event + incidence.clear(); + } + else + { + KCalCore::Event::Ptr ev(incidence.staticCast()); + event.updateKCalEvent(ev, KAEvent::UID_SET); + calendar()->setModified(true); + } + } + if (!incidence) + { + // not in the calendar yet, should not happen -> add it + KCalCore::Event::Ptr kcalEvent(new KCalCore::Event); + event.updateKCalEvent(kcalEvent, KAEvent::UID_SET); + calendar()->addIncidence(kcalEvent); + } + scheduleWrite(); + changeCommitted(item); +} + +/****************************************************************************** +* Called when a collection has been changed. +* Determine the calendar file's storage format. +*/ +void KAlarmResource::collectionChanged(const Akonadi::Collection& collection) +{ + ICalResourceBase::collectionChanged(collection); + + mFetchedAttributes = true; + // Check whether calendar file format needs to be updated + checkFileCompatibility(collection); +} + +/****************************************************************************** +* Retrieve all events from the calendar, and set each into a new item's +* payload. Items are identified by their remote IDs. The Akonadi ID is not +* used. +* Signal the retrieval of the items by calling itemsRetrieved(items), which +* updates Akonadi with any changes to the items. itemsRetrieved() compares +* the new and old items, matching them on the remoteId(). If the flags or +* payload have changed, or the Item has any new Attributes, the Akonadi +* storage is updated. +*/ +void KAlarmResource::doRetrieveItems(const Akonadi::Collection& collection) +{ + kDebug(); + + // Set the collection's compatibility status + KAlarmResourceCommon::setCollectionCompatibility(collection, mCompatibility, mVersion); + + // Retrieve events from the calendar + const KCalCore::Event::List events = calendar()->events(); + Item::List items; + foreach (const KCalCore::Event::Ptr& kcalEvent, events) + { + if (kcalEvent->alarms().isEmpty()) + { + kWarning() << "KCalCore::Event has no alarms:" << kcalEvent->uid(); + continue; // ignore events without alarms + } + + const KAEvent event(kcalEvent); + const QString mime = CalEvent::mimeType(event.category()); + if (mime.isEmpty()) + { + kWarning() << "KAEvent has no alarms:" << event.id(); + continue; // event has no usable alarms + } + + Item item(mime); + item.setRemoteId(kcalEvent->uid()); + item.setPayload(event); + items << item; + } + itemsRetrieved(items); +} + +/****************************************************************************** +* Execute a CollectionFetchJob to fetch details of this resource's collection. +*/ +CollectionFetchJob* KAlarmResource::fetchCollection(const char* slot) +{ + CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel); + job->fetchScope().setResource(identifier()); + connect(job, SIGNAL(result(KJob*)), slot); + return job; +} + +AKONADI_AGENT_FACTORY(KAlarmResource, akonadi_kalarm_resource) + + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.desktop b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.desktop new file mode 100644 index 00000000..ebdf86d3 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.desktop @@ -0,0 +1,97 @@ +[Desktop Entry] +Name=KAlarm Calendar File +Name[bs]=KAlarm Kalendar Datoteka +Name[ca]=Fitxer de calendari del KAlarm +Name[ca@valencia]=Fitxer de calendari del KAlarm +Name[cs]=Soubor kalendáře KAlarm +Name[da]=KAlarm-kalenderfil +Name[de]=KAlarm-Kalenderdatei +Name[el]=ΑÏχείο ημεÏολογίου KAlarm +Name[en_GB]=KAlarm Calendar File +Name[es]=Archivo de calendario de KAlarm +Name[et]=KAlarmi kalendrifail +Name[fi]=KAlarm-kalenteritiedosto +Name[fr]=Fichier d'agenda KAlarm +Name[ga]=Comhad Féilire KAlarm +Name[gl]=Ficheiro de calendario de KAlarm +Name[hu]=KAlarm naptárfájl +Name[ia]=File de calendario de KAlarm +Name[it]=Calendario di KAlarm +Name[ja]=KAlarm カレンダーファイル +Name[kk]=KAlarm күнтізбе файлы +Name[km]=ឯកសារ​ប្រážáž·áž‘ិន​របស់ KAlarm +Name[ko]=KAlarm 달력 íŒŒì¼ +Name[lt]=KAlarm kalendoriaus failas +Name[lv]=KAlarm kalendÄra fails +Name[nb]=KAlarm kalenderfil +Name[nds]=KAlarm-Kalennerdatei +Name[nl]=KAlarm-agendabestand +Name[nn]=KAlarm-kalenderfil +Name[pa]=ਕੇ-ਅਲਾਰਮ ਕੈਲੰਡਰ ਫਾਇਲ +Name[pl]=Plik kalendarza KAlarm +Name[pt]=Ficheiro de Calendário do KAlarm +Name[pt_BR]=Arquivo de calendário do KAlarm +Name[ru]=Файл ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ KAlarm +Name[sk]=Súbor kalendára KAlarm +Name[sl]=Koledarska datoteka KAlarma +Name[sr]=К‑алармов календарÑки фајл +Name[sr@ijekavian]=К‑алармов календарÑки фајл +Name[sr@ijekavianlatin]=K‑alarmov kalendarski fajl +Name[sr@latin]=K‑alarmov kalendarski fajl +Name[sv]=Kalarm-kalenderfil +Name[tr]=KAlarm Takvim Dosyası +Name[uk]=Файл ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ KAlarm +Name[x-test]=xxKAlarm Calendar Filexx +Name[zh_CN]=KAlarm 日历文件 +Name[zh_TW]=KAlarm 行事曆檔案 +Comment=Loads data from a KAlarm calendar file +Comment[bs]=UÄitava podatke iz KAlarm kalendar datoteke +Comment[ca]=Carrega les dades des d'un fitxer de calendari del KAlarm +Comment[ca@valencia]=Carrega dades des d'un fitxer de calendari del KAlarm +Comment[cs]=NaÄítá data ze souboru kalendáře KAlarm +Comment[da]=Indlæser data fra KAlarm-kalenderfil +Comment[de]=Lädt Daten aus einer KAlarm-Kalenderdatei +Comment[el]=ΦόÏτωση δεδομένων από ένα αÏχείο ημεÏολογίου του KAlarm +Comment[en_GB]=Loads data from a KAlarm calendar file +Comment[es]=Carga datos de un archivo de calendario de KAlarm +Comment[et]=Andmete laadimine KAlarmi kalendrifailist +Comment[fi]=Noutaa tietoa KAlarmin kalenteritiedostosta +Comment[fr]=Charge les données depuis un fichier d'agenda KAlarm +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad féilire KAlarm +Comment[gl]=Carga datos desde un ficheiro de calendario do KAlarm. +Comment[hu]=Adatok betöltése egy KAlarm naptárfájlból +Comment[ia]=Carga datos ex un file de calendario de KAlarm +Comment[it]=Carica dati da un calendario di KAlarm +Comment[ja]=KAlarm ã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ãƒ•ã‚¡ã‚¤ãƒ«ã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=KAlarm-дың күнтізбе файлдан деректі алу +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ឯកសារ​ប្រážáž·áž‘ិន​របស់ KAlarm +Comment[ko]=KAlarm 달력 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ KAlarm kalendoriaus failo +Comment[lv]=IelÄdÄ“ datus no KAlarm kalendÄra faila +Comment[nb]=Laster data fra en KAlarm kalenderfil +Comment[nds]=Laadt Daten ut en KAlarm-Kalennerdatei +Comment[nl]=Laadt gegevens van een KAlarm-agendabestand +Comment[nn]=Lastar data frÃ¥ ei KAlarm-kalenderfil +Comment[pl]=Wczytuje dane z pliku kalendarza KAlarm +Comment[pt]=Carrega os dados de um ficheiro de calendário do KAlarm +Comment[pt_BR]=Carrega os dados de um arquivo de calendário do KAlarm +Comment[ru]=Загрузка данных из файла ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ KAlarm +Comment[sk]=NaÄíta dáta zo súboru kalendára KAlarm +Comment[sl]=Naloži podatke iz koledarske datoteke KAlarma +Comment[sr]=Учитава податке из К‑алармовог календарÑког фајла +Comment[sr@ijekavian]=Учитава податке из К‑алармовог календарÑког фајла +Comment[sr@ijekavianlatin]=UÄitava podatke iz K‑alarmovog kalendarskog fajla +Comment[sr@latin]=UÄitava podatke iz K‑alarmovog kalendarskog fajla +Comment[sv]=Laddar data frÃ¥n en Kalarm-kalenderfil +Comment[tr]=KAlarm takvim dosyasından veri yükler +Comment[uk]=Завантажує дані з файла ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ KAlarm +Comment[x-test]=xxLoads data from a KAlarm calendar filexx +Comment[zh_CN]=从 KAlarm æ—¥åŽ†æ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從 KAlarm 行事曆檔載入資料 +Type=AkonadiResource +Exec=akonadi_kalarm_resource +Icon=kalarm +X-Akonadi-MimeTypes=application/x-vnd.kde.alarm,application/x-vnd.kde.alarm.active,application/x-vnd.kde.alarm.archived,application/x-vnd.kde.alarm.template +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_kalarm_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.h b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.h new file mode 100644 index 00000000..7b2f139e --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.h @@ -0,0 +1,79 @@ +/* + * kalarmresource.h - Akonadi resource for KAlarm + * Program: kalarm + * Copyright © 2009-2014 by David Jarvie + * + * 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 KALARMRESOURCE_H +#define KALARMRESOURCE_H + +#include "icalresourcebase.h" + +#include + +using namespace KAlarmCal; + +class KJob; +namespace Akonadi { class CollectionFetchJob; } +class AlarmTypeRadioWidget; + +class KAlarmResource : public ICalResourceBase +{ + Q_OBJECT + public: + explicit KAlarmResource(const QString& id); + virtual ~KAlarmResource(); + + protected: + /** + * Customize the configuration dialog before it is displayed. + */ + virtual void customizeConfigDialog(Akonadi::SingleFileResourceConfigDialog*); + virtual void configDialogAcceptedActions(Akonadi::SingleFileResourceConfigDialog*); + + virtual void doRetrieveItems(const Akonadi::Collection&); + virtual bool doRetrieveItem(const Akonadi::Item&, const QSet& parts); + virtual bool readFromFile(const QString& fileName); + virtual bool writeToFile(const QString& fileName); + virtual void itemAdded(const Akonadi::Item&, const Akonadi::Collection&); + virtual void itemChanged(const Akonadi::Item&, const QSet& parts); + virtual void collectionChanged(const Akonadi::Collection&); + virtual void retrieveCollections(); + + private Q_SLOTS: + void settingsChanged(); + void collectionFetchResult(KJob*); + void updateFormat(KJob*); + void setCompatibility(KJob*); + + private: + void checkFileCompatibility(const Akonadi::Collection& = Akonadi::Collection(), bool createAttribute = false); + Akonadi::CollectionFetchJob* fetchCollection(const char* slot); + + AlarmTypeRadioWidget* mTypeSelector; + KACalendar::Compat mCompatibility; + KACalendar::Compat mFileCompatibility; // calendar file compatibility found by readFromFile() + int mVersion; // calendar format version + int mFileVersion; // calendar format version found by readFromFile() + bool mHaveReadFile; // the calendar file has been read + bool mFetchedAttributes; // attributes have been fetched after initialisation +}; + +#endif + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.kcfg b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.kcfg new file mode 100644 index 00000000..5e096fa7 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarm/kalarmresource.kcfg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + false + + + + true + + + + + + + false + + + diff --git a/kdepim-runtime/resources/kalarm/kalarm/settings.kcfgc b/kdepim-runtime/resources/kalarm/kalarm/settings.kcfgc new file mode 100644 index 00000000..65400bd4 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarm/settings.kcfgc @@ -0,0 +1,10 @@ +File=kalarmresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true +Namespace=Akonadi_KAlarm_Resource + diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/CMakeLists.txt b/kdepim-runtime/resources/kalarm/kalarmdir/CMakeLists.txt new file mode 100644 index 00000000..2a64575e --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/CMakeLists.txt @@ -0,0 +1,45 @@ +include_directories( + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../shared +) + +########### next target ############### +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5953) +add_definitions(-DSETTINGS_NAMESPACE=Akonadi_KAlarm_Dir_Resource) + +set(kalarmdirresource_SRCS + settingsdialog.cpp + kalarmdirresource.cpp + ../shared/kalarmresourcecommon.cpp + ../shared/alarmtypewidget.cpp +) + +install(FILES kalarmdirresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") + +kde4_add_ui_files(kalarmdirresource_SRCS settingsdialog.ui ../shared/alarmtypewidget.ui) +kde4_add_kcfg_files(kalarmdirresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/kalarmdirresource.kcfg org.kde.Akonadi.KAlarmDir.Settings) +qt4_add_dbus_adaptor(kalarmdirresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.KAlarmDir.Settings.xml settings.h Akonadi_KAlarm_Dir_Resource::Settings kalarmdirsettingsadaptor KAlarmDirSettingsAdaptor) +add_custom_target(kalarmdir_resource_xml ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.KAlarmDir.Settings.xml) + +kde4_add_plugin(akonadi_kalarm_dir_resource ${kalarmdirresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_kalarm_dir_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_kalarm_dir_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.KAlarmDir") + set_target_properties(akonadi_kalarm_dir_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi KAlarm Directory Resource") +endif () + +target_link_libraries(akonadi_kalarm_dir_resource + ${KDEPIMLIBS_KALARMCAL_LIBS} + ${KDEPIMLIBS_AKONADI_KCAL_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDE4_KIO_LIBS} + ${QT_QTDBUS_LIBRARY} + ) + +install(TARGETS akonadi_kalarm_dir_resource DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/autoqpointer.h b/kdepim-runtime/resources/kalarm/kalarmdir/autoqpointer.h new file mode 100644 index 00000000..fba65111 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/autoqpointer.h @@ -0,0 +1,46 @@ +/* + * autoqpointer.h - QPointer which on destruction deletes object + * Program: kalarm + * Copyright © 2009 by David Jarvie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef AUTOQPOINTER_H +#define AUTOQPOINTER_H + +#include + + +/** + * A QPointer which when destructed, deletes the object it points to. + * + * @author David Jarvie + */ +template +class AutoQPointer : public QPointer +{ + public: + AutoQPointer() : QPointer() {} + AutoQPointer(T* p) : QPointer(p) {} + AutoQPointer(const QPointer& p) : QPointer(p) {} + ~AutoQPointer() { delete this->data(); } + AutoQPointer& operator=(const AutoQPointer& p) { QPointer::operator=(p); return *this; } + AutoQPointer& operator=(T* p) { QPointer::operator=(p); return *this; } +}; + +#endif // AUTOQPOINTER_H + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.cpp b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.cpp new file mode 100644 index 00000000..9262bd21 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.cpp @@ -0,0 +1,1229 @@ +/* + * kalarmdirresource.cpp - Akonadi directory resource for KAlarm + * Program: kalarm + * Copyright © 2011-2014 by David Jarvie + * Copyright (c) 2008 Tobias Koenig + * Copyright (c) 2008 Bertjan Broeksema + * + * 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 "kalarmdirresource.h" +#include "kalarmresourcecommon.h" +#include "autoqpointer.h" + +#include "kalarmdirsettingsadaptor.h" +#include "settingsdialog.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Akonadi; +using namespace KCalCore; +using namespace Akonadi_KAlarm_Dir_Resource; +using KAlarmResourceCommon::errorMessage; + +static bool isFileValid(const QString& file); + +static const char warningFile[] = "WARNING_README.txt"; + +#define DEBUG_DATA \ +kDebug()<<"ID:Files:"; \ +foreach (const QString& id, mEvents.uniqueKeys()) { kDebug()<itemFetchScope().fetchFullPayload(); + changeRecorder()->fetchCollection(true); + + connect(KDirWatch::self(), SIGNAL(created(QString)), SLOT(fileCreated(QString))); + connect(KDirWatch::self(), SIGNAL(dirty(QString)), SLOT(fileChanged(QString))); + connect(KDirWatch::self(), SIGNAL(deleted(QString)), SLOT(fileDeleted(QString))); + + // Find the collection which this resource manages + CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel); + job->fetchScope().setResource(identifier()); + connect(job, SIGNAL(result(KJob*)), SLOT(collectionFetchResult(KJob*))); + + QTimer::singleShot(0, this, SLOT(loadFiles())); +} + +KAlarmDirResource::~KAlarmDirResource() +{ + delete mSettings; +} + +void KAlarmDirResource::aboutToQuit() +{ + mSettings->writeConfig(); +} + +/****************************************************************************** +* Called when the collection fetch job completes. +* Check the calendar files' compatibility statuses if pending. +*/ +void KAlarmDirResource::collectionFetchResult(KJob* j) +{ + kDebug(); + if (j->error()) + kError() << "CollectionFetchJob error: " << j->errorString(); + else + { + CollectionFetchJob* job = static_cast(j); + Collection::List collections = job->collections(); + int count = collections.count(); + kDebug() << "Count:" << count; + if (!count) + kError() << "Cannot retrieve this resource's collection"; + else + { + if (count > 1) + kError() << "Multiple collections for this resource:" << count; + Collection& c(collections[0]); + kDebug() << "Id:" << c.id() << ", remote id:" << c.remoteId(); + if (!mCollectionFetched) + { + bool recreate = mSettings->path().isEmpty(); + if (!recreate) + { + // Remote ID could be path or URL, depending on which version + // of Akonadi created it. + QString rid = c.remoteId(); + KUrl url(mSettings->path()); + if (!url.isLocalFile() + || (rid != url.toLocalFile() && rid != url.url() && rid != url.prettyUrl())) + { + kError() << "Collection remote ID does not match settings: changing settings"; + recreate = true; + } + } + if (recreate) + { + // Initialising a resource which seems to have no stored + // settings config file. Recreate the settings. + static Collection::Rights writableRights = Collection::CanChangeItem | Collection::CanCreateItem | Collection::CanDeleteItem; + kDebug() << "Recreating config for remote id:" << c.remoteId(); + mSettings->setPath(c.remoteId()); + mSettings->setDisplayName(c.name()); + mSettings->setAlarmTypes(c.contentMimeTypes()); + mSettings->setReadOnly((c.rights() & writableRights) != writableRights); + mSettings->writeConfig(); + } + mCollectionId = c.id(); + if (recreate) + { + // Load items from the backend files now that their location is known + loadFiles(true); + } + + // Set collection's format compatibility flag now that the collection + // and its attributes have been fetched. + KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); + } + } + } + mCollectionFetched = true; + if (mWaitingToRetrieve) + { + mWaitingToRetrieve = false; + retrieveCollections(); + } +} + +void KAlarmDirResource::configure(WId windowId) +{ + kDebug(); + // Keep note of the old configuration settings + QString path = mSettings->path(); + QString name = mSettings->displayName(); + bool readOnly = mSettings->readOnly(); + QStringList types = mSettings->alarmTypes(); + // Note: mSettings->monitorFiles() can't change here + + // Use AutoQPointer to guard against crash on application exit while + // the dialogue is still open. It prevents double deletion (both on + // deletion of parent, and on return from this function). + AutoQPointer dlg = new SettingsDialog(windowId, mSettings); + if (dlg->exec()) + { + if (path.isEmpty()) + { + // Creating a new resource + clearCache(); // this deletes any existing collection + loadFiles(true); + synchronizeCollectionTree(); + } + else if (mSettings->path() != path) + { + // Directory path change is not allowed for existing resources + emit configurationDialogRejected(); + return; + } + else + { + bool modify = false; + Collection c(mCollectionId); + if (mSettings->alarmTypes() != types) + { + // Settings have changed which might affect the alarm configuration + initializeDirectory(); // should only be needed for new resource, but just in case ... + CalEvent::Types newTypes = CalEvent::types(mSettings->alarmTypes()); + CalEvent::Types oldTypes = CalEvent::types(types); + changeAlarmTypes(~newTypes & oldTypes); + c.setContentMimeTypes(mSettings->alarmTypes()); + modify = true; + } + if (mSettings->readOnly() != readOnly + || mSettings->displayName() != name) + { + // Need to change the collection's rights or name + c.setRemoteId(directoryName()); + setNameRights(c); + modify = true; + } + if (modify) + { + // Update the Akonadi server with the changes + CollectionModifyJob* job = new CollectionModifyJob(c); + connect(job, SIGNAL(result(KJob*)), SLOT(jobDone(KJob*))); + } + } + emit configurationDialogAccepted(); + } + else + { + emit configurationDialogRejected(); + } +} + +/****************************************************************************** +* Add/remove events to ensure that they match the changed alarm types for the +* resource. +*/ +void KAlarmDirResource::changeAlarmTypes(CalEvent::Types removed) +{ +DEBUG_DATA; + const QString dirPath = directoryName(); + kDebug() << dirPath; + const QDir dir(dirPath); + + // Read and parse each file in turn + QDirIterator it(dir); + while (it.hasNext()) + { + it.next(); + int removeIfInvalid = 0; + QString fileEventId; + const QString file = it.fileName(); + if (!isFileValid(file)) + continue; + QHash::iterator fit = mFileEventIds.find(file); + if (fit != mFileEventIds.end()) + { + // The file is in the existing file list + fileEventId = fit.value(); + QHash::ConstIterator it = mEvents.constFind(fileEventId); + if (it != mEvents.constEnd()) + { + // And its event is in the existing events list + const EventFile& data = it.value(); + if (data.files[0] == file) + { + // It's the file for a used event + if (data.event.category() & removed) + { + // The event's type is no longer wanted, so remove it + deleteItem(data.event); + removeEvent(data.event.id(), false); + } + continue; + } + else + { + // The file's event is not currently used - load the + // file and use its event if appropriate. + removeIfInvalid = 0x03; // remove from mEvents and mFileEventIds + } + } + else + { + // The file's event isn't in the list of current valid + // events - this shouldn't ever happen + removeIfInvalid = 0x01; // remove from mFileEventIds + } + } + + // Load the file and use its event if appropriate. + const QString path = filePath(file); + if (QFileInfo(path).isFile()) + { + if (createItemAndIndex(path, file)) + continue; + } + // The event wasn't wanted, so remove from lists + if (removeIfInvalid & 0x01) + mFileEventIds.erase(fit); + if (removeIfInvalid & 0x02) + removeEventFile(fileEventId, file); + } +DEBUG_DATA; + setCompatibility(); +} + +/****************************************************************************** +* Called when the resource settings have changed. +* Update the display name if it has changed. +* Stop monitoring the directory if 'monitorFiles' is now false. +* Update the storage format if UpdateStorageFormat setting = true. +* NOTE: no provision is made for changes to the directory path, since this is +* not permitted (would need remote ID changed, plus other complications). +*/ +void KAlarmDirResource::settingsChanged() +{ + kDebug(); + const QString display = mSettings->displayName(); + if (display != name()) + setName(display); + + const QString dirPath = mSettings->path(); + if (!dirPath.isEmpty()) + { + const bool monitoring = KDirWatch::self()->contains(dirPath); + if (monitoring && !mSettings->monitorFiles()) + KDirWatch::self()->removeDir(dirPath); + else if (!monitoring && mSettings->monitorFiles()) + KDirWatch::self()->addDir(dirPath, KDirWatch::WatchFiles); +#if 0 + if (mSettings->monitorFiles() && !monitor) + { + // Settings have changed which might affect the alarm configuration +kDebug()<<"Monitored changed"; + loadFiles(true); +// synchronizeCollectionTree(); + } +#endif + } + + if (mSettings->updateStorageFormat()) + { + // This is a flag to request that the backend calendar storage format should + // be updated to the current KAlarm format. + KACalendar::Compat okCompat(KACalendar::Current | KACalendar::Convertible); + if (mCompatibility & ~okCompat) + kWarning() << "Either incompatible storage format or nothing to update"; + else if (mSettings->readOnly()) + kWarning() << "Cannot update storage format for a read-only resource"; + else + { + // Update the backend storage format to the current KAlarm format + bool ok = true; + for (QHash::iterator it = mEvents.begin(); it != mEvents.end(); ++it) + { + KAEvent& event = it.value().event; + if (event.compatibility() == KACalendar::Convertible) + { + if (writeToFile(event)) + event.setCompatibility(KACalendar::Current); + else + { + kWarning() << "Error updating storage format for event id" << event.id(); + ok = false; + } + } + } + if (ok) + { + mCompatibility = KACalendar::Current; + mVersion = KACalendar::CurrentFormat; + const Collection c(mCollectionId); + if (c.isValid()) + KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); + } + } + mSettings->setUpdateStorageFormat(false); + mSettings->writeConfig(); + } +} + +/****************************************************************************** +* Load and parse data from each file in the directory. +* The events are cached in mEvents. +*/ +bool KAlarmDirResource::loadFiles(bool sync) +{ + const QString dirPath = directoryName(); + if (dirPath.isEmpty()) + return false; + kDebug() << dirPath; + const QDir dir(dirPath); + + // Create the directory if it doesn't exist. + // This should only be needed for a new resource, but just in case ... + initializeDirectory(); + + mEvents.clear(); + mFileEventIds.clear(); + + // Set the resource display name to the configured name, else the directory + // name, if not already set. + QString display = mSettings->displayName(); + if (display.isEmpty() && (name().isEmpty() || name() == identifier())) + display = dir.dirName(); + if (!display.isEmpty()) + setName(display); + + // Read and parse each file in turn + QDirIterator it(dir); + while (it.hasNext()) + { + it.next(); + const QString file = it.fileName(); + if (isFileValid(file)) + { + const QString path = filePath(file); + if (QFileInfo(path).isFile()) + { + const KAEvent event = loadFile(path, file); + if (event.isValid()) + { + addEventFile(event, file); + mFileEventIds.insert(file, event.id()); + } + } + } + } +DEBUG_DATA; + + setCompatibility(false); // don't write compatibility - no collection exists yet + + if (mSettings->monitorFiles()) + { + // Monitor the directory for changes to the files + if (!KDirWatch::self()->contains(dirPath)) + KDirWatch::self()->addDir(dirPath, KDirWatch::WatchFiles); + } + + if (sync) + { + // Ensure the Akonadi server is updated with the current list of events + synchronize(); + } + + emit status(Idle); + return true; +} + +/****************************************************************************** +* Load and parse data a single file in the directory. +*/ +KAEvent KAlarmDirResource::loadFile(const QString& path, const QString& file) +{ + kDebug() << path; + MemoryCalendar::Ptr calendar(new MemoryCalendar(QLatin1String("UTC"))); + FileStorage::Ptr fileStorage(new FileStorage(calendar, path, new ICalFormat())); + if (!fileStorage->load()) + { + kWarning() << "Error loading" << path; + return KAEvent(); + } + const Event::List events = calendar->events(); + if ( events.isEmpty() ) + { + kDebug() << "Empty calendar in file" << path; + return KAEvent(); + } + if (events.count() > 1) + { + kWarning() << "Deleting" << events.count() - 1 << "excess events found in file" << path; + for (int i = 1; i < events.count(); ++i) + calendar->deleteEvent(events[i]); + } + const Event::Ptr kcalEvent(events[0]); + if (kcalEvent->uid() != file) + kWarning() << "File" << path << ": event id differs from file name"; + if (kcalEvent->alarms().isEmpty()) + { + kWarning() << "File" << path << ": event contains no alarms"; + return KAEvent(); + } + // Convert event in memory to current KAlarm format if possible + int version; + KACalendar::Compat compat = KAlarmResourceCommon::getCompatibility(fileStorage, version); + KAEvent event(kcalEvent); + const QString mime = CalEvent::mimeType(event.category()); + if (mime.isEmpty()) + { + kWarning() << "KAEvent has no usable alarms:" << event.id(); + return KAEvent(); + } + if (!mSettings->alarmTypes().contains(mime)) + { + kWarning() << "KAEvent has wrong alarm type for resource:" << mime; + return KAEvent(); + } + event.setCompatibility(compat); + return event; +} + +/****************************************************************************** +* After a file/event has been removed, load the next file in the list for the +* event ID. +* Reply = new event, or invalid if none. +*/ +KAEvent KAlarmDirResource::loadNextFile(const QString& eventId, const QString& file) +{ + QString nextFile = file; + while (!nextFile.isEmpty()) + { + // There is another file with the same ID - load it + const KAEvent event = loadFile(filePath(nextFile), nextFile); + if (event.isValid()) + { + addEventFile(event, nextFile); + mFileEventIds.insert(nextFile, event.id()); + return event; + } + mFileEventIds.remove(nextFile); + nextFile = removeEventFile(eventId, nextFile); + } + return KAEvent(); +} + +/****************************************************************************** +* Retrieve an event from the calendar, whose uid and Akonadi id are given by +* 'item' (item.remoteId() and item.id() respectively). +* Set the event into a new item's payload, and signal its retrieval by calling +* itemRetrieved(newitem). +*/ +bool KAlarmDirResource::retrieveItem(const Akonadi::Item& item, const QSet&) +{ + const QString rid = item.remoteId(); + QHash::ConstIterator it = mEvents.constFind(rid); + if (it == mEvents.constEnd()) + { + kWarning() << "Event not found:" << rid; + emit error(errorMessage(KAlarmResourceCommon::UidNotFound, rid)); + return false; + } + + KAEvent event(it.value().event); + const Item newItem = KAlarmResourceCommon::retrieveItem(item, event); + itemRetrieved(newItem); + return true; +} + +/****************************************************************************** +* Called when an item has been added to the collection. +* Store the event in a file, and set its Akonadi remote ID to the KAEvent's UID. +*/ +void KAlarmDirResource::itemAdded(const Akonadi::Item& item, const Akonadi::Collection&) +{ + kDebug() << item.id(); + if (cancelIfReadOnly()) + return; + + KAEvent event; + if (item.hasPayload()) + event = item.payload(); + if (!event.isValid()) + { + changeProcessed(); + return; + } + event.setCompatibility(KACalendar::Current); + setCompatibility(); + + if (!writeToFile(event)) + return; + + addEventFile(event, event.id()); + + Item newItem(item); + newItem.setRemoteId(event.id()); +// scheduleWrite(); //???? is this needed? + changeCommitted(newItem); +} + +/****************************************************************************** +* Called when an item has been changed. +* Store the changed event in a file. +*/ +void KAlarmDirResource::itemChanged(const Akonadi::Item& item, const QSet&) +{ + kDebug() << item.id() << ", remote ID:" << item.remoteId(); + if (cancelIfReadOnly()) + return; + QHash::iterator it = mEvents.find(item.remoteId()); + if (it != mEvents.end()) + { + if (it.value().event.isReadOnly()) + { + kWarning() << "Event is read only:" << item.remoteId(); + cancelTask(errorMessage(KAlarmResourceCommon::EventReadOnly, item.remoteId())); + return; + } + if (it.value().event.compatibility() != KACalendar::Current) + { + kWarning() << "Event not in current format:" << item.remoteId(); + cancelTask(errorMessage(KAlarmResourceCommon::EventNotCurrentFormat, item.remoteId())); + return; + } + } + + KAEvent event; + if (item.hasPayload()) + event = item.payload(); + if (!event.isValid()) + { + changeProcessed(); + return; + } +#if 0 + QString errorMsg; + KAEvent event = KAlarmResourceCommon::checkItemChanged(item, errorMsg); + if (!event.isValid()) + { + if (errorMsg.isEmpty()) + changeProcessed(); + else + cancelTask(errorMsg); + return; + } +#endif + event.setCompatibility(KACalendar::Current); + if (mCompatibility != KACalendar::Current) + setCompatibility(); + + if (!writeToFile(event)) + return; + + it.value().event = event; + + changeCommitted(item); +} + +/****************************************************************************** +* Called when an item has been deleted. +* Delete the item's file. +*/ +void KAlarmDirResource::itemRemoved(const Akonadi::Item& item) +{ + kDebug() << item.id(); + if (cancelIfReadOnly()) + return; + + QString nextFile; + removeEvent(item.remoteId(), true); + setCompatibility(); + changeProcessed(); +} + +/****************************************************************************** +* Remove an event from the indexes, and optionally delete its file. +*/ +void KAlarmDirResource::removeEvent(const QString& eventId, bool deleteFile) +{ + QString file = eventId; + QString nextFile; + QHash::iterator it = mEvents.find(eventId); + if (it != mEvents.end()) + { + file = it.value().files[0]; + nextFile = removeEventFile(eventId, file); + mFileEventIds.remove(file); +DEBUG_DATA; + } + if (deleteFile) + QFile::remove(filePath(file)); + + loadNextFile(eventId, nextFile); // load any other file with the same event ID +} + +/****************************************************************************** +* If the resource is read-only, cancel the task and emit an error. +* Reply = true if cancelled. +*/ +bool KAlarmDirResource::cancelIfReadOnly() +{ + if (mSettings->readOnly()) + { + kWarning() << "Calendar is read-only:" << directoryName(); + emit error(i18nc("@info", "Trying to write to a read-only calendar: '%1'", directoryName())); + cancelTask(); + return true; + } + return false; +} + +/****************************************************************************** +* Write an event to a file. The file name is the event's id. +*/ +bool KAlarmDirResource::writeToFile(const KAEvent& event) +{ + Event::Ptr kcalEvent(new Event); + event.updateKCalEvent(kcalEvent, KAEvent::UID_SET); + MemoryCalendar::Ptr calendar(new MemoryCalendar(QLatin1String("UTC"))); + KACalendar::setKAlarmVersion(calendar); // set the KAlarm custom property + if (!calendar->addIncidence(kcalEvent)) + { + kError() << "Error adding event with id" << event.id(); + emit error(errorMessage(KAlarmResourceCommon::CalendarAdd, event.id())); + cancelTask(); + return false; + } + + mChangedFiles += event.id(); // suppress KDirWatch processing for this write + + const QString path = filePath(event.id()); + kDebug() << event.id() << " File:" << path; + FileStorage::Ptr fileStorage(new FileStorage(calendar, path, new ICalFormat())); + if (!fileStorage->save()) + { + emit error(i18nc("@info", "Failed to save event file: %1", path)); + cancelTask(); + return false; + } + return true; +} + +/****************************************************************************** +* Create the resource's collection. +*/ +void KAlarmDirResource::retrieveCollections() +{ + QString rid = mSettings->path(); + if (!mCollectionFetched && rid.isEmpty()) + { + // The resource config seems to be missing. Execute this function + // once the collection config has been set up. + mWaitingToRetrieve = true; + return; + } + + kDebug(); + Collection c; + c.setParentCollection(Collection::root()); + c.setRemoteId(rid); + c.setContentMimeTypes(mSettings->alarmTypes()); + setNameRights(c); + + // Don't update CollectionAttribute here, since it hasn't yet been fetched + // from Akonadi database. + + Collection::List list; + list << c; + collectionsRetrieved(list); +} + +/****************************************************************************** +* Set the collection's name and rights. +* It is the caller's responsibility to notify the Akonadi server. +*/ +void KAlarmDirResource::setNameRights(Collection& c) +{ + kDebug(); + const QString display = mSettings->displayName(); + c.setName(display.isEmpty() ? name() : display); + EntityDisplayAttribute* attr = c.attribute(Collection::AddIfMissing); + attr->setDisplayName(name()); + attr->setIconName(QLatin1String("kalarm")); + if (mSettings->readOnly()) + { + c.setRights(Collection::CanChangeCollection); + } + else + { + Collection::Rights rights = Collection::ReadOnly; + rights |= Collection::CanChangeItem; + rights |= Collection::CanCreateItem; + rights |= Collection::CanDeleteItem; + rights |= Collection::CanChangeCollection; + c.setRights(rights); + } + kDebug()<<"end"; +} + +/****************************************************************************** +* Retrieve all events from the directory, and set each into a new item's +* payload. Items are identified by their remote IDs. The Akonadi ID is not +* used. +* Signal the retrieval of the items by calling itemsRetrieved(items), which +* updates Akonadi with any changes to the items. itemsRetrieved() compares +* the new and old items, matching them on the remoteId(). If the flags or +* payload have changed, or the Item has any new Attributes, the Akonadi +* storage is updated. +*/ +void KAlarmDirResource::retrieveItems(const Akonadi::Collection& collection) +{ + mCollectionId = collection.id(); // note the one and only collection for this resource + kDebug() << "Collection id:" << mCollectionId; + + // Set the collection's compatibility status + KAlarmResourceCommon::setCollectionCompatibility(collection, mCompatibility, mVersion); + + // Fetch the list of valid mime types + const QStringList mimeTypes = mSettings->alarmTypes(); + + // Retrieve events + Item::List items; + foreach (const EventFile& data, mEvents) + { + const KAEvent& event = data.event; + const QString mime = CalEvent::mimeType(event.category()); + if (mime.isEmpty()) + { + kWarning() << "KAEvent has no alarms:" << event.id(); + continue; // event has no usable alarms + } + if (!mimeTypes.contains(mime)) + continue; // restrict alarms returned to the defined types + + Item item(mime); + item.setRemoteId(event.id()); + item.setPayload(event); + items.append(item); + } + + itemsRetrieved(items); +} + +/****************************************************************************** +* Called when the collection has been changed. +* Set its display name if that has changed. +*/ +void KAlarmDirResource::collectionChanged(const Akonadi::Collection& collection) +{ + kDebug(); + // If the collection has a new display name, set the resource's display + // name the same, and save to the settings. + const QString newName = collection.displayName(); + if (!newName.isEmpty() && newName != name()) + setName(newName); + if (newName != mSettings->displayName()) + { + mSettings->setDisplayName(newName); + mSettings->writeConfig(); + } + + changeCommitted(collection); +} + +/****************************************************************************** +* Called when a file has been created in the directory. +*/ +void KAlarmDirResource::fileCreated(const QString& path) +{ + kDebug() << path; + if (path == directoryName()) + { + // The directory has been created. Load all files in it, and + // tell the Akonadi server to create an Item for each event. + loadFiles(true); + foreach (const EventFile& data, mEvents) + { + createItem(data.event); + } + } + else + { + const QString file = fileName(path); + int i = mChangedFiles.indexOf(file); + if (i >= 0) + mChangedFiles.removeAt(i); // the file was updated by this resource + else if (isFileValid(file)) + { + if (createItemAndIndex(path, file)) + setCompatibility(); +DEBUG_DATA; + } + } +} + + +/****************************************************************************** +* Called when a file has changed in the directory. +*/ +void KAlarmDirResource::fileChanged(const QString& path) +{ + if (path != directoryName()) + { + kDebug() << path; + const QString file = fileName(path); + int i = mChangedFiles.indexOf(file); + if (i >= 0) + mChangedFiles.removeAt(i); // the file was updated by this resource + else if (isFileValid(file)) + { + QString nextFile, oldId; + KAEvent oldEvent; + const KAEvent event = loadFile(path, file); + // Get the file's old event ID + QHash::iterator fit = mFileEventIds.find(file); + if (fit != mFileEventIds.end()) + { + oldId = fit.value(); + if (event.id() != oldId) + { + // The file's event ID has changed - remove the old event + nextFile = removeEventFile(oldId, file, &oldEvent); + if (event.isValid()) + fit.value() = event.id(); + else + mFileEventIds.erase(fit); + } + } + else if (event.isValid()) + { + // The file didn't contain an event before. Save details of the new event. + mFileEventIds.insert(file, event.id()); + } + addEventFile(event, file); + + KAEvent e = loadNextFile(oldId, nextFile); // load any other file with the same event ID + setCompatibility(); + + // Tell the Akonadi server to amend the Item for the event + if (event.id() != oldId) + { + if (e.isValid()) + modifyItem(e); + else + deleteItem(oldEvent); + createItem(event); // create a new Item for the new event ID + } + else + modifyItem(event); +DEBUG_DATA; + } + } +} + +/****************************************************************************** +* Called when a file has been deleted in the directory. +*/ +void KAlarmDirResource::fileDeleted(const QString& path) +{ + kDebug() << path; + if (path == directoryName()) + { + // The directory has been deleted + mEvents.clear(); + mFileEventIds.clear(); + + // Tell the Akonadi server to delete all Items in the collection + Collection c(mCollectionId); + ItemDeleteJob* job = new ItemDeleteJob(c); + connect(job, SIGNAL(result(KJob*)), SLOT(jobDone(KJob*))); + } + else + { + // A single file has been deleted + const QString file = fileName(path); + if (isFileValid(file)) + { + QHash::iterator fit = mFileEventIds.find(file); + if (fit != mFileEventIds.end()) + { + QString eventId = fit.value(); + KAEvent event; + QString nextFile = removeEventFile(eventId, file, &event); + mFileEventIds.erase(fit); + + KAEvent e = loadNextFile(eventId, nextFile); // load any other file with the same event ID + setCompatibility(); + + if (e.isValid()) + { + // Tell the Akonadi server to amend the Item for the event + modifyItem(e); + } + else + { + // Tell the Akonadi server to delete the Item for the event + deleteItem(event); + } +DEBUG_DATA; + } + } + } +} + +/****************************************************************************** +* Tell the Akonadi server to create an Item for a given file's event, and add +* it to the indexes. +*/ +bool KAlarmDirResource::createItemAndIndex(const QString& path, const QString& file) +{ + const KAEvent event = loadFile(path, file); + if (event.isValid()) + { + // Tell the Akonadi server to create an Item for the event + if (createItem(event)) + { + addEventFile(event, file); + mFileEventIds.insert(file, event.id()); + + return true; + } + } + return false; +} + +/****************************************************************************** +* Tell the Akonadi server to create an Item for a given event. +*/ +bool KAlarmDirResource::createItem(const KAEvent& event) +{ + Item item; + if (!event.setItemPayload(item, mSettings->alarmTypes())) + { + kWarning() << "Invalid mime type for collection"; + return false; + } + Collection c(mCollectionId); + item.setParentCollection(c); + item.setRemoteId(event.id()); + ItemCreateJob* job = new ItemCreateJob(item, c); + connect(job, SIGNAL(result(KJob*)), SLOT(jobDone(KJob*))); + return true; +} + +/****************************************************************************** +* Tell the Akonadi server to amend the Item for a given event. +*/ +bool KAlarmDirResource::modifyItem(const KAEvent& event) +{ + Item item; + if (!event.setItemPayload(item, mSettings->alarmTypes())) + { + kWarning() << "Invalid mime type for collection"; + return false; + } + Collection c(mCollectionId); + item.setParentCollection(c); + item.setRemoteId(event.id()); + ItemModifyJob* job = new ItemModifyJob(item); + job->disableRevisionCheck(); + connect(job, SIGNAL(result(KJob*)), SLOT(jobDone(KJob*))); + return true; +} + +/****************************************************************************** +* Tell the Akonadi server to delete the Item for a given event. +*/ +void KAlarmDirResource::deleteItem(const KAEvent& event) +{ + Item item(CalEvent::mimeType(event.category())); + Collection c(mCollectionId); + item.setParentCollection(c); + item.setRemoteId(event.id()); + ItemDeleteJob* job = new ItemDeleteJob(item); + connect(job, SIGNAL(result(KJob*)), SLOT(jobDone(KJob*))); +} + +/****************************************************************************** +* Called when a collection or item job has completed. +* Checks for any error. +*/ +void KAlarmDirResource::jobDone(KJob* j) +{ + if (j->error()) + kError() << j->metaObject()->className() << "error:" << j->errorString(); +} + +/****************************************************************************** +* Create the directory if it doesn't already exist, and ensure that it +* contains a WARNING_README.txt file. +*/ +void KAlarmDirResource::initializeDirectory() const +{ + kDebug(); + const QDir dir(directoryName()); + const QString dirPath = dir.absolutePath(); + + // If folder does not exist, create it + if (!dir.exists()) + { + kDebug() << "Creating" << dirPath; + QDir::root().mkpath(dirPath); + } + + // Check whether warning file is in place... + QFile file(dirPath + QDir::separator() + warningFile); + if (!file.exists()) + { + // ... if not, create it + file.open(QIODevice::WriteOnly); + file.write("Important Warning!!!\n" + "Do not create or copy items inside this folder manually:\n" + "they are managed by the Akonadi framework!\n"); + file.close(); + } +} + +QString KAlarmDirResource::directoryName() const +{ + return mSettings->path(); +} + +QString KAlarmDirResource::filePath(const QString& file) const +{ + return mSettings->path() + QDir::separator() + file; +} + +/****************************************************************************** +* Strip the directory path from a file name. +*/ +QString KAlarmDirResource::fileName(const QString& path) const +{ + const QFileInfo fi(path); + if (fi.isDir() || fi.isBundle()) + return QString(); + if (fi.path() == mSettings->path()) + return fi.fileName(); + return path; +} + +/****************************************************************************** +* Evaluate the version compatibility status of the calendar. This is the OR of +* the statuses of the individual events. +*/ +void KAlarmDirResource::setCompatibility(bool writeAttr) +{ + static const KACalendar::Compat AllCompat(KACalendar::Current | KACalendar::Convertible | KACalendar::Incompatible); + + const KACalendar::Compat oldCompatibility = mCompatibility; + const int oldVersion = mVersion; + if (mEvents.isEmpty()) + mCompatibility = KACalendar::Current; + else + { + mCompatibility = KACalendar::Unknown; + foreach (const EventFile& data, mEvents) + { + const KAEvent& event = data.event; + mCompatibility |= event.compatibility(); + if ((mCompatibility & AllCompat) == AllCompat) + break; + } + } + mVersion = (mCompatibility == KACalendar::Current) ? KACalendar::CurrentFormat : KACalendar::MixedFormat; + if (writeAttr && (mCompatibility != oldCompatibility || mVersion != oldVersion)) + { + const Collection c(mCollectionId); + if (c.isValid()) + KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); + } +} + +/****************************************************************************** +* Add an event/file combination to the mEvents map. +*/ +void KAlarmDirResource::addEventFile(const KAEvent& event, const QString& file) +{ + if (event.isValid()) + { + QHash::iterator it = mEvents.find(event.id()); + if (it != mEvents.end()) + { + EventFile& data = it.value(); + data.event = event; + data.files.removeAll(file); // in case it isn't the first file + data.files.prepend(file); + } + else + mEvents.insert(event.id(), EventFile(event, QStringList(file))); + } +} + +/****************************************************************************** +* Remove an event ID/file combination from the mEvents map. +* Reply = next file with the same event ID. +*/ +QString KAlarmDirResource::removeEventFile(const QString& eventId, const QString& file, KAEvent* event) +{ + QHash::iterator it = mEvents.find(eventId); + if (it != mEvents.end()) + { + if (event) + *event = it.value().event; + it.value().files.removeAll(file); + if (!it.value().files.isEmpty()) + return it.value().files[0]; + mEvents.erase(it); + } + else if (event) + *event = KAEvent(); + return QString(); +} + +/****************************************************************************** +* Check whether a file is to be ignored. +* Reply = false if file is to be ignored. +*/ +bool isFileValid(const QString& file) +{ + return !file.isEmpty() + && !file.startsWith(QLatin1Char('.')) && !file.endsWith(QLatin1Char('~')) + && file != QLatin1String(warningFile); +} + +AKONADI_AGENT_FACTORY(KAlarmDirResource, akonadi_kalarm_dir_resource) + + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.desktop b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.desktop new file mode 100644 index 00000000..da8409c4 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.desktop @@ -0,0 +1,92 @@ +[Desktop Entry] +Name=KAlarm Directory +Name[bs]=KAlarm direktorij +Name[ca]=Directori del KAlarm +Name[ca@valencia]=Directori del KAlarm +Name[cs]=Adresář KAlarmu +Name[da]=KAlarm-mappe +Name[de]=KAlarm-Ordner +Name[el]=Κατάλογος KAlarm +Name[en_GB]=KAlarm Directory +Name[es]=Directorio de KAlarm +Name[et]=KAlarmi kataloog +Name[fi]=KAlarm-kansio +Name[fr]=Dossier KAlarm +Name[ga]=Comhadlann KAlarm +Name[gl]=Directorio de KAlarm +Name[hu]=KAlarm mappa +Name[ia]=Directorio de KAlarm +Name[it]=Directory KAlarm +Name[kk]=KAlarm каталогын +Name[km]=ážáž KAlarm +Name[ko]=KAlarm 디렉터리 +Name[lt]=KAlarm aplankas +Name[lv]=KAlarm mape +Name[nb]=KAlarn-mappe +Name[nds]=KAlarm-Verteken +Name[nl]=KAlarm-map +Name[pl]=Katalog KAlarm +Name[pt]=Pasta do KAlarm +Name[pt_BR]=Pasta do KAlarm +Name[ru]=Папка KAlarm +Name[sk]=Adresár KAlarm +Name[sl]=Mapa za KAlarm +Name[sr]=К‑алармова фаÑцикла +Name[sr@ijekavian]=К‑алармова фаÑцикла +Name[sr@ijekavianlatin]=K‑alarmova fascikla +Name[sr@latin]=K‑alarmova fascikla +Name[sv]=Kalarm-katalog +Name[tr]=KAlarm Dizini +Name[uk]=Каталог KAlarm +Name[x-test]=xxKAlarm Directoryxx +Name[zh_CN]=KAlarm 目录 +Name[zh_TW]=KAlarm 目錄 +Comment=Loads data from a local KAlarm folder +Comment[bs]=ÄŒita podatke iz lokalnog KAlarm direktorija +Comment[ca]=Carrega les dades des d'una carpeta KAlarm local +Comment[ca@valencia]=Carrega dades des d'una carpeta KAlarm local +Comment[cs]=NaÄítá data z místní složky KAlarmu +Comment[da]=Indlæser data fra en lokal KAlarm-mappe +Comment[de]=Laden von Daten aus einem lokalen KAlarm-Ordner +Comment[el]=ΦοÏτώνει δεδομένα από έναν τοπικό φάκελο KAlarm +Comment[en_GB]=Loads data from a local KAlarm folder +Comment[es]=Carga datos de una carpeta local de KAlarm +Comment[et]=Andmete laadimine kohalikust KAlarmi kataloogist +Comment[fi]=Lataa tietoja paikallisesta KAlarm-kansiosta +Comment[fr]=Charge des données d'un dossier KAlarm +Comment[ga]=Luchtaíonn sé sonraí ó fhillteán logánta KAlarm +Comment[gl]=Carga datos desde un cartafol de KAlarm local. +Comment[hu]=Adatok betöltése egy helyi KAlarm mappából +Comment[ia]=Carga datos ex un dossier local de KAlarm +Comment[it]=Carica dati da una cartella locale KAlarm +Comment[kk]=Жергілікті KAlarm қапшығынан деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ážáž KAlarm មូលដ្ឋាន +Comment[ko]=로컬 KAlarm í´ë”ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ vietinio KAlarm aplanko +Comment[lv]=IelÄdÄ“ datus no lokÄlÄs KAlarm mapes +Comment[nb]=Laster data fra en lokal KAlarm-mappe +Comment[nds]=Laadt Daten ut en lokaal KAlarm-Orner +Comment[nl]=Laadt gegevens uit een lokale KAlarm-map +Comment[pl]=Wczytuje dane z lokalnego katalogu KAlarm +Comment[pt]=Carrega os dados de uma pasta local do KAlarm +Comment[pt_BR]=Carrega os dados de uma pasta local do KAlarm +Comment[ru]=Загрузка данных из локальной папки KAlarm +Comment[sk]=NaÄíta dáta z miestneho prieÄinka KAlarm +Comment[sl]=Naloži podatke iz krajevne mape za KAlarm +Comment[sr]=Учитава податке из локалне К‑алармове фаÑцикле +Comment[sr@ijekavian]=Учитава податке из локалне К‑алармове фаÑцикле +Comment[sr@ijekavianlatin]=UÄitava podatke iz lokalne K‑alarmove fascikle +Comment[sr@latin]=UÄitava podatke iz lokalne K‑alarmove fascikle +Comment[sv]=Laddar data frÃ¥n en lokal Kalarm-katalog +Comment[tr]=Yerel KAlarm dizininden veri yükleme aracı +Comment[uk]=Завантажує дані з локальної теки KAlarm +Comment[x-test]=xxLoads data from a local KAlarm folderxx +Comment[zh_CN]=从本地 KAlarm æ–‡ä»¶å¤¹è½½å…¥æ•°æ® +Comment[zh_TW]=從本地 KAlarm 目錄中載入資料 +Type=AkonadiResource +Exec=akonadi_kalarm_dir_resource +Icon=kalarm +X-Akonadi-MimeTypes=application/x-vnd.kde.alarm,application/x-vnd.kde.alarm.active,application/x-vnd.kde.alarm.archived,application/x-vnd.kde.alarm.template +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_kalarm_dir_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.h b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.h new file mode 100644 index 00000000..94e8cd39 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.h @@ -0,0 +1,105 @@ +/* + * kalarmdirresource.h - Akonadi directory resource for KAlarm + * Program: kalarm + * Copyright © 2011-2012 by David Jarvie + * + * 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 KALARMDIRRESOURCE_H +#define KALARMDIRRESOURCE_H + +#include + +#include +#include + +namespace Akonadi_KAlarm_Dir_Resource { class Settings; } +using namespace KAlarmCal; + +class KAlarmDirResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + public: + explicit KAlarmDirResource(const QString& id); + ~KAlarmDirResource(); + + public Q_SLOTS: + virtual void configure(WId windowId); + virtual void aboutToQuit(); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems(const Akonadi::Collection&); + bool retrieveItem(const Akonadi::Item&, const QSet& parts); + + protected: + virtual void collectionChanged(const Akonadi::Collection&); + virtual void itemAdded(const Akonadi::Item&, const Akonadi::Collection&); + virtual void itemChanged(const Akonadi::Item&, const QSet& parts); + virtual void itemRemoved(const Akonadi::Item&); + + private Q_SLOTS: + void settingsChanged(); + void fileCreated(const QString& path); + void fileChanged(const QString& path); + void fileDeleted(const QString& path); + void loadFiles() { loadFiles(true); } + void collectionFetchResult(KJob*); + void jobDone(KJob*); + + private: + void changeAlarmTypes(CalEvent::Types removed); + bool loadFiles(bool sync); + KAEvent loadFile(const QString& path, const QString& file); + KAEvent loadNextFile(const QString& eventId, const QString& file); + QString directoryName() const; + QString filePath(const QString& file) const; + QString fileName(const QString& path) const; + void initializeDirectory() const; + void setNameRights(Akonadi::Collection&); + bool cancelIfReadOnly(); + bool writeToFile(const KAEvent&); + void setCompatibility(bool writeAttr = true); + void removeEvent(const QString& eventId, bool deleteFile); + void addEventFile(const KAEvent&, const QString& file); + QString removeEventFile(const QString& eventId, const QString& file, KAEvent* = 0); + bool createItemAndIndex(const QString& path, const QString& file); + bool createItem(const KAEvent&); + bool modifyItem(const KAEvent&); + void deleteItem(const KAEvent&); + + struct EventFile // data to be indexed by event ID + { + EventFile() {} + EventFile(const KAEvent& e, const QStringList& f) : event(e), files(f) {} + KAEvent event; + QStringList files; // files containing this event ID, in-use one first + }; + QHash mEvents; // cached alarms and file names, indexed by ID + QHash mFileEventIds; // alarm IDs, indexed by file name + Akonadi_KAlarm_Dir_Resource::Settings* mSettings; + Akonadi::Collection::Id mCollectionId; // ID of this resource's collection + KACalendar::Compat mCompatibility; + int mVersion; // calendar format version + QStringList mChangedFiles; // files being written to + bool mCollectionFetched; // mCollectionId has been initialised + bool mWaitingToRetrieve; // retrieveCollections() needs to be called +}; + +#endif + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.kcfg b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.kcfg new file mode 100644 index 00000000..3252a7d4 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/kalarmdirresource.kcfg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + false + + + + true + + + + + + + false + + + diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/settings.kcfgc b/kdepim-runtime/resources/kalarm/kalarmdir/settings.kcfgc new file mode 100644 index 00000000..30ae4d11 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/settings.kcfgc @@ -0,0 +1,8 @@ +File=kalarmdirresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +GlobalEnums=true +Namespace=Akonadi_KAlarm_Dir_Resource diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.cpp b/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.cpp new file mode 100644 index 00000000..d9583e5c --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.cpp @@ -0,0 +1,135 @@ +/* + * settingsdialog.cpp - Akonadi KAlarm directory resource configuration dialog + * Program: kalarm + * Copyright © 2011 by David Jarvie + * + * 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 "settingsdialog.h" +#include "settings.h" +#include "alarmtypewidget.h" + +#include +#include + +#include + +namespace Akonadi_KAlarm_Dir_Resource +{ + +SettingsDialog::SettingsDialog(WId windowId, Settings* settings) + : KDialog(), + mSettings(settings), + mReadOnlySelected(false) +{ + ui.setupUi(mainWidget()); + mTypeSelector = new AlarmTypeWidget(ui.tab, ui.tabLayout); + ui.ktabwidget->setTabBarHidden(true); + ui.kcfg_Path->setMode(KFile::LocalOnly | KFile::Directory); + setButtons(Ok | Cancel); + setCaption(i18nc("@title", "Configure Calendar")); + + if (windowId) + KWindowSystem::setMainWindow(this, windowId); + + // Make directory path read-only if the resource already exists + KUrl path(mSettings->path()); + ui.kcfg_Path->setUrl(path); + if (!path.isEmpty()) + ui.kcfg_Path->setEnabled(false); + + mTypeSelector->setAlarmTypes(CalEvent::types(mSettings->alarmTypes())); + mManager = new KConfigDialogManager(this, mSettings); + mManager->updateWidgets(); + + connect(this, SIGNAL(okClicked()), SLOT(save())); + connect(ui.kcfg_Path, SIGNAL(textChanged(QString)), SLOT(textChanged())); + connect(ui.kcfg_ReadOnly, SIGNAL(clicked(bool)), SLOT(readOnlyClicked(bool))); + connect(mTypeSelector, SIGNAL(changed()), SLOT(validate())); + + QTimer::singleShot(0, this, SLOT(validate())); +} + +void SettingsDialog::save() +{ + mManager->updateSettings(); + mSettings->setPath(ui.kcfg_Path->url().toLocalFile()); + mSettings->setAlarmTypes(CalEvent::mimeTypes(mTypeSelector->alarmTypes())); + mSettings->writeConfig(); +} + +void SettingsDialog::readOnlyClicked(bool set) +{ + mReadOnlySelected = set; +} + +void SettingsDialog::textChanged() +{ + bool oldReadOnly = ui.kcfg_ReadOnly->isEnabled(); + validate(); + if (ui.kcfg_ReadOnly->isEnabled() && !oldReadOnly) + { + // If read-only was only set earlier by validating the input path, + // and the path is now ok to be read-write, clear the read-only status. + ui.kcfg_ReadOnly->setChecked(mReadOnlySelected); + } +} + +void SettingsDialog::validate() +{ + bool enableOk = false; + // At least one alarm type must be selected + if (mTypeSelector->alarmTypes() != CalEvent::EMPTY) + { + // The entered URL must be valid and local + const KUrl currentUrl = ui.kcfg_Path->url(); + if (currentUrl.isEmpty()) + ui.kcfg_ReadOnly->setEnabled(true); + else if (currentUrl.isLocalFile()) + { + QFileInfo file(currentUrl.toLocalFile()); + // It must either be an existing directory, or in a writable + // directory, and it must not be an existing file. + if (file.exists()) + { + if (file.isDir()) + enableOk = true; // it's an existing directory + } + else + { + // Specified directory doesn't already exist. + // Find the first level of parent directory which exists, + // and check that it is writable. + for ( ; ; ) + { + file.setFile(file.dir().absolutePath()); // get parent dir's file info + if (file.exists()) + { + if (file.isDir() && file.isWritable()) + enableOk = true; // it's possible to create the directory + break; + } + } + } + } + } + enableButton(Ok, enableOk); +} + +} + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.h b/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.h new file mode 100644 index 00000000..2e1e6670 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.h @@ -0,0 +1,68 @@ +/* + * settingsdialog.h - Akonadi KAlarm directory resource configuration dialog + * Program: kalarm + * Copyright © 2011 by David Jarvie + * + * 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 KALARMDIR_SETTINGSDIALOG_H +#define KALARMDIR_SETTINGSDIALOG_H + +#include "ui_settingsdialog.h" +#include "ui_alarmtypewidget.h" + +#include + +#include + +using namespace KAlarmCal; + +class KConfigDialogManager; +class AlarmTypeWidget; + +namespace Akonadi_KAlarm_Dir_Resource +{ + +class Settings; + +class SettingsDialog : public KDialog +{ + Q_OBJECT + public: + SettingsDialog(WId windowId, Settings*); + void setAlarmTypes(CalEvent::Types); + CalEvent::Types alarmTypes() const; + + private Q_SLOTS: + void save(); + void validate(); + void textChanged(); + void readOnlyClicked(bool); + + private: + Ui::SettingsDialog ui; + AlarmTypeWidget* mTypeSelector; + KConfigDialogManager* mManager; + Akonadi_KAlarm_Dir_Resource::Settings* mSettings; + bool mReadOnlySelected; // read-only was set by user (not by validate()) +}; + +} + +#endif // KALARMDIR_SETTINGSDIALOG_H + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.ui b/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.ui new file mode 100644 index 00000000..7fa3e5c1 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/kalarmdir/settingsdialog.ui @@ -0,0 +1,152 @@ + + SettingsDialog + + + + 0 + 0 + 400 + 320 + + + + + 400 + 10 + + + + + + + 0 + + + + Directory + + + + + + Directory Name + + + + + + + + &Directory: + + + kcfg_Path + + + + + + + + + + + + Select the directory whose contents should be represented by this resource. If the directory does not exist, it will be created. + + + true + + + + + + + + + + Display Name + + + + + + + + &Name: + + + kcfg_DisplayName + + + + + + + + + + + + Enter the name used to identify this resource in displays. If not specified, the directory name will be used. + + + true + + + + + + + + + + Access Rights + + + + + + Read only + + + + + + + If read-only mode is enabled, no changes will be written to the directory selected above. Read-only mode will be automatically enabled if you do not have write access to the directory. + + + true + + + + + + + + + + + + + + + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+ + KLineEdit + QLineEdit +
klineedit.h
+
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ +
diff --git a/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.cpp b/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.cpp new file mode 100644 index 00000000..a2fca200 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.cpp @@ -0,0 +1,73 @@ +/* + * alarmtyperadiowidget.cpp - KAlarm alarm type exclusive selection widget + * Program: kalarm + * Copyright © 2011 by David Jarvie + * + * 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 "alarmtyperadiowidget.h" + + +AlarmTypeRadioWidget::AlarmTypeRadioWidget(QWidget* parent) + : Akonadi::SingleFileValidatingWidget(parent) +{ + ui.setupUi(this); + ui.mainLayout->setContentsMargins(0, 0, 0, 0); + mButtonGroup = new QButtonGroup(ui.groupBox); + mButtonGroup->addButton(ui.activeRadio); + mButtonGroup->addButton(ui.archivedRadio); + mButtonGroup->addButton(ui.templateRadio); + connect(ui.activeRadio, SIGNAL(toggled(bool)), SIGNAL(changed())); + connect(ui.archivedRadio, SIGNAL(toggled(bool)), SIGNAL(changed())); + connect(ui.templateRadio, SIGNAL(toggled(bool)), SIGNAL(changed())); +} + +void AlarmTypeRadioWidget::setAlarmType(CalEvent::Type type) +{ + switch (type) + { + case CalEvent::ACTIVE: + ui.activeRadio->setChecked(true); + break; + case CalEvent::ARCHIVED: + ui.archivedRadio->setChecked(true); + break; + case CalEvent::TEMPLATE: + ui.templateRadio->setChecked(true); + break; + default: + break; + } +} + +CalEvent::Type AlarmTypeRadioWidget::alarmType() const +{ + if (ui.activeRadio->isChecked()) + return CalEvent::ACTIVE; + if (ui.archivedRadio->isChecked()) + return CalEvent::ARCHIVED; + if (ui.templateRadio->isChecked()) + return CalEvent::TEMPLATE; + return CalEvent::EMPTY; +} + +bool AlarmTypeRadioWidget::validate() const +{ + return static_cast(mButtonGroup->checkedButton()); +} + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.h b/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.h new file mode 100644 index 00000000..cecfb559 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.h @@ -0,0 +1,50 @@ +/* + * alarmtyperadiowidget.h - KAlarm alarm type exclusive selection widget + * Program: kalarm + * Copyright © 2011 by David Jarvie + * + * 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 ALARMTYPERADIOWIDGET_H +#define ALARMTYPERADIOWIDGET_H + +#include "singlefileresourceconfigdialogbase.h" +#include "ui_alarmtyperadiowidget.h" + +#include + +using namespace KAlarmCal; + +class QButtonGroup; + +class AlarmTypeRadioWidget : public Akonadi::SingleFileValidatingWidget +{ + Q_OBJECT + public: + explicit AlarmTypeRadioWidget(QWidget* parent = 0); + void setAlarmType(CalEvent::Type); + CalEvent::Type alarmType() const; + virtual bool validate() const; + + private: + Ui::AlarmTypeRadioWidget ui; + QButtonGroup* mButtonGroup; +}; + +#endif // ALARMTYPERADIOWIDGET_H + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.ui b/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.ui new file mode 100644 index 00000000..1af63741 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/alarmtyperadiowidget.ui @@ -0,0 +1,50 @@ + + AlarmTypeRadioWidget + + + + + + Alarm Types + + + Select which alarm type this resource should contain. + + + + + + Active Alarms + + + false + + + + + + + Archived Alarms + + + false + + + + + + + Alarm Templates + + + false + + + + + + + + + + diff --git a/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.cpp b/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.cpp new file mode 100644 index 00000000..343ff3c0 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.cpp @@ -0,0 +1,57 @@ +/* + * alarmtypewidget.cpp - KAlarm Akonadi configuration alarm type selection widget + * Program: kalarm + * Copyright © 2011 by David Jarvie + * + * 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 "alarmtypewidget.h" + + +AlarmTypeWidget::AlarmTypeWidget(QWidget* parent, QLayout* layout) + : QWidget() +{ + ui.setupUi(parent); + layout->addWidget(ui.groupBox); + connect(ui.activeCheckBox, SIGNAL(toggled(bool)), SIGNAL(changed())); + connect(ui.archivedCheckBox, SIGNAL(toggled(bool)), SIGNAL(changed())); + connect(ui.templateCheckBox, SIGNAL(toggled(bool)), SIGNAL(changed())); +} + +void AlarmTypeWidget::setAlarmTypes(CalEvent::Types types) +{ + if (types & CalEvent::ACTIVE) + ui.activeCheckBox->setChecked(true); + if (types & CalEvent::ARCHIVED) + ui.archivedCheckBox->setChecked(true); + if (types & CalEvent::TEMPLATE) + ui.templateCheckBox->setChecked(true); +} + +CalEvent::Types AlarmTypeWidget::alarmTypes() const +{ + CalEvent::Types types = CalEvent::EMPTY; + if (ui.activeCheckBox->isChecked()) + types |= CalEvent::ACTIVE; + if (ui.archivedCheckBox->isChecked()) + types |= CalEvent::ARCHIVED; + if (ui.templateCheckBox->isChecked()) + types |= CalEvent::TEMPLATE; + return types; +} + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.h b/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.h new file mode 100644 index 00000000..d9b5b484 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.h @@ -0,0 +1,48 @@ +/* + * alarmtypewidget.h - KAlarm Akonadi configuration alarm type selection widget + * Program: kalarm + * Copyright © 2011 by David Jarvie + * + * 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 ALARMTYPEWIDGET_H +#define ALARMTYPEWIDGET_H + +#include "ui_alarmtypewidget.h" + +#include + +using namespace KAlarmCal; + +class AlarmTypeWidget : public QWidget +{ + Q_OBJECT + public: + AlarmTypeWidget(QWidget* parent, QLayout* layout); + void setAlarmTypes(CalEvent::Types); + CalEvent::Types alarmTypes() const; + + signals: + void changed(); + + private: + Ui::AlarmTypeWidget ui; +}; + +#endif // ALARMTYPEWIDGET_H + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.ui b/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.ui new file mode 100644 index 00000000..a767a118 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/alarmtypewidget.ui @@ -0,0 +1,50 @@ + + AlarmTypeWidget + + + + + + Alarm Types + + + Select which alarm type(s) this resource should contain. + + + + + + Active Alarms + + + false + + + + + + + Archived Alarms + + + false + + + + + + + Alarm Templates + + + false + + + + + + + + + + diff --git a/kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.cpp b/kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.cpp new file mode 100644 index 00000000..d9992e86 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.cpp @@ -0,0 +1,207 @@ +/* + * kalarmresourcecommon.cpp - common functions for KAlarm Akonadi resources + * Program: kalarm + * Copyright © 2009-2014 by David Jarvie + * + * 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 "kalarmresourcecommon.h" + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +using namespace Akonadi; +using namespace KCalCore; +using namespace KAlarmCal; + + +class Private : public QObject +{ + Q_OBJECT + public: + Private(QObject* parent) : QObject(parent) {} + static Private* mInstance; + + private slots: + void modifyCollectionJobDone(KJob*); +}; +Private* Private::mInstance = 0; + + +namespace KAlarmResourceCommon +{ + +/****************************************************************************** +* Perform common initialisation for KAlarm resources. +*/ +void initialise(QObject* parent) +{ + // Create an object which can receive signals. + if (!Private::mInstance) + Private::mInstance = new Private(parent); + + // Set a default start-of-day time for date-only alarms. + KAEvent::setStartOfDay(QTime(0,0,0)); + + AttributeFactory::registerAttribute(); + AttributeFactory::registerAttribute(); + + KGlobal::locale()->insertCatalog(QLatin1String("akonadi_kalarm_resource")); +} + +/****************************************************************************** +* Find the compatibility of an existing calendar file, and convert it in +* memory to the current KAlarm format (if possible). +*/ +KACalendar::Compat getCompatibility(const FileStorage::Ptr& fileStorage, int& version) +{ + QString versionString; + version = KACalendar::updateVersion(fileStorage, versionString); + switch (version) + { + case KACalendar::IncompatibleFormat: + return KACalendar::Incompatible; // calendar is not in KAlarm format, or is in a future format + case KACalendar::CurrentFormat: + return KACalendar::Current; // calendar is in the current format + default: + return KACalendar::Convertible; // calendar is in an out of date format + } +} + +/****************************************************************************** +* Set an event into a new item's payload and return the new item. +* The caller should signal its retrieval by calling itemRetrieved(newitem). +* NOTE: the caller must set the event's compatibility beforehand. +*/ +Item retrieveItem(const Akonadi::Item& item, KAEvent& event) +{ + QString mime = CalEvent::mimeType(event.category()); + event.setItemId(item.id()); + if (item.hasAttribute()) + event.setCommandError(item.attribute()->commandError()); + + Item newItem = item; + newItem.setMimeType(mime); + newItem.setPayload(event); + return newItem; +} + +/****************************************************************************** +* Called when an item has been changed to validate it. +* Reply = the KAEvent for the item +* = invalid if error, in which case errorMsg contains the error message +* (which will be empty if the KAEvent is simply invalid). +*/ +KAEvent checkItemChanged(const Akonadi::Item& item, QString& errorMsg) +{ + KAEvent event; + if (item.hasPayload()) + event = item.payload(); + if (event.isValid()) + { + if (item.remoteId() != event.id()) + { + kWarning() << "Item ID" << item.remoteId() << "differs from payload ID" << event.id(); + errorMsg = i18nc("@info", "Item ID %1 differs from payload ID %2.", item.remoteId(), event.id()); + return KAEvent(); + } + } + + errorMsg.clear(); + return event; +} + +/****************************************************************************** +* Set a collection's compatibility attribute. +* Note that because this parameter is set asynchronously by the resource, it +* can't be stored in the same attribute as other collection parameters which +* are written by the application. This avoids the resource and application +* overwriting each other's changes if they attempt simultaneous updates. +*/ +void setCollectionCompatibility(const Collection& collection, KACalendar::Compat compatibility, int version) +{ + kDebug() << collection.id() << "->" << compatibility << version; + // Update the CompatibilityAttribute value only. + // Note that we can't supply 'collection' to CollectionModifyJob since + // that may also contain the CollectionAttribute value, which is read-only + // for the resource. So create a new Collection instance and only set a + // value for CompatibilityAttribute. + Collection col(collection.id()); + if (!collection.isValid()) + { + col.setParentCollection(collection.parentCollection()); + col.setRemoteId(collection.remoteId()); + } + CompatibilityAttribute* attr = col.attribute(Collection::AddIfMissing); + attr->setCompatibility(compatibility); + attr->setVersion(version); + Q_ASSERT(Private::mInstance); + CollectionModifyJob* job = new CollectionModifyJob(col, Private::mInstance->parent()); + Private::mInstance->connect(job, SIGNAL(result(KJob*)), SLOT(modifyCollectionJobDone(KJob*))); +} + +/****************************************************************************** +* Return an error message common to more than one resource. +*/ +QString errorMessage(ErrorCode code, const QString& param) +{ + switch (code) + { + case UidNotFound: + return i18nc("@info", "Event with uid '%1' not found.", param); + case NotCurrentFormat: + return i18nc("@info", "Calendar is not in current KAlarm format."); + case EventNotCurrentFormat: + return i18nc("@info", "Event with uid '%1' is not in current KAlarm format.", param); + case EventNoAlarms: + return i18nc("@info", "Event with uid '%1' contains no usable alarms.", param); + case EventReadOnly: + return i18nc("@info", "Event with uid '%1' is read only", param); + case CalendarAdd: + return i18nc("@info", "Failed to add event with uid '%1' to calendar", param); + } + return QString(); +} + +} // namespace KAlarmResourceCommon + +/****************************************************************************** +* Called when a collection modification job has completed, to report any error. +*/ +void Private::modifyCollectionJobDone(KJob* j) +{ + kDebug(); + if (j->error()) + { + Collection collection = static_cast(j)->collection(); + kError() << "Error: collection id" << collection.id() << ":" << j->errorString(); + } +} + +#include "kalarmresourcecommon.moc" + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.h b/kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.h new file mode 100644 index 00000000..dda49ff1 --- /dev/null +++ b/kdepim-runtime/resources/kalarm/shared/kalarmresourcecommon.h @@ -0,0 +1,60 @@ +/* + * kalarmresourcecommon.h - common functions for KAlarm Akonadi resources + * Program: kalarm + * Copyright © 2011-2014 by David Jarvie + * + * 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 KALARMRESOURCECOMMON_H +#define KALARMRESOURCECOMMON_H + +#include +#include + +#include + +namespace KCalCore { class FileStorage; } +namespace Akonadi { + class Collection; + class Item; +} +using namespace KAlarmCal; + +namespace KAlarmResourceCommon +{ + void initialise(QObject* parent); +// void customizeConfigDialog(SingleFileResourceConfigDialog*); + KACalendar::Compat getCompatibility(const KCalCore::FileStorage::Ptr&, int& version); + Akonadi::Item retrieveItem(const Akonadi::Item&, KAEvent&); + KAEvent checkItemChanged(const Akonadi::Item&, QString& errorMsg); + void setCollectionCompatibility(const Akonadi::Collection&, KACalendar::Compat, int version); + + enum ErrorCode + { + UidNotFound, + NotCurrentFormat, + EventNotCurrentFormat, + EventNoAlarms, + EventReadOnly, + CalendarAdd + }; + QString errorMessage(ErrorCode, const QString& param = QString()); +} + +#endif // KALARMRESOURCECOMMON_H + +// vim: et sw=4: diff --git a/kdepim-runtime/resources/kcal/CMakeLists.txt b/kdepim-runtime/resources/kcal/CMakeLists.txt new file mode 100644 index 00000000..1c55c7bf --- /dev/null +++ b/kdepim-runtime/resources/kcal/CMakeLists.txt @@ -0,0 +1,36 @@ +add_definitions( + -DKRESOURCES_DEPRECATED= + -DKABC_DEPRECATED= + -DKCAL_DEPRECATED= +) +# This one won't be needed when CMake 2.8.13 is depended on. +add_definitions( + -DKRESOURCES_DEPRECATED_EXPORT=KRESOURCES_EXPORT + -DKABC_DEPRECATED_EXPORT=KABC_EXPORT + -DKCAL_DEPRECATED_EXPORT=KCAL_EXPORT +) + +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( kcalresource_SRCS + kcalresource.cpp + ../kabc/kresourceassistant.cpp +) + +install( FILES kcalresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + + +kde4_add_executable(akonadi_kcal_resource ${kcalresource_SRCS}) + +target_link_libraries(akonadi_kcal_resource ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KCAL_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KCAL_LIBS} ${KDE4_KIO_LIBS}) + +install(TARGETS akonadi_kcal_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/kcal/Messages.sh b/kdepim-runtime/resources/kcal/Messages.sh new file mode 100644 index 00000000..f154aaa4 --- /dev/null +++ b/kdepim-runtime/resources/kcal/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/akonadi_kcal_resource.pot diff --git a/kdepim-runtime/resources/kcal/kcalresource.cpp b/kdepim-runtime/resources/kcal/kcalresource.cpp new file mode 100644 index 00000000..b5c89647 --- /dev/null +++ b/kdepim-runtime/resources/kcal/kcalresource.cpp @@ -0,0 +1,695 @@ +/* + Copyright (c) 2008-2009 Kevin Krammer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "kcalresource.h" +#include "resources/kabc/kresourceassistant.h" + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +typedef boost::shared_ptr IncidencePtr; + +using namespace Akonadi; + +static const char progName[] = "KCalResource"; +static const char progVersion[] = "0.9"; + +KCalResource::KCalResource( const QString &id ) + : ResourceBase( id ), + mManager( new KCal::CalendarResourceManager( QLatin1String( "calendar" ) ) ), + mResource( 0 ), + mMimeVisitor( new IncidenceMimeTypeVisitor() ), + mFullItemRetrieve( false ), + mDelayedSaveTimer( new QTimer( this ) ), + mIncidenceAssigner( new KCal::AssignmentVisitor() ) +{ + KGlobal::locale()->insertCatalog( QLatin1String( "akonadi_kresourceassistant" ) ); + + // setup for UID generation + const QString prodId = QLatin1String( "-//Akonadi//NONSGML KDE Compatibility Resource %1//EN" ); + KCal::CalFormat::setApplication( QLatin1String( progName ), prodId.arg( progVersion ) ); + + connect( this, SIGNAL(reloadConfiguration()), SLOT(reloadConfig()) ); + + connect( mDelayedSaveTimer, SIGNAL(timeout()), this, SLOT(delayedSaveCalendar()) ); + + changeRecorder()->itemFetchScope().fetchFullPayload(); + changeRecorder()->fetchCollection( true ); + + mDelayedSaveTimer->setSingleShot( true ); +} + +KCalResource::~KCalResource() +{ + delete mMimeVisitor; + delete mIncidenceAssigner; +} + +void KCalResource::configure( WId windowId ) +{ + if ( mResource != 0 ) { + emit status( Running, + i18nc( "@info:status", "Changing calendar plugin configuration" ) ); + KRES::ConfigDialog dlg( 0, QLatin1String( "calendar" ), mResource ); + if ( windowId ) + KWindowSystem::setMainWindow( &dlg, windowId ); + if ( dlg.exec() ) { + setName( mResource->resourceName() ); + mManager->writeConfig( KGlobal::config().data() ); + + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + + emit status( Idle, QString() ); + // TODO: need to react on name changes, but do we need to react on data changes? + // as a workaround lets sync the collection tree + synchronizeCollectionTree(); + return; + } + + emit status( Running, + i18nc( "@info:status", "Acquiring calendar plugin configuration" ) ); + + KResourceAssistant kresAssistant( QLatin1String( "Calendar" ) ); + KWindowSystem::setMainWindow( &kresAssistant, windowId ); + + connect( &kresAssistant, SIGNAL(error(QString)), + this, SIGNAL(error(QString)) ); + + if ( kresAssistant.exec() != QDialog::Accepted ) { + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + emit configurationDialogRejected(); + return; + } + + emit configurationDialogAccepted(); + + mResource = dynamic_cast( kresAssistant.resource() ); + Q_ASSERT( mResource != 0 ); + + mManager->add( mResource ); + mManager->writeConfig( KGlobal::config().data() ); + + if ( !openConfiguration() ) { + const QString message = i18nc( "@info:status", "Initialization based on newly created configuration failed." ); + emit error( message ); + emit status( NotConfigured, message ); + return; + } + + emit status( Running, i18nc( "@info:status", "Loading calendar" ) ); + + // signals emitted by initialLoadingFinished() or loadingError() + if ( !mResource->load() ) { + kError() << "Resource::load() failed"; + } +} + +void KCalResource::retrieveCollections() +{ + kDebug(); + if ( mResource == 0 ) { + kError() << "No KCal resource"; + + const QString message = i18nc( "@info:status", "No KDE calendar plugin configured yet" ); + emit error( message ); + + emit status( NotConfigured, message ); + return; + } + + Collection topLevelCollection; + topLevelCollection.setParentCollection( Collection::root() ); + topLevelCollection.setRemoteId( mResource->identifier() ); + topLevelCollection.setName( mResource->resourceName() ); + + EntityDisplayAttribute* attr = + topLevelCollection.attribute( Collection::AddIfMissing ); + attr->setDisplayName( mResource->resourceName() ); + attr->setIconName( QLatin1String( "office-calendar" ) ); + + QStringList mimeTypes; + mimeTypes << QLatin1String( "text/calendar" ); + mimeTypes += mMimeVisitor->allMimeTypes(); + + Collection::Rights readOnlyRights; + + Collection::Rights readWriteRights; + readWriteRights |= Collection::CanCreateItem; + readWriteRights |= Collection::CanChangeItem; + readWriteRights |= Collection::CanDeleteItem; + + if ( mResource->canHaveSubresources() ) { + mimeTypes << Collection::mimeType(); + + readWriteRights |= Collection::CanCreateCollection; + readWriteRights |= Collection::CanChangeCollection; + readWriteRights |= Collection::CanDeleteCollection; + } + + topLevelCollection.setContentMimeTypes( mimeTypes ); + topLevelCollection.setRights( mResource->readOnly() ? readOnlyRights : readWriteRights ); + + Collection::List list; + list << topLevelCollection; + + const QStringList subResources = mResource->subresources(); + kDebug() << "subResources" << subResources; + foreach ( const QString &subResource, subResources ) { + // TODO check with KOrganizer how it handles subresources + Collection childCollection; + childCollection.setParentCollection( topLevelCollection ); + childCollection.setRemoteId( subResource ); + childCollection.setName( mResource->labelForSubresource( subResource ) ); + + attr = childCollection.attribute( Collection::AddIfMissing ); + attr->setDisplayName( childCollection.name() ); + + const QString type = mResource->subresourceType( subResource ); + + if ( !type.isEmpty() ) { + QStringList childMimeTypes( Collection::mimeType() ); + childMimeTypes << QLatin1String( "application/x-vnd.akonadi.calendar." ) + type; + childCollection.setContentMimeTypes( childMimeTypes ); + + if ( type == QLatin1String( "journal" ) ) + attr->setIconName( QLatin1String( "view-pim-journal" ) ); + else if ( type == QLatin1String( "todo" ) ) + attr->setIconName( QLatin1String( "view-pim-tasks" ) ); + else + attr->setIconName( QLatin1String( "office-calendar" ) ); + } else { + attr->setIconName( QLatin1String( "office-calendar" ) ); + childCollection.setContentMimeTypes( mimeTypes ); + } + + // TODO we have no API for adding incidences to specific sub resources, so we should + // not tell Akonadi adding that items is allowed + childCollection.setRights( mResource->readOnly() ? readOnlyRights : readWriteRights ); + + list << childCollection; + } + + collectionsRetrieved( list ); +} + +void KCalResource::retrieveItems( const Akonadi::Collection &collection ) +{ + kDebug(); + if ( mResource == 0 ) { + kError() << "No KCal resource"; + + const QString message = i18nc( "@info:status", "No KDE calendar plugin configured yet" ); + emit error( message ); + + emit status( NotConfigured, message ); + return; + } + + Item::List items; + + const KCal::Incidence::List incidenceList = mResource->rawIncidences(); + foreach ( KCal::Incidence *incidence, incidenceList ) { + const QString subResource = mResource->subresourceIdentifier( incidence ); + if ( subResource == collection.remoteId() || + ( subResource.isEmpty() && collection.parentCollection() == Collection::root() ) ) { + Item item( mMimeVisitor->mimeType( incidence ) ); + item.setRemoteId( incidence->uid() ); + if ( mFullItemRetrieve ) + item.setPayload( IncidencePtr( incidence->clone() ) ); + items << item; + } + } + + itemsRetrieved( items ); +} + +bool KCalResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType() << "parts=" << parts; + + if ( mResource == 0 ) { + kError() << "No KCal resource"; + + const QString message = i18nc( "@info:status", "No KDE calendar plugin configured yet" ); + emit error( message ); + + emit status( NotConfigured, message ); + return false; + } + + const QString rid = item.remoteId(); + + KCal::Incidence *incidence = mResource->incidence( rid ); + if ( incidence == 0 ) { + kError() << "No incidence with uid" << rid; + emit error( i18nc( "@info:status", + "Request for data of a specific calendar entry failed " + "because there is no such entry" ) ); + return false; + } + + Item i( item ); + i.setMimeType( mMimeVisitor->mimeType( incidence ) ); + i.setPayload( IncidencePtr( incidence->clone() ) ); + itemRetrieved( i ); + + return true; +} + +void KCalResource::aboutToQuit() +{ + mManager->writeConfig( KGlobal::config().data() ); + + if ( mResource ) + mResource->save(); +} + +void KCalResource::doSetOnline( bool online ) +{ + kDebug() << "online=" << online << ", isOnline=" << isOnline(); + if ( online ) + reloadConfig(); + else + closeConfiguration(); + + ResourceBase::doSetOnline( online ); +} + +void KCalResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& collection ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType(); + + if ( mResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + Q_UNUSED( collection ); + + if ( item.hasPayload() ) { + IncidencePtr incidencePtr = item.payload(); + if ( incidencePtr->uid().isEmpty() ) { + const QString uid = KCal::CalFormat::createUniqueId(); + incidencePtr->setUid( uid ); + } + + KCal::Incidence *incidence = incidencePtr->clone(); + if ( mResource->addIncidence( incidence ) ) { + Item newItem( item ); + newItem.setRemoteId( incidencePtr->uid() ); + newItem.setPayload( incidencePtr ); + if ( !mResource->save( incidence ) ) { + kError() << "Failed to save incidence" << incidence->uid(); + // resource error emitted by savingError() + } + changeCommitted( newItem ); + return; + } + + kError() << "Failed to add incidence to resource"; + } + + changeProcessed(); +} + +void KCalResource::itemChanged( const Akonadi::Item &item, const QSet& parts ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId() + << "mimeType=" << item.mimeType() << "parts=" << parts; + + if ( mResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + if ( item.hasPayload() ) { + IncidencePtr incidencePtr = item.payload(); + + KCal::Incidence *incidence = mResource->incidence( incidencePtr->uid() ); + if ( incidence == 0 ) { + incidence = incidencePtr->clone(); + if ( mResource->addIncidence( incidence ) ) { + if ( !mResource->save( incidence ) ) { + kError() << "Failed to save incidence" << incidence->uid(); + // resource error emitted by savingError() + } + changeCommitted( item ); + return; + } + + kError() << "Failed to add incidence to resource"; + } else { + // make sure any observer the resource might have installed gets properly notified + incidence->startUpdates(); + bool assignResult = mIncidenceAssigner->assign( incidence, incidencePtr.get() ); + if ( assignResult ) + incidence->updated(); + incidence->endUpdates(); + + if ( !assignResult ) { + kWarning() << "Item changed incidence type. Replacing it."; + + mResource->deleteIncidence( incidence ); + mResource->addIncidence( incidencePtr->clone() ); + scheduleSaveCalendar(); + } else { + if ( !mResource->save( incidence ) ) { + kError() << "Failed to save incidence" << incidence->uid(); + // resource error emitted by savingError() + } + } + changeCommitted( item ); + return; + } + } + + changeProcessed(); +} + +void KCalResource::itemRemoved( const Akonadi::Item &item ) +{ + kDebug() << "item id=" << item.id() << ", remoteId=" << item.remoteId(); + + if ( mResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + KCal::Incidence *incidence = mResource->incidence( item.remoteId() ); + if ( incidence != 0 && mResource->deleteIncidence( incidence ) ) { + if ( !mResource->save( incidence ) ) { + kError() << "Failed to save incidence" << incidence->uid(); + // resource error emitted by savingError() + } + changeCommitted( item ); + return; + } + + changeProcessed(); +} + +void KCalResource::collectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ) +{ + kDebug() << "collection id=" << collection.id() << ", remoteId=" << collection.remoteId() << ", name=" << collection.name() + << ", parent id=" << parent.id() << ", remoteId=" << parent.remoteId(); + + if ( mResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + if ( mResource->addSubresource( collection.name(), parent.remoteId() ) ) { + // result delivered by signalSubresourceAdded(). how do we best relate this? + changeCommitted( collection ); + return; + } + + changeProcessed(); +} + +void KCalResource::collectionChanged( const Akonadi::Collection &collection ) +{ + kDebug() << "collection id=" << collection.id() << ", remoteId=" << collection.remoteId(); + + if ( mResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + // currently only changing the top level collection's name supported + if ( collection.parentCollection() == Collection::root() ) { + const QString newName = collection.displayName(); + + if ( newName != mResource->resourceName() ) { + mResource->setResourceName( newName ); + setName( newName ); + changeCommitted( collection ); + // TODO save config + return; + } + } + + changeProcessed(); +} + +void KCalResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + kDebug() << "collection id=" << collection.id() << ", remoteId=" << collection.remoteId(); + + if ( mResource == 0 ) { + kError() << "Resource not fully operational yet"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + // currently only removing sub resource supported + const QStringList subResources = mResource->subresources(); + if ( subResources.contains( collection.remoteId() ) ) { + if ( mResource->removeSubresource( collection.remoteId() ) ) { + changeCommitted( collection ); + scheduleSaveCalendar(); + return; + } + } + + changeProcessed(); +} + +bool KCalResource::openConfiguration() +{ + if ( mResource != 0 ) { + if ( !mResource->isOpen() ) { + if ( !mResource->open() ) { + kError() << "Opening resource" << mResource->identifier() << "failed"; + return false; + } + } + + connect( mResource, SIGNAL(resourceLoaded(ResourceCalendar*)), + this, SLOT(initialLoadingFinished(ResourceCalendar*)) ); + connect( mResource, SIGNAL(resourceLoadError(ResourceCalendar*,QString)), + this, SLOT(loadingError(ResourceCalendar*,QString)) ); + + setName( mResource->resourceName() ); + } + + return true; +} + +void KCalResource::closeConfiguration() +{ + mDelayedSaveTimer->stop(); + + if ( mResource != 0 ) { + if ( mResource->isOpen() ) + mResource->close(); + + disconnect( mResource, SIGNAL(resourceLoadError(ResourceCalendar*,QString)), + this, SLOT(loadingError(ResourceCalendar*,QString)) ); + disconnect( mResource, SIGNAL(resourceSaveError(ResourceCalendar*,QString)), + this, SLOT(savingError(ResourceCalendar*,QString)) ); + disconnect( mResource, SIGNAL(resourceChanged(ResourceCalendar*)), + this, SLOT(resourceChanged(ResourceCalendar*)) ); + } +} + +bool KCalResource::saveCalendar() +{ + kDebug(); + mDelayedSaveTimer->stop(); + + if ( !mResource || mResource->readOnly() ) + return false; + + if ( !mResource->save() ) { + kError() << "Saving failed"; + // resource error emitted by savingError() + return false; + } + + kDebug() << "Saving succeeded"; + return true; +} + +bool KCalResource::scheduleSaveCalendar() +{ + if ( !mResource || mResource->readOnly() ) + return false; + + if ( !mDelayedSaveTimer->isActive() ) + mDelayedSaveTimer->start( 5000 ); + + return true; +} + +void KCalResource::reloadConfig() +{ + kDebug() << "resource=" << (void*) mResource; + if ( mResource != 0 ) { + if ( !mResource->save() ) { + kError() << "Saving of calendar failed"; + } + } + closeConfiguration(); + + KGlobal::config()->reparseConfiguration(); + + if ( KGlobal::config()->groupList().isEmpty() ) { + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + Q_ASSERT( KGlobal::config().data() != 0); + + mManager->readConfig( KGlobal::config().data() ); + + mResource = mManager->standardResource(); + if ( mResource != 0 ) { + if ( mResource->type().toLower() == QLatin1String( "akonadi" ) ) { + kError() << "Resource config points to an Akonadi bridge resource"; + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + } + + if ( mResource == 0 ) { + emit status( NotConfigured, i18nc( "@info:status", "No KDE calendar plugin configured yet" ) ); + return; + } + + if ( !isOnline() ) + return; + + if ( !openConfiguration() ) { + kError() << "openConfiguration() failed"; + + const QString message = i18nc( "@info:status", "Initialization based on stored configuration failed." ); + emit error( message ); + emit status( NotConfigured, message ); + return; + } + + emit status( Running, i18nc( "@info:status", "Loading calendar" ) ); + + // signals emitted by initialLoadingFinished() or loadingError() + if ( !mResource->load() ) { + kError() << "Resource::load() failed"; + } +} + +void KCalResource::initialLoadingFinished( ResourceCalendar *resource ) +{ + Q_UNUSED( resource ); + Q_ASSERT( resource == mResource ); + + kDebug(); + + disconnect( mResource, SIGNAL(resourceLoaded(ResourceCalendar*)), + this, SLOT(initialLoadingFinished(ResourceCalendar*)) ); + + connect( mResource, SIGNAL(resourceChanged(ResourceCalendar*)), + this, SLOT(resourceChanged(ResourceCalendar*)) ); + + emit status( Idle, QString() ); + mFullItemRetrieve = false; + synchronize(); +} + +void KCalResource::resourceChanged( ResourceCalendar *resource ) +{ + Q_UNUSED( resource ); + Q_ASSERT( resource == mResource ); + + kDebug(); + if ( mDelayedSaveTimer->isActive() ) { + // TODO should record changes for delayed saving + kError() << "Delayed saving scheduled when resource changed. We might have lost changes"; + mDelayedSaveTimer->stop(); + } + + mFullItemRetrieve = true; + synchronize(); +} + +void KCalResource::loadingError( ResourceCalendar *resource, const QString &message ) +{ + Q_UNUSED( resource ); + Q_ASSERT( resource == mResource ); + + kError() << "Loading error: " << message; + const QString statusMessage = + i18nc( "@info:status", "Loading of calendar failed: %1", message ); + + emit error( statusMessage ); + emit status( Broken, statusMessage ); +} + +void KCalResource::savingError( ResourceCalendar *resource, const QString &message ) +{ + Q_UNUSED( resource ); + Q_ASSERT( resource == mResource ); + + kError() << "Saving error: " << message; + const QString statusMessage = + i18nc( "@info:status", "Saving of calendar failed: %1", message ); + + emit error( statusMessage ); + emit status( Broken, statusMessage ); +} + +void KCalResource::delayedSaveCalendar() +{ + if ( !saveCalendar() ) { + kError() << "Saving failed, rescheduling delayed save"; + if ( !scheduleSaveCalendar() ) { + kError() << "Scheduling failed as well, giving up"; + } + } +} + +AKONADI_RESOURCE_MAIN( KCalResource ) + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/kcal/kcalresource.desktop b/kdepim-runtime/resources/kcal/kcalresource.desktop new file mode 100644 index 00000000..b202c660 --- /dev/null +++ b/kdepim-runtime/resources/kcal/kcalresource.desktop @@ -0,0 +1,101 @@ +[Desktop Entry] +Name=KDE Calendar (traditional) +Name[ar]=تقويم كدي (تقليدي) +Name[bs]=KDE kalendar (tradicionalno) +Name[ca]=Calendari del KDE (tradicional) +Name[ca@valencia]=Calendari del KDE (tradicional) +Name[cs]=KDE kalendář (tradiÄní) +Name[da]=KDE-kalender (traditionel) +Name[de]=KDE-Kalender (herkömmlich) +Name[el]=ΗμεÏολόγιο KDE (παÏαδοσιακό) +Name[en_GB]=KDE Calendar (traditional) +Name[es]=Calendario de KDE (tradicional) +Name[et]=KDE kalender (traditsiooniline) +Name[fi]=KDE:n kalenteri (perinteinen) +Name[fr]=Agenda KDE (traditionnel) +Name[ga]=Féilire KDE (traidisiúnta) +Name[gl]=Calendario do KDE (tradicional) +Name[hu]=KDE naptár (hagyományos) +Name[ia]=Calendario KDE (traditional) +Name[it]=Calendario di KDE (tradizionale) +Name[ja]=KDE カレンダー (従æ¥ã®ã‚¿ã‚¤ãƒ—) +Name[kk]=KDE күнтіÑзбеÑÑ– (дәÑтүрлі) +Name[km]=ប្រážáž·áž‘áž·áž“ KDE (បុរាណ) +Name[ko]=KDE 달력 (ì´ì „ 버전) +Name[lt]=KDE kalendorius (tradicinis) +Name[lv]=KDE kalendÄrs (tradicionÄls) +Name[nb]=KDE-kalender (tradisjonell) +Name[nds]=KDE-Kalenner (normaal) +Name[nl]=Agenda van KDE (traditioneel) +Name[nn]=KDE-kalender (tradisjonell) +Name[pa]=KDE ਕੈਲੰਡਰ (ਪà©à¨°à¨¾à¨£à¨¾) +Name[pl]=Wtyczka kalendarza KDE (tradycyjna) +Name[pt]=Calendário do KDE (tradicional) +Name[pt_BR]=Calendário do KDE (tradicional) +Name[ro]=Calendar KDE (tradiÈ›ional) +Name[ru]=Календарь KDE (традиционный иÑточник) +Name[sk]=KDE kalendár (tradiÄný) +Name[sl]=KDE-jev koledar (tradicionalni) +Name[sr]=КДЕ‑ов календар (традиционални) +Name[sr@ijekavian]=КДЕ‑ов календар (традиционални) +Name[sr@ijekavianlatin]=KDE‑ov kalendar (tradicionalni) +Name[sr@latin]=KDE‑ov kalendar (tradicionalni) +Name[sv]=KDE:s kalender (traditionell) +Name[tr]=KDE Takvimi (geleneksel) +Name[uk]=Календар KDE (традиційний) +Name[x-test]=xxKDE Calendar (traditional)xx +Name[zh_CN]=KDE 日历(传统) +Name[zh_TW]=KDE 行事曆(傳統) +Comment=Loads data from a traditional KDE calendar resource +Comment[ar]=تحمل البيانات من موارد تقويم كدي التقليدي +Comment[bs]=UÄitava podatke iz tradicionalog KDE izvora kalendara +Comment[ca]=Carregar les dades des d'un recurs de calendari tradicional del KDE +Comment[ca@valencia]=Carregar dades des d'un recurs de calendari tradicional del KDE +Comment[cs]=NaÄítá data z tradiÄního kalendáře KDE +Comment[da]=Indlæser data fra en traditionel KDE kalenderressource +Comment[de]=Lädt Daten aus einer herkömmlichen KDE-Kalender-Ressource +Comment[el]=ΦόÏτωση δεδομένων από έναν παÏαδοσιακό πόÏο ημεÏολογίου του KDE +Comment[en_GB]=Loads data from a traditional KDE calendar resource +Comment[es]=Carga datos desde un recurso tradicional de Calendario de KDE +Comment[et]=Andmete laadimine traditsioonilisest KDE kalendri ressursist +Comment[fi]=Noutaa tietoa KDE:n perinteisestä kalenteriresurssista +Comment[fr]=Charge des données depuis un agenda KDE traditionnel +Comment[ga]=Breiseán a luchtaíonn sonraí ó acmhainn thraidisiúnta fhéilire KDE +Comment[gl]=Carga datos desde un recurso de calendario tradicional do KDE +Comment[hu]=Adatbetöltés egy hagyományos KDE naptárerÅ‘forrásból +Comment[ia]=Lege datos de un ressource de un calendario KDE traditional +Comment[it]=Carica dati da una tradizionale risorsa calendario di KDE +Comment[ja]=従æ¥ã® KDE カレンダーリソースã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=ДәÑтүрлі KDE күнтізбеÑінен деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ធនធាន​ប្រážáž·áž‘áž·áž“ KDE បុរាណ +Comment[ko]=ì´ì „ ë²„ì „ì˜ KDE 달력 ìžì›ì—ì„œ ë°ì´í„°ë¥¼ 불러옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ tradicinio KDE kalendoriaus resurso +Comment[lv]=IelÄdÄ“ datus no tradicionÄlÄ KDE kalendÄra resursa +Comment[nb]=Laster data fra en tradisjonell KDE-kalenderresurs +Comment[nds]=Laadt Daten ut en normaal KDE-Kalennerressource +Comment[nl]=Laadt gegevens van een traditioneel KDE agendabron +Comment[nn]=Lastar data frÃ¥ tradisjonelle KDE-kalenderressursar +Comment[pl]=Wczytuje dane z tradycyjnego zasobu kalendarza KDE +Comment[pt]=Carrega os dados de um recurso de calendário tradicional do KDE +Comment[pt_BR]=Carrega os dados do tradicional recurso de calendário do KDE +Comment[ro]=ÃŽncarcă date dintr-o resursă de calendar KDE tradiÈ›ională +Comment[ru]=Загрузка данных из иÑточника ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ KDE +Comment[sk]=NaÄíta dáta z prechodného KDE zdroja kalendára +Comment[sl]=Naloži podatke iz tradicionalnega KDE-jevega koledarja +Comment[sr]=Учитава податке из традиционалног КДЕ‑овог реÑурÑа календара +Comment[sr@ijekavian]=Учитава податке из традиционалног КДЕ‑овог реÑурÑа календара +Comment[sr@ijekavianlatin]=UÄitava podatke iz tradicionalnog KDE‑ovog resursa kalendara +Comment[sr@latin]=UÄitava podatke iz tradicionalnog KDE‑ovog resursa kalendara +Comment[sv]=Laddar data frÃ¥n en traditionell kalenderresurs i KDE +Comment[tr]=Geleneksel KDE takvim kaynağından veri yükler +Comment[uk]=Завантажує дані зі звичайного реÑурÑу ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð´Ð»Ñ KDE +Comment[x-test]=xxLoads data from a traditional KDE calendar resourcexx +Comment[zh_CN]=从传统 KDE 日历资æºè½½å…¥æ•°æ® +Comment[zh_TW]=從傳統 KDE 行事曆資æºè¼‰å…¥è³‡æ–™ +Type=AkonadiResource +Exec=akonadi_kcal_resource +Icon=text-calendar + +X-Akonadi-MimeTypes=text/calendar,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_kcal_resource diff --git a/kdepim-runtime/resources/kcal/kcalresource.h b/kdepim-runtime/resources/kcal/kcalresource.h new file mode 100644 index 00000000..1a9dd276 --- /dev/null +++ b/kdepim-runtime/resources/kcal/kcalresource.h @@ -0,0 +1,109 @@ +/* + Copyright (c) 2008-2009 Kevin Krammer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef KCALRESOURCE +#define KCALRESOURCE + +#include + +namespace KRES { + template class Manager; +} + +namespace KCal { + class AssignmentVisitor; + class ResourceCalendar; + + typedef KRES::Manager CalendarResourceManager; +} + +namespace Akonadi { + class IncidenceMimeTypeVisitor; +} + +class QTimer; + +class KCalResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + KCalResource( const QString &id ); + virtual ~KCalResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &collection ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void aboutToQuit(); + + virtual void doSetOnline( bool online ); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + virtual void collectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ); + + virtual void collectionChanged( const Akonadi::Collection &collection ); + + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + private: + KCal::CalendarResourceManager *mManager; + KCal::ResourceCalendar *mResource; + Akonadi::IncidenceMimeTypeVisitor *mMimeVisitor; + + bool mFullItemRetrieve; + + QTimer *mDelayedSaveTimer; + + KCal::AssignmentVisitor *mIncidenceAssigner; + + private: + bool openConfiguration(); + void closeConfiguration(); + + bool saveCalendar(); + + bool scheduleSaveCalendar(); + + typedef KCal::ResourceCalendar ResourceCalendar; + + private Q_SLOTS: + void reloadConfig(); + + void initialLoadingFinished( ResourceCalendar *resource ); + + void resourceChanged( ResourceCalendar *resource ); + + void loadingError( ResourceCalendar *resource, const QString &message ); + + void savingError( ResourceCalendar *resource, const QString &message ); + + void delayedSaveCalendar(); +}; + +#endif +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/kdeaccounts/CMakeLists.txt b/kdepim-runtime/resources/kdeaccounts/CMakeLists.txt new file mode 100644 index 00000000..d8569fef --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/CMakeLists.txt @@ -0,0 +1,34 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +########### next target ############### + +set( kdeaccountsresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES} + kdeaccountsresource.cpp +) + +install( FILES kdeaccountsresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(kdeaccountsresource_SRCS ${AKONADI_SINGLEFILERESOURCE_SHARED_UI}) +kde4_add_kcfg_files(kdeaccountsresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/kdeaccountsresource.kcfg org.kde.Akonadi.KDEAccounts.Settings) +qt4_add_dbus_adaptor(kdeaccountsresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.KDEAccounts.Settings.xml settings.h Settings +) + +kde4_add_executable(akonadi_kdeaccounts_resource ${kdeaccountsresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_kdeaccounts_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_kdeaccounts_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.KDEAccounts") + set_target_properties(akonadi_kdeaccounts_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi KDEAccounts Resource") +endif () + +target_link_libraries(akonadi_kdeaccounts_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KABC_LIBS} ${KDE4_KIO_LIBS}) + +install(TARGETS akonadi_kdeaccounts_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/kdeaccounts/Messages.sh b/kdepim-runtime/resources/kdeaccounts/Messages.sh new file mode 100644 index 00000000..485dab8b --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_kdeaccounts_resource.pot diff --git a/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.cpp b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.cpp new file mode 100644 index 00000000..d9394af3 --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.cpp @@ -0,0 +1,168 @@ +/* + Copyright (c) 2010 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. +*/ + +#include "kdeaccountsresource.h" + +#include "settingsadaptor.h" +#include "singlefileresourceconfigdialog.h" + +#include + +#include +#include +#include + + +using namespace Akonadi; + +KDEAccountsResource::KDEAccountsResource( const QString &id ) + : SingleFileResource( id ) +{ + setSupportedMimetypes( QStringList() << KABC::Addressee::mimeType(), QLatin1String("kde") ); + + setName( i18n( "KDE Accounts" ) ); + + new SettingsAdaptor( mSettings ); + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + mSettings, QDBusConnection::ExportAdaptors ); +} + +KDEAccountsResource::~KDEAccountsResource() +{ + mContacts.clear(); +} + +bool KDEAccountsResource::retrieveItem( const Akonadi::Item &item, const QSet& ) +{ + const QString remoteId = item.remoteId(); + if ( !mContacts.contains( remoteId ) ) { + cancelTask( i18n( "Contact with uid '%1' not found.", remoteId ) ); + return false; + } + + Item newItem( item ); + newItem.setPayload( mContacts.value( remoteId ) ); + itemRetrieved( newItem ); + + return true; +} + +void KDEAccountsResource::customizeConfigDialog( SingleFileResourceConfigDialog* dlg ) +{ + dlg->setWindowIcon( KIcon( QLatin1String("kde") ) ); + dlg->setCaption( i18n( "Select KDE Accounts File" ) ); +} + +void KDEAccountsResource::configDialogAcceptedActions( SingleFileResourceConfigDialog* ) +{ + // We can't hide the GUI element but we can enforce that the + // resource is read-only + mSettings->setReadOnly( true ); + mSettings->writeConfig(); +} + +void KDEAccountsResource::itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) +{ + cancelTask( i18n( "KDE Accounts file is read-only" ) ); +} + +void KDEAccountsResource::itemChanged( const Akonadi::Item&, const QSet& ) +{ + cancelTask( i18n( "KDE Accounts file is read-only" ) ); +} + +void KDEAccountsResource::itemRemoved( const Akonadi::Item& ) +{ + cancelTask( i18n( "KDE Accounts file is read-only" ) ); +} + +void KDEAccountsResource::retrieveItems( const Akonadi::Collection &collection ) +{ + // KDEAccounts does not support folders so we can safely ignore the collection + Q_UNUSED( collection ); + + Akonadi::Item::List items; + ContactsMap::const_iterator end(mContacts.constEnd()); + for ( ContactsMap::const_iterator it = mContacts.constBegin(); it != end; ++it ) { + Item item; + item.setRemoteId( it.key() ); + item.setMimeType( KABC::Addressee::mimeType() ); + item.setPayload( it.value() ); + items.append( item ); + } + + itemsRetrieved( items ); +} + +bool KDEAccountsResource::readFromFile( const QString &fileName ) +{ + mContacts.clear(); + + QFile file( KUrl( fileName ).toLocalFile() ); + if ( !file.open( QIODevice::ReadOnly ) ) { + emit status( Broken, i18n( "Unable to open KDE accounts file '%1'.", fileName ) ); + return false; + } + + while ( !file.atEnd() ) { + QString line = QString::fromUtf8( file.readLine() ); + // We have a line like the following now, which consists of a nickname, + // the full name and an email address + // 'tokoe Tobias Koenig tokoe@kde.org' + + // remove all leading, trailing and duplicated white spaces + line = line.trimmed().simplified(); + if (line.isEmpty()) { + continue; + } + + QStringList parts = line.split( QLatin1Char( ' ' ) ); + if ( parts.count() < 3 ) { + kWarning() << "Failed to parse line \"" << line << "\", invalid format"; + continue; + } + + // The first part is the nick name, the last one the email address and + // everything else in between the full name + const QString nickName = parts.takeFirst(); + const QString email = parts.takeLast(); + const QString fullName = parts.join( QLatin1String( " " ) ); + + KABC::Addressee contact; + contact.setNickName( nickName ); + contact.setNameFromString( fullName ); + contact.setOrganization( i18n( "KDE Project" ) ); + contact.insertCategory( i18n( "KDE Developer" ) ); + contact.insertEmail( email ); + + mContacts.insert( nickName, contact ); + } + + return true; +} + +bool KDEAccountsResource::writeToFile( const QString& ) +{ + Q_ASSERT( false ); // this method should never be called + + return false; +} + +AKONADI_RESOURCE_MAIN( KDEAccountsResource ) + diff --git a/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.desktop b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.desktop new file mode 100644 index 00000000..32cd1b87 --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.desktop @@ -0,0 +1,99 @@ +[Desktop Entry] +Name=KDE Accounts +Name[bs]=KDE nalozi +Name[ca]=Comptes del KDE +Name[ca@valencia]=Comptes del KDE +Name[cs]=ÚÄty KDE +Name[da]=KDE-konti +Name[de]=KDE-Zugänge +Name[el]=ΛογαÏιασμοί KDE +Name[en_GB]=KDE Accounts +Name[es]=Cuentas de KDE +Name[et]=KDE kontod +Name[fi]=KDE-tilit +Name[fr]=Comptes KDE +Name[ga]=Cuntais KDE +Name[gl]=Contas do KDE +Name[hu]=KDE azonosítók +Name[ia]=Contos KDE +Name[it]=Account KDE +Name[ja]=KDE アカウント +Name[kk]=KDE тіркелгілері +Name[km]=គណនី KDE +Name[ko]=KDE ì—°ë½ì²˜ +Name[lt]=KDE paskyros +Name[lv]=KDE konti +Name[nb]=KDE-kontoer +Name[nds]=KDE-Kontos +Name[nl]=KDE-accounts +Name[nn]=KDE-kontoar +Name[pa]=KDE ਅਕਾਊਂਟ +Name[pl]=Konta KDE +Name[pt]=Contas do KDE +Name[pt_BR]=Contas do KDE +Name[ro]=Conturi KDE +Name[ru]=Учётные запиÑи KDE +Name[sk]=KDE úÄty +Name[sl]=KDE-jevi raÄuni +Name[sr]=КДЕ налози +Name[sr@ijekavian]=КДЕ налози +Name[sr@ijekavianlatin]=KDE nalozi +Name[sr@latin]=KDE nalozi +Name[sv]=KDE-konton +Name[tr]=KDE Hesapları +Name[ug]=KDE Ú¾Ûساباتى +Name[uk]=Облікові запиÑи KDE +Name[x-test]=xxKDE Accountsxx +Name[zh_CN]=KDE 账户 +Name[zh_TW]=KDE 帳號 +Comment=Loads contacts from the KDE accounts file +Comment[bs]=UÄitava kontakte s KDE datoteke naloga +Comment[ca]=Carrega contactes des d'un fitxer de comptes del KDE +Comment[ca@valencia]=Carrega contactes des d'un fitxer de comptes del KDE +Comment[da]=Indlæser kontakter fra KDE-konti-filen +Comment[de]=Lädt Kontakte aus der KDE-Zugangsdatei +Comment[el]=ΦόÏτωση επαφών από ένα αÏχείο λογαÏιασμών KDE +Comment[en_GB]=Loads contacts from the KDE accounts file +Comment[es]=Cargar contactos desde el archivo de cuentas de KDE +Comment[et]=Kontaktide laadimine KDE kontode failist +Comment[fi]=Lataa yhteystiedot KDE:n tilitiedostosta +Comment[fr]=Charge les contacts depuis le fichier de comptes KDE +Comment[ga]=Luchtaíonn sé seo teagmhálacha ón chomhad cuntas KDE +Comment[gl]=Carga contactos desde un ficheiro de contas do KDE +Comment[hu]=Adatok betöltése a KDE azonosítófájlból +Comment[ia]=Lege contactos de un file de un conto KDE +Comment[it]=Carica contatti dal file degli account KDE +Comment[ja]=KDE アカウントファイルã‹ã‚‰é€£çµ¡å…ˆã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=KDE тіркелгілер файлынан контакттарды алып береді +Comment[km]=ផ្ទុក​ទំនាក់ទំនង​ពី​ឯកសារគណនី​របស់ KDE +Comment[ko]=KDE ì—°ë½ì²˜ 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=Ä®kelia kontaktus iÅ¡ KDE paskyros failo +Comment[lv]=IelÄdÄ“ datus no KDE kontu faila +Comment[nb]=Laster kontakter fra KDE-kontofila +Comment[nds]=Laadt Kontakten ut de KDE-Kontendatei +Comment[nl]=Laadt contactpersonen uit het bestand met KDE-accounts +Comment[nn]=Lastar inn kontaktar frÃ¥ ei KDE-kontofil +Comment[pl]=Wczytuje kontakty z pliku kontaktów KDE +Comment[pt]=Carrega os dados do ficheiro de contas do KDE +Comment[pt_BR]=Carrega os contatos a partir do arquivo de contas do KDE +Comment[ro]=ÃŽncarcă date din fiÈ™ierul cu conturi KDE +Comment[ru]=Загрузка контактов из файла контактов KDE +Comment[sk]=NaÄíta kontakty zo súboru KDE úÄtov +Comment[sl]=Naloži stike iz datoteke KDE-jevih raÄunov +Comment[sr]=Учитава контакте из фајла КДЕ налога +Comment[sr@ijekavian]=Учитава контакте из фајла КДЕ налога +Comment[sr@ijekavianlatin]=UÄitava kontakte iz fajla KDE naloga +Comment[sr@latin]=UÄitava kontakte iz fajla KDE naloga +Comment[sv]=Laddar kontakter frÃ¥n KDE:s kontofil +Comment[tr]=KDE hesap dosyasından kiÅŸileri yükler +Comment[uk]=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ñ–Ð² з файла облікових запиÑів KDE +Comment[x-test]=xxLoads contacts from the KDE accounts filexx +Comment[zh_CN]=从 KDE 账户文件中装入è”系人 +Comment[zh_TW]=從 KDE 帳號檔載入è¯çµ¡äºº +Type=AkonadiResource +Exec=akonadi_kdeaccounts_resource +Icon=kde + +X-Akonadi-MimeTypes=text/directory +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_kdeaccounts_resource diff --git a/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.h b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.h new file mode 100644 index 00000000..33ac5d46 --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2010 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. +*/ + +#ifndef KDEACCOUNTSRESOURCE_H +#define KDEACCOUNTSRESOURCE_H + +#include "singlefileresource.h" +#include "settings.h" + +#include + +class KDEAccountsResource : public Akonadi::SingleFileResource +{ + Q_OBJECT + + public: + KDEAccountsResource( const QString &id ); + ~KDEAccountsResource(); + + protected Q_SLOTS: + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + void retrieveItems( const Akonadi::Collection &collection ); + + protected: + /** + * Customize the configuration dialog before it is displayed. + */ + virtual void customizeConfigDialog( Akonadi::SingleFileResourceConfigDialog* dlg ); + + /* + * Do stuff when the configuration dialog has been accepted, before + * reloadFile() is called. + */ + virtual void configDialogAcceptedActions( Akonadi::SingleFileResourceConfigDialog* dlg ); + + bool readFromFile( const QString &fileName ); + bool writeToFile( const QString &fileName ); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + private: + typedef QMap ContactsMap; + ContactsMap mContacts; +}; + +#endif diff --git a/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.kcfg b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.kcfg new file mode 100644 index 00000000..035be821 --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/kdeaccountsresource.kcfg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + true + + + + true + + + diff --git a/kdepim-runtime/resources/kdeaccounts/settings.kcfgc b/kdepim-runtime/resources/kdeaccounts/settings.kcfgc new file mode 100644 index 00000000..26055173 --- /dev/null +++ b/kdepim-runtime/resources/kdeaccounts/settings.kcfgc @@ -0,0 +1,7 @@ +File=kdeaccountsresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +GlobalEnums=true diff --git a/kdepim-runtime/resources/kolab/CMakeLists.txt b/kdepim-runtime/resources/kolab/CMakeLists.txt new file mode 100644 index 00000000..0e8e237a --- /dev/null +++ b/kdepim-runtime/resources/kolab/CMakeLists.txt @@ -0,0 +1,61 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../imap + ${CMAKE_CURRENT_BINARY_DIR}/../imap + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ${Libkolab_INCLUDES} + ${Libkolabxml_INCLUDES} +) +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +########### next target ############### + +set(kolabresource_SRCS + ../imap/imapresource.cpp + ../imap/settingspasswordrequester.cpp + ../imap/setupserver.cpp + ../imap/serverinfodialog.cpp + kolabretrievecollectionstask.cpp + kolabresource.cpp + kolabresourcestate.cpp + kolabhelpers.cpp + kolabmessagehelper.cpp +) + +kde4_add_kcfg_files(kolabresource_SRCS ../imap/settingsbase.kcfgc) + +if (KDEPIM_MOBILE_UI) +kde4_add_ui_files(kolabresource_SRCS ../imap/setupserverview_mobile.ui) +else () +kde4_add_ui_files(kolabresource_SRCS ../imap/setupserverview_desktop.ui) +endif () +kde4_add_ui_files(kolabresource_SRCS ../imap/serverinfo.ui) + +kde4_add_executable(akonadi_kolab_resource ${kolabresource_SRCS}) +target_link_libraries(akonadi_kolab_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTDBUS_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ${KDEPIMLIBS_KIMAP_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} + imapresource + folderarchivesettings + ${Libkolab_LIBRARIES} + ${Libkolabxml_LIBRARIES} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} +) + +install(FILES kolabresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") +install(TARGETS akonadi_kolab_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/kolab/kolabhelpers.cpp b/kdepim-runtime/resources/kolab/kolabhelpers.cpp new file mode 100644 index 00000000..084bcb53 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabhelpers.cpp @@ -0,0 +1,387 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 "kolabhelpers.h" +#include +#include +#include +#include +#include +#include +#include +#include + +bool KolabHelpers::checkForErrors(const Akonadi::Item &item) +{ + if (!Kolab::ErrorHandler::instance().errorOccured()) { + Kolab::ErrorHandler::instance().clear(); + return false; + } + + QString errorMsg; + foreach (const Kolab::ErrorHandler::Err &error, Kolab::ErrorHandler::instance().getErrors()) { + errorMsg.append(error.message); + errorMsg.append(QLatin1String("\n")); + } + + kWarning() << "Error on item with id: " << item.id() << " remote id: " << item.remoteId() << ":\n" << errorMsg; + Kolab::ErrorHandler::instance().clear(); + return true; +} + +Akonadi::Item KolabHelpers::translateFromImap(Kolab::FolderType folderType, const Akonadi::Item &imapItem, bool &ok) +{ + //Avoid trying to convert imap messages + if (folderType == Kolab::MailType) { + return imapItem; + } + + //No payload, so it's a flag change. We ignore flag changes on groupware data. + if (!imapItem.hasPayload()) { + ok = false; + return Akonadi::Item(); + } + if (!imapItem.hasPayload()) { + kWarning() << "Payload is not a MessagePtr!"; + Q_ASSERT(false); + ok = false; + return Akonadi::Item(); + } + + const KMime::Message::Ptr payload = imapItem.payload(); + const Kolab::KolabObjectReader reader(payload); + if (checkForErrors(imapItem)) { + //By not delivering items we cannot translate, they are simply deleted from local storage + ok = false; + return Akonadi::Item(); + } + switch (reader.getType()) { + case Kolab::EventObject: + case Kolab::TodoObject: + case Kolab::JournalObject: + { + const KCalCore::Incidence::Ptr incidencePtr = reader.getIncidence(); + if (!incidencePtr) { + kWarning() << "Failed to read incidence."; + ok = false; + return Akonadi::Item(); + } + Akonadi::Item newItem(incidencePtr->mimeType()); + newItem.setPayload(incidencePtr); + newItem.setRemoteId(imapItem.remoteId()); + newItem.setGid(incidencePtr->instanceIdentifier()); + return newItem; + } + break; + case Kolab::NoteObject: + { + const KMime::Message::Ptr note = reader.getNote(); + if (!note) { + kWarning() << "Failed to read note."; + ok = false; + return Akonadi::Item(); + } + Akonadi::Item newItem(QLatin1String("text/x-vnd.akonadi.note")); + newItem.setPayload(note); + newItem.setRemoteId(imapItem.remoteId()); + const Akonadi::NoteUtils::NoteMessageWrapper wrapper(note); + newItem.setGid(wrapper.uid()); + return newItem; + } + break; + case Kolab::ContactObject: + { + Akonadi::Item newItem(KABC::Addressee::mimeType()); + newItem.setPayload(reader.getContact()); + newItem.setRemoteId(imapItem.remoteId()); + newItem.setGid(reader.getContact().uid()); + return newItem; + } + break; + case Kolab::DistlistObject: + { + KABC::ContactGroup contactGroup = reader.getDistlist(); + + QList toAdd; + for (uint index = 0; index < contactGroup.contactReferenceCount(); ++index) { + const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference(index); + KABC::ContactGroup::ContactReference ref; + ref.setGid(reference.uid()); //libkolab set a gid with setUid() + toAdd << ref; + } + contactGroup.removeAllContactReferences(); + foreach (const KABC::ContactGroup::ContactReference &ref, toAdd) { + contactGroup.append(ref); + } + + Akonadi::Item newItem(KABC::ContactGroup::mimeType()); + newItem.setPayload(contactGroup); + newItem.setRemoteId(imapItem.remoteId()); + newItem.setGid(contactGroup.id()); + return newItem; + } + break; + default: + kWarning() << "Object type not handled"; + ok = false; + break; + } + return Akonadi::Item(); +} + +Akonadi::Item::List KolabHelpers::translateToImap(const Akonadi::Item::List &items, bool &ok) +{ + Akonadi::Item::List imapItems; + Q_FOREACH(const Akonadi::Item &item, items) { + bool translationOk = true; + imapItems << translateToImap(item, translationOk); + if (!translationOk) { + ok = false; + } + } + return imapItems; +} + +static KABC::ContactGroup convertToGidOnly(const KABC::ContactGroup &contactGroup) +{ + QList toAdd; + for ( uint index = 0; index < contactGroup.contactReferenceCount(); ++index ) { + const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference( index ); + QString gid; + if (!reference.gid().isEmpty()) { + gid = reference.gid(); + } else { + // WARNING: this is an ugly hack for backwards compatiblity. Normally this codepath shouldn't be hit. + // Replace all references with real data-sets + // Hopefully all resources are available during saving, so we can look up + // in the addressbook to get name+email from the UID. + + const Akonadi::Item item(reference.uid().toLongLong()); + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item); + job->fetchScope().fetchFullPayload(); + if (!job->exec()) { + continue; + } + + const Akonadi::Item::List items = job->items(); + if (items.count() != 1) { + continue; + } + const KABC::Addressee addressee = job->items().first().payload(); + gid = addressee.uid(); + } + KABC::ContactGroup::ContactReference ref; + ref.setUid(gid); //libkolab expects a gid for uid() + toAdd << ref; + } + KABC::ContactGroup gidOnlyContactGroup = contactGroup; + gidOnlyContactGroup.removeAllContactReferences(); + foreach ( const KABC::ContactGroup::ContactReference &ref, toAdd ) { + gidOnlyContactGroup.append( ref ); + } + return gidOnlyContactGroup; +} + +Akonadi::Item KolabHelpers::translateToImap(const Akonadi::Item &item, bool &ok) +{ + ok = true; + //imap messages don't need to be translated + if (item.mimeType() == KMime::Message::mimeType()) { + Q_ASSERT(item.hasPayload()); + return item; + } + const QLatin1String productId("Akonadi-Kolab-Resource"); + //Everthing stays the same, except mime type and payload + Akonadi::Item imapItem = item; + imapItem.setMimeType( QLatin1String("message/rfc822") ); + switch(getKolabTypeFromMimeType(item.mimeType())) { + case Kolab::EventObject: + case Kolab::TodoObject: + case Kolab::JournalObject: + { + Q_ASSERT(item.hasPayload()); + kDebug() << "converted event"; + const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeIncidence( + item.payload(), + Kolab::KolabV3, productId, QLatin1String("UTC") ); + imapItem.setPayload( message ); + } + break; + case Kolab::NoteObject: + { + Q_ASSERT(item.hasPayload()); + kDebug() << "converted note"; + const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeNote( + item.payload(), Kolab::KolabV3, productId); + imapItem.setPayload( message ); + } + break; + case Kolab::ContactObject: + { + Q_ASSERT(item.hasPayload()); + kDebug() << "converted contact"; + const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeContact( + item.payload(), Kolab::KolabV3, productId); + imapItem.setPayload( message ); + } + break; + case Kolab::DistlistObject: + { + Q_ASSERT(item.hasPayload()); + const KABC::ContactGroup contactGroup = convertToGidOnly(item.payload()); + kDebug() << "converted distlist"; + const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeDistlist( + contactGroup, Kolab::KolabV3, productId); + imapItem.setPayload( message ); + } + break; + default: + kWarning() << "object type not handled: " << item.id() << item.mimeType(); + ok = false; + return Akonadi::Item(); + + } + + if (checkForErrors(item)) { + kWarning() << "an error occured while trying to translate the item to the kolab format: " << item.id(); + ok = false; + return Akonadi::Item(); + } + return imapItem; +} + +QByteArray KolabHelpers::kolabTypeForMimeType( const QStringList &contentMimeTypes ) +{ + if (contentMimeTypes.contains(KABC::Addressee::mimeType())) { + return "contact"; + } else if (contentMimeTypes.contains( KCalCore::Event::eventMimeType())) { + return "event"; + } else if (contentMimeTypes.contains( KCalCore::Todo::todoMimeType())) { + return "task"; + } else if (contentMimeTypes.contains( KCalCore::Journal::journalMimeType())) { + return "journal"; + } else if (contentMimeTypes.contains(QLatin1String("application/x-vnd.akonadi.note")) || + contentMimeTypes.contains(QLatin1String("text/x-vnd.akonadi.note"))) { + return "note"; + } + return QByteArray(); +} + +Kolab::ObjectType KolabHelpers::getKolabTypeFromMimeType(const QString &type) +{ + if (type == KCalCore::Event::eventMimeType()) { + return Kolab::EventObject; + } else if (type == KCalCore::Todo::todoMimeType()) { + return Kolab::TodoObject; + } else if (type == KCalCore::Journal::journalMimeType()) { + return Kolab::JournalObject; + } else if (type == KABC::Addressee::mimeType()) { + return Kolab::ContactObject; + } else if (type == KABC::ContactGroup::mimeType()) { + return Kolab::DistlistObject; + } else if (type == QLatin1String("text/x-vnd.akonadi.note") || + type == QLatin1String("application/x-vnd.akonadi.note")) { + return Kolab::NoteObject; + } + return Kolab::InvalidObject; +} + +QStringList KolabHelpers::getContentMimeTypes(Kolab::FolderType type) +{ + QStringList contentTypes; + contentTypes << Akonadi::Collection::mimeType(); + switch (type) { + case Kolab::EventType: + case Kolab::TaskType: + case Kolab::JournalType: + contentTypes << KCalCore::Incidence::mimeTypes(); + break; + case Kolab::ContactType: + contentTypes << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType(); + break; + case Kolab::NoteType: + contentTypes << QLatin1String("text/x-vnd.akonadi.note") << QLatin1String("application/x-vnd.akonadi.note"); + break; + case Kolab::MailType: + contentTypes << KMime::Message::mimeType(); + break; + default: + kDebug() << "unhandled folder type: " << type; + } + return contentTypes; +} + +Kolab::FolderType KolabHelpers::folderTypeFromString(const QByteArray& folderTypeName) +{ + return Kolab::folderTypeFromString( std::string(folderTypeName.data(), folderTypeName.size()) ); +} + +QByteArray KolabHelpers::getFolderTypeAnnotation(const QMap< QByteArray, QByteArray > &annotations) +{ + if (annotations.contains("/shared" KOLAB_FOLDER_TYPE_ANNOTATION)) { + return annotations.value( "/shared" KOLAB_FOLDER_TYPE_ANNOTATION); + } + return annotations.value(KOLAB_FOLDER_TYPE_ANNOTATION); +} + +void KolabHelpers::setFolderTypeAnnotation(QMap< QByteArray, QByteArray >& annotations, const QByteArray& value) +{ + annotations["/shared" KOLAB_FOLDER_TYPE_ANNOTATION] = value; +} + +QString KolabHelpers::getIcon(Kolab::FolderType type) +{ + switch (type) { + case Kolab::EventType: + case Kolab::TaskType: + case Kolab::JournalType: + return QLatin1String("view-calendar"); + case Kolab::ContactType: + return QLatin1String("view-pim-contacts"); + case Kolab::NoteType: + return QLatin1String("view-pim-notes"); + case Kolab::MailType: + case Kolab::ConfigurationType: + case Kolab::FreebusyType: + case Kolab::FileType: + default: + break; + } + return QString(); +} + +bool KolabHelpers::isHandledType(Kolab::FolderType type) +{ + switch (type) { + case Kolab::EventType: + case Kolab::TaskType: + case Kolab::JournalType: + case Kolab::ContactType: + case Kolab::NoteType: + case Kolab::MailType: + return true; + case Kolab::ConfigurationType: + case Kolab::FreebusyType: + case Kolab::FileType: + default: + break; + } + return false; +} + diff --git a/kdepim-runtime/resources/kolab/kolabhelpers.h b/kdepim-runtime/resources/kolab/kolabhelpers.h new file mode 100644 index 00000000..4ceacb2b --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabhelpers.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 KOLABHELPERS_H +#define KOLABHELPERS_H + +#include +#include //libkolab +#include //libkolab + +class KolabHelpers { +public: + static bool checkForErrors(const Akonadi::Item &affectedItem); + static Akonadi::Item translateFromImap(Kolab::FolderType folderType, const Akonadi::Item &item, bool &ok); + static Akonadi::Item::List translateToImap(const Akonadi::Item::List &items, bool &ok); + static Akonadi::Item translateToImap(const Akonadi::Item &item, bool &ok); + static Kolab::FolderType folderTypeFromString( const QByteArray &folderTypeName ); + static QByteArray getFolderTypeAnnotation( const QMap &annotations); + static void setFolderTypeAnnotation( QMap &annotations, const QByteArray &value); + static Kolab::ObjectType getKolabTypeFromMimeType(const QString &type); + static QByteArray kolabTypeForMimeType( const QStringList &contentMimeTypes ); + static QStringList getContentMimeTypes(Kolab::FolderType type); + static QString getIcon(Kolab::FolderType type); + //Returns true if the folder type shouldn't be ignored + static bool isHandledType(Kolab::FolderType type); +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolab/kolabmessagehelper.cpp b/kdepim-runtime/resources/kolab/kolabmessagehelper.cpp new file mode 100644 index 00000000..777d48e2 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabmessagehelper.cpp @@ -0,0 +1,58 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 "kolabmessagehelper.h" + +#include +#include //libkolab + +#include "kolabhelpers.h" + + +KolabMessageHelper::KolabMessageHelper(const Akonadi::Collection &col) + : mCollection(col) +{ + +} + +KolabMessageHelper::~KolabMessageHelper() +{ + +} + +Akonadi::Item KolabMessageHelper::createItemFromMessage(KMime::Message::Ptr message, + const qint64 uid, + const qint64 size, + const QList &attrs, + const QList &flags, + const KIMAP::FetchJob::FetchScope &scope, + bool &ok) const +{ + const Akonadi::Item item = MessageHelper::createItemFromMessage(message, uid, size, attrs, flags, scope, ok); + if (!ok) { + kWarning() << "Failed to read imap message"; + return item; + } + Kolab::FolderType folderType = Kolab::MailType; + if (mCollection.hasAttribute()) { + const QByteArray folderTypeString = KolabHelpers::getFolderTypeAnnotation(mCollection.attribute()->annotations()); + folderType = KolabHelpers::folderTypeFromString(folderTypeString); + } + return KolabHelpers::translateFromImap(folderType, item, ok); +} diff --git a/kdepim-runtime/resources/kolab/kolabmessagehelper.h b/kdepim-runtime/resources/kolab/kolabmessagehelper.h new file mode 100644 index 00000000..bdc64f92 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabmessagehelper.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 KOLABMESSAGEHELPER_H +#define KOLABMESSAGEHELPER_H + +#include +#include + +class KolabMessageHelper : public MessageHelper { +public: + explicit KolabMessageHelper(const Akonadi::Collection &collection); + virtual ~KolabMessageHelper(); + virtual Akonadi::Item createItemFromMessage(KMime::Message::Ptr message, + const qint64 uid, + const qint64 size, + const QList &attrs, + const QList &flags, + const KIMAP::FetchJob::FetchScope &scope, + bool &ok) const; + +private: + Akonadi::Collection mCollection; +}; + +#endif diff --git a/kdepim-runtime/resources/kolab/kolabresource.cpp b/kdepim-runtime/resources/kolab/kolabresource.cpp new file mode 100644 index 00000000..c1502e05 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabresource.cpp @@ -0,0 +1,180 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 "kolabresource.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kolabretrievecollectionstask.h" +#include "kolabresourcestate.h" +#include "kolabhelpers.h" + +KolabResource::KolabResource(const QString& id) + :ImapResource(id) +{ + //Load translations from imap resource + KGlobal::locale()->insertCatalog(QLatin1String("akonadi_imap_resource")); +} + +KolabResource::~KolabResource() +{ + +} + +QString KolabResource::defaultName() const +{ + return i18n("Kolab Resource"); +} + +ResourceStateInterface::Ptr KolabResource::createResourceState(const TaskArguments &args) +{ + return ResourceStateInterface::Ptr(new KolabResourceState(this, args)); +} + +void KolabResource::retrieveCollections() +{ + emit status(AgentBase::Running, i18nc("@info:status", "Retrieving folders")); + + setKeepLocalCollectionChanges(QSet() << "CONTENTMIMETYPES" << "AccessRights"); + startTask(new KolabRetrieveCollectionsTask(createResourceState(TaskArguments()), this)); +} + +void KolabResource::retrieveItems(const Akonadi::Collection &col) +{ + //The collection that we receive was fetched when the task was scheduled, it is therefore possible that it is outdated. + //We refetch the collection since we rely on up-to-date annotations. + //FIXME: because this is async and not part of the resourcetask, it can't be killed. ResourceBase should just provide an up-to date copy of the collection. + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, Akonadi::CollectionFetchJob::Base, this); + fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); + fetchJob->fetchScope().setIncludeStatistics(true); + fetchJob->fetchScope().setIncludeUnsubscribed(true); + connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onItemRetrievalCollectionFetchDone(KJob*))); +} + +void KolabResource::onItemRetrievalCollectionFetchDone(KJob *job) +{ + if (job->error()) { + kWarning() << "Failed to retrieve collection before RetrieveItemsTask: " << job->errorString(); + cancelTask(i18n("Failed to retrieve items.")); + return; + } + + Akonadi::CollectionFetchJob *fetchJob = static_cast(job); + Q_ASSERT(fetchJob->collections().size() == 1); + const Akonadi::Collection col = fetchJob->collections().first(); + + //This is the only part that differs form the imap resource: We make sure the annotations are up-to date before synchronizing + //HACK avoid infinite recursions, the metadatatask should be scheduled at most once per retrieveItemsJob + static QSet updatedCollections; + if (!updatedCollections.contains(col.id()) && + (!col.attribute() || + col.attribute()->timestamp() < QDateTime::currentDateTime().addSecs(-60).toTime_t())) { + updatedCollections.insert(col.id()); + synchronizeCollectionAttributes(col.id()); + deferTask(); + return; + } + updatedCollections.remove(col.id()); + + setItemStreamingEnabled(true); + + RetrieveItemsTask *task = new RetrieveItemsTask( createResourceState(TaskArguments(col)), this); + connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); + connect(this, SIGNAL(retrieveNextItemSyncBatch(int)), task, SLOT(onReadyForNextBatch(int))); + startTask(task); +} + +void KolabResource::itemAdded(const Akonadi::Item& item, const Akonadi::Collection& collection) +{ + bool ok = true; + const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok); + if (!ok) { + kWarning() << "Failed to convert item"; + cancelTask(); + return; + } + ImapResource::itemAdded(imapItem, collection); +} + +void KolabResource::itemChanged(const Akonadi::Item& item, const QSet< QByteArray >& parts) +{ + bool ok = true; + const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok); + if (!ok) { + kWarning() << "Failed to convert item"; + cancelTask(); + return; + } + ImapResource::itemChanged(imapItem, parts); +} + +void KolabResource::itemsMoved(const Akonadi::Item::List& items, const Akonadi::Collection& source, const Akonadi::Collection& destination) +{ + bool ok = true; + const Akonadi::Item::List imapItems = KolabHelpers::translateToImap(items, ok); + if (!ok) { + kWarning() << "Failed to convert item"; + cancelTask(); + return; + } + ImapResource::itemsMoved(imapItems, source, destination); +} + +static Akonadi::Collection updateAnnotations(const Akonadi::Collection &collection) +{ + //Set the annotations on new folders + const QByteArray kolabType = KolabHelpers::kolabTypeForMimeType(collection.contentMimeTypes()); + if (!kolabType.isEmpty()) { + Akonadi::Collection col = collection; + Akonadi::CollectionAnnotationsAttribute *attr = col.attribute(Akonadi::Collection::AddIfMissing); + QMap annotations = attr->annotations(); + KolabHelpers::setFolderTypeAnnotation(annotations, kolabType); + attr->setAnnotations(annotations); + return col; + } + return collection; +} + +void KolabResource::collectionAdded(const Akonadi::Collection& collection, const Akonadi::Collection& parent) +{ + //Set the annotations on new folders + const Akonadi::Collection col = updateAnnotations(collection); + //TODO we need to save the collections as well if the annotations have changed + //or we simply don't have the annotations locally, which perhaps is also not required? + ImapResource::collectionAdded(col, parent); +} + +void KolabResource::collectionChanged(const Akonadi::Collection& collection, const QSet< QByteArray >& parts) +{ + //Update annotations if necessary + const Akonadi::Collection col = updateAnnotations(collection); + //TODO we need to save the collections as well if the annotations have changed + ImapResource::collectionChanged(col, parts); +} + +AKONADI_RESOURCE_MAIN( KolabResource ) diff --git a/kdepim-runtime/resources/kolab/kolabresource.desktop b/kdepim-runtime/resources/kolab/kolabresource.desktop new file mode 100644 index 00000000..56583588 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabresource.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Name=Kolab Groupware Server +Name[bg]=Сървър Kolab Groupware +Name[bs]=Server kolaborativnog softvera +Name[ca]=Servidor de treball en grup Kolab +Name[ca@valencia]=Servidor de treball en grup Kolab +Name[cs]=Kolab Groupware server +Name[da]=Kolab groupware-server +Name[de]=Kolab Groupware-Server +Name[el]=ΕξυπηÏετητής Groupware Kolab +Name[en_GB]=Kolab Groupware Server +Name[es]=Servidor de trabajo en grupo Kolab +Name[et]=Kolabi grupitöö server +Name[fi]=Kolab-työryhmäpalvelin +Name[fr]=Serveur de logiciels de collaboration Kolab +Name[ga]=Freastalaí Groupware Kolab +Name[gl]=Servidor de Traballo en Grupo Kolab +Name[hu]=Kolab csoportmunka-kiszolgáló +Name[ia]=Servitor de Kolab Groupware +Name[it]=Server di groupware Kolab +Name[ja]=Kolab グループウェアサーム+Name[kk]=Kolab топтық Ñ–Ñ Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ Kolab Groupware +Name[ko]=Kolab 그룹웨어 서버 +Name[lt]=Kolab grupinio darbo serveris +Name[lv]=Kolab grupdarba serveris +Name[nb]=Kolab groupware-tjener +Name[nds]=Kolab-Arbeitkoppelserver +Name[nl]=Kolab groupwareserver +Name[nn]=Kolab Groupware-tenar +Name[pl]=Serwer Groupware Kolab +Name[pt]=Servidor de Groupware Kolab +Name[pt_BR]=Servidor groupware Kolab +Name[ro]=Server Kolab Groupware +Name[ru]=Сервер ÑовмеÑтной работы Kolab +Name[sk]=Groupware Server Kolab +Name[sl]=Strežnik za skupinsko delo Kolab +Name[sr]=Колабов групверÑки Ñервер +Name[sr@ijekavian]=Колабов групверÑки Ñервер +Name[sr@ijekavianlatin]=Kolabov grupverski server +Name[sr@latin]=Kolabov grupverski server +Name[sv]=Kolab grupprogramserver +Name[tr]=Kolab Groupware Sunucusu +Name[uk]=Сервер групової роботи Kolab +Name[x-test]=xxKolab Groupware Serverxx +Name[zh_CN]=Kolab 群件æœåŠ¡å™¨ +Name[zh_TW]=Kolab 群組伺æœå™¨ +Comment=Provides access to Kolab groupware folders and e-mail on a Kolab IMAP Server. +Comment[ca]=Proporciona accés a les carpetes i al correu electrònic del treball en grup Kolab en un servidor IMAP del Kolab. +Comment[da]=Giver adgang til Kolab groupware-mapper og e-mail pÃ¥ en Kolab IMAP-server. +Comment[de]=Ermöglicht den Zugriff auf Kolab-Groupware-Ordner auf einem Kolab-IMAP-Server. +Comment[en_GB]=Provides access to Kolab groupware folders and e-mail on a Kolab IMAP Server. +Comment[es]=Proporciona acceso a carpetas de trabajo en grupo de Kolab y correo en un servidor IMAP Kolab. +Comment[et]=Ligipääsu tagamine Kolabi grupitöökaustadele ja kirjadele Kolabi IMAP serveris. +Comment[fi]=Tarjoaa pääsyn Kolab-työryhmäkansioihin ja sähköpostiin Kolab IMAP-palvelimella. +Comment[fr]=Fournit l'accès aux dossiers et aux courriels du logiciel de collaboration Kolab sur un serveur IMAP. +Comment[hu]=Hozzáférést biztosít a Kolab csoportmunka-mappákhoz és e-mailekhez egy Kolab IMAP kiszolgálón. +Comment[it]=Fornisce l'accesso a cartelle e messaggi di posta di groupware Kolab su un server Kolab IMAP. +Comment[ko]=Kolab 그룹웨어 í´ë” ë° Kolab IMAP ì„œë²„ì— ì ‘ê·¼í•©ë‹ˆë‹¤ +Comment[nb]=Gir tilgang til Kolab gruppevaremapper og e-post pÃ¥ en KOLAB IMAP-tjener. +Comment[nds]=Stellt Togriep op Kolab-Arbeitkoppelornern un Nettpost op en Kolab-IMAP-Server praat. +Comment[nl]=Levert toegang tot Kolab groupwaremappen en e-mail op een Kolab IMAP-server. +Comment[pl]=Zapewnia dostÄ™p do katalogów do pracy grupowej Kolab i poczty elektronicznej na serwerze Kolab IMAP. +Comment[pt]=Oferece o acesso às pastas de 'groupware' e de e-mail num servidor IMAP do Kolab. +Comment[pt_BR]=Fornece acesso as pastas groupware Kolab em de e-mail em um servidor IMAP Kolab. +Comment[sk]=Poskytuje prístup k prieÄinkom Kolab groupware a e-mailu na Kolab IMAP serveri. +Comment[sr]=Омогућава приÑтуп Колабовим групверÑким фаÑциклама и е‑пошти на Колабовом ИМÐП Ñерверу. +Comment[sr@ijekavian]=Омогућава приÑтуп Колабовим групверÑким фаÑциклама и е‑пошти на Колабовом ИМÐП Ñерверу. +Comment[sr@ijekavianlatin]=Omogućava pristup Kolabovim grupverskim fasciklama i e‑poÅ¡ti na Kolabovom IMAP serveru. +Comment[sr@latin]=Omogućava pristup Kolabovim grupverskim fasciklama i e‑poÅ¡ti na Kolabovom IMAP serveru. +Comment[sv]=Ger tillgÃ¥ng till Kolab grupprogramkorgar och e-post pÃ¥ en Kolab IMAP-server. +Comment[uk]=Ðадає доÑтуп до тек групової роботи та електронної пошти на Ñерверів IMAP Kolab. +Comment[x-test]=xxProvides access to Kolab groupware folders and e-mail on a Kolab IMAP Server.xx +Comment[zh_TW]=æ供存å–æŸ Kolab IMAP 伺æœå™¨ä¸Šçš„ Kolab 群組資料夾與電å­éƒµä»¶ +Type=AkonadiResource +Exec=akonadi_kolab_resource +Icon=kolab + +X-Akonadi-MimeTypes=message/rfc822,text/directory,text/calendar,application/x-vnd.kde.contactgroup,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy,text/x-vnd.akonadi.note +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_kolab_resource diff --git a/kdepim-runtime/resources/kolab/kolabresource.h b/kdepim-runtime/resources/kolab/kolabresource.h new file mode 100644 index 00000000..9b7b7c8a --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabresource.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 KOLABRESOURCE_H +#define KOLABRESOURCE_H + +#include "imapresource.h" +#include + +class KolabResource : public ImapResource +{ + Q_OBJECT + + using Akonadi::AgentBase::Observer::collectionChanged; + +public: + explicit KolabResource(const QString &id); + ~KolabResource(); + +protected Q_SLOTS: + virtual void retrieveCollections(); + virtual void retrieveItems(const Akonadi::Collection& col); + +protected: + virtual ResourceStateInterface::Ptr createResourceState(const TaskArguments &); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemsMoved( const Akonadi::Item::List &item, const Akonadi::Collection &source, + const Akonadi::Collection &destination ); + //itemsRemoved and itemsFlags changed do not require translation, because they don't use the payload + + virtual void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent); + virtual void collectionChanged(const Akonadi::Collection &collection, const QSet &parts); + //collectionRemoved & collectionMoved do not require adjustments since they don't change the annotations + + virtual QString defaultName() const; + +private Q_SLOTS: + void onItemRetrievalCollectionFetchDone(KJob *job); +}; + +#endif diff --git a/kdepim-runtime/resources/kolab/kolabresourcestate.cpp b/kdepim-runtime/resources/kolab/kolabresourcestate.cpp new file mode 100644 index 00000000..00f0a462 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabresourcestate.cpp @@ -0,0 +1,82 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 "kolabresourcestate.h" +#include "kolabhelpers.h" +#include "kolabmessagehelper.h" + +#include + +#include +#include +#include +#include +#include + + +KolabResourceState::KolabResourceState(ImapResource* resource, const TaskArguments& arguments) + : ResourceState(resource, arguments) +{ + +} + +void KolabResourceState::collectionAttributesRetrieved(const Akonadi::Collection& collection) +{ + if (!collection.isValid() && collection.remoteId().isEmpty()) { + ResourceState::collectionAttributesRetrieved(collection); + return; + } + Akonadi::Collection col = collection; + if (col.attribute()) { + const QMap rawAnnotations = col.attribute()->annotations(); + const QByteArray type = KolabHelpers::getFolderTypeAnnotation(rawAnnotations); + const Kolab::FolderType folderType = KolabHelpers::folderTypeFromString(type); + col.setContentMimeTypes(KolabHelpers::getContentMimeTypes(folderType)); + + const QString icon = KolabHelpers::getIcon(folderType); + if (!icon.isEmpty()) { + kDebug() << " setting icon " << icon; + Akonadi::EntityDisplayAttribute *attr = col.attribute(Akonadi::Collection::AddIfMissing); + attr->setIconName(icon); + } + if (folderType != Kolab::MailType) { + //Groupware data always requires the full message, because it cannot translate without the body + Akonadi::CachePolicy cachePolicy = col.cachePolicy(); + QStringList localParts = cachePolicy.localParts(); + if (!localParts.contains(QLatin1String(Akonadi::MessagePart::Body))) { + localParts << QLatin1String(Akonadi::MessagePart::Body); + cachePolicy.setLocalParts(localParts); + cachePolicy.setCacheTimeout(-1); + cachePolicy.setInheritFromParent(false); + cachePolicy.setSyncOnDemand(true); + col.setCachePolicy(cachePolicy); + } + } + if (!KolabHelpers::isHandledType(folderType)) { + //If we don't handle the folder, make sure we don't download the messages + col.attribute(Akonadi::Entity::AddIfMissing); + } + } + ResourceState::collectionAttributesRetrieved(col); +} + +MessageHelper::Ptr KolabResourceState::messageHelper() const +{ + return MessageHelper::Ptr(new KolabMessageHelper(collection())); +} diff --git a/kdepim-runtime/resources/kolab/kolabresourcestate.h b/kdepim-runtime/resources/kolab/kolabresourcestate.h new file mode 100644 index 00000000..2d37f29b --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabresourcestate.h @@ -0,0 +1,37 @@ +/* + Copyright (c) 2014 Christian Mollekopf + + 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 KOLABRESOURCESTATE_H +#define KOLABRESOURCESTATE_H + +#include + +class ImapResource; + +class KolabResourceState : public ::ResourceState +{ +public: + explicit KolabResourceState(ImapResource* resource, const TaskArguments& arguments); + +private: + virtual void collectionAttributesRetrieved(const Akonadi::Collection& collection); + virtual MessageHelper::Ptr messageHelper() const; +}; + +#endif diff --git a/kdepim-runtime/resources/kolab/kolabretrievecollectionstask.cpp b/kdepim-runtime/resources/kolab/kolabretrievecollectionstask.cpp new file mode 100644 index 00000000..5594b8f4 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabretrievecollectionstask.cpp @@ -0,0 +1,249 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + Copyright (c) 2014 Christian Mollekopf + + 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 "kolabretrievecollectionstask.h" +#include "kolabhelpers.h" + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +KolabRetrieveCollectionsTask::KolabRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject* parent) + : ResourceTask(CancelIfNoSession, resource, parent), + mJobs(0) +{ + +} + +KolabRetrieveCollectionsTask::~KolabRetrieveCollectionsTask() +{ + +} + +void KolabRetrieveCollectionsTask::doStart(KIMAP::Session *session) +{ + Akonadi::Collection root; + root.setName(resourceName()); + root.setRemoteId(rootRemoteId()); + root.setContentMimeTypes(QStringList(Akonadi::Collection::mimeType())); + root.setParentCollection(Akonadi::Collection::root()); + root.addAttribute(new NoSelectAttribute(true)); + root.attribute(Akonadi::Collection::AddIfMissing)->setIconName(QLatin1String("kolab")); + + Akonadi::CachePolicy policy; + policy.setInheritFromParent(false); + policy.setSyncOnDemand(true); + + QStringList localParts; + localParts << QLatin1String(Akonadi::MessagePart::Envelope) + << QLatin1String(Akonadi::MessagePart::Header); + int cacheTimeout = 60; + + if (isDisconnectedModeEnabled()) { + // For disconnected mode we also cache the body + // and we keep all data indifinitely + localParts << QLatin1String(Akonadi::MessagePart::Body); + cacheTimeout = -1; + } + + policy.setLocalParts(localParts); + policy.setCacheTimeout(cacheTimeout); + policy.setIntervalCheckTime(intervalCheckTime()); + + root.setCachePolicy(policy); + + mMailCollections.insert(QString(), root); + + //jobs are serialized by the session + if (isSubscriptionEnabled()) { + KIMAP::ListJob *fullListJob = new KIMAP::ListJob(session); + fullListJob->setOption(KIMAP::ListJob::NoOption); + fullListJob->setQueriedNamespaces(serverNamespaces()); + connect( fullListJob, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onFullMailBoxesReceived(QList,QList >)) ); + connect( fullListJob, SIGNAL(result(KJob*)), SLOT(onFullMailBoxesReceiveDone(KJob*))); + mJobs++; + fullListJob->start(); + } + + KIMAP::ListJob *listJob = new KIMAP::ListJob(session); + listJob->setOption(KIMAP::ListJob::IncludeUnsubscribed); + listJob->setQueriedNamespaces(serverNamespaces()); + connect(listJob, SIGNAL(mailBoxesReceived(QList,QList >)), + this, SLOT(onMailBoxesReceived(QList,QList >))); + connect(listJob, SIGNAL(result(KJob*)), SLOT(onMailBoxesReceiveDone(KJob*))); + mJobs++; + listJob->start(); +} + +void KolabRetrieveCollectionsTask::onMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor > &descriptors, + const QList< QList > &flags) +{ + for (int i=0; i ¤tFlags) +{ + const QString separator = separatorCharacter(); + Q_ASSERT(separator.size() == 1); + const QString boxName = mailbox.endsWith( separator ) + ? mailbox.left( mailbox.size()-1 ) + : mailbox; + const QStringList pathParts = boxName.split( separator ); + const QString pathPart = pathParts.last(); + + Akonadi::Collection c; + //If we had a dummy collection we need to replace it + if (mMailCollections.contains(mailbox)) { + c = mMailCollections.value(mailbox); + } + c.setName( pathPart ); + c.setRemoteId( separator + pathPart ); + const QStringList parentPath = pathParts.mid(0, pathParts.size() - 1); + const Akonadi::Collection parentCollection = getOrCreateParent(parentPath.join(separator)); + c.setParentCollection(parentCollection); + //TODO get from ResourceState, and add KMime::Message::mimeType() for the normal imap resource by default + //We add a dummy mimetype, otherwise the itemsync doesn't even work (action is disabled and resourcebase aborts the operation) + c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType() << QLatin1String("application/x-kolab-objects")); + //assume LRS, until myrights is executed + if (serverCapabilities().contains(QLatin1String("ACL"))) { + c.setRights(Akonadi::Collection::ReadOnly); + } else { + c.setRights(Akonadi::Collection::AllRights); + } + + // If the folder is the Inbox, make some special settings. + if (pathParts.size() == 1 && pathPart.compare(QLatin1String("inbox") , Qt::CaseInsensitive) == 0) { + Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); + attr->setDisplayName(i18n("Inbox")); + attr->setIconName(QLatin1String("mail-folder-inbox")); + setIdleCollection(c); + } + + // If the folder is the user top-level folder, mark it as well, although it is not officially noted in the RFC + if (pathParts.size() == 1 && pathPart == QLatin1String("user") && currentFlags.contains("\\noselect")) { + Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); + attr->setDisplayName(i18n("Shared Folders")); + attr->setIconName(QLatin1String("x-mail-distribution-list")); + } + + // If this folder is a noselect folder, make some special settings. + if (currentFlags.contains("\\noselect")) { + c.addAttribute(new NoSelectAttribute(true)); + c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType()); + c.setRights( Akonadi::Collection::ReadOnly ); + } else { + // remove the noselect attribute explicitly, in case we had set it before (eg. for non-subscribed non-leaf folders) + c.removeAttribute(); + } + + // If this folder is a noinferiors folder, it is not allowed to create subfolders inside. + if (currentFlags.contains("\\noinferiors")) { + //kDebug() << "Noinferiors: " << currentPath; + c.addAttribute(new NoInferiorsAttribute(true)); + c.setRights(c.rights() & ~Akonadi::Collection::CanCreateCollection); + } + + kDebug() << "creating collection " << mailbox << " with parent " << parentPath; + mMailCollections.insert(mailbox, c); + //This is no longer required + mSubscribedMailboxes.remove(mailbox); +} + +void KolabRetrieveCollectionsTask::onMailBoxesReceiveDone(KJob* job) +{ + mJobs--; + if (job->error()) { + cancelTask(job->errorString()); + } else { + checkDone(); + } +} + +void KolabRetrieveCollectionsTask::checkDone() +{ + if (!mJobs) { + collectionsRetrieved(mMailCollections.values()); + } +} + +void KolabRetrieveCollectionsTask::onFullMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor >& descriptors, + const QList< QList< QByteArray > >& flags) +{ + Q_UNUSED(flags); + foreach (const KIMAP::MailBoxDescriptor &descriptor, descriptors) { + mSubscribedMailboxes.insert(descriptor.name); + } +} + +void KolabRetrieveCollectionsTask::onFullMailBoxesReceiveDone(KJob* job) +{ + mJobs--; + if (job->error()) { + cancelTask(job->errorString()); + } else { + checkDone(); + } +} diff --git a/kdepim-runtime/resources/kolab/kolabretrievecollectionstask.h b/kdepim-runtime/resources/kolab/kolabretrievecollectionstask.h new file mode 100644 index 00000000..3e9a14d0 --- /dev/null +++ b/kdepim-runtime/resources/kolab/kolabretrievecollectionstask.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + Copyright (c) 2014 Christian Mollekopf + + 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 KOLABRETRIEVECOLLECTIONSTASK_H +#define KOLABRETRIEVECOLLECTIONSTASK_H + +#include + +#include + +#include + +class KolabRetrieveCollectionsTask : public ResourceTask +{ + Q_OBJECT + +public: + explicit KolabRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0); + virtual ~KolabRetrieveCollectionsTask(); + +private slots: + void onMailBoxesReceived(const QList &descriptors, + const QList< QList > &flags); + void onMailBoxesReceiveDone(KJob *job); + void onFullMailBoxesReceived(const QList &descriptors, const QList > &flags); + void onFullMailBoxesReceiveDone(KJob *job); + +protected: + virtual void doStart(KIMAP::Session *session); + +private: + void checkDone(); + Akonadi::Collection getOrCreateParent(const QString &parentPath); + void createCollection(const QString &mailbox, const QList &flags); + + int mJobs; + QHash mMailCollections; + QSet mSubscribedMailboxes; +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/CMakeLists.txt b/kdepim-runtime/resources/kolabproxy/CMakeLists.txt new file mode 100644 index 00000000..9827ade7 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/CMakeLists.txt @@ -0,0 +1,108 @@ +project(kolabproxy) + +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ${Libkolab_INCLUDES} + ${Libkolabxml_INCLUDES} +) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS} -fPIC") + +########### next target ############### + +set(kolabproxyresource_shared_SRCS + kolabhandler.cpp + addressbookhandler.cpp + incidencehandler.cpp + calendarhandler.cpp + freebusyupdatehandler.cpp + taskshandler.cpp + journalhandler.cpp + notehandler.cpp + setupdefaultfoldersjob.cpp + kolabdefs.cpp + upgradejob.cpp + imapitemaddedjob.cpp + imapitemremovedjob.cpp + itemaddedjob.cpp + itemchangedjob.cpp + handlermanager.cpp + revertitemchangesjob.cpp +) + +set(kolabproxyresource_SRCS + kolabproxyresource.cpp + collectiontreebuilder.cpp + setupkolab.cpp + ${kolabproxyresource_shared_SRCS} + ${AKONADI_COLLECTIONATTRIBUTES_SHARED_SOURCES} +) + +qt4_add_dbus_adaptor(kolabproxyresource_SRCS org.freedesktop.Akonadi.kolabproxy.xml kolabproxyresource.h KolabProxyResource) + +install(FILES kolabproxyresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents") + +kde4_add_kcfg_files(kolabproxyresource_SRCS settings.kcfgc) + +kde4_add_ui_files(kolabproxyresource_SRCS kolabsettings.ui changeformat.ui) + +kcfg_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/kolabproxyresource.kcfg + org.kde.Akonadi.kolabproxy.Settings +) + +qt4_add_dbus_adaptor(kolabproxyresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.kolabproxy.Settings.xml + settings.h Settings +) + +if(RUNTIME_PLUGINS_STATIC) + add_definitions(-DRUNTIME_PLUGINS_STATIC) +endif() + +kde4_add_executable(akonadi_kolabproxy_resource ${kolabproxyresource_SRCS}) +install(FILES akonadi_kolabproxy_resource.notifyrc DESTINATION "${DATA_INSTALL_DIR}/akonadi_kolabproxy_resource" ) + +if(Q_WS_MAC) + set_target_properties(akonadi_kolabproxy_resource PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template + ) + set_target_properties(akonadi_kolabproxy_resource PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.kolabproxy" + ) + set_target_properties(akonadi_kolabproxy_resource PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Kolabproxy Resource" + ) +endif() + +target_link_libraries(akonadi_kolabproxy_resource + kdepim-copy + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_KCALCORE_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDE4_KIO_LIBS} + ${QT_QTDBUS_LIBRARY} + ${QT_QTXML_LIBRARY} + ${Libkolab_LIBRARIES} + ${Libkolabxml_LIBRARIES} +) + +if(RUNTIME_PLUGINS_STATIC) + target_link_libraries(akonadi_kolabproxy_resource + akonadi_serializer_addressee + akonadi_serializer_contactgroup + akonadi_serializer_kcalcore + akonadi_serializer_mail + ) +endif() + +install(TARGETS akonadi_kolabproxy_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) + +kde4_install_icons(${ICON_INSTALL_DIR}) + +add_subdirectory(tests) +add_subdirectory(wizard) diff --git a/kdepim-runtime/resources/kolabproxy/Messages.sh b/kdepim-runtime/resources/kolabproxy/Messages.sh new file mode 100644 index 00000000..59455c93 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_kolabproxy_resource.pot diff --git a/kdepim-runtime/resources/kolabproxy/addressbookhandler.cpp b/kdepim-runtime/resources/kolabproxy/addressbookhandler.cpp new file mode 100644 index 00000000..c5a10867 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/addressbookhandler.cpp @@ -0,0 +1,167 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2009 Kevin Krammer + Copyright (c) 2012 Christian Mollekopf + + 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 "addressbookhandler.h" +#include +#include + +AddressBookHandler::AddressBookHandler( const Akonadi::Collection &imapCollection ) + : KolabHandler( imapCollection ) +{ + m_mimeType = "application/x-vnd.kolab.contact"; +} + +AddressBookHandler::~AddressBookHandler() +{ +} + +Akonadi::Item::List AddressBookHandler::translateItems( const Akonadi::Item::List &items ) +{ + Akonadi::Item::List newItems; + Q_FOREACH ( const Akonadi::Item &item, items ) { + if ( !item.hasPayload() ) { + kWarning() << "Payload is not a MessagePtr!"; + continue; + } + const KMime::Message::Ptr payload = item.payload(); + Kolab::KolabObjectReader reader( payload ); + if ( reader.getType() == Kolab::ContactObject ) { + Akonadi::Item newItem( KABC::Addressee::mimeType() ); + newItem.setRemoteId( QString::number( item.id() ) ); + newItem.setPayload( reader.getContact() ); + newItems << newItem; + } else if ( reader.getType() == Kolab::DistlistObject ) { + KABC::ContactGroup contactGroup = reader.getDistlist(); + + QList toAdd; + for ( uint index = 0; index < contactGroup.contactReferenceCount(); ++index ) { + const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference( index ); + KABC::ContactGroup::ContactReference ref; + ref.setGid( reference.uid() ); //libkolab set a gid with setUid() + toAdd << ref; + } + contactGroup.removeAllContactReferences(); + foreach ( const KABC::ContactGroup::ContactReference &ref, toAdd ) { + contactGroup.append( ref ); + } + + Akonadi::Item newItem( KABC::ContactGroup::mimeType() ); + newItem.setRemoteId( QString::number( item.id() ) ); + newItem.setPayload( contactGroup ); + newItems << newItem; + } + if ( checkForErrors( item.id() ) && !newItems.isEmpty() ) { + newItems.removeLast(); //TODO: does this delete the item? + //rather set it to read-only (v2 never sets an error, + //so we should be safe for now). + } + } + + return newItems; +} + +bool AddressBookHandler::toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ) +{ + if ( item.hasPayload() ) { + const KABC::Addressee &addressee = item.payload(); + + const KMime::Message::Ptr &message = + Kolab::KolabObjectWriter::writeContact( addressee, m_formatVersion, PRODUCT_ID ); + + if ( checkForErrors( item.id() ) ) { + return false; + } + imapItem.setMimeType( QLatin1String("message/rfc822") ); + imapItem.setPayload( message ); + } else if ( item.hasPayload() ) { + KABC::ContactGroup contactGroup = item.payload(); + + // Replace all references with real data-sets + // Hopefully all resources are available during saving, so we can look up + // in the addressbook to get name+email from the UID. + // TODO proxy should at least know the addressees it created + QList toAdd; + for ( uint index = 0; index < contactGroup.contactReferenceCount(); ++index ) { + const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference( index ); + + QString gid; + if (!reference.gid().isEmpty()) { + gid = reference.gid(); + } else { + const Akonadi::Item item( reference.uid().toLongLong() ); + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( item ); + job->fetchScope().fetchFullPayload(); + if ( !job->exec() ) + continue; + + const Akonadi::Item::List items = job->items(); + if ( items.count() != 1 ) + continue; + + const KABC::Addressee addressee = job->items().first().payload(); + gid = addressee.uid(); + } + KABC::ContactGroup::ContactReference ref; + ref.setUid(gid); //libkolab expects a gid for uid() + toAdd << ref; + } + contactGroup.removeAllContactReferences(); + foreach ( const KABC::ContactGroup::ContactReference &ref, toAdd ) { + contactGroup.append( ref ); + } + + const KMime::Message::Ptr &message = + Kolab::KolabObjectWriter::writeDistlist( contactGroup, m_formatVersion, PRODUCT_ID ); + + if ( checkForErrors( item.id() ) ) { + return false; + } + imapItem.setMimeType( QLatin1String("message/rfc822") ); + imapItem.setPayload( message ); + } else { + kWarning() << "Payload is neither a KABC::Addressee nor KABC::ContactGroup!"; + return false; + } + return true; +} + +QStringList AddressBookHandler::contentMimeTypes() +{ + return QStringList() << KABC::Addressee::mimeType() + << KABC::ContactGroup::mimeType(); +} + +QString AddressBookHandler::iconName() const +{ + return QString::fromLatin1( "view-pim-contacts" ); +} + +QString AddressBookHandler::extractGid(const Akonadi::Item& kolabItem) +{ + if ( kolabItem.hasPayload() ) { + return kolabItem.payload().uid(); + } else if ( kolabItem.hasPayload() ) { + return kolabItem.payload().id(); + } + kWarning() << "invalid payload type!"; + return QString(); +} + diff --git a/kdepim-runtime/resources/kolabproxy/addressbookhandler.h b/kdepim-runtime/resources/kolabproxy/addressbookhandler.h new file mode 100644 index 00000000..3959acfb --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/addressbookhandler.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2009 Kevin Krammer + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_ADDRESSBOOKHANDLER_H +#define KOLABPROXY_ADDRESSBOOKHANDLER_H + +#include "kolabhandler.h" + +/** + @author Andras Mantia +*/ +class AddressBookHandler : public KolabHandler +{ + public: + explicit AddressBookHandler( const Akonadi::Collection &imapCollection ); + + virtual ~AddressBookHandler(); + + virtual Akonadi::Item::List translateItems( const Akonadi::Item::List &addrs ); + virtual bool toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ); + virtual QStringList contentMimeTypes(); + virtual QString iconName() const; + + virtual QString extractGid(const Akonadi::Item& kolabItem); +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/akonadi_kolabproxy_resource.notifyrc b/kdepim-runtime/resources/kolabproxy/akonadi_kolabproxy_resource.notifyrc new file mode 100644 index 00000000..7075dc6b --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/akonadi_kolabproxy_resource.notifyrc @@ -0,0 +1,112 @@ +[Global] +IconName=kolab +Comment=Kolab Proxy Resource Notifications +Comment[bs]=Notifikacija Kolab Proxy resursa +Comment[ca]=Notificacions del recurs d'intermediari del Kolab +Comment[ca@valencia]=Notificacions del recurs d'intermediari del Kolab +Comment[da]=Bekendtgørelser fra Kolab-proxy-ressource +Comment[de]=Benachrichtigungen der Proxy-Ressource für Kolab +Comment[el]=Ειδοποιήσεις πόÏου διαμεσολαβητή Kolab +Comment[en_GB]=Kolab Proxy Resource Notifications +Comment[es]=Notificaciones de recursos del proxy Kolab +Comment[et]=Kolabi puhverserveri ressursi märguanded +Comment[fi]=Kolab Proxy -resurssin ilmoitukset +Comment[fr]=Configuration des notifications Kolab +Comment[gl]=Notificacións do proxy de recurso de Kolab +Comment[hu]=Kolab proxy-erÅ‘forrás értesítések +Comment[ia]=Notificationes de ressource de proxy de Kolab +Comment[it]=Notifiche della risorsa proxy Kolab +Comment[ko]=Kolab 프ë¡ì‹œ ìžì› 알림 +Comment[lt]=Kolab įgaliotojo serverio resurso praneÅ¡imai +Comment[nb]=Kolab Proxy ressursvarslinger +Comment[nds]=Kolab-Proxy-Ressourcebescheden +Comment[nl]=Melding van Kolab-proxy-hulpbron +Comment[pl]=Powiadomienia zasobu poÅ›rednika Kolab +Comment[pt]=Configuração dos Recursos de 'Proxy' do Kolab +Comment[pt_BR]=Notificação dos recursos de proxy do Kolab +Comment[ru]=Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚ иÑточника данных Kolab +Comment[sk]=Upozornenia zdroja Kolab Proxy +Comment[sr]=Обавештења прокÑи реÑурÑа Колаба +Comment[sr@ijekavian]=Обавештења прокÑи реÑурÑа Колаба +Comment[sr@ijekavianlatin]=ObaveÅ¡tenja proksi resursa Kolaba +Comment[sr@latin]=ObaveÅ¡tenja proksi resursa Kolaba +Comment[sv]=Kolab-proxy resursunderrättelser +Comment[tr]=Kolab Vekil Sunucu Kaynak Bildirimleri +Comment[uk]=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ñ€ÐµÑурÑу прокÑÑ–-Ñервера Kolab +Comment[x-test]=xxKolab Proxy Resource Notificationsxx +Comment[zh_TW]=Kolab Proxy 資æºé€šçŸ¥ +Name=Kolab Resource +Name[bs]=Kolab resurs +Name[ca]=Recurs de Kolab +Name[ca@valencia]=Recurs de Kolab +Name[cs]=Zdroj Kolab +Name[da]=Kolab-ressource +Name[de]=Kolab-Ressource +Name[el]=ΠόÏος Kolab +Name[en_GB]=Kolab Resource +Name[es]=Recurso de Kolab +Name[et]=Kolabi ressurss +Name[fi]=Kolab-resurssi +Name[fr]=Ressource Kolab +Name[gl]=Recurso do Kolab +Name[hu]=Kolab erÅ‘forrás +Name[ia]=Ressource de Kolab +Name[it]=Risorsa Kolab +Name[ko]=Kolab ìžì› +Name[lt]=Kolab resursas +Name[nb]=Kolab-ressurs +Name[nds]=Kolab-Ressource +Name[nl]=Kolab-hulpbron +Name[pl]=Zasób Kolab +Name[pt]=Recurso do Kolab +Name[pt_BR]=Recurso do Kolab +Name[ru]=ИÑточник данных Kolab +Name[sk]=Kolab zdroj +Name[sr]=РеÑÑƒÑ€Ñ ÐšÐ¾Ð»Ð°Ð±Ð° +Name[sr@ijekavian]=РеÑÑƒÑ€Ñ ÐšÐ¾Ð»Ð°Ð±Ð° +Name[sr@ijekavianlatin]=Resurs Kolaba +Name[sr@latin]=Resurs Kolaba +Name[sv]=Kolab-resurs +Name[tr]=Kolab Kaynağı +Name[uk]=РеÑÑƒÑ€Ñ Kolab +Name[x-test]=xxKolab Resourcexx +Name[zh_TW]=Kolab è³‡æº + +[Event/Error] +Name=An error occurred +Name[bs]=Desila se greÅ¡ka +Name[ca]=S'ha produït un error +Name[ca@valencia]=S'ha produït un error +Name[cs]=Vyskytla se chyba +Name[da]=En fejl opstod +Name[de]=Es ist ein Fehler aufgetreten +Name[el]=Συνέβη ένα σφάλμα +Name[en_GB]=An error occurred +Name[es]=Ocurrió un error +Name[et]=Tekkis tõrge +Name[fi]=Tapahtui virhe +Name[fr]=Une erreur est survenue +Name[gl]=Produciuse un erro +Name[hu]=Hiba történt +Name[ia]=Un error occurreva +Name[it]=Si è verificato un errore +Name[ko]=오류가 ë°œìƒí•¨ +Name[lt]=Ä®vyko klaida +Name[nb]=Det oppsto en feil +Name[nds]=Dat geev en Fehler +Name[nl]=Er deed zich een fout voor +Name[pl]=WystÄ…piÅ‚ bÅ‚Ä…d +Name[pt]=Ocorreu um erro +Name[pt_BR]=Ocorreu um erro +Name[ru]=Произошла ошибка +Name[sk]=Vyskytla sa chyba +Name[sr]=Дошло је до грешке +Name[sr@ijekavian]=Дошло је до грешке +Name[sr@ijekavianlatin]=DoÅ¡lo je do greÅ¡ke +Name[sr@latin]=DoÅ¡lo je do greÅ¡ke +Name[sv]=Ett fel uppstod +Name[tr]=Bir hata oluÅŸtu +Name[uk]=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° +Name[x-test]=xxAn error occurredxx +Name[zh_TW]=發生錯誤 +Action=Popup diff --git a/kdepim-runtime/resources/kolabproxy/calendarhandler.cpp b/kdepim-runtime/resources/kolabproxy/calendarhandler.cpp new file mode 100644 index 00000000..ebf293f1 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/calendarhandler.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 "calendarhandler.h" + +CalendarHandler::CalendarHandler( const Akonadi::Collection &imapCollection ) + : IncidenceHandler( imapCollection ) +{ + m_mimeType = "application/x-vnd.kolab.event"; +} + +CalendarHandler::~CalendarHandler() +{ +} + +KMime::Message::Ptr CalendarHandler::incidenceToMime( const KCalCore::Incidence::Ptr &incidence ) +{ + return + Kolab::KolabObjectWriter::writeEvent( + incidence.dynamicCast(), + m_formatVersion, PRODUCT_ID, QLatin1String("UTC") ); +} + +QStringList CalendarHandler::contentMimeTypes() +{ + return QStringList() << KCalCore::Event::eventMimeType(); +} + +QString CalendarHandler::iconName() const +{ + return QString::fromLatin1( "view-calendar" ); +} diff --git a/kdepim-runtime/resources/kolabproxy/calendarhandler.h b/kdepim-runtime/resources/kolabproxy/calendarhandler.h new file mode 100644 index 00000000..6c715610 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/calendarhandler.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_CALENDARHANDLER_H +#define KOLABPROXY_CALENDARHANDLER_H + +#include "incidencehandler.h" + +/** + @author Andras Mantia +*/ +class CalendarHandler : public IncidenceHandler +{ + Q_OBJECT + public: + explicit CalendarHandler( const Akonadi::Collection &imapCollection ); + virtual ~CalendarHandler(); + virtual QStringList contentMimeTypes(); + virtual QString iconName() const; + + private: + virtual KMime::Message::Ptr incidenceToMime( const KCalCore::Incidence::Ptr &incidence ); + +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/changeformat.ui b/kdepim-runtime/resources/kolabproxy/changeformat.ui new file mode 100644 index 00000000..214bb7d6 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/changeformat.ui @@ -0,0 +1,133 @@ + + + ChangeFormatView + + + + 0 + 0 + 515 + 327 + + + + + QLayout::SetMinimumSize + + + QFormLayout::ExpandingFieldsGrow + + + + + Please select the format which should be used to write new Kolab objects according to your Kolab Groupware Server's version. You may close this window after selecting your version. + + + true + + + + + + + Kolab Format Version: + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + false + + + Change Format Version + + + false + + + + QFormLayout::ExpandingFieldsGrow + + + + + This allows you to upgrade your Kolab account to a different format version. This is normally not required and you should contact your administrator before doing so. + + + true + + + + + + + Status: + + + + + + + Not Started + + + + + + + 0 + + + + + + + Attention: Changing the format version will rewrite all Kolab objects. Make sure you have a backup of your data, and the account is fully synchronized and connected to the server. + + + true + + + + + + + Change Format Version + + + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/kolabproxy/collectiontreebuilder.cpp b/kdepim-runtime/resources/kolabproxy/collectiontreebuilder.cpp new file mode 100644 index 00000000..88d0678b --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/collectiontreebuilder.cpp @@ -0,0 +1,103 @@ +/* + Copyright (c) 2009 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 "collectiontreebuilder.h" + +#include + +CollectionTreeBuilder::CollectionTreeBuilder( KolabProxyResource *parent ) + : Job( parent ), + m_resource( parent ) +{ +} + +void CollectionTreeBuilder::doStart() +{ + Akonadi::CollectionFetchJob *job = + new Akonadi::CollectionFetchJob( Akonadi::Collection::root(), + Akonadi::CollectionFetchJob::Recursive, this ); + + connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), + SLOT(collectionsReceived(Akonadi::Collection::List)) ); + + connect( job, SIGNAL(result(KJob*)), SLOT(collectionFetchResult(KJob*)) ); +} + +void CollectionTreeBuilder::collectionsReceived( const Akonadi::Collection::List &collections ) +{ + foreach ( const Akonadi::Collection &collection, collections ) { + if ( collection.resource() == resource()->identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) { + continue; + } + resource()->updateHiddenAttribute( collection ); + if ( resource()->registerHandlerForCollection( collection ) ) { + m_kolabCollections.append( collection ); + } + m_allCollections.insert( collection.id(), collection ); + } +} + +Akonadi::Collection::List CollectionTreeBuilder::allCollections() const +{ + return m_resultCollections; +} + +Akonadi::Collection::List CollectionTreeBuilder::treeToList( + const QHash< Akonadi::Entity::Id, Akonadi::Collection::List > &tree, + const Akonadi::Collection ¤t ) +{ + Akonadi::Collection::List rv; + foreach ( const Akonadi::Collection &child, tree.value( current.id() ) ) { + rv += child; + rv += treeToList( tree, child ); + } + return rv; +} + +void CollectionTreeBuilder::collectionFetchResult( KJob *job ) +{ + Q_UNUSED( job ); + m_resultCollections.clear(); + + // step 1: build the minimal sub-tree that contains all Kolab collections + QHash remainingTree; + foreach ( const Akonadi::Collection &kolabCollection, m_kolabCollections ) { + Akonadi::Collection child = kolabCollection; + Akonadi::Collection::Id parentId = child.parentCollection().id(); + while ( child.isValid() && !remainingTree.value( parentId ).contains( child ) ) { + remainingTree[ parentId ].append( child ); + child = m_allCollections.value( parentId ); + parentId = child.parentCollection().id(); + } + } + + // step 2: path contraction + // TODO + + // step 3: flatten the tree and adjust root node + Akonadi::Collection::List collections = remainingTree.value( Akonadi::Collection::root().id() ); + foreach ( Akonadi::Collection topLevel, collections ) { //krazy:exclude=foreach + topLevel.setParentCollection( Akonadi::Collection::root() ); + m_resultCollections.append( topLevel ); + m_resultCollections += treeToList( remainingTree, topLevel ); + } + + emitResult(); // error handling already done by Akonadi::Job +} + diff --git a/kdepim-runtime/resources/kolabproxy/collectiontreebuilder.h b/kdepim-runtime/resources/kolabproxy/collectiontreebuilder.h new file mode 100644 index 00000000..4e951b3c --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/collectiontreebuilder.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2009 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 KOLABPROXY_COLLECTIONTREEBUILDER_H +#define KOLABPROXY_COLLECTIONTREEBUILDER_H + +#include "kolabproxyresource.h" + +#include + +class CollectionTreeBuilder : public Akonadi::Job +{ + Q_OBJECT + public: + explicit CollectionTreeBuilder( KolabProxyResource *parent = 0 ); + + Akonadi::Collection::List allCollections() const; + + protected: + virtual void doStart(); + + private: + inline KolabProxyResource *resource() const + { + return m_resource; + } + + static Akonadi::Collection::List treeToList( const QHash &tree, + const Akonadi::Collection ¤t ); + + private slots: + void collectionsReceived( const Akonadi::Collection::List &collections ); + void collectionFetchResult( KJob *job ); + + private: + KolabProxyResource *m_resource; + Akonadi::Collection::List m_resultCollections; + Akonadi::Collection::List m_kolabCollections; + QHash m_allCollections; +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.cpp b/kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.cpp new file mode 100644 index 00000000..ca3147cd --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.cpp @@ -0,0 +1,112 @@ +/* + Copyright (c) 2011 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. +*/ + +#include "freebusyupdatehandler.h" + +#include +#include +#include +#include + +#include + +FreeBusyUpdateHandler::FreeBusyUpdateHandler( QObject *parent ) + : QObject( parent ), mTimer( new QTimer( this ) ) +{ + mTimer->setInterval( 2000 ); + connect( mTimer, SIGNAL(timeout()), SLOT(timeout()) ); +} + +FreeBusyUpdateHandler::~FreeBusyUpdateHandler() +{ + mTimer->stop(); +} + +void FreeBusyUpdateHandler::updateFolder( const QString &folderPath, const QString &userName, + const QString &password, const QString &host ) +{ + QString path( folderPath ); + + /* Steffen said: you must issue an authenticated HTTP GET request to + https://kolabserver/freebusy/trigger/user@domain/Folder/NestedFolder.pfb + (replace .pfb with .xpfb for extended fb lists). */ + + KUrl httpUrl; + httpUrl.setUser( userName ); + httpUrl.setPassword( password ); + httpUrl.setHost( host ); + httpUrl.setProtocol( QLatin1String( "https" ) ); + + // IMAP path is either /INBOX/ or /user/someone/ + //FIXME this assumption is no longer true. Kolabfolders can also be toplevel. + if ( !path.startsWith( QLatin1Char('/') ) ) { + //The path separator can i.e. also be '.' on a different imap server + kWarning() << "Unsupported path separator"; + return; + } + const int secondSlash = path.indexOf( QLatin1Char('/'), 1 ); + if ( secondSlash == -1 ) { + kWarning() << "path is too short: " << path; + return; + } + + if ( path.startsWith( QLatin1String( "/INBOX/" ), Qt::CaseInsensitive ) ) { + // If INBOX, replace it with the username (which is user@domain) + path = path.mid( secondSlash ); + path.prepend( userName ); + } else { + // If user, just remove it. So we keep the IMAP-returned username. + // This assumes it's a known user on the same domain. + path = path.mid( secondSlash ); + } + + if ( path.startsWith( QLatin1Char('/') ) ) { + httpUrl.setPath( QLatin1String("/freebusy/trigger") + path + QLatin1String(".pfb") ); + } else { + httpUrl.setPath( QLatin1String("/freebusy/trigger/") + path + QLatin1String(".pfb") ); + } + + mUrls.insert( httpUrl ); + mTimer->start(); +} + +void FreeBusyUpdateHandler::timeout() +{ + foreach ( const KUrl &url, mUrls ) { + kDebug() << "Triggering PFB update for " << url; + + KIO::Job *job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo ); + // we want an error in case of 404 + job->addMetaData( QLatin1String( "errorPage" ), QLatin1String( "false" ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(slotFreeBusyTriggerResult(KJob*)) ); + } + + mUrls.clear(); +} + +void FreeBusyUpdateHandler::slotFreeBusyTriggerResult( KJob *job ) +{ + if ( job->error() ) { + KUrl url( job->errorText() ); + KPassivePopup::message( + i18n( "Could not trigger Free/Busy information update: %1.", url.prettyUrl() ), + (QWidget*)0 ); + } +} + diff --git a/kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.h b/kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.h new file mode 100644 index 00000000..651f0f89 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/freebusyupdatehandler.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2011 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. +*/ + +#ifndef KOLABPROXY_FREEBUSYUPDATEHANDLER_H +#define KOLABPROXY_FREEBUSYUPDATEHANDLER_H + +#include +#include + +class KJob; +class KUrl; + +class QTimer; + +/** + * A class that triggers the update of freebusy lists on the Kolab server + * whenever a calendar IMAP folder has been changed. + */ +class FreeBusyUpdateHandler : public QObject +{ + Q_OBJECT + + public: + explicit FreeBusyUpdateHandler( QObject *parent = 0 ); + ~FreeBusyUpdateHandler(); + + void updateFolder( const QString &path, const QString &userName, + const QString &password, const QString &host ); + + private Q_SLOTS: + void timeout(); + void slotFreeBusyTriggerResult( KJob * ); + + private: + QSet mUrls; + QTimer *mTimer; +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/handlermanager.cpp b/kdepim-runtime/resources/kolabproxy/handlermanager.cpp new file mode 100644 index 00000000..3513c3ec --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/handlermanager.cpp @@ -0,0 +1,83 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 "handlermanager.h" +#include + +Kolab::FolderType HandlerManager::getFolderType( const Akonadi::Collection& collection ) +{ + if (Akonadi::CollectionAnnotationsAttribute *attr = collection.attribute() ) { + return Kolab::folderTypeFromString( Kolab::getFolderTypeAnnotation(attr->annotations())); + } + return Kolab::MailType; +} + +bool HandlerManager::isKolabFolder(const Akonadi::Collection &collection) +{ + return (getFolderType(collection) != Kolab::MailType); +} + +bool HandlerManager::isHandledKolabFolder(const Akonadi::Collection& collection) +{ + return KolabHandler::hasHandler(getFolderType(collection)); +} + +KolabHandler::Ptr HandlerManager::getHandler(Akonadi::Entity::Id collectionId) +{ + return mMonitoredCollections.value(collectionId); +} + +QString HandlerManager::imapResourceForCollection( Akonadi::Collection::Id id ) +{ + return mResourceIdentifiers.value(id); +} + +bool HandlerManager::registerHandlerForCollection( const Akonadi::Collection &imapCollection, Kolab::Version version ) +{ + if (isHandledKolabFolder(imapCollection)) { + KolabHandler::Ptr handler = + KolabHandler::createHandler(getFolderType(imapCollection), imapCollection); + if (handler) { + handler->setKolabFormatVersion(version); + mMonitoredCollections.insert(imapCollection.id(), handler); + mResourceIdentifiers.insert(imapCollection.id(), imapCollection.resource()); + return true; + } + } + return false; +} + +void HandlerManager::removeFolder(Akonadi::Entity::Id imapCollection) +{ + mMonitoredCollections.remove(imapCollection); +} + +bool HandlerManager::isMonitored(Akonadi::Entity::Id imapCollection) const +{ + return mMonitoredCollections.contains(imapCollection); +} + +QList< Akonadi::Entity::Id > HandlerManager::monitoredCollections() const +{ + return mMonitoredCollections.keys(); +} + +void HandlerManager::clear() +{ + mMonitoredCollections.clear(); +} diff --git a/kdepim-runtime/resources/kolabproxy/handlermanager.h b/kdepim-runtime/resources/kolabproxy/handlermanager.h new file mode 100644 index 00000000..6735f584 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/handlermanager.h @@ -0,0 +1,57 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 HANDLERMANAGER_H +#define HANDLERMANAGER_H + +#include +#include +#include "kolabhandler.h" + +class HandlerManager +{ +public: + bool isMonitored(Akonadi::Collection::Id) const; + KolabHandler::Ptr getHandler(Akonadi::Collection::Id); + + /** + * Creates a new KolabHandler for @p imapCollection given it actually is + * a Kolab folder. + * + * @return @c true if @p imapCollection is a Kolab folder, @c false otherwise. + */ + bool registerHandlerForCollection(const Akonadi::Collection &imapCollection, Kolab::Version version); + + QString imapResourceForCollection(Akonadi::Collection::Id id); + + void removeFolder(Akonadi::Collection::Id imapCollection); + + QList monitoredCollections() const; + + void clear(); + + static bool isKolabFolder( const Akonadi::Collection &collection ); + static bool isHandledKolabFolder( const Akonadi::Collection &collection ); + static Kolab::FolderType getFolderType( const Akonadi::Collection &collection ); +private: + QMap mMonitoredCollections; + QMap mResourceIdentifiers; + +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolabproxy/hi64-apps-kolab.png b/kdepim-runtime/resources/kolabproxy/hi64-apps-kolab.png new file mode 100644 index 0000000000000000000000000000000000000000..f7edd6c01f504d70d06b6457112c3267b34b237b GIT binary patch literal 5496 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 + + 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 "imapitemaddedjob.h" +#include +#include +#include +#include +#include + +ImapItemAddedJob::ImapItemAddedJob(const Akonadi::Item &imapItem, const Akonadi::Collection &imapCollection, KolabHandler &handler, QObject* parent) + :KJob(parent), + mHandler(handler), + mImapItem(imapItem), + mKolabCollection(imapToKolab(imapCollection)), + mImapCollection(imapCollection) +{ + +} + +void ImapItemAddedJob::start() +{ + //TODO: slow, would be nice if ItemCreateJob would work with a Collection + // having only the remoteId set + Akonadi::CollectionFetchJob *job = + new Akonadi::CollectionFetchJob(mKolabCollection, Akonadi::CollectionFetchJob::Base, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(onCollectionFetchDone(KJob*)) ); +} + +void ImapItemAddedJob::onCollectionFetchDone( KJob *job ) +{ + if ( job->error() ) { + kWarning( ) << "Error on collection fetch:" << job->errorText(); + return; + } + Akonadi::Collection::List collections = + qobject_cast(job)->collections(); + Q_ASSERT(collections.size() == 1); + mKolabCollection = collections.first(); + + const Akonadi::Item::List newItems = mHandler.translateItems(Akonadi::Item::List() << mImapItem); + if (newItems.isEmpty()) { + setError(KJob::UserDefinedError); + setErrorText("Failed to translate item"); + emitResult(); + return; + } + mTranslatedItem = newItems.first(); + + const QString &gid = mHandler.extractGid(mTranslatedItem); + if (!gid.isEmpty()) { + //We have a gid, let's see if this item already exists + Akonadi::Item item; + item.setGid(gid); + Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(item); + itemFetchJob->fetchScope().fetchFullPayload(false); + itemFetchJob->fetchScope().setFetchModificationTime(false); + itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); + connect(itemFetchJob, SIGNAL(result(KJob*)), this, SLOT(onItemFetchJobDone(KJob*))); + } else { + kDebug() << "no gid, creating directly"; + Akonadi::ItemCreateJob *cjob = new Akonadi::ItemCreateJob(mTranslatedItem, mKolabCollection); + connect( cjob, SIGNAL(result(KJob*)), this, SLOT(itemCreatedDone(KJob*)) ); + } + } + + Akonadi::Item ImapItemAddedJob::getTranslatedItem() + { + return Akonadi::Item(); + } + + void ImapItemAddedJob::onItemFetchJobDone(KJob *job) + { + Akonadi::ItemFetchJob * const fetchJob = static_cast(job); + + Akonadi::Item::List conflictingItems; + foreach (const Akonadi::Item &item, fetchJob->items()) { + Q_ASSERT(item.parentCollection().isValid()); + //The same object may be in other collection, just not in this one. + if (item.parentCollection().id() != mKolabCollection.id()) { + continue; + } + conflictingItems << item; + } + if (conflictingItems.size() > 1) { + kWarning() << "Multiple conflicting items detected in col " << mKolabCollection.id() << ", this should never happen: "; + foreach (const Akonadi::Item &item, conflictingItems) { + kWarning() << "Conflicting kolab item: " << item.id(); + } + } + if (!conflictingItems.isEmpty()) { + //This is a conflict + const Akonadi::Item conflictingKolabItem = conflictingItems.first(); + mTranslatedItem.setId(conflictingKolabItem.id()); + imapToKolab(mImapItem, mTranslatedItem); + //TODO ensure the modifyjob doesn't collide with a removejob due to the original imap item vanishing. + kDebug() << "conflict, modifying existing item: " << conflictingKolabItem.id(); + Akonadi::ItemModifyJob *modJob = new Akonadi::ItemModifyJob(mTranslatedItem, this); + modJob->disableRevisionCheck(); + connect(modJob, SIGNAL(result(KJob*)), this, SLOT(itemCreatedDone(KJob*))); + } else { + kDebug() << "creating new item"; + Akonadi::ItemCreateJob *cjob = new Akonadi::ItemCreateJob(mTranslatedItem, mKolabCollection, this); + connect(cjob, SIGNAL(result(KJob*)), this, SLOT(itemCreatedDone(KJob*))); + } +} + +void ImapItemAddedJob::itemCreatedDone(KJob *job) +{ + if (job->error()) { + kWarning() << "Error on creating item:" << job->errorText(); + kWarning() << "imap item: " << mImapItem.id() << " in collection " << mImapCollection.id(); + } + emitResult(); +} diff --git a/kdepim-runtime/resources/kolabproxy/imapitemaddedjob.h b/kdepim-runtime/resources/kolabproxy/imapitemaddedjob.h new file mode 100644 index 00000000..b9c23e2c --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/imapitemaddedjob.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 IMAPITEMADDEDJOB_H +#define IMAPITEMADDEDJOB_H +#include +#include "kolabhandler.h" + +class ImapItemAddedJob: public KJob +{ + Q_OBJECT +public: + ImapItemAddedJob(const Akonadi::Item &imapItem, const Akonadi::Collection &imapCollection, KolabHandler &handler, QObject* parent); + virtual void start(); +private slots: + void onItemFetchJobDone(KJob *job); + void onCollectionFetchDone(KJob *job); + void itemCreatedDone(KJob *job); +private: + Akonadi::Item getTranslatedItem(); + KolabHandler &mHandler; + Akonadi::Item mImapItem; + Akonadi::Item mTranslatedItem; + Akonadi::Collection mKolabCollection; + const Akonadi::Collection mImapCollection; + +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolabproxy/imapitemremovedjob.cpp b/kdepim-runtime/resources/kolabproxy/imapitemremovedjob.cpp new file mode 100644 index 00000000..15109efc --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/imapitemremovedjob.cpp @@ -0,0 +1,72 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 "imapitemremovedjob.h" +#include +#include +#include +#include +#include +#include + +ImapItemRemovedJob::ImapItemRemovedJob(const Akonadi::Item& imapItem, QObject* parent) + :KJob(parent), + mImapItem(imapItem), + mKolabItem(imapToKolab(imapItem)) +{ + +} + +void ImapItemRemovedJob::start() +{ + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(mKolabItem, this); + fetchJob->fetchScope().setFetchRemoteIdentification(true); + fetchJob->fetchScope().setCacheOnly(true); //don't access the imap backend again + connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onItemFetchJobDone(KJob*))); +} + +void ImapItemRemovedJob::onItemFetchJobDone(KJob* job) +{ + //This fetch job can fail if the kolab item has already been removed, so we ignore errors + Akonadi::ItemFetchJob *fetchJob = static_cast(job); + Q_ASSERT(fetchJob->items().size() <= 1); + if (fetchJob->items().isEmpty()) { + emitResult(); + return; + } + const Akonadi::Item kolabItem = fetchJob->items().first(); + //Check if the currently relevant imap item was removed or just an outdated copy + if (kolabItem.remoteId().toLongLong() == mImapItem.id()) { + Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(kolabItem, this ); + connect(deleteJob, SIGNAL(result(KJob*)), this, SLOT(onDeleteDone(KJob*))); + } else { + emitResult(); + } +} + +void ImapItemRemovedJob::onDeleteDone(KJob* job) +{ + if (job->error()) { + kWarning() << "Delete job failed" << job->errorString(); + } + emitResult(); +} + + + + diff --git a/kdepim-runtime/resources/kolabproxy/imapitemremovedjob.h b/kdepim-runtime/resources/kolabproxy/imapitemremovedjob.h new file mode 100644 index 00000000..470abcf8 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/imapitemremovedjob.h @@ -0,0 +1,39 @@ + +/* + Copyright (c) 2013 Christian Mollekopf + + 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 IMAPITEMREMOVEDJOB_H +#define IMAPITEMREMOVEDJOB_H +#include +#include "kolabhandler.h" + +class ImapItemRemovedJob: public KJob +{ + Q_OBJECT +public: + ImapItemRemovedJob(const Akonadi::Item &imapItem, QObject* parent); + virtual void start(); +private slots: + void onItemFetchJobDone(KJob *job); + void onDeleteDone(KJob *job); +private: + const Akonadi::Item mImapItem; + const Akonadi::Item mKolabItem; +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolabproxy/incidencehandler.cpp b/kdepim-runtime/resources/kolabproxy/incidencehandler.cpp new file mode 100644 index 00000000..bc7f210a --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/incidencehandler.cpp @@ -0,0 +1,97 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 "incidencehandler.h" + +#include +#include + +#include + +#include + +IncidenceHandler::IncidenceHandler( const Akonadi::Collection &imapCollection ) + : KolabHandler( imapCollection ) +{ +} + +IncidenceHandler::~IncidenceHandler() +{ +} + +Akonadi::Item::List IncidenceHandler::translateItems( const Akonadi::Item::List &items ) +{ + Akonadi::Item::List newItems; + Q_FOREACH ( const Akonadi::Item &item, items ) { + if ( !item.hasPayload() ) { + kWarning() << "Payload is not a MessagePtr!"; + continue; + } + const KMime::Message::Ptr payload = item.payload(); + + KCalCore::Incidence::Ptr incidencePtr = Kolab::KolabObjectReader(payload).getIncidence(); + if ( checkForErrors( item.id() ) ) { + continue; + } + if ( !incidencePtr ) { + kWarning() << "Failed to read incidence."; + continue; + } + Akonadi::Item newItem( incidencePtr->mimeType() ); + newItem.setPayload( incidencePtr ); + newItem.setRemoteId( QString::number( item.id() ) ); + newItems << newItem; + } + + return newItems; +} + +bool IncidenceHandler::toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ) +{ + if ( !item.hasPayload() ) { + kWarning() << "item is not an incidence"; + return false; + } + KCalCore::Incidence::Ptr incidencePtr = item.payload(); + if ( !incidencePtr ) { + kWarning() << "invalid incidence"; + return false; + } + + const KMime::Message::Ptr &message = incidenceToMime( incidencePtr ); + imapItem.setMimeType( QLatin1String("message/rfc822") ); + imapItem.setPayload( message ); + + if ( checkForErrors( item.id() ) ) { + return false; + } + return true; +} + +QString IncidenceHandler::extractGid(const Akonadi::Item& kolabItem) +{ + if ( !kolabItem.hasPayload() ) { + kWarning() << "Invalid payload!"; + return QString(); + } + return kolabItem.payload()->instanceIdentifier(); +} + diff --git a/kdepim-runtime/resources/kolabproxy/incidencehandler.h b/kdepim-runtime/resources/kolabproxy/incidencehandler.h new file mode 100644 index 00000000..cb56d610 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/incidencehandler.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_INCIDENCEHANDLER_H +#define KOLABPROXY_INCIDENCEHANDLER_H + +#include "kolabhandler.h" + +#include + +/** + @author Andras Mantia +*/ +class IncidenceHandler : public KolabHandler +{ + Q_OBJECT + public: + explicit IncidenceHandler( const Akonadi::Collection &imapCollection ); + + virtual ~IncidenceHandler(); + + virtual Akonadi::Item::List translateItems( const Akonadi::Item::List &addrs ); + virtual bool toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ); + + virtual QString extractGid(const Akonadi::Item& imapItem); + + protected: + virtual KMime::Message::Ptr incidenceToMime( const KCalCore::Incidence::Ptr &incidence ) = 0; +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/itemaddedjob.cpp b/kdepim-runtime/resources/kolabproxy/itemaddedjob.cpp new file mode 100644 index 00000000..85f57976 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/itemaddedjob.cpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 "itemaddedjob.h" +#include +#include +#include + +ItemAddedJob::ItemAddedJob(const Akonadi::Item& kolabItem, const Akonadi::Collection& col, KolabHandler& handler, QObject* parent) + :KJob(parent), + mHandler(handler), + mKolabItem(kolabItem), + mParentCollection(col) +{ + +} + +void ItemAddedJob::start() +{ + QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); +} + +void ItemAddedJob::doStart() +{ + const Akonadi::Collection imapCollection = kolabToImap(mParentCollection); + kDebug() << imapCollection.id(); + Akonadi::Item imapItem( "message/rfc822" ); + if (!mHandler.toKolabFormat(mKolabItem, imapItem)) { + kWarning() << "Failed to convert item to kolab format: " << mKolabItem.id(); + setError(KJob::UserDefinedError); + setErrorText(i18n("Failed to convert item %1 to kolab format", mKolabItem.id())); + emitResult(); + return; + } + //FIXME remove + imapItem.setFlag(Akonadi::MessageFlags::Seen); + + Akonadi::ItemCreateJob *cjob = new Akonadi::ItemCreateJob(imapItem, imapCollection); + connect(cjob, SIGNAL(result(KJob*)), SLOT(onItemCreatedDone(KJob*))); +} + +void ItemAddedJob::onItemCreatedDone(KJob* job) +{ + if (job->error()) { + setError(KJob::UserDefinedError); + } else { + Akonadi::ItemCreateJob *cjob = static_cast(job); + mImapItem = cjob->item(); + } + emitResult(); +} + +Akonadi::Item ItemAddedJob::kolabItem() const +{ + return mKolabItem; +} + +Akonadi::Item ItemAddedJob::imapItem() const +{ + return mImapItem; +} diff --git a/kdepim-runtime/resources/kolabproxy/itemaddedjob.h b/kdepim-runtime/resources/kolabproxy/itemaddedjob.h new file mode 100644 index 00000000..997892fa --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/itemaddedjob.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 ITEMADDEDJOB_H +#define ITEMADDEDJOB_H +#include +#include "kolabhandler.h" + +class ItemAddedJob: public KJob +{ + Q_OBJECT +public: + ItemAddedJob(const Akonadi::Item &kolabItem, const Akonadi::Collection &col, KolabHandler &handler, QObject* parent); + virtual void start(); + Akonadi::Item kolabItem() const; + Akonadi::Item imapItem() const; +private slots: + void doStart(); + void onItemCreatedDone(KJob *job); +private: + KolabHandler &mHandler; + const Akonadi::Item mKolabItem; + Akonadi::Item mImapItem; + const Akonadi::Collection mParentCollection; +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolabproxy/itemchangedjob.cpp b/kdepim-runtime/resources/kolabproxy/itemchangedjob.cpp new file mode 100644 index 00000000..66427b0a --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/itemchangedjob.cpp @@ -0,0 +1,136 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 "itemchangedjob.h" +#include "itemaddedjob.h" +#include +#include +#include +#include + +ItemChangedJob::ItemChangedJob(const Akonadi::Item& kolabItem, HandlerManager& handler, QObject* parent) +: KJob(parent), + mHandlerManager(handler), + mKolabItem(kolabItem) +{ + +} + +void ItemChangedJob::start() +{ + Akonadi::CollectionFetchJob *collectionFetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection(mKolabItem.storageCollectionId()), Akonadi::CollectionFetchJob::Base); + connect(collectionFetchJob, SIGNAL(result(KJob*)), this, SLOT(onKolabCollectionFetched(KJob*))); +} + +void ItemChangedJob::onKolabCollectionFetched(KJob* job) +{ + Akonadi::CollectionFetchJob *fetchjob = static_cast(job); + if (job->error() || fetchjob->collections().isEmpty()) { + kWarning() << "collection fetch job failed " << job->errorString() << fetchjob->collections().isEmpty(); + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + emitResult(); + return; + } + const Akonadi::Collection imapCollection = kolabToImap(fetchjob->collections().first()); + mHandler = mHandlerManager.getHandler(imapCollection.id()); + if (!mHandler) { + kWarning() << "Couldn't find a handler for the collection, but we should have one: " << imapCollection.id(); + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + emitResult(); + return; + } + + Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(kolabToImap(mKolabItem), this); + connect(itemFetchJob, SIGNAL(result(KJob*)), SLOT(onImapItemFetchDone(KJob*))); +} + +void ItemChangedJob::onImapItemFetchDone(KJob* job) +{ + if ( job->error() ) { + kWarning() << job->error() << job->errorString(); + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + emitResult(); + return; + } + + Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); + if (fetchJob->items().isEmpty()) { //The corresponding imap item hasn't been created yet + kDebug() << "item is not yet created in imap resource"; + Akonadi::CollectionFetchJob *fetch = + new Akonadi::CollectionFetchJob( Akonadi::Collection( mKolabItem.storageCollectionId() ), + Akonadi::CollectionFetchJob::Base, this ); + connect( fetch, SIGNAL(result(KJob*)), SLOT(onCollectionFetchDone(KJob*)) ); + } else { + kDebug() << "item is in imap resource"; + Akonadi::Item imapItem = fetchJob->items().first(); + + if (!mHandler->toKolabFormat(mKolabItem, imapItem)) { + kWarning() << "Failed to convert item to kolab format: " << mKolabItem.id(); + setError(KJob::UserDefinedError); + setErrorText(i18n("Failed to convert item %1 to kolab format", mKolabItem.id())); + emitResult(); + return; + } + Akonadi::ItemModifyJob *mjob = new Akonadi::ItemModifyJob(imapItem); + connect(mjob, SIGNAL(result(KJob*)), SLOT(onItemModifyDone(KJob*))); + } +} + +void ItemChangedJob::onCollectionFetchDone(KJob *job) +{ + Akonadi::CollectionFetchJob *fetchJob = static_cast(job); + if ( job->error() || fetchJob->collections().isEmpty() ) { + kWarning() << "Collection fetch job failed" << fetchJob->errorString(); + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + emitResult(); + return; + } + + const Akonadi::Collection kolabCollection = fetchJob->collections().first(); + ItemAddedJob *itemAddedJob = new ItemAddedJob(mKolabItem, kolabCollection, *mHandler, this); + connect(itemAddedJob, SIGNAL(result(KJob*)), SLOT(onItemAddedDone(KJob*)) ); + itemAddedJob->start(); +} + +void ItemChangedJob::onItemAddedDone(KJob* job) +{ + if (job->error()) { + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + } + emitResult(); +} + +void ItemChangedJob::onItemModifyDone(KJob *job) +{ + if (job->error()) { + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + } + emitResult(); +} + +Akonadi::Item ItemChangedJob::item() const +{ + return mKolabItem; +} diff --git a/kdepim-runtime/resources/kolabproxy/itemchangedjob.h b/kdepim-runtime/resources/kolabproxy/itemchangedjob.h new file mode 100644 index 00000000..461fa5a5 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/itemchangedjob.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 ITEMCHANGEDJOB_H +#define ITEMCHANGEDJOB_H +#include +#include "kolabhandler.h" +#include "handlermanager.h" + +class ItemChangedJob: public KJob +{ + Q_OBJECT +public: + ItemChangedJob(const Akonadi::Item &kolabItem, HandlerManager &handler, QObject* parent); + virtual void start(); + Akonadi::Item item() const; +private slots: + void onKolabCollectionFetched(KJob* job); + void onImapItemFetchDone(KJob *job); + void onCollectionFetchDone(KJob *job); + void onItemAddedDone(KJob *job); + void onItemModifyDone(KJob *job); +private: + HandlerManager &mHandlerManager; + KolabHandler::Ptr mHandler; + const Akonadi::Item mKolabItem; +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolabproxy/journalhandler.cpp b/kdepim-runtime/resources/kolabproxy/journalhandler.cpp new file mode 100644 index 00000000..799fc898 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/journalhandler.cpp @@ -0,0 +1,50 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 "journalhandler.h" + +JournalHandler::JournalHandler( const Akonadi::Collection &imapCollection ) + : IncidenceHandler( imapCollection ) +{ + m_mimeType = "application/x-vnd.kolab.journal"; +} + +JournalHandler::~JournalHandler() +{ +} + +KMime::Message::Ptr JournalHandler::incidenceToMime( const KCalCore::Incidence::Ptr &incidence ) +{ + return + Kolab::KolabObjectWriter::writeJournal( + incidence.dynamicCast(), + m_formatVersion, PRODUCT_ID, QLatin1String("UTC") ); +} + +QStringList JournalHandler::contentMimeTypes() +{ + return QStringList() << KCalCore::Journal::journalMimeType(); +} + +QString JournalHandler::iconName() const +{ + return QString::fromLatin1( "view-pim-journal" ); +} diff --git a/kdepim-runtime/resources/kolabproxy/journalhandler.h b/kdepim-runtime/resources/kolabproxy/journalhandler.h new file mode 100644 index 00000000..7ddd8151 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/journalhandler.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2009 Klar?lvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_JOURNALHANDLER_H +#define KOLABPROXY_JOURNALHANDLER_H + +#include "incidencehandler.h" + +/** + @author Andras Mantia +*/ +class JournalHandler : public IncidenceHandler +{ + public: + explicit JournalHandler( const Akonadi::Collection &imapCollection ); + virtual ~JournalHandler(); + + virtual QStringList contentMimeTypes(); + virtual QString iconName() const; + + private: + virtual KMime::Message::Ptr incidenceToMime( const KCalCore::Incidence::Ptr &incidence ); +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/kolabdefs.cpp b/kdepim-runtime/resources/kolabproxy/kolabdefs.cpp new file mode 100644 index 00000000..cc019997 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabdefs.cpp @@ -0,0 +1,117 @@ +/* + Copyright (c) 2010 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 "kolabdefs.h" + +#include + +#include + +using namespace KolabV2; + +static const struct { + const char *name; + const char *label; +} folderTypeData[] = { + { KOLAB_FOLDER_TYPE_MAIL, "" }, + { KOLAB_FOLDER_TYPE_CONTACT, I18N_NOOP( "Contacts" ) }, + { KOLAB_FOLDER_TYPE_EVENT, I18N_NOOP( "Calendar" ) }, + { KOLAB_FOLDER_TYPE_TASK, I18N_NOOP( "Tasks" ) }, + { KOLAB_FOLDER_TYPE_JOURNAL, I18N_NOOP( "Journal" ) }, + { KOLAB_FOLDER_TYPE_NOTE, I18N_NOOP( "Notes" ) } +}; +static const int numFolderTypeData = sizeof folderTypeData / sizeof *folderTypeData; + +BOOST_STATIC_ASSERT( numFolderTypeData == KolabV2::FolderTypeSize ); + +FolderType KolabV2::folderTypeFromString( const QByteArray &folderTypeName ) +{ + if ( folderTypeName == KOLAB_FOLDER_TYPE_CONTACT || + folderTypeName == KOLAB_FOLDER_TYPE_CONTACT KOLAB_FOLDER_TYPE_DEFAULT_SUFFIX ) { + return KolabV2::Contact; + } + + if ( folderTypeName == KOLAB_FOLDER_TYPE_EVENT || + folderTypeName == KOLAB_FOLDER_TYPE_EVENT KOLAB_FOLDER_TYPE_DEFAULT_SUFFIX ) { + return KolabV2::Event; + } + + if ( folderTypeName == KOLAB_FOLDER_TYPE_TASK || + folderTypeName == KOLAB_FOLDER_TYPE_TASK KOLAB_FOLDER_TYPE_DEFAULT_SUFFIX ) { + return KolabV2::Task; + } + + if ( folderTypeName == KOLAB_FOLDER_TYPE_JOURNAL || + folderTypeName == KOLAB_FOLDER_TYPE_JOURNAL KOLAB_FOLDER_TYPE_DEFAULT_SUFFIX ) { + return KolabV2::Journal; + } + + if ( folderTypeName == KOLAB_FOLDER_TYPE_NOTE || + folderTypeName == KOLAB_FOLDER_TYPE_NOTE KOLAB_FOLDER_TYPE_DEFAULT_SUFFIX ) { + return KolabV2::Note; + } + + return KolabV2::Mail; +} + +QByteArray KolabV2::folderTypeToString( FolderType type, bool isDefault ) +{ + Q_ASSERT( type >= 0 && type < FolderTypeSize ); + QByteArray result = folderTypeData[ type ].name; + if ( isDefault ) { + result += KOLAB_FOLDER_TYPE_DEFAULT_SUFFIX; + } + return result; +} + +KolabV2::FolderType KolabV2::guessFolderTypeFromName( const QString &name ) +{ + for ( int i = 0; i < numFolderTypeData; ++i ) { + if ( name == i18n( folderTypeData[ i ].label ) || + name == QString::fromLatin1( folderTypeData[ i ].label ) ) { + return static_cast( i ); + } + } + return KolabV2::Mail; +} + +QString KolabV2::nameForFolderType( FolderType type ) +{ + Q_ASSERT( type >= 0 && type < FolderTypeSize ); + return i18n( folderTypeData[ type ].label ); +} + +Kolab::FolderType Kolab::folderTypeFromString(const QByteArray& folderTypeName) +{ + return Kolab::folderTypeFromString( std::string(folderTypeName.data(), folderTypeName.size()) ); +} + +QByteArray Kolab::getFolderTypeAnnotation(const QMap< QByteArray, QByteArray > &annotations) +{ + if ( annotations.contains( "/shared" KOLAB_FOLDER_TYPE_ANNOTATION ) ) { + return annotations.value( "/shared" KOLAB_FOLDER_TYPE_ANNOTATION ); + } + return annotations.value(KOLAB_FOLDER_TYPE_ANNOTATION); +} + +void Kolab::setFolderTypeAnnotation(QMap< QByteArray, QByteArray >& annotations, const QByteArray& value) +{ + annotations["/shared" KOLAB_FOLDER_TYPE_ANNOTATION] = value; +} + diff --git a/kdepim-runtime/resources/kolabproxy/kolabdefs.h b/kdepim-runtime/resources/kolabproxy/kolabdefs.h new file mode 100644 index 00000000..92acdbb7 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabdefs.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2010 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 KOLABPROXY_KOLABDEFS_H +#define KOLABPROXY_KOLABDEFS_H + +#include +#include + +#include //libkolab +#include //libkolab + +namespace Kolab { + FolderType folderTypeFromString( const QByteArray &folderTypeName ); + QByteArray getFolderTypeAnnotation( const QMap &annotations); + void setFolderTypeAnnotation( QMap &annotations, const QByteArray &value); +} + +namespace KolabV2 { + +#define PRODUCT_ID QLatin1String("Akonadi-KolabResource") + + enum FolderType { + Mail = 0, + Contact, + Event, + Task, + Journal, + Note, + FolderTypeSize = Note + 1 + }; + + FolderType folderTypeFromString( const QByteArray &folderTypeName ); + QByteArray folderTypeToString( FolderType type, bool isDefault = false ); + FolderType guessFolderTypeFromName( const QString &name ); + QString nameForFolderType( FolderType type ); +} + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/kolabhandler.cpp b/kdepim-runtime/resources/kolabproxy/kolabhandler.cpp new file mode 100644 index 00000000..0574464d --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabhandler.cpp @@ -0,0 +1,225 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 "kolabhandler.h" + +#include "addressbookhandler.h" +#include "calendarhandler.h" +#include "notehandler.h" +#include "taskshandler.h" +#include "imapitemaddedjob.h" +#include "imapitemremovedjob.h" + +#include //libkolab + +#include +#include + +KolabHandler::KolabHandler( const Akonadi::Collection &imapCollection ) + : m_imapCollection( imapCollection ), + m_formatVersion ( Kolab::KolabV3 ), + m_warningDisplayLevel( Kolab::ErrorHandler::Error ), + mItemAddJobInProgress( false ) +{ +} + +KolabHandler::Ptr KolabHandler::createHandler( Kolab::FolderType type, + const Akonadi::Collection &imapCollection ) +{ + switch (type) { + case Kolab::ContactType: + return Ptr( new AddressBookHandler( imapCollection ) ); + case Kolab::EventType: + return Ptr( new CalendarHandler( imapCollection ) ); + case Kolab::TaskType: + return Ptr( new TasksHandler( imapCollection ) ); + case Kolab::JournalType: + return Ptr( new JournalHandler( imapCollection ) ); + case Kolab::NoteType: + return Ptr( new NotesHandler( imapCollection ) ); + default: + qWarning() << "invalid type"; + } + return KolabHandler::Ptr(); +} + +bool KolabHandler::hasHandler( Kolab::FolderType type ) +{ + switch (type) { + case Kolab::ContactType: + case Kolab::EventType: + case Kolab::TaskType: + case Kolab::JournalType: + case Kolab::NoteType: + return true; + default: + return false; + } + return false; +} + +KolabHandler::Ptr KolabHandler::createHandler( const KolabV2::FolderType &type, + const Akonadi::Collection &imapCollection ) +{ + switch (type) { + case KolabV2::Contact: + return Ptr( new AddressBookHandler( imapCollection ) ); + case KolabV2::Event: + return Ptr( new CalendarHandler( imapCollection ) ); + case KolabV2::Task: + return Ptr( new TasksHandler( imapCollection ) ); + case KolabV2::Journal: + return Ptr( new JournalHandler( imapCollection ) ); + case KolabV2::Note: + return Ptr( new NotesHandler( imapCollection ) ); + default: + qWarning() << "invalid type"; + } + return KolabHandler::Ptr(); +} + +void KolabHandler::setKolabFormatVersion( Kolab::Version version ) +{ + m_formatVersion = version; +} + +QByteArray KolabHandler::kolabTypeForMimeType( const QStringList &contentMimeTypes ) +{ + if ( contentMimeTypes.contains( KABC::Addressee::mimeType() ) ) { + return "contact"; + } else if ( contentMimeTypes.contains( KCalCore::Event::eventMimeType() ) ) { + return "event"; + } else if ( contentMimeTypes.contains( KCalCore::Todo::todoMimeType() ) ) { + return "task"; + } else if ( contentMimeTypes.contains( KCalCore::Journal::journalMimeType() ) ) { + return "journal"; + } else if ( contentMimeTypes.contains( QLatin1String("application/x-vnd.akonadi.note") ) || + contentMimeTypes.contains( QLatin1String("text/x-vnd.akonadi.note") ) ) { + return "note"; + } + return QByteArray(); +} + +QStringList KolabHandler::allSupportedMimeTypes() +{ + return QStringList() + << KABC::Addressee::mimeType() + << KABC::ContactGroup::mimeType() + << KCalCore::Event::eventMimeType() + << KCalCore::Todo::todoMimeType() + << KCalCore::Journal::journalMimeType() + << QLatin1String( "application/x-vnd.akonadi.note" ) + << QLatin1String( "text/x-vnd.akonadi.note" ); +} + +KolabHandler::~KolabHandler() +{ +} + +QByteArray KolabHandler::mimeType() const +{ + return m_mimeType; +} + +bool KolabHandler::checkForErrors( Akonadi::Item::Id affectedItem ) +{ + if ( Kolab::ErrorHandler::instance().error() < m_warningDisplayLevel ) { + Kolab::ErrorHandler::instance().clear(); + return false; + } + + QString errorMsg; + foreach ( const Kolab::ErrorHandler::Err &error, Kolab::ErrorHandler::instance().getErrors() ) { + errorMsg.append( error.message ); + errorMsg.append( QLatin1String("\n") ); + } + + kWarning() << "Error on item " << affectedItem << ":\n" << errorMsg; + Kolab::ErrorHandler::instance().clear(); + return true; +} + +Akonadi::Item::List KolabHandler::resolveConflicts(const Akonadi::Item::List& kolabItems) +{ + //we should preserve the order here + Akonadi::Item::List finalItems; + QMap gidItemMap; + foreach (const Akonadi::Item &item, kolabItems) { + const QString gid = extractGid(item); + if (!gid.isEmpty()) { + gidItemMap[gid] << item; + } + } + foreach (const Akonadi::Item &item, kolabItems) { + const QString gid = extractGid(item); + if (gid.isEmpty()) { + finalItems << item; + } else if (gidItemMap.contains(gid)) { + //TODO assuming the items are in revers imap uid order (newest first) + finalItems << gidItemMap.value(gid).first(); + gidItemMap.remove(gid); + } + } + return finalItems; +} + +void KolabHandler::processItemAddedQueue() +{ + if (mItemAddedQueue.isEmpty() || mItemAddJobInProgress) { + return; + } + //TODO we would only have to serialize add jobs for items with the same GID + mItemAddJobInProgress = true; + const QPair pair = mItemAddedQueue.dequeue(); + ImapItemAddedJob *addedJob = new ImapItemAddedJob( pair.first, pair.second, *this, this ); + connect(addedJob, SIGNAL(result(KJob*)), this, SLOT(onItemAdded(KJob*))); + addedJob->start(); +} + +void KolabHandler::onItemAdded(KJob *job) +{ + mItemAddJobInProgress = false; + if (job->error()) { + kWarning() << job->errorString(); + } + processItemAddedQueue(); +} + +void KolabHandler::imapItemAdded(const Akonadi::Item& imapItem, const Akonadi::Collection& imapCollection) +{ + mItemAddedQueue.enqueue(qMakePair(imapItem, imapCollection)); + processItemAddedQueue(); +} + +void KolabHandler::imapItemRemoved(const Akonadi::Item& imapItem) +{ + //TODO delay this in case an imapItemAdded job is already running (it might reuse the item) + ImapItemRemovedJob *job = new ImapItemRemovedJob(imapItem, this); + connect(job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + job->start(); +} + +void KolabHandler::checkResult(KJob* job) +{ + if ( job->error() ) { + kWarning() << "Error occurred: " << job->errorString(); + } +} + diff --git a/kdepim-runtime/resources/kolabproxy/kolabhandler.h b/kdepim-runtime/resources/kolabproxy/kolabhandler.h new file mode 100644 index 00000000..4e77cdaf --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabhandler.h @@ -0,0 +1,160 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_KOLABHANDLER_H +#define KOLABPROXY_KOLABHANDLER_H + +#include "kolabdefs.h" + +#include +#include + +#include +#include + +#include //libkolab + +class KJob; +/** + @author Andras Mantia +*/ +class KolabHandler : public QObject +{ + Q_OBJECT + + public: + typedef QSharedPointer Ptr; + + static Ptr createHandler( Kolab::FolderType type, + const Akonadi::Collection &imapCollection ); + + static bool hasHandler( Kolab::FolderType type ); + + static Ptr createHandler( const KolabV2::FolderType &type, + const Akonadi::Collection &imapCollection ); + + /** + * Returns the Kolab folder type for the given collection. + */ + static QByteArray kolabTypeForMimeType( const QStringList &mimetypes ); + + /** + * Returns all mime types supported by Kolab. + */ + static QStringList allSupportedMimeTypes(); + + virtual ~KolabHandler(); + + /** + * Extracts the UID of a kolab object from an imap item. + * + * Used for conflict detection. + */ + virtual QString extractGid( const Akonadi::Item &kolabItem ) = 0; + + Akonadi::Item::List resolveConflicts( const Akonadi::Item::List &kolabItems ); + + /** + * Translates Kolab items into the items supported by the handler. + * @param addrs + * @return the translated items + */ + virtual Akonadi::Item::List translateItems( const Akonadi::Item::List &addrs ) = 0; + + /** + * Translates an item into Kolab format. + * @param item the item to be translated + * @param imapItem the item that will hold the Kolab format payload data. + * @return false if the conversion failed + */ + virtual bool toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ) = 0; + + /** + * Return the mimetypes for the collections managed by the handler. + */ + virtual QStringList contentMimeTypes() = 0; + + /** + * Returns the default icon for this folder type. + */ + virtual QString iconName() const = 0; + + virtual QByteArray mimeType() const; + + void setKolabFormatVersion( Kolab::Version ); + + /** + * Returns true if the current operation should be aborted and false + * if everything is ok. + * + * Error handling strategy: + * If an error happened, either: + * - completely skip item => will be redownloaded + * (or will it be deleted from the server? (it shouldn't)) + * - mark item as corrup => readonly, will not be written back + * and will be redownloaded + * + * @param affectedItem The item which is currently being processed. + */ + bool checkForErrors( Akonadi::Item::Id affectedItem ); + + void imapItemAdded(const Akonadi::Item &imapItem, const Akonadi::Collection &imapCollection); + void imapItemRemoved(const Akonadi::Item &imapItem); + + protected: + explicit KolabHandler( const Akonadi::Collection &imapCollection ); + + QByteArray m_mimeType; + Akonadi::Collection m_imapCollection; + Kolab::Version m_formatVersion; + int m_warningDisplayLevel; + + private slots: + void onItemAdded(KJob*); + void checkResult(KJob*); + + private: + void processItemAddedQueue(); + QQueue > mItemAddedQueue; + bool mItemAddJobInProgress; +}; + +template +static inline T kolabToImap( const T &kolabObject ) +{ + return T( kolabObject.remoteId().toLongLong() ); +} + +template +static inline T imapToKolab( const T &imapObject, T &kolabObject) +{ + kolabObject.setRemoteId( QString::number( imapObject.id() ) ); + return kolabObject; +} + +template +static inline T imapToKolab( const T &imapObject ) +{ + T kolabObject; + imapToKolab( imapObject, kolabObject ); + return kolabObject; +} + + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/kolabproxyresource.cpp b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.cpp new file mode 100644 index 00000000..dfdd55f0 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.cpp @@ -0,0 +1,832 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 "kolabproxyresource.h" + +#include "collectiontreebuilder.h" +#include "freebusyupdatehandler.h" +#include "settings.h" +#include "settingsadaptor.h" +#include "kolabproxyadaptor.h" +#include "setupkolab.h" +#include "imapitemaddedjob.h" +#include "imapitemremovedjob.h" +#include "itemaddedjob.h" +#include "itemchangedjob.h" +#include "revertitemchangesjob.h" +#include + +#include "collectionannotationsattribute.h" //from shared + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef RUNTIME_PLUGINS_STATIC +#include + +Q_IMPORT_PLUGIN(akonadi_serializer_mail) +Q_IMPORT_PLUGIN(akonadi_serializer_addressee) +Q_IMPORT_PLUGIN(akonadi_serializer_kcalcore) +Q_IMPORT_PLUGIN(akonadi_serializer_contactgroup) +#endif + +static const char KOLAB_COLLECTION[] = "KolabCollection"; + +static QString mailBoxForImapCollection( const Akonadi::Collection &imapCollection, + bool showWarnings ) +{ + if ( imapCollection.remoteId().isEmpty() ) { + if ( showWarnings ) { + kWarning() << "Got incomplete ancestor chain:" << imapCollection; + } + return QString(); + } + + if ( imapCollection.parentCollection() == Akonadi::Collection::root() ) { + return QLatin1String( "" ); + } + + const QString parentMailbox = + mailBoxForImapCollection( imapCollection.parentCollection(), showWarnings ); + + if ( parentMailbox.isNull() ) { + // invalid, != isEmpty() here! + return QString(); + } + + const QString mailbox = parentMailbox + imapCollection.remoteId(); + + return mailbox; +} + +KolabProxyResource::KolabProxyResource( const QString &id ) + : ResourceBase( id ), + mHandlerManager( new HandlerManager ) +{ + Akonadi::AttributeFactory::registerAttribute(); + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( + QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + new KolabproxyAdaptor( this ); + Akonadi::DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/KolabProxy" ), this, QDBusConnection::ExportAdaptors ); + Akonadi::DBusConnectionPool::threadConnection().registerService( QLatin1String( "Agent.akonadi_kolabproxy_resource" ) ); + + changeRecorder()->fetchCollection( true ); + changeRecorder()->itemFetchScope().fetchFullPayload(); + + m_monitor = new Akonadi::Monitor( this ); + m_monitor->itemFetchScope().fetchFullPayload(); + m_monitor->itemFetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::All ); + + m_collectionMonitor = new Akonadi::Monitor( this ); + m_collectionMonitor->fetchCollection( true ); + m_collectionMonitor->setCollectionMonitored( Akonadi::Collection::root() ); + m_collectionMonitor->ignoreSession( Akonadi::Session::defaultSession() ); + m_collectionMonitor->collectionFetchScope().setAncestorRetrieval( + Akonadi::CollectionFetchScope::All ); + + m_freeBusyUpdateHandler = new FreeBusyUpdateHandler( this ); + + connect( m_monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), + this, SLOT(imapItemAdded(Akonadi::Item,Akonadi::Collection)) ); + + connect( m_monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), + this, SLOT(imapItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) ); + + connect( m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), + this, SLOT(imapItemRemoved(Akonadi::Item)) ); + + //We don't connect to changed because an edit results in a new item (append/delete) on imap + + connect( m_collectionMonitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), + this, SLOT(imapCollectionAdded(Akonadi::Collection,Akonadi::Collection)) ); + + connect( m_collectionMonitor, SIGNAL(collectionRemoved(Akonadi::Collection)), + this, SLOT(imapCollectionRemoved(Akonadi::Collection)) ); + + connect( m_collectionMonitor, SIGNAL(collectionChanged(Akonadi::Collection)), + this, SLOT(imapCollectionChanged(Akonadi::Collection)) ); + + connect( m_collectionMonitor, + SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)), + this, + SLOT(imapCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)) ); + + setName( i18n( "Kolab" ) ); + + // among other things, this ensures that m_root actually exists when a new imap folder is added + synchronizeCollectionTree(); +} + +KolabProxyResource::~KolabProxyResource() +{ +} + +void KolabProxyResource::showErrorMessage(const QString &msg) +{ + KNotification *notification = new KNotification(QLatin1String("Error"), KNotification::CloseOnTimeout, 0); + notification->setText(msg); + notification->setComponentData(KGlobal::mainComponent()); + notification->sendEvent(); +} + +bool KolabProxyResource::registerHandlerForCollection(const Akonadi::Collection& imapCollection) +{ + const Kolab::Version v = SetupKolab::readKolabVersion( imapCollection.resource() ); + if ( mHandlerManager->registerHandlerForCollection( imapCollection, v ) ) { + m_monitor->setCollectionMonitored( imapCollection ); + return true; + } + return false; +} + +QString KolabProxyResource::imapResourceForCollection( Akonadi::Entity::Id id) +{ + return mHandlerManager->imapResourceForCollection( id ); +} + +void KolabProxyResource::retrieveCollections() +{ + CollectionTreeBuilder *job = new CollectionTreeBuilder( this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(retrieveCollectionsTreeDone(KJob*)) ); +} + +void KolabProxyResource::retrieveCollectionsTreeDone( KJob *job ) +{ + if ( job->error() ) { + kWarning( ) << "Error on collection fetch:" << job->errorText(); + cancelTask( job->errorText() ); + } else { + Akonadi::Collection::List imapCollections = + qobject_cast( job )->allCollections(); + + Akonadi::Collection::List kolabCollections; + Q_FOREACH ( const Akonadi::Collection &collection, imapCollections ) { + kolabCollections.append( createCollection( collection ) ); + } + collectionsRetrieved( kolabCollections ); + } +} + +void KolabProxyResource::retrieveItems( const Akonadi::Collection &collection ) +{ + const Akonadi::Collection imapCollection = kolabToImap( collection ); + if ( !mHandlerManager->isMonitored( imapCollection.id() ) ) { + //This should never happen + kWarning() << "received a retrieveItems request for a collection without imap counterpart" << collection.id(); + cancelTask(); + return; + } + + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( imapCollection ); + job->fetchScope().fetchFullPayload(); + job->fetchScope().setIgnoreRetrievalErrors( true ); + + connect( job, SIGNAL(result(KJob*)), this, SLOT(retrieveItemsFetchDone(KJob*)) ); +} + +void KolabProxyResource::retrieveItemsFetchDone( KJob *job ) +{ + if ( job->error() ) { + kWarning( ) << "Error on item fetch:" << job->errorText(); + cancelTask(); + return; + } + + const Akonadi::Item::List items = qobject_cast(job)->items(); + if ( items.isEmpty() ) { + itemsRetrieved( Akonadi::Item::List() ); + return; + } + const KolabHandler::Ptr handler = mHandlerManager->getHandler( items[0].storageCollectionId() ); + if ( !handler ) { + cancelTask(); + return; + } + const Akonadi::Item::List newItems = handler->resolveConflicts( handler->translateItems( items ) ); + + itemsRetrieved( newItems ); +} + +bool KolabProxyResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( parts ); + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( kolabToImap( item ) ); + job->fetchScope().fetchFullPayload(); + job->setProperty( "itemId", item.id() ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(retrieveItemFetchDone(KJob*)) ); + return true; +} + +void KolabProxyResource::retrieveItemFetchDone( KJob *job ) +{ + if ( job->error() ) { + kWarning( ) << "Error on item fetch:" << job->errorText(); + cancelTask(); + return; + } + const Akonadi::Item::List items = qobject_cast(job)->items(); + if ( items.isEmpty() ) { + kWarning() << "Items is emtpy"; + cancelTask(); + return; + } + const KolabHandler::Ptr handler = mHandlerManager->getHandler( items[0].storageCollectionId() ); + if ( !handler ) { + cancelTask(); + return; + } + const Akonadi::Item::List newItems = handler->translateItems( items ); + if ( newItems.isEmpty() ) { + kWarning() << "Could not translate item"; + cancelTask(); + return; + } + Akonadi::Item item = newItems[0]; + item.setId(job->property("itemId").value()); + itemRetrieved( item ); +} + +void KolabProxyResource::aboutToQuit() +{ + mHandlerManager->clear(); +} + +void KolabProxyResource::configure( WId windowId ) +{ + // TODO: this method is usually called when a new resource is being + // added to the Akonadi setup. You can do any kind of user interaction here, + // e.g. showing dialogs. + // The given window ID is usually useful to get the correct + // "on top of parent" behavior if the running window manager applies any kind + // of focus stealing prevention technique + + QPointer kolabConfigDialog( new SetupKolab( this ) ); + if ( windowId ) + KWindowSystem::setMainWindow( kolabConfigDialog, windowId ); + + kolabConfigDialog->setWindowIcon( KIcon( QLatin1String("kolab") ) ); + kolabConfigDialog->exec(); + emit configurationDialogAccepted(); + + foreach ( Akonadi::Entity::Id id, mHandlerManager->monitoredCollections() ) { //krazy:exclude=foreach + KolabHandler::Ptr handler = mHandlerManager->getHandler( id ); + Kolab::Version v = SetupKolab::readKolabVersion( mHandlerManager->imapResourceForCollection( id ) ); + handler->setKolabFormatVersion( v ); + } + + delete kolabConfigDialog; +} + +void KolabProxyResource::itemAdded( const Akonadi::Item &kolabItem, + const Akonadi::Collection &collection ) +{ + const Akonadi::Collection imapCollection = kolabToImap( collection ); + const KolabHandler::Ptr handler = mHandlerManager->getHandler( imapCollection.id() ); + if ( !handler ) { + kWarning() << "Couldn't find a handler for the collection, but we should have one: " << imapCollection.id(); + showErrorMessage(i18n("An error occured while writing the item to the backend.")); + cancelTask(); + new Akonadi::ItemDeleteJob(kolabItem); + return; + } + ItemAddedJob *itemAddedJob = new ItemAddedJob(kolabItem, collection, *handler, this); + connect(itemAddedJob, SIGNAL(result(KJob*)), this, SLOT(onItemAddedDone(KJob*))); + itemAddedJob->start(); +} + +void KolabProxyResource::onItemAddedDone(KJob* job) +{ + ItemAddedJob *itemAddedJob = static_cast(job); + Akonadi::Item kolabItem = itemAddedJob->kolabItem(); + const Akonadi::Item imapItem = itemAddedJob->imapItem(); + if (job->error()) { + kWarning() << "Failed to create imap item: " << job->errorString(); + showErrorMessage(i18n("An error occured while writing the item to the backend.")); + cancelTask(); + new Akonadi::ItemDeleteJob(kolabItem); + return; + } + m_excludeAppend << imapItem.id(); + + kolabItem.setRemoteId( QString::number( imapItem.id() ) ); + changeCommitted( kolabItem ); +} + +void KolabProxyResource::itemChanged( const Akonadi::Item &kolabItem, + const QSet &parts ) +{ + Q_UNUSED( parts ); + ItemChangedJob *itemChangedJob = new ItemChangedJob(kolabItem, *mHandlerManager, this); + connect(itemChangedJob, SIGNAL(result(KJob*)), this, SLOT(onItemChangedDone(KJob*))); + itemChangedJob->start(); +} + +void KolabProxyResource::onItemChangedDone(KJob* job) +{ + ItemChangedJob *itemChangedJob = static_cast(job); + if ( job->error() ) { + showErrorMessage(i18n("An error occured while writing the item to the backend.")); + cancelTask( job->errorText() ); + kWarning() << "Failed to modify item, reverting to state of imap item: " << itemChangedJob->item().id(); + RevertItemChangesJob *revertJob = new RevertItemChangesJob(itemChangedJob->item(), *mHandlerManager, this); + connect(revertJob, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + revertJob->start(); + return; + } + changeCommitted( itemChangedJob->item() ); +} + +void KolabProxyResource::itemMoved( const Akonadi::Item &item, + const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ) +{ + Q_UNUSED( collectionSource ); + KJob *job = new Akonadi::ItemMoveJob( kolabToImap( item ), kolabToImap( collectionDestination ), this ); + connect(job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + changeCommitted( item ); +} + +void KolabProxyResource::itemRemoved( const Akonadi::Item &item ) +{ + const Akonadi::Item imapItem( item.remoteId().toUInt() ); + Akonadi::ItemDeleteJob *djob = new Akonadi::ItemDeleteJob( imapItem ); + changeCommitted( item ); + Q_UNUSED(djob); +} + +void KolabProxyResource::collectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ) +{ + if ( KolabHandler::kolabTypeForMimeType( collection.contentMimeTypes() ).isEmpty() ) { + kWarning() << "Collection " << collection.name() << collection.id() << collection.isValid() + << "doesn't have kolab type set. isValid = " + << "; parent is " << parent.name() << parent.id() << parent.isValid(); + cancelTask( QLatin1String( "Collection doesn't have kolab type." ) ); + return; + } + + Akonadi::Collection imapCollection( collection ); + imapCollection.setId( -1 ); + imapCollection.setRemoteId( QString() ); + imapCollection.setContentMimeTypes( QStringList() + << Akonadi::Collection::mimeType() + << QLatin1String( "message/rfc822" ) ); + const Akonadi::Collection imapParent = kolabToImap( parent ); + imapCollection.setParentCollection( imapParent ); + + Akonadi::CollectionAnnotationsAttribute *attr = + imapCollection.attribute( + Akonadi::Collection::AddIfMissing ); + + QMap annotations = attr->annotations(); + Kolab::setFolderTypeAnnotation( annotations, KolabHandler::kolabTypeForMimeType( collection.contentMimeTypes() ) ); + attr->setAnnotations( annotations ); + + Akonadi::CollectionCreateJob *job = new Akonadi::CollectionCreateJob( imapCollection, this ); + job->setProperty( KOLAB_COLLECTION, QVariant::fromValue( collection ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(imapFolderCreateResult(KJob*)) ); +} + +void KolabProxyResource::imapFolderCreateResult( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + } else { + const Akonadi::Collection imapCollection = + qobject_cast( job )->collection(); + + registerHandlerForCollection( imapCollection ); + Akonadi::Collection kolabCollection = + job->property( KOLAB_COLLECTION ).value(); + kolabCollection.setRemoteId( QString::number( imapCollection.id() ) ); + changeCommitted( kolabCollection ); + } +} + +void KolabProxyResource::applyAttributesToImap( Akonadi::Collection &imapCollection, + const Akonadi::Collection &kolabCollection ) +{ + static const Akonadi::EntityDisplayAttribute eda; + static const Akonadi::EntityHiddenAttribute hidden; + foreach ( const Akonadi::Attribute *attr, kolabCollection.attributes() ) { + if ( attr->type() == hidden.type() ) { + // Don't propagate HIDDEN because that would hide collections in korg, kab too. + continue; + } + + if ( attr->type() == eda.type() ) { + // Don't propagate DISPLAYATTRIBUTE because that would cause icons + // from the imap resource to use kolab icons. + Akonadi::EntityDisplayAttribute *imapEda = + imapCollection.attribute( Akonadi::Entity::AddIfMissing ); + + const QString dName = + static_cast( attr )->displayName(); + imapEda->setDisplayName( dName ); + continue; + } + + if ( attr->type() == "AccessRights" ) { + // Don't propagate access rights here, since it's already propagated using Collection::rights + continue; + } + + imapCollection.addAttribute( attr->clone() ); + } +} + +void KolabProxyResource::applyAttributesFromImap( Akonadi::Collection &kolabCollection, + const Akonadi::Collection &imapCollection ) +{ + static const Akonadi::EntityDisplayAttribute eda; + static const Akonadi::EntityHiddenAttribute hidden; + foreach ( const Akonadi::Attribute *attr, imapCollection.attributes() ) { + if ( attr->type() == hidden.type() ) { + continue; + } + + if ( attr->type() == eda.type() ) { + continue; + } + + if ( attr->type() == "AccessRights" ) { + continue; + } + + kolabCollection.addAttribute( attr->clone() ); + } +} + +void KolabProxyResource::updateFreeBusyInformation( const Akonadi::Collection &imapCollection ) +{ + if ( !HandlerManager::isHandledKolabFolder( imapCollection ) ) { + return; + } + + if ( HandlerManager::getFolderType( imapCollection ) != Kolab::EventType ) { + return; + } + + if ( !Settings::self()->updateFreeBusy() ) { + return; // disabled by user + } + + Kolab::Version v = SetupKolab::readKolabVersion( imapCollection.resource() ); + if (v != Kolab::KolabV2) { + return; + } + + const QString path = mailBoxForImapCollection( imapCollection, true ); + if ( path.isEmpty() ) { + return; + } + + const QString resourceId = imapCollection.resource(); + + QDBusInterface settingsInterface( + QString::fromLatin1( "org.freedesktop.Akonadi.Agent.%1" ).arg( resourceId ), + QLatin1String( "/Settings" ), QLatin1String( "org.kde.Akonadi.Imap.Settings" ) ); + + QDBusInterface walletInterface( + QString::fromLatin1( "org.freedesktop.Akonadi.Agent.%1" ).arg( resourceId ), + QLatin1String( "/Settings" ), QLatin1String( "org.kde.Akonadi.Imap.Wallet" ) ); + + if ( !settingsInterface.isValid() || !walletInterface.isValid() ) { + kWarning() << "unable to retrieve imap resource settings interface"; + return; + } + + const QDBusReply userNameReply = settingsInterface.call( QLatin1String( "userName" ) ); + if ( !userNameReply.isValid() ) { + kWarning() << "unable to retrieve user name from imap resource settings"; + return; + } + + const QDBusReply passwordReply = walletInterface.call( QLatin1String( "password" ) ); + if ( !passwordReply.isValid() ) { + kWarning() << "unable to retrieve password from imap resource settings"; + return; + } + + const QDBusReply hostReply = settingsInterface.call( QLatin1String( "imapServer" ) ); + if ( !hostReply.isValid() ) { + kWarning() << "unable to retrieve host from imap resource settings"; + return; + } + + m_freeBusyUpdateHandler->updateFolder( path, + userNameReply.value(), + passwordReply.value(), + hostReply.value() ); +} + +void KolabProxyResource::collectionChanged( const Akonadi::Collection &collection ) +{ + Akonadi::Collection imapCollection; + imapCollection.setId( collection.remoteId().toLongLong() ); + imapCollection.setName( collection.name() ); + imapCollection.setCachePolicy( collection.cachePolicy() ); + imapCollection.setRights( collection.rights() ); + + applyAttributesToImap( imapCollection, collection ); + + Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( imapCollection, this ); + Q_UNUSED( job ); + // TODO wait for the result + changeCommitted( collection ); +} + +void KolabProxyResource::collectionMoved( const Akonadi::Collection &collection, + const Akonadi::Collection &source, + const Akonadi::Collection &destination ) +{ + Q_UNUSED( source ); + KJob *job = new Akonadi::CollectionMoveJob( kolabToImap( collection ), kolabToImap( destination ), this ); + connect(job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + changeCommitted( collection ); +} + +void KolabProxyResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + Akonadi::Collection imapCollection = kolabToImap( collection ); + + Akonadi::CollectionDeleteJob *job = new Akonadi::CollectionDeleteJob( imapCollection, this ); + Q_UNUSED( job ); + // TODO wait for result + changeCommitted( collection ); +} + +void KolabProxyResource::imapItemAdded( const Akonadi::Item &item, + const Akonadi::Collection &collection ) +{ + //We only want updates about collections that are not from this resource + if ( collection.resource() == identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) { + return; + } + if ( m_excludeAppend.contains( item.id() ) ) { + kDebug() << "item already present"; + m_excludeAppend.removeAll( item.id() ); + return; + } + if ( const KolabHandler::Ptr handler = mHandlerManager->getHandler( collection.id() ) ) { + handler->imapItemAdded(item, collection); + } +} + +void KolabProxyResource::imapItemRemoved( const Akonadi::Item &item ) +{ + //We only want updates about collections that are not from this resource + if ( item.parentCollection().resource() == identifier() || item.parentCollection().resource().startsWith("akonadi_kolab_resource") ) { + return; + } + if ( const KolabHandler::Ptr handler = mHandlerManager->getHandler( item.parentCollection().id() ) ) { + handler->imapItemRemoved(item); + } else { + //The handler is already gone, + kWarning() << "Couldn't find handler for collection " << item.storageCollectionId(); + ImapItemRemovedJob *job = new ImapItemRemovedJob(item, this); + connect(job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + job->start(); + } +} + +void KolabProxyResource::imapItemMoved( const Akonadi::Item &item, + const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ) +{ + //We only want updates about collections that are not from this resource + if ( collectionSource.resource() == identifier() || collectionDestination.resource() == identifier() || + collectionSource.resource().startsWith("akonadi_kolab_resource") || collectionDestination.resource().startsWith("akonadi_kolab_resource") ) { + return; + } + KJob *job = new Akonadi::ItemMoveJob( imapToKolab( item ), imapToKolab( collectionDestination ), this ); + connect(job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); +} + +void KolabProxyResource::imapCollectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ) +{ + Q_UNUSED( parent ); + //We only want updates about collections that are not from this resource + if ( collection.resource() == identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) { + return; + } + if ( mHandlerManager->isMonitored( collection.id() ) ) { + // something is wrong, so better reload out collection tree + kWarning() << "IMAPCOLLECTIONADDED ABORT"; + synchronizeCollectionTree(); + return; + } + + updateHiddenAttribute( collection ); + + if ( registerHandlerForCollection( collection ) ) { + const Akonadi::Collection kolabCollection = createCollection( collection ); + Akonadi::CollectionCreateJob *job = new Akonadi::CollectionCreateJob( kolabCollection, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(kolabFolderChangeResult(KJob*)) ); + } +} + +void KolabProxyResource::imapCollectionChanged( const Akonadi::Collection &collection ) +{ + //We only want updates about collections that are not from this resource + if ( collection.resource() == identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) { + return; + } + + if ( !mHandlerManager->isMonitored( collection.id() ) ) { + if ( HandlerManager::isHandledKolabFolder( collection ) ) { + synchronizeCollectionTree(); + return; + } + + // not a Kolab folder, no need to resync the tree. + // just try to update a possible structural collection. + // if that fails it's not in our tree -> we don't care + Akonadi::Collection kolabCollection = createCollection( collection ); + Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( kolabCollection, this ); + Q_UNUSED( job ); + } else { + if ( !HandlerManager::isHandledKolabFolder( collection ) ) { + //This is no longer a kolab folder, remove + removeFolder( collection ); + return; + } + // Kolab folder we already have in our tree, if the update fails, reload our tree + Akonadi::Collection kolabCollection = createCollection( collection ); + Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( kolabCollection, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(kolabFolderChangeResult(KJob*)) ); + } + + updateHiddenAttribute( collection ); + updateFreeBusyInformation( collection ); +} + +void KolabProxyResource::imapCollectionMoved( const Akonadi::Collection &collection, + const Akonadi::Collection &source, + const Akonadi::Collection &destination ) +{ + //We only want updates about collections that are not from this resource + if ( source.resource() == identifier() || destination.resource() == identifier() || + source.resource().startsWith("akonadi_kolab_resource") || destination.resource().startsWith("akonadi_kolab_resource") ) { + return; + } + if ( mHandlerManager->isMonitored( collection.id() ) ) { + KJob *job = new Akonadi::CollectionMoveJob( imapToKolab( collection ), imapToKolab( destination ), this ); + connect(job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + } +} + +void KolabProxyResource::kolabFolderChangeResult( KJob *job ) +{ + if ( job->error() ) { + // something went wrong or the change was too complex to handle in the above slots, + // so re-sync the entire tree. + kWarning() << "Re-syncing collection tree as incremental changes did not succeed." + << job->errorText(); + synchronizeCollectionTree(); + } +} + +void KolabProxyResource::removeFolder( const Akonadi::Collection &imapCollection ) +{ + KJob *deleteJob = new Akonadi::CollectionDeleteJob( imapToKolab( imapCollection ) ); + connect(deleteJob, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*))); + mHandlerManager->removeFolder( imapCollection.id() ); + updateFreeBusyInformation( imapCollection ); +} + +void KolabProxyResource::imapCollectionRemoved( const Akonadi::Collection &imapCollection ) +{ + //We only want updates about collections that are not from this resource + if ( imapCollection.resource() == identifier() || imapCollection.resource().startsWith("akonadi_kolab_resource") ) { + return; + } + if (mHandlerManager->isMonitored( imapCollection.id())) { + removeFolder(imapCollection); + } else if ( imapCollection.parentCollection() == Akonadi::Collection::root() ) { + //we are not explicitly monitoring the top-level collection, but it should be removed anyways when the rest is gone + removeFolder(imapCollection); + } +} + +Akonadi::Collection KolabProxyResource::createCollection( + const Akonadi::Collection &imapCollection ) +{ + Akonadi::Collection c; + QStringList contentTypes; + if ( imapCollection.parentCollection() == Akonadi::Collection::root() ) { + c.setParentCollection( Akonadi::Collection::root() ); + Akonadi::CachePolicy policy; + policy.setInheritFromParent( false ); + policy.setCacheTimeout( -1 ); + policy.setLocalParts( QStringList() << QLatin1String( "ALL" ) ); + c.setCachePolicy( policy ); + } else { + c.parentCollection().setRemoteId( QString::number( imapCollection.parentCollection().id() ) ); + } + c.setName( imapCollection.name() ); + c.setRights( imapCollection.rights() ); + + Akonadi::EntityDisplayAttribute *imapAttr = + imapCollection.attribute(); + Akonadi::EntityDisplayAttribute *kolabAttr = + c.attribute( Akonadi::Collection::AddIfMissing ); + + if ( imapAttr ) { + if ( imapAttr->iconName() == QLatin1String( "mail-folder-inbox" ) ) { + kolabAttr->setDisplayName( i18n( "My Data" ) ); + kolabAttr->setIconName( QLatin1String( "view-pim-summary" ) ); + //contentTypes << KolabHandler::allSupportedMimeTypes(); + c.setRights( Akonadi::Collection::ReadOnly | Akonadi::Collection::CanCreateCollection ); + } else if ( imapCollection.parentCollection() == Akonadi::Collection::root() ) { + c.setName( i18n( "Kolab (%1)", imapAttr->displayName() ) ); + kolabAttr->setIconName( QLatin1String( "kolab" ) ); + } else { + kolabAttr->setDisplayName( imapAttr->displayName() ); + kolabAttr->setIconName( imapAttr->iconName() ); + } + } else { + if ( imapCollection.parentCollection() == Akonadi::Collection::root() ) { + c.setName( i18n( "Kolab (%1)", imapCollection.name() ) ); + kolabAttr->setIconName( QLatin1String( "kolab" ) ); + } + } + applyAttributesFromImap( c, imapCollection ); + if ( HandlerManager::isKolabFolder( imapCollection ) ) { + KolabHandler::Ptr handler = mHandlerManager->getHandler( imapCollection.id() ); + if ( handler ) { + contentTypes.append( handler->contentMimeTypes() ); + kolabAttr->setIconName( handler->iconName() ); + } + } + contentTypes.append( Akonadi::Collection::mimeType() ); + c.setContentMimeTypes( contentTypes ); + c.setRemoteId( QString::number( imapCollection.id() ) ); + return c; +} + +void KolabProxyResource::updateHiddenAttribute( const Akonadi::Collection &imapCollection ) +{ + if ( HandlerManager::isKolabFolder( imapCollection ) && !imapCollection.hasAttribute()) { + Akonadi::Collection hiddenImapCol( imapCollection ); + hiddenImapCol.attribute( Akonadi::Collection::AddIfMissing ); + KJob *job = new Akonadi::CollectionModifyJob( hiddenImapCol, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*)) ); + } + if ( !HandlerManager::isKolabFolder( imapCollection ) && imapCollection.hasAttribute()) { + Akonadi::Collection unhiddenImapCol( imapCollection ); + unhiddenImapCol.removeAttribute(); + KJob *job = new Akonadi::CollectionModifyJob( unhiddenImapCol, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(checkResult(KJob*)) ); + } +} + +void KolabProxyResource::checkResult(KJob* job) +{ + if ( job->error() ) { + kWarning() << "Error occurred: " << job->errorString(); + } +} + +AKONADI_RESOURCE_MAIN( KolabProxyResource ) diff --git a/kdepim-runtime/resources/kolabproxy/kolabproxyresource.desktop b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.desktop new file mode 100644 index 00000000..cf8a4c91 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.desktop @@ -0,0 +1,62 @@ +[Desktop Entry] +Name=Kolab Groupware Server (legacy) +Name[ca]=Servidor de treball en grup Kolab (llegat) +Name[da]=Kolab groupware-server (forældet) +Name[de]=Kolab-Groupware-Server (veraltet) +Name[en_GB]=Kolab Groupware Server (legacy) +Name[es]=Servidor de trabajo en grupo Kolab (heredado) +Name[et]=Kolabi grupitöö server (pärand) +Name[fi]=Kolab-työryhmäpalvelin (vanha) +Name[fr]=Serveur de logiciels de collaboration Kolab (historique) +Name[hu]=Kolab csoportmunka-kiszolgáló (örökölt) +Name[it]=Server di groupware Kolab (legacy) +Name[ko]=Kolab 그룹웨어 서버(레거시) +Name[nb]=Kolab groupware-tjener (gammeldags) +Name[nds]=Kolab-Arbeitkoppel-Server (öller) +Name[nl]=Kolab groupwareserver (oude versie) +Name[pl]=Serwer Kolab Groupware (przestarzaÅ‚y) +Name[pt]=Servidor de Groupware Kolab (antigo) +Name[pt_BR]=Servidor Groupware Kolab (antigo) +Name[sk]=Groupware Server Kolab (starý) +Name[sr]=Колабов групверÑки Ñервер (заÑтарело) +Name[sr@ijekavian]=Колабов групверÑки Ñервер (заÑтарело) +Name[sr@ijekavianlatin]=Kolabov grupverski server (zastarelo) +Name[sr@latin]=Kolabov grupverski server (zastarelo) +Name[sv]=Kolab grupprogramserver (förÃ¥ldrad) +Name[uk]=Сервер групової роботи Kolab (заÑтаріле) +Name[x-test]=xxKolab Groupware Server (legacy)xx +Name[zh_TW]=Kolab 群組伺æœå™¨ï¼ˆå‚³çµ±ï¼‰ +Comment=Provides access to Kolab groupware folders on an IMAP server (IMAP accounts need to be set up separately). This resource is obsolete, use the Kolab Groupware Server resource instead. +Comment[ca]=Proporciona accés a les carpetes de treball en grup Kolab en un servidor IMAP (els comptes IMAP s'han de configurar per separat). Aquest recurs és obsolet, en el seu lloc useu el recurs del servidor de treball en grup del Kolab. +Comment[da]=Giver adgang til Kolab groupware-mapper pÃ¥ en IMAP-server (IMAP-konti skal sættes op separat). Denne ressource er forældet, brug ressourcen til Kolab Groupware Server i stedet for. +Comment[de]=Ermöglicht den Zugriff auf Kolab-Groupware-Ordner auf einem Kolab-IMAP-Server. Diese Ressource ist veraltet, verwenden Sie statt dessen Ressource Kolab-Groupware-Server. +Comment[en_GB]=Provides access to Kolab groupware folders on an IMAP server (IMAP accounts need to be set up separately). This resource is obsolete, use the Kolab Groupware Server resource instead. +Comment[es]=Proporciona acceso a carpetas de trabajo en grupo de Kolab en un servidor IMAP (las cuentas IMAP necesitan ser configuradas por separado). Este recurso es obsoleto, usar el servidor de colaboración Kolab en su lugar. +Comment[et]=Ligipääsu tagamine Kolabi grupitöökaustadele IMAP serveris (IMAP kontod tuleb eraldi seadistada). See ressurss on iganenud, kasuta selle asemel Kolabi grupitöö serverit. +Comment[fi]=Tarjoaa pääsyn Kolab-työryhmäkansioihin IMAP-palvelimella (IMAP-tilit täytyy luoda erikseen). Tämä resurssi on vanha. Käytä tämän sijaan Kolab-työryhmäpalvelinta. +Comment[fr]=Fournit l'accès aux dossiers de logiciels de collaboration Kolab sur un serveur IMAP (il est nécessaire de configurer les comptes IMAP séparément). Cette ressource est obsolète. Utilisez la ressource Kolab. +Comment[hu]=Hozzáférést biztosít a Kolab csoportmunka-mappákhoz egy IMAP kiszolgálón (külön IMAP azonosítók beállítása szükséges). Ez az erÅ‘forrás elavult, használja helyette a Kolab csoportmunka-kiszolgálót. +Comment[it]=Fornisce l'accesso a cartelle di groupware Kolab su un server IMAP (gli account IMAP devono essere impostati separatamente). Questa risorsa è obsoleta, usa invece la risorsa «Server di groupware Kolab». +Comment[ko]=IMAP ì„œë²„ì— ìžˆëŠ” Kolab 그룹웨어 í´ë”ì— ì ‘ê·¼í•©ë‹ˆë‹¤(IMAP ê³„ì •ì€ ë³„ë„ë¡œ 설정해야 함). ì´ ìžì›ì€ 오래ë˜ì—ˆìœ¼ë¯€ë¡œ Kolab 그룹웨어 서버 ìžì›ì„ 사용하십시오. +Comment[nb]=Gir tilgang til Kolab gruppevaremapper pÃ¥ en IMAP-tjener (IMAP-kontoer mÃ¥ settes opp separat). Denne ressursen er utdatert, bruk i stedet ressursen Kolab groupware-tjener. +Comment[nds]=Stellt Togriep op Kolab-Arbeitkoppel-Ornern op en IMAP-Server praat (IMAP-Kontos mööt enkelt inricht warrn). Disse Ressource is överhaalt, bruuk ansteed bitte de Kolab-Arbeitkoppelserver-Ressource. +Comment[nl]=Levert toegang tot Kolab groupwaremappen op een IMAP-server (het is nodig om IMAP-accounts separaat in te stellen). Deze hulpbron is verouderd, gebruik in plaats daarvan de Kolab groupwareserver. +Comment[pl]=Zapewnia dostÄ™p do katalogów do pracy grupowej Kolab na serwerze IMAP (konta IMAP muszÄ… zostać ustawione osobno). Zasób ten jest przestarzaÅ‚y, zamiast niego użyj Serwera Kolab Groupware. +Comment[pt]=Oferece o acesso às pastas de 'groupware' do Kolab num servidor IMAP (as contas de IMAP têm de ser definidas em separado). Esta funcionalidade é obsoleta - use o recurso do Servidor de 'Groupware' Kolab em alternativa. +Comment[pt_BR]=Fornece acesso as pastas groupware Kolab em um servidor IMAP (as contas IMAP precisam ser definidas separadamente). Este recurso está obsoleto - use o recurso do Servidor Groupware Kolab como alternativa. +Comment[sk]=Poskytuje prístup k prieÄinkom Kolab groupware na IMAP serveri (IMAP úÄty musia byÅ¥ nastavené oddelene). Tento zdroj je zastaralý, použite namiesto neho Server Kolab Groupware. +Comment[sr]=Омогућава приÑтуп Колабовим групверÑким фаÑциклама на ИМÐП Ñерверу (ИМÐП налози Ñе морају одвојено поÑтавити). Овај реÑÑƒÑ€Ñ Ñ˜Ðµ заÑтарео, кориÑтите умеÑто њега реÑÑƒÑ€Ñ â€žÐšÐ¾Ð»Ð°Ð±Ð¾Ð² групверÑки Ñервер“. +Comment[sr@ijekavian]=Омогућава приÑтуп Колабовим групверÑким фаÑциклама на ИМÐП Ñерверу (ИМÐП налози Ñе морају одвојено поÑтавити). Овај реÑÑƒÑ€Ñ Ñ˜Ðµ заÑтарео, кориÑтите умеÑто њега реÑÑƒÑ€Ñ â€žÐšÐ¾Ð»Ð°Ð±Ð¾Ð² групверÑки Ñервер“. +Comment[sr@ijekavianlatin]=Omogućava pristup Kolabovim grupverskim fasciklama na IMAP serveru (IMAP nalozi se moraju odvojeno postaviti). Ovaj resurs je zastareo, koristite umesto njega resurs „Kolabov grupverski server“. +Comment[sr@latin]=Omogućava pristup Kolabovim grupverskim fasciklama na IMAP serveru (IMAP nalozi se moraju odvojeno postaviti). Ovaj resurs je zastareo, koristite umesto njega resurs „Kolabov grupverski server“. +Comment[sv]=Ger tillgÃ¥ng till Kolab grupprogramkorgar pÃ¥ en IMAP-server (IMAP-konton mÃ¥ste ställas in separat). Resursen är förÃ¥ldrad, använd istället Kolab grupprogramserver. +Comment[uk]=Ðадає доÑтуп до тек групової роботи Kolab на Ñервері IMAP (облікові запиÑи IMAP Ñлід налаштувати окремо). Цей реÑÑƒÑ€Ñ Ð²Ð²Ð°Ð¶Ð°Ñ”Ñ‚ÑŒÑÑ Ð·Ð°Ñтарілим, заміÑÑ‚ÑŒ нього Ñлід викориÑтовувати реÑÑƒÑ€Ñ Ñервера групової роботи Kolab. +Comment[x-test]=xxProvides access to Kolab groupware folders on an IMAP server (IMAP accounts need to be set up separately). This resource is obsolete, use the Kolab Groupware Server resource instead.xx +Comment[zh_TW]=æ供存å–æŸ IMAP 伺æœå™¨ä¸Šçš„ Kolab 群組資料夾(IMAP 帳號è¦åˆ†åˆ¥è¨­å®šï¼‰ã€‚此資æºå·²å»¢æ£„,請改用 Kolab 群組伺æœå™¨è³‡æºã€‚ +Type=AkonadiResource +Exec=akonadi_kolabproxy_resource +Icon=kolab + +X-Akonadi-MimeTypes=text/directory,text/calendar,application/x-vnd.kde.contactgroup,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy,text/x-vnd.akonadi.note +X-Akonadi-Capabilities=Resource,Unique +X-Akonadi-Identifier=akonadi_kolabproxy_resource diff --git a/kdepim-runtime/resources/kolabproxy/kolabproxyresource.h b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.h new file mode 100644 index 00000000..25866775 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.h @@ -0,0 +1,144 @@ +/* + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_KOLABPROXYRESOURCE_H +#define KOLABPROXY_KOLABPROXYRESOURCE_H + +#include "kolabhandler.h" +#include "handlermanager.h" + +#include + +class FreeBusyUpdateHandler; + +namespace Akonadi { + class Monitor; +} + +class KolabProxyResource : public Akonadi::ResourceBase, + public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + explicit KolabProxyResource( const QString &id ); + ~KolabProxyResource(); + + /** + * Creates a new KolabHandler for @p imapCollection given it actually is + * a Kolab folder. + * + * @return @c true if @p imapCollection is a Kolab folder, @c false otherwise. + */ + bool registerHandlerForCollection( const Akonadi::Collection &imapCollection ); + + QString imapResourceForCollection( Akonadi::Collection::Id id ); + + void updateHiddenAttribute( const Akonadi::Collection &imapCollection ); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + + void retrieveItems( const Akonadi::Collection &col ); + + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + void imapItemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + + void imapItemMoved( const Akonadi::Item &item, + const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + void imapItemRemoved( const Akonadi::Item &item ); + + void imapCollectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ); + + void imapCollectionRemoved( const Akonadi::Collection &collection ); + + void imapCollectionChanged( const Akonadi::Collection &collection ); + + void imapCollectionMoved( const Akonadi::Collection &collection, + const Akonadi::Collection &source, + const Akonadi::Collection &destination ); + + void retrieveItemFetchDone( KJob * ); + void retrieveItemsFetchDone( KJob * ); + void retrieveCollectionsTreeDone( KJob *job ); + + protected: + virtual void aboutToQuit(); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + + virtual void itemMoved( const Akonadi::Item &item, + const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + virtual void itemRemoved( const Akonadi::Item &item ); + + virtual void collectionAdded( const Akonadi::Collection &collection, + const Akonadi::Collection &parent ); + + virtual void collectionChanged( const Akonadi::Collection &collection ); + + // do not hide the other variant, use implementation from base class + // which just forwards to the one above + using Akonadi::AgentBase::ObserverV2::collectionChanged; + virtual void collectionMoved( const Akonadi::Collection &collection, + const Akonadi::Collection &source, + const Akonadi::Collection &destination ); + + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + private: + Akonadi::Collection createCollection( const Akonadi::Collection &imapCollection ); + void createItem( const Akonadi::Collection &imapCollection, const Akonadi::Item &kolabItem ); + + void applyAttributesToImap( Akonadi::Collection &imapCollection, + const Akonadi::Collection &kolabCollection ); + + void applyAttributesFromImap( Akonadi::Collection &kolabCollection, + const Akonadi::Collection &imapCollection ); + + void updateFreeBusyInformation( const Akonadi::Collection &imapCollection ); + + private slots: + void onItemAddedDone(KJob *job); + void onItemChangedDone(KJob *job); + void imapFolderCreateResult( KJob *job ); + void kolabFolderChangeResult( KJob *job ); + void checkResult( KJob *job ); + + private: + void showErrorMessage(const QString &); + void removeFolder( const Akonadi::Collection &imapCollection ); + Akonadi::Monitor *m_monitor; + Akonadi::Monitor *m_collectionMonitor; + QList m_excludeAppend; + FreeBusyUpdateHandler *m_freeBusyUpdateHandler; + QScopedPointer mHandlerManager; +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/kolabproxyresource.kcfg b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.kcfg new file mode 100644 index 00000000..1aab5914 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabproxyresource.kcfg @@ -0,0 +1,18 @@ + + + + + + + false + + + + true + + + diff --git a/kdepim-runtime/resources/kolabproxy/kolabsettings.ui b/kdepim-runtime/resources/kolabproxy/kolabsettings.ui new file mode 100644 index 00000000..1ab92992 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/kolabsettings.ui @@ -0,0 +1,195 @@ + + + SetupKolabView + + + + 0 + 0 + 667 + 491 + + + + + QLayout::SetMinimumSize + + + + + + 75 + true + + + + Account Type: Kolab Account + + + + + + + + + + 1 + + + + + + + The kolab resource needs an Imap resource. + + + + + + + Add Imap Resource + + + + + + + + + + + + + Select IMAP-Account: + + + true + + + + + + + IMAP-Account used for Kolab-Groupware. + + + + + + + + + + 0 + 0 + + + + Account Settings + + + + QLayout::SetMinimumSize + + + QFormLayout::ExpandingFieldsGrow + + + + + Create/Recover KolabFolders in IMAP-Account: + + + + + + + Ensure that Kolab-Groupware-Folders are existing in IMAP-Account. + + + Create Kolab Folders + + + + + + + Current Format Version: + + + + + + + Change the Kolab-Format Version: + + + + + + + Convert Account to different format version. + + + Change Format Version + + + + + + + TextLabel + + + + + + + + + + Note that the Kolab-Resource will use all available IMAP accounts that contain Kolab folders to provide Groupware-Data. The selection below applies to the settings only. + + + true + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + + KSeparator + QFrame +
kseparator.h
+ 1 +
+ + KComboBox + QComboBox +
kcombobox.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/kolabproxy/notehandler.cpp b/kdepim-runtime/resources/kolabproxy/notehandler.cpp new file mode 100644 index 00000000..96b444cb --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/notehandler.cpp @@ -0,0 +1,104 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2012 Christian Mollekopf + + 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 "notehandler.h" +#include + +NotesHandler::NotesHandler( const Akonadi::Collection &imapCollection ) + : JournalHandler( imapCollection ) +{ + m_mimeType = "application/x-vnd.kolab.note"; +} + +QStringList NotesHandler::contentMimeTypes() +{ + return QStringList() << QLatin1String( "text/x-vnd.akonadi.note" ); +} + +bool NotesHandler::toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ) +{ + if ( item.hasPayload() ) { + KMime::Message::Ptr note = item.payload(); + KMime::Message::Ptr msg = Kolab::KolabObjectWriter::writeNote( note, m_formatVersion ); + if ( checkForErrors( item.id() ) ) { + return false; + } + imapItem.setMimeType( QLatin1String("message/rfc822") ); + imapItem.setPayload( msg ); + } else { + kWarning() << "Payload is not a note!"; + return false; + } + return true; +} + +Akonadi::Item::List NotesHandler::translateItems( const Akonadi::Item::List &kolabItems ) +{ + Akonadi::Item::List newItems; + foreach ( const Akonadi::Item &item, kolabItems ) { + if ( !item.hasPayload() ) { + kWarning() << "Payload is not a MessagePtr!"; + continue; + } + const KMime::Message::Ptr payload = item.payload(); + Akonadi::Item noteItem( QLatin1String("text/x-vnd.akonadi.note") ); + bool ret = noteFromKolab( payload, noteItem ); + if ( checkForErrors( item.id() ) ) { + continue; + } + if ( ret ) { + noteItem.setRemoteId( QString::number( item.id() ) ); + newItems.append( noteItem ); + } else { + kWarning() << "Failed to convert kolab item ( id:" << item.id() + << "rid:" << item.remoteId() << ") to Note message"; + continue; + } + } + + return newItems; +} + +QString NotesHandler::iconName() const +{ + return QString::fromLatin1( "view-pim-notes" ); +} + +bool NotesHandler::noteFromKolab( const KMime::Message::Ptr &kolabMsg, Akonadi::Item ¬eItem ) +{ + Kolab::KolabObjectReader reader( kolabMsg ); + if ( reader.getType() != Kolab::NoteObject ) { + return false; + } + noteItem.setPayload( reader.getNote() ); + return true; +} + +QString NotesHandler::extractGid(const Akonadi::Item& kolabItem) +{ + if ( !kolabItem.hasPayload() ) { + kWarning() << "Payload is not a MessagePtr!"; + return QString(); + } + const KMime::Message::Ptr payload = kolabItem.payload(); + const Akonadi::NoteUtils::NoteMessageWrapper note(payload); + return note.uid(); +} + diff --git a/kdepim-runtime/resources/kolabproxy/notehandler.h b/kdepim-runtime/resources/kolabproxy/notehandler.h new file mode 100644 index 00000000..b76ee4db --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/notehandler.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_NOTEHANDLER_H +#define KOLABPROXY_NOTEHANDLER_H + +#include "journalhandler.h" + +class NotesHandler : public JournalHandler +{ + public: + explicit NotesHandler( const Akonadi::Collection &imapCollection ); + + virtual Akonadi::Item::List translateItems( const Akonadi::Item::List &kolabItems ); + virtual bool toKolabFormat( const Akonadi::Item &item, Akonadi::Item &imapItem ); + virtual QStringList contentMimeTypes(); + virtual QString iconName() const; + + virtual QString extractGid(const Akonadi::Item& kolabItem); + + private: + bool noteFromKolab( const KMime::Message::Ptr &kolabMsg, Akonadi::Item ¬eItem ); + void noteToKolab( const KMime::Message::Ptr ¬e, Akonadi::Item &kolabItem ); + +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/org.freedesktop.Akonadi.kolabproxy.xml b/kdepim-runtime/resources/kolabproxy/org.freedesktop.Akonadi.kolabproxy.xml new file mode 100644 index 00000000..2d1f2b1a --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/org.freedesktop.Akonadi.kolabproxy.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/kdepim-runtime/resources/kolabproxy/revertitemchangesjob.cpp b/kdepim-runtime/resources/kolabproxy/revertitemchangesjob.cpp new file mode 100644 index 00000000..d2c50745 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/revertitemchangesjob.cpp @@ -0,0 +1,87 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 "revertitemchangesjob.h" +#include +#include +#include +#include + +RevertItemChangesJob::RevertItemChangesJob(const Akonadi::Item& kolabItem, HandlerManager &handlerManager, QObject* parent) + :KJob(parent), + mHandlerManager(handlerManager), + mKolabItem(kolabItem) +{ + +} + +void RevertItemChangesJob::start() +{ + Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(kolabToImap(mKolabItem), this); + itemFetchJob->fetchScope().fetchFullPayload(); + itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); + connect(itemFetchJob, SIGNAL(result(KJob*)), SLOT(onImapItemFetchDone(KJob*))); +} + +void RevertItemChangesJob::onImapItemFetchDone(KJob* job) +{ + if ( job->error() ) { + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + emitResult(); + return; + } + + Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); + if (fetchJob->items().isEmpty()) { //The corresponding imap item hasn't been created yet + kDebug() << "item is not yet created in imap resource, deleting the kolab item"; + Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(mKolabItem, this); + connect(deleteJob, SIGNAL(result(KJob*)), SLOT(onItemModifyDone(KJob*))); + } else { + kDebug() << "reverting to state of imap item"; + Akonadi::Item imapItem = fetchJob->items().first(); + const KolabHandler::Ptr handler = mHandlerManager.getHandler(imapItem.parentCollection().id()); + if (!handler) { + kWarning() << "No handler: " << imapItem.parentCollection().id(); + setError(KJob::UserDefinedError); + emitResult(); + return; + } + const Akonadi::Item::List translatedItems = handler->translateItems(fetchJob->items()); + + if (translatedItems.isEmpty()) { + kWarning() << "Failed to reload item: " << mKolabItem.id(); + setError(KJob::UserDefinedError); + emitResult(); + return; + } + Akonadi::Item kolabItem = translatedItems.first(); + kolabItem.setId(mKolabItem.id()); + Akonadi::ItemModifyJob *mjob = new Akonadi::ItemModifyJob(kolabItem); + connect(mjob, SIGNAL(result(KJob*)), SLOT(onItemModifyDone(KJob*))); + } +} + +void RevertItemChangesJob::onItemModifyDone(KJob *job) +{ + if (job->error()) { + setError(KJob::UserDefinedError); + setErrorText(job->errorText()); + } + emitResult(); +} diff --git a/kdepim-runtime/resources/kolabproxy/revertitemchangesjob.h b/kdepim-runtime/resources/kolabproxy/revertitemchangesjob.h new file mode 100644 index 00000000..443d5a4c --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/revertitemchangesjob.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 REVERTITEMCHANGESJOB +#define REVERTITEMCHANGESJOB + +#include +#include "handlermanager.h" + +/** + * Restore kolab item to the state of imapItem + */ +class RevertItemChangesJob: public KJob +{ + Q_OBJECT +public: + RevertItemChangesJob(const Akonadi::Item &kolabItem, HandlerManager &handlerManager, QObject* parent); + virtual void start(); + +private slots: + void onImapItemFetchDone(KJob* job); + void onItemModifyDone(KJob *job); + +private: + HandlerManager &mHandlerManager; + const Akonadi::Item mKolabItem; +}; + +#endif \ No newline at end of file diff --git a/kdepim-runtime/resources/kolabproxy/settings.kcfgc b/kdepim-runtime/resources/kolabproxy/settings.kcfgc new file mode 100644 index 00000000..fda5e24c --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/settings.kcfgc @@ -0,0 +1,8 @@ +File=kolabproxyresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.cpp b/kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.cpp new file mode 100644 index 00000000..bf86946a --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.cpp @@ -0,0 +1,175 @@ +/* + Copyright (c) 2010 Volker Krause + Copyright (c) 2012 Christian Mollekopf + + 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 "setupdefaultfoldersjob.h" + +#include "kolabdefs.h" + +#include "collectionannotationsattribute.h" //from shared + +#include +#include +#include +#include +#include + +#include + +SetupDefaultFoldersJob::SetupDefaultFoldersJob( const Akonadi::AgentInstance &instance, + QObject *parent ) + : Job( parent ), + m_agentInstance( instance ) +{ + Q_ASSERT( instance.isValid() ); +} + +void SetupDefaultFoldersJob::doStart() +{ + Akonadi::CollectionFetchJob *job = + new Akonadi::CollectionFetchJob( Akonadi::Collection::root(), + Akonadi::CollectionFetchJob::Recursive, this ); + job->fetchScope().setResource( m_agentInstance.identifier() ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionFetchResult(KJob*)) ); +} + +void SetupDefaultFoldersJob::collectionFetchResult( KJob *job ) +{ + if ( job->error() ) { + return; // Akonadi::Job propagates that automatically + } + + const Akonadi::Collection::List &collections = static_cast( job )->collections(); + + //FIXME: This should really look for the personal namespace, and use the folder there. + // As a workaround we try to use the inbox, and fallback to toplevel otherwise. + + // look for inbox + Akonadi::Collection defaultParent; + foreach ( const Akonadi::Collection &col, collections ) { + if ( !( col.rights() & Akonadi::Collection::CanCreateCollection ) ) { + continue; + } + Akonadi::EntityDisplayAttribute *attr = 0; + if ( ( attr = col.attribute() ) ) { + if ( attr->iconName() == QLatin1String( "mail-folder-inbox" ) ) { + defaultParent = col; + } + } + } + + if (!defaultParent.isValid()) { //get the toplevel collection as fallback + foreach ( const Akonadi::Collection &col, collections ) { + if ( col.parentCollection() == Akonadi::Collection::root() ) { + defaultParent = col; + } + } + } + + if (!defaultParent.isValid()) { + setError(KJob::UserDefinedError); + setErrorText(i18n("Could not find valid parent collection.")); + emitResult(); + } + kDebug() << "default parent " << defaultParent.id(); + + // look for existing folders + QVector existingDefaultFolders( KolabV2::FolderTypeSize ); + QVector recoveryCandidates( KolabV2::FolderTypeSize ); + foreach ( const Akonadi::Collection &col, collections ) { + if ( col.parentCollection() != defaultParent ) { + continue; + } + KolabV2::FolderType folderType = KolabV2::Mail; + Akonadi::CollectionAnnotationsAttribute *attr = 0; + if ( ( attr = col.attribute() ) ) { + folderType = + KolabV2::folderTypeFromString( Kolab::getFolderTypeAnnotation( attr->annotations() ) ); + } + KolabV2::FolderType guessedType = KolabV2::guessFolderTypeFromName( col.name() ); + + if ( folderType != KolabV2::Mail ) { + existingDefaultFolders[ folderType ] = col; + } else if ( guessedType != KolabV2::Mail ) { + recoveryCandidates[ guessedType ] = col; + } + } + + // create/fix folders + for ( int i = KolabV2::Contact; i < KolabV2::FolderTypeSize; ++i ) { + QString iconName; + if ( i == KolabV2::Mail ) { + //Nothing + } else if ( i == KolabV2::Contact ) { + iconName = QString::fromLatin1( "view-pim-contacts" ); + } else if ( i == KolabV2::Event ) { + iconName = QString::fromLatin1( "view-calendar" ); + } else if ( i == KolabV2::Task ) { + iconName = QString::fromLatin1( "view-calendar-tasks" ); + } else if ( i == KolabV2::Journal ) { + iconName = QString::fromLatin1( "view-pim-journal" ); + } else if ( i == KolabV2::Note ) { + iconName = QString::fromLatin1( "view-pim-notes" ); + } + + if ( existingDefaultFolders[ i ].isValid() ) { + kDebug() << "Existing collection ok: " << iconName; + continue; // all good + } else if ( recoveryCandidates[ i ].isValid() ) { + Akonadi::Collection col = recoveryCandidates[ i ]; + if ( !( col.rights() & Akonadi::Collection::CanChangeCollection ) ) { + kWarning() << "no change rights on collection"; + continue; + } + Akonadi::CollectionAnnotationsAttribute *attr = + col.attribute( Akonadi::Entity::AddIfMissing ); + + QMap annotations; + Kolab::setFolderTypeAnnotation( annotations, KolabV2::folderTypeToString( static_cast( i ), true ) ); + attr->setAnnotations( annotations ); + if ( !iconName.isEmpty() ) { + Akonadi::EntityDisplayAttribute *attribute = + col.attribute( Akonadi::Entity::AddIfMissing ); + attribute->setIconName( iconName ); + } + kDebug() << "Fixing collection: " << col.id() << iconName; + new Akonadi::CollectionModifyJob( col, 0 ); + } else { + Akonadi::Collection col; + col.setName( KolabV2::nameForFolderType( static_cast( i ) ) ); + col.setParentCollection( defaultParent ); + Akonadi::CollectionAnnotationsAttribute *attr = + col.attribute( Akonadi::Entity::AddIfMissing ); + + QMap annotations; + Kolab::setFolderTypeAnnotation( annotations, KolabV2::folderTypeToString( static_cast( i ), true ) ); + attr->setAnnotations( annotations ); + if ( !iconName.isEmpty() ) { + Akonadi::EntityDisplayAttribute *attribute = + col.attribute( Akonadi::Entity::AddIfMissing ); + attribute->setIconName( iconName ); + } + kDebug() << "Creating new collection: " << iconName; + new Akonadi::CollectionCreateJob( col, 0 ); + } + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.h b/kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.h new file mode 100644 index 00000000..1eb6b0f3 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/setupdefaultfoldersjob.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2010 Volker Krause + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_SETUPDEFAULTFOLDERSJOB_H +#define KOLABPROXY_SETUPDEFAULTFOLDERSJOB_H + +#include +#include + +class SetupDefaultFoldersJob : public Akonadi::Job +{ + Q_OBJECT + public: + explicit SetupDefaultFoldersJob( const Akonadi::AgentInstance &instance, QObject *parent = 0 ); + + protected: + void doStart(); + + private slots: + void collectionFetchResult( KJob *job ); + + private: + Akonadi::AgentInstance m_agentInstance; +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/setupkolab.cpp b/kdepim-runtime/resources/kolabproxy/setupkolab.cpp new file mode 100644 index 00000000..ab0c5465 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/setupkolab.cpp @@ -0,0 +1,228 @@ +/* + Copyright (c) 2010 Laurent Montel + Copyright (c) 2012 Christian Mollekopf + + 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 "setupkolab.h" + +#include "kolabproxyresource.h" +#include "setupdefaultfoldersjob.h" +#include "upgradejob.h" + +#include + +#include +#include + +#include + +#define IMAP_RESOURCE_IDENTIFIER QLatin1String("akonadi_imap_resource") + +SetupKolab::SetupKolab( KolabProxyResource *parentResource ) + : KDialog(), + m_ui( new Ui::SetupKolabView ), + m_versionUi( new Ui::ChangeFormatView ), + m_parentResource( parentResource ) +{ + m_ui->setupUi( mainWidget() ); + setButtons( Ok ); + initConnection(); + updateCombobox(); +} + +SetupKolab::~SetupKolab() +{ + delete m_ui; + delete m_versionUi; +} + +KConfigGroup SetupKolab::getConfigGroup() +{ + //This is a bit of a hack, but we have to reload the config file in case it was edited by the setupwizard (it's not reloaded automatically). + //Without this the config will not be able to load the correct values until restarted (e.g. akonadiconsole). + KSharedConfigPtr config = KGlobal::mainComponent().config(); + config->reparseConfiguration(); + return KConfigGroup( KGlobal::mainComponent().config(), "KolabProxyResourceSettings" ); +} + +Kolab::Version SetupKolab::readKolabVersion( const QString &resourceIdentifier ) +{ + KConfigGroup grp( getConfigGroup() ); + if ( resourceIdentifier.isEmpty() ) { + return Kolab::KolabV3; + } + const QString key (QLatin1String("KolabFormatVersion") + resourceIdentifier ); + Kolab::Version version = static_cast( + grp.readEntry( key, static_cast( Kolab::KolabV3 ) ) ); + return version; +} + +void SetupKolab::initConnection() +{ + connect( m_ui->launchWizard, SIGNAL(clicked()), this, SLOT(slotLaunchWizard()) ); + connect( m_ui->createKolabFolderButton, SIGNAL(clicked()), + this, SLOT(slotCreateDefaultKolabCollections()) ); + connect( m_ui->upgradeFormatButton, SIGNAL(clicked()), + this, SLOT(slotShowUpgradeDialog()) ); + connect( m_ui->imapAccountComboBox, SIGNAL(currentIndexChanged(QString)), + this, SLOT(slotSelectedAccountChanged()) ); + connect( Akonadi::AgentManager::self(), SIGNAL(instanceAdded(Akonadi::AgentInstance)), + this, SLOT(slotInstanceAddedRemoved()) ); + connect( Akonadi::AgentManager::self(), SIGNAL(instanceRemoved(Akonadi::AgentInstance)), + this, SLOT(slotInstanceAddedRemoved()) ); +} + +void SetupKolab::slotShowUpgradeDialog() +{ + const Akonadi::AgentInstance instanceSelected = + m_agentList[m_ui->imapAccountComboBox->currentText()]; + + KDialog *dialog = new KDialog( this ); + dialog->setButtons( Ok ); + m_versionUi->setupUi( dialog->mainWidget() ); + m_versionUi->progressBar->setDisabled( true ); + connect( m_versionUi->pushButton, SIGNAL(clicked()), this, SLOT(slotDoUpgrade()) ); + + Kolab::Version v = readKolabVersion( instanceSelected.identifier() ); + + m_versionUi->formatVersion->insertItem( 0, i18n("Kolab Format v2"), Kolab::KolabV2 ); + m_versionUi->formatVersion->insertItem( 1, i18n("Kolab Format v3"), Kolab::KolabV3 ); + if ( v == Kolab::KolabV2 ) { + m_versionUi->formatVersion->setCurrentIndex( 0 ); + } else { + m_versionUi->formatVersion->setCurrentIndex( 1 ); + } + KConfigGroup grp( getConfigGroup() ); + m_versionUi->upgradeGroupBox->setEnabled( grp.readEntry("UpgradeEnabled", false) ); + dialog->exec(); + grp.writeEntry( + QLatin1String("KolabFormatVersion") + instanceSelected.identifier(), + m_versionUi->formatVersion->itemData( m_versionUi->formatVersion->currentIndex() ) ); + grp.sync(); + slotSelectedAccountChanged(); + dialog->deleteLater(); +} + +void SetupKolab::slotDoUpgrade() +{ + const Akonadi::AgentInstance instanceSelected = + m_agentList[m_ui->imapAccountComboBox->currentText()]; + + m_versionUi->statusLabel->setText( i18n("Started") ); + m_versionUi->progressBar->setEnabled( true ); + + UpgradeJob *job = + new UpgradeJob( + static_cast( + m_versionUi->formatVersion->itemData( + m_versionUi->formatVersion->currentIndex() ).toInt() ), + instanceSelected, this ); + + connect( job, SIGNAL(percent(KJob*,ulong)), + this, SLOT(slotUpgradeProgress(KJob*,ulong)) ); + connect( job, SIGNAL(result(KJob*)), + this, SLOT(slotUpgradeDone(KJob*)) ); +} + +void SetupKolab::slotUpgradeProgress( KJob *job, ulong value ) +{ + Q_UNUSED( job ); + m_versionUi->progressBar->setValue( value ); +} + +void SetupKolab::slotUpgradeDone( KJob *job ) +{ + if ( job->error() ) { + kWarning() << job->errorString(); + m_versionUi->statusLabel->setText( i18n("Error") ); + KMessageBox::error( + this, + i18n( "Could not complete the upgrade process: " ) + job->errorString(), + i18n( "Error during Upgrade Process" ) ); + return; + } + m_versionUi->statusLabel->setText( i18n("Complete") ); + m_versionUi->progressBar->setValue( 100 ); +} + +void SetupKolab::slotSelectedAccountChanged() +{ + const Akonadi::AgentInstance instanceSelected = + m_agentList[m_ui->imapAccountComboBox->currentText()]; + + Kolab::Version v = readKolabVersion( instanceSelected.identifier() ); + + if ( v == Kolab::KolabV2 ) { + m_ui->formatVersion->setText( i18n("Kolab Format v2") ); + } else { + m_ui->formatVersion->setText( i18n("Kolab Format v3") ); + } +} + +void SetupKolab::updateCombobox() +{ + bool imapAccountFound = false; + m_ui->imapAccountComboBox->clear(); + m_agentList.clear(); + + foreach ( const Akonadi::AgentInstance &instance, Akonadi::AgentManager::self()->instances() ) { + if ( instance.identifier().contains( IMAP_RESOURCE_IDENTIFIER ) ) { + const QString instanceName = instance.name(); + m_agentList.insert( instanceName, instance ); + m_ui->imapAccountComboBox->addItem( instanceName ); + imapAccountFound = true; + } + } + if ( imapAccountFound ) { + m_ui->stackedWidget->setCurrentIndex( 1 ); + } else { + m_ui->stackedWidget->setCurrentIndex( 0 ); + } +} + +void SetupKolab::slotLaunchWizard() +{ + QStringList lst; + lst.append( QLatin1String("--assistant" )); + lst.append( QLatin1String("imap") ); + + const QString path = KStandardDirs::findExe( QLatin1String( "accountwizard" ) ); + if ( !QProcess::startDetached( path, lst ) ) { + KMessageBox::error( + this, + i18n( "Could not start the account wizard. " + "Please check your installation." ), + i18n( "Unable to start account wizard" ) ); + } +} + +void SetupKolab::slotInstanceAddedRemoved() +{ + updateCombobox(); +} + +void SetupKolab::slotCreateDefaultKolabCollections() +{ + const Akonadi::AgentInstance instanceSelected = + m_agentList[m_ui->imapAccountComboBox->currentText()]; + + if ( instanceSelected.isValid() ) { + new SetupDefaultFoldersJob( instanceSelected, this ); + } +} + diff --git a/kdepim-runtime/resources/kolabproxy/setupkolab.h b/kdepim-runtime/resources/kolabproxy/setupkolab.h new file mode 100644 index 00000000..2beb71b7 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/setupkolab.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2010 Laurent Montel + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_SETUPKOLAB_H +#define KOLABPROXY_SETUPKOLAB_H + +#include "ui_changeformat.h" +#include "ui_kolabsettings.h" + +#include + +#include +#include + +class KolabProxyResource; + +class KJob; + +class SetupKolab : public KDialog +{ + Q_OBJECT + + public: + explicit SetupKolab( KolabProxyResource *parentResource ); + ~SetupKolab(); + + static KConfigGroup getConfigGroup(); + static Kolab::Version readKolabVersion( const QString &resourceIdentifier ); + + protected: + void initConnection(); + void updateCombobox(); + + protected slots: + void slotLaunchWizard(); + void slotInstanceAddedRemoved(); + void slotCreateDefaultKolabCollections(); + void slotShowUpgradeDialog(); + void slotDoUpgrade(); + void slotSelectedAccountChanged(); + void slotUpgradeProgress( KJob *, ulong ); + void slotUpgradeDone( KJob * ); + + private: + QMap m_agentList; + Ui::SetupKolabView *m_ui; + Ui::ChangeFormatView *m_versionUi; + KolabProxyResource *m_parentResource; +}; + +#endif /* SETUPKOLAB_H */ + diff --git a/kdepim-runtime/resources/kolabproxy/taskshandler.cpp b/kdepim-runtime/resources/kolabproxy/taskshandler.cpp new file mode 100644 index 00000000..ec8cf6ef --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/taskshandler.cpp @@ -0,0 +1,50 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 "taskshandler.h" + +TasksHandler::TasksHandler( const Akonadi::Collection &imapCollection ) + : IncidenceHandler( imapCollection ) +{ + m_mimeType = "application/x-vnd.kolab.task"; +} + +TasksHandler::~TasksHandler() +{ +} + +KMime::Message::Ptr TasksHandler::incidenceToMime( const KCalCore::Incidence::Ptr &incidence ) +{ + return + Kolab::KolabObjectWriter::writeTodo( + incidence.dynamicCast(), + m_formatVersion, PRODUCT_ID, QLatin1String("UTC") ); +} + +QStringList TasksHandler::contentMimeTypes() +{ + return QStringList() << KCalCore::Todo::todoMimeType(); +} + +QString TasksHandler::iconName() const +{ + return QString::fromLatin1( "view-calendar-tasks" ); +} diff --git a/kdepim-runtime/resources/kolabproxy/taskshandler.h b/kdepim-runtime/resources/kolabproxy/taskshandler.h new file mode 100644 index 00000000..ccfca135 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/taskshandler.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + Copyright (c) 2012 Christian Mollekopf + + 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 KOLABPROXY_TASKSHANDLER_H +#define KOLABPROXY_TASKSHANDLER_H + +#include "incidencehandler.h" + +/** + @author Andras Mantia +*/ +class TasksHandler : public IncidenceHandler +{ + public: + explicit TasksHandler( const Akonadi::Collection &imapCollection ); + virtual ~TasksHandler(); + virtual QStringList contentMimeTypes(); + virtual QString iconName() const; + + private: + virtual KMime::Message::Ptr incidenceToMime( const KCalCore::Incidence::Ptr &incidence ); + +}; + +#endif diff --git a/kdepim-runtime/resources/kolabproxy/tests/CMakeLists.txt b/kdepim-runtime/resources/kolabproxy/tests/CMakeLists.txt new file mode 100644 index 00000000..c456e2df --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/CMakeLists.txt @@ -0,0 +1,42 @@ + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ) +set( RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + +foreach( _file ${kolabproxyresource_shared_SRCS} ) + list( APPEND kolabproxy_shared_relative_SRCS "../${_file}" ) +endforeach() + +kde4_add_library(kolabresourcetest STATIC + ${kolabproxy_shared_relative_SRCS} + ${AKONADI_COLLECTIONATTRIBUTES_SHARED_SOURCES} + testutils.cpp +) + +kde4_add_unit_test(kolabconvertertest kolabconvertertest.cpp) +target_link_libraries(kolabconvertertest + kolabresourcetest + ${Libkolab_LIBRARIES} + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTXML_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${QT_QTTEST_LIBRARY} + kdepim-copy + ${KDEPIMLIBS_KCALCORE_LIBS} +) + +#akonadi_add_resourcetest( imap-kolab imaptest-kolab.es ) +#akonadi_add_resourcetest( imap-dovecot imaptest-dovecot.es ) +#akonadi_add_resourcetest( kolab kolabtest.es ) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) +include(AkonadiMacros) +set(KDEPIMLIBS_RUN_ISOLATED_TESTS TRUE) +set(KDEPIMLIBS_RUN_SQLITE_ISOLATED_TESTS TRUE) + +add_akonadi_isolated_test_advanced(proxyintegrationtest.cpp "" "kolabresourcetest;${Libkolab_LIBRARIES}") +add_akonadi_isolated_test_advanced(imapsignaltest.cpp "" "kolabresourcetest;${Libkolab_LIBRARIES}") +add_akonadi_isolated_test_advanced(clientsidetest.cpp "" "kolabresourcetest;${Libkolab_LIBRARIES}") diff --git a/kdepim-runtime/resources/kolabproxy/tests/clientsidetest.cpp b/kdepim-runtime/resources/kolabproxy/tests/clientsidetest.cpp new file mode 100644 index 00000000..2d9a2a26 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/clientsidetest.cpp @@ -0,0 +1,237 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //libkolab +#include + +#include "../kolabdefs.h" +#include "testutils.h" + +Q_DECLARE_METATYPE(QSet) + +using namespace Akonadi; + +/* + * Tests clientside actions against the kolab proxy, such as creating an item. + */ +class ClientSideTest : public QObject +{ + Q_OBJECT + + AgentInstance mInstance; + Akonadi::Collection imapCollection; + Akonadi::Collection kolabCollection; + +public: + ClientSideTest(): + QObject() + { + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType >(); + } + + void cleanup() { + //cleanup + Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(imapCollection); + deleteJob->exec(); + Akonadi::ItemDeleteJob *deleteJob2 = new Akonadi::ItemDeleteJob(kolabCollection); + deleteJob2->exec(); + QTest::qWait(TIMEOUT); + } + +private slots: + void initTestCase() { + AkonadiTest::checkTestIsIsolated(); + AttributeFactory::registerAttribute(); + + const AgentType type = AgentManager::self()->type("akonadi_kolabproxy_resource"); + AgentInstanceCreateJob *agentCreateJob = new AgentInstanceCreateJob(type); + AKVERIFYEXEC(agentCreateJob); + mInstance = agentCreateJob->instance(); + + //Wait for kolabproxy to create all folders + QVERIFY(TestUtils::ensurePopulated(mInstance.identifier(), 6)); + + Akonadi::CollectionPathResolver *resolver = new CollectionPathResolver(QLatin1String("res1/Calendar"), this); + AKVERIFYEXEC(resolver); + imapCollection = Akonadi::Collection( resolver->collection() ); + QVERIFY(imapCollection.isValid()); + + kolabCollection = TestUtils::findCollection(mInstance.identifier(), "Calendar"); + QVERIFY(kolabCollection.isValid()); + } + + void testItemCreate() + { + KDateTime date(QDate(2013,10,10), KDateTime::ClockTime); + date.setDateOnly(true); + + KCalCore::Event::Ptr event(new KCalCore::Event()); + event->setDtStart(date); + Akonadi::Item createdItem; + { + Akonadi::Item item(event->mimeType()); + item.setPayload(event); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(item, kolabCollection); + QVERIFY(TestUtils::ensure(imapCollection, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), createJob)); + createdItem = createJob->item(); + QVERIFY(createdItem.isValid()); + } + + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(imapCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); +// const Akonadi::Item item = fetchJob->items().first(); +// QCOMPARE(item.id(), createdItem.remoteId().toLongLong()); + } + cleanup(); + } + + void testItemCreateFailure() + { + KCalCore::Event::Ptr event(new KCalCore::Event()); + //an event requires an start date, so this will fail + { + Akonadi::Item item(event->mimeType()); + item.setPayload(event); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(item, kolabCollection); + //Check that the signal is NOT emitted within the timeout + QVERIFY(!TestUtils::ensure(imapCollection, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), createJob)); + //TODO akonadi currently doesn't support failing itemcreatejobs if the resource fails to store the item. + // the item will simply remain dirty in the akonadi server +// QVERIFY(createJob->error()); + } + + //Ensure the item has been removed by the kolabproxy + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(kolabCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 0); + } + } + + void testItemModify() + { + KDateTime date1(QDate(2013,10,10), KDateTime::ClockTime); + date1.setDateOnly(true); + KDateTime date2(QDate(2014,10,10), KDateTime::ClockTime); + date2.setDateOnly(true); + + KCalCore::Event::Ptr event(new KCalCore::Event()); + event->setDtStart(date1); + Akonadi::Item createdItem; + { + Akonadi::Item item(event->mimeType()); + item.setPayload(event); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(item, kolabCollection); + QVERIFY(TestUtils::ensure(imapCollection, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), createJob)); + createdItem = createJob->item(); + QVERIFY(createdItem.isValid()); + } + + { + event->setDtStart(date2); + createdItem.setPayload(event); + Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(createdItem); + QVERIFY(TestUtils::ensure(imapCollection, SIGNAL(itemChanged(Akonadi::Item,QSet)), modifyJob)); + Akonadi::Item modifiedItem = modifyJob->item(); + QVERIFY(modifiedItem.hasPayload()); + QCOMPARE(modifiedItem.payload()->dtStart().toString(), date2.toString()); + } + + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(imapCollection); + fetchJob->fetchScope().fetchFullPayload(true); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); + const Akonadi::Item item = fetchJob->items().first(); + QVERIFY(item.hasPayload()); + Kolab::KolabObjectReader reader(item.payload()); + QCOMPARE(reader.getEvent()->dtStart().toString(), date2.toString()); + } + cleanup(); + } + + void testItemModifyFailure() + { + KDateTime date1(QDate(2013,10,10), KDateTime::ClockTime); + date1.setDateOnly(true); + + KCalCore::Event::Ptr event(new KCalCore::Event()); + event->setDtStart(date1); + Akonadi::Item createdItem; + { + Akonadi::Item item(event->mimeType()); + item.setPayload(event); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(item, kolabCollection); + QVERIFY(TestUtils::ensure(imapCollection, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), createJob)); + createdItem = createJob->item(); + QVERIFY(createdItem.isValid()); + } + + { + event->setDtStart(KDateTime()); + createdItem.setPayload(event); + Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(createdItem); + AKVERIFYEXEC(modifyJob); + QTest::qWait(TIMEOUT); + //FIXME this fails, no idea why +// QVERIFY(!TestUtils::ensure(imapCollection, SIGNAL(itemChanged(Akonadi::Item,QSet)), modifyJob)); + } + + //Ensure the change has been reverted for the kolab item + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(kolabCollection); + fetchJob->fetchScope().fetchFullPayload(); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); + const Akonadi::Item item = fetchJob->items().first(); + QVERIFY(item.hasPayload()); + QCOMPARE(item.payload()->dtStart().toString(), date1.toString()); + } + cleanup(); + } + +}; + +QTEST_AKONADIMAIN( ClientSideTest, NoGUI ) + +#include "clientsidetest.moc" diff --git a/kdepim-runtime/resources/kolabproxy/tests/couriervm.conf b/kdepim-runtime/resources/kolabproxy/tests/couriervm.conf new file mode 100644 index 00000000..539a2f74 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/couriervm.conf @@ -0,0 +1,10 @@ +[Emulator] +Arguments=-m 256M +Ports=22,25,80,143,389,443,465,993 +WaitForPorts=22,25,143 +MonitorPort=23 +SnapshotName=running-server + +[Image] +Source=https://files.kolab.org/local/imapd-testing/qemu-courier-0.53.3-20090923/qemu-courier-server-20090923.tar.bz2 +File=qemu-courier-server/running-server.qcow2 diff --git a/kdepim-runtime/resources/kolabproxy/tests/create_ldap_users.py b/kdepim-runtime/resources/kolabproxy/tests/create_ldap_users.py new file mode 100755 index 00000000..40046a23 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/create_ldap_users.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python +"""Create some few kolab users in ldap. + +Usage: + +create_ldap_users.py [-h hostname] [-p port] [-u admin_dn] [-n num] [-o offset] [-t type] [--member dnmember] basedn cmd [passwd] + +cmd may be one of + + add add num (default 10) users named autoNUM + + delete delete all users named auto*. deletion is done by setting + kolabDeleteFlag so that the kolabd will later remove the users. + +Host and port default to localhost and the standard ldap port. + +The type can be "user" (default) or "group". + +The admin_dn should be the initial part of the dn of the admin account +to use, that ist admin + ',cn=internal,' + base_dn will be used as the +dn for the ldap bind. The default is 'cn=manager'. + +Offset will be the start number for additions and deletion (default = 0). +of users only. + +dn_member is the dn of the initial member for groups. + + +Security considerations: + The connection is not encrypted by default and thus the password + can be sniffed. +""" + +# requires the python-ldap module from +# http://python-ldap.sourceforge.net/ +# on debian woody it's packaged as python-ldap + +#import random +import re +import sys +import getopt +import ldap +import ldap.dn +import ldap.modlist +import getpass +import time +import sha +import base64 +import httplib + +def open_ldap(ldapuri, admin_dn_part, pwd = None): + conn = ldap.initialize(ldapuri) + if not pwd: + pwd = getpass.getpass("ldap bind password:") + conn.simple_bind_s(admin_dn_part + "," + "cn=internal" + "," + base_dn, + pwd) + return conn + +def fetch_kolab_info(conn): + try: + return conn.search_s("k=kolab," + base_dn, ldap.SCOPE_BASE, + filterstr='(objectClass=*)')[0][1] + except ldap.INVALID_DN_SYNTAX: + # should only happen if the server is no Kolab server, because + # k=... is only valid on Kolab servers. Since the script should + # also support non-kolab servers to an extent, we return None to + # indicate that the server is no Kolab server. + return None + +def set_delete_flag(conn, filterstr, offset): + hosts = fetch_kolab_info(conn)["kolabHost"] + result = conn.search_s(base_dn, ldap.SCOPE_ONELEVEL, filterstr=filterstr) + for dn, attrs in result: + n = re.match(r'cn=[^,0-9]+(\d+),',dn).group(1) + if int(n) >= offset: + conn.modify_s(dn, [(ldap.MOD_ADD, "kolabDeleteFlag", hosts)]) + print dn, hosts + +def random_salt(length): + """Returns a random salt for use with salted password hashes""" + random = open("/dev/urandom") + try: + return random.read(length) + finally: + random.close() + +SSHA_PREFIX = "{SSHA}" +def encode_ssha(password, salt): + """SSHA-Encodes the password with the given salt""" + digester = sha.new(password) + digester.update(salt) + return SSHA_PREFIX + base64.b64encode(digester.digest() + salt) + +def encode_clear(password, salt): + return password + + +def mail_domain_from_basedn(base_dn): + """Determine the mail-domain from the dn. + The function simple concatenates the values of the dc=... parts of + the dn separated by periods.""" + print "mail_domain_from_basedn", base_dn + return ".".join([item[0][1] + for item in ldap.dn.str2dn(base_dn) if item[0][0] == "dc"]) + + +def add_user(conn, num_users, offset, set_password=None, + http_hostname="localhost", http_port=80): + common_attrs = { + 'objectClass': ['top', 'inetOrgPerson'], + } + + kolab_info = fetch_kolab_info(conn) + + if kolab_info: + mail_domain = kolab_info["postfix-mydomain"][0] + # use the first of the kolab hosts as home server. + kolab_home_server = kolab_info["kolabHost"][0] + + common_attrs['objectClass'].append("kolabInetOrgPerson") + common_attrs['kolabHomeServer'] = [kolab_home_server] + common_attrs['kolabInvitationPolicy'] = ['ACT_MANUAL'] + + password_encoder = encode_ssha + else: + # The server is not a Kolab server, which currently means that + # it's the courier server in the qemu image. Here, the password + # is unencrypted and the domain can be inferred from the base + # dn. + mail_domain = mail_domain_from_basedn(base_dn) + password_encoder = encode_clear + + if set_password is not None: + common_attrs["userPassword"] = password_encoder(set_password, random_salt(8)) + + users = [("test%d" % n, "auto", "autotest%d" % n) + for n in range(offset, num_users + offset)] + for sn, givenName, mailuid in users: + #time.sleep(random.randrange(0,4)) + descr = common_attrs.copy() + cn = givenName + " " + sn + descr["cn"] = [cn] + descr["givenName"] = [givenName] + descr["sn"] = [sn] + descr["mail"] = descr["uid"] = [mailuid + "@" + mail_domain] + dn = "cn=" + cn + "," + base_dn + print dn, descr + print conn.add_s(dn, ldap.modlist.addModlist(descr)) + + if not kolab_info: + # The server is not a Kolab server, which currently means that + # it's the courier server in the qemu image which requires an + # extra step to finish the user creation process. + conn = httplib.HTTPConnection(http_hostname, http_port) + conn.request("GET", "/courier-maildir-trigger") + response = conn.getresponse() + if response.status != 200: + raise RuntimeError("User creation trigger failed: %d: %s" + % (response.status, response.reason)) + + +def delete_auto_users(conn, offset): + set_delete_flag(conn, "(&(objectClass=kolabInetOrgPerson)(cn=auto*))", + offset) + + +def add_groups(conn, num_entries, member, offset=0): + kolab_info = fetch_kolab_info(conn) + + mail_domain = kolab_info["postfix-mydomain"][0] + # use the first of the kolab hosts as home server. + kolab_home_server = kolab_info["kolabHost"][0] + + common_attrs = { + 'objectClass': ['top', 'groupOfNames', 'kolabGroupOfNames'], + } + + groups = ["testgrp%d@%s" % (n, mail_domain) + for n in range(offset, offset + num_entries)] + for mailuid in groups: + descr = common_attrs.copy() + descr["cn"] = descr["mail"] = [mailuid] + descr["member"] = [member] + dn = "cn=" + mailuid + "," + base_dn + print dn, descr + print conn.add_s(dn, ldap.modlist.addModlist(descr)) + + +def delete_auto_groups(conn, offset=0): + set_delete_flag(conn, "(&(objectClass=kolabGroupOfNames)(cn=testgrp*))", offset) + + +def main(): + global base_dn + + hostname = "localhost" + port = None + admin_dn_part = "cn=manager" + base_dn = None + num_entries = 10 + entry_type = "user" + group_member = None + offset = 0 + set_password = None + + opts, args = getopt.getopt(sys.argv[1:], 'h:p:u:n:o:t:', + ["host=", "port=", "user=", "num=", "offset=", + "set-password=", "type=", "member="]) + for optchar, value in opts: + if optchar in ("-h", "--host"): + hostname = value + elif optchar in ("-p", "--port"): + port = int(value) + elif optchar in ("-u", "--user"): + admin_dn_part = value + elif optchar in ("-o", "--offset"): + offset = int(value) + elif optchar == "--set-password": + set_password = value + elif optchar in ("-t", "--type"): + entry_type = value + elif optchar == "--member": + # dn of the initial member. groups may not be empty in Kolab + group_member = value + elif optchar in ("-n", "--num"): + num_entries = int(value) + else: + print >>sys.stderr, "Unknown option", optchar + sys.exit(1) + + if len(args) < 2: + print >>sys.stderr, "missing parameters" + sys.exit(1) + elif len(args) > 3: + print >>sys.stderr, "too many parameters" + sys.exit(1) + + base_dn, cmd, pwd = args + + if cmd not in ("add", "delete"): + print >>sys.stderr, "unknown command", repr(cmd) + sys.exit(1) + + + if port is not None: + ldap_uri ="ldap://%s:%d" % (hostname, port) + else: + ldap_uri ="ldap://%s" % (hostname,) + + + + # derive the http_port from the ldap port, assuming both are the + # standard ports for the respective protocol and are offset by the + # same value. + if port is not None: + http_port = port - 389 + 80 + else: + http_port = 80 + + conn = open_ldap(ldap_uri, admin_dn_part, pwd) + if entry_type == "user": + if cmd == "add": + add_user(conn, num_entries, offset, set_password=set_password, + http_hostname=hostname, http_port=http_port) + elif cmd == "delete": + delete_auto_users(conn, offset) + elif entry_type == "group": + if cmd == "add": + add_groups(conn, num_entries, group_member, offset) + elif cmd == "delete": + delete_auto_groups(conn, offset) + + + +if __name__ == "__main__": + main() diff --git a/kdepim-runtime/resources/kolabproxy/tests/dovecotvm.conf b/kdepim-runtime/resources/kolabproxy/tests/dovecotvm.conf new file mode 100644 index 00000000..578f6890 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/dovecotvm.conf @@ -0,0 +1,10 @@ +[Emulator] +Arguments=-m 256M +Ports=22,25,80,143,389,443,465,993 +WaitForPorts=22,25,143 +MonitorPort=23 +SnapshotName=running-server + +[Image] +Source=https://files.kolab.org/local/imapd-testing/qemu-dovecot-1.2-kolab-server-2.2.2-20090807/qemu-dovecot-server-20090807.tar.bz2 +File=qemu-dovecot-server/running-server.qcow2 diff --git a/kdepim-runtime/resources/kolabproxy/tests/event.ical b/kdepim-runtime/resources/kolabproxy/tests/event.ical new file mode 100644 index 00000000..a7844eea --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/event.ical @@ -0,0 +1,26 @@ +BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN +VERSION:2.0 +BEGIN:VEVENT +DTSTAMP:20070109T100625Z +ORGANIZER;CN=Volker Krause:MAILTO:vkrause@kde.org +CREATED:20070109T100553Z +UID:libkcal-1135684253.945 +SEQUENCE:1 +LAST-MODIFIED:20070109T100625Z +SUMMARY:Test event +LOCATION:here +CLASS:PUBLIC +PRIORITY:5 +CATEGORIES:KDE +DTSTART:20070109T183000Z +DTEND:20070109T225900Z +TRANSP:OPAQUE +BEGIN:VALARM +DESCRIPTION: +ACTION:DISPLAY +TRIGGER;VALUE=DURATION:-PT45M +END:VALARM +END:VEVENT +END:VCALENDAR + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imap-quicktest.es b/kdepim-runtime/resources/kolabproxy/tests/imap-quicktest.es new file mode 100644 index 00000000..9a0bee0f --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imap-quicktest.es @@ -0,0 +1,28 @@ +/* + Copyright (c) 2009 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. +*/ + + +var imapResource = Resource.newInstance( "akonadi_imap_resource" ); +imapResource.setOption( "ImapServer", "localhost:42143" ); +imapResource.setOption( "UserName", "autotest0@example.com" ); +imapResource.setOption( "Password", "nichtgeheim" ); +imapResource.create(); + +Test.alert( "resource created" ); + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imapsignaltest.cpp b/kdepim-runtime/resources/kolabproxy/tests/imapsignaltest.cpp new file mode 100644 index 00000000..dfa898d2 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imapsignaltest.cpp @@ -0,0 +1,339 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //libkolab +#include + +#include "../kolabdefs.h" +#include "testutils.h" + +using namespace Akonadi; + +Q_DECLARE_METATYPE(QSet) + +class ImapSignalTest : public QObject +{ + Q_OBJECT + + AgentInstance mInstance; + Akonadi::Collection imapCollection; + Akonadi::Collection kolabCollection; + +public: + ImapSignalTest(): + QObject() + { + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType >(); + } + +private: + static Akonadi::Item createImapItem(const KCalCore::Event::Ptr &event) { + const KMime::Message::Ptr &message = Kolab::KolabObjectWriter::writeEvent(event, Kolab::KolabV3, "Proxytest", QLatin1String("UTC") ); + Q_ASSERT(message); + Akonadi::Item imapItem1; + imapItem1.setMimeType( QLatin1String("message/rfc822") ); + imapItem1.setPayload( message ); + return imapItem1; + } + + void cleanup() { + //cleanup +// Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(imapCollection); +// AKVERIFYEXEC(deleteJob); + Akonadi::ItemDeleteJob *deleteJob2 = new Akonadi::ItemDeleteJob(kolabCollection); + AKVERIFYEXEC(deleteJob2); + QTest::qWait(TIMEOUT); + } + +private slots: + void initTestCase() { + AkonadiTest::checkTestIsIsolated(); + AttributeFactory::registerAttribute(); + + const AgentType type = AgentManager::self()->type("akonadi_kolabproxy_resource"); + AgentInstanceCreateJob *agentCreateJob = new AgentInstanceCreateJob(type); + AKVERIFYEXEC(agentCreateJob); + mInstance = agentCreateJob->instance(); + + //Wait for kolabproxy to create all folders + QVERIFY(TestUtils::ensurePopulated(mInstance.identifier(), 5)); + + Akonadi::CollectionPathResolver *resolver = new CollectionPathResolver(QLatin1String("res1/Calendar"), this); + AKVERIFYEXEC(resolver); + imapCollection = Akonadi::Collection( resolver->collection() ); + QVERIFY(imapCollection.isValid()); + + kolabCollection = TestUtils::findCollection(mInstance.identifier(), "Calendar"); + QVERIFY(kolabCollection.isValid()); + } + + + void itemAddedSignal() { + KCalCore::Event::Ptr event(new KCalCore::Event); + event->setSummary("summary1"); + event->setDtStart(KDateTime(QDate(2013,02,01), QTime(1,1), KDateTime::ClockTime)); + + //Create item in imap resource + { + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(createImapItem(event), imapCollection, this); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), createJob)); + } + + //TestUtils::ensure kolab equivalent gets created + { + Akonadi::Item expectedItem; + expectedItem.setGid(event->instanceIdentifier()); + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(expectedItem); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); +// QCOMPARE(fetchJob->items().first().parentCollection().id() ); + } + + //create item again in imap resource (same gid), but with different content + Akonadi::Item recreatdImapItem; + { + event->setSummary("summary2"); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(createImapItem(event), imapCollection, this); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(itemChanged(Akonadi::Item,QSet)), createJob)); + recreatdImapItem = createJob->item(); + } + + //TestUtils::ensure only one item with gid exists, and it's content and rid has been updated as expected + { + Akonadi::Item expectedItem; + expectedItem.setGid(event->instanceIdentifier()); + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(expectedItem); + fetchJob->fetchScope().setFetchRemoteIdentification(true); + fetchJob->fetchScope().fetchFullPayload(true); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); + const Akonadi::Item item = fetchJob->items().first(); + QCOMPARE(item.remoteId().toLongLong(), recreatdImapItem.id()); + QVERIFY(item.hasPayload()); + QCOMPARE(item.payload()->summary(), event->summary()); + } + + //test retrieve items with duplicates + { + + mInstance.synchronize(); //triggers retrieve items + //TODO listen for some signals instead of a timeout + QTest::qWait(TIMEOUT); + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(kolabCollection); + fetchJob->fetchScope().setFetchRemoteIdentification(true); + fetchJob->fetchScope().fetchFullPayload(true); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); + const Akonadi::Item item = fetchJob->items().first(); + QCOMPARE(item.remoteId().toLongLong(), recreatdImapItem.id()); + QVERIFY(item.hasPayload()); + QCOMPARE(item.payload()->summary(), event->summary()); + } + } + + cleanup(); + } + + void twoItemAddedSignals() { + KCalCore::Event::Ptr event(new KCalCore::Event); + event->setSummary("summary1"); + event->setDtStart(KDateTime(QDate(2013,02,01), QTime(1,1), KDateTime::ClockTime)); + + //Create item in imap resource + { + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(createImapItem(event), imapCollection, this); + AKVERIFYEXEC(createJob); + } + Akonadi::Item secondImapItem; + { + event->setSummary("summary2"); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(createImapItem(event), imapCollection, this); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(itemChanged(Akonadi::Item,QSet)), createJob)); + secondImapItem = createJob->item(); + } + + //Ensure the conflict resolution still works with two consequitive item creates + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(kolabCollection); + fetchJob->fetchScope().setFetchRemoteIdentification(true); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); + const Akonadi::Item item = fetchJob->items().first(); + QCOMPARE(item.remoteId().toLongLong(), secondImapItem.id()); + } + + cleanup(); + + } + + void itemRemovedSignal() { + + KCalCore::Event::Ptr event(new KCalCore::Event); + event->setSummary("summary1"); + event->setDtStart(KDateTime(QDate(2013,02,01), QTime(1,1), KDateTime::ClockTime)); + + Akonadi::Item firstImapItem; + { + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(createImapItem(event), imapCollection, this); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), createJob)); + firstImapItem = createJob->item(); + } + + //create item again in imap resource (same gid), but with different content + Akonadi::Item secondImapItem; + { + event->setSummary("summary2"); + Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(createImapItem(event), imapCollection, this); +// AKVERIFYEXEC(createJob); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(itemChanged(Akonadi::Item,QSet)), createJob)); + secondImapItem = createJob->item(); + } + //we expect one kolab item that is linked to the second imap item + + //remove first imap item + { + Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(firstImapItem); + QVERIFY(!TestUtils::ensure(kolabCollection, SIGNAL(itemRemoved(Akonadi::Item)), deleteJob)); + } + + //TestUtils::ensure kolab item remains + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(kolabCollection); + fetchJob->fetchScope().setFetchRemoteIdentification(true); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 1); + const Akonadi::Item item = fetchJob->items().first(); + QCOMPARE(item.remoteId().toLongLong(), secondImapItem.id()); + } + + //remove second imap item + { + Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(secondImapItem); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(itemRemoved(Akonadi::Item)), deleteJob)); + } + + //TestUtils::ensure kolab item is removed + { + Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(kolabCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->items().size(), 0); + } + } + + void collectionAddedRemovedSignal() { + Akonadi::Collection createdCollection; + { + Akonadi::Collection col; + col.setParent(imapCollection); + col.setName("test"); + QMap annotations; + annotations.insert("/shared/vendor/kolab/folder-type", "event"); + col.addAttribute(new CollectionAnnotationsAttribute(annotations)); + Akonadi::CollectionCreateJob *createJob = new Akonadi::CollectionCreateJob(col, this); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), createJob)); + createdCollection = createJob->collection(); + } + { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(kolabCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->collections().size(), 1); + } + //cleanup + { + Akonadi::CollectionDeleteJob *deleteJob = new Akonadi::CollectionDeleteJob(createdCollection); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(collectionRemoved(Akonadi::Collection)), deleteJob)); + } + { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(kolabCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->collections().size(), 0); + } + } + + void collectionChangedSignal() { + Akonadi::Collection createdCollection; + { + Akonadi::Collection col; + col.setParent(imapCollection); + col.setName("test"); + QMap annotations; + annotations.insert("/shared/vendor/kolab/folder-type", "event"); + col.addAttribute(new CollectionAnnotationsAttribute(annotations)); + col.setRights(Akonadi::Collection::AllRights); + Akonadi::CollectionCreateJob *createJob = new Akonadi::CollectionCreateJob(col, this); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), createJob)); + createdCollection = createJob->collection(); + } + + { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(kolabCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->collections().size(), 1); + QCOMPARE(fetchJob->collections().first().rights(), Akonadi::Collection::AllRights); + } + { + createdCollection.setRights(Akonadi::Collection::ReadOnly); + Akonadi::CollectionModifyJob *modJob = new Akonadi::CollectionModifyJob(createdCollection); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(collectionChanged(Akonadi::Collection)), modJob)); + } + { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(kolabCollection); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->collections().size(), 1); + QCOMPARE(fetchJob->collections().first().rights(), Akonadi::Collection::ReadOnly); + } + //cleanup + { + Akonadi::CollectionDeleteJob *deleteJob = new Akonadi::CollectionDeleteJob(createdCollection); + QVERIFY(TestUtils::ensure(kolabCollection, SIGNAL(collectionRemoved(Akonadi::Collection)), deleteJob)); + } + } + +}; + +QTEST_AKONADIMAIN( ImapSignalTest, NoGUI ) + +#include "imapsignaltest.moc" + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step1.xml b/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step1.xml new file mode 100644 index 00000000..93000b1e --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step1.xml @@ -0,0 +1,93 @@ + + + + + + + 1 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661411 + + 1 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661412 + + + + 2 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661409 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + 1 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661410 + + + ("Shared Folders" "x-mail-distribution-list" false) + + 1 + + + + 1 + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + autotest0@example.com lrswipckxtdae + 1249661407 + + ("Inbox" "mail-folder-inbox" false) + 2 + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + autotest0@example.com lrswipckxtdae + 1249661406 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step2.xml b/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step2.xml new file mode 100644 index 00000000..94414964 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot-step2.xml @@ -0,0 +1,121 @@ + + + + + + + 1 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661417 + + 1 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661418 + + + + 2 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661415 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + 1 + + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + 1249661416 + + + ("Shared Folders" "x-mail-distribution-list" false) + + 1 + + + + 2 + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + autotest0@example.com lrswipckxtdae + 1249661415 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + 1 + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + autotest0@example.com lrswipckxtdae + 1249661414 + + ("Inbox" "mail-folder-inbox" false) + 2 + \ANSWERED \FLAGGED \DELETED \SEEN \DRAFT + autotest0@example.com lrswipckxtdae + 1249661413 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot.es b/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot.es new file mode 100644 index 00000000..fe7495e9 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest-dovecot.es @@ -0,0 +1,2 @@ +Script.include( "imaptest.es" ); +testImap( "dovecot" ); diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step1.xml b/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step1.xml new file mode 100644 index 00000000..c2c9eb9b --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step1.xml @@ -0,0 +1,99 @@ + + + + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251189141 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251189141 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + + + 2 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251189141 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 627 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251189141 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + + 1 + + + 1 + + + + 1 + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1251189141 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + 2 + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1251189140 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 627 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step2.xml b/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step2.xml new file mode 100644 index 00000000..0cbde4dd --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab-step2.xml @@ -0,0 +1,128 @@ + + + + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251191271 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251191271 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + + + 2 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251191271 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 627 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1251191271 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + + 1 + + + 1 + + + + 2 + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1251191279 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 627 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + 1 + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1251191270 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + 2 + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1251191270 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 627 + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab.es b/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab.es new file mode 100644 index 00000000..11abbb11 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest-kolab.es @@ -0,0 +1,2 @@ +Script.include( "imaptest.es" ); +testImap( "kolab" ); diff --git a/kdepim-runtime/resources/kolabproxy/tests/imaptest.es b/kdepim-runtime/resources/kolabproxy/tests/imaptest.es new file mode 100644 index 00000000..de8927db --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/imaptest.es @@ -0,0 +1,112 @@ +/* + Copyright (c) 2009 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. +*/ + +/** helper function to run IMAP commands for the initial VM setup */ +runImapCmd = function( user, cmd ) +{ + var args = ["--port", QEmu.portOffset() + 143, "--user", user + "@example.com", "--password", "nichtgeheim" ]; + args = args.concat( cmd ); + System.exec( "runimapcommand.py", args ); +} + +/** creates two user accounts with a standard set of Kolab folders shared between two users */ +setupServer = function() +{ + System.exec( "create_ldap_users.py", ["-h", "localhost", "-p", QEmu.portOffset() + 389, + "-n", "2", "--set-password", "nichtgeheim", "dc=example,dc=com", "add", "nichtgeheim" ] ); + + // Wait until the user accounts have been fully created + runImapCmd( "autotest0", [ "waitformailbox", "INBOX"] ); + runImapCmd( "autotest1", [ "waitformailbox", "INBOX"] ); + + // creates folders and messages for the primary test user + runImapCmd( "autotest0", [ "create", "INBOX/Calendar" ] ); + runImapCmd( "autotest0", [ "append", "INBOX", + Script.absoluteFileName( "testmail.mbox" ) ] ); + + // creates folders and messages for the secondary test user and share them with the first one + runImapCmd( "autotest1", [ "create", "INBOX/child1" ] ); + runImapCmd( "autotest1", [ "create", "INBOX/child2" ] ); + runImapCmd( "autotest1", [ "create", "INBOX/child1/grandchild1" ] ); + runImapCmd( "autotest1", [ "create", "INBOX/child2/grandchild1" ] ); + runImapCmd( "autotest1", [ "append", "INBOX/child1/grandchild1", + Script.absoluteFileName( "testmail.mbox" ) ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/child1", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/child2", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/child1/grandchild1", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/child2/grandchild1", "autotest0@example.com", "lrs" ] ); +} + +/** The actual tests for the IMAP resource, @p vm is the name of the VM + * to use. The names of the VM config file and the XML files with the + * expected contents of hte resources are derived from this. */ +testImap = function( vm ) +{ + QEmu.setVMConfig( vm + "vm.conf" ); + QEmu.start(); + + setupServer(); + + var imapResource = Resource.newInstance( "akonadi_imap_resource" ); + imapResource.setOption( "ImapServer", "localhost:42143" ); + imapResource.setOption( "UserName", "autotest0@example.com" ); + imapResource.setOption( "Password", "nichtgeheim" ); + imapResource.create(); + + // verify reading by checking if we can read the initial server state + XmlOperations.setXmlFile( "imaptest-" + vm + "-step1.xml" ); + XmlOperations.setRootCollections( imapResource.identifier() ); + // FIXME: one of the attributes contains a current date/time breaking the comparison + XmlOperations.ignoreCollectionField( "Attributes" ); + XmlOperations.assertEqual(); + + // folder creation + CollectionTest.setParent( "localhost:42143\\/autotest0@example.com/INBOX" ); + CollectionTest.addContentType( "message/rfc822" ); + CollectionTest.setName( "test folder" ); + CollectionTest.create(); + + // item creation + ItemTest.setParentCollection( "localhost:42143\\/autotest0@example.com/INBOX/test folder" ); + ItemTest.setMimeType( "message/rfc822" ); + ItemTest.setPayloadFromFile( "testmail.mbox" ); + ItemTest.create(); + + imapResource.recreate(); + XmlOperations.setXmlFile( "imaptest-" + vm + "-step2.xml" ); + XmlOperations.setRootCollections( imapResource.identifier() ); + // FIXME: one of the attributes contains a current date/time breaking the comparison + XmlOperations.ignoreCollectionField( "Attributes" ); + XmlOperations.assertEqual(); + + // TODO: test change, move, item delete + + // folder deletion + CollectionTest.setCollection( "localhost:42143\\/autotest0@example.com/INBOX/test folder" ); + CollectionTest.remove(); + + XmlOperations.setXmlFile( "imaptest-" + vm + "-step1.xml" ); + XmlOperations.setRootCollections( imapResource.identifier() ); + // FIXME: one of the attributes contains a current date/time breaking the comparison + XmlOperations.ignoreCollectionField( "Attributes" ); + XmlOperations.assertEqual(); + + imapResource.destroy(); + QEmu.stop(); +} diff --git a/kdepim-runtime/resources/kolabproxy/tests/kolab-step1.xml b/kdepim-runtime/resources/kolabproxy/tests/kolab-step1.xml new file mode 100644 index 00000000..d0729b6e --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/kolab-step1.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + ("Shared Folders" "x-mail-distribution-list") + + + + + a + + + a + + + a + + + a + + + a + + BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN +VERSION:2.0 +BEGIN:VEVENT +DTSTAMP:20090730T201426Z +ORGANIZER;CN="Volker Krause":MAILTO:vkrause@kde.org +CREATED:20090412T113649Z +UID:libkcal-1109914.774 +LAST-MODIFIED:20090412T113649Z +SUMMARY:Release KDE 4.2.4 +CATEGORIES:KDE +DTSTART;VALUE=DATE:20090603 +DTEND;VALUE=DATE:20090604 +TRANSP:TRANSPARENT +END:VEVENT + +END:VCALENDAR + + + ("My Data" "view-pim-summary") + a + + + + ("Kolab" "kolab") + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/kolab-step2.xml b/kdepim-runtime/resources/kolabproxy/tests/kolab-step2.xml new file mode 100644 index 00000000..030ac9d3 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/kolab-step2.xml @@ -0,0 +1,270 @@ + + + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1245682294 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:37 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type task.default + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1245682293 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:37 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type note.default + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1245682293 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:37 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type journal.default + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1245682293 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:37 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type contact.default + + + + 1 + + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + 1245682293 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:36 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type event.default + + + + 1 + + ("Shared Folders" "x-mail-distribution-list") + + 1 + + + + 2 + a + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1245682291 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:31 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type task.default + + + From: vkrause@kde.org, vkrause@kde.org +Subject: libkcal-1506191911.958 +User-Agent: Akonadi Kolab Proxy Resource +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="nextPart1765520.vjZoYI2vSi" +Date: Mon Jun 22 16:53:27 2009 +X-Kolab-Type: application/x-vnd.kolab.task + + +--nextPart1765520.vjZoYI2vSi + + +--nextPart1765520.vjZoYI2vSi +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +This is a Kolab Groupware object. +To view this object you will need an email client that can understand the Kolab Groupware format. +For a list of such email clients please visit +http://www.kolab.org/kolab2-clients.html + +--nextPart1765520.vjZoYI2vSi +Content-Type: application/x-vnd.kolab.task; name="kolab.xml" +Content-Transfer-Encoding: quoted-printable +Content-Disposition: attachment; filename="kolab.xml" + +<?xml version=3D"1.0" encoding=3D"UTF-8"?> +<task version=3D"1.0" > + <product-id>KOrganizer 4.3.0 rc1, Kolab resource</product-id> + <uid>libkcal-1506191911.958</uid> + <creation-date>2004-05-05T09:41:43Z</creation-date> + <last-modification-date>2004-05-12T13:39:25+00:00</last-modification-date> + <sensitivity>public</sensitivity> + <summary>Add a demo task to this file</summary> + <organizer> + <smtp-address>vkrause@kde.org</smtp-address> + </organizer> + <revision>0</revision> + <priority>3</priority> + <completed>100</completed> + <status>not-started</status> + <due-date>2009-01-01T00:00:00Z</due-date> + <x-completed-date>2009-01-01T13:39:25Z</x-completed-date> +</task> + +--nextPart1765520.vjZoYI2vSi-- + + + + + 1 + a + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1245682290 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:30 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type note.default + + + + 1 + a + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1245682289 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:29 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type journal.default + + + + 1 + a + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1245682289 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:29 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 % /vendor/kolab/folder-type contact.default + + + + 3 + a + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1245682289 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:51:32 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 1750 % /vendor/kolab/folder-type event.default + + + From: Volker Krause <vkrause@kde.org> +Subject: libkcal-1109914.774 +Date: Sun, 12 Apr 2009 13:36:53 +0200 +User-Agent: KMail/1.9.9 +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="Boundary-00=_VJd4J1dXKX5M02c" +X-KMail-Fcc: sent-mail +X-Kolab-Type: application/x-vnd.kolab.event +Status: RO +X-Status: OT +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: +X-UID: 150 +X-Length: 1728 + + +--Boundary-00=_VJd4J1dXKX5M02c +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit +Content-Disposition: + +This is a Kolab Groupware object. +To view this object you will need an email client that can understand the Kolab Groupware format. +For a list of such email clients please visit +http://www.kolab.org/kolab2-clients.html +--Boundary-00=_VJd4J1dXKX5M02c +Content-Type: application/x-vnd.kolab.event; name="kolab.xml" +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; filename="kolab.xml" + +<?xml version="1.0" encoding="UTF-8"?> +<event version="1.0" > + <product-id>KOrganizer 3.5.9 (enterprise35 0.20080722.836276), Kolab resource</product-id> + <uid>libkcal-1109914.774</uid> + <categories>KDE</categories> + <creation-date>2009-04-12T11:36:49Z</creation-date> + <last-modification-date>2009-04-12T11:36:49Z</last-modification-date> + <sensitivity>public</sensitivity> + <pilot-sync-status>0</pilot-sync-status> + <start-date>2009-06-03</start-date> + <summary>Release KDE 4.2.4</summary> + <organizer> + <display-name>Volker Krause</display-name> + <smtp-address>vkrause@kde.org</smtp-address> + </organizer> + <revision>0</revision> + <show-time-as>free</show-time-as> + <end-date>2009-06-03</end-date> +</event> + +--Boundary-00=_VJd4J1dXKX5M02c-- + + + + From: Volker Krause <vkrause@kde.org>, vkrause@kde.org +Subject: libkcal-1135684253.945 +User-Agent: Akonadi Kolab Proxy Resource +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="nextPart4368523.kW76qGQa90" +Date: Mon Jun 22 16:53:26 2009 +X-Kolab-Type: application/x-vnd.kolab.event + + +--nextPart4368523.kW76qGQa90 + + +--nextPart4368523.kW76qGQa90 +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +This is a Kolab Groupware object. +To view this object you will need an email client that can understand the Kolab Groupware format. +For a list of such email clients please visit +http://www.kolab.org/kolab2-clients.html + +--nextPart4368523.kW76qGQa90 +Content-Type: application/x-vnd.kolab.event; name="kolab.xml" +Content-Transfer-Encoding: quoted-printable +Content-Disposition: attachment; filename="kolab.xml" + +<?xml version=3D"1.0" encoding=3D"UTF-8"?> +<event version=3D"1.0" > + <product-id>KOrganizer 4.3.0 rc1, Kolab resource</product-id> + <uid>libkcal-1135684253.945</uid> + <categories>KDE</categories> + <creation-date>2007-01-09T10:05:53Z</creation-date> + <last-modification-date>2007-01-09T10:06:25+00:00</last-modification-date> + <sensitivity>public</sensitivity> + <start-date>2007-01-09T18:30:00Z</start-date> + <summary>Test event</summary> + <location>here</location> + <organizer> + <display-name>Volker Krause</display-name> + <smtp-address>vkrause@kde.org</smtp-address> + </organizer> + <alarm>45</alarm> + <advanced-alarms> + <alarm type=3D"display" > + <enabled>1</enabled> + <start-offset>-45</start-offset> + </alarm> + </advanced-alarms> + <revision>0</revision> + <show-time-as>busy</show-time-as> + <end-date>2007-01-09T22:59:00Z</end-date> +</event> + +--nextPart4368523.kW76qGQa90-- + + + + ("Inbox" "mail-folder-inbox") + 1 + a + \ANSWERED \FLAGGED \DRAFT \DELETED \SEEN + autotest0@example.com lrswipckxtdae + 1245682273 + /vendor/cmu/cyrus-imapd/condstore false % /vendor/cmu/cyrus-imapd/duplicatedeliver false % /vendor/cmu/cyrus-imapd/lastpop % /vendor/cmu/cyrus-imapd/lastupdate 22-Jun-2009 16:53:28 +0200 % /vendor/cmu/cyrus-imapd/partition default % /vendor/cmu/cyrus-imapd/sharedseen false % /vendor/cmu/cyrus-imapd/size 0 + + + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/kolabconvertertest.cpp b/kdepim-runtime/resources/kolabproxy/tests/kolabconvertertest.cpp new file mode 100644 index 00000000..720ccac2 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/kolabconvertertest.cpp @@ -0,0 +1,120 @@ +/* + Copyright (c) 2009 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 +#include +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; +using namespace KMime; +Q_DECLARE_METATYPE(KCalCore::Incidence::Ptr) + +class KolabConverterTest : public QObject +{ + Q_OBJECT + private slots: + void testContacts_data() + { + QTest::addColumn( "addressee" ); + KABC::Addressee contact; + contact.setName(QLatin1String("John Doe")); + contact.setEmails(QStringList() << QLatin1String("doe@example.org")); + + QTest::newRow( "contact" ) << contact; + } + + void testContacts() + { + QFETCH( KABC::Addressee, addressee ); + + KolabHandler::Ptr handler = KolabHandler::createHandler( Kolab::ContactType, Collection() ); + QVERIFY( handler ); + + Item vcardItem( QLatin1String("text/directory") ); + vcardItem.setPayload( addressee ); + + //Convert to kolab item + Item kolabItem; + handler->toKolabFormat( vcardItem, kolabItem ); + QVERIFY( kolabItem.hasPayload() ); + + //and back + Item::List vcardItems = handler->translateItems( Akonadi::Item::List() << kolabItem ); + QCOMPARE( vcardItems.size(), 1 ); + QVERIFY( vcardItems.first().hasPayload() ); + } + + void testIncidences_data() + { + QTest::addColumn( "type" ); + QTest::addColumn( "incidence" ); + + KCalCore::Incidence::Ptr event(new KCalCore::Event); + event->setDtStart(KDateTime::currentUtcDateTime()); + QTest::newRow("event") << "event" << event; + + KCalCore::Incidence::Ptr todo(new KCalCore::Todo); + todo->setDtStart(KDateTime::currentUtcDateTime()); + QTest::newRow("todo") << "task" << todo; + + KCalCore::Incidence::Ptr journal(new KCalCore::Journal); + journal->setDtStart(KDateTime::currentUtcDateTime()); + QTest::newRow("journal") << "journal" << journal; + } + + void testIncidences() + { + QFETCH( QString, type ); + QFETCH( KCalCore::Incidence::Ptr, incidence ); + + KolabHandler::Ptr handler = KolabHandler::createHandler( Kolab::folderTypeFromString( type.toLatin1() ), Collection() ); + QVERIFY( handler ); + + Item icalItem; + switch ( incidence->type() ) { + case KCalCore::IncidenceBase::TypeEvent: icalItem.setMimeType( KCalCore::Event::eventMimeType() ); return; + case KCalCore::IncidenceBase::TypeTodo: icalItem.setMimeType( KCalCore::Todo::todoMimeType() ); return; + case KCalCore::IncidenceBase::TypeJournal: icalItem.setMimeType( KCalCore::Journal::journalMimeType() ); return; + default: QFAIL( "incidence type not supported" ); + } + icalItem.setPayload( incidence ); + + // Convert to kolab item + Item kolabItem; + handler->toKolabFormat( icalItem, kolabItem ); + QVERIFY( kolabItem.hasPayload() ); + + //and back + Item::List icalItems = handler->translateItems( Akonadi::Item::List() << kolabItem ); + QCOMPARE( icalItems.size(), 1 ); + QVERIFY( icalItems.first().hasPayload() ); + if ( type == QLatin1String("task") ) { + QVERIFY( icalItems.first().hasPayload() ); + } + } +}; + +QTEST_KDEMAIN( KolabConverterTest, NoGUI ) + +#include "kolabconvertertest.moc" diff --git a/kdepim-runtime/resources/kolabproxy/tests/kolabevent.mbox b/kdepim-runtime/resources/kolabproxy/tests/kolabevent.mbox new file mode 100644 index 00000000..8646ffcd --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/kolabevent.mbox @@ -0,0 +1,56 @@ +From: Volker Krause +X-KMail-Fcc: sent-mail +Subject: libkcal-1109914.774 +Date: Sun, 12 Apr 2009 13:36:53 +0200 +User-Agent: KMail/1.9.9 +MIME-Version: 1.0 +X-Kolab-Type: application/x-vnd.kolab.event +Content-Type: Multipart/Mixed; + boundary="Boundary-00=_VJd4J1dXKX5M02c" +Status: RO +X-Status: OT +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: +X-UID: 150 +X-Length: 1728 + +--Boundary-00=_VJd4J1dXKX5M02c +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Content-Disposition: + +This is a Kolab Groupware object. +To view this object you will need an email client that can understand the Kolab Groupware format. +For a list of such email clients please visit +http://www.kolab.org/kolab2-clients.html +--Boundary-00=_VJd4J1dXKX5M02c +Content-Type: application/x-vnd.kolab.event; + name="kolab.xml" +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; + filename="kolab.xml" + + + + KOrganizer 3.5.9 (enterprise35 0.20080722.836276), Kolab resource + libkcal-1109914.774 + KDE + 2009-04-12T11:36:49Z + 2009-04-12T11:36:49Z + public + 0 + 2009-06-03 + Release KDE 4.2.4 + + Volker Krause + vkrause@kde.org + + 0 + free + 2009-06-03 + + +--Boundary-00=_VJd4J1dXKX5M02c-- + diff --git a/kdepim-runtime/resources/kolabproxy/tests/kolabtest.es b/kdepim-runtime/resources/kolabproxy/tests/kolabtest.es new file mode 100644 index 00000000..ae1a5776 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/kolabtest.es @@ -0,0 +1,129 @@ +/* + Copyright (c) 2009 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. +*/ + +/** helper function to run IMAP commands for the initial VM setup */ +function runImapCmd( user, cmd ) +{ + var args = ["--port", QEmu.portOffset() + 143, "--user", user + "@example.com", "--password", "nichtgeheim" ]; + args = args.concat( cmd ); + System.exec( "runimapcommand.py", args ); +} + +/** helper function to create a Kolab folder setup */ +function createKolabFolders( user ) +{ + runImapCmd( user, [ "create", "INBOX/Calendar" ] ); + runImapCmd( user, [ "create", "INBOX/Contacts" ] ); + runImapCmd( user, [ "create", "INBOX/Journal" ] ); + runImapCmd( user, [ "create", "INBOX/Notes" ] ); + runImapCmd( user, [ "create", "INBOX/Tasks" ] ); + + runImapCmd( user, [ "setannotation", "INBOX/Calendar", "/vendor/kolab/folder-type", "event.default" ] ); + runImapCmd( user, [ "setannotation", "INBOX/Contacts", "/vendor/kolab/folder-type", "contact.default" ] ); + runImapCmd( user, [ "setannotation", "INBOX/Journal", "/vendor/kolab/folder-type", "journal.default" ] ); + runImapCmd( user, [ "setannotation", "INBOX/Notes", "/vendor/kolab/folder-type", "note.default" ] ); + runImapCmd( user, [ "setannotation", "INBOX/Tasks", "/vendor/kolab/folder-type", "task.default" ] ); +} + +/** creates two user accounts with a standard set of Kolab folders shared between two users */ +function setupKolab() +{ + System.exec( "create_ldap_users.py", ["-h", "localhost", "-p", QEmu.portOffset() + 389, + "-n", "2", "--set-password", "nichtgeheim", "dc=example,dc=com", "add", "nichtgeheim" ] ); + + // Wait until the user accounts have been fully created + runImapCmd( "autotest0", [ "waitformailbox", "INBOX"] ); + runImapCmd( "autotest1", [ "waitformailbox", "INBOX"] ); + + // creates folders and messages for the primary test user + createKolabFolders( "autotest0" ); + runImapCmd( "autotest0", [ "append", "INBOX/Calendar", "kolabevent.mbox" ] ); + + // create folders and messages for the user sharing his folders with the test user + createKolabFolders( "autotest1" ); + runImapCmd( "autotest1", [ "setacl", "INBOX/Calendar", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/Contacts", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/Journal", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/Notes", "autotest0@example.com", "lrs" ] ); + runImapCmd( "autotest1", [ "setacl", "INBOX/Tasks", "autotest0@example.com", "lrs" ] ); +} + +/** The actual tests for the Kolab resource. */ +function testKolab( vm ) +{ + QEmu.setVMConfig( vm ); + QEmu.start(); + + setupKolab(); + + var imapResource = Resource.newInstance( "akonadi_imap_resource" ); + imapResource.setOption( "ImapServer", "localhost:42143" ); + imapResource.setOption( "UserName", "autotest0@example.com" ); + imapResource.setOption( "Password", "nichtgeheim" ); + imapResource.create(); + + Test.alert( "wait" ); // FIXME + + XmlOperations.setXmlFile( "kolab-step1.xml" ); + XmlOperations.setRootCollections( kolabResource.identifier() ); + XmlOperations.setCollectionKey( "Name" ); + XmlOperations.ignoreCollectionField( "RemoteId" ); + XmlOperations.setItemKey( "None" ); // FIXME this should not be necessary when using the testrunner? + XmlOperations.ignoreItemField( "RemoteId" ); + XmlOperations.assertEqual(); + + // TODO: adding/changing/removing collections, adding/changing/removing items, in both directions + + var eventItem = ItemTest.newInstance(); + eventItem.setMimeType( "application/x-vnd.akonadi.calendar.event" ); + eventItem.setParentCollection( "akonadi_kolabproxy_resource/localhost:42143\\/autotest0@example.com/INBOX/Calendar" ); + eventItem.setPayloadFromFile( "event.ical" ); + eventItem.create(); + + var taskItem = ItemTest.newInstance(); + taskItem.setMimeType( "application/x-vnd.akonadi.calendar.todo" ); + taskItem.setParentCollection( "akonadi_kolabproxy_resource/localhost:42143\\/autotest0@example.com/INBOX/Tasks" ); + taskItem.setPayloadFromFile( "task.ical" ); + taskItem.create(); + + Test.alert( "wait again" ); // FIXME + + // FIXME I need either two instances or a way to reset this thing... + XmlOperations.setXmlFile( "kolab-step2.xml" ); + XmlOperations.setRootCollections( imapResource.identifier() ); + XmlOperations.setCollectionKey( "RemoteId" ); + // FIXME: one of the attributes contains a current date/time breaking the comparison + XmlOperations.ignoreCollectionField( "Attributes" ); + XmlOperations.setItemKey( "RemoteId" ); + // FIXME: payload contains a current date/time... + XmlOperations.ignoreItemField( "Payload" ); + XmlOperations.assertEqual(); + + Test.alert( "done" ); + imapResource.destroy(); + QEmu.stop(); +} + +var kolabResource = Resource.newInstance( "akonadi_kolabproxy_resource" ); +kolabResource.create(); + +testKolab( "kolabvm.conf" ); +// testKolab( "dovecotvm.conf" ); + +kolabResource.destroy(); diff --git a/kdepim-runtime/resources/kolabproxy/tests/kolabvm.conf b/kdepim-runtime/resources/kolabproxy/tests/kolabvm.conf new file mode 100644 index 00000000..a027e186 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/kolabvm.conf @@ -0,0 +1,11 @@ +[Emulator] +Arguments=-m 256M +Ports=22,25,80,143,389,443,465,993 +WaitForPorts=22,25,143 +MonitorPort=23 +SnapshotName=running-server + +[Image] +Source=http://files.kolab.org/server/development-2.2/testing/qemu-kolab-server-2.2.0-20090807/qemu-kolab-server-2.2.0-20090807.tar.bz2 +File=qemu-kolab-server/running-server.qcow2 + diff --git a/kdepim-runtime/resources/kolabproxy/tests/proxyintegrationtest.cpp b/kdepim-runtime/resources/kolabproxy/tests/proxyintegrationtest.cpp new file mode 100644 index 00000000..1bf72848 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/proxyintegrationtest.cpp @@ -0,0 +1,157 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //libkolab + +#include "testutils.h" + +#include "../kolabdefs.h" + +using namespace Akonadi; + +class ProxyIntegrationTest : public QObject +{ + Q_OBJECT + + static Collection createCollection(const QString &name, const QByteArray &annotation) + { + Collection col; + col.setName(name); + QMap annotations; + Kolab::setFolderTypeAnnotation( annotations, annotation ); + col.addAttribute(new CollectionAnnotationsAttribute(annotations)); + col.setRights(Collection::CanCreateItem | Collection::CanChangeItem | Collection::CanDeleteItem | Collection::CanCreateCollection | Collection::CanChangeCollection | Collection::CanDeleteCollection); + return col; + } + + AgentInstance mInstance; + +private slots: + void initTestCase() { + AkonadiTest::checkTestIsIsolated(); + AttributeFactory::registerAttribute(); + + const AgentType type = AgentManager::self()->type("akonadi_kolabproxy_resource"); + AgentInstanceCreateJob *agentCreateJob = new AgentInstanceCreateJob(type); + AKVERIFYEXEC(agentCreateJob); + mInstance = agentCreateJob->instance(); + + //Wait for kolabproxy to create all folders + QVERIFY(TestUtils::ensurePopulated(mInstance.identifier(), 6)); + } + + void setupKolabProxy() { + //Check that all collections in the kolab proxy have been created + CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); + fetchJob->fetchScope().setResource(mInstance.identifier()); + AKVERIFYEXEC(fetchJob); + + QList expectedCollections; + expectedCollections << createCollection("Calendar", "event"); + expectedCollections << createCollection("Tasks", "task"); + expectedCollections << createCollection("Journal", "journal"); + expectedCollections << createCollection("Notes", "note"); + expectedCollections << createCollection("Contact", "contact"); + + foreach (const Collection &col, fetchJob->collections()) { + if (!col.attribute()) { + continue; + } + for (int i = 0; i < expectedCollections.size(); i++) { + const Collection &expectedCol = expectedCollections.at(i); +// kDebug() << col.name() << expectedCol.name(); +// kDebug() << col.attribute()->annotations(); +// kDebug() << expectedCol.attribute()->annotations(); + if (col.name() == expectedCol.name()) { + kDebug() << " found " << col.name(); + QCOMPARE(col.attribute()->annotations(), expectedCol.attribute()->annotations()); + QCOMPARE(col.rights(), expectedCol.rights()); + expectedCollections.removeAt(i); + break; + } + } + } + QCOMPARE(expectedCollections.size(), 0); + } + + /** + * All kolab folders, if handled by kolab proxy or not, should receive an entity hidden attribute + */ + void ensureHiddenAttribute() { + CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); + fetchJob->fetchScope().setResource("akonadi_knut_resource_0"); + AKVERIFYEXEC(fetchJob); + + foreach (const Collection &col, fetchJob->collections()) { + if (!col.attribute()) { + continue; + } + if (Kolab::folderTypeFromString(Kolab::getFolderTypeAnnotation(col.attribute()->annotations())) != Kolab::MailType) { + //Kolab folder + QVERIFY(col.attribute()); + } + } + } + + void testRemoval() { + Akonadi::AgentInstance instance = AgentManager::self()->instance("akonadi_knut_resource_0"); + AgentManager::self()->removeInstance(instance); + + //Ensure all kolab collections are removed as well + QTest::qWait(10); + bool kolabCollectionsAreGone = false; + Akonadi::Collection::List rootCollections; + for (int i = 0; i < TIMEOUT/10; i++) { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection::root()); + AKVERIFYEXEC(fetchJob); + rootCollections.clear(); + foreach (const Akonadi::Collection &col, fetchJob->collections()) { + if (col.resource().contains("kolabproxy")) { + rootCollections << col; + } + } + if (rootCollections.size() == 0) { + kolabCollectionsAreGone = true; + break; + } + QTest::qWait(10); + } + if (!kolabCollectionsAreGone) { + kDebug() << rootCollections; + } + QVERIFY(kolabCollectionsAreGone); + } + +}; + +QTEST_AKONADIMAIN( ProxyIntegrationTest, NoGUI ) + +#include "proxyintegrationtest.moc" diff --git a/kdepim-runtime/resources/kolabproxy/tests/runimapcommand.py b/kdepim-runtime/resources/kolabproxy/tests/runimapcommand.py new file mode 100755 index 00000000..b10871ad --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/runimapcommand.py @@ -0,0 +1,284 @@ +#! /usr/bin/env python + +import sys +import optparse +import inspect +import time +import imaplib +import re + + +class AnnotationsMixin: + + def __install_commands_in_imaplib(self): + imaplib.Commands['GETANNOTATION'] = ('AUTH', 'SELECTED') + imaplib.Commands['SETANNOTATION'] = ('AUTH', 'SELECTED') + + def __to_argument_string(self, arg): + if isinstance(arg, basestring): + return self._quote(arg) + elif arg is None: + return "NIL" + else: + return "(%s)" % (" ".join(self.__to_argument_string(item) + for item in arg),) + + def getannotation(self, *args): + # normally we would implement this command like the other + # commands in IMAP with an explicit list of parameters. For + # tests it's useful to be able to omit parameters or to send too + # many, so we allow an arbitrary number of parameters + self.__install_commands_in_imaplib() + args = tuple(self.__to_argument_string(item) for item in args) + typ, dat = self._simple_command('GETANNOTATION', *args) + return self._untagged_response(typ, dat, 'ANNOTATION') + + def setannotation(self, *args): + # normally we would implement this command like the other + # commands in IMAP with an explicit list of parameters. For + # tests it's useful to be able to omit parameters or to send too + # many, so we allow an arbitrary number of parameters + self.__install_commands_in_imaplib() + args = tuple(self.__to_argument_string(item) for item in args) + return self._simple_command('SETANNOTATION', *args) + + +class IMAP4_annotations(AnnotationsMixin, imaplib.IMAP4): + + pass + + +class IMAP4_SSL_annotations(AnnotationsMixin, imaplib.IMAP4_SSL): + + pass + + + +class ImapError(Exception): + + def __init__(self, host, port, imap_result): + Exception.__init__(self, "Unexpected response from imap server" + " (%r:%d):%r" % (host, port, imap_result)) + self.imap_result = imap_result + + +def extract_namespaces(raw_namespaces): + # Unfortunately, Python's imaplib doesn't contain a parser for IMAP + # data structures. This insufficient parser can at least extract + # most namespace definitions for common imap servers. + namespaces = [] + while raw_namespaces: + match = re.search(r'\("([^")]*)"[ \t]+"([^")]*)"\)', raw_namespaces) + if match: + namespaces.append((match.group(1), match.group(2))) + raw_namespaces = raw_namespaces[match.end(0):] + else: + break + return namespaces + + + +class ImapConnection(object): + + def __init__(self, host, port, login, password, use_ssl): + self.host = host + self.port = port + if use_ssl: + cls = IMAP4_SSL_annotations + else: + cls = IMAP4_annotations + self.error_class = cls.error + self.cached_namespaces = None + self.imap = cls(host, port) + self.auth_plain(login, password) + + def check_res(self, res, ok_responses=("OK",)): + assert len(res) == 2 + if res[0] not in ok_responses: + raise ImapError(self.host, self.port, res) + data = res[1] + # for some reason imaplib uses a one-element list containing + # None to indicate that no untagged responses came from the + # server. Translate this to an empty list. + if data == [None]: + data = [] + return data + + def lookup_namespace(self, mailbox): + if self.cached_namespaces is None: + self.cached_namespaces = extract_namespaces(self.namespace()[0]) + for prefix, separator in self.cached_namespaces: + if mailbox.startswith(prefix): + return (prefix, separator) + return (None, None) + + def convert_separator(self, mailbox): + split_mailbox = mailbox.split("/") + cache = dict() + + if self.cached_namespaces is None: + self.cached_namespaces = extract_namespaces(self.namespace()[0]) + + for prefix, separator in self.cached_namespaces: + if separator in cache: + converted_mailbox = cache[separator] + else: + converted_mailbox = separator.join(split_mailbox) + cache[separator] = converted_mailbox + if converted_mailbox.startswith(prefix): + return converted_mailbox + return mailbox + + def auth_plain(self, login, password): + self.check_res(self.imap.login(login, password)) + + def logout(self): + self.check_res(self.imap.logout(), ok_responses=("OK", "BYE")) + + @property + def capabilities(self): + return self.imap.capabilities + + def namespace(self): + return self.check_res(self.imap.namespace()) + + def create(self, mailbox): + self.check_res(self.imap.create(mailbox)) + + def delete(self, mailbox): + self.check_res(self.imap.delete(mailbox)) + + def rename(self, oldmailbox, newmailbox): + self.check_res(self.imap.rename(oldmailbox, newmailbox)) + + def getannotation(self, *args): + return self.check_res(self.imap.getannotation(*args)) + + def setannotation(self, *args): + return self.check_res(self.imap.setannotation(*args)) + + def setacl(self, *args): + return self.check_res(self.imap.setacl(*args)) + + def append(self, *args): + return self.check_res(self.imap.append(*args)) + + def list(self, *args): + return self.check_res(self.imap.list(*args)) + + +def command_create(connection, mailbox): + """Create a mailbox. Parameter: MAILBOX + Example: create INBOX/MyCalendar + """ + connection.create(connection.convert_separator(mailbox)) + +def command_setacl(connection, mailbox, user, rights): + """Set an ACL. Parameters: MAILBOX USERNAME RIGHTS + Example: setacl INBOX/MyCalendar someone@example.com lrs + """ + connection.setacl(connection.convert_separator(mailbox), user, rights) + +def command_setannotation(connection, mailbox, annotation, value): + """Set an annotation. Parameters: MAILBOX ANNOTATION VALUE + Example: setannotation INBOX/MyCalendar /vendor/kolab/folder-type event + """ + connection.setannotation(connection.convert_separator(mailbox), + annotation, ["value.shared", value]) + +def command_append(connection, mailbox, filename): + """Append a message to a mailbox. Parameters: MAILBOX FILENAME + Example: append INBOX/MyCalendar testdata/mytestmail + """ + connection.append(connection.convert_separator(mailbox), + None, None, open(filename).read()) + +def command_waitformailbox(connection, mailbox): + mailbox = connection.convert_separator(mailbox) + timeout = 20 + start = time.time() + while time.time() - start < timeout: + found = connection.list("", mailbox) + if found: + break + time.sleep(1) + else: + raise RuntimeError("waitformailbox timed out after %s seconds" % + (time.time() - start,)) + + +def find_commands(): + commands = [obj for obj in globals().values() + if inspect.isfunction(obj) + and obj.__name__.startswith("command_")] + commands.sort() + return commands + + +class OptionParserWithCommands(optparse.OptionParser): + + def format_help(self, formatter=None): + option_help = optparse.OptionParser.format_help(self, + formatter=formatter) + command_help = ["commands:"] + for command in find_commands(): + doc_lines = command.__doc__.splitlines() + command_help.append(" %-15s %s" + % (command.__name__[len("command_"):], + doc_lines[0])) + command_help.extend(doc_lines[1:]) + return option_help + "\n" + "\n".join(command_help) + "\n" + + +def main(): + parser = OptionParserWithCommands(usage=("%prog [options]" + " [command [arg1 ...]]")) + parser.set_defaults(host="localhost") + parser.add_option("--host", + help=("Hostname of the IMAP server" + " (default 'localhost').")) + parser.add_option("--port", type="int", + help=("Port number of the IMAP server (required).")) + parser.add_option("--ssl", action="store_true", + help=("Enable SSL.")) + parser.add_option("--user", + help=("Username for login (required).")) + parser.add_option("--password", + help=("Password for login (required).")) + + options, args = parser.parse_args() + + missing_required_option = False + for optname in "port user password".split(): + if getattr(options, optname) is None: + print >>sys.stderr, "Missing required option %r" % optname + missing_required_option = True + if missing_required_option: + sys.exit(1) + + if len(args) == 0: + print >>sys.stderr, "No command given" + sys.exit(1) + + command_name = args[0] + command_args = args[1:] + command = globals().get("command_" + command_name) + if command is None: + print >>sys.stderr, "Unknown command %r" % command_name + sys.exit(1) + + expected_num_args = len(inspect.getargspec(command)[0]) - 1 + if len(command_args) != expected_num_args: + print >>sys.stderr, "--%s requires %d arguments, got %d" % \ + (options.command, expected_num_args, len(command_args)) + sys.exit(1) + + connection = ImapConnection(options.host, options.port, + options.user, options.password, + options.ssl) + command(connection, *command_args) + connection.logout() + + +if __name__ == "__main__": + main() diff --git a/kdepim-runtime/resources/kolabproxy/tests/task.ical b/kdepim-runtime/resources/kolabproxy/tests/task.ical new file mode 100644 index 00000000..cc51d6b9 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/task.ical @@ -0,0 +1,16 @@ +BEGIN:VCALENDAR +PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN +VERSION:2.0 +BEGIN:VTODO +DTSTAMP:20090101T154017Z +ORGANIZER:MAILTO:vkrause@kde.org +CREATED:20040505T094143Z +UID:libkcal-1506191911.958 +LAST-MODIFIED:20040512T133925Z +SUMMARY:Add a demo task to this file +PRIORITY:3 +DUE;VALUE=DATE:20090101 +COMPLETED:20090101T133925Z +PERCENT-COMPLETE:100 +END:VTODO +END:VCALENDAR diff --git a/kdepim-runtime/resources/kolabproxy/tests/testmail.mbox b/kdepim-runtime/resources/kolabproxy/tests/testmail.mbox new file mode 100644 index 00000000..f14d60de --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/testmail.mbox @@ -0,0 +1,19 @@ +From: Volker Krause +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h diff --git a/kdepim-runtime/resources/kolabproxy/tests/testutils.cpp b/kdepim-runtime/resources/kolabproxy/tests/testutils.cpp new file mode 100644 index 00000000..2fd29721 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/testutils.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2013 Christian Mollekopf + + 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 "testutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include //libkolab +#include + +#include "../kolabdefs.h" + +using namespace Akonadi; + +Akonadi::Item TestUtils::createImapItem(const KCalCore::Event::Ptr &event) { + const KMime::Message::Ptr &message = Kolab::KolabObjectWriter::writeEvent(event, Kolab::KolabV3, "Proxytest", QLatin1String("UTC") ); + Q_ASSERT(message); + Akonadi::Item imapItem1; + imapItem1.setMimeType( QLatin1String("message/rfc822") ); + imapItem1.setPayload( message ); + return imapItem1; +} + +TestUtils::MonitorPair TestUtils::monitor(Akonadi::Collection col, const char *signal) { + QSharedPointer monitor(new Akonadi::Monitor); + monitor->setCollectionMonitored(col); + QSharedPointer spy(new QSignalSpy(monitor.data(), signal)); + Q_ASSERT_X(spy->isValid(), "", signal); + return qMakePair, QSharedPointer >(spy, monitor); +} + +bool TestUtils::wait(const MonitorPair &pair) { + for (int i = 0; i < TIMEOUT/10 ; i++) { + if (pair.first->count() >= 1) { + kDebug() << pair.first->first(); + return true; + } + QTest::qWait(10); + } + return false; +} + +bool TestUtils::ensure(Akonadi::Collection col, const char *signal, Akonadi::Job *job) { + MonitorPair m = monitor(col, signal); + if (!job->exec()) { + return false; + } + return wait(m); +} + +bool TestUtils::ensurePopulated(QString agentinstance, int count) { + for (int i = 0; i < TIMEOUT/10 ; i++) { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); + fetchJob->fetchScope().setResource(agentinstance); + if (!fetchJob->exec()) { + return false; + } + if (fetchJob->collections().size() >= count) { + return true; + } + QTest::qWait(10); + } + return false; +} + +Akonadi::Collection TestUtils::findCollection(QString agentinstance, QString name) { + for (int i = 0; i < 500 ; i++) { + Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); + fetchJob->fetchScope().setResource(agentinstance); + if (!fetchJob->exec()) { + return Akonadi::Collection(); + } + foreach (const Collection &col, fetchJob->collections()) { + if (col.name().contains(name)) { + return col; + } + } + QTest::qWait(10); + } + return Akonadi::Collection(); +} diff --git a/kdepim-runtime/resources/kolabproxy/tests/testutils.h b/kdepim-runtime/resources/kolabproxy/tests/testutils.h new file mode 100644 index 00000000..836fc70d --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/testutils.h @@ -0,0 +1,39 @@ + +/* + Copyright (c) 2013 Christian Mollekopf + + 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 +#include +#include +#include +#include + +using namespace Akonadi; +#define TIMEOUT 1000 +struct TestUtils +{ + static Akonadi::Item createImapItem(const KCalCore::Event::Ptr &event); + + typedef QPair, QSharedPointer > MonitorPair; + + static MonitorPair monitor(Akonadi::Collection col, const char *signal); + static bool wait(const MonitorPair &pair); + static bool ensure(Akonadi::Collection col, const char *signal, Akonadi::Job *job); + static bool ensurePopulated(QString agentinstance, int count); + static Akonadi::Collection findCollection(QString agentinstance, QString name); +}; diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-db.xml b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-db.xml new file mode 100644 index 00000000..011bc571 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-db.xml @@ -0,0 +1,8 @@ + + kdehome + xdgconfig-mysql.db + xdglocal + akonadi_knut_resource + true + mysql + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-fs.xml b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-fs.xml new file mode 100644 index 00000000..4988fcaa --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-mysql-fs.xml @@ -0,0 +1,8 @@ + + kdehome + xdgconfig-mysql.fs + xdglocal + akonadi_knut_resource + true + mysql + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-db.xml b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-db.xml new file mode 100644 index 00000000..615e0d17 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-db.xml @@ -0,0 +1,8 @@ + + kdehome + xdgconfig-postgresql.db + xdglocal + akonadi_knut_resource + true + postgresql + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-fs.xml b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-fs.xml new file mode 100644 index 00000000..3ba8d74a --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-postgresql-fs.xml @@ -0,0 +1,8 @@ + + kdehome + xdgconfig-postgresql.fs + xdglocal + akonadi_knut_resource + true + postgresql + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-sqlite-db.xml b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-sqlite-db.xml new file mode 100644 index 00000000..c36076f1 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/config-sqlite-db.xml @@ -0,0 +1,8 @@ + + kdehome + xdgconfig-sqlite.db + xdglocal + akonadi_knut_resource + true + sqlite + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc new file mode 100644 index 00000000..c5e90d84 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc @@ -0,0 +1,4 @@ +[ProcessedDefaults] +defaultaddressbook=done +defaultcalendar=done +defaultnotebook=done diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc new file mode 100644 index 00000000..61bb2f0c --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/akonadi_knut_resource_0rc @@ -0,0 +1,4 @@ +[General] +DataFile[$e]=$KDEHOME/testdata-res1.xml +FileWatchingEnabled=false + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdebugrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdebugrc new file mode 100755 index 00000000..a8438b93 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdebugrc @@ -0,0 +1,80 @@ +DisableAll=false + +[0] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[264] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[5250] +InfoOutput=2 + +[7009] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7011] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7012] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7014] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=0 +FatalFilename[$e]=kdebug.dbg +FatalOutput=0 +InfoFilename[$e]=kdebug.dbg +InfoOutput=0 +WarnFilename[$e]=kdebug.dbg +WarnOutput=0 + +[7021] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdedrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdedrc new file mode 100644 index 00000000..41d17814 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/share/config/kdedrc @@ -0,0 +1,3 @@ +[General] +CheckSycoca=false +CheckFileStamps=false diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/testdata-res1.xml b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/testdata-res1.xml new file mode 100644 index 00000000..00ed6420 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/kdehome/testdata-res1.xml @@ -0,0 +1,37 @@ + + + + + testmailbody + From: <test@user.tst> + \SEEN + \FLAGGED + \DRAFT + + + + /shared/vendor/kolab/folder-type event + + + /shared/vendor/kolab/folder-type contact + + + /shared/vendor/kolab/folder-type note + + + /shared/vendor/kolab/folder-type task + + + /shared/vendor/kolab/folder-type journal + + + /shared/vendor/kolab/folder-type configuration + + + /shared/vendor/kolab/folder-type freebusy + + + /shared/vendor/kolab/folder-type file + + + diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc new file mode 100644 index 00000000..fa9b2d47 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc @@ -0,0 +1,5 @@ +[%General] +ExternalPayload=false + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.fs/akonadi/akonadiserverrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.fs/akonadi/akonadiserverrc new file mode 100644 index 00000000..a7bb0c20 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-mysql.fs/akonadi/akonadiserverrc @@ -0,0 +1,6 @@ +[%General] +SizeThreshold=0 +ExternalPayload=true + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.db/akonadi/akonadiserverrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.db/akonadi/akonadiserverrc new file mode 100644 index 00000000..b2c8b1ab --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.db/akonadi/akonadiserverrc @@ -0,0 +1,9 @@ +[%General] +Driver=QPSQL +ExternalPayload=false + +[Search] +Manager=Dummy + +[QPSQL] +StartServer=true diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.fs/akonadi/akonadiserverrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.fs/akonadi/akonadiserverrc new file mode 100644 index 00000000..8333c730 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-postgresql.fs/akonadi/akonadiserverrc @@ -0,0 +1,10 @@ +[%General] +Driver=QPSQL +SizeThreshold=0 +ExternalPayload=true + +[Search] +Manager=Dummy + +[QPSQL] +StartServer=true diff --git a/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-sqlite.db/akonadi/akonadiserverrc b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-sqlite.db/akonadi/akonadiserverrc new file mode 100644 index 00000000..33e7f817 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/tests/unittestenv/xdgconfig-sqlite.db/akonadi/akonadiserverrc @@ -0,0 +1,10 @@ +[%General] +# This is a slightly adjusted version of the QSQLITE driver from Qt +# It is provided by akonadi itself +Driver=QSQLITE3 + +[Debug] +Tracer=null + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/resources/kolabproxy/upgradejob.cpp b/kdepim-runtime/resources/kolabproxy/upgradejob.cpp new file mode 100644 index 00000000..74f80fe5 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/upgradejob.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2012 Christian Mollekopf + * + * 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 "upgradejob.h" + +#include "kolabdefs.h" +#include "kolabhandler.h" + +#include "collectionannotationsattribute.h" //from shared + +#include +#include +#include +#include +#include + +#define IMAP_COLLECTION "IMAP_COLLECTION" +#define FOLDER_TYPE "FOLDER_TYPE" + +UpgradeJob::UpgradeJob( Kolab::Version targetVersion, + const Akonadi::AgentInstance &instance, + QObject *parent ) + : Akonadi::Job( parent ), + m_agentInstance( instance ), + m_targetVersion( targetVersion ) +{ + kDebug() << targetVersion; +} + +void UpgradeJob::doStart() +{ + kDebug(); + //Get all subdirectories of kolab resource + Akonadi::CollectionFetchJob *job = + new Akonadi::CollectionFetchJob( Akonadi::Collection::root(), + Akonadi::CollectionFetchJob::Recursive, this ); + job->fetchScope().setResource( m_agentInstance.identifier() ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*)) ); +} + +void UpgradeJob::collectionFetchResult( KJob *job ) +{ + kDebug(); + if ( job->error() ) { + kDebug() << job->errorString(); + emitResult(); + return; + } + int collCount = 0; + Akonadi::Collection::List collections = + static_cast( job )->collections(); + foreach ( const Akonadi::Collection &col, collections ) { + //FIXME: + // find a way to properly detect shared folders. + // Collection::CanCreateCollection never applies to shared folders, + // but that's just a workaround. + if ( !( col.rights() & Akonadi::Collection::CanCreateCollection ) || + !( col.rights() & Akonadi::Collection::CanChangeItem ) ) { + kDebug() << "skipping shared/non-editable folder"; + continue; + } + KolabV2::FolderType folderType = KolabV2::Mail; + Akonadi::CollectionAnnotationsAttribute *attr = 0; + if ( ( attr = col.attribute() ) ) { + folderType = + KolabV2::folderTypeFromString( Kolab::getFolderTypeAnnotation( attr->annotations() ) ); + } + if ( folderType == KolabV2::Mail ) { + //kWarning() << "Wrong folder annotation " + // << "(this should never happen, the annotation is probably not available)"; + continue; + } + + kDebug() << "upgrading " << col.id(); + collCount++; + Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob( col, this ); + itemFetchJob->fetchScope().fetchFullPayload( true ); + itemFetchJob->fetchScope().setCacheOnly( false ); + itemFetchJob->setProperty( IMAP_COLLECTION, QVariant::fromValue( col ) ); + itemFetchJob->setProperty( FOLDER_TYPE, QVariant::fromValue( static_cast(folderType) ) ); + connect( itemFetchJob, SIGNAL(result(KJob*)), this, SLOT(itemFetchResult(KJob*)) ); + } + //Percent is only emitted when Bytes is the unit + setTotalAmount( Bytes, collCount ); + if ( !collCount ) { + emitResult(); + } +} + +void UpgradeJob::itemFetchResult( KJob *job ) +{ + if ( job->error() ) { + kDebug() << job->errorString(); + checkResult(); + return; // Akonadi::Job propagates that automatically + } + Akonadi::ItemFetchJob *j = static_cast( job ); + if ( j->items().isEmpty() ) { + qWarning() << "no items fetched "; + checkResult(); + return; + } + + const Akonadi::Collection imapCollection = + j->property( IMAP_COLLECTION ).value(); + if ( !imapCollection.isValid() ) { + qWarning() << "invalid imap collection"; + checkResult(); + return; + } + + KolabV2::FolderType folderType = + static_cast( j->property(FOLDER_TYPE).toInt() ); + + KolabHandler::Ptr handler = KolabHandler::createHandler( folderType, imapCollection ); + + if ( !handler ) { + qWarning() << "invalid handler"; + checkResult(); + return; + } + handler->setKolabFormatVersion( m_targetVersion ); + + foreach ( Akonadi::Item imapItem, j->items() ) { //krazy:exclude=foreach + if ( !imapItem.isValid() ) { + qWarning() << "invalid item"; + continue; + } + kDebug() << "updating item " << imapItem.id(); + const Akonadi::Item::List &translatedItems = + handler->translateItems( Akonadi::Item::List() << imapItem ); + + if ( translatedItems.size() != 1 ) { + qWarning() << "failed to translateItems" << translatedItems.size(); + continue; + } + handler->toKolabFormat( translatedItems.first(), imapItem ); + Akonadi::ItemModifyJob *modJob = new Akonadi::ItemModifyJob( imapItem, this ); + connect( modJob, SIGNAL(result(KJob*)), this, SLOT(itemModifyResult(KJob*)) ); + } + checkResult(); +} + +void UpgradeJob::checkResult() +{ + setProcessedAmount( Bytes, processedAmount( Bytes ) + 1 ); + if ( processedAmount( Bytes ) >= totalAmount( Bytes ) ) { + emitResult(); + } +} + +void UpgradeJob::itemModifyResult( KJob *job ) +{ + if ( job->error() ) { + kDebug() << job->errorString(); + return; // Akonadi::Job propagates that automatically + } + kDebug() << "modjob done"; +} + diff --git a/kdepim-runtime/resources/kolabproxy/upgradejob.h b/kdepim-runtime/resources/kolabproxy/upgradejob.h new file mode 100644 index 00000000..4b8c31f7 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/upgradejob.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012 Christian Mollekopf + * + * 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 KOLABPROXY_UPGRADEJOB_H +#define KOLABPROXY_UPGRADEJOB_H + +#include +#include + +#include //libkolab + +/** + * Fetch all items of an imap resource, read them, and write them out in the target version. + */ +class UpgradeJob : public Akonadi::Job +{ + Q_OBJECT + public: + explicit UpgradeJob( Kolab::Version targetVersion, + const Akonadi::AgentInstance &instance, + QObject *parent = 0 ); + + protected: + void doStart(); + + private slots: + void collectionFetchResult( KJob *job ); + void itemFetchResult( KJob *job ); + void itemModifyResult( KJob *job ); + + private: + void checkResult(); + Akonadi::AgentInstance m_agentInstance; + Kolab::Version m_targetVersion; +}; + +#endif // UPGRADEJOB_H diff --git a/kdepim-runtime/resources/kolabproxy/wizard/CMakeLists.txt b/kdepim-runtime/resources/kolabproxy/wizard/CMakeLists.txt new file mode 100644 index 00000000..b18bebf5 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/wizard/CMakeLists.txt @@ -0,0 +1,5 @@ + +install( + FILES kolabwizard.desktop kolabwizard.es kolabwizard.ui + DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/kolab +) diff --git a/kdepim-runtime/resources/kolabproxy/wizard/Messages.sh b/kdepim-runtime/resources/kolabproxy/wizard/Messages.sh new file mode 100644 index 00000000..a41341f5 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_kolab.pot +$XGETTEXT -kqsTr *.es -j -o $podir/accountwizard_kolab.pot diff --git a/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.desktop b/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.desktop new file mode 100644 index 00000000..367dcd41 --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.desktop @@ -0,0 +1,101 @@ +[Desktop Entry] +Name=Kolab Groupware Server +Name[bg]=Сървър Kolab Groupware +Name[bs]=Server kolaborativnog softvera +Name[ca]=Servidor de treball en grup Kolab +Name[ca@valencia]=Servidor de treball en grup Kolab +Name[cs]=Kolab Groupware server +Name[da]=Kolab groupware-server +Name[de]=Kolab Groupware-Server +Name[el]=ΕξυπηÏετητής Groupware Kolab +Name[en_GB]=Kolab Groupware Server +Name[es]=Servidor de trabajo en grupo Kolab +Name[et]=Kolabi grupitöö server +Name[fi]=Kolab-työryhmäpalvelin +Name[fr]=Serveur de logiciels de collaboration Kolab +Name[ga]=Freastalaí Groupware Kolab +Name[gl]=Servidor de Traballo en Grupo Kolab +Name[hu]=Kolab csoportmunka-kiszolgáló +Name[ia]=Servitor de Kolab Groupware +Name[it]=Server di groupware Kolab +Name[ja]=Kolab グループウェアサーム+Name[kk]=Kolab топтық Ñ–Ñ Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ Kolab Groupware +Name[ko]=Kolab 그룹웨어 서버 +Name[lt]=Kolab grupinio darbo serveris +Name[lv]=Kolab grupdarba serveris +Name[nb]=Kolab groupware-tjener +Name[nds]=Kolab-Arbeitkoppelserver +Name[nl]=Kolab groupwareserver +Name[nn]=Kolab Groupware-tenar +Name[pl]=Serwer Groupware Kolab +Name[pt]=Servidor de Groupware Kolab +Name[pt_BR]=Servidor groupware Kolab +Name[ro]=Server Kolab Groupware +Name[ru]=Сервер ÑовмеÑтной работы Kolab +Name[sk]=Groupware Server Kolab +Name[sl]=Strežnik za skupinsko delo Kolab +Name[sr]=Колабов групверÑки Ñервер +Name[sr@ijekavian]=Колабов групверÑки Ñервер +Name[sr@ijekavianlatin]=Kolabov grupverski server +Name[sr@latin]=Kolabov grupverski server +Name[sv]=Kolab grupprogramserver +Name[tr]=Kolab Groupware Sunucusu +Name[uk]=Сервер групової роботи Kolab +Name[x-test]=xxKolab Groupware Serverxx +Name[zh_CN]=Kolab 群件æœåŠ¡å™¨ +Name[zh_TW]=Kolab 群組伺æœå™¨ +Icon=kolab +Comment=Kolab Groupware Server +Comment[bg]=Сървър Kolab Groupware +Comment[bs]=Server kolaborativnog softvera +Comment[ca]=Servidor de treball en grup Kolab +Comment[ca@valencia]=Servidor de treball en grup Kolab +Comment[cs]=Kolab Groupware server +Comment[da]=Kolab groupware-server +Comment[de]=Kolab Groupware-Server +Comment[el]=ΕξυπηÏετητής Groupware Kolab +Comment[en_GB]=Kolab Groupware Server +Comment[es]=Servidor de trabajo en grupo Kolab +Comment[et]=Kolabi grupitöö server +Comment[fi]=Kolab-työryhmäpalvelin +Comment[fr]=Serveur de logiciels de collaboration Kolab +Comment[ga]=Freastalaí Groupware Kolab +Comment[gl]=Servidor de traballo en grupo Kolab +Comment[hu]=Kolab csoportmunka-kiszolgáló +Comment[ia]=Servitor de Kolab Groupware +Comment[it]=Server di groupware Kolab +Comment[ja]=Kolab グループウェアサーム+Comment[kk]=Kolab топтық Ñ–Ñ Ñервері +Comment[km]=ម៉ាស៊ីន​បម្រើ Kolab Groupware +Comment[ko]=Kolab 그룹웨어 서버 +Comment[lt]=Kolab grupinio darbo serveris +Comment[lv]=Kolab grupdarba serveris +Comment[nb]=Kolab groupware-tjener +Comment[nds]=Kolab-Arbeitkoppelserver +Comment[nl]=Kolab-groupware-server +Comment[nn]=Kolab Groupware-tenar +Comment[pl]=Serwer Kolab Groupware +Comment[pt]=Servidor de 'Groupware' Kolab +Comment[pt_BR]=Servidor groupware Kolab +Comment[ro]=Server Kolab Groupware +Comment[ru]=Сервер ÑовмеÑтной работы Kolab +Comment[sk]=Groupware Server Kolab +Comment[sl]=Strežnik za skupinsko delo Kolab +Comment[sr]=Колабов групверÑки Ñервер +Comment[sr@ijekavian]=Колабов групверÑки Ñервер +Comment[sr@ijekavianlatin]=Kolabov grupverski server +Comment[sr@latin]=Kolabov grupverski server +Comment[sv]=Kolab grupprogramserver +Comment[tr]=Kolab Groupware Sunucusu +Comment[uk]=Сервер групової роботи Kolab +Comment[x-test]=xxKolab Groupware Serverxx +Comment[zh_CN]=Kolab 群件æœåŠ¡å™¨ +Comment[zh_TW]=Kolab 群組伺æœå™¨ + +[Wizard] +Type=message/rfc822,text/directory,text/calendar,text/x-vnd.akonadi.note +Script=kolabwizard.es + +[Translate] +Filename=accountwizard_kolab diff --git a/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.es b/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.es new file mode 100644 index 00000000..14eabb9d --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.es @@ -0,0 +1,192 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2010 Casey Link + + 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. +*/ + +// add this function to trim user input of whitespace when needed +String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); }; + +var page = Dialog.addPage( "kolabwizard.ui", qsTr("Personal Settings") ); +var userChangedServerAddress = false; + +page.widget().nameEdit.text = SetupManager.name() +page.widget().emailEdit.text = SetupManager.email() +page.widget().passwordEdit.text = SetupManager.password() +guessServerName(); + +if ( SetupManager.personalDataAvailable() ) { + page.widget().nameEdit.visible = false; + page.widget().nameLabel.visible = false; + page.widget().emailEdit.visible = false; + page.widget().emailLabel.visible = false; + page.widget().passwordEdit.visible = false; + page.widget().passwordLabel.visible = false; +} + + +function guessServerName() +{ + if ( userChangedServerAddress == true ) { + return; + } + + var email = page.widget().emailEdit.text; + var pos = email.indexOf( "@" ); + if ( pos >= 0 && (pos + 1) < email.length ) { + var server = email.slice( pos + 1, email.length ); + page.widget().serverAddress.text = server; + } + + userChangedServerAddress = false; +} + +function emailChanged( arg ) +{ + validateInput(); + guessServerName(); +} + +function serverChanged( arg ) +{ + validateInput(); + if ( arg == "" ) { + userChangedServerAddress = false; + } else { + userChangedServerAddress = true; + } +} + +function validateInput() +{ + if ( page.widget().serverAddress.text.trim() == "" || page.widget().emailEdit.text.trim() == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +// stage 1, setup identity and run imap server test +// stage 2, smtp setup (no server test), ldap and korganizer +var stage = 1; +var identity; // global so it can be accesed in setup and testOk + +var kolabRes; +function setup() +{ + var serverAddress = page.widget().serverAddress.text.trim(); + if ( stage == 1 ) { + SetupManager.openWallet(); + kolabRes = SetupManager.createResource( "akonadi_kolabproxy_resource" ); + + identity = SetupManager.createIdentity(); + identity.setEmail( page.widget().emailEdit.text ); + identity.setRealName( page.widget().nameEdit.text ); + + ServerTest.test( serverAddress, "imap" ); + } else { // stage 2 + var smtp = SetupManager.createTransport( "smtp" ); + smtp.setName( serverAddress ); + smtp.setHost( serverAddress ); + smtp.setPort( 465 ); + smtp.setEncryption( "SSL" ); + smtp.setAuthenticationType( "plain" ); // using plain is ok, because we are using SSL. + smtp.setUsername( page.widget().emailEdit.text ); + smtp.setPassword( page.widget().passwordEdit.text ); + identity.setTransport( smtp ); + + var ldap = SetupManager.createLdap(); + ldap.setUser( page.widget().emailEdit.text ); + ldap.setServer( serverAddress ); + + var korganizer = SetupManager.createConfigFile( "korganizerrc" ); + korganizer.setName( "korganizer" ); + korganizer.setConfig( "FreeBusy Retrieve", "FreeBusyFullDomainRetrieval","true"); + korganizer.setConfig( "FreeBusy Retrieve", "FreeBusyRetrieveAuto", "true" ); + korganizer.setConfig( "FreeBusy Retrieve", "FreeBusyRetrieveUrl", "https://" + serverAddress + "/freebusy/" ); + SetupManager.execute(); + } +} + +function testResultFail() +{ + testOk( -1 ); +} + +var imapRes; +function testOk( arg ) +{ + print("testOk arg =", arg); + imapRes = SetupManager.createResource( "akonadi_imap_resource" ); + imapRes.setName( page.widget().serverAddress.text.trim() ); + imapRes.setOption( "ImapServer", page.widget().serverAddress.text.trim() ); + imapRes.setOption( "UserName", page.widget().emailEdit.text.trim() ); + imapRes.setOption( "Password", page.widget().passwordEdit.text.trim() ); + imapRes.setOption( "UseDefaultIdentity", false ); + imapRes.setOption( "AccountIdentity", identity.uoid() ); + imapRes.setOption( "DisconnectedModeEnabled", true ); + imapRes.setOption( "IntervalCheckTime", 60 ); + imapRes.setOption( "SubscriptionEnabled", true ); + imapRes.setOption( "SieveSupport", true ); + imapRes.setOption( "Authentication", 7 ); // ClearText + if ( arg == "ssl" ) { + // The ENUM used for authentication (in the imap resource only) + imapRes.setOption( "Safety", "SSL" ); // SSL/TLS + imapRes.setOption( "ImapPort", 993 ); + } else if ( arg == "tls" ) { // tls is really STARTTLS + imapRes.setOption( "Safety", "STARTTLS" ); // STARTTLS + imapRes.setOption( "ImapPort", 143 ); + } else if ( arg == "none" ) { + imapRes.setOption( "Safety", "NONE" ); // No encryption + imapRes.setOption( "ImapPort", 143 ); + } else { + // safe default fallback in case server test failed + imapRes.setOption( "Safety", "STARTTLS" ); + imapRes.setOption( "ImapPort", 143 ); + } + imapRes.finished.connect(configureKolabVersion); + stage = 2; + setup(); +} + +function configureKolabVersion( arg ) +{ + var kolabVersion = page.widget().versionComboBox.currentIndex; + var kolabproxyConfig = SetupManager.createConfigFile( "akonadi_kolabproxy_resourcerc" ); + kolabproxyConfig.setName("kolabproxy"); + if (kolabVersion == 0) { + kolabproxyConfig.setConfig( "KolabProxyResourceSettings", "KolabFormatVersion" + imapRes.identifier(), "0"); + } else { + kolabproxyConfig.setConfig( "KolabProxyResourceSettings", "KolabFormatVersion" + imapRes.identifier(), "1"); + } + //We have to write to the config file manually as the setup process has already finished + kolabproxyConfig.write(); + //And load the config + kolabRes.reconfigure(); +} + +try { + ServerTest.testFail.connect(testResultFail); + ServerTest.testResult.connect(testOk); + page.widget().emailEdit.textChanged.connect(emailChanged); + page.widget().serverAddress.textChanged.connect(serverChanged); + page.pageLeftNext.connect(setup); +} catch (e) { + print(e); +} + +validateInput(); diff --git a/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.ui b/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.ui new file mode 100644 index 00000000..8cebe47a --- /dev/null +++ b/kdepim-runtime/resources/kolabproxy/wizard/kolabwizard.ui @@ -0,0 +1,111 @@ + + + kolabWizard + + + + 0 + 0 + 368 + 124 + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Name: + + + nameEdit + + + + + + + + + + &Email: + + + emailEdit + + + + + + + + + + &Password: + + + passwordEdit + + + + + + + QLineEdit::Password + + + + + + + &Server Address: + + + serverAddress + + + + + + + + + + Kolab Version: + + + + + + + 1 + + + + v2 + + + + + v3 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + serverAddress + + + +
diff --git a/kdepim-runtime/resources/localbookmarks/CMakeLists.txt b/kdepim-runtime/resources/localbookmarks/CMakeLists.txt new file mode 100644 index 00000000..92f8259e --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/CMakeLists.txt @@ -0,0 +1,36 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( localbookmarksresource_SRCS + localbookmarksresource.cpp +) + +install( FILES localbookmarksresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + + +kde4_add_kcfg_files(localbookmarksresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/localbookmarksresource.kcfg org.kde.Akonadi.LocalBookmarks.Settings) +qt4_add_dbus_adaptor(localbookmarksresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.LocalBookmarks.Settings.xml settings.h Settings +) + +kde4_add_executable(akonadi_localbookmarks_resource ${localbookmarksresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_localbookmarks_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_localbookmarks_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.LocalBookmarks") + set_target_properties(akonadi_localbookmarks_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi LocalBookmarks Resource") +endif () + +target_link_libraries(akonadi_localbookmarks_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTDBUS_LIBRARY} ${KDE4_KIO_LIBS}) + +install(TARGETS akonadi_localbookmarks_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) + diff --git a/kdepim-runtime/resources/localbookmarks/Messages.sh b/kdepim-runtime/resources/localbookmarks/Messages.sh new file mode 100644 index 00000000..460d0dc9 --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_localbookmarks_resource.pot +rm -f rc.cpp diff --git a/kdepim-runtime/resources/localbookmarks/localbookmarksresource.cpp b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.cpp new file mode 100644 index 00000000..de2737b1 --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.cpp @@ -0,0 +1,221 @@ +/* + Copyright (c) 2006 Till Adam + + 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 "localbookmarksresource.h" + +#include "settings.h" +#include "settingsadaptor.h" + +#include +#include +#include +#include + +using namespace Akonadi; + +LocalBookmarksResource::LocalBookmarksResource( const QString &id ) + : ResourceBase( id ), mBookmarkManager( 0 ) +{ + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + const QString fileName = Settings::self()->path(); + if (!fileName.isEmpty() ) { + mBookmarkManager = KBookmarkManager::managerForFile( fileName, name() ); + } +} + +LocalBookmarksResource::~LocalBookmarksResource() +{ +} + +bool LocalBookmarksResource::retrieveItem( const Akonadi::Item &item, const QSet& ) +{ + itemRetrieved( item ); + + return true; +} + +void LocalBookmarksResource::configure( WId windowId ) +{ + const QString oldFile = Settings::self()->path(); + + KUrl url; + if ( !oldFile.isEmpty() ) + url = KUrl::fromPath( oldFile ); + else + url = KUrl::fromPath( QDir::homePath() ); + + const QString newFile = KFileDialog::getOpenFileNameWId( url, QLatin1String("*.xml |") + i18nc( "Filedialog filter for *.xml", + "XML Bookmark file"), + windowId, i18n( "Select Bookmarks File" ) ); + + if ( newFile.isEmpty() ) { + emit configurationDialogRejected(); + return; + } + + if ( oldFile == newFile ) { + emit configurationDialogAccepted(); + return; + } + + Settings::self()->setPath( newFile ); + + mBookmarkManager = KBookmarkManager::managerForFile( newFile, name() ); + + Settings::self()->writeConfig(); + synchronize(); + + emit configurationDialogAccepted(); +} + +void LocalBookmarksResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + if ( item.mimeType() != QLatin1String( "application/x-xbel" ) ) { + cancelTask( i18n( "Item is not a bookmark" ) ); + return; + } + + KBookmark bookmark = item.payload(); + KBookmark bookmarkGroup = mBookmarkManager->findByAddress( collection.remoteId() ); + if ( !bookmarkGroup.isGroup() ) { + cancelTask( i18n( "Bookmark group not found" ) ); + return; + } + + KBookmarkGroup group = bookmarkGroup.toGroup(); + group.addBookmark( bookmark ); + + // saves the file + mBookmarkManager->emitChanged( group ); + + changeCommitted( item ); +} + +void LocalBookmarksResource::itemChanged( const Akonadi::Item& item, const QSet& ) +{ + KBookmark bookmark = item.payload(); + + // saves the file + mBookmarkManager->emitChanged( bookmark.parentGroup() ); + + changeCommitted( item ); +} + +void LocalBookmarksResource::itemRemoved( const Akonadi::Item &item ) +{ + const KBookmark bookmark = mBookmarkManager->findByAddress( item.remoteId() ); + KBookmarkGroup bookmarkGroup = bookmark.parentGroup(); + + bookmarkGroup.deleteBookmark( bookmark ); + + // saves the file + mBookmarkManager->emitChanged( bookmarkGroup ); + + changeCommitted( item ); +} + +static Collection::List listRecursive( const KBookmarkGroup &parentGroup, const Collection &parentCollection ) +{ + const QStringList mimeTypes = QStringList() << QLatin1String("application/x-xbel") << Collection::mimeType(); + + Collection::List collections; + + for ( KBookmark it = parentGroup.first(); !it.isNull(); it = parentGroup.next( it ) ) { + + if ( !it.isGroup() ) + continue; + + KBookmarkGroup bookmarkGroup = it.toGroup(); + Collection collection; + collection.setName( bookmarkGroup.fullText() + QLatin1Char('(') + bookmarkGroup.address() + QLatin1Char(')') ); // has to be unique + collection.setRemoteId( bookmarkGroup.address() ); + collection.setParentCollection( parentCollection ); + collection.setContentMimeTypes( mimeTypes ); // ### + + collections << collection; + collections << listRecursive( bookmarkGroup, collection ); + } + + return collections; +} + +void LocalBookmarksResource::retrieveCollections() +{ + Collection root; + root.setParentCollection( Collection::root() ); + root.setRemoteId( Settings::self()->path() ); + root.setName( name() ); + QStringList mimeTypes; + mimeTypes << QLatin1String("application/x-xbel") << Collection::mimeType(); + root.setContentMimeTypes( mimeTypes ); + + + if (!mBookmarkManager) { + mBookmarkManager = KBookmarkManager::managerForFile( Settings::self()->path(), name() ); + } + + Collection::List list; + list << root; + list << listRecursive( mBookmarkManager->root(), root ); + + collectionsRetrieved( list ); +} + +void LocalBookmarksResource::retrieveItems( const Akonadi::Collection &collection ) +{ + if ( !collection.isValid() ) { + cancelTask( i18n( "Bookmark collection is invalid" ) ); + return; + } + + KBookmarkGroup bookmarkGroup; + if ( collection.remoteId() == Settings::self()->path() ) { + bookmarkGroup = mBookmarkManager->root(); + } else { + + const KBookmark bookmark = mBookmarkManager->findByAddress( collection.remoteId() ); + if ( bookmark.isNull() || !bookmark.isGroup() ) { + cancelTask( i18n( "Bookmark collection is invalid" ) ); + return; + } + + bookmarkGroup = bookmark.toGroup(); + } + + Item::List itemList; + for ( KBookmark it = bookmarkGroup.first(); !it.isNull(); it = bookmarkGroup.next( it ) ) { + + if ( it.isGroup() || it.isSeparator() || it.isNull() ) + continue; + + Item item; + item.setRemoteId( it.address() ); + item.setMimeType( QLatin1String("application/x-xbel") ); + item.setPayload( it ); + itemList.append( item ); + } + + itemsRetrieved( itemList ); +} + +AKONADI_RESOURCE_MAIN( LocalBookmarksResource ) + diff --git a/kdepim-runtime/resources/localbookmarks/localbookmarksresource.desktop b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.desktop new file mode 100644 index 00000000..996e716b --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.desktop @@ -0,0 +1,102 @@ +[Desktop Entry] +Name=Local Bookmarks +Name[ar]=علامات محلية +Name[bs]=Lokale oznake +Name[ca]=Adreces d'interès locals +Name[ca@valencia]=Punts locals +Name[cs]=Místní záložky +Name[da]=Lokale bogmærker +Name[de]=Lokale Lesezeichen +Name[el]=Τοπικοί σελιδοδείκτες +Name[en_GB]=Local Bookmarks +Name[es]=Marcadores locales +Name[et]=Kohalikud järjehoidjad +Name[fi]=Paikalliset kirjanmerkit +Name[fr]=Signets locaux +Name[ga]=Leabharmharcanna Logánta +Name[gl]=Marcadores Locais +Name[hu]=Helyi könyvjelzÅ‘k +Name[ia]=Favoritos local +Name[it]=Segnalibri locali +Name[ja]=ローカルブックマーク +Name[kk]=Жергілікті бетбелгілер +Name[km]=ចំណាំ​មូលដ្ឋាន +Name[ko]=로컬 책갈피 +Name[lt]=Vietinis adresynas +Name[lv]=LokÄlÄs grÄmatzÄ«mes +Name[nb]=Lokale bokmerker +Name[nds]=Lokaal Leestekens +Name[nl]=Lokale bladwijzers +Name[nn]=Lokalbokmerke +Name[pa]=ਲੋਕਲ ਬà©à©±à¨•à¨®à¨¾à¨°à¨• +Name[pl]=ZakÅ‚adki lokalne +Name[pt]=Favoritos Locais +Name[pt_BR]=Favoritos locais +Name[ro]=Semne de carte locale +Name[ru]=Локальные закладки +Name[sk]=Miestne záložky +Name[sl]=Krajevni zaznamki +Name[sr]=Локални обележивачи +Name[sr@ijekavian]=Локални обиљеживачи +Name[sr@ijekavianlatin]=Lokalni obilježivaÄi +Name[sr@latin]=Lokalni obeleživaÄi +Name[sv]=Lokala bokmärken +Name[tr]=Yerel Yer Ä°mleri +Name[uk]=Локальні закладки +Name[x-test]=xxLocal Bookmarksxx +Name[zh_CN]=本地书签 +Name[zh_TW]=本地書籤 +Comment=Loads data from a local bookmarks file +Comment[ar]=تحمل البيانات من مل٠العلامات المحلية +Comment[bs]=UÄitava podatke iz datoteke lokalnih oznaka +Comment[ca]=Carrega les dades des d'un fitxer d'adreces d'interès local +Comment[ca@valencia]=Carrega dades des d'un fitxer de punts local +Comment[cs]=NaÄítá data z místních záložek +Comment[da]=Indlæser data fra en lokal bogmærkefil +Comment[de]=Daten werden aus einer lokalen Lesezeichen-Datei geladen +Comment[el]=ΦόÏτωση δεδομένων από ένα τοπικό αÏχείο σελιδοδεικτών +Comment[en_GB]=Loads data from a local bookmarks file +Comment[es]=Carga datos de un archivo de marcadores local +Comment[et]=Andmete laadimine kohalikust järjehoidjafailist +Comment[fi]=Lataa tietoja paikallisesta kirjanmerkkitiedostosta +Comment[fr]=Charge des données depuis un fichier local de signets +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad logánta leabharmharcanna +Comment[gl]=Carga datos desde un ficheiro local de marcadores +Comment[hu]=AdatbetöltÅ‘ helyi könyvjelzÅ‘fájlokhoz +Comment[ia]=Lege datos de un file con favoritos local +Comment[it]=Carica dati da un file locale di segnalibri +Comment[ja]=ローカルã®ãƒ–ックマークファイルã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=Жергілікті бетбелгілер файлынан деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ឯកសារ​ចំណាំ​មូលដ្ឋាន +Comment[ko]=로컬 책갈피 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 불러옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ vietinio adresyno failo +Comment[lv]=IelÄdÄ“ datus no lokÄlÄ grÄmatzÄ«mju faila +Comment[nb]=Laster data fra en lokal bokmerkefil +Comment[nds]=Laadt Daten ut en lokaal Leestekendatei +Comment[nl]=Laadt gegevens van een lokaal bladwijzerbestand +Comment[nn]=Lastar data frÃ¥ ei lokal bokmerkefil +Comment[pa]=ਇੱਕ ਲੋਕਲ ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਫਾਇਲ ਤੋਂ ਡਾਟਾ ਲੋਡ ਕਰੋ +Comment[pl]=Wczytuje dane z pliku lokalnych zakÅ‚adek +Comment[pt]=Carrega os dados a partir de um ficheiro de favoritos local +Comment[pt_BR]=Carrega os dados de um arquivo de favoritos local +Comment[ro]=ÃŽncarcă date dintr-un fiÈ™ier local de tip semn de carte +Comment[ru]=Загрузка данных из локального файла Ñ Ð·Ð°ÐºÐ»Ð°Ð´ÐºÐ°Ð¼Ð¸ +Comment[sk]=NaÄíta dáta z miestneho súboru záložiek +Comment[sl]=Naloži podatke iz krajevne datoteke z zaznamki +Comment[sr]=Учитава податке из локалног фајла обележивача +Comment[sr@ijekavian]=Учитава податке из локалног фајла обиљеживача +Comment[sr@ijekavianlatin]=UÄitava podatke iz lokalnog fajla obilježivaÄa +Comment[sr@latin]=UÄitava podatke iz lokalnog fajla obeleživaÄa +Comment[sv]=Laddar data frÃ¥n en lokal bokmärkesfil +Comment[tr]=Yerel bir yer imleri dosyasından veri yükler +Comment[uk]=Завантажує дані з локального файла закладок +Comment[x-test]=xxLoads data from a local bookmarks filexx +Comment[zh_CN]=ä»Žæœ¬åœ°ä¹¦ç­¾æ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從本地書籤檔載入資料 +Type=AkonadiResource +Exec=akonadi_localbookmarks_resource +Icon=bookmarks + +X-Akonadi-MimeTypes=text/bookmark +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_localbookmarks_resource diff --git a/kdepim-runtime/resources/localbookmarks/localbookmarksresource.h b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.h new file mode 100644 index 00000000..bfa9fa65 --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2006 Till Adam + + 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 LOCALBOOKMARKS_H +#define LOCALBOOKMARKS_H + +#include +#include + +class KBookmarkManager; + +class LocalBookmarksResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + explicit LocalBookmarksResource( const QString &id ); + ~LocalBookmarksResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + private: + KBookmarkManager *mBookmarkManager; +}; + +#endif diff --git a/kdepim-runtime/resources/localbookmarks/localbookmarksresource.kcfg b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.kcfg new file mode 100644 index 00000000..d44d122e --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/localbookmarksresource.kcfg @@ -0,0 +1,18 @@ + + + + + + + + + + + false + + + diff --git a/kdepim-runtime/resources/localbookmarks/settings.kcfgc b/kdepim-runtime/resources/localbookmarks/settings.kcfgc new file mode 100644 index 00000000..858d6a2d --- /dev/null +++ b/kdepim-runtime/resources/localbookmarks/settings.kcfgc @@ -0,0 +1,8 @@ +File=localbookmarksresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/maildir/CMakeLists.txt b/kdepim-runtime/resources/maildir/CMakeLists.txt new file mode 100644 index 00000000..7df8c33c --- /dev/null +++ b/kdepim-runtime/resources/maildir/CMakeLists.txt @@ -0,0 +1,44 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/libmaildir + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +add_subdirectory( tests ) +add_subdirectory( wizard ) + +# maildir access library +add_subdirectory(libmaildir) + + +########### next target ############### + +set( maildirresource_SRCS + maildirresource.cpp + configdialog.cpp + main.cpp + retrieveitemsjob.cpp +) + +install( FILES maildirresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(maildirresource_SRCS settings.ui) + +kde4_add_kcfg_files(maildirresource_SRCS settings.kcfgc) + +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/maildirresource.kcfg org.kde.Akonadi.Maildir.Settings) + +qt4_add_dbus_adaptor(maildirresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Maildir.Settings.xml settings.h Akonadi_Maildir_Resource::MaildirSettings maildirsettingsadaptor MaildirSettingsAdaptor +) + +kde4_add_plugin(akonadi_maildir_resource ${maildirresource_SRCS}) + +target_link_libraries(akonadi_maildir_resource ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} maildir ${QT_QTDBUS_LIBRARY} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS} folderarchivesettings ) + +install(TARGETS akonadi_maildir_resource DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Maildir.Settings.xml + DESTINATION ${DBUS_INTERFACES_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/maildir/Messages.sh b/kdepim-runtime/resources/maildir/Messages.sh new file mode 100644 index 00000000..ed3a4ea3 --- /dev/null +++ b/kdepim-runtime/resources/maildir/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT libmaildir/*.cpp *.cpp -o $podir/akonadi_maildir_resource.pot diff --git a/kdepim-runtime/resources/maildir/configdialog.cpp b/kdepim-runtime/resources/maildir/configdialog.cpp new file mode 100644 index 00000000..d5cf947d --- /dev/null +++ b/kdepim-runtime/resources/maildir/configdialog.cpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2008 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 "configdialog.h" +#include "settings.h" +#include "resources/folderarchivesettings/folderarchivesettingpage.h" + +#include + +#include +#include +#include + +using KPIM::Maildir; +using namespace Akonadi_Maildir_Resource; + +ConfigDialog::ConfigDialog(MaildirSettings *settings, const QString &identifier, QWidget * parent) : + KDialog( parent ), + mSettings( settings ), + mToplevelIsContainer( false ) +{ + setCaption( i18n( "Select a MailDir folder" ) ); + ui.setupUi( mainWidget() ); + mFolderArchiveSettingPage = new FolderArchiveSettingPage(identifier); + mFolderArchiveSettingPage->loadSettings(); + ui.tabWidget->addTab(mFolderArchiveSettingPage, i18n("Folder Archive")); + + mManager = new KConfigDialogManager( this, mSettings ); + mManager->updateWidgets(); + ui.kcfg_Path->setMode( KFile::Directory | KFile::ExistingOnly ); + ui.kcfg_Path->setUrl( KUrl( mSettings->path() ) ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); + connect( ui.kcfg_Path->lineEdit(), SIGNAL(textChanged(QString)), SLOT(checkPath()) ); + ui.kcfg_Path->lineEdit()->setFocus(); + checkPath(); +} + +void ConfigDialog::checkPath() +{ + if ( ui.kcfg_Path->url().isEmpty() ) { + ui.statusLabel->setText( i18n( "The selected path is empty." ) ); + enableButton( Ok, false ); + return; + } + bool ok = false; + mToplevelIsContainer = false; + QDir d( ui.kcfg_Path->url().toLocalFile() ); + + if ( d.exists() ) { + Maildir md( d.path() ); + if ( !md.isValid( false ) ) { + Maildir md2( d.path(), true ); + if ( md2.isValid( false ) ) { + ui.statusLabel->setText( i18n( "The selected path contains valid Maildir folders." ) ); + mToplevelIsContainer = true; + ok = true; + } else { + ui.statusLabel->setText( md.lastError() ); + } + } else { + ui.statusLabel->setText( i18n( "The selected path is a valid Maildir." ) ); + ok = true; + } + } else { + d.cdUp(); + if ( d.exists() ) { + ui.statusLabel->setText( i18n( "The selected path does not exist yet, a new Maildir will be created." ) ); + mToplevelIsContainer = true; + ok = true; + } else { + ui.statusLabel->setText( i18n( "The selected path does not exist." ) ); + } + } + enableButton( Ok, ok ); +} + +void ConfigDialog::save() +{ + mFolderArchiveSettingPage->writeSettings(); + mManager->updateSettings(); + QString path = ui.kcfg_Path->url().isLocalFile() ? ui.kcfg_Path->url().toLocalFile() : ui.kcfg_Path->url().path(); + mSettings->setPath( path ); + mSettings->setTopLevelIsContainer( mToplevelIsContainer ); + mSettings->writeConfig(); + + if ( ui.kcfg_Path->url().isLocalFile() ) { + QDir d( path ); + if ( !d.exists() ) { + d.mkpath( ui.kcfg_Path->url().toLocalFile() ); + } + } +} + diff --git a/kdepim-runtime/resources/maildir/configdialog.h b/kdepim-runtime/resources/maildir/configdialog.h new file mode 100644 index 00000000..db7202de --- /dev/null +++ b/kdepim-runtime/resources/maildir/configdialog.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2008 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +#include "ui_settings.h" + +class KConfigDialogManager; +namespace Akonadi_Maildir_Resource +{ +class MaildirSettings; +} +class FolderArchiveSettingPage; +class ConfigDialog : public KDialog +{ + Q_OBJECT + public: + explicit ConfigDialog( Akonadi_Maildir_Resource::MaildirSettings *settings, const QString &identifier, QWidget *parent = 0 ); + + private slots: + void checkPath(); + void save(); + + private: + Ui::ConfigDialog ui; + KConfigDialogManager* mManager; + FolderArchiveSettingPage *mFolderArchiveSettingPage; + Akonadi_Maildir_Resource::MaildirSettings *mSettings; + bool mToplevelIsContainer; +}; + +#endif diff --git a/kdepim-runtime/resources/maildir/libmaildir/CMakeLists.txt b/kdepim-runtime/resources/maildir/libmaildir/CMakeLists.txt new file mode 100644 index 00000000..5bc752e1 --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/CMakeLists.txt @@ -0,0 +1,12 @@ + +add_subdirectory( tests ) + +set(maildir_LIB_SRCS keycache.cpp maildir.cpp) + +kde4_add_library(maildir ${LIBRARY_TYPE} ${maildir_LIB_SRCS}) + +target_link_libraries(maildir ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KPIMUTILS_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${QT_QTNETWORK_LIBRARY}) + +set_target_properties(maildir PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) + +install(TARGETS maildir ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/maildir/libmaildir/keycache.cpp b/kdepim-runtime/resources/maildir/libmaildir/keycache.cpp new file mode 100644 index 00000000..814ce6c2 --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/keycache.cpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2012 Andras Mantia + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "keycache.h" + +#include + +KeyCache* KeyCache::mSelf = 0; + +void KeyCache::addKeys( const QString& dir ) +{ + if ( !mNewKeys.contains( dir ) ) { + mNewKeys.insert( dir, listNew( dir ) ); + //kDebug() << "Added new keys for: " << dir; + } + + if ( !mCurKeys.contains( dir ) ) { + mCurKeys.insert( dir, listCurrent( dir ) ); + //kDebug() << "Added cur keys for: " << dir; + } +} + +void KeyCache::refreshKeys( const QString& dir ) +{ + mNewKeys.remove( dir ); + mCurKeys.remove( dir ); + addKeys( dir ); +} + +void KeyCache::addNewKey( const QString& dir, const QString& key ) +{ + mNewKeys[dir].insert( key ); + // kDebug() << "Added new key for : " << dir << " key: " << key; +} + +void KeyCache::addCurKey( const QString& dir, const QString& key ) +{ + mCurKeys[dir].insert( key ); + // kDebug() << "Added cur key for : " << dir << " key:" << key; +} + +void KeyCache::removeKey( const QString& dir, const QString& key ) +{ + //kDebug() << "Removed new and cur key for: " << dir << " key:" << key; + mNewKeys[dir].remove( key ); + mCurKeys[dir].remove( key ); +} + +bool KeyCache::isCurKey( const QString& dir, const QString& key ) const +{ + return mCurKeys.value( dir ).contains( key ); +} + +bool KeyCache::isNewKey( const QString& dir, const QString& key ) const +{ + return mNewKeys.value( dir ).contains( key ); +} + +QSet< QString > KeyCache::listNew( const QString& dir ) const +{ + QDir d( dir + QString::fromLatin1( "/new" ) ); + d.setSorting(QDir::NoSort); + return d.entryList( QDir::Files ).toSet(); +} + +QSet< QString > KeyCache::listCurrent( const QString& dir ) const +{ + QDir d( dir + QString::fromLatin1( "/cur" ) ); + d.setSorting(QDir::NoSort); + return d.entryList( QDir::Files ).toSet(); +} + diff --git a/kdepim-runtime/resources/maildir/libmaildir/keycache.h b/kdepim-runtime/resources/maildir/libmaildir/keycache.h new file mode 100644 index 00000000..3cce6f08 --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/keycache.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2012 Andras Mantia + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef KEYCACHE_H +#define KEYCACHE_H + +/** @brief a cache for the maildir keys (file names in cur/new folders). + * It is used to find if a file is in cur or new + */ + +#include +#include + +class KeyCache { + +public: + static KeyCache *self() + { + if ( !mSelf ) + mSelf = new KeyCache(); + return mSelf; + } + + /** Find the new and cur keys on the disk for @param dir and add them to the cache */ + void addKeys( const QString& dir ); + + /** Refresh the new and cur keys for @param dir */ + void refreshKeys( const QString& dir ); + + /** Add a "new" key for @param dir. */ + void addNewKey( const QString& dir, const QString& key ); + + /** Add a "cur" key for @param dir. */ + void addCurKey( const QString& dir, const QString& key ); + + /** Remove all keys associated with @param dir. */ + void removeKey( const QString& dir, const QString& key ); + + /** Check if the @param key is a "cur" key in @param dir */ + bool isCurKey( const QString& dir, const QString& key ) const; + + /** Check if the @param key is a "new" key in @param dir */ + bool isNewKey( const QString& dir, const QString& key ) const; + +private: + KeyCache() { + } + + QSet listNew( const QString& dir ) const; + + QSet listCurrent( const QString& dir ) const; + + QHash< QString, QSet > mNewKeys; + QHash< QString, QSet > mCurKeys; + + static KeyCache* mSelf; + +}; + + +#endif // KEYCACHE_H diff --git a/kdepim-runtime/resources/maildir/libmaildir/maildir.cpp b/kdepim-runtime/resources/maildir/libmaildir/maildir.cpp new file mode 100644 index 00000000..9ad4a66d --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/maildir.cpp @@ -0,0 +1,846 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "maildir.h" +#include "keycache.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +//Define it to get more debug output to expense of operating speed +// #define DEBUG_KEYCACHE_CONSITENCY + + +static void initRandomSeed() +{ + static bool init = false; + if ( !init ) { + unsigned int seed; + init = true; + int fd = KDE_open( "/dev/urandom", O_RDONLY ); + if ( fd < 0 || ::read( fd, &seed, sizeof( seed ) ) != sizeof( seed ) ) { + // No /dev/urandom... try something else. + srand( getpid() ); + seed = rand() + time( 0 ); + } + + if ( fd >= 0 ) + close( fd ); + + qsrand( seed ); + } +} + +using namespace KPIM; + +Q_GLOBAL_STATIC_WITH_ARGS( QRegExp, statusSeparatorRx, (":|!") ) + +class Maildir::Private +{ +public: + Private( const QString& p, bool isRoot ) + :path(p), isRoot(isRoot) + { + hostName = QHostInfo::localHostName(); + // The default implementation of QUuid::createUuid() doesn't use + // a seed that is random enough. Therefor we use our own initialization + // until this issue will be fixed in Qt 4.7. + initRandomSeed(); + + //Cache object is created the first time this runs. + //It will live throughout the lifetime of the application + KeyCache::self()->addKeys( path ); + } + + Private( const Private& rhs ) + { + path = rhs.path; + isRoot = rhs.isRoot; + hostName = rhs.hostName; + } + + bool operator==( const Private& rhs ) const + { + return path == rhs.path; + } + bool accessIsPossible( bool createMissingFolders = true ); + bool canAccess( const QString& path ) const; + + QStringList subPaths() const + { + QStringList paths; + paths << path + QString::fromLatin1( "/cur" ); + paths << path + QString::fromLatin1( "/new" ); + paths << path + QString::fromLatin1( "/tmp" ); + return paths; + } + + QStringList listNew() const + { + QDir d( path + QString::fromLatin1( "/new" ) ); + d.setSorting(QDir::NoSort); + return d.entryList( QDir::Files ); + } + + QStringList listCurrent() const + { + QDir d( path + QString::fromLatin1( "/cur" ) ); + d.setSorting(QDir::NoSort); + return d.entryList( QDir::Files ); + } + + QString findRealKey( const QString& key ) const + { + KeyCache* keyCache = KeyCache::self(); + if ( keyCache->isNewKey( path, key ) ) { +#ifdef DEBUG_KEYCACHE_CONSITENCY + if ( !QFile::exists( path + QString::fromLatin1( "/new/" ) + key ) ) { + kDebug() << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1( "/new/" ) + key; + } +#endif + return path + QString::fromLatin1( "/new/" ) + key; + } + if ( keyCache->isCurKey( path, key ) ) { +#ifdef DEBUG_KEYCACHE_CONSITENCY + if ( !QFile::exists( path + QString::fromLatin1( "/cur/" ) + key ) ) { + kDebug() << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1( "/cur/" ) + key; + } +#endif + return path + QString::fromLatin1( "/cur/" ) + key; + } + QString realKey = path + QString::fromLatin1( "/new/" ) + key; + + QFile f( realKey ); + if ( f.exists() ) { + keyCache->addNewKey( path, key ); + } else { //not in "new", search in "cur" + realKey = path + QString::fromLatin1( "/cur/" ) + key; + QFile f2( realKey ); + if ( f2.exists() ) { + keyCache->addCurKey( path, key ); + } else { + realKey.clear(); //not in "cur" either + } + } + + return realKey; + } + + static QString subDirNameForFolderName( const QString &folderName ) + { + return QString::fromLatin1( ".%1.directory" ).arg( folderName ); + } + + QString subDirPath() const + { + QDir dir( path ); + return subDirNameForFolderName( dir.dirName() ); + } + + bool moveAndRename( QDir &dest, const QString &newName ) + { + if ( !dest.exists() ) { + kDebug() << "Destination does not exist"; + return false; + } + if ( dest.exists( newName ) || dest.exists( subDirNameForFolderName( newName ) ) ) { + kDebug() << "New name already in use"; + return false; + } + + if ( !dest.rename( path, newName ) ) { + kDebug() << "Failed to rename maildir"; + return false; + } + const QDir subDirs( Maildir::subDirPathForFolderPath( path ) ); + if ( subDirs.exists() && !dest.rename( subDirs.path(), subDirNameForFolderName( newName ) ) ) { + kDebug() << "Failed to rename subfolders"; + return false; + } + + path = dest.path() + QDir::separator() + newName; + return true; + } + + QString path; + bool isRoot; + QString hostName; + QString lastError; +}; + +Maildir::Maildir( const QString& path, bool isRoot ) +:d( new Private(path, isRoot) ) +{ +} + +void Maildir::swap( const Maildir &rhs ) +{ + Private *p = d; + d = new Private( *rhs.d ); + delete p; +} + + +Maildir::Maildir(const Maildir & rhs) + :d( new Private( *rhs.d ) ) + +{ +} + +Maildir& Maildir::operator= (const Maildir & rhs) +{ + // copy and swap, exception safe, and handles assignment to self + Maildir temp( rhs ); + swap( temp ); + return *this; +} + + +bool Maildir::operator== (const Maildir & rhs) const +{ + return *d == *rhs.d; +} + + +Maildir::~Maildir() +{ + delete d; +} + +bool Maildir::Private::canAccess( const QString& path ) const +{ + //return access( QFile::encodeName( path ), R_OK | W_OK | X_OK ) != 0; + // FIXME X_OK? + QFileInfo d( path ); + return d.isReadable() && d.isWritable(); +} + +bool Maildir::Private::accessIsPossible( bool createMissingFolders ) +{ + QStringList paths = subPaths(); + + paths.prepend( path ); + + Q_FOREACH ( const QString &p, paths ) { + if ( !QFile::exists( p ) ) { + if ( !createMissingFolders ) { + lastError = i18n( "Error opening %1; this folder is missing.", p ); + return false; + } + QDir().mkpath( p ); + if ( !QFile::exists( p ) ) { + lastError = i18n( "Error opening %1; this folder is missing.", p ); + return false; + } + } + if ( !canAccess( p ) ) { + lastError = i18n( "Error opening %1; either this is not a valid " + "maildir folder, or you do not have sufficient access permissions." ,p ); + return false; + } + } + return true; +} + +bool Maildir::isValid( bool createMissingFolders ) const +{ + if ( path().isEmpty() ) { + return false; + } + if ( !d->isRoot ) { + if ( d->accessIsPossible( createMissingFolders ) ) { + return true; + } + } else { + Q_FOREACH ( const QString &sf, subFolderList() ) { + const Maildir subMd = Maildir( path() + QLatin1Char( '/' ) + sf ); + if ( !subMd.isValid() ) { + d->lastError = subMd.lastError(); + return false; + } + } + return true; + } + return false; +} + +bool Maildir::isRoot() const +{ + return d->isRoot; +} + +bool Maildir::create() +{ + // FIXME: in a failure case, this will leave partially created dirs around + // we should clean them up, but only if they didn't previously existed... + Q_FOREACH ( const QString &p, d->subPaths() ) { + QDir dir( p ); + if ( !dir.exists( p ) ) { + if ( !dir.mkpath( p ) ) + return false; + } + } + return true; +} + +QString Maildir::path() const +{ + return d->path; +} + +QString Maildir::name() const +{ + const QDir dir( d->path ); + return dir.dirName(); +} + +QString Maildir::addSubFolder( const QString& path ) +{ + if ( !isValid() ) + return QString(); + + // make the subdir dir + QDir dir( d->path ); + if ( !d->isRoot ) { + dir.cdUp(); + if ( !dir.exists( d->subDirPath() ) ) + dir.mkdir( d->subDirPath() ); + dir.cd( d->subDirPath() ); + } + + const QString fullPath = dir.path() + QLatin1Char( '/' ) + path; + Maildir subdir( fullPath ); + if ( subdir.create() ) + return fullPath; + return QString(); +} + +bool Maildir::removeSubFolder( const QString& folderName ) +{ + if ( !isValid() ) return false; + QDir dir( d->path ); + if ( !d->isRoot ) { + dir.cdUp(); + if ( !dir.exists( d->subDirPath() ) ) return false; + dir.cd( d->subDirPath() ); + } + if ( !dir.exists( folderName ) ) return false; + + // remove it recursively + bool result = KPIMUtils::removeDirAndContentsRecursively( dir.absolutePath() + QLatin1Char( '/' ) + folderName ); + QString subfolderName = subDirNameForFolderName(folderName); + if ( dir.exists( subfolderName ) ) + result &= KPIMUtils::removeDirAndContentsRecursively( dir.absolutePath() + QLatin1Char( '/' ) + subfolderName ); + return result; +} + +Maildir Maildir::subFolder( const QString& subFolder ) const +{ + // make the subdir dir + QDir dir( d->path ); + if ( !d->isRoot ) { + dir.cdUp(); + if ( dir.exists( d->subDirPath() ) ) { + dir.cd( d->subDirPath() ); + } + } + return Maildir( dir.path() + QLatin1Char( '/' ) + subFolder ); +} + +Maildir Maildir::parent() const +{ + if ( !isValid() || d->isRoot ) + return Maildir(); + QDir dir( d->path ); + dir.cdUp(); + if ( !dir.dirName().startsWith( QLatin1Char('.') ) || !dir.dirName().endsWith( QLatin1String( ".directory" ) ) ) + return Maildir(); + const QString parentName = dir.dirName().mid( 1, dir.dirName().size() - 11 ); + dir.cdUp(); + dir.cd( parentName ); + return Maildir ( dir.path() ); +} + +QStringList Maildir::entryList() const +{ + QStringList result; + if ( isValid() ) { + result += d->listNew(); + result += d->listCurrent(); + } + // kDebug() <<"Maildir::entryList()" << result; + return result; +} + +QStringList Maildir::listCurrent() const +{ + QStringList result; + if ( isValid() ) { + result += d->listCurrent(); + } + return result; +} + +QString Maildir::findRealKey(const QString& key) const +{ + return d->findRealKey( key ); +} + + +QStringList Maildir::listNew() const +{ + QStringList result; + if ( isValid() ) { + result += d->listNew(); + } + return result; +} + +QString Maildir::pathToNew() const +{ + if ( isValid() ) { + return d->path + QString::fromLatin1( "/new" ); + } + return QString(); +} + +QString Maildir::pathToCurrent() const +{ + if ( isValid() ) { + return d->path + QString::fromLatin1( "/cur" ); + } + return QString(); +} + +QString Maildir::subDirPath() const +{ + QDir dir( d->path ); + dir.cdUp(); + return dir.path() + QDir::separator() + d->subDirPath(); +} + + + +QStringList Maildir::subFolderList() const +{ + QDir dir( d->path ); + + // the root maildir has its subfolders directly beneath it + if ( !d->isRoot ) { + dir.cdUp(); + if ( !dir.exists( d->subDirPath() ) ) + return QStringList(); + dir.cd( d->subDirPath() ); + } + dir.setFilter( QDir::Dirs | QDir::NoDotAndDotDot ); + QStringList entries = dir.entryList(); + entries.removeAll( QLatin1String( "cur" ) ); + entries.removeAll( QLatin1String( "new" ) ); + entries.removeAll( QLatin1String( "tmp" ) ); + return entries; +} + +QByteArray Maildir::readEntry( const QString& key ) const +{ + QByteArray result; + + QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + // FIXME error handling? + qWarning() << "Maildir::readEntry unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." ,key ); + return result; + } + + QFile f( realKey ); + if ( !f.open( QIODevice::ReadOnly ) ) { + d->lastError = i18n( "Cannot open mail file %1.", realKey ); + return result; + } + + // FIXME be safer than this + result = f.readAll(); + + return result; +} +qint64 Maildir::size( const QString& key ) const +{ + QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + // FIXME error handling? + qWarning() << "Maildir::size unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." , key ); + return -1; + } + + QFileInfo info( realKey ); + if ( !info.exists() ) { + d->lastError = i18n( "Cannot open mail file %1." ,realKey ); + return -1; + } + + return info.size(); +} + +QDateTime Maildir::lastModified(const QString& key) const +{ + const QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + qWarning() << "Maildir::lastModified unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." , key ); + return QDateTime(); + } + + const QFileInfo info( realKey ); + if ( !info.exists() ) + return QDateTime(); + + return info.lastModified(); +} + +QByteArray Maildir::readEntryHeadersFromFile( const QString& file ) const +{ + QByteArray result; + + QFile f( file ); + if ( !f.open( QIODevice::ReadOnly ) ) { + // FIXME error handling? + qWarning() << "Maildir::readEntryHeaders unable to find: " << file; + d->lastError = i18n( "Cannot locate mail file %1." , file ); + return result; + } + f.map( 0, qMin( (qint64)8000, f.size() ) ); + forever { + QByteArray line = f.readLine(); + if ( line.isEmpty() || line.startsWith('\n') ) + break; + result.append( line ); + } + return result; +} + +QByteArray Maildir::readEntryHeaders( const QString& key ) const +{ + const QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + qWarning() << "Maildir::readEntryHeaders unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." , key ); + return QByteArray(); + } + + return readEntryHeadersFromFile( realKey ); +} + + +static QString createUniqueFileName() +{ + qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000; + int r = qrand() % 1000; + QString identifier = QLatin1String( "R" ) + QString::number( r ); + + QString fileName = QString::number( time ) + QLatin1Char( '.' ) + identifier + QLatin1Char( '.' ); + + return fileName; +} + +bool Maildir::writeEntry( const QString& key, const QByteArray& data ) +{ + QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + // FIXME error handling? + qWarning() << "Maildir::writeEntry unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." ,key ); + return false; + } + QFile f( realKey ); + bool result = f.open( QIODevice::WriteOnly ); + result = result & ( f.write( data ) != -1 ); + f.close(); + if ( !result) { + d->lastError = i18n( "Cannot write to mail file %1." ,realKey ); + return false; + } + return true; +} + +QString Maildir::addEntry( const QByteArray& data ) +{ + QString uniqueKey; + QString key; + QString finalKey; + QString curKey; + + // QUuid doesn't return globally unique identifiers, therefor we query until we + // get one that doesn't exists yet + do { + uniqueKey = createUniqueFileName() + d->hostName; + key = d->path + QLatin1String( "/tmp/" ) + uniqueKey; + finalKey = d->path + QLatin1String( "/new/" ) + uniqueKey; + curKey = d->path + QLatin1String( "/cur/" ) + uniqueKey; + } while ( QFile::exists( key ) || QFile::exists( finalKey ) || QFile::exists( curKey ) ); + + QFile f( key ); + bool result = f.open( QIODevice::WriteOnly ); + result = result & ( f.write( data ) != -1 ); + f.close(); + if ( !result) { + d->lastError = i18n( "Cannot write to mail file %1." , key ); + return QString(); + } + /* + * FIXME: + * + * The whole point of the locking free maildir idea is that the moves between + * the internal directories are atomic. Afaik QFile::rename does not guarantee + * that, so this will need to be done properly. - ta + * + * For reference: http://trolltech.com/developer/task-tracker/index_html?method=entry&id=211215 + */ + if ( !f.rename( finalKey ) ) { + qWarning() << "Maildir: Failed to add entry: " << finalKey << "! Error: " << f.errorString(); + d->lastError = i18n( "Failed to create mail file %1. The error was: %2" , finalKey, f.errorString() ); + return QString(); + } + KeyCache *keyCache = KeyCache::self(); + keyCache->removeKey( d->path, key ); //remove all keys, be it "cur" or "new" first + keyCache->addNewKey( d->path, key ); //and add a key for "new", as the mail was moved there + return uniqueKey; +} + +bool Maildir::removeEntry( const QString& key ) +{ + QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + qWarning() << "Maildir::removeEntry unable to find: " << key; + return false; + } + KeyCache *keyCache = KeyCache::self(); + keyCache->removeKey( d->path, key ); + return QFile::remove( realKey ); +} + +QString Maildir::changeEntryFlags(const QString& key, const Akonadi::Item::Flags& flags) +{ + QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + qWarning() << "Maildir::changeEntryFlags unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." , key ); + return QString(); + } + + const QRegExp rx = *( statusSeparatorRx() ); + QString finalKey = key.left( key.indexOf( rx ) ); + + QStringList mailDirFlags; + Q_FOREACH ( const Akonadi::Item::Flag &flag, flags ) { + if ( flag == Akonadi::MessageFlags::Forwarded ) + mailDirFlags << QLatin1String( "P" ); + if ( flag == Akonadi::MessageFlags::Replied ) + mailDirFlags << QLatin1String( "R" ); + if ( flag == Akonadi::MessageFlags::Seen ) + mailDirFlags << QLatin1String( "S" ); + if ( flag == Akonadi::MessageFlags::Deleted ) + mailDirFlags << QLatin1String( "T" ); + if ( flag == Akonadi::MessageFlags::Flagged ) + mailDirFlags << QLatin1String( "F" ); + } + mailDirFlags.sort(); + if ( !mailDirFlags.isEmpty() ) { +#ifdef Q_OS_WIN + finalKey.append( QLatin1String( "!2," ) + mailDirFlags.join( QString() ) ); +#else + finalKey.append( QLatin1String( ":2," ) + mailDirFlags.join( QString() ) ); +#endif + } + + QString newUniqueKey = finalKey; //key without path + finalKey.prepend( d->path + QString::fromLatin1( "/cur/" ) ); + + if ( realKey == finalKey ) { + // Somehow it already is named this way (e.g. migration bug -> wrong status in akonadi) + return newUniqueKey; + } + + QFile f( realKey ); + if ( QFile::exists( finalKey ) ) { + QFile destFile( finalKey ); + QByteArray destContent; + if ( destFile.open( QIODevice::ReadOnly ) ) { + destContent = destFile.readAll(); + destFile.close(); + } + QByteArray sourceContent; + if ( f.open( QIODevice::ReadOnly ) ) { + sourceContent = f.readAll(); + f.close(); + } + + if ( destContent != sourceContent ) { + QString newFinalKey = QLatin1String("1-") + newUniqueKey; + int i = 1; + while ( QFile::exists( d->path + QString::fromLatin1( "/cur/" ) + newFinalKey ) ) { + i++; + newFinalKey = QString::number( i ) + QLatin1Char('-') + newUniqueKey; + } + finalKey = d->path + QString::fromLatin1( "/cur/" ) + newFinalKey; + } else { + QFile::remove( finalKey ); //they are the same + } + } + + if ( !f.rename( finalKey ) ) { + qWarning() << "Maildir: Failed to rename entry: " << f.fileName() << " to " << finalKey << "! Error: " << f.errorString(); + d->lastError = i18n( "Failed to update the file name %1 to %2 on the disk. The error was: %3." , f.fileName(), finalKey, f.errorString() ); + return QString(); + } + + KeyCache *keyCache = KeyCache::self(); + keyCache->removeKey( d->path, key ); + keyCache->addCurKey( d->path, newUniqueKey ); + + return newUniqueKey; +} + +Akonadi::Item::Flags Maildir::readEntryFlags(const QString& key) const +{ + Akonadi::Item::Flags flags; + + const QRegExp rx = *( statusSeparatorRx() ); + const int index = key.indexOf( rx ); + if ( index != -1 ) { + const QString mailDirFlags = key.mid( index + 3 ); // after "(:|!)2," + const int flagSize(mailDirFlags.size()); + for ( int i = 0; i < flagSize; ++i ) { + if ( mailDirFlags[i] == QLatin1Char( 'P' ) ) + flags << Akonadi::MessageFlags::Forwarded; + else if ( mailDirFlags[i] == QLatin1Char( 'R' ) ) + flags << Akonadi::MessageFlags::Replied; + else if ( mailDirFlags[i] == QLatin1Char( 'S' ) ) + flags << Akonadi::MessageFlags::Seen; + else if ( mailDirFlags[i] == QLatin1Char( 'F' ) ) + flags << Akonadi::MessageFlags::Flagged; + } + } + + return flags; +} + + +bool Maildir::moveTo( const Maildir &newParent ) +{ + if ( d->isRoot ) + return false; // not supported + + QDir newDir( newParent.path() ); + if ( !newParent.d->isRoot ) { + newDir.cdUp(); + if ( !newDir.exists( newParent.d->subDirPath() ) ) + newDir.mkdir( newParent.d->subDirPath() ); + newDir.cd( newParent.d->subDirPath() ); + } + + QDir currentDir( d->path ); + currentDir.cdUp(); + + if ( newDir == currentDir ) + return true; + + return d->moveAndRename( newDir, name() ); +} + +bool Maildir::rename( const QString &newName ) +{ + if ( name() == newName ) + return true; + if ( d->isRoot ) + return false; // not (yet) supported + + QDir dir( d->path ); + dir.cdUp(); + + return d->moveAndRename( dir, newName ); +} + +QString Maildir::moveEntryTo( const QString &key, const Maildir &destination ) +{ + const QString realKey( d->findRealKey( key ) ); + if ( realKey.isEmpty() ) { + kWarning() << "Unable to find: " << key; + d->lastError = i18n( "Cannot locate mail file %1." , key ); + return QString(); + } + QFile f( realKey ); + // ### is this safe regarding the maildir locking scheme? + const QString targetKey = destination.path() + QDir::separator() + QLatin1String( "new" ) + QDir::separator() + key; + if ( !f.rename( targetKey ) ) { + kDebug() << "Failed to rename" << realKey << "to" << targetKey << "! Error: " << f.errorString();; + d->lastError = f.errorString(); + return QString(); + } + + KeyCache* keyCache = KeyCache::self(); + + keyCache->addNewKey( destination.path(), key ); + keyCache->removeKey( d->path, key ); + + return key; +} + +QString Maildir::subDirPathForFolderPath( const QString &folderPath ) +{ + QDir dir( folderPath ); + const QString dirName = dir.dirName(); + dir.cdUp(); + return QFileInfo( dir, Private::subDirNameForFolderName( dirName ) ).filePath(); +} + +QString Maildir::subDirNameForFolderName( const QString &folderName ) +{ + return Private::subDirNameForFolderName( folderName ); +} + +void Maildir::removeCachedKeys(const QStringList & keys) +{ + KeyCache *keyCache = KeyCache::self(); + Q_FOREACH ( const QString &key, keys ) { + keyCache->removeKey( d->path, key ); + } +} + +void Maildir::refreshKeyCache() +{ + KeyCache::self()->refreshKeys( d->path ); +} + +QString Maildir::lastError() const +{ + return d->lastError; +} diff --git a/kdepim-runtime/resources/maildir/libmaildir/maildir.h b/kdepim-runtime/resources/maildir/libmaildir/maildir.h new file mode 100644 index 00000000..5c4c8703 --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/maildir.h @@ -0,0 +1,259 @@ +/* + Copyright (c) 2007 Till Adam + + 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 MAILDIR_H +#define MAILDIR_H + + +#include "maildir_export.h" + +#include +#include +#include + +class QDateTime; + +namespace KPIM { + +class MAILDIR_EXPORT Maildir +{ +public: + /** + Create a new Maildir object. + @param path The path to the maildir, if @p isRoot is @c false, that's the path + to the folder containing the cur/new/tmp folders, if @p isRoot is @c true this + is the path to a folder containing a number of maildirs. + @param isRoot Indicate whether this is a maildir containing mails and various + sub-folders or a container only containing maildirs. + */ + explicit Maildir( const QString& path = QString(), bool isRoot = false ); + /* Copy constructor */ + Maildir(const Maildir & rhs); + /* Copy operator */ + Maildir& operator=(const Maildir & rhs); + /** Equality comparison */ + bool operator==(const Maildir & rhs) const; + /* Destructor */ + ~Maildir(); + + /** Returns whether the maildir has all the necessary subdirectories, + * that they are readable, etc. + * @param createMissingFolders if true (the default), the cur/new/tmp folders are created if they are missing + */ + bool isValid( bool createMissingFolders = true ) const; + + /** + * Returns whether this is a normal maildir or a container containing maildirs. + */ + bool isRoot() const; + + /** + * Make a valid maildir at the path of this Maildir object. This involves + * creating the necessary subdirs, etc. Note that an empty Maildir is + * not valid, unless it is given valid path, or until create( ) is + * called on it. + */ + bool create(); + + /** + * Returns the path of this maildir. + */ + QString path() const; + + /** + * Returns the name of this maildir. + */ + QString name() const; + + /** + * Returns the list of items (mails) in the maildir. These are keys, which + * map to filenames, internally, but that's an implementation detail, which + * should not be relied on. + */ + QStringList entryList() const; + + /** Returns the list of items (mails) in the maildirs "new" folder. These are keys, which + * map to filenames, internally, but that's an implementation detail, which + * should not be relied on. + */ + QStringList listNew() const; + + /** Returns the list of items (mails) in the maildirs "cur" folder. These are keys, which + * map to filenames, internally, but that's an implementation detail, which + * should not be relied on. + */ + QStringList listCurrent() const; + + /** Return the path to the "new" directory */ + QString pathToNew() const; + + /** Return the path to the "cur" directory */ + QString pathToCurrent() const; + + /** + * Returns the full path to the subdir (the NAME.directory folder ). + **/ + QString subDirPath() const; + + /** + * Return the full path to the file identified by key (it can be either in the "new" or "cur" folder + **/ + QString findRealKey( const QString& key ) const; + + /** + * Returns the list of subfolders, as names (relative paths). Use the + * subFolder method to get Maildir objects representing them. + */ + QStringList subFolderList() const; + + /** + * Adds subfolder with the given @p folderName. + * @return an empty string on failure or the full path of the new subfolder + * on success + */ + QString addSubFolder( const QString& folderName ); + + /** + * Removes subfolder with the given @p folderName. Returns success or failure. + */ + bool removeSubFolder( const QString& folderName ); + + /** + * Returns a Maildir object for the given @p folderName. If such a folder + * exists, the Maildir object will be valid, otherwise you can call create() + * on it, to make a subfolder with that name. + */ + Maildir subFolder( const QString& folderName ) const; + + /** + * Returns the parent Maildir object for this Maildir, if there is one (ie. this is not the root). + */ + Maildir parent() const; + + /** + * Returns the size of the file in the maildir with the given @p key or \c -1 if key is not valid. + * @since 4.2 + */ + qint64 size( const QString& key ) const; + + /** + * Returns the modification time of the file in the maildir with the given @p key. + * @since 4.7 + */ + QDateTime lastModified( const QString &key ) const; + + /** + * Return the contents of the file in the maildir with the given @p key. + */ + QByteArray readEntry( const QString& key ) const; + + /** + * Return the flags encoded in the maildir file name for an entry + **/ + Akonadi::Item::Flags readEntryFlags( const QString& key ) const; + + /** + * Return the contents of the headers section of the file the maildir with the given @p file, that + * is a full path to the file. You can get it by using findRealKey(key) . + */ + QByteArray readEntryHeadersFromFile( const QString& file ) const; + + /** + * Return the contents of the headers section of the file the maildir with the given @p key. + */ + QByteArray readEntryHeaders( const QString& key ) const; + + /** + * Write the given @p data to a file in the maildir with the given @p key. + * Returns true in case of success, false in case of any error. + */ + bool writeEntry( const QString& key, const QByteArray& data ); + + /** + * Adds the given @p data to the maildir. Returns the key of the entry. + */ + QString addEntry( const QByteArray& data ); + + /** + * Removes the entry with the given @p key. Returns success or failure. + */ + bool removeEntry( const QString& key ); + + /** + * Change the flags for an entry specified by @p key. Returns the new key of the entry (the key might change because + * flags are stored in the unique filename). + */ + QString changeEntryFlags( const QString& key, const Akonadi::Item::Flags& flags ); + + /** + * Moves this maildir into @p destination. + */ + bool moveTo( const Maildir &destination ); + + /** + * Renames this maildir to @p newName. + */ + bool rename( const QString &newName ); + + /** + * Moves the file with the given @p key into the Maildir @p destination. + * @returns The new file name inside @p destination. + */ + QString moveEntryTo( const QString& key, const KPIM::Maildir& destination ); + + /** + * Creates the maildir tree structure specific directory path that the + * given @p folderPath folder would have for its sub folders + * @param folderPath a maildir folder path + * @return the relative subDirPath for the given @p folderPath + * + * @see subDirNameForFolderName() + */ + static QString subDirPathForFolderPath( const QString &folderPath ); + + /** + * Creates the maildir tree structure specific directory name that the + * given @p folderName folder would have for its sub folders + * @param folderName a maildir folder name + * @return the relative subDirName for the given @p folderMame + * + * @see subDirPathForFolderPath() + */ + static QString subDirNameForFolderName( const QString &folderName ); + + /** Removes the listed keys from the key cache */ + void removeCachedKeys(const QStringList & keys); + + + /** Reloads the keys associated with the maildir in the key cache*/ + void refreshKeyCache(); + + /** Return the last error message string. The error might not come from the last performed operation, + if that was sucessful. The caller should always check the return value of the methods before + querying the last error string. */ + QString lastError() const; + +private: + void swap( const Maildir& ); + class Private; + Private *d; +}; + +} +#endif // __MAILDIR_H__ diff --git a/kdepim-runtime/resources/maildir/libmaildir/maildir_export.h b/kdepim-runtime/resources/maildir/libmaildir/maildir_export.h new file mode 100644 index 00000000..9246b53f --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/maildir_export.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure + + 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 MAILDIR_EXPORT_H +#define MAILDIR_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef MAILDIR_EXPORT +# if defined(KDEPIM_STATIC_LIBS) + /* No export/import for static libraries */ +# define MAILDIR_EXPORT +# elif defined(MAKE_MAILDIR_LIB) + /* We are building this library */ +# define MAILDIR_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define MAILDIR_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef MAILDIR_EXPORT_DEPRECATED +# define MAILDIR_EXPORT_DEPRECATED KDE_DEPRECATED MAILDIR_EXPORT +# endif + +#endif diff --git a/kdepim-runtime/resources/maildir/libmaildir/tests/CMakeLists.txt b/kdepim-runtime/resources/maildir/libmaildir/tests/CMakeLists.txt new file mode 100644 index 00000000..c6aae23c --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ) + +########### next target ############### + +set(testmaildir_SRCS testmaildir.cpp ) + + +kde4_add_unit_test(testmaildir TESTNAME maildir-testmaildir ${testmaildir_SRCS}) + +target_link_libraries(testmaildir ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} maildir) + diff --git a/kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.cpp b/kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.cpp new file mode 100644 index 00000000..aabe95c8 --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.cpp @@ -0,0 +1,435 @@ +/* + This file is part of the kpimutils library. + + Copyright (C) 2007 Till Adam + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + 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 "testmaildir.h" + +#include + +#include +#include + +#include +#include +#include +#include + +QTEST_KDEMAIN( MaildirTest, NoGUI ) + +#include "../maildir.h" +using namespace KPIM; + +static const char * testDir = "libmaildir-unit-test"; +static const char * testString = "From: theDukeOfMonmouth@uk.gov\n \nTo: theDukeOfBuccleuch@uk.gov\n\ntest\n"; +static const char * testStringHeaders = "From: theDukeOfMonmouth@uk.gov\n \nTo: theDukeOfBuccleuch@uk.gov\n"; + +void MaildirTest::init() +{ + m_temp = new KTempDir( KStandardDirs::locateLocal( "tmp", QLatin1String( testDir ) ) ); + + QDir temp( m_temp->name() ); + QVERIFY( temp.exists() ); + + temp.mkdir( QLatin1String( "new" ) ); + QVERIFY( temp.exists( QLatin1String( "new" ) ) ); + temp.mkdir( QLatin1String( "cur" ) ); + QVERIFY( temp.exists( QLatin1String( "cur" ) ) ); + temp.mkdir( QLatin1String( "tmp" ) ); + QVERIFY( temp.exists( QLatin1String( "tmp" ) ) ); +} + +void MaildirTest::cleanup() +{ + m_temp->unlink(); + QDir d( m_temp->name() ); + const QString subFolderPath( QString::fromLatin1( ".%1.directory" ).arg( d.dirName() ) ); + KTempDir::removeDir(subFolderPath); + + delete m_temp; + m_temp = 0; +} + +void MaildirTest::fillDirectory(const QString& name, int limit ) +{ + QFile file; + QDir::setCurrent( m_temp->name() + QLatin1Char( '/' ) + name ); + for ( int i=0; iname() ); + const QString subFolderPath( QString::fromLatin1( ".%1.directory" ).arg( d.dirName() ) ); + d.cdUp(); + d.mkdir( subFolderPath ); + d.cd( subFolderPath ); + d.mkdir( QLatin1String( "foo" ) ); + d.mkdir( QLatin1String( "barbar" ) ); + d.mkdir( QLatin1String( "bazbaz" ) ); +} + +void MaildirTest::fillNewDirectory() +{ + fillDirectory( QLatin1String( "new" ), 140 ); +} + +void MaildirTest::fillCurrentDirectory() +{ + fillDirectory( QLatin1String( "cur" ), 20 ); +} + + +void MaildirTest::testMaildirInstantiation() +{ + Maildir d( QLatin1String( "/foo/bar/Mail" ) ); + Maildir d2( d ); + Maildir d3; + d3 = d; + QVERIFY( d == d2 ); + QVERIFY( d3 == d2 ); + QVERIFY( d == d3 ); + QCOMPARE( d.path(), QString( QLatin1String( "/foo/bar/Mail" ) ) ); + QCOMPARE( d.name(), QString( QLatin1String( "Mail" ) ) ); + + QVERIFY( !d.isValid() ); + + Maildir good( m_temp->name() ); + QVERIFY( good.isValid() ); + + QDir temp( m_temp->name() ); + temp.rmdir( QLatin1String( "new" ) ); + QVERIFY( !good.isValid( false ) ); + QVERIFY( !good.lastError().isEmpty() ); + + Maildir root1( QLatin1String( "/foo/bar/Mail" ), true ); + QVERIFY( root1.isRoot() ); + + Maildir root1Copy = root1; + QCOMPARE( root1Copy.path(), root1.path() ); + QCOMPARE( root1Copy.isRoot(), root1.isRoot() ); + + // FIXME test insufficient permissions? +} + +void MaildirTest::testMaildirListing() +{ + fillNewDirectory(); + + Maildir d( m_temp->name() ); + QStringList entries = d.entryList(); + + QCOMPARE( entries.count(), 140 ); + + fillCurrentDirectory(); + entries = d.entryList(); + QCOMPARE( entries.count(), 160 ); +} + +void MaildirTest::testMaildirAccess() +{ + fillCurrentDirectory(); + Maildir d( m_temp->name() ); + QStringList entries = d.entryList(); + QCOMPARE( entries.count(), 20 ); + + QByteArray data = d.readEntry( entries[0] ); + QVERIFY( !data.isEmpty() ); + QCOMPARE( data, QByteArray( testString ) ); +} + +void MaildirTest::testMaildirReadHeaders() +{ + fillCurrentDirectory(); + Maildir d( m_temp->name() ); + QStringList entries = d.entryList(); + QCOMPARE( entries.count(), 20 ); + + QByteArray data = d.readEntryHeaders( entries[0] ); + QVERIFY( !data.isEmpty() ); + QCOMPARE( data, QByteArray( testStringHeaders ) ); +} + +void MaildirTest::testMaildirWrite() +{ + fillCurrentDirectory(); + Maildir d( m_temp->name() ); + QStringList entries = d.entryList(); + QCOMPARE( entries.count(), 20 ); + + QByteArray data = d.readEntry( entries[0] ); + QByteArray data2 = "changed\n"; + QVERIFY( d.writeEntry( entries[0], data2 ) ); + QCOMPARE( data2, d.readEntry( entries[0] ) ); +} + +void MaildirTest::testMaildirAppend() +{ + Maildir d( m_temp->name() ); + QByteArray data = "newentry\n"; + QString key = d.addEntry( data ); + QVERIFY( !key.isEmpty() ); + QCOMPARE( data, d.readEntry( key ) ); +} + +void MaildirTest::testMaildirCreation() +{ + QString p( QLatin1String( "CREATETEST" ) ); + std::auto_ptr temp ( new KTempDir( KStandardDirs::locateLocal( "tmp", p ) ) ); + Maildir d( temp->name() + p ); + QVERIFY( !d.isValid( false ) ); + d.create(); + QVERIFY( d.isValid() ); +} + +void MaildirTest::testMaildirRemoveEntry() +{ + Maildir d( m_temp->name() ); + QByteArray data = "newentry\n"; + QString key = d.addEntry( data ); + QVERIFY( !key.isEmpty() ); + QCOMPARE( data, d.readEntry( key ) ); + QVERIFY( d.removeEntry( key ) ); + QVERIFY( d.readEntry( key ).isEmpty() ); +} + +void MaildirTest::testMaildirListSubfolders() +{ + fillNewDirectory(); + + Maildir d( m_temp->name() ); + QStringList entries = d.subFolderList(); + + QVERIFY( entries.isEmpty() ); + + createSubFolders(); + + entries = d.subFolderList(); + QVERIFY( !entries.isEmpty() ); + QCOMPARE( entries.count(), 3 ); +} + + +void MaildirTest::testMaildirCreateSubfolder() +{ + Maildir d( m_temp->name() ); + QStringList entries = d.subFolderList(); + QVERIFY( entries.isEmpty() ); + + d.addSubFolder( QLatin1String( "subFolderTest" ) ); + entries = d.subFolderList(); + QVERIFY( !entries.isEmpty() ); + QCOMPARE( entries.count(), 1 ); + Maildir child = d.subFolder( entries.first() ); + QVERIFY( child.isValid() ); +} + +void MaildirTest::testMaildirRemoveSubfolder() +{ + Maildir d( m_temp->name() ); + QVERIFY( d.isValid() ); + + QString folderPath = d.addSubFolder( QLatin1String( "subFolderTest" ) ); + QVERIFY( !folderPath.isEmpty() ); + QVERIFY( folderPath.endsWith( QLatin1String( ".directory/subFolderTest" ) ) ); + bool removingWorked = d.removeSubFolder( QLatin1String( "subFolderTest" ) ); + QVERIFY( removingWorked ); +} + +void MaildirTest::testMaildirRename() +{ + Maildir d( m_temp->name() ); + QVERIFY( d.isValid() ); + + QString folderPath = d.addSubFolder( QLatin1String( "rename me!" ) ); + QVERIFY( !folderPath.isEmpty() ); + + Maildir d2( folderPath ); + QVERIFY( d2.isValid() ); + QVERIFY( d2.rename( QLatin1String( "renamed" ) ) ); + QCOMPARE( d2.name(), QString( QLatin1String( "renamed" ) ) ); + + // same again, should not fail + QVERIFY( d2.rename( QLatin1String( "renamed" ) ) ); + QCOMPARE( d2.name(), QString( QLatin1String( "renamed" ) ) ); + + // already existing name + QVERIFY( !d.addSubFolder( QLatin1String( "this name is already taken" ) ).isEmpty() ); + QVERIFY( !d2.rename( QLatin1String( "this name is already taken" ) ) ); +} + +void MaildirTest::testMaildirMoveTo() +{ + Maildir d( m_temp->name() ); + QVERIFY( d.isValid() ); + + QString folderPath1 = d.addSubFolder( QLatin1String( "child1" ) ); + QVERIFY( !folderPath1.isEmpty() ); + + Maildir d2( folderPath1 ); + QVERIFY( d2.isValid() ); + + QDir d2Dir( d2.path() ); + QVERIFY( d2Dir.exists() ); + + QString folderPath11 = d2.addSubFolder( QLatin1String( "grandchild1" ) ); + + Maildir d21( folderPath11 ); + QVERIFY( d21.isValid() ); + + QDir d2SubDir( Maildir::subDirPathForFolderPath( d2.path() ) ); + QVERIFY( d2SubDir.exists() ); + + QString folderPath2 = d.addSubFolder( QLatin1String( "child2" ) ); + QVERIFY( !folderPath2.isEmpty() ); + + Maildir d3( folderPath2 ); + QVERIFY( d3.isValid() ); + + // move child1 to child2 + QVERIFY( d2.moveTo( d3 ) ); + + Maildir d31 = d3.subFolder( QLatin1String( "child1" ) ); + QVERIFY( d31.isValid() ); + + QVERIFY( !d2Dir.exists() ); + QVERIFY( !d2SubDir.exists() ); + + QDir d31Dir( d31.path() ); + QVERIFY( d31Dir.exists() ); + + QDir d31SubDir( Maildir::subDirPathForFolderPath( d31.path() ) ); + QVERIFY( d31SubDir.exists() ); + + Maildir d311 = d31.subFolder( QLatin1String( "grandchild1" ) ); + QVERIFY( d311.isValid() ); + + // try moving again + d2 = Maildir( folderPath1 ); + QVERIFY( !d2.isValid( false ) ); + QVERIFY( !d2.moveTo( d3 ) ); +} + +void MaildirTest::testMaildirFlagsReading() +{ + QFile file; + const QStringList markers = QStringList() << "P" << "R" << "S" << "F" << "FPRS"; + QDir::setCurrent( m_temp->name() + QLatin1Char( '/' ) + "cur" ); + for ( int i=0; i<6 ; i++) { + QString fileName = QLatin1String( "testmail-" ) + QString::number( i ); + if ( i < 5 ) { + fileName += + #ifdef Q_OS_WIN + "!2," + #else + ":2," + #endif + + markers[i]; + } + file.setFileName( fileName ); + file.open( QIODevice::WriteOnly ); + file.write( testString ); + file.flush(); + file.close(); + } + + Maildir d( m_temp->name() ); + QStringList entries = d.entryList(); + // Maildir::entryList() doesn't sort for performance reasons, + // do it here to make test sequence reliable. + entries.sort(); + + QCOMPARE( entries.count(), 6 ); + + Akonadi::Item::Flags flags = d.readEntryFlags( entries[0] ); + QCOMPARE( flags.count(), 1 ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Forwarded ) ); + + flags = d.readEntryFlags( entries[1] ); + QCOMPARE( flags.count(), 1 ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Replied ) ); + + flags = d.readEntryFlags( entries[2] ); + QCOMPARE( flags.count(), 1 ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Seen ) ); + + flags = d.readEntryFlags( entries[3] ); + QCOMPARE( flags.count(), 1 ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Flagged ) ); + + flags = d.readEntryFlags( entries[4] ); + QCOMPARE( flags.count(), 4 ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Forwarded ) ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Replied ) ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Seen ) ); + QVERIFY( flags.contains( Akonadi::MessageFlags::Flagged ) ); + + flags = d.readEntryFlags( entries[5] ); + QVERIFY( flags.isEmpty() ); +} + +void MaildirTest::testMaildirFlagsWriting_data() +{ + QTest::addColumn( "origDir" ); + QTest::addColumn( "origFileName" ); + QTest::newRow( "cur/" ) << "cur" << "testmail"; + QTest::newRow( "cur/S" ) << "cur" << "testmail:2,S"; // wrongly marked as "seen" on disk (#289428) + QTest::newRow( "new/" ) << "new" << "testmail"; + QTest::newRow( "new/S" ) << "new" << "testmail:2,S"; +} + +void MaildirTest::testMaildirFlagsWriting() +{ + QFETCH( QString, origDir ); + QFETCH( QString, origFileName ); + + // create an initialy new mail + QFile file; + QDir::setCurrent( m_temp->name() ); + file.setFileName( origDir + '/' + origFileName ); + file.open( QIODevice::WriteOnly ); + file.write( testString ); + file.flush(); + file.close(); + + // add a single flag + Maildir d( m_temp->name() ); + const QStringList entries = d.entryList(); + QCOMPARE( entries.size(), 1 ); + QVERIFY( QFile::exists( origDir + '/' + entries[0] ) ); + const QString newKey = d.changeEntryFlags( entries[0], Akonadi::Item::Flags() << Akonadi::MessageFlags::Seen ); + // make sure the new key exists + QCOMPARE( newKey, d.entryList()[0] ); + QVERIFY( QFile::exists( "cur/" + newKey ) ); + // and it's the right file + QCOMPARE( d.readEntry( newKey ), QByteArray( testString ) ); + // now check the file name + QVERIFY( newKey.endsWith( QLatin1String( "2,S" ) ) ); + // and more flags + const QString newKey2 = d.changeEntryFlags( newKey, Akonadi::Item::Flags() << Akonadi::MessageFlags::Seen << Akonadi::MessageFlags::Replied ); + // check the file name, and the sorting of markers + QVERIFY( newKey2.endsWith( QLatin1String( "2,RS" ) ) ); + QVERIFY( QFile::exists( "cur/" + newKey2 ) ); +} diff --git a/kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.h b/kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.h new file mode 100644 index 00000000..ca10afa0 --- /dev/null +++ b/kdepim-runtime/resources/maildir/libmaildir/tests/testmaildir.h @@ -0,0 +1,59 @@ +/* + This file is part of the kpimutils library. + + Copyright (c) 2007 Till Adam + + 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 MAILDIRTEST_H +#define MAILDIRTEST_H + +#include + +class KTempDir; + +class MaildirTest : public QObject +{ + Q_OBJECT + private Q_SLOTS: + void init(); + void testMaildirInstantiation(); + void testMaildirCreation(); + void testMaildirListing(); + void testMaildirAccess(); + void testMaildirReadHeaders(); + void testMaildirWrite(); + void testMaildirAppend(); + void testMaildirRemoveEntry(); + void testMaildirListSubfolders(); + void testMaildirCreateSubfolder(); + void testMaildirRemoveSubfolder(); + void testMaildirRename(); + void testMaildirMoveTo(); + void testMaildirFlagsReading(); + void testMaildirFlagsWriting_data(); + void testMaildirFlagsWriting(); + void cleanup(); +private: + void fillDirectory(const QString &name, int limit ); + void fillNewDirectory(); + void fillCurrentDirectory(); + void createSubFolders(); + KTempDir *m_temp; +}; + +#endif diff --git a/kdepim-runtime/resources/maildir/maildirresource.cpp b/kdepim-runtime/resources/maildir/maildirresource.cpp new file mode 100644 index 00000000..390274dc --- /dev/null +++ b/kdepim-runtime/resources/maildir/maildirresource.cpp @@ -0,0 +1,881 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "maildirresource.h" +#include "settings.h" +#include "maildirsettingsadaptor.h" +#include "configdialog.h" +#include "retrieveitemsjob.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "libmaildir/maildir.h" +#include + +using namespace Akonadi; +using KPIM::Maildir; +using namespace Akonadi_Maildir_Resource; + +#define CLEANER_TIMEOUT 2*6000 + +Maildir MaildirResource::maildirForCollection( const Collection& col ) +{ + const QString path = maildirPathForCollection( col ); + if ( mMaildirsForCollection.contains( path ) ) { + return mMaildirsForCollection.value( path ); + } + + if ( col.remoteId().isEmpty() ) { + kWarning() << "Got incomplete ancestor chain:" << col; + return Maildir(); + } + + if ( col.parentCollection() == Collection::root() ) { + kWarning( col.remoteId() != mSettings->path() ) << "RID mismatch, is " << col.remoteId() << " expected " << mSettings->path(); + Maildir maildir( col.remoteId(), mSettings->topLevelIsContainer() ); + mMaildirsForCollection.insert( path, maildir ); + return maildir; + } + Maildir parentMd = maildirForCollection( col.parentCollection() ); + Maildir maildir = parentMd.subFolder( col.remoteId() ); + mMaildirsForCollection.insert( path, maildir ); + return maildir; +} + +Collection MaildirResource::collectionForMaildir(const Maildir& md) const +{ + if ( !md.isValid() ) + return Collection(); + + Collection col; + if ( md.path() == mSettings->path() ) { + col.setRemoteId( md.path() ); + col.setParentCollection( Collection::root() ); + } else { + const Collection parent = collectionForMaildir( md.parent() ); + col.setRemoteId( md.name() ); + col.setParentCollection( parent ); + } + + return col; +} + +MaildirResource::MaildirResource( const QString &id ) + :ResourceBase( id ), + mSettings( new MaildirSettings( componentData().config() ) ), + mFsWatcher( new KDirWatch( this ) ) +{ + // we cannot be sure that a config file is existing + // the MaildirResource will always be build + // look for a resource of this name + QString configFile = componentData().dirs()->findResource( "config", id + "rc" ); + // if not present, create it + if ( configFile.isEmpty() ) { + // check if the resource was used before + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), Akonadi::CollectionFetchJob::FirstLevel, this ); + job->fetchScope().setResource( id ); + connect( job, SIGNAL(result(KJob*)), SLOT(attemptConfigRestoring(KJob*)) ); + job->start(); + } + new MaildirSettingsAdaptor( mSettings ); + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + mSettings, QDBusConnection::ExportAdaptors ); + connect( this, SIGNAL(reloadConfiguration()), SLOT(configurationChanged()) ); + + // We need to enable this here, otherwise we neither get the remote ID of the + // parent collection when a collection changes, nor the full item when an item + // is added. + changeRecorder()->fetchCollection( true ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All ); + changeRecorder()->itemFetchScope().setFetchModificationTime( false ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + changeRecorder()->fetchChangedOnly( true ); + + setHierarchicalRemoteIdentifiersEnabled( true ); + + ItemFetchScope scope( changeRecorder()->itemFetchScope() ); + scope.fetchFullPayload( false ); + scope.fetchPayloadPart( MessagePart::Header ); + scope.setAncestorRetrieval( ItemFetchScope::None ); + setItemSynchronizationFetchScope( scope ); + + connect( mFsWatcher, SIGNAL(dirty(QString)), SLOT(slotDirChanged(QString)) ); + if (!ensureSaneConfiguration()) { + emit error( i18n( "Unusable configuration." ) ); + } else { + synchronizeCollectionTree(); + } + + mChangedCleanerTimer = new QTimer( this ); + connect( mChangedCleanerTimer, SIGNAL(timeout()), this, SLOT(changedCleaner()) ); +} + +void MaildirResource::attemptConfigRestoring( KJob * job ) +{ + if ( job->error() ) { + kDebug() << job->errorString(); + return; + } + // we cannot be sure that a config file is existing + const QString id = identifier(); + const QString configFile = componentData().dirs()->findResource( "config", id + "rc" ); + // we test it again, to be sure + if ( configFile.isEmpty() ) { + // it is still empty, create it + kWarning() << "the resource is not properly configured:"; + kWarning() << "there is no config file for the resource."; + kWarning() << "we create a new one."; + const Collection::List cols = qobject_cast( job )->collections(); + QString path; + if ( !cols.isEmpty() ) { + kDebug() << "the collections list is not empty"; + Collection col = cols.first(); + // get the path of the collection + path = col.remoteId(); + } + // test the path + if ( path.isEmpty() ) { + kDebug() << "build a new path"; + const QString dataDir = componentData().dirs()->localxdgdatadir(); + // we use "id" to get an unique path + path = dataDir; + if (!defaultResourceType().isEmpty()) { + path += defaultResourceType() + QLatin1Char('/'); + } + path += id; + kDebug() << "set the path" << path; + mSettings->setPath( path ); + // set the resource into container mode for its top level + mSettings->setTopLevelIsContainer( true ); + } else { + // check how the directory looks like the actual check is missing. + Maildir root( mSettings->path(), true ); + mSettings->setTopLevelIsContainer( root.isValid() ); + } + kDebug() << "synchronize"; + configurationChanged(); + } +} + +MaildirResource::~ MaildirResource() +{ + delete mSettings; +} + +bool MaildirResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( parts ); + + const Maildir md = maildirForCollection( item.parentCollection() ); + if ( !md.isValid() ) { + cancelTask( i18n( "Unable to fetch item: The maildir folder \"%1\" is not valid.", + md.path() ) ); + return false; + } + + const QByteArray data = md.readEntry( item.remoteId() ); + KMime::Message *mail = new KMime::Message(); + mail->setContent( KMime::CRLFtoLF( data ) ); + mail->parse(); + + Item i( item ); + i.setPayload( KMime::Message::Ptr( mail ) ); + itemRetrieved( i ); + return true; +} + +QString MaildirResource::itemMimeType() const +{ + return KMime::Message::mimeType(); +} + +void MaildirResource::configurationChanged() +{ + mSettings->writeConfig(); + bool configValid = ensureSaneConfiguration(); + configValid &= ensureDirExists(); + if ( configValid ) { + emit status( Idle ); + setOnline( true ); + } +} + + +void MaildirResource::aboutToQuit() +{ + // The settings may not have been saved if e.g. they have been modified via + // DBus instead of the config dialog. + mSettings->writeConfig(); +} + +QString MaildirResource::defaultResourceType() +{ + return QString(); +} + +void MaildirResource::configure( WId windowId ) +{ + ConfigDialog dlg( mSettings, identifier() ); + if ( windowId ) + KWindowSystem::setMainWindow( &dlg, windowId ); + dlg.setWindowIcon( KIcon( QLatin1String("message-rfc822") ) ); + if ( dlg.exec() ) { + // if we have no name, or the default one, + // better use the name of the top level collection + // that looks nicer + if ( name().isEmpty() || name() == identifier() ) { + Maildir md( mSettings->path() ); + setName( md.name() ); + } + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + + configurationChanged(); + synchronizeCollectionTree(); +} + +void MaildirResource::itemAdded( const Akonadi::Item & item, const Akonadi::Collection& collection ) +{ + if ( !ensureSaneConfiguration() ) { + cancelTask( i18n( "Unusable configuration." ) ); + return; + } + Maildir dir = maildirForCollection( collection ); + if ( mSettings->readOnly() || !dir.isValid() ) { + cancelTask( dir.lastError() ); + return; + } + + // we can only deal with mail + if ( !item.hasPayload() ) { + cancelTask( i18n( "Error: Unsupported type." ) ); + return; + } + const KMime::Message::Ptr mail = item.payload(); + + stopMaildirScan( dir ); + + const QString rid = dir.addEntry( mail->encodedContent() ); + mChangedFiles.insert( rid ); + mChangedCleanerTimer->start( CLEANER_TIMEOUT ); + + if ( rid.isEmpty() ) { + restartMaildirScan( dir ); + cancelTask( dir.lastError() ); + return; + } + + restartMaildirScan( dir ); + + Item i( item ); + i.setRemoteId( rid ); + changeCommitted( i ); +} + +void MaildirResource::itemChanged( const Akonadi::Item& item, const QSet& parts ) +{ + if ( !ensureSaneConfiguration() ) { + cancelTask( i18n( "Unusable configuration." ) ); + return; + } + + + bool bodyChanged = false; + bool flagsChanged = false; + bool headChanged = false; + Q_FOREACH ( const QByteArray &part, parts ) { + if ( part.startsWith( "PLD:RFC822" ) ) { + bodyChanged = true; + } else if ( part.startsWith( "PLD:HEAD" ) ) { + headChanged = true; + } + if ( part.contains( "FLAGS" ) ) { + flagsChanged = true; + } + } + + if ( mSettings->readOnly() || ( !bodyChanged && !flagsChanged && !headChanged ) ) { + changeProcessed(); + return; + } + + Maildir dir = maildirForCollection( item.parentCollection() ); + if ( !dir.isValid() ) { + cancelTask( dir.lastError() ); + return; + } + + Item newItem( item ); + + if ( flagsChanged || bodyChanged || headChanged ) { //something has changed that we can deal with + stopMaildirScan( dir ); + + if ( flagsChanged ) { //flags changed, store in file name and get back the new filename (id) + const QString newKey = dir.changeEntryFlags( item.remoteId(), item.flags() ); + if ( newKey.isEmpty() ) { + restartMaildirScan( dir ); + cancelTask( i18n( "Failed to change the flags for the mail. %1" ).arg( dir.lastError() ) ); + return; + } + newItem.setRemoteId( newKey ); + } + + if ( bodyChanged || headChanged ) { //head or body changed + // we can only deal with mail + if ( item.hasPayload() ) { + const KMime::Message::Ptr mail = item.payload(); + QByteArray data = mail->encodedContent(); + if ( headChanged && !bodyChanged ) { + //only the head has changed, get the current version of the mail + //replace the head and store the new mail in the file + const QByteArray currentData = dir.readEntry( newItem.remoteId() ); + if ( currentData.isEmpty() && !dir.lastError().isEmpty() ) { + restartMaildirScan( dir ); + cancelTask( dir.lastError() ); + return; + } + const QByteArray newHead = mail->head(); + mail->setContent( currentData ); + mail->setHead( newHead ); + mail->parse(); + data = mail->encodedContent(); + } + if ( !dir.writeEntry( newItem.remoteId(), data ) ) { + restartMaildirScan( dir ); + cancelTask( dir.lastError() ); + return; + } + mChangedFiles.insert( newItem.remoteId() ); + mChangedCleanerTimer->start( CLEANER_TIMEOUT ); + } else { + restartMaildirScan( dir ); + cancelTask( i18n( "Maildir resource got a non-mail content!" ) ); + return; + } + } + + restartMaildirScan( dir ); + + changeCommitted( newItem ); + } else { + emit changeProcessed(); + } +} + +void MaildirResource::itemMoved( const Item &item, const Collection &source, const Collection &destination ) +{ + if ( source == destination ) { // should not happen but would confuse Maildir::moveEntryTo + changeProcessed(); + return; + } + + if ( !ensureSaneConfiguration() ) { + cancelTask( i18n( "Unusable configuration." ) ); + return; + } + + Maildir sourceDir = maildirForCollection( source ); + if ( !sourceDir.isValid() ) { + cancelTask( i18n( "Source folder is invalid: '%1'.", sourceDir.lastError() ) ); + return; + } + + Maildir destDir = maildirForCollection( destination ); + if ( !destDir.isValid() ) { + cancelTask( i18n( "Destination folder is invalid: '%1'.", destDir.lastError() ) ); + return; + } + + stopMaildirScan( sourceDir ); + stopMaildirScan( destDir ); + + const QString newRid = sourceDir.moveEntryTo( item.remoteId(), destDir ); + + mChangedFiles.insert( newRid ); + mChangedCleanerTimer->start( CLEANER_TIMEOUT ); + + restartMaildirScan( sourceDir ); + restartMaildirScan( destDir ); + + if ( newRid.isEmpty() ) { + cancelTask( i18n( "Could not move message '%1' from '%2' to '%3'. The error was %4.", item.remoteId(), sourceDir.path(), destDir.path(), sourceDir.lastError() ) ); + return; + } + + Item i( item ); + i.setRemoteId( newRid ); + changeCommitted( i ); +} + +void MaildirResource::itemRemoved(const Akonadi::Item & item) +{ + if ( !ensureSaneConfiguration() ) { + cancelTask( i18n( "Unusable configuration." ) ); + return; + } + + if ( !mSettings->readOnly() ) { + Maildir dir = maildirForCollection( item.parentCollection() ); + // !dir.isValid() means that our parent folder has been deleted already, + // so we don't care at all as that one will be recursive anyway + stopMaildirScan( dir ); + if ( dir.isValid() && !dir.removeEntry( item.remoteId() ) ) { + emit error( i18n( "Failed to delete message: %1", item.remoteId() ) ); + } + restartMaildirScan( dir ); + } + kDebug() << "Item removed" << item.id() << " in collection :" << item.parentCollection().id(); + changeProcessed(); +} + +Collection::List MaildirResource::listRecursive( const Collection &root, const Maildir &dir ) +{ + if ( mSettings->monitorFilesystem() ) { + mFsWatcher->addDir( dir.path() + QDir::separator() + QLatin1String( "new" ) ); + mFsWatcher->addDir( dir.path() + QDir::separator() + QLatin1String( "cur" ) ); + mFsWatcher->addDir( dir.subDirPath() ); + if ( dir.isRoot() ) { + mFsWatcher->addDir( dir.path() ); + } + } + + Collection::List list; + const QStringList mimeTypes = QStringList() << itemMimeType() << Collection::mimeType(); + foreach ( const QString &sub, dir.subFolderList() ) { + Collection c; + c.setName( sub ); + c.setRemoteId( sub ); + c.setParentCollection( root ); + c.setContentMimeTypes( mimeTypes ); + + const Maildir md = maildirForCollection( c ); + if ( !md.isValid() ) + continue; + + list << c; + list += listRecursive( c, md ); + } + return list; +} + +void MaildirResource::retrieveCollections() +{ + Maildir dir( mSettings->path(), mSettings->topLevelIsContainer() ); + if ( !dir.isValid() ) { + emit error( dir.lastError() ); + collectionsRetrieved( Collection::List() ); + return; + } + + Collection root; + root.setParentCollection( Collection::root() ); + root.setRemoteId( mSettings->path() ); + root.setName( name() ); + if ( mSettings->readOnly() ) { + root.setRights( Collection::ReadOnly ); + } else { + if ( mSettings->topLevelIsContainer() ) { + root.setRights( Collection::ReadOnly | Collection::CanCreateCollection ); + } else { + root.setRights( Collection::CanChangeItem | Collection::CanCreateItem | Collection::CanDeleteItem + | Collection::CanCreateCollection ); + } + } + + CachePolicy policy; + policy.setInheritFromParent( false ); + policy.setSyncOnDemand( true ); + policy.setLocalParts( QStringList() << MessagePart::Envelope ); + policy.setCacheTimeout( 1 ); + policy.setIntervalCheckTime( -1 ); + root.setCachePolicy( policy ); + + QStringList mimeTypes; + mimeTypes << Collection::mimeType(); + mimeTypes << itemMimeType(); + root.setContentMimeTypes( mimeTypes ); + + + Collection::List list; + list << root; + list += listRecursive( root, dir ); + collectionsRetrieved( list ); +} + +void MaildirResource::retrieveItems( const Akonadi::Collection & col ) +{ + const Maildir md = maildirForCollection( col ); + if ( !md.isValid() ) { + cancelTask( i18n( "Maildir '%1' for collection '%2' is invalid.", md.path(), col.remoteId() ) ); + return; + } + + RetrieveItemsJob *job = new RetrieveItemsJob( col, md, this ); + job->setMimeType( itemMimeType() ); + connect( job, SIGNAL(result(KJob*)), SLOT(slotItemsRetrievalResult(KJob*)) ); +} + +void MaildirResource::slotItemsRetrievalResult ( KJob* job ) +{ + if ( job->error() ) + cancelTask( job->errorString() ); + else + itemsRetrievalDone(); +} + +void MaildirResource::collectionAdded(const Collection & collection, const Collection &parent) +{ + if ( !ensureSaneConfiguration() ) { + emit error( i18n( "Unusable configuration." ) ); + changeProcessed(); + return; + } + + Maildir md = maildirForCollection( parent ); + kDebug( 5254 ) << md.subFolderList() << md.entryList(); + if ( mSettings->readOnly() || !md.isValid() ) { + changeProcessed(); + return; + } else { + const QString collectionName( collection.name().replace( QDir::separator(), QString() ) ); + const QString newFolderPath = md.addSubFolder( collectionName ); + if ( newFolderPath.isEmpty() ) { + changeProcessed(); + return; + } + + kDebug( 5254 ) << md.subFolderList() << md.entryList(); + + Collection col = collection; + col.setRemoteId( collectionName ); + col.setName( collectionName ); + changeCommitted( col ); + } + +} + +void MaildirResource::collectionChanged(const Collection & collection) +{ + if ( !ensureSaneConfiguration() ) { + emit error( i18n( "Unusable configuration." ) ); + changeProcessed(); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + if ( collection.name() != name() ) + setName( collection.name() ); + changeProcessed(); + return; + } + + if ( collection.remoteId() == collection.name() ) { + changeProcessed(); + return; + } + + Maildir md = maildirForCollection( collection ); + if ( !md.isValid() ) { + assert( !collection.remoteId().isEmpty() ); // caught in resourcebase + // we don't have a maildir for this collection yet, probably due to a race + // make one, otherwise the rename below will fail + md.create(); + } + + const QString collectionName( collection.name().replace( QDir::separator(), QString() ) ); + if ( !md.rename( collectionName ) ) { + emit error( i18n( "Unable to rename maildir folder '%1'.", collection.name() ) ); + changeProcessed(); + return; + } + Collection c( collection ); + c.setRemoteId( collectionName ); + c.setName( collectionName ); + changeCommitted( c ); +} + +void MaildirResource::collectionMoved( const Collection &collection, const Collection &source, const Collection &dest ) +{ + kDebug() << collection << source << dest; + + if ( !ensureSaneConfiguration() ) { + emit error( i18n( "Unusable configuration." ) ); + changeProcessed(); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + emit error( i18n( "Cannot move root maildir folder '%1'." ,collection.remoteId() ) ); + changeProcessed(); + return; + } + + if ( source == dest ) { // should not happen, but who knows... + changeProcessed(); + return; + } + + Collection c( collection ); + c.setParentCollection( source ); + Maildir md = maildirForCollection( c ); + Maildir destMd = maildirForCollection( dest ); + if ( !md.moveTo( destMd ) ) { + emit error( i18n( "Unable to move maildir folder '%1' from '%2' to '%3'.", collection.remoteId(), source.remoteId(), dest.remoteId() ) ); + changeProcessed(); + } else { + const QString path = maildirPathForCollection( c ); + mMaildirsForCollection.remove( path ); + changeCommitted( collection ); + } +} + +void MaildirResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + if ( !ensureSaneConfiguration() ) { + emit error( i18n( "Unusable configuration." ) ); + changeProcessed(); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + emit error( i18n( "Cannot delete top-level maildir folder '%1'.", mSettings->path() ) ); + changeProcessed(); + return; + } + + Maildir md = maildirForCollection( collection.parentCollection() ); + // !md.isValid() means that our parent folder has been deleted already, + // so we don't care at all as that one will be recursive anyway + if ( md.isValid() && !md.removeSubFolder( collection.remoteId() ) ) { + emit error( i18n( "Failed to delete sub-folder '%1'.", collection.remoteId() ) ); + } + + const QString path = maildirPathForCollection( collection ); + mMaildirsForCollection.remove( path ); + + changeProcessed(); +} + +bool MaildirResource::ensureDirExists() +{ + Maildir root( mSettings->path() ); + if ( !root.isValid( false ) && !mSettings->topLevelIsContainer() ) { + if ( !root.create() ) + emit status( Broken, i18n( "Unable to create maildir '%1'.", mSettings->path() ) ); + return false; + } + return true; +} + +bool MaildirResource::ensureSaneConfiguration() +{ + if ( mSettings->path().isEmpty() ) { + emit status( NotConfigured, i18n( "No usable storage location configured." ) ); + setOnline( false ); + return false; + } + return true; +} + + +void MaildirResource::slotDirChanged(const QString& dir) +{ + QFileInfo fileInfo( dir ); + if ( fileInfo.isFile() ) { + slotFileChanged( fileInfo ); + return; + } + + if ( dir == mSettings->path() ) { + synchronizeCollectionTree(); + synchronizeCollection( Collection::root().id() ); + return; + } + + if ( dir.endsWith( QLatin1String( ".directory" ) ) ) { + synchronizeCollectionTree(); //might be too much, but this is not a common case anyway + return; + } + + QDir d( dir ); + if ( !d.cdUp() ) + return; + + Maildir md( d.path() ); + if ( !md.isValid() ) + return; + + md.refreshKeyCache(); + + const Collection col = collectionForMaildir( md ); + if ( col.remoteId().isEmpty() ) { + kDebug() << "unable to find collection for path" << dir; + return; + } + + CollectionFetchJob *job = new CollectionFetchJob( col, Akonadi::CollectionFetchJob::Base, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(fsWatchDirFetchResult(KJob*)) ); +} + +void MaildirResource::fsWatchDirFetchResult(KJob* job) +{ + if ( job->error() ) { + kDebug() << job->errorString(); + return; + } + const Collection::List cols = qobject_cast( job )->collections(); + if ( cols.isEmpty() ) + return; + + synchronizeCollection( cols.first().id() ); +} + +void MaildirResource::slotFileChanged( const QFileInfo& fileInfo ) +{ + const QString key = fileInfo.fileName(); + if ( mChangedFiles.contains( key ) ) { + mChangedFiles.remove( key ); + return; + } + + QString path = fileInfo.path(); + if ( path.endsWith( QLatin1String( "/new" ) ) ) { + path.remove( path.length() - 4, 4 ); + } else if ( path.endsWith( QLatin1String( "/cur" ) ) ) { + path.remove( path.length() - 4, 4 ); + } + + const Maildir md( path ); + if ( !md.isValid() ) + return; + + const Collection col = collectionForMaildir( md ); + if ( col.remoteId().isEmpty() ) { + kDebug() << "unable to find collection for path" << fileInfo.path(); + return; + } + + Item item; + item.setRemoteId( key ); + item.setParentCollection( col ); + + ItemFetchJob *job = new ItemFetchJob( item, this ); + job->setProperty( "entry", key ); + job->setProperty( "dir", path ); + connect( job, SIGNAL(result(KJob*)), SLOT(fsWatchFileFetchResult(KJob*)) ); +} + +void MaildirResource::fsWatchFileFetchResult( KJob* job ) +{ + if ( job->error() ) { + kDebug() << job->errorString(); + return; + } + Item::List items = qobject_cast( job )->items(); + if ( items.isEmpty() ) + return; + + const QString fileName = job->property( "entry" ).toString(); + const QString path = job->property( "dir" ).toString(); + + const Maildir md( path ); + + QString entry = fileName; + Item item( items.at( 0 ) ); + const qint64 entrySize = md.size( entry ); + if ( entrySize >= 0 ) + item.setSize( entrySize ); + + Item::Flags flags = md.readEntryFlags( entry ); + Q_FOREACH ( const Item::Flag &flag, flags ) { + item.setFlag( flag ); + } + + const QByteArray data = md.readEntry( entry ); + KMime::Message *mail = new KMime::Message(); + mail->setContent( KMime::CRLFtoLF( data ) ); + mail->parse(); + + item.setPayload( KMime::Message::Ptr( mail ) ); + + ItemModifyJob *mjob = new ItemModifyJob( item ); + connect( mjob, SIGNAL(result(KJob*)), SLOT(fsWatchFileModifyResult(KJob*)) ); +} + +void MaildirResource::fsWatchFileModifyResult(KJob* job) +{ + if ( job->error() ) { + kDebug() << job->errorString(); + return; + } +} + +QString MaildirResource::maildirPathForCollection(const Collection& collection) const +{ + QString path = collection.remoteId(); + Akonadi::Collection parent = collection.parentCollection(); + while ( !parent.remoteId().isEmpty() ) { + path.prepend( parent.remoteId() + QLatin1Char('/') ); + parent = parent.parentCollection(); + } + + return path; +} + +void MaildirResource::stopMaildirScan(const Maildir &maildir) +{ + const QString path = maildir.path(); + mFsWatcher->stopDirScan( path + QLatin1Literal( "/new" ) ); + mFsWatcher->stopDirScan( path + QLatin1Literal( "/cur" ) ); +} + +void MaildirResource::restartMaildirScan(const Maildir &maildir) +{ + const QString path = maildir.path(); + mFsWatcher->restartDirScan( path + QLatin1Literal( "/new" ) ); + mFsWatcher->restartDirScan( path + QLatin1Literal( "/cur" ) ); +} + +void MaildirResource::changedCleaner() +{ + mChangedFiles.clear(); +} diff --git a/kdepim-runtime/resources/maildir/maildirresource.desktop b/kdepim-runtime/resources/maildir/maildirresource.desktop new file mode 100644 index 00000000..3b6ecceb --- /dev/null +++ b/kdepim-runtime/resources/maildir/maildirresource.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Name=Maildir +Name[ar]=Maildir +Name[bg]=Maildir +Name[bs]=Maildir +Name[ca]=Maildir +Name[ca@valencia]=Maildir +Name[cs]=Maildir +Name[da]=Maildir +Name[de]=Maildir +Name[el]=Maildir +Name[en_GB]=Maildir +Name[eo]=Maildir +Name[es]=Maildir +Name[et]=Maildir +Name[fi]=Maildir +Name[fr]=Maildir +Name[ga]=Maildir +Name[gl]=Maildir +Name[hu]=Maildir +Name[ia]=Maildir +Name[it]=Maildir +Name[ja]=Maildir +Name[kk]=Maildir +Name[km]=ážážâ€‹ážŸáŸ†áž”áž»ážáŸ’ážš +Name[ko]=Maildir +Name[lt]=Maildir +Name[lv]=Maildir +Name[nb]=Maildir +Name[nds]=Nettpostorner +Name[nl]=Maildir +Name[nn]=Maildir +Name[pa]=Maildir +Name[pl]=Maildir +Name[pt]=Maildir +Name[pt_BR]=Maildir +Name[ro]=Maildir +Name[ru]=Maildir +Name[sk]=Maildir +Name[sl]=MailDir +Name[sq]=Maildir +Name[sr]=Мејлдир +Name[sr@ijekavian]=Мејлдир +Name[sr@ijekavianlatin]=Maildir +Name[sr@latin]=Maildir +Name[sv]=Maildir +Name[tr]=Maildir +Name[uk]=Maildir +Name[x-test]=xxMaildirxx +Name[zh_CN]=邮件目录 +Name[zh_TW]=Maildir +Comment=Loads data from a local maildir folder +Comment[ar]=تحمل البيانات من مجلد maildir المحلي +Comment[bg]=Зареждане на данни от локална папка maildir +Comment[bs]=UÄitava podatke iz lokalnog maildir direktorija +Comment[ca]=Carrega les dades des d'una carpeta pel directori de correu local +Comment[ca@valencia]=Carrega les dades des d'una carpeta pel directori de correu local +Comment[cs]=NaÄítá data z místní složky maildir +Comment[da]=Indlæser data fra en lokal maildir-mappe +Comment[de]=Daten werden aus einem lokalen Maildir-Ordner geladen +Comment[el]=ΦόÏτωσης δεδομένων από έναν τοπικό φάκελο maildir +Comment[en_GB]=Loads data from a local maildir folder +Comment[es]=Carga datos de una carpeta de maildir local +Comment[et]=Andmete laadimine kohalikust maildir-kaustast +Comment[fi]=Lataa tietoja paikallisesta maildir-kansiosta +Comment[fr]=Charge des données d'un dossier au format « maildir » +Comment[ga]=Breiseán a luchtaíonn sonraí ó fhillteán logánta maildir +Comment[gl]=Carga datos desde un cartafol de correo local +Comment[hu]=AdatbetöltÅ‘ helyi maildir típusú mappákhoz +Comment[ia]=Lege datos de un dossier local de Maildir +Comment[it]=Carica dati da una cartella locale maildir +Comment[ja]=ローカル㮠maildir フォルダã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=Жергілікті maildir қапшығынан деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ážáž maildir មូលដ្ឋាន +Comment[ko]=로컬 maildir í´ë”ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ vietinio maildir aplanko +Comment[lv]=IelÄdÄ“ datus no lokÄlas maildir mapes +Comment[nb]=Laster data fra en lokal maildir-mappe +Comment[nds]=Laadt Daten ut en lokaal Nettpostorner +Comment[nl]=Laadt gegevens van een lokaal maildir-map +Comment[nn]=Lastar data frÃ¥ ei lokal iCal-fil +Comment[pa]=ਲੋਕਲ maildir ਫੋਲਡਰ ਤੋਂ ਡਾਟਾ ਲੋਡ ਕਰੋ +Comment[pl]=Wczytuje dane z lokalnego folderu maildir +Comment[pt]=Carrega os dados de uma pasta Maildir local +Comment[pt_BR]=Carrega os dados de uma pasta maildir local +Comment[ro]=ÃŽncarcă date dintr-un dosar maildir local +Comment[ru]=Загрузка данных из локальной папки Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð¹ +Comment[sk]=NaÄíta dáta z miestneho súboru maildir +Comment[sl]=Naloži podatke iz krajevne poÅ¡tne mape MailDir +Comment[sr]=Учитава податке из локалне мејлдир фаÑцикле +Comment[sr@ijekavian]=Учитава податке из локалне мејлдир фаÑцикле +Comment[sr@ijekavianlatin]=UÄitava podatke iz lokalne maildir fascikle +Comment[sr@latin]=UÄitava podatke iz lokalne maildir fascikle +Comment[sv]=Laddar data frÃ¥n en lokal maildir-katalog +Comment[tr]=Yerel maildir dizininden veri yükleme aracı +Comment[uk]=Завантажує дані з локальної теки maildir +Comment[x-test]=xxLoads data from a local maildir folderxx +Comment[zh_CN]=ä»Žæœ¬åœ°é‚®ä»¶ç›®å½•è½½å…¥æ•°æ® +Comment[zh_TW]=從本地 Maildir æ ¼å¼çš„目錄中載入資料 +Type=AkonadiResource +Exec=akonadi_maildir_resource +Icon=message-rfc822 + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_maildir_resource +X-Akonadi-LaunchMethod=AgentServer +X-Akonadi-Custom-HasLocalStorage=true diff --git a/kdepim-runtime/resources/maildir/maildirresource.h b/kdepim-runtime/resources/maildir/maildirresource.h new file mode 100644 index 00000000..494da516 --- /dev/null +++ b/kdepim-runtime/resources/maildir/maildirresource.h @@ -0,0 +1,106 @@ +/* + Copyright (c) 2007 Till Adam + + 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 MAILDIR_RESOURCE_H +#define MAILDIR_RESOURCE_H + +#include +#include + +class QTimer; +class QFileInfo; +class KDirWatch; +namespace Akonadi_Maildir_Resource +{ +class MaildirSettings; +} +namespace KPIM +{ +class Maildir; +} + +class MaildirResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + MaildirResource( const QString &id ); + ~MaildirResource(); + + virtual QString defaultResourceType(); +public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual QString itemMimeType() const; + + virtual void aboutToQuit(); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest ); + virtual void itemRemoved( const Akonadi::Item &item ); + + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection ); + // do not hide the other variant, use implementation from base class + // which just forwards to the one above + using Akonadi::AgentBase::ObserverV2::collectionChanged; + virtual void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &dest ); + virtual void collectionRemoved( const Akonadi::Collection &collection ); + + private slots: + void configurationChanged(); + void slotItemsRetrievalResult(KJob* job); + void slotDirChanged( const QString &dir ); + void slotFileChanged( const QFileInfo &fileInfo ); + void fsWatchDirFetchResult( KJob* job ); + void fsWatchFileFetchResult( KJob* job ); + void fsWatchFileModifyResult( KJob* job ); + // Try to restore some config values from Akonadi data + void attemptConfigRestoring( KJob* job ); + void changedCleaner(); + + private: + bool ensureDirExists(); + bool ensureSaneConfiguration(); + Akonadi::Collection::List listRecursive( const Akonadi::Collection &root, const KPIM::Maildir &dir ); + /** Creates a maildir object for the collection @p col, given it has the full ancestor chain set. */ + KPIM::Maildir maildirForCollection( const Akonadi::Collection &col ); + /** Creates a collection object for the given maildir @p md. */ + Akonadi::Collection collectionForMaildir( const KPIM::Maildir &md ) const; + + QString maildirPathForCollection( const Akonadi::Collection &collection) const; + void stopMaildirScan(const KPIM::Maildir &maildir); + void restartMaildirScan(const KPIM::Maildir &maildir); + +private: + Akonadi_Maildir_Resource::MaildirSettings *mSettings; + KDirWatch *mFsWatcher; + QHash mMaildirsForCollection; + QSet mChangedFiles; //files changed by the resource and that should be ignored in slotFileChanged + QTimer *mChangedCleanerTimer; +}; + +#endif diff --git a/kdepim-runtime/resources/maildir/maildirresource.kcfg b/kdepim-runtime/resources/maildir/maildirresource.kcfg new file mode 100644 index 00000000..5775fff4 --- /dev/null +++ b/kdepim-runtime/resources/maildir/maildirresource.kcfg @@ -0,0 +1,25 @@ + + + + + + + $HOME/.local/share/local-mail/ + + + + false + + + + false + + + true + + + diff --git a/kdepim-runtime/resources/maildir/main.cpp b/kdepim-runtime/resources/maildir/main.cpp new file mode 100644 index 00000000..b62b3a71 --- /dev/null +++ b/kdepim-runtime/resources/maildir/main.cpp @@ -0,0 +1,26 @@ +/* + Copyright (c) 2007 Till Adam + + 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 "maildirresource.h" + +#include + +#include + +AKONADI_AGENT_FACTORY( MaildirResource, akonadi_maildir_resource ) diff --git a/kdepim-runtime/resources/maildir/retrieveitemsjob.cpp b/kdepim-runtime/resources/maildir/retrieveitemsjob.cpp new file mode 100644 index 00000000..8b6cca6e --- /dev/null +++ b/kdepim-runtime/resources/maildir/retrieveitemsjob.cpp @@ -0,0 +1,185 @@ +/* + Copyright (c) 2011 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 "retrieveitemsjob.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +RetrieveItemsJob::RetrieveItemsJob ( const Akonadi::Collection& collection, const KPIM::Maildir& md, QObject* parent ) : + Job ( parent ), + m_collection( collection ), + m_maildir( md ), + m_mimeType( KMime::Message::mimeType() ), + m_transaction( 0 ), + m_entryIterator(0), + m_previousMtime( 0 ), + m_highestMtime( 0 ) +{ + Q_ASSERT( m_collection.isValid() ); + Q_ASSERT( m_maildir.isValid() ); +} + +void RetrieveItemsJob::setMimeType ( const QString& mimeType ) +{ + m_mimeType = mimeType; +} + +void RetrieveItemsJob::doStart() +{ + Q_ASSERT( !m_mimeType.isEmpty() ); + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( m_collection, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(localListDone(KJob*)) ); +} + +void RetrieveItemsJob::localListDone ( KJob* job ) +{ + if ( job->error() ) return; // handled by base class + + const Akonadi::Item::List items = qobject_cast( job )->items(); + m_localItems.reserve( items.size() ); + foreach ( const Akonadi::Item &item, items ) { + if ( !item.remoteId().isEmpty() ) + m_localItems.insert( item.remoteId(), item ); + } + + m_listingPath = m_maildir.path() + QLatin1String( "/new/" ); + delete m_entryIterator; + m_entryIterator = new QDirIterator( m_maildir.pathToNew(), QDir::Files ); + m_previousMtime = m_collection.remoteRevision().toLongLong(); + m_highestMtime = 0; + processEntry(); +} + +void RetrieveItemsJob::processEntry() +{ + QFileInfo entryInfo; + + QString filePath = m_entryIterator->next(); + + QString fileName = m_entryIterator->fileName(); + + bool newItemFound = false; + while ( !newItemFound ) { + if ( filePath.isEmpty() ) { + if ( m_listingPath.endsWith( QLatin1String( "/new/" ) ) ) { + m_listingPath = m_maildir.path() + QLatin1String( "/cur/" ); + delete m_entryIterator; + m_entryIterator = new QDirIterator( m_maildir.pathToCurrent(), QDir::Files ); + processEntry(); + } else { + entriesProcessed(); + } + return; + } + + entryInfo = m_entryIterator->fileInfo(); + const qint64 currentMtime = entryInfo.lastModified().toMSecsSinceEpoch(); + m_highestMtime = qMax( m_highestMtime, currentMtime ); + if ( currentMtime <= m_previousMtime && m_localItems.contains( fileName ) ) { // old, we got this one already + m_localItems.remove( fileName ); + filePath = m_entryIterator->next(); + fileName = m_entryIterator->fileName(); + } else { + newItemFound = true; + } + } + Akonadi::Item item; + item.setRemoteId( fileName ); + item.setMimeType( m_mimeType ); + const qint64 entrySize = entryInfo.size(); + if ( entrySize >= 0 ) + item.setSize( entrySize ); + + KMime::Message *msg = new KMime::Message; + msg->setHead( KMime::CRLFtoLF( m_maildir.readEntryHeadersFromFile( m_listingPath + fileName ) ) ); + msg->parse(); + + Akonadi::Item::Flags flags = m_maildir.readEntryFlags( fileName ); + Q_FOREACH ( const Akonadi::Item::Flag &flag, flags ) { + item.setFlag( flag ); + } + + item.setPayload( KMime::Message::Ptr( msg ) ); + + KJob *job = 0; + if ( m_localItems.contains( fileName ) ) { // modification + item.setId( m_localItems.value( fileName ).id() ); + job = new Akonadi::ItemModifyJob( item, transaction() ); + m_localItems.remove( fileName ); + } else { // new item + job = new Akonadi::ItemCreateJob( item, m_collection, transaction() ); + } + connect(job, SIGNAL(result(KJob*)), SLOT(processEntryDone(KJob*)) ); +} + +void RetrieveItemsJob::processEntryDone( KJob* ) +{ + processEntry(); +} + +void RetrieveItemsJob::entriesProcessed() +{ + delete m_entryIterator; + m_entryIterator = 0; + if ( !m_localItems.isEmpty() ) { + Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob( m_localItems.values(), transaction() ); + m_maildir.removeCachedKeys( m_localItems.keys() ); + transaction()->setIgnoreJobFailure( job ); + } + + // update mtime + if ( m_highestMtime != m_previousMtime ) { + Akonadi::Collection newCol( m_collection ); + newCol.setRemoteRevision( QString::number( m_highestMtime ) ); + Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( newCol, transaction() ); + transaction()->setIgnoreJobFailure( job ); + } + + if ( !m_transaction ) // no jobs created here -> done + emitResult(); + else + m_transaction->commit(); +} + + + +Akonadi::TransactionSequence* RetrieveItemsJob::transaction() +{ + if ( !m_transaction ) { + m_transaction = new Akonadi::TransactionSequence( this ); + m_transaction->setAutomaticCommittingEnabled( false ); + connect( m_transaction, SIGNAL(result(KJob*)), SLOT(transactionDone(KJob*)) ); + } + return m_transaction; +} + +void RetrieveItemsJob::transactionDone ( KJob* job ) +{ + if ( job->error() ) return; // handled by base class + emitResult(); +} + diff --git a/kdepim-runtime/resources/maildir/retrieveitemsjob.h b/kdepim-runtime/resources/maildir/retrieveitemsjob.h new file mode 100644 index 00000000..538d5ad2 --- /dev/null +++ b/kdepim-runtime/resources/maildir/retrieveitemsjob.h @@ -0,0 +1,71 @@ +/* + Copyright (c) 2011 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 MAILDIR_RETRIEVEITEMSJOB_H +#define MAILDIR_RETRIEVEITEMSJOB_H + +#include +#include +#include + +#include "maildir.h" + +class QDirIterator; +namespace Akonadi +{ +class TransactionSequence; +} + +/** + * Used to implement ResourceBase::retrieveItems() for Maildirs. + * This completely bypasses ItemSync in order to achieve maximum performance. + */ +class RetrieveItemsJob : public Akonadi::Job +{ + Q_OBJECT + public: + RetrieveItemsJob( const Akonadi::Collection &collection, const KPIM::Maildir &md, QObject* parent = 0 ); + void setMimeType( const QString &mimeType ); + + protected: + void doStart(); + + private: + void entriesProcessed(); + Akonadi::TransactionSequence* transaction(); + + private slots: + void localListDone( KJob *job ); + void transactionDone( KJob *job ); + void processEntry(); + void processEntryDone( KJob * ); + + private: + Akonadi::Collection m_collection; + KPIM::Maildir m_maildir; + QHash m_localItems; + QString m_mimeType; + Akonadi::TransactionSequence *m_transaction; + QDirIterator *m_entryIterator; + qint64 m_previousMtime; + qint64 m_highestMtime; + QString m_listingPath; +}; + +#endif diff --git a/kdepim-runtime/resources/maildir/settings.kcfgc b/kdepim-runtime/resources/maildir/settings.kcfgc new file mode 100644 index 00000000..f4b61a3c --- /dev/null +++ b/kdepim-runtime/resources/maildir/settings.kcfgc @@ -0,0 +1,9 @@ +File=maildirresource.kcfg +ClassName=MaildirSettings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true +Namespace=Akonadi_Maildir_Resource diff --git a/kdepim-runtime/resources/maildir/settings.ui b/kdepim-runtime/resources/maildir/settings.ui new file mode 100644 index 00000000..e22fe5f3 --- /dev/null +++ b/kdepim-runtime/resources/maildir/settings.ui @@ -0,0 +1,84 @@ + + + Till Adam <adam@kde.org> + ConfigDialog + + + + 0 + 0 + 400 + 290 + + + + Mail Directory Settings + + + + + + 0 + + + + Maildir + + + + + + Select the folder containing the maildir information: + + + + + + + + + + Open in read-only mode + + + + + + + Qt::Vertical + + + + 20 + 141 + + + + + + + + + + + + + + + true + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/resources/maildir/tests/CMakeLists.txt b/kdepim-runtime/resources/maildir/tests/CMakeLists.txt new file mode 100644 index 00000000..b98a2f3f --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +# Stolen from kdepimlibs/akonadi/tests +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} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ) + + # based on kde4_add_unit_test + if (WIN32) + get_target_property( _loc ${_targetName} LOCATION ) + set(_executable ${_loc}.bat) + else () + set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_targetName}) + endif () + if (UNIX) + set(_executable ${_executable}.shell) + endif () + + find_program(_testrunner akonaditest) + + if (KDEPIM_RUN_ISOLATED_TESTS) + add_test( maildir-${_targetName} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config.xml ${_executable} ) + endif () +endmacro(add_akonadi_isolated_test) + + + +add_akonadi_isolated_test( synctest.cpp ) +akonadi_add_resourcetest( maildir maildir.js ) + diff --git a/kdepim-runtime/resources/maildir/tests/maildir-empty.xml b/kdepim-runtime/resources/maildir/tests/maildir-empty.xml new file mode 100644 index 00000000..589bd6f6 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir-empty.xml @@ -0,0 +1,5 @@ + + + wcdC + + diff --git a/kdepim-runtime/resources/maildir/tests/maildir-step1.xml b/kdepim-runtime/resources/maildir/tests/maildir-step1.xml new file mode 100644 index 00000000..faa20e26 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir-step1.xml @@ -0,0 +1,29 @@ + + + wcdC + + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + diff --git a/kdepim-runtime/resources/maildir/tests/maildir-step2.xml b/kdepim-runtime/resources/maildir/tests/maildir-step2.xml new file mode 100644 index 00000000..538885ab --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir-step2.xml @@ -0,0 +1,29 @@ + + + wcdC + + + From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + + + + diff --git a/kdepim-runtime/resources/maildir/tests/maildir.js b/kdepim-runtime/resources/maildir/tests/maildir.js new file mode 100644 index 00000000..a05eee45 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir.js @@ -0,0 +1,63 @@ +Resource.setType( "akonadi_maildir_resource" ); + +// read test +Resource.setPathOption( "Path", "maildir/root" ); +Resource.create(); + +XmlOperations.setXmlFile( "maildir.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.setNormalizeRemoteIds( true ); +XmlOperations.ignoreCollectionField( "Name" ); +XmlOperations.assertEqual(); + +Resource.destroy(); + +// empty maildir +Resource.setPathOption( "Path", "newmaildir" ); +Resource.create(); + +XmlOperations.setXmlFile( "maildir-empty.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.assertEqual(); + +// folder creation +CollectionTest.setParent( Resource.identifier() ); +CollectionTest.addContentType( "message/rfc822" ); +CollectionTest.setName( "test folder" ); +CollectionTest.create(); + +// item creation +ItemTest.setParentCollection( Resource.identifier() + "/test folder" ); +ItemTest.setMimeType( "message/rfc822" ); +ItemTest.setPayloadFromFile( "testmail.mbox" ); +ItemTest.create(); + +Resource.recreate(); + +XmlOperations.setXmlFile( "maildir-step1.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.setItemKey( "None" ); +XmlOperations.ignoreItemField( "RemoteId" ); +XmlOperations.assertEqual(); + +// folder modification +CollectionTest.setCollection( Resource.identifier() + "/test folder" ); +CollectionTest.setName( "changed folder" ); +CollectionTest.update(); + +Resource.recreate(); + +XmlOperations.setXmlFile( "maildir-step2.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.assertEqual(); + +// folder deletion +CollectionTest.setCollection( Resource.identifier() + "/changed folder" ); +CollectionTest.remove(); + +Resource.recreate(); + +XmlOperations.setXmlFile( "maildir-empty.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.assertEqual(); + diff --git a/kdepim-runtime/resources/maildir/tests/maildir.xml b/kdepim-runtime/resources/maildir/tests/maildir.xml new file mode 100644 index 00000000..8caf7ea6 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir.xml @@ -0,0 +1,732 @@ + + + wcdC + + + + + Return-Path: <commitfilter@new.kstuff.org> +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 12:10:48 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 4BDF8E6C790 + for <asok@kdab.net>; Sun, 22 Mar 2009 12:10:48 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 03694-02 for <asok@kdab.net>; + Sun, 22 Mar 2009 12:10:45 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id B289FE6C79B + for <asok@kdab.net>; Sun, 22 Mar 2009 12:10:45 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id 3B465E6C790 + for <asok@kdab.net>; Sun, 22 Mar 2009 12:10:45 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from <kde-commits-bounces-+commitfilter=new.kstuff.org@kde.org>) + id 1LlLfE-0001OT-K7 + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 12:16:25 +0100 +Received: (qmail 23006 invoked by uid 72); 22 Mar 2009 11:16:19 -0000 +Received: (qmail 22986 invoked from network); 22 Mar 2009 11:16:14 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 11:16:11 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 85EE718E + for <kde-commits@kde.org>; Sun, 22 Mar 2009 12:16:12 +0100 (CET) +Received: (nullmailer pid 13467 invoked by uid 30); + Sun, 22 Mar 2009 11:16:12 -0000 +From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester + trunk/playground/pim/akonaditest/resourcetester/tests +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 11:16:12 +0000 +Message-Id: <1237720572.493438.13466.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits <kde-commits.kde.org> +List-Unsubscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=unsubscribe> +List-Post: <mailto:kde-commits@kde.org> +List-Help: <mailto:kde-commits-request@kde.org?subject=help> +List-Subscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=subscribe> +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26666 +X-Length: 11240 +Status: RO +X-Status: ORC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942640 by vkrause: + +Allow to specifiy the collection property used to identify corresponding +collections. + + + M +2 -0 tests/vcardtest.js + M +1 -1 tests/vcardtest.xml + M +29 -14 xmloperations.cpp + M +46 -0 xmloperations.h + + +--- trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest.js #942639:942640 +@@ -4,6 +4,8 @@ + + XmlOperations.setXmlFile( "vcardtest.xml" ); + XmlOperations.setRootCollections( Resource.identifier() ); ++XmlOperations.setCollectionKey( "None" ); // we only expect one collection + XmlOperations.ignoreCollectionField( "Name" ); // name is the resource identifier and thus unpredictable ++XmlOperations.ignoreCollectionField( "RemoteId" ); // remote id is the absolute path + XmlOperations.assertEqual(); + +--- trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest.xml #942639:942640 +@@ -1,5 +1,5 @@ + <knut> +- <collection rid="/k/kde4/src/playground/pim/akonaditest/resourcetester/tests/vcardtest.vcf" name="akonadi_vcard_resource_0" content="text/directory"> ++ <collection rid="vcardtest.vcf" name="akonadi_vcard_resource_0" content="text/directory"> + <attribute type="AccessRights" >wcdW</attribute> + <attribute type="ENTITYDISPLAY" >("vcardtest.vcf" "office-address-book")</attribute> + <item rid="bb2slGmqxb" mimetype="text/directory"> +--- trunk/playground/pim/akonaditest/resourcetester/xmloperations.cpp #942639:942640 +@@ -31,9 +31,6 @@ + #include <QFileInfo> + #include <QStringList> + +-#include <boost/bind.hpp> +-#include <algorithm> +- + using namespace Akonadi; + + template <typename T> QTextStream& operator<<( QTextStream &s, const QSet<T> &set ) +@@ -53,7 +50,8 @@ + + XmlOperations::XmlOperations(QObject* parent) : + QObject( parent ), +- mCollectionFields( 0xFF ) ++ mCollectionFields( 0xFF ), ++ mCollectionKey( RemoteId ) + { + } + +@@ -99,6 +97,17 @@ + return mErrorMsg; + } + ++void XmlOperations::setCollectionKey(XmlOperations::CollectionField field) ++{ ++ mCollectionKey = field; ++} ++ ++void XmlOperations::setCollectionKey(const QString& fieldName) ++{ ++ const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); ++ setCollectionKey( static_cast<CollectionField>( me.keyToValue( fieldName.toLatin1() ) ) ); ++} ++ + void XmlOperations::ignoreCollectionField(XmlOperations::CollectionField field) + { + mCollectionFields = mCollectionFields & ~field; +@@ -137,8 +146,20 @@ + { + Collection::List cols( _cols ); + Collection::List refCols( _refCols ); +- std::sort( cols.begin(), cols.end(), boost::bind( &Collection::remoteId, _1 ) < boost::bind( &Collection::remoteId, _2 ) ); +- std::sort( refCols.begin(), refCols.end(), boost::bind( &Collection::remoteId, _1 ) < boost::bind( &Collection::remoteId, _2 ) ); ++ switch ( mCollectionKey ) { ++ case RemoteId: ++ sortCollectionList( cols, &Collection::remoteId ); ++ sortCollectionList( refCols, &Collection::remoteId ); ++ break; ++ case Name: ++ sortCollectionList( cols, &Collection::name ); ++ sortCollectionList( refCols, &Collection::name ); ++ break; ++ case None: ++ break; ++ default: ++ Q_ASSERT( false ); ++ } + + for ( int i = 0; i < cols.count(); ++i ) { + const Collection col = cols.at( i ); +@@ -148,11 +169,6 @@ + } + + const Collection refCol = refCols.at( i ); +- if ( col.remoteId() != refCol.remoteId() ) { +- mErrorMsg = QString::fromLatin1( "Collection with remote id '%1' is missing." ).arg( refCol.remoteId() ); +- return false; +- } +- + if ( !compareCollection( col, refCol ) ) + return false; + } +@@ -177,14 +193,13 @@ + + bool XmlOperations::compareCollection(const Collection& _col, const Collection& _refCol) + { +- Q_ASSERT( _col.remoteId() == _refCol.remoteId() ); +- + // normalize + Collection col( normalize( _col ) ); + Collection refCol( normalize( _refCol ) ); + + // compare the two collections +- if ( !compareValue( col, refCol, &Collection::contentMimeTypes, ContentMimeType ) || ++ if ( !compareValue( col, refCol, &Collection::remoteId, RemoteId ) || ++ !compareValue( col, refCol, &Collection::contentMimeTypes, ContentMimeType ) || + !compareValue( col, refCol, &Collection::name, Name ) ) + return false; + +--- trunk/playground/pim/akonaditest/resourcetester/xmloperations.h #942639:942640 +@@ -28,6 +28,10 @@ + #include <QtCore/QObject> + #include <QtCore/QTextStream> + ++#include <boost/bind.hpp> ++#include <algorithm> ++ ++ + /** + Compares a Akonadi collection sub-tree with reference data supplied in an XML file. + */ +@@ -49,6 +53,7 @@ + + Q_DECLARE_FLAGS( CollectionFields, CollectionField ) + ++ void setCollectionKey( CollectionField field ); + void ignoreCollectionField( CollectionField field ); + + public slots: +@@ -59,6 +64,7 @@ + Akonadi::Item getItemByRemoteId(const QString& rid); + Akonadi::Collection getCollectionByRemoteId(const QString& rid); + ++ void setCollectionKey( const QString &fieldName ); + void ignoreCollectionField( const QString &fieldName ); + + bool compare(); +@@ -78,16 +84,25 @@ + template <typename T> bool compareValue( const Akonadi::Collection &col, const Akonadi::Collection &refCol, + T (Akonadi::Collection::*property)() const, + CollectionField propertyType ); ++ template <typename T> bool compareValue( const Akonadi::Collection &col, const Akonadi::Collection &refCol, ++ T (Akonadi::Entity::*property)() const, ++ CollectionField propertyType ); + template <typename T> bool compareValue( const Akonadi::Item& item, const Akonadi::Item& refItem, + T (Akonadi::Item::*property)() const, + const char* propertyName ); + template <typename T> bool compareValue( const T& value, const T& refValue ); + ++ template <typename T> void sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Collection::*property)() const ) const; ++ template <typename T> void sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Entity::*property)() const ) const; ++ + private: + Akonadi::Collection::List mRoots; + Akonadi::XmlDocument mDocument; + QString mErrorMsg; + CollectionFields mCollectionFields; ++ CollectionField mCollectionKey; + }; + + +@@ -109,6 +124,23 @@ + } + + template <typename T> ++bool XmlOperations::compareValue( const Akonadi::Collection& col, const Akonadi::Collection& refCol, ++ T (Akonadi::Entity::*property)() const, ++ CollectionField propertyType ) ++{ ++ if ( mCollectionFields & propertyType ) { ++ const bool result = compareValue<T>( ((col).*(property))(), ((refCol).*(property))() ); ++ if ( !result ) { ++ const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); ++ mErrorMsg.prepend( QString::fromLatin1( "Collection with remote id '%1' differs in property '%2':\n" ) ++ .arg( col.remoteId() ).arg( me.valueToKey( propertyType ) ) ); ++ } ++ return result; ++ } ++ return true; ++} ++ ++template <typename T> + bool XmlOperations::compareValue( const Akonadi::Item& item, const Akonadi::Item& refItem, + T (Akonadi::Item::*property)() const, + const char* propertyName ) +@@ -131,4 +163,18 @@ + return false; + } + ++template <typename T> ++void XmlOperations::sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Collection::*property)() const ) const ++{ ++ std::sort( list.begin(), list.end(), boost::bind( property, _1 ) < boost::bind( property, _2 ) ); ++} ++ ++template <typename T> ++void XmlOperations::sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Entity::*property)() const ) const ++{ ++ std::sort( list.begin(), list.end(), boost::bind( property, _1 ) < boost::bind( property, _2 ) ); ++} ++ + #endif + + + + + Return-Path: <commitfilter@new.kstuff.org> +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 12:55:23 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id EF869E6C77A + for <asok@kdab.net>; Sun, 22 Mar 2009 12:55:22 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 06346-10 for <asok@kdab.net>; + Sun, 22 Mar 2009 12:55:21 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 24127E6C79E + for <asok@kdab.net>; Sun, 22 Mar 2009 12:55:21 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id D175FE6C77A + for <asok@kdab.net>; Sun, 22 Mar 2009 12:55:20 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from <kde-commits-bounces-+commitfilter=new.kstuff.org@kde.org>) + id 1LlMMP-0003EH-9D + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 13:01:02 +0100 +Received: (qmail 14097 invoked by uid 72); 22 Mar 2009 12:00:55 -0000 +Received: (qmail 14075 invoked from network); 22 Mar 2009 12:00:53 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 12:00:51 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 0F54D18E + for <kde-commits@kde.org>; Sun, 22 Mar 2009 13:00:53 +0100 (CET) +Received: (nullmailer pid 17237 invoked by uid 30); + Sun, 22 Mar 2009 12:00:53 -0000 +From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester + trunk/playground/pim/akonaditest/resourcetester/tests +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:00:53 +0000 +Message-Id: <1237723253.005953.17235.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits <kde-commits.kde.org> +List-Unsubscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=unsubscribe> +List-Post: <mailto:kde-commits@kde.org> +List-Help: <mailto:kde-commits-request@kde.org?subject=help> +List-Subscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=subscribe> +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26667 +X-Length: 4226 +Status: RO +X-Status: ORC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942650 by vkrause: + +Add CMake macro to run resource tests. + + + M +20 -0 CMakeLists.txt + A tests/CMakeLists.txt + AM tests/vcardtest-readonly.js tests/vcardtest.js#942640 + AM tests/vcardtest-readonly.xml tests/vcardtest.xml#942640 + + +--- trunk/playground/pim/akonaditest/resourcetester/CMakeLists.txt #942649:942650 +@@ -17,6 +17,26 @@ + + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + ++macro( akonadi_add_resourcetest _testname _script ) ++ if ( ${EXECUTABLE_OUTPUT_PATH} ) ++ set( _exepath ${EXECUTABLE_OUTPUT_PATH} ) ++ else ( ${EXECUTABLE_OUTPUT_PATH} ) ++ set( _exepath ${CMAKE_CURRENT_BINARY_DIR}/.. ) ++ endif ( ${EXECUTABLE_OUTPUT_PATH} ) ++ if (WIN32) ++ set(_resourcetester ${_exepath}/resourcetester.bat) ++ else (WIN32) ++ set(_resourcetester ${_exepath}/resourcetester) ++ endif (WIN32) ++ if (UNIX) ++ set(_resourcetester ${_resourcetester}.shell) ++ endif (UNIX) ++ ++ add_test( ${_testname} ${_resourcetester} -c ${CMAKE_CURRENT_SOURCE_DIR}/${_script} ) ++endmacro( akonadi_add_resourcetest ) ++ ++add_subdirectory( tests ) ++ + set( resourcetester_SRCS + global.cpp + main.cpp +** trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest-readonly.js #property svn:mergeinfo + + +** trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest-readonly.xml #property svn:mergeinfo + + + + + + Return-Path: <commitfilter@new.kstuff.org> +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 13:16:01 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id CA2E8E6C783 + for <asok@kdab.net>; Sun, 22 Mar 2009 13:16:00 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 07855-05 for <asok@kdab.net>; + Sun, 22 Mar 2009 13:15:58 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id ABDFDE6C79B + for <asok@kdab.net>; Sun, 22 Mar 2009 13:15:58 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id 5CD44E6C783 + for <asok@kdab.net>; Sun, 22 Mar 2009 13:15:58 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from <kde-commits-bounces-+commitfilter=new.kstuff.org@kde.org>) + id 1LlMgP-00046D-6T + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 13:21:41 +0100 +Received: (qmail 27078 invoked by uid 72); 22 Mar 2009 12:21:36 -0000 +Received: (qmail 27060 invoked from network); 22 Mar 2009 12:21:34 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 12:21:32 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 0A38E18E + for <kde-commits@kde.org>; Sun, 22 Mar 2009 13:21:34 +0100 (CET) +Received: (nullmailer pid 20237 invoked by uid 30); + Sun, 22 Mar 2009 12:21:34 -0000 +From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:21:34 +0000 +Message-Id: <1237724494.009832.20236.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits <kde-commits.kde.org> +List-Unsubscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=unsubscribe> +List-Post: <mailto:kde-commits@kde.org> +List-Help: <mailto:kde-commits-request@kde.org?subject=help> +List-Subscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=subscribe> +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26668 +X-Length: 4765 +Status: RO +X-Status: ORC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942656 by vkrause: + +- propagate script errors +- make sure the Akonadi server is operational + + + M +5 -0 main.cpp + M +13 -1 script.cpp + M +6 -3 script.h + + +--- trunk/playground/pim/akonaditest/resourcetester/main.cpp #942655:942656 +@@ -21,6 +21,8 @@ + #include "global.h" + #include "test.h" + ++#include <akonadi/control.h> ++ + #include <KApplication> + #include <KAboutData> + #include <KCmdLineArgs> +@@ -71,6 +73,9 @@ + signal( SIGQUIT, sigHandler ); + #endif + ++ if ( !Akonadi::Control::start() ) ++ qFatal( "Unable to start Akonadi!" ); ++ + Script *script = new Script(); + + script->configure(path); +--- trunk/playground/pim/akonaditest/resourcetester/script.cpp #942655:942656 +@@ -16,12 +16,13 @@ + */ + + #include "script.h" ++#include <KDebug> + #include <qcoreapplication.h> + + Script::Script() + { + action = new Kross::Action(this, "ResourceTester"); +- connect( action, SIGNAL(finished(Kross::Action*)), QCoreApplication::instance(), SLOT(quit()) ); ++ connect( action, SIGNAL(finished(Kross::Action*)), SLOT(finished(Kross::Action*)) ); + } + + void Script::configure(const QString &path, QHash<QString, QObject * > hash) +@@ -51,4 +52,15 @@ + action->trigger(); + } + ++void Script::finished(Kross::Action* action) ++{ ++ if ( action->hadError() ) { ++ kError() << action->errorMessage() << action->errorTrace(); ++ QCoreApplication::instance()->exit( 1 ); ++ } else { ++ QCoreApplication::instance()->quit(); ++ } ++} ++ ++ + #include "script.moc" +--- trunk/playground/pim/akonaditest/resourcetester/script.h #942655:942656 +@@ -24,9 +24,6 @@ + class Script : public QObject + { + Q_OBJECT +- private: +- Kross::Action *action; +- + public: + Script(); + void configure(const QString &path, QHash<QString, QObject *> hash); +@@ -35,6 +32,12 @@ + + public slots: + void start(); ++ ++ private slots: ++ void finished( Kross::Action *action ); ++ ++ private: ++ Kross::Action *action; + }; + + #endif + + + + + Return-Path: <commitfilter@new.kstuff.org> +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 13:45:00 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 8C4FFE6C79B + for <asok@kdab.net>; Sun, 22 Mar 2009 13:45:00 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 09703-05 for <asok@kdab.net>; + Sun, 22 Mar 2009 13:45:00 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 163D2E6C7AF + for <asok@kdab.net>; Sun, 22 Mar 2009 13:45:00 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id 31945E6C79E + for <asok@kdab.net>; Sun, 22 Mar 2009 13:44:59 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from <kde-commits-bounces-+commitfilter=new.kstuff.org@kde.org>) + id 1LlN8R-0005Jr-VE + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 13:50:40 +0100 +Received: (qmail 7667 invoked by uid 72); 22 Mar 2009 12:50:33 -0000 +Received: (qmail 7658 invoked from network); 22 Mar 2009 12:50:31 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 12:50:29 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 93E9F18E + for <kde-commits@kde.org>; Sun, 22 Mar 2009 13:50:30 +0100 (CET) +Received: (nullmailer pid 25707 invoked by uid 30); + Sun, 22 Mar 2009 12:50:30 -0000 +From: Volker Krause <vkrause@kde.org> +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits <kde-commits.kde.org> +List-Unsubscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=unsubscribe> +List-Post: <mailto:kde-commits@kde.org> +List-Help: <mailto:kde-commits-request@kde.org?subject=help> +List-Subscribe: <https://mail.kde.org/mailman/listinfo/kde-commits>, + <mailto:kde-commits-request@kde.org?subject=subscribe> +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26669 +X-Length: 5694 +Status: RO +X-Status: RC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + +--- trunk/playground/pim/akonaditest/resourcetester/resourcesynchronizationjob.cpp #942676:942677 +@@ -18,12 +18,14 @@ + #include "resourcesynchronizationjob.h" + + #include <akonadi/agentinstance.h> ++#include <akonadi/agentmanager.h> + + #include <KDebug> + #include <KLocale> + + #include <QDBusConnection> + #include <QDBusInterface> ++#include <QTimer> + + namespace Akonadi + { +@@ -31,15 +33,31 @@ + class ResourceSynchronizationJobPrivate + { + public: ++ ResourceSynchronizationJobPrivate() : ++ interface( 0 ), ++ safetyTimer( 0 ), ++ timeoutCount( 0 ) ++ {} ++ + AgentInstance instance; + QDBusInterface* interface; ++ QTimer* safetyTimer; ++ int timeoutCount; ++ static int timeoutCountLimit; + }; + ++int ResourceSynchronizationJobPrivate::timeoutCountLimit = 60; ++ + ResourceSynchronizationJob::ResourceSynchronizationJob(const AgentInstance& instance, QObject* parent) : + KJob( parent ), + d( new ResourceSynchronizationJobPrivate ) + { + d->instance = instance; ++ d->safetyTimer = new QTimer( this ); ++ connect( d->safetyTimer, SIGNAL(timeout()), SLOT(slotTimeout()) ); ++ d->safetyTimer->setInterval( 10 * 1000 ); ++ d->safetyTimer->setSingleShot( false ); ++ d->safetyTimer->start(); + } + + ResourceSynchronizationJob::~ResourceSynchronizationJob() +@@ -72,9 +90,31 @@ + + void ResourceSynchronizationJob::slotSynchronized() + { ++ disconnect( d->interface, SIGNAL(synchronized()), this, SLOT(slotSynchronized()) ); ++ d->safetyTimer->stop(); + emitResult(); + } + ++void ResourceSynchronizationJob::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(); ++ return; ++ } ++ ++ if ( d->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(); ++ } + } + ++} ++ + #include "resourcesynchronizationjob.moc" +--- trunk/playground/pim/akonaditest/resourcetester/resourcesynchronizationjob.h #942676:942677 +@@ -27,7 +27,6 @@ + + /** + Synchronizes a given resource. +- @todo Add safety timeouts. + */ + class ResourceSynchronizationJob : public KJob + { +@@ -48,6 +47,7 @@ + + private slots: + void slotSynchronized(); ++ void slotTimeout(); + + private: + ResourceSynchronizationJobPrivate* const d; + + + + diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/cur/1237726881.6570.rfoxg!2,S b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/cur/1237726881.6570.rfoxg!2,S new file mode 100644 index 00000000..382f2ad8 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/cur/1237726881.6570.rfoxg!2,S @@ -0,0 +1,282 @@ +Return-Path: +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 12:10:48 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 4BDF8E6C790 + for ; Sun, 22 Mar 2009 12:10:48 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 03694-02 for ; + Sun, 22 Mar 2009 12:10:45 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id B289FE6C79B + for ; Sun, 22 Mar 2009 12:10:45 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id 3B465E6C790 + for ; Sun, 22 Mar 2009 12:10:45 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from ) + id 1LlLfE-0001OT-K7 + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 12:16:25 +0100 +Received: (qmail 23006 invoked by uid 72); 22 Mar 2009 11:16:19 -0000 +Received: (qmail 22986 invoked from network); 22 Mar 2009 11:16:14 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 11:16:11 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 85EE718E + for ; Sun, 22 Mar 2009 12:16:12 +0100 (CET) +Received: (nullmailer pid 13467 invoked by uid 30); + Sun, 22 Mar 2009 11:16:12 -0000 +From: Volker Krause +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester + trunk/playground/pim/akonaditest/resourcetester/tests +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 11:16:12 +0000 +Message-Id: <1237720572.493438.13466.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits +List-Unsubscribe: , + +List-Post: +List-Help: +List-Subscribe: , + +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26666 +X-Length: 11240 +Status: RO +X-Status: ORC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942640 by vkrause: + +Allow to specifiy the collection property used to identify corresponding +collections. + + + M +2 -0 tests/vcardtest.js + M +1 -1 tests/vcardtest.xml + M +29 -14 xmloperations.cpp + M +46 -0 xmloperations.h + + +--- trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest.js #942639:942640 +@@ -4,6 +4,8 @@ + + XmlOperations.setXmlFile( "vcardtest.xml" ); + XmlOperations.setRootCollections( Resource.identifier() ); ++XmlOperations.setCollectionKey( "None" ); // we only expect one collection + XmlOperations.ignoreCollectionField( "Name" ); // name is the resource identifier and thus unpredictable ++XmlOperations.ignoreCollectionField( "RemoteId" ); // remote id is the absolute path + XmlOperations.assertEqual(); + +--- trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest.xml #942639:942640 +@@ -1,5 +1,5 @@ + +- ++ + wcdW + ("vcardtest.vcf" "office-address-book") + +--- trunk/playground/pim/akonaditest/resourcetester/xmloperations.cpp #942639:942640 +@@ -31,9 +31,6 @@ + #include + #include + +-#include +-#include +- + using namespace Akonadi; + + template QTextStream& operator<<( QTextStream &s, const QSet &set ) +@@ -53,7 +50,8 @@ + + XmlOperations::XmlOperations(QObject* parent) : + QObject( parent ), +- mCollectionFields( 0xFF ) ++ mCollectionFields( 0xFF ), ++ mCollectionKey( RemoteId ) + { + } + +@@ -99,6 +97,17 @@ + return mErrorMsg; + } + ++void XmlOperations::setCollectionKey(XmlOperations::CollectionField field) ++{ ++ mCollectionKey = field; ++} ++ ++void XmlOperations::setCollectionKey(const QString& fieldName) ++{ ++ const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); ++ setCollectionKey( static_cast( me.keyToValue( fieldName.toLatin1() ) ) ); ++} ++ + void XmlOperations::ignoreCollectionField(XmlOperations::CollectionField field) + { + mCollectionFields = mCollectionFields & ~field; +@@ -137,8 +146,20 @@ + { + Collection::List cols( _cols ); + Collection::List refCols( _refCols ); +- std::sort( cols.begin(), cols.end(), boost::bind( &Collection::remoteId, _1 ) < boost::bind( &Collection::remoteId, _2 ) ); +- std::sort( refCols.begin(), refCols.end(), boost::bind( &Collection::remoteId, _1 ) < boost::bind( &Collection::remoteId, _2 ) ); ++ switch ( mCollectionKey ) { ++ case RemoteId: ++ sortCollectionList( cols, &Collection::remoteId ); ++ sortCollectionList( refCols, &Collection::remoteId ); ++ break; ++ case Name: ++ sortCollectionList( cols, &Collection::name ); ++ sortCollectionList( refCols, &Collection::name ); ++ break; ++ case None: ++ break; ++ default: ++ Q_ASSERT( false ); ++ } + + for ( int i = 0; i < cols.count(); ++i ) { + const Collection col = cols.at( i ); +@@ -148,11 +169,6 @@ + } + + const Collection refCol = refCols.at( i ); +- if ( col.remoteId() != refCol.remoteId() ) { +- mErrorMsg = QString::fromLatin1( "Collection with remote id '%1' is missing." ).arg( refCol.remoteId() ); +- return false; +- } +- + if ( !compareCollection( col, refCol ) ) + return false; + } +@@ -177,14 +193,13 @@ + + bool XmlOperations::compareCollection(const Collection& _col, const Collection& _refCol) + { +- Q_ASSERT( _col.remoteId() == _refCol.remoteId() ); +- + // normalize + Collection col( normalize( _col ) ); + Collection refCol( normalize( _refCol ) ); + + // compare the two collections +- if ( !compareValue( col, refCol, &Collection::contentMimeTypes, ContentMimeType ) || ++ if ( !compareValue( col, refCol, &Collection::remoteId, RemoteId ) || ++ !compareValue( col, refCol, &Collection::contentMimeTypes, ContentMimeType ) || + !compareValue( col, refCol, &Collection::name, Name ) ) + return false; + +--- trunk/playground/pim/akonaditest/resourcetester/xmloperations.h #942639:942640 +@@ -28,6 +28,10 @@ + #include + #include + ++#include ++#include ++ ++ + /** + Compares a Akonadi collection sub-tree with reference data supplied in an XML file. + */ +@@ -49,6 +53,7 @@ + + Q_DECLARE_FLAGS( CollectionFields, CollectionField ) + ++ void setCollectionKey( CollectionField field ); + void ignoreCollectionField( CollectionField field ); + + public slots: +@@ -59,6 +64,7 @@ + Akonadi::Item getItemByRemoteId(const QString& rid); + Akonadi::Collection getCollectionByRemoteId(const QString& rid); + ++ void setCollectionKey( const QString &fieldName ); + void ignoreCollectionField( const QString &fieldName ); + + bool compare(); +@@ -78,16 +84,25 @@ + template bool compareValue( const Akonadi::Collection &col, const Akonadi::Collection &refCol, + T (Akonadi::Collection::*property)() const, + CollectionField propertyType ); ++ template bool compareValue( const Akonadi::Collection &col, const Akonadi::Collection &refCol, ++ T (Akonadi::Entity::*property)() const, ++ CollectionField propertyType ); + template bool compareValue( const Akonadi::Item& item, const Akonadi::Item& refItem, + T (Akonadi::Item::*property)() const, + const char* propertyName ); + template bool compareValue( const T& value, const T& refValue ); + ++ template void sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Collection::*property)() const ) const; ++ template void sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Entity::*property)() const ) const; ++ + private: + Akonadi::Collection::List mRoots; + Akonadi::XmlDocument mDocument; + QString mErrorMsg; + CollectionFields mCollectionFields; ++ CollectionField mCollectionKey; + }; + + +@@ -109,6 +124,23 @@ + } + + template ++bool XmlOperations::compareValue( const Akonadi::Collection& col, const Akonadi::Collection& refCol, ++ T (Akonadi::Entity::*property)() const, ++ CollectionField propertyType ) ++{ ++ if ( mCollectionFields & propertyType ) { ++ const bool result = compareValue( ((col).*(property))(), ((refCol).*(property))() ); ++ if ( !result ) { ++ const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); ++ mErrorMsg.prepend( QString::fromLatin1( "Collection with remote id '%1' differs in property '%2':\n" ) ++ .arg( col.remoteId() ).arg( me.valueToKey( propertyType ) ) ); ++ } ++ return result; ++ } ++ return true; ++} ++ ++template + bool XmlOperations::compareValue( const Akonadi::Item& item, const Akonadi::Item& refItem, + T (Akonadi::Item::*property)() const, + const char* propertyName ) +@@ -131,4 +163,18 @@ + return false; + } + ++template ++void XmlOperations::sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Collection::*property)() const ) const ++{ ++ std::sort( list.begin(), list.end(), boost::bind( property, _1 ) < boost::bind( property, _2 ) ); ++} ++ ++template ++void XmlOperations::sortCollectionList( Akonadi::Collection::List &list, ++ T ( Akonadi::Entity::*property)() const ) const ++{ ++ std::sort( list.begin(), list.end(), boost::bind( property, _1 ) < boost::bind( property, _2 ) ); ++} ++ + #endif diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/new/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/tmp/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/.child1.directory/grandchild/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726858.6570.dtdn4!2,S b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726858.6570.dtdn4!2,S new file mode 100644 index 00000000..b9ee984f --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726858.6570.dtdn4!2,S @@ -0,0 +1,107 @@ +Return-Path: +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 12:55:23 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id EF869E6C77A + for ; Sun, 22 Mar 2009 12:55:22 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 06346-10 for ; + Sun, 22 Mar 2009 12:55:21 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 24127E6C79E + for ; Sun, 22 Mar 2009 12:55:21 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id D175FE6C77A + for ; Sun, 22 Mar 2009 12:55:20 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from ) + id 1LlMMP-0003EH-9D + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 13:01:02 +0100 +Received: (qmail 14097 invoked by uid 72); 22 Mar 2009 12:00:55 -0000 +Received: (qmail 14075 invoked from network); 22 Mar 2009 12:00:53 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 12:00:51 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 0F54D18E + for ; Sun, 22 Mar 2009 13:00:53 +0100 (CET) +Received: (nullmailer pid 17237 invoked by uid 30); + Sun, 22 Mar 2009 12:00:53 -0000 +From: Volker Krause +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester + trunk/playground/pim/akonaditest/resourcetester/tests +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:00:53 +0000 +Message-Id: <1237723253.005953.17235.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits +List-Unsubscribe: , + +List-Post: +List-Help: +List-Subscribe: , + +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26667 +X-Length: 4226 +Status: RO +X-Status: ORC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942650 by vkrause: + +Add CMake macro to run resource tests. + + + M +20 -0 CMakeLists.txt + A tests/CMakeLists.txt + AM tests/vcardtest-readonly.js tests/vcardtest.js#942640 + AM tests/vcardtest-readonly.xml tests/vcardtest.xml#942640 + + +--- trunk/playground/pim/akonaditest/resourcetester/CMakeLists.txt #942649:942650 +@@ -17,6 +17,26 @@ + + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + ++macro( akonadi_add_resourcetest _testname _script ) ++ if ( ${EXECUTABLE_OUTPUT_PATH} ) ++ set( _exepath ${EXECUTABLE_OUTPUT_PATH} ) ++ else ( ${EXECUTABLE_OUTPUT_PATH} ) ++ set( _exepath ${CMAKE_CURRENT_BINARY_DIR}/.. ) ++ endif ( ${EXECUTABLE_OUTPUT_PATH} ) ++ if (WIN32) ++ set(_resourcetester ${_exepath}/resourcetester.bat) ++ else (WIN32) ++ set(_resourcetester ${_exepath}/resourcetester) ++ endif (WIN32) ++ if (UNIX) ++ set(_resourcetester ${_resourcetester}.shell) ++ endif (UNIX) ++ ++ add_test( ${_testname} ${_resourcetester} -c ${CMAKE_CURRENT_SOURCE_DIR}/${_script} ) ++endmacro( akonadi_add_resourcetest ) ++ ++add_subdirectory( tests ) ++ + set( resourcetester_SRCS + global.cpp + main.cpp +** trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest-readonly.js #property svn:mergeinfo + + +** trunk/playground/pim/akonaditest/resourcetester/tests/vcardtest-readonly.xml #property svn:mergeinfo + + diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726875.6570.R4KOW!2,S b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726875.6570.R4KOW!2,S new file mode 100644 index 00000000..447a2676 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/cur/1237726875.6570.R4KOW!2,S @@ -0,0 +1,150 @@ +Return-Path: +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 13:16:01 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id CA2E8E6C783 + for ; Sun, 22 Mar 2009 13:16:00 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 07855-05 for ; + Sun, 22 Mar 2009 13:15:58 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id ABDFDE6C79B + for ; Sun, 22 Mar 2009 13:15:58 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id 5CD44E6C783 + for ; Sun, 22 Mar 2009 13:15:58 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from ) + id 1LlMgP-00046D-6T + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 13:21:41 +0100 +Received: (qmail 27078 invoked by uid 72); 22 Mar 2009 12:21:36 -0000 +Received: (qmail 27060 invoked from network); 22 Mar 2009 12:21:34 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 12:21:32 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 0A38E18E + for ; Sun, 22 Mar 2009 13:21:34 +0100 (CET) +Received: (nullmailer pid 20237 invoked by uid 30); + Sun, 22 Mar 2009 12:21:34 -0000 +From: Volker Krause +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:21:34 +0000 +Message-Id: <1237724494.009832.20236.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits +List-Unsubscribe: , + +List-Post: +List-Help: +List-Subscribe: , + +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26668 +X-Length: 4765 +Status: RO +X-Status: ORC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942656 by vkrause: + +- propagate script errors +- make sure the Akonadi server is operational + + + M +5 -0 main.cpp + M +13 -1 script.cpp + M +6 -3 script.h + + +--- trunk/playground/pim/akonaditest/resourcetester/main.cpp #942655:942656 +@@ -21,6 +21,8 @@ + #include "global.h" + #include "test.h" + ++#include ++ + #include + #include + #include +@@ -71,6 +73,9 @@ + signal( SIGQUIT, sigHandler ); + #endif + ++ if ( !Akonadi::Control::start() ) ++ qFatal( "Unable to start Akonadi!" ); ++ + Script *script = new Script(); + + script->configure(path); +--- trunk/playground/pim/akonaditest/resourcetester/script.cpp #942655:942656 +@@ -16,12 +16,13 @@ + */ + + #include "script.h" ++#include + #include + + Script::Script() + { + action = new Kross::Action(this, "ResourceTester"); +- connect( action, SIGNAL(finished(Kross::Action*)), QCoreApplication::instance(), SLOT(quit()) ); ++ connect( action, SIGNAL(finished(Kross::Action*)), SLOT(finished(Kross::Action*)) ); + } + + void Script::configure(const QString &path, QHash hash) +@@ -51,4 +52,15 @@ + action->trigger(); + } + ++void Script::finished(Kross::Action* action) ++{ ++ if ( action->hadError() ) { ++ kError() << action->errorMessage() << action->errorTrace(); ++ QCoreApplication::instance()->exit( 1 ); ++ } else { ++ QCoreApplication::instance()->quit(); ++ } ++} ++ ++ + #include "script.moc" +--- trunk/playground/pim/akonaditest/resourcetester/script.h #942655:942656 +@@ -24,9 +24,6 @@ + class Script : public QObject + { + Q_OBJECT +- private: +- Kross::Action *action; +- + public: + Script(); + void configure(const QString &path, QHash hash); +@@ -35,6 +32,12 @@ + + public slots: + void start(); ++ ++ private slots: ++ void finished( Kross::Action *action ); ++ ++ private: ++ Kross::Action *action; + }; + + #endif diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/new/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/tmp/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child1/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/cur/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/cur/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/cur/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/new/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/tmp/.keep b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/.root.directory/child2/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/root/cur/1237726845.6570.BejQg!2,S b/kdepim-runtime/resources/maildir/tests/maildir/root/cur/1237726845.6570.BejQg!2,S new file mode 100644 index 00000000..c9b1d83e --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/root/cur/1237726845.6570.BejQg!2,S @@ -0,0 +1,171 @@ +Return-Path: +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA; + Sun, 22 Mar 2009 13:45:00 +0100 +X-Sieve: CMU Sieve 2.2 +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 8C4FFE6C79B + for ; Sun, 22 Mar 2009 13:45:00 +0100 (CET) +Received: from smykowski.kdab.net ([127.0.0.1]) + by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 09703-05 for ; + Sun, 22 Mar 2009 13:45:00 +0100 (CET) +Received: from localhost (localhost [127.0.0.1]) + by smykowski.kdab.net (Postfix) with ESMTP id 163D2E6C7AF + for ; Sun, 22 Mar 2009 13:45:00 +0100 (CET) +Received: from kdeget.osuosl.org (kdeget.osuosl.org [140.211.166.77]) + by smykowski.kdab.net (Postfix) with ESMTP id 31945E6C79E + for ; Sun, 22 Mar 2009 13:44:59 +0100 (CET) +Received: from ktown.kde.org ([131.246.120.250]) + by kdeget.osuosl.org with smtp (Exim 4.63) + (envelope-from ) + id 1LlN8R-0005Jr-VE + for commitfilter@new.kstuff.org; Sun, 22 Mar 2009 13:50:40 +0100 +Received: (qmail 7667 invoked by uid 72); 22 Mar 2009 12:50:33 -0000 +Received: (qmail 7658 invoked from network); 22 Mar 2009 12:50:31 -0000 +Received: from unknown (HELO office.kde.org) (195.135.221.67) + by ktown.kde.org with SMTP; 22 Mar 2009 12:50:29 -0000 +Received: from svn.kde.org (localhost [127.0.0.1]) + by office.kde.org (Postfix) with SMTP id 93E9F18E + for ; Sun, 22 Mar 2009 13:50:30 +0100 (CET) +Received: (nullmailer pid 25707 invoked by uid 30); + Sun, 22 Mar 2009 12:50:30 -0000 +From: Volker Krause +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +X-Commit-Directories: (0) trunk/playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> +X-BeenThere: kde-commits@kde.org +X-Mailman-Version: 2.1.9 +Precedence: list +Reply-To: kde-commits@kde.org +List-Id: Notification of KDE commits +List-Unsubscribe: , + +List-Post: +List-Help: +List-Subscribe: , + +X-Virus-Scanned: by amavisd-new at kdab.net +X-Kolab-Scheduling-Message: FALSE +X-UID: 26669 +X-Length: 5694 +Status: RO +X-Status: RC +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h + + +--- trunk/playground/pim/akonaditest/resourcetester/resourcesynchronizationjob.cpp #942676:942677 +@@ -18,12 +18,14 @@ + #include "resourcesynchronizationjob.h" + + #include ++#include + + #include + #include + + #include + #include ++#include + + namespace Akonadi + { +@@ -31,15 +33,31 @@ + class ResourceSynchronizationJobPrivate + { + public: ++ ResourceSynchronizationJobPrivate() : ++ interface( 0 ), ++ safetyTimer( 0 ), ++ timeoutCount( 0 ) ++ {} ++ + AgentInstance instance; + QDBusInterface* interface; ++ QTimer* safetyTimer; ++ int timeoutCount; ++ static int timeoutCountLimit; + }; + ++int ResourceSynchronizationJobPrivate::timeoutCountLimit = 60; ++ + ResourceSynchronizationJob::ResourceSynchronizationJob(const AgentInstance& instance, QObject* parent) : + KJob( parent ), + d( new ResourceSynchronizationJobPrivate ) + { + d->instance = instance; ++ d->safetyTimer = new QTimer( this ); ++ connect( d->safetyTimer, SIGNAL(timeout()), SLOT(slotTimeout()) ); ++ d->safetyTimer->setInterval( 10 * 1000 ); ++ d->safetyTimer->setSingleShot( false ); ++ d->safetyTimer->start(); + } + + ResourceSynchronizationJob::~ResourceSynchronizationJob() +@@ -72,9 +90,31 @@ + + void ResourceSynchronizationJob::slotSynchronized() + { ++ disconnect( d->interface, SIGNAL(synchronized()), this, SLOT(slotSynchronized()) ); ++ d->safetyTimer->stop(); + emitResult(); + } + ++void ResourceSynchronizationJob::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(); ++ return; ++ } ++ ++ if ( d->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(); ++ } + } + ++} ++ + #include "resourcesynchronizationjob.moc" +--- trunk/playground/pim/akonaditest/resourcetester/resourcesynchronizationjob.h #942676:942677 +@@ -27,7 +27,6 @@ + + /** + Synchronizes a given resource. +- @todo Add safety timeouts. + */ + class ResourceSynchronizationJob : public KJob + { +@@ -48,6 +47,7 @@ + + private slots: + void slotSynchronized(); ++ void slotTimeout(); + + private: + ResourceSynchronizationJobPrivate* const d; diff --git a/kdepim-runtime/resources/maildir/tests/maildir/root/new/.keep b/kdepim-runtime/resources/maildir/tests/maildir/root/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/root/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/maildir/root/tmp/.keep b/kdepim-runtime/resources/maildir/tests/maildir/root/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/maildir/root/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/tests/synctest.cpp b/kdepim-runtime/resources/maildir/tests/synctest.cpp new file mode 100644 index 00000000..1fffe3c8 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/synctest.cpp @@ -0,0 +1,62 @@ +/* + Copyright 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 "synctest.h" + +#include +#include + +#include + +#include +#include +#include +#include + +#define TIMES 100 // How many times to sync. +#define TIMEOUT 10 // How many seconds to wait before declaring the resource dead. + +using namespace Akonadi; + +void SyncTest::initTestCase() +{ + QVERIFY( Control::start() ); + QTest::qWait( 1000 ); +} + +void SyncTest::testSync() +{ + AgentInstance instance = AgentManager::self()->instance( "akonadi_maildir_resource_0" ); + QVERIFY( instance.isValid() ); + + for ( int i = 0; i < 100; i++ ) { + QDBusInterface *interface = new QDBusInterface( + QString::fromLatin1( "org.freedesktop.Akonadi.Resource.%1" ).arg( instance.identifier() ), + "/", "org.freedesktop.Akonadi.Resource", QDBusConnection::sessionBus(), this ); + QVERIFY( interface->isValid() ); + QTime t; + t.start(); + instance.synchronize(); + QVERIFY( QTest::kWaitForSignal( interface, SIGNAL(synchronized()), TIMEOUT * 1000 ) ); + kDebug() << "Sync attempt" << i << "in" << t.elapsed() << "ms."; + } +} + +QTEST_AKONADIMAIN( SyncTest, NoGUI ) + diff --git a/kdepim-runtime/resources/maildir/tests/synctest.h b/kdepim-runtime/resources/maildir/tests/synctest.h new file mode 100644 index 00000000..4019ff52 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/synctest.h @@ -0,0 +1,44 @@ +/* + Copyright 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 SYNCTEST_H +#define SYNCTEST_H + +#include + +#include + +/** + This syncs the resource again and again, watching out for "lost" + synchronized() signals. + */ +class SyncTest : public QObject +{ + Q_OBJECT + + private Q_SLOTS: + void initTestCase(); + void testSync(); + + private: + +}; + + +#endif diff --git a/kdepim-runtime/resources/maildir/tests/testmail.mbox b/kdepim-runtime/resources/maildir/tests/testmail.mbox new file mode 100644 index 00000000..f14d60de --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/testmail.mbox @@ -0,0 +1,19 @@ +From: Volker Krause +To: kde-commits@kde.org +Subject: playground/pim/akonaditest/resourcetester +MIME-Version: 1.0 +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 8bit +Date: Sun, 22 Mar 2009 12:50:30 +0000 +Message-Id: <1237726230.394911.25706.nullmailer@svn.kde.org> + +SVN commit 942677 by vkrause: + +Add a safety timeout in case we do not receive the synchronized() signal +or the resource hangs during syncing. The first seems to happen randomly +if syncing is extremely fast. + + + M +40 -0 resourcesynchronizationjob.cpp + M +1 -1 resourcesynchronizationjob.h diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/config.xml b/kdepim-runtime/resources/maildir/tests/unittestenv/config.xml new file mode 100644 index 00000000..0995c4b9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/config.xml @@ -0,0 +1,6 @@ + + kdehome + xdgconfig + xdglocal + akonadi_maildir_resource + diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc new file mode 100644 index 00000000..1cac492a --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc @@ -0,0 +1,3 @@ +[ProcessedDefaults] +defaultaddressbook=done +defaultcalendar=done diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi_maildir_resource_0rc b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi_maildir_resource_0rc new file mode 100644 index 00000000..f1a0fd15 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/akonadi_maildir_resource_0rc @@ -0,0 +1,2 @@ +[General] +Path[$e]=$HOME/.local/share/mail diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdebugrc b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdebugrc new file mode 100644 index 00000000..32317f74 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdebugrc @@ -0,0 +1,110 @@ +[0] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5250] +InfoOutput=2 + +[5251] +InfoOutput=2 + +[5252] +InfoOutput=2 + +[5253] +InfoOutput=2 + +[5254] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5255] +InfoOutput=2 + +[5256] +InfoOutput=2 + +[5257] +InfoOutput=2 + +[5258] +InfoOutput=2 + +[5259] +InfoOutput=2 + +[5260] +InfoOutput=2 + +[5261] +InfoOutput=2 + +[5262] +InfoOutput=2 + +[5263] +InfoOutput=2 + +[5264] +InfoOutput=2 + +[5265] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5266] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5295] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[5324] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[7129] +InfoOutput=2 diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdedrc b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdedrc new file mode 100644 index 00000000..41d17814 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kdedrc @@ -0,0 +1,3 @@ +[General] +CheckSycoca=false +CheckFileStamps=false diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kwalletrc b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kwalletrc new file mode 100644 index 00000000..8ba29ca1 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/kwalletrc @@ -0,0 +1,2 @@ +[Wallet] +Enabled=false diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/qttestrc b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/qttestrc new file mode 100644 index 00000000..2e2f28ea --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/share/config/qttestrc @@ -0,0 +1,2 @@ +[Notification Messages] +WalletMigrate=false diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/testdata.xml b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/testdata.xml new file mode 100644 index 00000000..8cf871b9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/kdehome/testdata.xml @@ -0,0 +1,4 @@ + + + + diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc b/kdepim-runtime/resources/maildir/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc new file mode 100644 index 00000000..7f738ce2 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc @@ -0,0 +1,4 @@ +[%General] + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/resources/maildir/tests/unittestenv/xdglocal/.keep b/kdepim-runtime/resources/maildir/tests/unittestenv/xdglocal/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/maildir/tests/unittestenv/xdglocal/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/maildir/wizard/CMakeLists.txt b/kdepim-runtime/resources/maildir/wizard/CMakeLists.txt new file mode 100644 index 00000000..c8af4324 --- /dev/null +++ b/kdepim-runtime/resources/maildir/wizard/CMakeLists.txt @@ -0,0 +1,2 @@ + +install ( FILES maildirwizard.desktop maildirwizard.es maildirwizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/maildir ) diff --git a/kdepim-runtime/resources/maildir/wizard/Messages.sh b/kdepim-runtime/resources/maildir/wizard/Messages.sh new file mode 100644 index 00000000..138740ca --- /dev/null +++ b/kdepim-runtime/resources/maildir/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_maildir.pot +$XGETTEXT -kqsTr *.es -j -o $podir/accountwizard_maildir.pot diff --git a/kdepim-runtime/resources/maildir/wizard/maildirwizard.desktop b/kdepim-runtime/resources/maildir/wizard/maildirwizard.desktop new file mode 100644 index 00000000..4cf9a9ae --- /dev/null +++ b/kdepim-runtime/resources/maildir/wizard/maildirwizard.desktop @@ -0,0 +1,105 @@ +[Desktop Entry] +Name=Maildir +Name[ar]=Maildir +Name[bg]=Maildir +Name[bs]=Maildir +Name[ca]=Maildir +Name[ca@valencia]=Maildir +Name[cs]=Maildir +Name[da]=Maildir +Name[de]=Maildir +Name[el]=Maildir +Name[en_GB]=Maildir +Name[eo]=Maildir +Name[es]=Maildir +Name[et]=Maildir +Name[fi]=Maildir +Name[fr]=Maildir +Name[ga]=Maildir +Name[gl]=Maildir +Name[hu]=Maildir +Name[ia]=Maildir +Name[it]=Maildir +Name[ja]=Maildir +Name[kk]=Maildir +Name[km]=ážážâ€‹ážŸáŸ†áž”áž»ážáŸ’ážš +Name[ko]=Maildir +Name[lt]=Maildir +Name[lv]=Maildir +Name[nb]=Maildir +Name[nds]=Nettpostorner +Name[nl]=Maildir +Name[nn]=Maildir +Name[pa]=Maildir +Name[pl]=Maildir +Name[pt]=Maildir +Name[pt_BR]=Maildir +Name[ro]=Maildir +Name[ru]=Maildir +Name[sk]=Maildir +Name[sl]=MailDir +Name[sq]=Maildir +Name[sr]=Мејлдир +Name[sr@ijekavian]=Мејлдир +Name[sr@ijekavianlatin]=Maildir +Name[sr@latin]=Maildir +Name[sv]=Maildir +Name[tr]=Maildir +Name[uk]=Maildir +Name[x-test]=xxMaildirxx +Name[zh_CN]=邮件目录 +Name[zh_TW]=Maildir +Icon=message-rfc822 +Comment=Maildir account +Comment[bs]=Maildir nalog +Comment[ca]=Compte Maildir +Comment[ca@valencia]=Compte Maildir +Comment[cs]=Maildir úÄet +Comment[da]=Maildir-konto +Comment[de]=Maildir-Zugang +Comment[el]=ΛογαÏιασμός Maildir +Comment[en_GB]=Maildir account +Comment[es]=Cuenta de Maildir +Comment[et]=Maildir-konto +Comment[fi]=Maildir-tili +Comment[fr]=Compte Maildir +Comment[ga]=Cuntas maildir +Comment[gl]=Conta de Maildir +Comment[hu]=Maildir azonosító +Comment[ia]=Conto de Maildir +Comment[it]=Account Maildir +Comment[ja]=Maildir アカウント +Comment[kk]=Maildir тіркелгіÑÑ– +Comment[km]=គណនី Maildir +Comment[ko]=Maildir 계정 +Comment[lt]=Maildir paskyra +Comment[lv]=Maildir konts +Comment[nb]=Maildir-konto +Comment[nds]=Nettpostkonto +Comment[nl]=Maildir-account +Comment[nn]=Maildir-konto +Comment[pa]=Maildir ਅਕਾਊਂਟ +Comment[pl]=Konto Maildir +Comment[pt]=Conta Maildir +Comment[pt_BR]=Conta Maildir +Comment[ro]=Cont Maildir +Comment[ru]=Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Maildir +Comment[sk]=ÚÄet poÅ¡tového adresára +Comment[sl]=RaÄun MailDir +Comment[sr]=Мејлдир налог +Comment[sr@ijekavian]=Мејлдир налог +Comment[sr@ijekavianlatin]=Maildir nalog +Comment[sr@latin]=Maildir nalog +Comment[sv]=Maildir-konto +Comment[tr]=Maildir hesabı +Comment[uk]=Обліковий Ð·Ð°Ð¿Ð¸Ñ Maildir +Comment[x-test]=xxMaildir accountxx +Comment[zh_CN]=Maildir 账户 +Comment[zh_TW]=Maildir 帳號 + +[Wizard] +Type=message/rfc822 +Script=maildirwizard.es + +[Translate] +Filename=accountwizard_maildir diff --git a/kdepim-runtime/resources/maildir/wizard/maildirwizard.es b/kdepim-runtime/resources/maildir/wizard/maildirwizard.es new file mode 100644 index 00000000..35ab3859 --- /dev/null +++ b/kdepim-runtime/resources/maildir/wizard/maildirwizard.es @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009 Montel Laurent + + 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. +*/ + +var page = Dialog.addPage( "maildirwizard.ui", qsTr("Personal Settings") ); + +function validateInput() +{ + if ( page.widget().maildirPath.text == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var maildirRes = SetupManager.createResource( "akonadi_maildir_resource" ); + maildirRes.setOption( "Path", page.widget().maildirPath.text ); + + SetupManager.execute(); +} + +page.widget().maildirPath.textChanged.connect( validateInput ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/maildir/wizard/maildirwizard.ui b/kdepim-runtime/resources/maildir/wizard/maildirwizard.ui new file mode 100644 index 00000000..c20efca8 --- /dev/null +++ b/kdepim-runtime/resources/maildir/wizard/maildirwizard.ui @@ -0,0 +1,56 @@ + + + maildirWizard + + + + 0 + 0 + 400 + 300 + + + + + + + + + URL: + + + + + + + KFile::Directory|KFile::ExistingOnly + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/mailtransport_dummy/CMakeLists.txt b/kdepim-runtime/resources/mailtransport_dummy/CMakeLists.txt new file mode 100644 index 00000000..c2dcf9ef --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/CMakeLists.txt @@ -0,0 +1,22 @@ +set( mtdummyresource_SRCS + configdialog.cpp + mtdummyresource.cpp +) + +# mailtransport debug area +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5324) + +install( FILES mtdummyresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(mtdummyresource_SRCS settings.ui) +kde4_add_kcfg_files(mtdummyresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/mtdummyresource.kcfg org.kde.Akonadi.MailTransportDummy.Settings) +qt4_add_dbus_adaptor(mtdummyresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.MailTransportDummy.Settings.xml settings.h Settings +) + +kde4_add_executable(akonadi_mailtransport_dummy_resource ${mtdummyresource_SRCS}) + +target_link_libraries(akonadi_mailtransport_dummy_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS}) + +install(TARGETS akonadi_mailtransport_dummy_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/mailtransport_dummy/Messages.sh b/kdepim-runtime/resources/mailtransport_dummy/Messages.sh new file mode 100644 index 00000000..bb5f4085 --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_mailtransport_resource.pot diff --git a/kdepim-runtime/resources/mailtransport_dummy/configdialog.cpp b/kdepim-runtime/resources/mailtransport_dummy/configdialog.cpp new file mode 100644 index 00000000..1fa164f9 --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/configdialog.cpp @@ -0,0 +1,56 @@ +/* + Copyright 2008 Ingo Klöcker + + 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 "configdialog.h" +#include "settings.h" + +#include +#include + +using namespace Akonadi; + +ConfigDialog::ConfigDialog(QWidget * parent) : + KDialog( parent ) +{ + ui.setupUi( mainWidget() ); + + ui.sink->setMimeTypeFilter( QStringList() << QLatin1String( "message/rfc822" ) ); + ui.sink->setAccessRightsFilter( Akonadi::Collection::CanCreateItem ); + // Don't bother fetching the collection. Will have an empty name :-/ + ui.sink->setCollection( Collection( Settings::self()->sink() ) ); + ui.sink->changeCollectionDialogOptions( Akonadi::CollectionDialog::AllowToCreateNewChildCollection ); + kDebug() << "Sink from settings" << Settings::self()->sink(); + + connect( this, SIGNAL(okClicked()), this, SLOT(save()) ); + connect( ui.sink, SIGNAL(collectionChanged(Akonadi::Collection)), this, SLOT(slotCollectionChanged(Akonadi::Collection)) ); + enableButtonOk(false); +} + +void ConfigDialog::slotCollectionChanged( const Akonadi::Collection& col ) +{ + enableButtonOk(col.isValid()); +} + +void ConfigDialog::save() +{ + kDebug() << "Sink changed to" << ui.sink->collection().id(); + Settings::self()->setSink( ui.sink->collection().id() ); + Settings::self()->writeConfig(); +} + diff --git a/kdepim-runtime/resources/mailtransport_dummy/configdialog.h b/kdepim-runtime/resources/mailtransport_dummy/configdialog.h new file mode 100644 index 00000000..18e88b92 --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/configdialog.h @@ -0,0 +1,43 @@ +/* + Copyright 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +#include "ui_settings.h" + + + +class ConfigDialog : public KDialog +{ + Q_OBJECT + public: + ConfigDialog( QWidget *parent = 0 ); + + private slots: + void save(); + void slotCollectionChanged(const Akonadi::Collection& ); + private: + Ui::ConfigDialog ui; + +}; + +#endif diff --git a/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.cpp b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.cpp new file mode 100644 index 00000000..0ed6acbf --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.cpp @@ -0,0 +1,108 @@ +/* + Copyright 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "mtdummyresource.h" + +#include "configdialog.h" +#include "settings.h" +#include "settingsadaptor.h" + +#include + +#include + +#include + +using namespace Akonadi; + +MTDummyResource::MTDummyResource( const QString &id ) + : ResourceBase( id ) +{ + KGlobal::locale()->insertCatalog( QLatin1String("akonadi_mailtransport_resource") ); + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + currentlySending = -1; +} + +MTDummyResource::~MTDummyResource() +{ +} + +void MTDummyResource::retrieveCollections() +{ + // we have no collections of our own + collectionsRetrieved( Collection::List() ); +} + +void MTDummyResource::retrieveItems( const Akonadi::Collection &collection ) +{ + Q_UNUSED( collection ); + // we have no collections of our own + Q_ASSERT( false ); +} + +bool MTDummyResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( item ); + Q_UNUSED( parts ); + // we have no collections of our own + Q_ASSERT( false ); + return false; +} + +void MTDummyResource::aboutToQuit() +{ +} + +void MTDummyResource::configure( WId windowId ) +{ + ConfigDialog dlg; + if ( windowId ) + KWindowSystem::setMainWindow( &dlg, windowId ); + + if ( dlg.exec() ) { + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } +} + +void MTDummyResource::sendItem( const Item &message ) +{ + kDebug() << "id" << message.id(); + Q_ASSERT( currentlySending == -1 ); + currentlySending = message.id(); + ItemCopyJob *job = new ItemCopyJob( message, Collection( Settings::self()->sink() ) ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(jobResult(KJob*)) ); +} + +void MTDummyResource::jobResult( KJob *job ) +{ + if( job->error() ) { + itemSent( Item( currentlySending ), TransportFailed, job->errorString() ); + } else { + itemSent( Item( currentlySending ), TransportSucceeded ); + } + currentlySending = -1; +} + +AKONADI_RESOURCE_MAIN( MTDummyResource ) + diff --git a/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.desktop b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.desktop new file mode 100644 index 00000000..4217d90e --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.desktop @@ -0,0 +1,90 @@ +[Desktop Entry] +Name=Dummy MailTransport Resource +Name[bs]=VjeÅ¡taÄki resurs za transport maila +Name[ca]=Recurs de transport de correu simulat +Name[ca@valencia]=Recurs de transport de correu simulat +Name[da]=MailTransport-ressource (attrap) +Name[de]=Dummy MailTransport-Ressource +Name[el]=Εικονικός πόÏος MailTransport +Name[en_GB]=Dummy MailTransport Resource +Name[es]=Recurso de transporte de correo vacío para pruebas +Name[et]=Kirjade edastamine libaressurss +Name[fi]=Tyhjä MailTransport-resurssi +Name[fr]=Ressource de test pour le transport de courriers électroniques +Name[gl]=Recurso de transporte de correo parvo +Name[hu]=Ãœres levéltovábbító erÅ‘forrás +Name[ia]=Ressource de Dummy MailTransport +Name[it]=Risorsa MailTransport fittizia +Name[kk]=Сынақ MailTransport реÑурÑÑ‹ +Name[km]=ធនធាន MailTransport សម្រាប់​អ្នក​មិន​ចáŸáŸ‡ +Name[ko]=ë”미 MailTransport ìžì› +Name[lt]=Fiktyvus paÅ¡to transporto resursus +Name[lv]=MailTransport resursa imitÄcija +Name[nb]=Attrapp-ressurs for e-posttransport +Name[nds]=Platzholl-Nettpostöverdreegmetoden-Ressource +Name[nl]=Dummy e-mailtransporthulpmiddel +Name[nn]=Testressurs for e-posttransport +Name[pl]=Prosty zasób metody przekazywania poczty +Name[pt]=Recurso de Transporte de Correio para Testes +Name[pt_BR]=Recurso de transporte de e-mails para testes +Name[ru]=Фиктивный иÑточник данных почтового транÑпорта +Name[sk]=Prázdny zdroj prenosu poÅ¡ty +Name[sl]=Lažen vir MailTransport +Name[sr]=Лажни реÑÑƒÑ€Ñ Ð¿Ð¾ÑˆÑ‚Ð°Ð½Ñког транÑпорта +Name[sr@ijekavian]=Лажни реÑÑƒÑ€Ñ Ð¿Ð¾ÑˆÑ‚Ð°Ð½Ñког транÑпорта +Name[sr@ijekavianlatin]=Lažni resurs poÅ¡tanskog transporta +Name[sr@latin]=Lažni resurs poÅ¡tanskog transporta +Name[sv]=E-postsändningsresurs för test +Name[tr]=BoÅŸ MailTransport Kaynağı +Name[uk]=ТеÑтовий реÑÑƒÑ€Ñ MailTransport +Name[x-test]=xxDummy MailTransport Resourcexx +Name[zh_CN]=è™šæ‹Ÿé‚®ä»¶ä¼ è¾“èµ„æº +Name[zh_TW]=ç©ºç™½éƒµä»¶å‚³è¼¸è³‡æº +Comment=Dummy Resource implementing mail transport interface +Comment[bs]=VjeÅ¡taÄki resurs implementira suÄelje mail transporta +Comment[ca]=Implementació de recurs simulat d'interfície de transport de correu +Comment[ca@valencia]=Implementació de recurs simulat d'interfície de transport de correu +Comment[da]=Attrap-ressource der implementerer mail-transport-grænseflade +Comment[de]=Dummy-Ressource, die die Mail-Transport-Schnittstelle implementiert +Comment[el]=Εικονικός πόÏος που υλοποιεί την διεπαφή mail transport +Comment[en_GB]=Dummy Resource implementing mail transport interface +Comment[es]=Recurso vacío que implementa una interfaz de transporte de correo +Comment[et]=Kirjade edastamise liidese libaressurss +Comment[fi]=Tyhjä postinvälitysliitännän toteuttava resurssi +Comment[fr]=Ressource de test implémentant l'interface de transport de courriers électroniques +Comment[gl]=Recurso parvo que aporta a interface de transporte de correo +Comment[hu]=A levéltovábbító interfészt implementáló üres erÅ‘forrás +Comment[ia]=Inferfacie pro facer possibile transporto de posta como Dummy Resource +Comment[it]=Risorsa fittizia che implementa l'interfaccia di trasporto della posta +Comment[kk]=Пошта таÑымалдау интерфейÑінің Ñынақ MailTransport реÑурÑÑ‹ +Comment[km]=ចំណុច​ប្រទាក់​បញ្ជូន​សំបុážáŸ’រ​ដោយ​អនុវážáŸ’ážâ€‹áž’នធាន​ពី​ដំបូង +Comment[ko]=ë©”ì¼ ì „ì†¡ ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 구현하는 ë”미 ìžì› +Comment[lt]=Fiktyvus resursas, įgyvendinantis paÅ¡to transporto sÄ…sajÄ… +Comment[lv]=Resursa imitÄcija, kas realizÄ“ pasta transporta saskarni +Comment[nb]=Attrappressurs som implementerer grensesnitt for e-posttransport +Comment[nds]=Platzhollressource mit inbuut Nettpostöverdreegmetood-Koppelsteed +Comment[nl]=Dummy hulpmiddel die een e-mailtransportinterface implementeert +Comment[nn]=Testressurs som tek i bruk eit grensesnitt for e-posttransport +Comment[pl]=Prosty zasób implementujÄ…cy interfejs do przekazywania poczty +Comment[pt]=Uma interface de transporte de correio que implementa um recurso de testes +Comment[pt_BR]=Interface de transporte de e-mails que implementa um recurso de testes +Comment[ru]="Фиктивный иÑточник данных, реализующий Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ð¾Ð³Ð¾ транÑпорта" +Comment[sk]=Prázdny zdroj implementujúci rozhranie prenosu poÅ¡ty +Comment[sl]=Lažen vir z izvedbo vmesnika za prenos poÅ¡te +Comment[sr]=Лажни реÑÑƒÑ€Ñ ÐºÐ¾Ñ˜Ð¸ изводи Ñучеље мрежног транÑпорта +Comment[sr@ijekavian]=Лажни реÑÑƒÑ€Ñ ÐºÐ¾Ñ˜Ð¸ изводи Ñучеље мрежног транÑпорта +Comment[sr@ijekavianlatin]=Lažni resurs koji izvodi suÄelje mrežnog transporta +Comment[sr@latin]=Lažni resurs koji izvodi suÄelje mrežnog transporta +Comment[sv]=Testresurs som implementerar gränssnitt för e-postöverföring +Comment[tr]=Posta nakletme arayüzünü gerçekleyen boÅŸ kaynak +Comment[uk]="ТеÑтовий реÑурÑ, що реалізує Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¿ÐµÑ€ÐµÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸" +Comment[x-test]=xxDummy Resource implementing mail transport interfacexx +Comment[zh_CN]=实现了邮件传输接å£çš„è™šæ‹Ÿèµ„æº +Comment[zh_TW]=空白資æºï¼Œç”¨æ–¼å¯¦ä½œéƒµä»¶å‚³è¼¸ä»‹é¢ +Type=AkonadiResource +Exec=akonadi_mailtransport_dummy_resource +Icon=message-rfc822 + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource,MailTransport +X-Akonadi-Identifier=akonadi_mailtransport_dummy_resource diff --git a/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.h b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.h new file mode 100644 index 00000000..fddb96e4 --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.h @@ -0,0 +1,58 @@ +/* + Copyright 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 MTDUMMYRESOURCE_H +#define MTDUMMYRESOURCE_H + +#include +#include +#include + +class MTDummyResource : public Akonadi::ResourceBase, public Akonadi::TransportResourceBase +{ + Q_OBJECT + + public: + explicit MTDummyResource( const QString &id ); + ~MTDummyResource(); + + public: + virtual void configure( WId windowId ); + + /* reimpl from ResourceBase::Transport */ + virtual void sendItem( const Akonadi::Item &message ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void aboutToQuit(); + + private Q_SLOTS: + void jobResult( KJob *job ); + + private: + Akonadi::Item::Id currentlySending; + +}; + +#endif diff --git a/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.kcfg b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.kcfg new file mode 100644 index 00000000..81fc61b3 --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/mtdummyresource.kcfg @@ -0,0 +1,14 @@ + + + + + + + -1 + + + diff --git a/kdepim-runtime/resources/mailtransport_dummy/settings.kcfgc b/kdepim-runtime/resources/mailtransport_dummy/settings.kcfgc new file mode 100644 index 00000000..7248e2f4 --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/settings.kcfgc @@ -0,0 +1,8 @@ +File=mtdummyresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/mailtransport_dummy/settings.ui b/kdepim-runtime/resources/mailtransport_dummy/settings.ui new file mode 100644 index 00000000..f1102c9a --- /dev/null +++ b/kdepim-runtime/resources/mailtransport_dummy/settings.ui @@ -0,0 +1,60 @@ + + + Till Adam <adam@kde.org> + ConfigDialog + + + + 0 + 0 + 400 + 250 + + + + Mail Dispatcher Agent Settings + + + + + + Select the collection to dump sent messages to: + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + Akonadi::CollectionRequester + QFrame +
akonadi/collectionrequester.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/resources/mbox/CMakeLists.txt b/kdepim-runtime/resources/mbox/CMakeLists.txt new file mode 100644 index 00000000..bae7df23 --- /dev/null +++ b/kdepim-runtime/resources/mbox/CMakeLists.txt @@ -0,0 +1,44 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +add_subdirectory(wizard) + +########### next target ############### + +set( mboxresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES} + compactpage.cpp + lockmethodpage.cpp + deleteditemsattribute.cpp + mboxresource.cpp +) + +install( FILES mboxresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(mboxresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_UI} + compactpage.ui + lockfilepage.ui +) +kde4_add_kcfg_files(mboxresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/mboxresource.kcfg org.kde.Akonadi.Mbox.Settings) +qt4_add_dbus_adaptor(mboxresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Mbox.Settings.xml settings.h Settings +) + +kde4_add_plugin(akonadi_mbox_resource ${mboxresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_mbox_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_mbox_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.Mbox") + set_target_properties(akonadi_mbox_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Mbox Resource") +endif () + +target_link_libraries(akonadi_mbox_resource ${KDEPIMLIBS_KMBOX_LIBS} ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${QT_QTDBUS_LIBRARY} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS}) + +install(TARGETS akonadi_mbox_resource DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/mbox/Messages.sh b/kdepim-runtime/resources/mbox/Messages.sh new file mode 100644 index 00000000..efefc50a --- /dev/null +++ b/kdepim-runtime/resources/mbox/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_mbox_resource.pot diff --git a/kdepim-runtime/resources/mbox/compactpage.cpp b/kdepim-runtime/resources/mbox/compactpage.cpp new file mode 100644 index 00000000..f52a8254 --- /dev/null +++ b/kdepim-runtime/resources/mbox/compactpage.cpp @@ -0,0 +1,139 @@ +/* + Copyright (c) 2009 Bertjan Broeksema + + 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 "compactpage.h" + +#include +#include +#include + +#include "deleteditemsattribute.h" + +#include + +using namespace Akonadi; + +CompactPage::CompactPage( const QString &collectionId, QWidget *parent ) + : QWidget( parent ) + , mCollectionId( collectionId ) +{ + ui.setupUi( this ); + + connect( ui.compactButton, SIGNAL(clicked()), this, SLOT(compact()) ); + + checkCollectionId(); +} + +void CompactPage::checkCollectionId() +{ + if ( !mCollectionId.isEmpty() ) { + Collection collection; + collection.setRemoteId( mCollectionId ); + CollectionFetchJob *fetchJob = + new CollectionFetchJob( collection, CollectionFetchJob::Base ); + + connect( fetchJob, SIGNAL(result(KJob*)), + this, SLOT(onCollectionFetchCheck(KJob*)) ); + } +} + +void CompactPage::compact() +{ + ui.compactButton->setEnabled( false ); + + Collection collection; + collection.setRemoteId( mCollectionId ); + CollectionFetchJob *fetchJob = + new CollectionFetchJob( collection, CollectionFetchJob::Base ); + + connect( fetchJob, SIGNAL(result(KJob*)), + this, SLOT(onCollectionFetchCompact(KJob*)) ); +} + +void CompactPage::onCollectionFetchCheck( KJob *job ) +{ + if ( job->error() ) { + // If we cannot fetch the collection, than also disable compacting. + ui.compactButton->setEnabled( false ); + return; + } + + CollectionFetchJob *fetchJob = dynamic_cast( job ); + Q_ASSERT( fetchJob ); + Q_ASSERT( fetchJob->collections().size() == 1 ); + + Collection mboxCollection = fetchJob->collections().first(); + DeletedItemsAttribute *attr + = mboxCollection.attribute( Akonadi::Entity::AddIfMissing ); + + if ( attr->deletedItemOffsets().size() > 0 ) { + ui.compactButton->setEnabled( true ); + ui.messageLabel->setText( i18np( "(1 message marked for deletion)", + "(%1 messages marked for deletion)", attr->deletedItemOffsets().size() ) ); + } +} + +void CompactPage::onCollectionFetchCompact( KJob *job ) +{ + if ( job->error() ) { + ui.messageLabel->setText( i18n( "Failed to fetch the collection." ) ); + ui.compactButton->setEnabled( true ); + return; + } + + CollectionFetchJob *fetchJob = dynamic_cast( job ); + Q_ASSERT( fetchJob ); + Q_ASSERT( fetchJob->collections().size() == 1 ); + + Collection mboxCollection = fetchJob->collections().first(); + DeletedItemsAttribute *attr + = mboxCollection.attribute( Akonadi::Entity::AddIfMissing ); + + KMBox::MBox mbox; + // TODO: Set lock method. + const QString fileName = KUrl(mCollectionId).toLocalFile(); + if ( !mbox.load(fileName) ) { + ui.messageLabel->setText( i18n( "Failed to load the mbox file" ) ); + } else { + ui.messageLabel->setText( i18np( "(Deleting 1 message)", + "(Deleting %1 messages)", attr->offsetCount() ) ); + // TODO: implement and connect to messageProcessed signal. + if ( mbox.purge(attr->deletedItemEntries()) || + (QFileInfo(fileName).size() == 0) ) { + // even if purge() failed but the file is now empty. + // it was probably deleted/emptied by an external prog. For whatever reason + // doesn't matter here. We know the file is empty so we can get rid + // of our stored DeletedItemsAttribute + mboxCollection.removeAttribute(); + CollectionModifyJob *modifyJob = new CollectionModifyJob( mboxCollection ); + connect( modifyJob, SIGNAL(result(KJob*)), + this, SLOT(onCollectionModify(KJob*)) ); + } else + ui.messageLabel->setText( i18n( "Failed to compact the mbox file." ) ); + } +} + +void CompactPage::onCollectionModify( KJob *job ) +{ + if ( job->error() ) + ui.messageLabel->setText( i18n( "Failed to compact the mbox file." ) ); + else + ui.messageLabel->setText( i18n( "MBox file compacted." ) ); +} + diff --git a/kdepim-runtime/resources/mbox/compactpage.h b/kdepim-runtime/resources/mbox/compactpage.h new file mode 100644 index 00000000..ccfbaf2b --- /dev/null +++ b/kdepim-runtime/resources/mbox/compactpage.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2009 Bertjan Broeksema + + 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 COMPACTPAGE_H +#define COMPACTPAGE_H + +#include + +#include "ui_compactpage.h" + +class KJob; + +class CompactPage : public QWidget +{ + Q_OBJECT + + public: + explicit CompactPage( const QString &collectionId, QWidget *parent = 0 ); + + private slots: + void compact(); + void onCollectionFetchCheck( KJob* ); + void onCollectionFetchCompact( KJob* ); + void onCollectionModify( KJob* ); + + private: // Methods + void checkCollectionId(); + + private: // Members + QString mCollectionId; + Ui::CompactPage ui; +}; + +#endif diff --git a/kdepim-runtime/resources/mbox/compactpage.ui b/kdepim-runtime/resources/mbox/compactpage.ui new file mode 100644 index 00000000..f11f777c --- /dev/null +++ b/kdepim-runtime/resources/mbox/compactpage.ui @@ -0,0 +1,140 @@ + + + CompactPage + + + + 0 + 0 + 362 + 568 + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The only way to fully remove a mail from an mbox file is by removing it from the actual file. As this can be a rather expensive operation, the mbox resource keeps a list of deleted messages. Once in a while these messages are really removed from the file.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note:</span> The downside of this is that if the file is changed by another program, the list of deleted messages cannot be trusted any longer and deleted messages might reappear.</p></body></html> + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + &Compact now + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + &Never compact automatically + + + + + + + C&ompact every + + + true + + + + + + + + + + msg + + + + + + + + + + + KButtonGroup + QGroupBox +
kbuttongroup.h
+ 1 +
+ + KIntNumInput + QWidget +
knuminput.h
+
+
+ + + + per_x_messages + toggled(bool) + kcfg_MessageCount + setEnabled(bool) + + + 75 + 254 + + + 198 + 253 + + + + +
diff --git a/kdepim-runtime/resources/mbox/deleteditemsattribute.cpp b/kdepim-runtime/resources/mbox/deleteditemsattribute.cpp new file mode 100644 index 00000000..f1bebd4e --- /dev/null +++ b/kdepim-runtime/resources/mbox/deleteditemsattribute.cpp @@ -0,0 +1,96 @@ +/* + Copyright (c) 2009 Bertjan Broeksema + + 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 "deleteditemsattribute.h" + +DeletedItemsAttribute::DeletedItemsAttribute() +{ +} + +DeletedItemsAttribute::DeletedItemsAttribute( const DeletedItemsAttribute &other ) + : Akonadi::Attribute() +{ + if ( &other == this ) + return; + + mDeletedItemOffsets = other.mDeletedItemOffsets; +} + +DeletedItemsAttribute::~DeletedItemsAttribute() +{ +} + +void DeletedItemsAttribute::addDeletedItemOffset( quint64 offset ) +{ + mDeletedItemOffsets.insert( offset ); +} + +Akonadi::Attribute *DeletedItemsAttribute::clone() const +{ + return new DeletedItemsAttribute( *this ); +} + +QSet DeletedItemsAttribute::deletedItemOffsets() const +{ + return mDeletedItemOffsets; +} + +KMBox::MBoxEntry::List DeletedItemsAttribute::deletedItemEntries() const +{ + KMBox::MBoxEntry::List entries; + + foreach ( quint64 offset, mDeletedItemOffsets ) + entries << KMBox::MBoxEntry( offset ); + + return entries; +} + +void DeletedItemsAttribute::deserialize( const QByteArray &data ) +{ + QList offsets = data.split(','); + mDeletedItemOffsets.clear(); + + foreach( const QByteArray& offset, offsets ) { + mDeletedItemOffsets.insert( offset.toULongLong() ); + } +} + +QByteArray DeletedItemsAttribute::serialized() const +{ + QByteArray serialized; + + foreach( quint64 offset, mDeletedItemOffsets ) { + serialized += QByteArray::number(offset); + serialized += ','; + } + + serialized.chop( 1 ); // Remove the last ',' + + return serialized; +} + +int DeletedItemsAttribute::offsetCount() const +{ + return mDeletedItemOffsets.size(); +} + +QByteArray DeletedItemsAttribute::type() const +{ + return "DeletedMboxItems"; +} diff --git a/kdepim-runtime/resources/mbox/deleteditemsattribute.h b/kdepim-runtime/resources/mbox/deleteditemsattribute.h new file mode 100644 index 00000000..dcd947d4 --- /dev/null +++ b/kdepim-runtime/resources/mbox/deleteditemsattribute.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2009 Bertjan Broeksema + + 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 DELETEDITEMSATTRIBUTE_H +#define DELETEDITEMSATTRIBUTE_H + +#include +#include + +#include + +/** + * This attribute stores a list of offdets in the mbox file of mails which are + * deleted but not yet actually removed from the file yet. + */ +class DeletedItemsAttribute : public Akonadi::Attribute +{ + public: + DeletedItemsAttribute(); + + DeletedItemsAttribute( const DeletedItemsAttribute &other ); + + ~DeletedItemsAttribute(); + + void addDeletedItemOffset( quint64 ); + + virtual Attribute *clone() const; + + QSet deletedItemOffsets() const; + KMBox::MBoxEntry::List deletedItemEntries() const; + + virtual void deserialize( const QByteArray &data ); + + /** + * Returns the number of offsets stored in this attribute. + */ + int offsetCount() const; + + virtual QByteArray serialized() const; + + virtual QByteArray type() const; + + private: + QSet mDeletedItemOffsets; +}; + +#endif + diff --git a/kdepim-runtime/resources/mbox/lockfilepage.ui b/kdepim-runtime/resources/mbox/lockfilepage.ui new file mode 100644 index 00000000..a07ad0ce --- /dev/null +++ b/kdepim-runtime/resources/mbox/lockfilepage.ui @@ -0,0 +1,156 @@ + + + Bertjan Broeksema <broeksema@kde.org> + LockFilePage + + + + 0 + 0 + 317 + 369 + + + + MBox Settings + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Select a method to lock the mbox file when data is read from or written to the file.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span>: For some methods you might need to install additional software before they can be used.</p></body></html> + + + true + + + + + + + + + + + + + Procmail loc&kfile + + + false + + + + + + + &Mutt dotlock + + + + + + + M&utt dotlock privileged + + + + + + + Non&e + + + true + + + + + + + false + + + true + + + + .lock + + + + + + + + None, the default configuration, should be safe in most cases. However, if programs that do not make use of Akonadi are also accessing the configured mbox file, you will need to set an appropriate locking method. Note that if this is the case, the resource and the other programs must all use the same locking method. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KButtonGroup + QGroupBox +
kbuttongroup.h
+ 1 +
+ + KComboBox + QComboBox +
kcombobox.h
+
+
+ + procmail + kcfg_Lockfile + mutt_dotlock + mutt_dotlock_privileged + none + + + + + procmail + toggled(bool) + kcfg_Lockfile + setEnabled(bool) + + + 64 + 141 + + + 211 + 140 + + + + +
diff --git a/kdepim-runtime/resources/mbox/lockmethodpage.cpp b/kdepim-runtime/resources/mbox/lockmethodpage.cpp new file mode 100644 index 00000000..ad0e1531 --- /dev/null +++ b/kdepim-runtime/resources/mbox/lockmethodpage.cpp @@ -0,0 +1,57 @@ +/* + Copyright (c) 2009 Bertjan Broeksema + + 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 "lockmethodpage.h" +#include "settings.h" + +#include +#include +#include + +LockMethodPage::LockMethodPage( QWidget *parent ) : QWidget( parent ) +{ + ui.setupUi( this ); + checkAvailableLockMethods(); +} + +void LockMethodPage::checkAvailableLockMethods() +{ + // FIXME: I guess this whole checking makes only sense on linux machines. + + // Check for procmail lock method. + if ( KStandardDirs::findExe( QLatin1String("lockfile") ).isEmpty() ) { + ui.procmail->setEnabled( false ); + if ( ui.procmail->isChecked() ) // Select another lock method if necessary + ui.mutt_dotlock->setChecked( true ); + } + + // Check for mutt lock method. + if ( KStandardDirs::findExe( QLatin1String("mutt_dotlock") ).isEmpty() ) { + ui.mutt_dotlock->setEnabled( false ); + ui.mutt_dotlock_privileged->setEnabled( false ); + if ( ui.mutt_dotlock->isChecked() || ui.mutt_dotlock_privileged->isChecked() ) + { + if ( ui.procmail->isEnabled() ) + ui.procmail->setChecked(true); + else + ui.none->setChecked(true); + } + } +} + diff --git a/kdepim-runtime/resources/mbox/lockmethodpage.h b/kdepim-runtime/resources/mbox/lockmethodpage.h new file mode 100644 index 00000000..53c5ceb4 --- /dev/null +++ b/kdepim-runtime/resources/mbox/lockmethodpage.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009 Bertjan Broeksema + + 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 LOCKMETHODPAGE_H +#define LOCKMETHODPAGE_H + +#include + +#include "ui_lockfilepage.h" + +class LockMethodPage : public QWidget +{ + Q_OBJECT + public: + explicit LockMethodPage( QWidget *parent = 0 ); + + private: + void checkAvailableLockMethods(); + + private: + Ui::LockFilePage ui; +}; + +#endif diff --git a/kdepim-runtime/resources/mbox/mboxresource.cpp b/kdepim-runtime/resources/mbox/mboxresource.cpp new file mode 100644 index 00000000..9b39870e --- /dev/null +++ b/kdepim-runtime/resources/mbox/mboxresource.cpp @@ -0,0 +1,376 @@ +/* + Copyright (c) 2009 Bertjan Broeksem + + 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 "mboxresource.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compactpage.h" +#include "deleteditemsattribute.h" +#include "lockmethodpage.h" +#include "settingsadaptor.h" +#include "singlefileresourceconfigdialog.h" + +using namespace Akonadi; + +static Entity::Id collectionId( const QString &remoteItemId ) +{ + // [CollectionId]::[RemoteCollectionId]::[Offset] + Q_ASSERT( remoteItemId.split( QLatin1String("::") ).size() == 3 ); + return remoteItemId.split( QLatin1String("::") ).first().toLongLong(); +} + +static QString mboxFile(const QString &remoteItemId) +{ + // [CollectionId]::[RemoteCollectionId]::[Offset] + Q_ASSERT(remoteItemId.split( QLatin1String("::") ).size() == 3); + return remoteItemId.split( QLatin1String("::") ).at(1); +} + +static quint64 itemOffset( const QString &remoteItemId ) +{ + // [CollectionId]::[RemoteCollectionId]::[Offset] + Q_ASSERT( remoteItemId.split( QLatin1String("::") ).size() == 3 ); + return remoteItemId.split( QLatin1String("::") ).last().toULongLong(); +} + +MboxResource::MboxResource( const QString &id ) + : SingleFileResource( id ) + , mMBox( 0 ) +{ + new SettingsAdaptor( mSettings ); + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + mSettings, QDBusConnection::ExportAdaptors ); + + QStringList mimeTypes; + mimeTypes << QLatin1String("message/rfc822"); + setSupportedMimetypes( mimeTypes, QLatin1String("message-rfc822") ); + + // Register the list of deleted items as an attribute of the collection. + AttributeFactory::registerAttribute(); +} + +MboxResource::~MboxResource() +{ + delete mMBox; +} + +void MboxResource::customizeConfigDialog( SingleFileResourceConfigDialog* dlg ) +{ + dlg->setWindowIcon( KIcon( QLatin1String("message-rfc822") ) ); + dlg->addPage( i18n( "Compact frequency" ), new CompactPage( mSettings->path() ) ); + dlg->addPage( i18n( "Lock method" ), new LockMethodPage() ); + dlg->setCaption( i18n( "Select MBox file" ) ); +} + +void MboxResource::retrieveItems( const Akonadi::Collection &col ) +{ + Q_UNUSED( col ); + if ( !mMBox ) { + cancelTask(); + return; + } + if (mMBox->fileName().isEmpty()) { + emit status(NotConfigured, i18nc( "@info:status", "MBox not configured." ) ); + return; + } + + reloadFile(); + + KMBox::MBoxEntry::List entryList; + if ( col.hasAttribute() ) { + DeletedItemsAttribute *attr = col.attribute(); + entryList = mMBox->entries( attr->deletedItemEntries() ); + } else { // No deleted items (yet) + entryList = mMBox->entries(); + } + + mMBox->lock(); // Lock the file so that it doesn't get locked for every + // readEntryHeaders() call. + + Item::List items; + QString colId = QString::number( col.id() ); + QString colRid = col.remoteId(); + double count = 1; + const int entryListSize( entryList.size() ); + foreach ( const KMBox::MBoxEntry &entry, entryList ) { + // TODO: Use cache policy to see what actually has to been set as payload. + // Currently most views need a minimal amount of information so the + // Items get Envelopes as payload. + KMime::Message *mail = new KMime::Message(); + mail->setHead( KMime::CRLFtoLF( mMBox->readMessageHeaders( entry ) ) ); + mail->parse(); + + Item item; + item.setRemoteId( colId + QLatin1String("::") + colRid + QLatin1String("::") + QString::number( entry.messageOffset() ) ); + item.setMimeType( QLatin1String("message/rfc822") ); + item.setSize( entry.messageSize() ); + item.setPayload( KMime::Message::Ptr( mail ) ); + + emit percent(count++ / entryListSize); + items << item; + } + + mMBox->unlock(); // Now we have the items, unlock + + itemsRetrieved( items ); +} + +bool MboxResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED(parts); + + if ( !mMBox ) { + emit error( i18n( "MBox not loaded." ) ); + return false; + } + if (mMBox->fileName().isEmpty()) { + emit status(NotConfigured, i18nc( "@info:status", "MBox not configured." ) ); + return false; + } + + const QString rid = item.remoteId(); + const quint64 offset = itemOffset( rid ); + KMime::Message *mail = mMBox->readMessage( KMBox::MBoxEntry( offset ) ); + if ( !mail ) { + emit error( i18n( "Failed to read message with uid '%1'.", rid ) ); + return false; + } + + Item i( item ); + i.setPayload( KMime::Message::Ptr( mail ) ); + itemRetrieved( i ); + return true; +} + +void MboxResource::aboutToQuit() +{ + if ( !mSettings->readOnly() ) + writeFile(); + mSettings->writeConfig(); +} + +void MboxResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + if ( !mMBox ) { + cancelTask( i18n( "MBox not loaded." ) ); + return; + } + if (mMBox->fileName().isEmpty()) { + emit status(NotConfigured, i18nc( "@info:status", "MBox not configured." ) ); + return; + } + + // we can only deal with mail + if ( !item.hasPayload() ) { + cancelTask( i18n( "Only email messages can be added to the MBox resource." ) ); + return; + } + + const KMBox::MBoxEntry entry = mMBox->appendMessage( item.payload() ); + if ( !entry.isValid() ) { + cancelTask( i18n( "Mail message not added to the MBox." ) ); + return; + } + + scheduleWrite(); + const QString rid = QString::number( collection.id() ) + QLatin1String("::") + + collection.remoteId() + QLatin1String("::") + QString::number( entry.messageOffset() ); + + Item i( item ); + i.setRemoteId( rid ); + + changeCommitted( i ); +} + +void MboxResource::itemChanged( const Akonadi::Item &item, const QSet &parts ) +{ + if ( parts.contains( "PLD:RFC822" ) ) { + kDebug() << itemOffset( item.remoteId() ); + // Only complete messages can be stored in a MBox file. Because all messages + // are stored in one single file we do an ItemDelete and an ItemCreate to + // prevent that whole file must been rewritten. + CollectionFetchJob *fetchJob = + new CollectionFetchJob( Collection( collectionId( item.remoteId() ) ) + , CollectionFetchJob::Base ); + + connect( fetchJob, SIGNAL(result(KJob*)), + this, SLOT(onCollectionFetch(KJob*)) ); + + mCurrentItemDeletions.insert( fetchJob, item ); + + fetchJob->start(); + return; + } + + changeProcessed(); +} + +void MboxResource::itemRemoved( const Akonadi::Item &item ) +{ + CollectionFetchJob *fetchJob = + new CollectionFetchJob( Collection( collectionId( item.remoteId() ) ) + , CollectionFetchJob::Base ); + + if ( !fetchJob->exec() ) { + cancelTask( i18n( "Could not fetch the collection: %1", fetchJob->errorString() ) ); + return; + } + + Q_ASSERT( fetchJob->collections().size() == 1 ); + Collection mboxCollection = fetchJob->collections().first(); + DeletedItemsAttribute *attr + = mboxCollection.attribute( Akonadi::Entity::AddIfMissing ); + + if ( mSettings->compactFrequency() == Settings::per_x_messages + && mSettings->messageCount() == static_cast( attr->offsetCount() + 1 ) ) { + kDebug() << "Compacting mbox file"; + mMBox->purge( attr->deletedItemEntries() << KMBox::MBoxEntry( itemOffset( item.remoteId() ) ) ); + scheduleWrite(); + mboxCollection.removeAttribute(); + } else { + attr->addDeletedItemOffset( itemOffset( item.remoteId() ) ); + } + + CollectionModifyJob *modifyJob = new CollectionModifyJob( mboxCollection ); + if ( !modifyJob->exec() ) { + cancelTask( modifyJob->errorString() ); + return; + } + + changeProcessed(); +} + +void MboxResource::handleHashChange() +{ + emit warning( i18n( "The MBox file was changed by another program. " + "A copy of the new file was made and pending changes " + "are appended to that copy. To prevent this from happening " + "use locking and make sure that all programs accessing the mbox " + "use the same locking method." ) ); +} + +bool MboxResource::readFromFile( const QString &fileName ) +{ + delete mMBox; + mMBox = new KMBox::MBox(); + + switch ( mSettings->lockfileMethod() ) { + case Settings::procmail: + mMBox->setLockType( KMBox::MBox::ProcmailLockfile ); + mMBox->setLockFile( mSettings->lockfile() ); + break; + case Settings::mutt_dotlock: + mMBox->setLockType( KMBox::MBox::MuttDotlock ); + break; + case Settings::mutt_dotlock_privileged: + mMBox->setLockType( KMBox::MBox::MuttDotlockPrivileged ); + break; + } + + return mMBox->load( KUrl( fileName ).toLocalFile() ); +} + +bool MboxResource::writeToFile( const QString &fileName ) +{ + if ( !mMBox->save( fileName ) ) { + emit error( i18n( "Failed to save mbox file to %1", fileName ) ); + return false; + } + + // HACK: When writeToFile is called with another file than with which the mbox + // was loaded we assume that a backup is made as result of the fileChanged slot + // in SingleFileResourceBase. The problem is that SingleFileResource assumes that + // the implementing resources can save/retrieve the data from before the file + // change we have a problem at this point in the mbox resource. Therefore we + // copy the original file and append pending changes to it but also add an extra + // '\n' to make sure that the hashes differ and the user gets notified. Normally + // if this happens the user should make use of locking in all applications that + // use the mbox file. + if ( fileName != mMBox->fileName() ) { + QFile file( fileName ); + file.open( QIODevice::WriteOnly ); + file.seek( file.size() ); + file.write( "\n" ); + } + + return true; +} + +/// Private slots + +void MboxResource::onCollectionFetch( KJob *job ) +{ + Q_ASSERT( mCurrentItemDeletions.contains( job ) ); + const Item item = mCurrentItemDeletions.take( job ); + + if ( job->error() ) { + cancelTask( job->errorString() ); + return; + } + + CollectionFetchJob *fetchJob = dynamic_cast( job ); + Q_ASSERT( fetchJob ); + Q_ASSERT( fetchJob->collections().size() == 1 ); + + Collection mboxCollection = fetchJob->collections().first(); + DeletedItemsAttribute *attr + = mboxCollection.attribute( Akonadi::Entity::AddIfMissing ); + attr->addDeletedItemOffset( itemOffset( item.remoteId() ) ); + + CollectionModifyJob *modifyJob = new CollectionModifyJob( mboxCollection ); + mCurrentItemDeletions.insert( modifyJob, item ); + connect( modifyJob, SIGNAL(result(KJob*)), + this, SLOT(onCollectionModify(KJob*)) ); + modifyJob->start(); +} + +void MboxResource::onCollectionModify( KJob *job ) +{ + Q_ASSERT( mCurrentItemDeletions.contains( job ) ); + const Item item = mCurrentItemDeletions.take( job ); + + if ( job->error() ) { + // Failed to store the offset of a deleted item in the DeletedItemsAttribute + // of the collection. In this case we shouldn't try to store the modified + // item. + cancelTask( i18n( "Failed to update the changed item because the old item " + "could not be deleted Reason: %1", job->errorString() ) ); + return; + } + + Collection c( collectionId( item.remoteId() ) ); + c.setRemoteId( mboxFile( item.remoteId() ) ); + + itemAdded( item, c ); +} + +AKONADI_AGENT_FACTORY( MboxResource, akonadi_mbox_resource ) + diff --git a/kdepim-runtime/resources/mbox/mboxresource.desktop b/kdepim-runtime/resources/mbox/mboxresource.desktop new file mode 100644 index 00000000..3ba2d13a --- /dev/null +++ b/kdepim-runtime/resources/mbox/mboxresource.desktop @@ -0,0 +1,103 @@ +[Desktop Entry] +Name=Mbox +Name[bg]=Mbox +Name[bs]=Mbox +Name[ca]=Mbox +Name[ca@valencia]=Mbox +Name[cs]=MBox +Name[da]=Mbox +Name[de]=Mbox +Name[el]=Mbox +Name[en_GB]=Mbox +Name[eo]=Mbox +Name[es]=Mbox +Name[et]=Mbox +Name[fi]=Mbox +Name[fr]=Mbox +Name[ga]=Mbox +Name[gl]=Mbox +Name[hu]=Mbox +Name[ia]=Mbox +Name[it]=Mbox +Name[ja]=Mbox +Name[kk]=Mbox +Name[km]=Mbox +Name[ko]=Mbox +Name[lt]=Mbox +Name[lv]=Mbox +Name[nb]=Mbox +Name[nds]=MBox +Name[nl]=Mbox +Name[nn]=MBOX +Name[pa]=Mbox +Name[pl]=Mbox +Name[pt]=Mbox +Name[pt_BR]=Mbox +Name[ro]=Mbox +Name[ru]=MBox +Name[sk]=Mbox +Name[sl]=Mbox +Name[sr]=ÐœÐ±Ð¾ÐºÑ +Name[sr@ijekavian]=ÐœÐ±Ð¾ÐºÑ +Name[sr@ijekavianlatin]=Mbox +Name[sr@latin]=Mbox +Name[sv]=Mbox +Name[tr]=Mbox +Name[uk]=Mbox +Name[x-test]=xxMboxxx +Name[zh_CN]=MBox +Name[zh_TW]=Mbox +Comment=Loads data from a local mbox file +Comment[bg]=Зареждане на данни от локален файл mbox +Comment[bs]=UÄitava podatke iz lokalne mbox datoteke +Comment[ca]=Carrega les dades des d'un fitxer «mbox» local +Comment[ca@valencia]=Carrega dades des d'un fitxer «mbox» local +Comment[da]=Indlæser data fra en lokal mbox-fil +Comment[de]=Daten werden aus einer lokalen MBox-Datei geladen +Comment[el]=ΦοÏτώνει δεδομένα από ένα τοπικό αÏχείο mbox +Comment[en_GB]=Loads data from a local mbox file +Comment[es]=Carga datos de un archivo mbox local +Comment[et]=Andmete laadimine kohalikust mbox-failist +Comment[fi]=Lataa tietoa paikallisesta mbox-tiedostosta +Comment[fr]=Charge des données depuis un fichier « mbox » +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad logánta mbox +Comment[gl]=Carga datos desde un ficheiro mbox local +Comment[hu]=Adatok betöltése egy helyi mbox fájlból +Comment[ia]=Lege datos de un file local de Mbox +Comment[it]=Carica dati da una cartella locale mbox +Comment[ja]=ローカル㮠mbox ファイルã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿è¾¼ã¿ã¾ã™ +Comment[kk]=Жергілікті mbox файлынан деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ឯកសារ mbox មូលដ្ឋាន +Comment[ko]=로컬 Mbox 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 불러옵니다 +Comment[lt]=įkelia duomenis iÅ¡ vietinio mbox failo +Comment[lv]=IelÄdÄ“ datus no lokÄlÄ mbox faila +Comment[nb]=Laster data fra en lokal mbox-fil +Comment[nds]=Laadt Daten ut en lokaal Postfach (MBox-Datei) +Comment[nl]=Laadt gegevens van een lokaal Mbox-bestand +Comment[nn]=Lastar data frÃ¥ ei lokal MBOX-fil +Comment[pa]=ਲੋਕਲ mbox ਫਾਇਲ ਤੋਂ ਡਾਟਾ ਲੋਡ ਕਰੋ +Comment[pl]=Wczytuje dane z pliku mbox +Comment[pt]=Carrega os dados a partir de um ficheiro 'mbox' local +Comment[pt_BR]=Carrega os dados de um arquivo mbox local +Comment[ro]=ÃŽncarcă date dintr-un fiÈ™ier mbox local +Comment[ru]=Загрузка данных из локального файла MBox +Comment[sk]=NaÄíta dáta z miestneho súboru mbox +Comment[sl]=Naloži podatke iz krajevne datoteke Mbox +Comment[sr]=Учитава податке из локалног Ð¼Ð±Ð¾ÐºÑ Ñ„Ð°Ñ˜Ð»Ð° +Comment[sr@ijekavian]=Учитава податке из локалног Ð¼Ð±Ð¾ÐºÑ Ñ„Ð°Ñ˜Ð»Ð° +Comment[sr@ijekavianlatin]=UÄitava podatke iz lokalnog mbox fajla +Comment[sr@latin]=UÄitava podatke iz lokalnog mbox fajla +Comment[sv]=Laddar data frÃ¥n en lokal mbox-fil +Comment[tr]=Yerel mbox dosyasından veri yükler +Comment[uk]=Завантажує дані з локального файла mbox +Comment[x-test]=xxLoads data from a local mbox filexx +Comment[zh_CN]=从本地 mbox æ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從本地 mbox 檔載入資料 +Type=AkonadiResource +Exec=akonadi_mbox_resource +Icon=message-rfc822 + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_mbox_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/mbox/mboxresource.h b/kdepim-runtime/resources/mbox/mboxresource.h new file mode 100644 index 00000000..67b5b0a4 --- /dev/null +++ b/kdepim-runtime/resources/mbox/mboxresource.h @@ -0,0 +1,68 @@ +/* + Copyright (c) 2009 Bertjan Broeksem + + 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 MBOX_RESOURCE_H +#define MBOX_RESOURCE_H + +#include "settings.h" +#include "singlefileresource.h" + +namespace KMBox { +class MBox; +} + +class MboxResource : public Akonadi::SingleFileResource +{ + Q_OBJECT + + public: + explicit MboxResource( const QString &id ); + ~MboxResource(); + + protected Q_SLOTS: + virtual bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + virtual void retrieveItems( const Akonadi::Collection &col ); + + protected: + virtual void aboutToQuit(); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + // From SingleFileResourceBase + virtual void handleHashChange(); + virtual bool readFromFile( const QString &fileName ); + virtual bool writeToFile( const QString &fileName ); + /** + * Customize the configuration dialog before it is displayed. + */ + virtual void customizeConfigDialog( Akonadi::SingleFileResourceConfigDialog* dlg ); + + + private Q_SLOTS: + void onCollectionFetch( KJob *job ); + void onCollectionModify( KJob *job ); + + private: + QMap mCurrentItemDeletions; + KMBox::MBox *mMBox; +}; + +#endif diff --git a/kdepim-runtime/resources/mbox/mboxresource.kcfg b/kdepim-runtime/resources/mbox/mboxresource.kcfg new file mode 100644 index 00000000..16c56fc9 --- /dev/null +++ b/kdepim-runtime/resources/mbox/mboxresource.kcfg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + false + + + + true + + + + + + + + + + + none + + + + + + + + + + + + + per_x_messages + + + + 50 + + + diff --git a/kdepim-runtime/resources/mbox/settings.kcfgc b/kdepim-runtime/resources/mbox/settings.kcfgc new file mode 100644 index 00000000..f939b907 --- /dev/null +++ b/kdepim-runtime/resources/mbox/settings.kcfgc @@ -0,0 +1,8 @@ +File=mboxresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/mbox/wizard/CMakeLists.txt b/kdepim-runtime/resources/mbox/wizard/CMakeLists.txt new file mode 100644 index 00000000..1550336b --- /dev/null +++ b/kdepim-runtime/resources/mbox/wizard/CMakeLists.txt @@ -0,0 +1,2 @@ + +install ( FILES mailboxwizard.desktop mailboxwizard.es mailboxwizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/mailbox ) diff --git a/kdepim-runtime/resources/mbox/wizard/Messages.sh b/kdepim-runtime/resources/mbox/wizard/Messages.sh new file mode 100644 index 00000000..fa2131b9 --- /dev/null +++ b/kdepim-runtime/resources/mbox/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_mailbox.pot +$XGETTEXT -kqsTr *.es -j -o $podir/accountwizard_mailbox.pot diff --git a/kdepim-runtime/resources/mbox/wizard/mailboxwizard.desktop b/kdepim-runtime/resources/mbox/wizard/mailboxwizard.desktop new file mode 100644 index 00000000..905ac9fd --- /dev/null +++ b/kdepim-runtime/resources/mbox/wizard/mailboxwizard.desktop @@ -0,0 +1,103 @@ +[Desktop Entry] +Name=MailBox +Name[bg]=MailBox +Name[bs]=MailBox +Name[ca]=Bústia +Name[ca@valencia]=Bústia +Name[cs]=PoÅ¡tovní schránka +Name[da]=MailBox +Name[de]=Mailbox +Name[el]=ΓÏαμματοκιβώτιο +Name[en_GB]=MailBox +Name[es]=MailBox +Name[et]=MailBox +Name[fi]=MailBox +Name[fr]=Messagerie +Name[ga]=MailBox +Name[gl]=Caixa de correo +Name[hu]=MailBox +Name[ia]=Cassa postal +Name[it]=MailBox +Name[ja]=メールボックス +Name[kk]=MailBox +Name[km]=MailBox +Name[ko]=MailBox +Name[lt]=PaÅ¡to dėžutÄ— +Name[lv]=MailBox +Name[nb]=Postkasse +Name[nds]=Postfach +Name[nl]=MailBox +Name[nn]=MailBox +Name[pa]=MailBox +Name[pl]=Skrzynka pocztowa +Name[pt]=Caixa de Correio +Name[pt_BR]=MailBox +Name[ro]=MailBox +Name[ru]=MailBox +Name[sk]=MailBox +Name[sl]=MailBox +Name[sr]=ÐœÐ±Ð¾ÐºÑ +Name[sr@ijekavian]=ÐœÐ±Ð¾ÐºÑ +Name[sr@ijekavianlatin]=Mbox +Name[sr@latin]=Mbox +Name[sv]=Mailbox +Name[tr]=MailBox +Name[ug]=خەت ساندۇقى +Name[uk]=MailBox +Name[x-test]=xxMailBoxxx +Name[zh_CN]=MailBox +Name[zh_TW]=信箱格å¼ï¼ˆMailbox) +Icon=message-rfc822 +Comment=Mailbox account +Comment[bs]=Mailbox nalog +Comment[ca]=Compte de bústia +Comment[ca@valencia]=Compte de bústia +Comment[cs]=ÚÄet poÅ¡tovní schránky +Comment[da]=Mailbox-konto +Comment[de]=Mailbox-Zugang +Comment[el]=ΛογαÏιασμός ΓÏαμματοκιβωτίου +Comment[en_GB]=Mailbox account +Comment[es]=Cuenta de Mailbox +Comment[et]=Mailbox-konto +Comment[fi]=Mailbox-tili +Comment[fr]=Compte de messagerie +Comment[ga]=Cuntas MailBox +Comment[gl]=Conta da caixa de correo +Comment[hu]=Mailbox azonosító +Comment[ia]=Conto de cassa postal +Comment[it]=Account Mailbox +Comment[ja]=メールボックスã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ +Comment[kk]=Mailbox тірккелгіÑÑ– +Comment[km]=គណនី Mailbox +Comment[ko]=Mailbox 계정 +Comment[lt]=PaÅ¡to dėžutÄ—s paskyra +Comment[lv]=Mailbox konts +Comment[nb]=Mailbox-konto +Comment[nds]=Nettpostkonto +Comment[nl]=Mailbox-account +Comment[nn]=MailBox-konto +Comment[pa]=Mailbox ਅਕਾਊਂਟ +Comment[pl]=Konto Mailbox +Comment[pt]=Conta de Caixa do Correio +Comment[pt_BR]=Conta Mailbox +Comment[ro]=Cont Mailbox +Comment[ru]=Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Maildir +Comment[sk]=ÚÄet mailboxu +Comment[sl]=RaÄun MailBox +Comment[sr]=ÐœÐ±Ð¾ÐºÑ Ð½Ð°Ð»Ð¾Ð³ +Comment[sr@ijekavian]=ÐœÐ±Ð¾ÐºÑ Ð½Ð°Ð»Ð¾Ð³ +Comment[sr@ijekavianlatin]=Mbox nalog +Comment[sr@latin]=Mbox nalog +Comment[sv]=Mailbox-konto +Comment[tr]=Mailbox hesabı +Comment[uk]=Обліковий Ð·Ð°Ð¿Ð¸Ñ Mailbox +Comment[x-test]=xxMailbox accountxx +Comment[zh_CN]=MailBox 账户 +Comment[zh_TW]=Mailbox 帳號 + +[Wizard] +Type=message/rfc822 +Script=mailboxwizard.es + +[Translate] +Filename=accountwizard_mailbox diff --git a/kdepim-runtime/resources/mbox/wizard/mailboxwizard.es b/kdepim-runtime/resources/mbox/wizard/mailboxwizard.es new file mode 100644 index 00000000..287c337a --- /dev/null +++ b/kdepim-runtime/resources/mbox/wizard/mailboxwizard.es @@ -0,0 +1,41 @@ +/* + Copyright (c) 2010 Montel Laurent + + 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. +*/ + +var page = Dialog.addPage( "mailboxwizard.ui", qsTr("Personal Settings") ); + +function validateInput() +{ + if ( page.widget().mailboxPath.text == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var mboxRes = SetupManager.createResource( "akonadi_mbox_resource" ); + mboxRes.setOption( "Path", page.widget().mailboxPath.text ); + + SetupManager.execute(); +} + +page.widget().mailboxPath.textChanged.connect( validateInput ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/mbox/wizard/mailboxwizard.ui b/kdepim-runtime/resources/mbox/wizard/mailboxwizard.ui new file mode 100644 index 00000000..032c3841 --- /dev/null +++ b/kdepim-runtime/resources/mbox/wizard/mailboxwizard.ui @@ -0,0 +1,56 @@ + + + mailboxWizard + + + + 0 + 0 + 400 + 300 + + + + + + + + + URL: + + + + + + + KFile::ExistingOnly + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/mixedmaildir/CMakeLists.txt b/kdepim-runtime/resources/mixedmaildir/CMakeLists.txt new file mode 100644 index 00000000..9445c017 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/CMakeLists.txt @@ -0,0 +1,70 @@ +project( mixedmaildirresource ) + +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${kdepim-runtime_SOURCE_DIR}/resources/shared + ${kdepim-runtime_SOURCE_DIR}/resources/shared/filestore + ${kdepim-runtime_SOURCE_DIR}/resources/maildir + ${kdepim-runtime_SOURCE_DIR}/resources/mbox + ${CMAKE_CURRENT_SOURCE_DIR}/kmindexreader + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +add_subdirectory(kmindexreader) +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5254) # TODO get our own debug area + +########### next target ############### + +set( mixedmaildirresource_SRCS + compactchangehelper.cpp + configdialog.cpp + mixedmaildirresource.cpp + mixedmaildirstore.cpp + retrieveitemsjob.cpp + ../shared/createandsettagsjob.cpp +) + +install( FILES mixedmaildirresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(mixedmaildirresource_SRCS settings.ui) + +kde4_add_kcfg_files(mixedmaildirresource_SRCS settings.kcfgc) + +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/mixedmaildirresource.kcfg org.kde.Akonadi.MixedMaildir.Settings) + +qt4_add_dbus_adaptor(mixedmaildirresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.MixedMaildir.Settings.xml settings.h Settings +) + +kde4_add_executable(akonadi_mixedmaildir_resource ${mixedmaildirresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_mixedmaildir_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_mixedmaildir_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.MixedMaildir") + set_target_properties(akonadi_mixedmaildir_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi MixedMaildir Resource") +endif () + +target_link_libraries(akonadi_mixedmaildir_resource + kmindexreader + maildir + akonadi-filestore + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${QT_QTDBUS_LIBRARY} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KMBOX_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_KPIMUTILS_LIBS} +) + +install(TARGETS akonadi_mixedmaildir_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.MixedMaildir.Settings.xml + DESTINATION ${DBUS_INTERFACES_INSTALL_DIR}) + +# unit tests +if (KDE4_BUILD_TESTS) + add_subdirectory( tests ) +endif () diff --git a/kdepim-runtime/resources/mixedmaildir/Messages.sh b/kdepim-runtime/resources/mixedmaildir/Messages.sh new file mode 100644 index 00000000..207aa063 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_mixedmaildir_resource.pot diff --git a/kdepim-runtime/resources/mixedmaildir/compactchangehelper.cpp b/kdepim-runtime/resources/mixedmaildir/compactchangehelper.cpp new file mode 100644 index 00000000..35227f98 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/compactchangehelper.cpp @@ -0,0 +1,238 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "compactchangehelper.h" + +#include "filestore/entitycompactchangeattribute.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Akonadi; + +typedef QMap OldIdItemMap; +typedef QMap RevisionChangeMap; +typedef QMap CollectionRevisionMap; + +struct UpdateBatch +{ + QQueue items; + Collection collection; +}; + +class CompactChangeHelper::Private +{ + CompactChangeHelper *const q; + + public: + explicit Private( CompactChangeHelper *parent ) : q( parent ), mSession( 0 ) + { + } + + public: + Session *mSession; + CollectionRevisionMap mChangesByCollection; + QQueue mPendingUpdates; + UpdateBatch mCurrentUpdate; + + public: // slots + void processNextBatch(); + void processNextItem(); + void itemFetchResult( KJob *job ); +}; + +void CompactChangeHelper::Private::processNextBatch() +{ + //kDebug() << "pendingUpdates.count=" << mPendingUpdates.count(); + if ( mPendingUpdates.isEmpty() ) { + return; + } + + mCurrentUpdate = mPendingUpdates.dequeue(); + + processNextItem(); +} + +void CompactChangeHelper::Private::processNextItem() +{ + //kDebug() << "mCurrentUpdate.items.count=" << mCurrentUpdate.items.count(); + if ( mCurrentUpdate.items.isEmpty() ) { + CollectionModifyJob *job = new CollectionModifyJob( mCurrentUpdate.collection, mSession ); + QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(processNextBatch()) ); + return; + } + + const Item nextItem = mCurrentUpdate.items.dequeue(); + + Item item; + item.setRemoteId( nextItem.remoteId() ); + + ItemFetchJob *job = new ItemFetchJob( item ); + job->setProperty( "oldRemoteId", item.remoteId() ); + job->setProperty( "newRemoteId", nextItem.attribute()->remoteId() ); + QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(itemFetchResult(KJob*)) ); +} + +void CompactChangeHelper::Private::itemFetchResult( KJob *job ) +{ + ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob != 0 ); + + const QString oldRemoteId = fetchJob->property( "oldRemoteId" ).value(); + Q_ASSERT( !oldRemoteId.isEmpty() ); + + const QString newRemoteId = fetchJob->property( "newRemoteId" ).value(); + Q_ASSERT( !newRemoteId.isEmpty() ); + + if ( fetchJob->error() != 0 ) { + kError() << "Item fetch for remoteId=" << oldRemoteId + << "new remoteId=" << newRemoteId << "failed:" << fetchJob->errorString(); + processNextItem(); + return; + } + + // since we only need the item to modify its remote ID, we don't care + // if it does not exist (anymore) + if ( fetchJob->items().isEmpty() ) { + processNextItem(); + return; + } + + const Item item = fetchJob->items().first(); + + Item updatedItem( item ); + updatedItem.setRemoteId( newRemoteId ); + + ItemModifyJob *modifyJob = new ItemModifyJob( updatedItem ); + QObject::connect( modifyJob, SIGNAL(result(KJob*)), q, SLOT(processNextItem()) ); +} + +CompactChangeHelper::CompactChangeHelper( const QByteArray &sessionId, QObject *parent ) + : QObject( parent ), d( new Private( this ) ) +{ + d->mSession = new Session( sessionId, this ); +} + +CompactChangeHelper::~CompactChangeHelper() +{ + delete d; +} + +void CompactChangeHelper::addChangedItems( const Item::List &items ) +{ + if ( items.isEmpty() ) { + return; + } + + kDebug() << "items.count=" << items.count() + << "pendingUpdates.count=" << d->mPendingUpdates.count(); + UpdateBatch updateBatch; + + Q_FOREACH( const Item &item, items ) { + const Collection collection = item.parentCollection(); + const qint64 revision = collection.remoteRevision().toLongLong(); + + RevisionChangeMap &changesByRevision = d->mChangesByCollection[ collection.id() ]; + OldIdItemMap &changes = changesByRevision[ revision ]; + changes.insert( item.remoteId(), item ); + + if ( !updateBatch.collection.isValid() ) { + updateBatch.collection = collection; + } else if ( updateBatch.collection != collection ) { + d->mPendingUpdates << updateBatch; + updateBatch.items.clear(); + updateBatch.collection = collection; + } + + updateBatch.items << item; + } + + if ( updateBatch.collection.isValid() ) { + d->mPendingUpdates << updateBatch; + } + + QMetaObject::invokeMethod( this, "processNextBatch", Qt::QueuedConnection ); +} + +QString CompactChangeHelper::currentRemoteId( const Item &item ) const +{ + const Collection collection = item.parentCollection(); + const qint64 revision = collection.remoteRevision().toLongLong(); + + QString remoteId = item.remoteId(); + + const CollectionRevisionMap::const_iterator colIt = d->mChangesByCollection.constFind( collection.id() ); + if ( colIt != d->mChangesByCollection.constEnd() ) { + // find revision and iterate until the highest available one + RevisionChangeMap::const_iterator revIt = colIt->constFind( revision ); + for ( ; revIt != colIt->constEnd(); ++revIt ) { + const OldIdItemMap::const_iterator idIt = revIt->constFind( remoteId ); + if ( idIt != revIt->constEnd() ) { + remoteId = idIt.value().attribute()->remoteId(); + } else { + break; + } + } + } + + if ( item.remoteId() != remoteId ) { + kDebug() << "item (id=" << item.id() << "remoteId=" << item.remoteId() + << "), col(id=" << collection.id() << ", name=" << collection.name() + << ", revision=" << revision << ") in compact change set (revisions=" + << colIt->keys() << ": current remoteId=" << remoteId; + } + + return remoteId; +} + +void CompactChangeHelper::checkCollectionChanged( const Collection &collection ) +{ + const qint64 revision = collection.remoteRevision().toLongLong(); + //kDebug() << "col.id=" << collection.id() << ", remoteId=" << collection.remoteId() + // << "revision=" << revision; + + const CollectionRevisionMap::iterator colIt = d->mChangesByCollection.find( collection.id() ); + if ( colIt != d->mChangesByCollection.end() ) { + kDebug() << "matching change map found with" << colIt->count() << "entries"; + // remove all revisions until the seen one appears + RevisionChangeMap::iterator revIt = colIt->begin(); + while ( revIt != colIt->end() && revIt.key() <= revision ) { + kDebug() << "removing entry for revision" << revIt.key(); + revIt = colIt->erase( revIt ); + } + + if ( revIt == colIt->end() ) { + kDebug() << "all change maps gone"; + d->mChangesByCollection.erase( colIt ); + } + } +} + +#include "moc_compactchangehelper.cpp" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/compactchangehelper.h b/kdepim-runtime/resources/mixedmaildir/compactchangehelper.h new file mode 100644 index 00000000..ad6a47ae --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/compactchangehelper.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 COMPACTCHANGEHELPER_H +#define COMPACTCHANGEHELPER_H + +#include + +template class QList; + +namespace Akonadi { + class Collection; + class Item; + + typedef QList ItemList; +} + +class CompactChangeHelper : public QObject +{ + Q_OBJECT + + public: + explicit CompactChangeHelper( const QByteArray &sessionId, QObject *parent = 0 ); + + ~CompactChangeHelper(); + + void addChangedItems( const Akonadi::ItemList &items ); + + QString currentRemoteId( const Akonadi::Item &item ) const; + + void checkCollectionChanged( const Akonadi::Collection &collection ); + + private: + class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void processNextBatch() ) + Q_PRIVATE_SLOT( d, void processNextItem() ) + Q_PRIVATE_SLOT( d, void itemFetchResult( KJob* ) ) +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/configdialog.cpp b/kdepim-runtime/resources/mixedmaildir/configdialog.cpp new file mode 100644 index 00000000..90374aef --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/configdialog.cpp @@ -0,0 +1,93 @@ +/* + Copyright (c) 2008 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 "configdialog.h" +#include "settings.h" + +#include "libmaildir/maildir.h" + +#include +#include +#include + +using KPIM::Maildir; + +ConfigDialog::ConfigDialog(QWidget * parent) : + KDialog( parent ), + mToplevelIsContainer( false ) +{ + setCaption( i18n( "Select a KMail Mail folder" ) ); + ui.setupUi( mainWidget() ); + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); + ui.kcfg_Path->setMode( KFile::Directory | KFile::ExistingOnly ); + ui.kcfg_Path->setUrl( KUrl( Settings::self()->path() ) ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); + connect( ui.kcfg_Path->lineEdit(), SIGNAL(textChanged(QString)), SLOT(checkPath()) ); + ui.kcfg_Path->lineEdit()->setFocus(); + checkPath(); +} + +void ConfigDialog::checkPath() +{ + if ( ui.kcfg_Path->url().isEmpty() ) { + ui.statusLabel->setText( i18n( "The selected path is empty.") ); + enableButton( Ok, false); + return; + } + bool ok = false; + mToplevelIsContainer = false; + QDir d( ui.kcfg_Path->url().toLocalFile() ); + + if ( d.exists() ) { + Maildir md( d.path() ); + if ( !md.isValid() ) { + Maildir md2( d.path(), true ); + if ( md2.isValid() ) { + ui.statusLabel->setText( i18n( "The selected path contains valid Maildir folders." ) ); + mToplevelIsContainer = true; + ok = true; + } else { + ui.statusLabel->setText( md.lastError() ); + } + } else { + ui.statusLabel->setText( i18n( "The selected path is a valid Maildir." ) ); + ok = true; + } + } else { + d.cdUp(); + if ( d.exists() ) { + ui.statusLabel->setText( i18n( "The selected path does not exist yet, a new Maildir will be created." ) ); + ok = true; + } else { + ui.statusLabel->setText( i18n( "The selected path does not exist." ) ); + } + } + enableButton( Ok, ok ); +} + +void ConfigDialog::save() +{ + mManager->updateSettings(); + Settings::self()->setPath( ui.kcfg_Path->url().isLocalFile() ? ui.kcfg_Path->url().toLocalFile() : ui.kcfg_Path->url().path() ); + Settings::self()->setTopLevelIsContainer( mToplevelIsContainer ); + Settings::self()->writeConfig(); +} + diff --git a/kdepim-runtime/resources/mixedmaildir/configdialog.h b/kdepim-runtime/resources/mixedmaildir/configdialog.h new file mode 100644 index 00000000..1982901f --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/configdialog.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2008 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +#include "ui_settings.h" + +class KConfigDialogManager; + +class ConfigDialog : public KDialog +{ + Q_OBJECT + public: + explicit ConfigDialog( QWidget *parent = 0 ); + + private slots: + void checkPath(); + void save(); + + private: + Ui::ConfigDialog ui; + KConfigDialogManager* mManager; + bool mToplevelIsContainer; +}; + +#endif diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/CMakeLists.txt b/kdepim-runtime/resources/mixedmaildir/kmindexreader/CMakeLists.txt new file mode 100644 index 00000000..141e8058 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/CMakeLists.txt @@ -0,0 +1,23 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${KDE4_INCLUDES} +) + +add_subdirectory(tests) + +########### next target ############### + +set(kmindexreader_LIB_SRCS + kmindexreader.cpp +) + +kde4_add_library(kmindexreader ${LIBRARY_TYPE} ${kmindexreader_LIB_SRCS}) + +target_link_libraries(kmindexreader + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} +) + +set_target_properties(kmindexreader PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) + +install(TARGETS kmindexreader ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.cpp b/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.cpp new file mode 100644 index 00000000..5fed5a55 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.cpp @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2010 Casey Link + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + + * This file includes code from old files from previous KDE versions: + * Copyright (c) 2003 Andreas Gungl + * Copyright (c) 1996-1998 Stefan Taferner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kmindexreader.h" + +#include +#include +#include +using Akonadi::MessageStatus; +#include + +#include + +//BEGIN: Magic definitions from old kmail code +#ifdef HAVE_BYTESWAP_H +#include +#endif + +static const int INDEX_VERSION = 1506; +const size_t readCount = 1; +#ifndef MAX_LINE + static const int MAX_LINE = 4096; +#endif + +// We define functions as kmail_swap_NN so that we don't get compile errors +// on platforms where bswap_NN happens to be a function instead of a define. + +/* Swap bytes in 16 bit value. */ +#ifdef bswap_16 +#define kmail_swap_16(x) bswap_16(x) +#else +#define kmail_swap_16(x) \ + ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) +#endif + +/* Swap bytes in 32 bit value. */ +#ifdef bswap_32 +#define kmail_swap_32(x) bswap_32(x) +#else +#define kmail_swap_32(x) \ + ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#endif + +/* Swap bytes in 64 bit value. */ +#ifdef bswap_64 +#define kmail_swap_64(x) bswap_64(x) +#else +#define kmail_swap_64(x) \ + ((((x) & 0xff00000000000000ull) >> 56) \ + | (((x) & 0x00ff000000000000ull) >> 40) \ + | (((x) & 0x0000ff0000000000ull) >> 24) \ + | (((x) & 0x000000ff00000000ull) >> 8) \ + | (((x) & 0x00000000ff000000ull) << 8) \ + | (((x) & 0x0000000000ff0000ull) << 24) \ + | (((x) & 0x000000000000ff00ull) << 40) \ + | (((x) & 0x00000000000000ffull) << 56)) +#endif + +/** The old status format, only one at a time possible. Needed + for upgrade path purposes. */ +typedef enum +{ + KMLegacyMsgStatusUnknown=' ', + KMLegacyMsgStatusNew='N', + KMLegacyMsgStatusUnread='U', + KMLegacyMsgStatusRead='R', + KMLegacyMsgStatusOld='O', + KMLegacyMsgStatusDeleted='D', + KMLegacyMsgStatusReplied='A', + KMLegacyMsgStatusForwarded='F', + KMLegacyMsgStatusQueued='Q', + KMLegacyMsgStatusSent='S', + KMLegacyMsgStatusFlag='G' +} KMLegacyMsgStatus; + +//END: Magic definitions from old kmail code + +//BEGIN: KMIndexMsg methods + +KMIndexData::KMIndexData() : mPartsCacheBuilt( false ) +{ + const uint count = sizeof( mCachedLongParts ) / sizeof( unsigned long ); + for ( uint i = 0; i < count; ++i ) { + mCachedLongParts[ i ] = 0; + } +} + +MessageStatus& KMIndexData::status() +{ + if ( mStatus.isOfUnknownStatus() ) { + mStatus.fromQInt32( mCachedLongParts[KMIndexReader::MsgStatusPart] ); + if ( mStatus.isOfUnknownStatus() ) { + // We are opening an old index for the first time, get the legacy + // status and merge it in. + // This is kept to provide an upgrade path from the old single + // status to the new multiple status scheme. + KMLegacyMsgStatus legacyMsgStatus = (KMLegacyMsgStatus) mCachedLongParts[KMIndexReader::MsgLegacyStatusPart]; + mStatus.setRead(); + switch ( legacyMsgStatus ) { + case KMLegacyMsgStatusUnknown: + mStatus.clear(); + break; + case KMLegacyMsgStatusUnread: + mStatus.setRead( false ); + break; + case KMLegacyMsgStatusRead: + mStatus.setRead(); + break; + case KMLegacyMsgStatusDeleted: + mStatus.setDeleted(); + break; + case KMLegacyMsgStatusReplied: + mStatus.setReplied(); + break; + case KMLegacyMsgStatusForwarded: + mStatus.setForwarded(); + break; + case KMLegacyMsgStatusQueued: + mStatus.setQueued(); + break; + case KMLegacyMsgStatusSent: + mStatus.setSent(); + break; + case KMLegacyMsgStatusFlag: + mStatus.setImportant(); + break; + default: + break; + } + + } + } + return mStatus; +} + +QStringList KMIndexData::tagList() const +{ + return mCachedStringParts[KMIndexReader::MsgTagPart].split( ',', QString::SkipEmptyParts ); +} + +quint64 KMIndexData::uid() const +{ + return mCachedLongParts[KMIndexReader::MsgUIDPart]; +} + +bool KMIndexData::isEmpty() const +{ + return !mPartsCacheBuilt; +} + +//END: KMIndexMsg methods + +KMIndexReader::KMIndexReader(const QString& indexFile) +: mIndexFileName( indexFile ) +, mIndexFile( indexFile ) +, mConvertToUtf8 ( false ) +, mIndexSwapByteOrder( false ) +, mHeaderOffset( 0 ) +, mError( false ) +{ + if( !mIndexFile.exists() ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "file doesn't exist"; + mError = true; + } + + if( !mIndexFile.open( QIODevice::ReadOnly ) ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "file cant be open"; + mError = true; + } + + mFp = fdopen( mIndexFile.handle(), "r"); +} + +KMIndexReader::~KMIndexReader() +{ + if (mFp) + fclose(mFp); +} + +bool KMIndexReader::error() const +{ + return mError; +} + +KMIndexDataPtr KMIndexReader::dataByOffset( quint64 offset ) const +{ + QHash::const_iterator it = mMsgByOffset.constFind( offset ); + if ( it == mMsgByOffset.constEnd() ) { + return KMIndexDataPtr(); + } + + return it.value(); +} + +KMIndexDataPtr KMIndexReader::dataByFileName( const QString &fileName ) const +{ + QHash::const_iterator it = mMsgByFileName.constFind( fileName ); + if ( it == mMsgByFileName.constEnd() ) { + return KMIndexDataPtr(); + } + + return it.value(); +} + +bool KMIndexReader::readHeader( int *version ) +{ + int indexVersion; + Q_ASSERT( mFp != 0 ); + mIndexSwapByteOrder = false; + mIndexSizeOfLong = sizeof( long ); + + int ret = fscanf( mFp, "# KMail-Index V%d\n", &indexVersion ); + if ( ret == EOF || ret == 0 ) + return false; // index file has invalid header + if( version ) + *version = indexVersion; + if ( indexVersion < 1505 ) { + if( indexVersion == 1503 ) { + kWarning() << "Need to convert old index file" << mIndexFileName << "to utf-8"; + mConvertToUtf8 = true; + } + return true; + } else if ( indexVersion == 1505 ) { + } else if ( indexVersion < INDEX_VERSION ) { + kFatal() << "Index file" << mIndexFileName << "is out of date. What to do?"; +// createIndexFromContents(); + return false; + } else if( indexVersion > INDEX_VERSION ) { + kFatal() << "index file of newer version"; + return false; + } else { + // Header + quint32 byteOrder = 0; + quint32 sizeOfLong = sizeof( long ); // default + + quint32 header_length = 0; + KDE_fseek( mFp, sizeof( char ), SEEK_CUR ); + if ( fread( &header_length, sizeof( header_length ), readCount, mFp ) != readCount ) { + kWarning() << "Failed to read header_length"; + return false; + } + if ( header_length > 0xFFFF ) + header_length = kmail_swap_32( header_length ); + + off_t endOfHeader = KDE_ftell( mFp ) + header_length; + + bool needs_update = true; + // Process available header parts + if ( header_length >= sizeof( byteOrder ) ) { + if ( fread( &byteOrder, sizeof( byteOrder ), readCount, mFp ) != readCount ) { + kWarning() << "Failed to read byteOrder"; + return false; + } + mIndexSwapByteOrder = ( byteOrder == 0x78563412 ); + header_length -= sizeof( byteOrder ); + + if ( header_length >= sizeof( sizeOfLong ) ) { + if ( fread( &sizeOfLong, sizeof( sizeOfLong ), readCount, mFp ) != readCount ) { + kWarning() << "Failed to read sizeOfLong"; + return false; + } + if ( mIndexSwapByteOrder ) + sizeOfLong = kmail_swap_32( sizeOfLong ); + mIndexSizeOfLong = sizeOfLong; + header_length -= sizeof( sizeOfLong ); + needs_update = false; + } + } + if ( needs_update || mIndexSwapByteOrder || ( mIndexSizeOfLong != sizeof( long ) ) ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "DIRTY!"; +// setDirty( true ); + } + // Seek to end of header + KDE_fseek( mFp, endOfHeader, SEEK_SET ); + + if ( mIndexSwapByteOrder ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Index File has byte order swapped!"; + } + if ( mIndexSizeOfLong != sizeof( long ) ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Index File sizeOfLong is" << mIndexSizeOfLong << "while sizeof(long) is" << sizeof(long) << "!"; + } + + } + return true; +} + +bool KMIndexReader::readIndex() +{ + qint32 len; + KMIndexData* msg; + + Q_ASSERT( mFp != 0 ); + rewind( mFp ); + + mMsgList.clear(); + mMsgByFileName.clear(); + mMsgByOffset.clear(); + + int version; + + if ( !readHeader( &version ) ) return false; + + mHeaderOffset = KDE_ftell( mFp ); + + // loop through the entire index + while ( !feof( mFp ) ) { + //kDebug( KDE_DEFAULT_DEBUG_AREA ) << "NEW MSG!"; + msg = 0; + // check version (parsed by readHeader) + // because different versions must be + // parsed differently + //kDebug( KDE_DEFAULT_DEBUG_AREA ) << "parsing version" << version; + if( version >= 1505 ) { + // parse versions >= 1505 + if( !fread( &len, sizeof( len ), 1, mFp ) ) + break; + + // swap bytes if needed + if ( mIndexSwapByteOrder ) + len = kmail_swap_32( len ); + + off_t offs = KDE_ftell( mFp ); + if( KDE_fseek( mFp, len, SEEK_CUR ) ) + break; + msg = new KMIndexData(); + fillPartsCache( msg, offs, len ); + } else { + ////////////////////// + //BEGIN UNTESTED CODE + ////////////////////// + //parse verions < 1505 + QByteArray line( MAX_LINE, '\0' ); + if ( fgets( line.data(), MAX_LINE, mFp ) == NULL ) break; + if ( feof( mFp ) ) break; + if ( *line.data() == '\0' ) { + // really, i have no idea when or how this would occur + // but we probably want to know if it does - Casey + kWarning() << "Unknowable bad occurred"; + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "fclose(mFp = " << mFp << ")"; + fclose( mFp ); + mFp = 0; + mMsgList.clear(); + mMsgByFileName.clear(); + mMsgByOffset.clear(); + return false; + } + off_t offs = KDE_ftell( mFp ); + if ( KDE_fseek( mFp, len, SEEK_CUR ) ) + break; + msg = new KMIndexData; + fromOldIndexString( msg, line, mConvertToUtf8 ); + + fillPartsCache( msg, offs, len ); + ////////////////////// + //END UNTESTED CODE + ////////////////////// + } + if ( !msg ) + break; + + if ( msg->status().isDeleted() ) { + delete msg; // skip messages that are marked as deleted + continue; + } +#ifdef OBSOLETE +// else if (mi->isNew()) +// { +// mi->setStatus(KMMsgStatusUnread); +// mi->setDirty(false); +// } +#endif + KMIndexDataPtr msgPtr( msg ); + mMsgList.append( msgPtr ); + const QString fileName = msg->mCachedStringParts[ MsgFilePart ]; + if ( !fileName.isEmpty() ) { + mMsgByFileName.insert( fileName, msgPtr ); + } + + const quint64 offset = msg->mCachedLongParts[ MsgOffsetPart ]; + if ( offset > 0 ) { + mMsgByOffset.insert( offset, msgPtr ); + } + } // end while + + return true; +} + +//--- For compatibility with old index files +bool KMIndexReader::fromOldIndexString( KMIndexData* msg, const QByteArray& str, bool toUtf8) +{ + Q_UNUSED( toUtf8 ) +// const char *start, *offset; +// msg->modifiers = KMMsgInfoPrivate::ALL_SET; +// msg->xmark = str.mid(33, 3).trimmed(); +// msg->folderOffset = str.mid(2,9).toULong(); +// msg->msgSize = str.mid(12,9).toULong(); +// msg->date = (time_t)str.mid(22,10).toULong(); +// mStatus.setStatusFromStr( str ); +// if (toUtf8) { +// msg->subject = str.mid(37, 100).trimmed(); +// msg->from = str.mid(138, 50).trimmed(); +// msg->to = str.mid(189, 50).trimmed(); +// } else { +// start = offset = str.data() + 37; +// while (*start == ' ' && start - offset < 100) start++; +// msg->subject = QString::fromUtf8(str.mid(start - str.data(), +// 100 - (start - offset)), 100 - (start - offset)); +// start = offset = str.data() + 138; +// while (*start == ' ' && start - offset < 50) start++; +// msg->from = QString::fromUtf8(str.mid(start - str.data(), +// 50 - (start - offset)), 50 - (start - offset)); +// start = offset = str.data() + 189; +// while (*start == ' ' && start - offset < 50) start++; +// msg->to = QString::fromUtf8(str.mid(start - str.data(), +// 50 - (start - offset)), 50 - (start - offset)); +// } +// msg->replyToIdMD5 = str.mid(240, 22).trimmed(); +// msg->msgIdMD5 = str.mid(263, 22).trimmed(); + msg->mStatus.setStatusFromStr( str ); + return true; +} + +//----------------------------------------------------------------------------- + +static void swapEndian( QString &str ) +{ + QChar *data = str.data(); + while ( !data->isNull() ) { + *data = kmail_swap_16( data->unicode() ); + data++; + } +} + +static int g_chunk_length = 0, g_chunk_offset=0; +static uchar *g_chunk = 0; + +namespace { + template < typename T > void copy_from_stream( T & x ) { + if( g_chunk_offset + int( sizeof( T ) ) > g_chunk_length ) { + g_chunk_offset = g_chunk_length; + kWarning() << "This should never happen.."; + x = 0; + } else { + // the memcpy is optimized out by the compiler for the values + // of sizeof(T) that is called with + memcpy( &x, g_chunk + g_chunk_offset, sizeof( T ) ); + g_chunk_offset += sizeof( T ); + } + } +} + +bool KMIndexReader::fillPartsCache( KMIndexData* msg, off_t indexOff, short int indexLen ) +{ + if( !msg ) + return false; + //kDebug( KDE_DEFAULT_DEBUG_AREA ); + if ( g_chunk_length < indexLen ) + g_chunk = (uchar *)realloc( g_chunk, g_chunk_length = indexLen ); + + off_t first_off = KDE_ftell( mFp ); + KDE_fseek( mFp, indexOff, SEEK_SET ); + if ( fread( g_chunk, indexLen, readCount, mFp ) != readCount ) { + kWarning() << "Failed to read index"; + return false; + } + KDE_fseek( mFp, first_off, SEEK_SET ); + + MsgPartType type; + quint16 len; + off_t ret = 0; + for ( g_chunk_offset = 0; g_chunk_offset < indexLen; g_chunk_offset += len ) { + quint32 tmp; + copy_from_stream( tmp ); + copy_from_stream( len ); + if ( mIndexSwapByteOrder ) { + tmp = kmail_swap_32( tmp ); + len = kmail_swap_16( len ); + } + type = (MsgPartType) tmp; + if ( g_chunk_offset + len > indexLen ) { + kWarning() << "g_chunk_offset + len > indexLen" << "This should never happen.."; + return false; + } + // Only try to create strings if the part is really a string part, see declaration of + // MsgPartType + if ( len && ( ( type >= 1 && type <= 6 ) || type == 11 || type == 14 || type == 15 || type == 19 ) ) { + + // This works because the QString constructor does a memcpy. + // Otherwise we would need to be concerned about the alignment. + QString str( (QChar *)( g_chunk + g_chunk_offset ), len/2 ); + msg->mCachedStringParts[type] = str; + + // Normally we need to swap the byte order because the QStrings are written + // in the style of Qt2 (MSB -> network ordered). + // QStrings in Qt3 expect host ordering. + // On e.g. Intel host ordering is LSB, on e.g. Sparc it is MSB. + +# if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // Byte order is little endian (swap is true) + swapEndian( msg->mCachedStringParts[type] ); +# else + // Byte order is big endian (swap is false) +# endif + } else if( ( type >= 7 && type <= 10 ) || type == 12 || type == 13 || ( type >= 16 && type <= 18 ) ) { + Q_ASSERT( mIndexSizeOfLong == len ); + if ( mIndexSizeOfLong == sizeof( ret ) ) { + //kDebug( KDE_DEFAULT_DEBUG_AREA ) << "mIndexSizeOfLong == sizeof(ret)"; + // this memcpy replaces the original call to copy_from_stream + // so that g_chunk_offset is not changed + memcpy( &ret, g_chunk + g_chunk_offset, sizeof( ret ) ); + if ( mIndexSwapByteOrder ) { + if ( sizeof(ret) == 4 ) + ret = kmail_swap_32( ret ); + else + ret = kmail_swap_64( ret ); + } + } + ////////////////////// + //BEGIN UNTESTED CODE + ////////////////////// + else if ( mIndexSizeOfLong == 4 ) { + // Long is stored as 4 bytes in index file, sizeof(long) = 8 + quint32 ret_32; + // this memcpy replaces the original call to copy_from_stream + // so that g_chunk_offset is not changed + memcpy( &ret_32, g_chunk + g_chunk_offset, sizeof( quint32 ) ); + if ( mIndexSwapByteOrder ) + ret_32 = kmail_swap_32( ret_32 ); + ret = ret_32; + } else if ( mIndexSizeOfLong == 8 ) { + // Long is stored as 8 bytes in index file, sizeof(long) = 4 + quint32 ret_1; + quint32 ret_2; + // these memcpys replace the original calls to copy_from_stream + // so that g_chunk_offset is not changed + memcpy( &ret_1, g_chunk + g_chunk_offset, sizeof( quint32 ) ); + memcpy( &ret_2, g_chunk + g_chunk_offset, sizeof( quint32 ) ); + if ( !mIndexSwapByteOrder ) { + // Index file order is the same as the order of this CPU. +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // Index file order is little endian + ret = ret_1; // We drop the 4 most significant bytes +#else + // Index file order is big endian + ret = ret_2; // We drop the 4 most significant bytes +#endif + } else { + // Index file order is different from this CPU. +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // Index file order is big endian + ret = ret_2; // We drop the 4 most significant bytes +#else + // Index file order is little endian + ret = ret_1; // We drop the 4 most significant bytes +#endif + // We swap the result to host order. + ret = kmail_swap_32( ret ); + } + } + ////////////////////// + //END UNTESTED CODE + ////////////////////// + msg->mCachedLongParts[type] = ret; + } + } // for loop + msg->mPartsCacheBuilt = true; + return true; +} + +//----------------------------------------------------------------------------- + +QList< KMIndexDataPtr > KMIndexReader::messages() +{ + return mMsgList; +} diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.h b/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.h new file mode 100644 index 00000000..65d18422 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2010 Casey Link + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KMINDEXREADER_H +#define KMINDEXREADER_H + +#include "kmindexreader_export.h" + +#include +using Akonadi::MessageStatus; + +#include +#include +#include +#include + +#include +#include +#include + +namespace boost +{ + template class shared_ptr; +} + +class KMINDEXREADER_EXPORT KMIndexData { + Q_DISABLE_COPY( KMIndexData ) +public: + KMIndexData(); + /** Status object of the message. */ + MessageStatus& status(); + QStringList tagList() const ; + quint64 uid() const; + bool isEmpty() const; + +private: + QString mCachedStringParts[20]; + unsigned long mCachedLongParts[20]; + bool mPartsCacheBuilt; + + MessageStatus mStatus; + friend class KMIndexReader; + friend class TestIdxReader; +}; + +typedef boost::shared_ptr KMIndexDataPtr; + +/** + * @short A class to read legacy kmail (< 4.5) index files + * + * This class provides read-only access to legacy kmail index files. + * It uses old kmfolderindex code, authors attributed as appropriate. + * @author Casey Link + */ +class KMINDEXREADER_EXPORT KMIndexReader { +public: + explicit KMIndexReader ( const QString &indexFile ); + ~KMIndexReader(); + + bool error() const; + + /** + * begins the index reading process + */ + bool readIndex(); + + enum MsgPartType { + MsgNoPart = 0, + //unicode strings + MsgFromPart = 1, + MsgSubjectPart = 2, + MsgToPart = 3, + MsgReplyToIdMD5Part = 4, + MsgIdMD5Part = 5, + MsgXMarkPart = 6, + //unsigned long + MsgOffsetPart = 7, + MsgLegacyStatusPart = 8, + MsgSizePart = 9, + MsgDatePart = 10, + // unicode string + MsgFilePart = 11, + // unsigned long + MsgCryptoStatePart = 12, + MsgMDNSentPart = 13, + //another two unicode strings + MsgReplyToAuxIdMD5Part = 14, + MsgStrippedSubjectMD5Part = 15, + // and another unsigned long + MsgStatusPart = 16, + MsgSizeServerPart = 17, + MsgUIDPart = 18, + // unicode string + MsgTagPart = 19 + }; + + KMIndexDataPtr dataByOffset( quint64 offset ) const; + + KMIndexDataPtr dataByFileName( const QString &fileName ) const; + +private: + + + /** + * Reads the header of an index + */ + bool readHeader ( int *version ); + + /** + * creates a message object from an old index files + */ + bool fromOldIndexString ( KMIndexData* msg, const QByteArray& str, bool toUtf8 ); + + bool fillPartsCache ( KMIndexData* msg, off_t off, short int len ); + + QList messages(); + + QString mIndexFileName; + QFile mIndexFile; + FILE* mFp; + + bool mConvertToUtf8; + bool mIndexSwapByteOrder; // Index file was written with swapped byte order + int mIndexSizeOfLong; // Index file was written with longs of this size + off_t mHeaderOffset; + + bool mError; + + /** list of index entries or messages */ + QList mMsgList; + QHash mMsgByFileName; + QHash mMsgByOffset; + friend class TestIdxReader; +}; + +#endif // KMINDEXREADER_H diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader_export.h b/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader_export.h new file mode 100644 index 00000000..824c90ac --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/kmindexreader_export.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + Author: Kevin Krammer + + 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 KMINDEXREADER_EXPORT_H +#define KMINDEXREADER_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef KMINDEXREADER_EXPORT +# if defined(KDEPIM_STATIC_LIBS) + /* No export/import for static libraries */ +# define KMINDEXREADER_EXPORT +# elif defined(MAKE_KMINDEXREADER_LIB) + /* We are building this library */ +# define KMINDEXREADER_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KMINDEXREADER_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef MBOX_EXPORT_DEPRECATED +# define MBOX_EXPORT_DEPRECATED KDE_DEPRECATED MBOX_EXPORT +# endif + +#endif diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/CMakeLists.txt b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/CMakeLists.txt new file mode 100644 index 00000000..9109c755 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/CMakeLists.txt @@ -0,0 +1,14 @@ +add_definitions(-DTEST_PATH=${CMAKE_CURRENT_SOURCE_DIR}) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${mixedmaildirresource_SOURCE_DIR}/kmindexreader + ${KDE4_INCLUDES} +) + +set( testidxreader_SRCS + testidxreader.cpp +) + +kde4_add_unit_test( testidxreader ${testidxreader_SRCS} ) + +target_link_libraries( testidxreader ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} kmindexreader ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ) diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/TestIdxReader_data.h b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/TestIdxReader_data.h new file mode 100644 index 00000000..f5535d28 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/TestIdxReader_data.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 Casey Link + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef TESTIDXREADER_DATA +#define TESTIDXREADER_DATA + +/* contains one maildir with one email inside*/ +static const char mailDirOneEmail[] = + "IyBLTWFpbC1JbmRleCBWMTUwNgoACAAAAHhWNBIIAAAAagEAAAUAAAAAAAgAAAAIAAAAAAAAAAAA" + "AQAAABQAAEMAYQBzAGUAeQAgAEwAaQBuAGsTAAAAAAACAAAAIAAAaABlAGwAbABvACAAZgByAG8A" + "bQAgAGsAbQBhAGkAbAMAAAAcAABLAEQAQQBCACAARQBtAHAAbABvAHkAZQBlAHMEAAAAAAAGAAAA" + "AAALAAAANAAAMQAyADcAMQA1ADEAMwA1ADMANwAuADEAOQA1ADcANgAuADEATABLADYAZQA6ADIA" + "LABTCQAAAAgAhAIAAAAAAAAHAAAACAAAAAAAAAAAAAoAAAAIAMHByUsAAAAADAAAAAgATgBOAAAA" + "AAANAAAACAAgAAAAAAAAAA4AAAAAAA8AAAAsAABxADEAUABJAFkAbgBhAGwAVwB3AFYAQgAzAHEA" + "aAB2AEYAUABUAHQAaQBBEAAAAAgABAAAAAAAAAARAAAACAAAAAAAAAAAABIAAAAIAAAAAAAAAAAA"; + +/* + Folder: "Test1" type:Maildir + Message1: "foo bar" Important, Unread + Message2: "hello from kmail" Read, tagged "N5tUHPOZFf" + */ +static const char mailDirTwoEmailOneTagFlags[] = +"IyBLTWFpbC1JbmRleCBWMTUwNgoACAAAAHhWNBIIAAAAlAEAAAUAAAAAAAgAAAAIAAAAAAAAAAAA" +"AQAAABQAAEMAYQBzAGUAeQAgAEwAaQBuAGsTAAAAKgAATgA1AHQAVQBIAFAATwBaAEYAZgAsAFUA" +"RgBNAEUAagBHAHMAUQA5AG8CAAAAIAAAaABlAGwAbABvACAAZgByAG8AbQAgAGsAbQBhAGkAbAMA" +"AAAcAABLAEQAQQBCACAARQBtAHAAbABvAHkAZQBlAHMEAAAAAAAGAAAAAAALAAAANAAAMQAyADcA" +"MQA1ADEAMwA1ADMANwAuADEAOQA1ADcANgAuADEATABLADYAZQA6ADIALABTCQAAAAgAhAIAAAAA" +"AAAHAAAACAAAAAAAAAAAAAoAAAAIAMHByUsAAAAADAAAAAgATgBOAAAAAAANAAAACAAgAAAAAAAA" +"AA4AAAAAAA8AAAAsAABxADEAUABJAFkAbgBhAGwAVwB3AFYAQgAzAHEAaAB2AEYAUABUAHQAaQBB" +"EAAAAAgABAAAAAAAAAARAAAACAAAAAAAAAAAABIAAAAIAAAAAAAAAAAASAEAAAUAAAAAAAgAAAAI" +"AAAAAAAAAAAAAQAAABQAAEMAYQBzAGUAeQAgAEwAaQBuAGsTAAAAAAACAAAADgAAZgBvAG8AIABi" +"AGEAcgMAAAAWAABvAG4AZQBAAG8AbgBlAC4AYwBvAG0EAAAAAAAGAAAAAAALAAAAKgAAMQAyADcA" +"MQA3ADcAMwAwADEAOQAuADUANwA1ADQALgBwAFcAcQBaAFMJAAAACABKAgAAAAAAAAcAAAAIAAAA" +"AAAAAAAACgAAAAgAW7fNSwAAAAAMAAAACABOAE4AAAAAAA0AAAAIACAAAAAAAAAADgAAAAAADwAA" +"ACwAAE0AbgB0AHYAQgAwAE4AWQBFAFMATwBiAHgASAA0AFYAUgBEAFUAeQBjAHcQAAAACAACAgAA" +"AAAAABEAAAAIAAAAAAAAAAAAEgAAAAgAAAAAAAAAAAA="; + +#endif diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/data/.keep b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/data/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/data/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.cpp b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.cpp new file mode 100644 index 00000000..d51cc9de --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 Casey Link + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "testidxreader.h" + +#include "../kmindexreader.h" + +#include "TestIdxReader_data.h" + +#include +using Akonadi::MessageStatus; +#include + +#include +#include +#include + +#include + +QTEST_MAIN ( TestIdxReader ) + + +TestIdxReader::TestIdxReader() { +} + +void TestIdxReader::testError() { + KMIndexReader reader ( "IDoNotExist" ); + + QVERIFY ( reader.error() == true ); +} + +void TestIdxReader::testReadHeader() { + QTemporaryFile tmp; + if ( !tmp.open() ) { + qDebug() << "Could not open temp file."; + return; + } + tmp.write ( QByteArray::fromBase64 ( mailDirOneEmail ) ); + tmp.close(); + KMIndexReader reader ( tmp.fileName() ); + + QVERIFY ( reader.error() == false ); + + int version = 0; + bool success = reader.readHeader ( &version ); + + QVERIFY ( success == true ); + QCOMPARE ( version, 1506 ); + + QVERIFY ( reader.error() == false ); +} + +void TestIdxReader::testRead() { + QTemporaryFile tmp; + if ( !tmp.open() ) { + qDebug() << "Could not open temp file."; + return; + } + tmp.write ( QByteArray::fromBase64 ( mailDirTwoEmailOneTagFlags ) ); + tmp.close(); + KMIndexReader reader ( tmp.fileName() ); + QVERIFY ( reader.error() == false ); + bool success = reader.readIndex(); + QVERIFY ( success == true ); + + QVERIFY ( reader.messages().size() == 2 ); + + KMIndexDataPtr msg = reader.messages().front(); + + QString subject = msg->mCachedStringParts[KMIndexReader::MsgSubjectPart]; + MessageStatus status; + status.fromQInt32 ( msg->mCachedLongParts[KMIndexReader::MsgStatusPart] ); + QCOMPARE ( subject, QString ( "hello from kmail" ) ); + QVERIFY ( !status.isImportant() ); + QVERIFY ( !msg->status().isImportant() ); + QVERIFY ( msg->status().isRead() ); + QVERIFY ( msg->tagList().contains( "N5tUHPOZFf" ) ); + + msg = reader.messages().back(); + status.fromQInt32 ( msg->mCachedLongParts[KMIndexReader::MsgStatusPart] ); + subject = msg->mCachedStringParts[KMIndexReader::MsgSubjectPart]; + QCOMPARE ( subject, QString ( "foo bar" ) ); + QVERIFY ( status.isImportant() ); + QVERIFY ( msg->status().isImportant() ); + QVERIFY ( !msg->status().isRead() ); + QVERIFY ( msg->tagList().size() == 0 ); +} + diff --git a/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.h b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.h new file mode 100644 index 00000000..0fd65eee --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/kmindexreader/tests/testidxreader.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 Casey Link + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef TESTIDXREADER_H +#define TESTIDXREADER_H + +#include + +class TestIdxReader : public QObject +{ +Q_OBJECT +public: + TestIdxReader(); +private slots: + void testError(); + void testReadHeader(); + void testRead(); +private: + +}; + +#endif // TESTIDXREADER_H diff --git a/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.cpp b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.cpp new file mode 100644 index 00000000..e28967da --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.cpp @@ -0,0 +1,835 @@ +/* This file is part of the KDE project + Copyright (c) 2007 Till Adam + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + Copyright (C) 2011 Kevin Krammer + + 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 "mixedmaildirresource.h" + +#include "compactchangehelper.h" +#include "configdialog.h" +#include "mixedmaildirstore.h" +#include "settings.h" +#include "settingsadaptor.h" +#include "retrieveitemsjob.h" +#include "createandsettagsjob.h" + + +#include "filestore/collectioncreatejob.h" +#include "filestore/collectiondeletejob.h" +#include "filestore/collectionfetchjob.h" +#include "filestore/collectionmodifyjob.h" +#include "filestore/collectionmovejob.h" +#include "filestore/entitycompactchangeattribute.h" +#include "filestore/itemcreatejob.h" +#include "filestore/itemdeletejob.h" +#include "filestore/itemfetchjob.h" +#include "filestore/itemmodifyjob.h" +#include "filestore/itemmovejob.h" +#include "filestore/storecompactjob.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace Akonadi; + + + +MixedMaildirResource::MixedMaildirResource( const QString &id ) + : ResourceBase( id ), mStore( new MixedMaildirStore() ), mCompactHelper( 0 ) +{ + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + connect( this, SIGNAL(reloadConfiguration()), SLOT(reapplyConfiguration()) ); + + // We need to enable this here, otherwise we neither get the remote ID of the + // parent collection when a collection changes, nor the full item when an item + // is added. + changeRecorder()->fetchCollection( true ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + + setHierarchicalRemoteIdentifiersEnabled( true ); + + if ( ensureSaneConfiguration() ) { + const bool changeName = name().isEmpty() || name() == identifier() || + name() == mStore->topLevelCollection().name(); + mStore->setPath( Settings::self()->path() ); + if ( changeName ) { + setName( mStore->topLevelCollection().name() ); + } + } + + const QByteArray compactHelperSessionId = id.toUtf8() + "-compacthelper"; + mCompactHelper = new CompactChangeHelper( compactHelperSessionId, this ); +} + +MixedMaildirResource::~MixedMaildirResource() +{ + delete mStore; +} + +void MixedMaildirResource::aboutToQuit() +{ + // The settings may not have been saved if e.g. they have been modified via + // DBus instead of the config dialog. + Settings::self()->writeConfig(); +} + +void MixedMaildirResource::configure( WId windowId ) +{ + ConfigDialog dlg; + if ( windowId ) { + KWindowSystem::setMainWindow( &dlg, windowId ); + } + dlg.setWindowIcon( KIcon( "message-rfc822" ) ); + + bool fullSync = false; + + if ( dlg.exec() ) { + const bool changeName = name().isEmpty() || name() == identifier() || + name() == mStore->topLevelCollection().name(); + + const QString oldPath = mStore->path(); + mStore->setPath( Settings::self()->path() ); + + fullSync = oldPath != mStore->path(); + + if ( changeName ) { + setName( mStore->topLevelCollection().name() ); + } + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + + if ( ensureDirExists() ) { + if ( fullSync ) { + mSynchronizedCollections.clear(); + mPendingSynchronizeCollections.clear(); + } + synchronizeCollectionTree(); + } +} + +void MixedMaildirResource::itemAdded( const Item &item, const Collection& collection ) +{ +/* kDebug() << "item.id=" << item.id() << "col=" << collection.remoteId();*/ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + FileStore::ItemCreateJob *job = mStore->createItem( item, collection ); + connect( job, SIGNAL(result(KJob*)), SLOT(itemAddedResult(KJob*)) ); +} + +void MixedMaildirResource::itemChanged( const Item &item, const QSet& parts ) +{ +/* kDebug() << "item.id=" << item.id() << "col=" << item.parentCollection().remoteId() + << "parts=" << parts;*/ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + if ( Settings::self()->readOnly() ) { + changeProcessed(); + return; + } + + Item storeItem( item ); + storeItem.setRemoteId( mCompactHelper->currentRemoteId( item ) ); + + FileStore::ItemModifyJob *job = mStore->modifyItem( storeItem ); + job->setIgnorePayload( !item.hasPayload() ); + job->setParts( parts ); + job->setProperty( "originalRemoteId", storeItem.remoteId() ); + connect( job, SIGNAL(result(KJob*)), SLOT(itemChangedResult(KJob*)) ); +} + +void MixedMaildirResource::itemMoved( const Item &item, const Collection &source, const Collection &destination ) +{ +/* kDebug() << "item.id=" << item.id() << "remoteId=" << item.remoteId() + << "source=" << source.remoteId() << "dest=" << destination.remoteId();*/ + if ( source == destination ) { + changeProcessed(); + return; + } + + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + Item moveItem = item; + moveItem.setRemoteId( mCompactHelper->currentRemoteId( item ) ); + moveItem.setParentCollection( source ); + + FileStore::ItemMoveJob *job = mStore->moveItem( moveItem, destination ); + job->setProperty( "originalRemoteId", moveItem.remoteId() ); + connect( job, SIGNAL(result(KJob*)), SLOT(itemMovedResult(KJob*)) ); +} + +void MixedMaildirResource::itemRemoved(const Item &item) +{ +/* kDebug() << "item.id=" << item.id() << "col=" << collection.remoteId() + << "collection.remoteRevision=" << item.parentCollection().remoteRevision();*/ + Q_ASSERT( !item.remoteId().isEmpty() ); + Q_ASSERT( item.parentCollection().isValid() ); + if ( item.parentCollection().remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Item %1 belongs to invalid collection %2. Maybe it was deleted meanwhile?", item.id(), item.parentCollection().id() ); + kError() << message; + cancelTask( message ); + return; + } + + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + Item storeItem( item ); + storeItem.setRemoteId( mCompactHelper->currentRemoteId( item ) ); + FileStore::ItemDeleteJob *job = mStore->deleteItem( storeItem ); + connect( job, SIGNAL(result(KJob*)), SLOT(itemRemovedResult(KJob*)) ); +} + +void MixedMaildirResource::retrieveCollections() +{ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + FileStore::CollectionFetchJob *job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::Recursive ); + connect( job, SIGNAL(result(KJob*)), SLOT(retrieveCollectionsResult(KJob*)) ); + + status( Running, i18nc( "@info:status", "Synchronizing email folders" ) ); +} + +void MixedMaildirResource::retrieveItems( const Collection & col ) +{ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + RetrieveItemsJob *job = new RetrieveItemsJob( col, mStore, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(retrieveItemsResult(KJob*)) ); + + status( Running, i18nc( "@info:status", "Synchronizing email folder %1", col.name() ) ); +} + +bool MixedMaildirResource::retrieveItem( const Item &item, const QSet &parts ) +{ + Q_UNUSED( parts ); + + FileStore::ItemFetchJob *job = mStore->fetchItem( item ); + if ( parts.contains( Item::FullPayload ) ) { + job->fetchScope().fetchFullPayload( true ); + } else { + Q_FOREACH( const QByteArray &part, parts ) { + job->fetchScope().fetchPayloadPart( part, true ); + } + } + connect( job, SIGNAL(result(KJob*)), SLOT(retrieveItemResult(KJob*)) ); + + return true; +} + +void MixedMaildirResource::collectionAdded(const Collection &collection, const Collection &parent) +{ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + FileStore::CollectionCreateJob *job = mStore->createCollection( collection, parent ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionAddedResult(KJob*)) ); +} + +void MixedMaildirResource::collectionChanged(const Collection &collection) +{ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + // when the top level collection gets renamed, we do not rename the directory + // but rename the resource. + if ( collection.remoteId() == mStore->topLevelCollection().remoteId() ) { + if ( collection.name() != name() ) { + kDebug() << "TopLevel collection name differs from resource name: collection=" + << collection.name() << "resource=" << name() << ". Renaming resource"; + setName( collection.name() ); + } + changeCommitted( collection ); + return; + } + + mCompactHelper->checkCollectionChanged( collection ); + + FileStore::CollectionModifyJob *job = mStore->modifyCollection( collection ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionChangedResult(KJob*)) ); +} + +void MixedMaildirResource::collectionChanged(const Collection &collection, const QSet &changedAttributes ) +{ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + // when the top level collection gets renamed, we do not rename the directory + // but rename the resource. + if ( collection.remoteId() == mStore->topLevelCollection().remoteId() ) { + if ( collection.name() != name() ) { + kDebug() << "TopLevel collection name differs from resource name: collection=" + << collection.name() << "resource=" << name() << ". Renaming resource"; + setName( collection.name() ); + } + changeCommitted( collection ); + return; + } + + mCompactHelper->checkCollectionChanged( collection ); + + Q_UNUSED( changedAttributes ); + + FileStore::CollectionModifyJob *job = mStore->modifyCollection( collection ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionChangedResult(KJob*)) ); +} + +void MixedMaildirResource::collectionMoved( const Collection &collection, const Collection &source, const Collection &dest ) +{ + //kDebug( KDE_DEFAULT_DEBUG_AREA ) << collection << source << dest; + + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + const QString message = i18nc( "@info:status", "Cannot move root maildir folder '%1'." ,collection.remoteId() ); + kError() << message; + cancelTask( message ); + return; + } + + if ( source == dest ) { // should not happen, but who knows... + changeProcessed(); + return; + } + + Collection moveCollection = collection; + moveCollection.setParentCollection( source ); + + FileStore::CollectionMoveJob *job = mStore->moveCollection( moveCollection, dest ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionMovedResult(KJob*)) ); +} + +void MixedMaildirResource::collectionRemoved( const Collection &collection ) +{ + if ( !ensureSaneConfiguration() ) { + const QString message = i18nc( "@info:status", "Unusable configuration." ); + kError() << message; + cancelTask( message ); + return; + } + + if ( collection.parentCollection() == Collection::root() ) { + emit error( i18n( "Cannot delete top-level maildir folder '%1'.", Settings::self()->path() ) ); + changeProcessed(); + return; + } + + FileStore::CollectionDeleteJob *job = mStore->deleteCollection( collection ); + connect( job, SIGNAL(result(KJob*)), SLOT(collectionRemovedResult(KJob*)) ); +} + +bool MixedMaildirResource::ensureDirExists() +{ + QDir dir( Settings::self()->path() ); + if ( !dir.exists() ) { + if ( !dir.mkpath( Settings::self()->path() ) ) { + const QString message = i18nc( "@info:status", "Unable to create maildir '%1'.", Settings::self()->path() ); + kError() << message; + status( Broken, message ); + return false; + } + } + return true; +} + +bool MixedMaildirResource::ensureSaneConfiguration() +{ + if ( Settings::self()->path().isEmpty() ) { + const QString message = i18nc( "@info:status", "No usable storage location configured." ); + kError() << message; + status( NotConfigured, message ); + return false; + } + return true; +} + +void MixedMaildirResource::checkForInvalidatedIndexCollections( KJob * job ) +{ + // when operations invalidate the on-disk index, we need to make sure all index + // data has been transferred into Akonadi by synchronizing the collections + const QVariant var = job->property( "onDiskIndexInvalidated" ); + if ( var.isValid() ) { + const Collection::List collections = var.value(); + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "On disk index of" << collections.count() + << "collections invalidated after" << job->metaObject()->className(); + + Q_FOREACH( const Collection &collection, collections ) { + const Collection::Id id = collection.id(); + if ( !mSynchronizedCollections.contains( id ) && !mPendingSynchronizeCollections.contains( id ) ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Requesting sync of collection" << collection.name() + << ", id=" << collection.id(); + mPendingSynchronizeCollections << id; + synchronizeCollection( id ); + } + } + } +} + +void MixedMaildirResource::reapplyConfiguration() +{ + if ( ensureSaneConfiguration() && ensureDirExists() ) { + const QString oldPath = mStore->path(); + mStore->setPath( Settings::self()->path() ); + + if ( oldPath != mStore->path() ) { + mSynchronizedCollections.clear(); + mPendingSynchronizeCollections.clear(); + } + synchronizeCollectionTree(); + } +} + +void MixedMaildirResource::retrieveCollectionsResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::CollectionFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob != 0 ); + + Collection topLevelCollection = mStore->topLevelCollection(); + if ( !name().isEmpty() && name() != identifier() ) { + topLevelCollection.setName( name() ); + } + + Collection::List collections; + collections << topLevelCollection; + collections << fetchJob->collections(); + collectionsRetrieved( collections ); +} + +void MixedMaildirResource::retrieveItemsResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + RetrieveItemsJob *retrieveJob = qobject_cast( job ); + Q_ASSERT( retrieveJob != 0 ); + + // messages marked as deleted have been deleted from mbox files but never got purged + // TODO FileStore could provide deleteItems() to deleted all filtered items in one go + KJob* deleteJob = 0; + kDebug( KDE_DEFAULT_DEBUG_AREA ) << retrieveJob->itemsMarkedAsDeleted().count() + << "items marked as Deleted"; + Q_FOREACH( const Item &item, retrieveJob->itemsMarkedAsDeleted() ) { + deleteJob = mStore->deleteItem( item ); + } + + if ( deleteJob != 0 ) { + // last item delete triggers mbox purge, i.e. store compact + const bool connected = connect( deleteJob, SIGNAL(result(KJob*)), this, SLOT(itemsDeleted(KJob*)) ); + Q_ASSERT( connected ); + Q_UNUSED( connected ); + } + + // if some items have tags, we need to complete the retrieval and schedule tagging + // to a later time so we can then fetch the items to get their Akonadi URLs + const Item::List items = retrieveJob->availableItems(); + const QVariant var = retrieveJob->property( "remoteIdToTagList" ); + if ( var.isValid() ) { + const QHash tagListHash = var.value< QHash >(); + if ( !tagListHash.isEmpty() ) { + kDebug() << tagListHash.count() << "of" << items.count() + << "items in collection" << retrieveJob->collection().remoteId() << "have tags"; + + TagContextList taggedItems; + Q_FOREACH( const Item &item, items ) { + const QVariant tagListVar = tagListHash[ item.remoteId() ]; + if ( tagListVar.isValid() ) { + const QStringList tagList = tagListVar.value(); + if ( !tagListHash.isEmpty() ) { + TagContext tag; + tag.mItem = item; + tag.mTagList = tagList; + + taggedItems << tag; + } + } + } + + if ( !taggedItems.isEmpty() ) { + mTagContextByColId.insert( retrieveJob->collection().id(), taggedItems ); + + scheduleCustomTask( this, "restoreTags", QVariant::fromValue( retrieveJob->collection() ) ); + } + } + } + + mSynchronizedCollections << retrieveJob->collection().id(); + mPendingSynchronizeCollections.remove( retrieveJob->collection().id() ); + + itemsRetrievalDone(); +} + +void MixedMaildirResource::retrieveItemResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob != 0 ); + Q_ASSERT( !fetchJob->items().isEmpty() ); + + itemRetrieved( fetchJob->items()[ 0 ] ); +} + +void MixedMaildirResource::itemAddedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::ItemCreateJob *itemJob = qobject_cast( job ); + Q_ASSERT( itemJob != 0 ); + +/* kDebug() << "item.id=" << itemJob->item().id() << "remoteId=" << itemJob->item().remoteId();*/ + changeCommitted( itemJob->item() ); + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::itemChangedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::ItemModifyJob *itemJob = qobject_cast( job ); + Q_ASSERT( itemJob != 0 ); + + changeCommitted( itemJob->item() ); + + const QString remoteId = itemJob->property( "originalRemoteId" ).value(); + + const QVariant compactStoreVar = itemJob->property( "compactStore" ); + if ( compactStoreVar.isValid() && compactStoreVar.toBool() ) { + scheduleCustomTask( this, "compactStore", QVariant() ); + } + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::itemMovedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::ItemMoveJob *itemJob = qobject_cast( job ); + Q_ASSERT( itemJob != 0 ); + + changeCommitted( itemJob->item() ); + + const QString remoteId = itemJob->property( "originalRemoteId" ).value(); +// kDebug() << "item.id=" << itemJob->item().id() << "remoteId=" << itemJob->item().remoteId() +// << "old remoteId=" << remoteId; + + const QVariant compactStoreVar = itemJob->property( "compactStore" ); + if ( compactStoreVar.isValid() && compactStoreVar.toBool() ) { + scheduleCustomTask( this, "compactStore", QVariant() ); + } + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::itemRemovedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::ItemDeleteJob *itemJob = qobject_cast( job ); + Q_ASSERT( itemJob != 0 ); + + changeCommitted( itemJob->item() ); + + const QString remoteId = itemJob->item().remoteId(); + + const QVariant compactStoreVar = itemJob->property( "compactStore" ); + if ( compactStoreVar.isValid() && compactStoreVar.toBool() ) { + scheduleCustomTask( this, "compactStore", QVariant() ); + } + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::itemsDeleted( KJob *job ) +{ + Q_UNUSED( job ); + scheduleCustomTask( this, "compactStore", QVariant() ); +} + +void MixedMaildirResource::collectionAddedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::CollectionCreateJob *colJob = qobject_cast( job ); + Q_ASSERT( colJob != 0 ); + + changeCommitted( colJob->collection() ); +} + +void MixedMaildirResource::collectionChangedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::CollectionModifyJob *colJob = qobject_cast( job ); + Q_ASSERT( colJob != 0 ); + + changeCommitted( colJob->collection() ); + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::collectionMovedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::CollectionMoveJob *colJob = qobject_cast( job ); + Q_ASSERT( colJob != 0 ); + + changeCommitted( colJob->collection() ); + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::collectionRemovedResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::CollectionDeleteJob *colJob = qobject_cast( job ); + Q_ASSERT( colJob != 0 ); + + changeCommitted( colJob->collection() ); +} + +void MixedMaildirResource::compactStore( const QVariant &arg ) +{ + Q_UNUSED( arg ); + + FileStore::StoreCompactJob *job = mStore->compactStore(); + connect( job, SIGNAL(result(KJob*)), SLOT(compactStoreResult(KJob*)) ); +} + +void MixedMaildirResource::compactStoreResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + status( Broken, job->errorString() ); + cancelTask( job->errorString() ); + return; + } + + FileStore::StoreCompactJob *compactJob = qobject_cast( job ); + Q_ASSERT( compactJob != 0 ); + + const Item::List items = compactJob->changedItems(); + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Compacting store resulted in" << items.count() << "changed items"; + + mCompactHelper->addChangedItems( items ); + + taskDone(); + + checkForInvalidatedIndexCollections( job ); +} + +void MixedMaildirResource::restoreTags( const QVariant &arg ) +{ + if ( !arg.isValid() ) { + kError() << "Given variant is not valid"; + cancelTask(); + return; + } + + const Collection collection = arg.value(); + if ( !collection.isValid() ) { + kError() << "Given variant is not valid"; + cancelTask(); + return; + } + + const TagContextList taggedItems = mTagContextByColId[ collection.id() ]; + mPendingTagContexts << taggedItems; + + QMetaObject::invokeMethod( this, "processNextTagContext", Qt::QueuedConnection ); + taskDone(); +} + +void MixedMaildirResource::processNextTagContext() +{ + kDebug() << mPendingTagContexts.count() << "items to go"; + if ( mPendingTagContexts.isEmpty() ) { + return; + } + + const TagContext tagContext = mPendingTagContexts.front(); + mPendingTagContexts.pop_front(); + + ItemFetchJob *fetchJob = new ItemFetchJob( tagContext.mItem ); + fetchJob->setProperty( "tagList", tagContext.mTagList ); + connect( fetchJob, SIGNAL(result(KJob*)), SLOT(tagFetchJobResult(KJob*)) ); +} + +void MixedMaildirResource::tagFetchJobResult( KJob *job ) +{ + if ( job->error() != 0 ) { + kError() << job->errorString(); + processNextTagContext(); + return; + } + + ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob != 0 ); + + Q_ASSERT( !fetchJob->items().isEmpty() ); + + const Item item = fetchJob->items()[ 0 ]; + const QStringList tagList = job->property( "tagList" ).value(); + kDebug() << "Tagging item" << item.url() << "with" << tagList; + + Akonadi::Tag::List tags; + Q_FOREACH( const QString &tag, tagList ) { + if ( tag.isEmpty() ) { + kWarning() << "TagList for item" << item.url() << "contains an empty tag"; + } else { + tags << Akonadi::Tag(tag); + } + } + new CreateAndSetTagsJob(item, tags); + + processNextTagContext(); +} + + +AKONADI_RESOURCE_MAIN( MixedMaildirResource ) + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.desktop b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.desktop new file mode 100644 index 00000000..7e6223d4 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.desktop @@ -0,0 +1,93 @@ +[Desktop Entry] +Name=KMail Mail Folder +Name[bs]=KMail fascikla poÅ¡te +Name[ca]=Carpeta de correu del KMail +Name[ca@valencia]=Carpeta de correu del KMail +Name[cs]=Složka poÅ¡ty KMailu +Name[da]=KMail's Mail-mappe +Name[de]=KMail-Mailordner +Name[el]=Φάκελος αλληλογÏαφίας KMail +Name[en_GB]=KMail Mail Folder +Name[es]=Carpeta de correo de KMail +Name[et]=KMaili kirjade kataloog +Name[fi]=KMail-postikansio +Name[fr]=Dossier de courriers électroniques KMail +Name[ga]=Fillteán Ríomhphoist KMail +Name[gl]=Cartafol de correo de KMail +Name[hu]=KMail levélmappa +Name[ia]=Dossier de posta de KMail +Name[it]=Cartella di posta di KMail +Name[kk]=KMail пошта қапшығы +Name[km]=ážážâ€‹ážŸáŸ†áž”áž»ážáŸ’ážš KMail +Name[ko]=KMail ë©”ì¼ í´ë” +Name[lt]=KMail el. laiÅ¡kų katalogas +Name[lv]=KMail pasta mape +Name[nb]=KMail e-postmappe +Name[nds]=KMail-Nettpostorner +Name[nl]=E-mailmap van KMail +Name[pl]=Katalog Poczty KMail +Name[pt]=Pasta de Correio do KMail +Name[pt_BR]=Pasta de e-mails do KMail +Name[ru]=ÐŸÐ¾Ñ‡Ñ‚Ð¾Ð²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° KMail +Name[sk]=PoÅ¡tový prieÄinok KMail +Name[sl]=PoÅ¡tna mapa za KMail +Name[sr]=К‑поштина поштанÑка фаÑцикла +Name[sr@ijekavian]=К‑поштина поштанÑка фаÑцикла +Name[sr@ijekavianlatin]=K‑poÅ¡tina poÅ¡tanska fascikla +Name[sr@latin]=K‑poÅ¡tina poÅ¡tanska fascikla +Name[sv]=Kmail brevkatalog +Name[tr]=KMail Mail Dizini +Name[uk]=Тека пошти KMail +Name[x-test]=xxKMail Mail Folderxx +Name[zh_CN]=KMail 邮件文件夹 +Name[zh_TW]=KMail 資料夾 +Comment=Loads data from a local KMail mail folder +Comment[bs]=UÄitava podatke iz lokalne fascikle KMail poÅ¡te +Comment[ca]=Carrega les dades des d'una carpeta local de correu del KMail +Comment[ca@valencia]=Carrega dades des d'una carpeta local de correu del KMail +Comment[cs]=NaÄítá data z místní složky poÅ¡ty KMail +Comment[da]=Indlæser data fra en lokal KMail mail-mappe +Comment[de]=Daten werden aus einem lokalen KMail-Mailordner geladen +Comment[el]=ΦοÏτώνει δεδομένα από έναν τοπικό φάκελο αλληλογÏαφίας του KMail +Comment[en_GB]=Loads data from a local KMail mail folder +Comment[es]=Carga datos de una carpeta de correo local de KMail +Comment[et]=Andmete laadimine kohalikust KMaili kirjade kataloogist +Comment[fi]=Lataa tietoa paikallisesta KMail-postikansiosta +Comment[fr]=Charge des données depuis un dossier local KMail +Comment[ga]=Luchtaíonn sé seo sonraí ó fhillteán logánta KMail +Comment[gl]=Carga datos desde un cartafol de correo local de KMail. +Comment[hu]=Adatok betöltése egy helyi KMail levélmappából +Comment[ia]=Carga datos ex un dossier local de posta de KMail +Comment[it]=Carica dati da una cartella locale di posta di KMail +Comment[kk]=Жергілікті KMail пошта қапшығынан деректі алып береді +Comment[km]=ផ្ទុក​ទិន្ននáŸáž™â€‹áž–ី​ážážâ€‹ážŸáŸ†áž”áž»ážáŸ’ážš KMail មូលដ្ឋាន +Comment[ko]=로컬 KMail ë©”ì¼ í´ë”ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ vietinio KMail aplanko +Comment[lv]=IelÄdÄ“ datus no lokÄlÄs KMail pasta mapes +Comment[nb]=Laster data fra en lokal KMail e-ppstmappe +Comment[nds]=Laadt Daten ut en lokaal KMail-Nettpostorner +Comment[nl]=Laadt gegevens van een lokale e-mailmap van Kmail +Comment[pl]=Wczytuje dane z lokalnego katalogu poczty KMail +Comment[pt]=Carrega os dados a partir de uma pasta de correio local do KMail +Comment[pt_BR]=Carrega os dados de uma pasta de e-mails local do KMail +Comment[ru]=Загрузка данных из локальной почтовой папки KMail +Comment[sk]=NaÄíta dáta z miestneho mailového prieÄinka KMail +Comment[sl]=Naloži podatke iz krajevne poÅ¡tne mape za KMail +Comment[sr]=Учитава податке из локалне К‑поштине поштанÑке фаÑцикле +Comment[sr@ijekavian]=Учитава податке из локалне К‑поштине поштанÑке фаÑцикле +Comment[sr@ijekavianlatin]=UÄitava podatke iz lokalne K‑poÅ¡tine poÅ¡tanske fascikle +Comment[sr@latin]=UÄitava podatke iz lokalne K‑poÅ¡tine poÅ¡tanske fascikle +Comment[sv]=Laddar data frÃ¥n en lokal Kmail brevkatalog +Comment[tr]=Yerel KMail maildir dizininden veri yükleme aracı +Comment[uk]=Завантажує дані з локальної теки пошти KMail +Comment[x-test]=xxLoads data from a local KMail mail folderxx +Comment[zh_CN]=从本地 KMail é‚®ä»¶æ–‡ä»¶å¤¹ä¸­è½½å…¥æ•°æ® +Comment[zh_TW]=從本地 KMail 資料夾中載入資料 +Type=AkonadiResource +Exec=akonadi_mixedmaildir_resource +Icon=message-rfc822 + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_mixedmaildir_resource +X-Akonadi-Custom-HasLocalStorage=true diff --git a/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.h b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.h new file mode 100644 index 00000000..9cf4f291 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.h @@ -0,0 +1,115 @@ +/* This file is part of the KDE project + Copyright (c) 2007 Till Adam + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 MIXEDMAILDIR_RESOURCE_H +#define MIXEDMAILDIR_RESOURCE_H + +#include + +#include + +class CompactChangeHelper; +class MixedMaildirStore; + +class MixedMaildirResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + explicit MixedMaildirResource( const QString &id ); + ~MixedMaildirResource(); + + public Q_SLOTS: + void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + void aboutToQuit(); + + void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + void itemChanged( const Akonadi::Item &item, const QSet &parts ); + void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest ); + void itemRemoved( const Akonadi::Item &item ); + + void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + void collectionChanged( const Akonadi::Collection &collection ); + void collectionChanged( const Akonadi::Collection &collection, const QSet &changedAttributes ); + void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &dest ); + void collectionRemoved( const Akonadi::Collection &collection ); + + private: + bool ensureDirExists(); + bool ensureSaneConfiguration(); + + void checkForInvalidatedIndexCollections( KJob * job ); + + private Q_SLOTS: + void reapplyConfiguration(); + + void retrieveCollectionsResult( KJob *job ); + void retrieveItemsResult( KJob *job ); + void retrieveItemResult( KJob *job ); + + void itemAddedResult( KJob *job ); + void itemChangedResult( KJob *job ); + void itemMovedResult( KJob *job ); + void itemRemovedResult( KJob *job ); + + void itemsDeleted( KJob *job ); + + void collectionAddedResult( KJob *job ); + void collectionChangedResult( KJob *job ); + void collectionMovedResult( KJob *job ); + void collectionRemovedResult( KJob *job ); + + void compactStore( const QVariant &arg ); + void compactStoreResult( KJob *job ); + + void restoreTags( const QVariant &arg ); + void processNextTagContext(); + void tagFetchJobResult( KJob *job ); + + private: + MixedMaildirStore *mStore; + + struct TagContext + { + Akonadi::Item mItem; + QStringList mTagList; + }; + + typedef QList TagContextList; + QHash mTagContextByColId; + TagContextList mPendingTagContexts; + + QSet mSynchronizedCollections; + QSet mPendingSynchronizeCollections; + + CompactChangeHelper *mCompactHelper; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.kcfg b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.kcfg new file mode 100644 index 00000000..5c993cff --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/mixedmaildirresource.kcfg @@ -0,0 +1,22 @@ + + + + + + + + + + + false + + + + false + + + diff --git a/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.cpp b/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.cpp new file mode 100644 index 00000000..1903c3ac --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.cpp @@ -0,0 +1,2366 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "kmindexreader/kmindexreader.h" + +#include "filestore/collectioncreatejob.h" +#include "filestore/collectiondeletejob.h" +#include "filestore/collectionfetchjob.h" +#include "filestore/collectionmovejob.h" +#include "filestore/collectionmodifyjob.h" +#include "filestore/entitycompactchangeattribute.h" +#include "filestore/itemcreatejob.h" +#include "filestore/itemdeletejob.h" +#include "filestore/itemfetchjob.h" +#include "filestore/itemmodifyjob.h" +#include "filestore/itemmovejob.h" +#include "filestore/storecompactjob.h" + +#include "libmaildir/maildir.h" + +#include +#include + +#include + +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace Akonadi; +using KPIM::Maildir; +using namespace KMBox; + +static bool fullEntryCompare( const KMBox::MBoxEntry &left, const KMBox::MBoxEntry &right ) +{ + return ( left.messageOffset() == right.messageOffset() && + left.separatorSize() == right.separatorSize() && + left.messageSize() == right.messageSize() ); +} + +static bool indexFileForFolder( const QFileInfo &folderDirInfo, QFileInfo &indexFileInfo ) +{ + indexFileInfo = QFileInfo( folderDirInfo.dir(), QString::fromUtf8( ".%1.index" ).arg( folderDirInfo.fileName() ) ); + + if ( !indexFileInfo.exists() || !indexFileInfo.isReadable() ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "No index file" << indexFileInfo.absoluteFilePath() << "or not readable"; + return false; + } + + return true; +} + +class MBoxContext +{ + public: + MBoxContext() : mRevision( 0 ), mIndexDataLoaded( false ), mHasIndexData( false ) {} + + QString fileName() const + { + return mMBox.fileName(); + } + + bool load( const QString &fileName ) + { + mModificationTime = QFileInfo( fileName ).lastModified(); + + // in case of reload, check if anything changed, otherwise keep deleted entries + if ( !mDeletedOffsets.isEmpty() && fileName == mMBox.fileName() ) { + const KMBox::MBoxEntry::List currentEntryList = mMBox.entries(); + if ( mMBox.load( fileName ) ) { + const KMBox::MBoxEntry::List newEntryList = mMBox.entries(); + if ( !std::equal( currentEntryList.begin(), currentEntryList.end(), newEntryList.begin(), fullEntryCompare ) ) { + mDeletedOffsets.clear(); + } + return true; + } + + return false; + } + + mDeletedOffsets.clear(); + return mMBox.load( fileName ); + } + + QDateTime modificationTime() const { return mModificationTime; } + + KMBox::MBoxEntry::List entryList() const + { + KMBox::MBoxEntry::List result; + Q_FOREACH( const KMBox::MBoxEntry &entry, mMBox.entries() ) { + if ( !mDeletedOffsets.contains( entry.messageOffset() ) ) { + result << entry; + } + } + return result; + } + + QByteArray readRawEntry( quint64 offset ) + { + return mMBox.readRawMessage( KMBox::MBoxEntry( offset ) ); + } + + QByteArray readEntryHeaders( quint64 offset ) + { + return mMBox.readMessageHeaders( KMBox::MBoxEntry( offset ) ); + } + + qint64 appendEntry( const KMime::Message::Ptr &entry ) + { + const KMBox::MBoxEntry result = mMBox.appendMessage( entry ); + if ( result.isValid() && mHasIndexData ) { + mIndexData.insert( result.messageOffset(), KMIndexDataPtr( new KMIndexData ) ); + Q_ASSERT( mIndexData.value( result.messageOffset() )->isEmpty() ); + } + + return result.messageOffset(); + } + + void deleteEntry( quint64 offset ) + { + mDeletedOffsets << offset; + } + + bool isValidOffset( quint64 offset ) const + { + if ( mDeletedOffsets.contains( offset ) ) { + return false; + } + + Q_FOREACH( const KMBox::MBoxEntry &entry, mMBox.entries() ) { + if ( entry.messageOffset() == offset ) { + return true; + } + } + + return false; + } + + bool save() + { + bool ret = mMBox.save(); + mModificationTime = QDateTime::currentDateTime(); + return ret; + } + + int purge( QList &movedEntries ) + { + const int deleteCount = mDeletedOffsets.count(); + + KMBox::MBoxEntry::List deletedEntries; + Q_FOREACH( quint64 offset, mDeletedOffsets ) + deletedEntries << KMBox::MBoxEntry( offset ); + + const bool result = mMBox.purge( deletedEntries, &movedEntries ); + + if ( mHasIndexData ) { + // keep copy of original for lookup + const IndexDataHash indexData = mIndexData; + + // delete index data for removed entries + Q_FOREACH( quint64 offset, mDeletedOffsets ) { + mIndexData.remove( offset ); + } + + // delete index data for changed entries + // readded below in an extra loop to handled cases where a new index is equal to an + // old index of a different entry + Q_FOREACH( const KMBox::MBoxEntry::Pair &entry, movedEntries ) { + mIndexData.remove( entry.first.messageOffset() ); + } + + // readd index data for changed entries at their new position + Q_FOREACH( const KMBox::MBoxEntry::Pair &entry, movedEntries ) { + const KMIndexDataPtr data = indexData.value( entry.first.messageOffset() ); + mIndexData.insert( entry.second.messageOffset(), data ); + } + } + + mDeletedOffsets.clear(); + mModificationTime = QDateTime::currentDateTime(); + return ( result ? deleteCount : -1 ); + } + + MBox &mbox() + { + return mMBox; + } + + const MBox &mbox() const + { + return mMBox; + } + + bool hasDeletedOffsets() const + { + return !mDeletedOffsets.isEmpty(); + } + + void readIndexData(); + + KMIndexDataPtr indexData( quint64 offset ) const { + if ( mHasIndexData ) { + if ( !mDeletedOffsets.contains( offset ) ) { + IndexDataHash::const_iterator it = mIndexData.constFind( offset ); + if ( it != mIndexData.constEnd() ) { + return it.value(); + } + } + } + + return KMIndexDataPtr(); + } + + bool hasIndexData() const { + return mHasIndexData; + } + + void updatePath( const QString &newPath ) { + // TODO FIXME there has to be a better of doing that + mMBox.load( newPath ); + } + + public: + Collection mCollection; + qint64 mRevision; + + private: + QSet mDeletedOffsets; + MBox mMBox; + QDateTime mModificationTime; + + typedef QHash IndexDataHash; + IndexDataHash mIndexData; + bool mIndexDataLoaded; + bool mHasIndexData; +}; + +typedef boost::shared_ptr MBoxPtr; + +void MBoxContext::readIndexData() +{ + if ( mIndexDataLoaded ) { + return; + } + + mIndexDataLoaded = true; + + const QFileInfo mboxFileInfo( mMBox.fileName() ); + QFileInfo indexFileInfo; + if ( !indexFileForFolder( mboxFileInfo, indexFileInfo ) ) { + return; + } + + if ( mboxFileInfo.lastModified() > indexFileInfo.lastModified() ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "MBox file " << mboxFileInfo.absoluteFilePath() + << "newer than the index: mbox modified at" + << mboxFileInfo.lastModified() << ", index modified at" + << indexFileInfo.lastModified(); + return; + } + + KMIndexReader indexReader( indexFileInfo.absoluteFilePath() ); + if ( indexReader.error() || !indexReader.readIndex() ) { + kError() << "Index file" << indexFileInfo.path() << "could not be read"; + return; + } + + mHasIndexData = true; + + const KMBox::MBoxEntry::List entries = mMBox.entries(); + Q_FOREACH( const KMBox::MBoxEntry &entry, entries ) { + const quint64 indexOffset = entry.messageOffset() + entry.separatorSize(); + const KMIndexDataPtr data = indexReader.dataByOffset( indexOffset ); + if ( data != 0 ) { + mIndexData.insert( entry.messageOffset(), data ); + } + } + + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Read" << mIndexData.count() << "index entries from" + << indexFileInfo.absoluteFilePath(); +} + +class MaildirContext +{ + public: + MaildirContext( const QString &path, bool isTopLevel ) + : mMaildir( path, isTopLevel ), mIndexDataLoaded( false ), mHasIndexData( false ) + { + } + + MaildirContext( const Maildir &md ) + : mMaildir( md ), mIndexDataLoaded( false ), mHasIndexData( false ) + { + } + + QStringList entryList() const { + return mMaildir.entryList(); + } + + QString addEntry( const QByteArray &data ) { + const QString result = mMaildir.addEntry( data ); + if ( !result.isEmpty() && mHasIndexData ) { + mIndexData.insert( result, KMIndexDataPtr( new KMIndexData ) ); + Q_ASSERT( mIndexData.value( result )->isEmpty() ); + } else { + //TODO: use the error string? + kWarning() << mMaildir.lastError(); + } + + return result; + } + + void writeEntry( const QString &key, const QByteArray &data ) { + mMaildir.writeEntry( key, data ); //TODO: error handling + if ( mHasIndexData ) { + mIndexData.insert( key, KMIndexDataPtr( new KMIndexData ) ); + } + } + + bool removeEntry( const QString &key ) { + const bool result = mMaildir.removeEntry( key ); + if ( result && mHasIndexData ) { + mIndexData.remove( key ); + } + + return result; + } + + QString moveEntryTo( const QString &key, MaildirContext &destination ) { + const QString result = mMaildir.moveEntryTo( key, destination.mMaildir ); + if ( !result.isEmpty() ) { + if ( mHasIndexData ) { + mIndexData.remove( key ); + } + + if ( destination.mHasIndexData ) { + destination.mIndexData.insert( result, KMIndexDataPtr( new KMIndexData ) ); + } + } else { + //TODO error handling? + kWarning() << mMaildir.lastError(); + } + + return result; + } + + QByteArray readEntryHeaders( const QString &key ) const { + return mMaildir.readEntryHeaders( key ); + } + + QByteArray readEntry( const QString &key ) const { + return mMaildir.readEntry( key ); + } + + bool isValid( QString &error ) const { + bool result = mMaildir.isValid(); + if ( !result ) { + error = mMaildir.lastError(); + } + return result; + } + + bool isValidEntry( const QString &entry ) const { + return !mMaildir.findRealKey( entry ).isEmpty(); + } + + void readIndexData(); + + KMIndexDataPtr indexData( const QString &fileName ) const { + if ( mHasIndexData ) { + IndexDataHash::const_iterator it = mIndexData.constFind( fileName ); + if ( it != mIndexData.constEnd() ) { + return it.value(); + } + } + + return KMIndexDataPtr(); + } + + bool hasIndexData() const { + return mHasIndexData; + } + + void updatePath( const QString &newPath ) { + mMaildir = Maildir( newPath, mMaildir.isRoot() ); + } + + const Maildir & maildir() const { + return mMaildir; + } + + private: + Maildir mMaildir; + + typedef QHash IndexDataHash; + IndexDataHash mIndexData; + bool mIndexDataLoaded; + bool mHasIndexData; +}; + +void MaildirContext::readIndexData() +{ + if ( mIndexDataLoaded ) { + return; + } + + mIndexDataLoaded = true; + + const QFileInfo maildirFileInfo( mMaildir.path() ); + QFileInfo indexFileInfo; + if ( !indexFileForFolder( maildirFileInfo, indexFileInfo ) ) { + return; + } + + const QDir maildirBaseDir( maildirFileInfo.absoluteFilePath() ); + const QFileInfo curDirFileInfo( maildirBaseDir, QLatin1String( "cur" ) ); + const QFileInfo newDirFileInfo( maildirBaseDir, QLatin1String( "new" ) ); + + if ( curDirFileInfo.lastModified() > indexFileInfo.lastModified() ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Maildir " << maildirFileInfo.absoluteFilePath() + << "\"cur\" directory newer than the index: cur modified at" + << curDirFileInfo.lastModified() << ", index modified at" + << indexFileInfo.lastModified(); + return; + } + if ( newDirFileInfo.lastModified() > indexFileInfo.lastModified() ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Maildir \"new\" directory newer than the index: cur modified at" + << newDirFileInfo.lastModified() << ", index modified at" + << indexFileInfo.lastModified(); + return; + } + + KMIndexReader indexReader( indexFileInfo.absoluteFilePath() ); + if ( indexReader.error() || !indexReader.readIndex() ) { + kError() << "Index file" << indexFileInfo.path() << "could not be read"; + return; + } + + mHasIndexData = true; + + const QStringList entries = mMaildir.entryList(); + Q_FOREACH( const QString &entry, entries ) { + const KMIndexDataPtr data = indexReader.dataByFileName( entry ); + if ( data != 0 ) { + mIndexData.insert( entry, data ); + } + } + + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Read" << mIndexData.count() << "index entries from" + << indexFileInfo.absoluteFilePath(); +} + +typedef boost::shared_ptr MaildirPtr; + +class MixedMaildirStore::Private : public FileStore::Job::Visitor +{ + MixedMaildirStore *const q; + + public: + enum FolderType + { + InvalidFolder, + TopLevelFolder, + MaildirFolder, + MBoxFolder + }; + + Private( MixedMaildirStore *parent ) : q( parent ) + { + } + + FolderType folderForCollection( const Collection &col, QString &path, QString &errorText ) const; + + MBoxPtr getOrCreateMBoxPtr( const QString &path ); + MaildirPtr getOrCreateMaildirPtr( const QString &path, bool isTopLevel ); + + void fillMBoxCollectionDetails( const MBoxPtr &mbox, Collection &collection ); + void fillMaildirCollectionDetails( const Maildir &md, Collection &collection ); + void fillMaildirTreeDetails( const Maildir &md, const Collection &collection, Collection::List &collections, bool recurse ); + void listCollection( FileStore::Job *job, MBoxPtr &mbox, const Collection &collection, Item::List &items ); + void listCollection( FileStore::Job *job, MaildirPtr &md, const Collection &collection, Item::List &items ); + bool fillItem( MBoxPtr &mbox, bool includeHeaders, bool includeBody, Item &item ) const; + bool fillItem( const MaildirPtr &md, bool includeHeaders, bool includeBody, Item &item ) const; + void updateContextHashes( const QString &oldPath, const QString &newPath ); + + public: // visitor interface implementation + bool visit( FileStore::Job *job ); + bool visit( FileStore::CollectionCreateJob *job ); + bool visit( FileStore::CollectionDeleteJob *job ); + bool visit( FileStore::CollectionFetchJob *job ); + bool visit( FileStore::CollectionModifyJob *job ); + bool visit( FileStore::CollectionMoveJob *job ); + bool visit( FileStore::ItemCreateJob *job ); + bool visit( FileStore::ItemDeleteJob *job ); + bool visit( FileStore::ItemFetchJob *job ); + bool visit( FileStore::ItemModifyJob *job ); + bool visit( FileStore::ItemMoveJob *job ); + bool visit( FileStore::StoreCompactJob *job ); + + public: + typedef QHash MBoxHash; + MBoxHash mMBoxes; + + typedef QHash MaildirHash; + MaildirHash mMaildirs; +}; + +MixedMaildirStore::Private::FolderType MixedMaildirStore::Private::folderForCollection( const Collection &col, QString &path, QString &errorText ) const +{ + path.clear(); + errorText.clear(); + + if ( col.remoteId().isEmpty() ) { + errorText = i18nc( "@info:status", "Given folder name is empty" ); + kWarning() << "Incomplete ancestor chain for collection."; + Q_ASSERT( !col.remoteId().isEmpty() ); // abort! Look at backtrace to see where we came from. + return InvalidFolder; + } + + if ( col.parentCollection() == Collection::root() ) { + path = q->path(); + if ( col.remoteId() != path ) + kWarning() << "RID mismatch, is" << col.remoteId() << "expected" << path; + return TopLevelFolder; + } + + FolderType type = folderForCollection( col.parentCollection(), path, errorText ); + switch ( type ) { + case InvalidFolder: return InvalidFolder; + + case TopLevelFolder: // fall through + case MaildirFolder: { + const Maildir parentMd( path, type == TopLevelFolder ); + const Maildir subFolder = parentMd.subFolder( col.remoteId() ); + if ( subFolder.isValid( false ) ) { + path = subFolder.path(); + return MaildirFolder; + } + + const QString subDirPath = + (type == TopLevelFolder ? path : Maildir::subDirPathForFolderPath( path ) ); + QFileInfo fileInfo( QDir( subDirPath ), col.remoteId() ); + if ( fileInfo.isFile() ) { + path = fileInfo.absoluteFilePath(); + return MBoxFolder; + } + + errorText = i18nc( "@info:status", "Folder %1 does not seem to be a valid email folder", fileInfo.absoluteFilePath() ); + return InvalidFolder; + } + + case MBoxFolder: { + const QString subDirPath = Maildir::subDirPathForFolderPath( path ); + QFileInfo fileInfo( QDir( subDirPath ), col.remoteId() ); + + if ( fileInfo.isFile() ) { + path = fileInfo.absoluteFilePath(); + return MBoxFolder; + } + + const Maildir subFolder( fileInfo.absoluteFilePath(), false ); + if ( subFolder.isValid( false ) ) { + path = subFolder.path(); + return MaildirFolder; + } + + errorText = i18nc( "@info:status", "Folder %1 does not seem to be a valid email folder", fileInfo.absoluteFilePath() ); + return InvalidFolder; + } + } + return InvalidFolder; +} + +MBoxPtr MixedMaildirStore::Private::getOrCreateMBoxPtr( const QString &path ) +{ + MBoxPtr mbox; + const MBoxHash::const_iterator it = mMBoxes.constFind( path ); + if ( it == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + mMBoxes.insert( path, mbox ); + } else { + mbox = it.value(); + } + + return mbox; +} + +MaildirPtr MixedMaildirStore::Private::getOrCreateMaildirPtr( const QString &path, bool isTopLevel ) +{ + MaildirPtr md; + const MaildirHash::const_iterator it = mMaildirs.constFind( path ); + if ( it == mMaildirs.constEnd() ) { + md = MaildirPtr( new MaildirContext( path, isTopLevel ) ); + mMaildirs.insert( path, md ); + } else { + md = it.value(); + } + + return md; +} + +void MixedMaildirStore::Private::fillMBoxCollectionDetails( const MBoxPtr &mbox, Collection &collection ) +{ + collection.setContentMimeTypes( QStringList() << Collection::mimeType() + << KMime::Message::mimeType() ); + + const QFileInfo fileInfo( mbox->fileName() ); + if ( fileInfo.isWritable() ) { + collection.setRights( Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + } else { + collection.setRights( Collection::ReadOnly ); + } + + if ( mbox->mRevision > 0 ) { + collection.setRemoteRevision( QString::number( mbox->mRevision ) ); + } +} + +void MixedMaildirStore::Private::fillMaildirCollectionDetails( const Maildir &md, Collection &collection ) +{ + collection.setContentMimeTypes( QStringList() << Collection::mimeType() + << KMime::Message::mimeType() ); + + const QFileInfo fileInfo( md.path() ); + if ( fileInfo.isWritable() ) { + collection.setRights( Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + } else { + collection.setRights( Collection::ReadOnly ); + } +} + +void MixedMaildirStore::Private::fillMaildirTreeDetails( const Maildir &md, const Collection &collection, Collection::List &collections, bool recurse ) +{ + if ( md.path().isEmpty() ) { + return; + } + + const QStringList maildirSubFolders = md.subFolderList(); + Q_FOREACH( const QString &subFolder, maildirSubFolders ) { + const Maildir subMd = md.subFolder( subFolder ); + + if ( !mMaildirs.contains( subMd.path() ) ) { + const MaildirPtr mdPtr = MaildirPtr( new MaildirContext( subMd ) ); + mMaildirs.insert( subMd.path(), mdPtr ); + } + + Collection col; + col.setRemoteId( subFolder ); + col.setName( subFolder ); + col.setParentCollection( collection ); + fillMaildirCollectionDetails( subMd, col ); + collections << col; + + if ( recurse ) { + fillMaildirTreeDetails( subMd, col, collections, true ); + } + } + + const QDir dir( md.isRoot() ? md.path() : Maildir::subDirPathForFolderPath( md.path() ) ); + const QFileInfoList fileInfos = dir.entryInfoList( QDir::Files ); + Q_FOREACH( const QFileInfo &fileInfo, fileInfos ) { + if ( fileInfo.isHidden() || !fileInfo.isReadable() ) { + continue; + } + + const QString mboxPath = fileInfo.absoluteFilePath(); + + MBoxPtr mbox = getOrCreateMBoxPtr( mboxPath ); + if ( mbox->load( mboxPath ) ) { + const QString subFolder = fileInfo.fileName(); + Collection col; + col.setRemoteId( subFolder ); + col.setName( subFolder ); + col.setParentCollection( collection ); + mbox->mCollection = col; + + fillMBoxCollectionDetails( mbox, col ); + collections << col; + + if ( recurse ) { + const QString subDirPath = Maildir::subDirPathForFolderPath( fileInfo.absoluteFilePath() ); + const Maildir subMd( subDirPath, true ); + fillMaildirTreeDetails( subMd, col, collections, true ); + } + } else { + mMBoxes.remove( fileInfo.absoluteFilePath() ); + } + } +} + +void MixedMaildirStore::Private::listCollection( FileStore::Job *job, MBoxPtr &mbox, const Collection &collection, Item::List &items ) +{ + mbox->readIndexData(); + + QHash uidHash; + QHash tagListHash; + + const KMBox::MBoxEntry::List entryList = mbox->entryList(); + Q_FOREACH( const KMBox::MBoxEntry &entry, entryList ) { + Item item; + item.setMimeType( KMime::Message::mimeType() ); + item.setRemoteId( QString::number( entry.messageOffset() ) ); + item.setParentCollection( collection ); + + if ( mbox->hasIndexData() ) { + const KMIndexDataPtr indexData = mbox->indexData( entry.messageOffset() ); + if ( indexData != 0 && !indexData->isEmpty() ) { + item.setFlags( indexData->status().statusFlags() ); + + quint64 uid = indexData->uid(); + if ( uid != 0 ) { + kDebug() << "item" << item.remoteId() << "has UID" << uid; + uidHash.insert( item.remoteId(), QString::number( uid ) ); + } + + const QStringList tagList = indexData->tagList(); + if ( !tagList.isEmpty() ) { + kDebug() << "item" << item.remoteId() << "has" + << tagList.count() << "tags:" << tagList; + tagListHash.insert( item.remoteId(), tagList ); + } + } else if ( indexData == 0 ) { + Akonadi::MessageStatus status; + status.setDeleted( true ), + item.setFlags( status.statusFlags() ); + kDebug() << "no index for item" << item.remoteId() << "in MBox" << mbox->fileName() + << "so it has been deleted but not purged. Marking it as" + << item.flags(); + } + } + + items << item; + } + + if ( mbox->hasIndexData() ) { + QVariant var; + + if ( !uidHash.isEmpty() ) { + var = QVariant::fromValue< QHash >( uidHash ); + job->setProperty( "remoteIdToIndexUid", var ); + } + + if ( !tagListHash.isEmpty() ) { + var = QVariant::fromValue< QHash >( tagListHash ); + job->setProperty( "remoteIdToTagList", var ); + } + } +} + +void MixedMaildirStore::Private::listCollection( FileStore::Job *job, MaildirPtr &md, const Collection &collection, Item::List &items ) +{ + md->readIndexData(); + + QHash uidHash; + QHash tagListHash; + + const QStringList entryList = md->entryList(); + Q_FOREACH( const QString &entry, entryList ) { + Item item; + item.setMimeType( KMime::Message::mimeType() ); + item.setRemoteId( entry ); + item.setParentCollection( collection ); + + if ( md->hasIndexData() ) { + const KMIndexDataPtr indexData = md->indexData( entry ); + if ( indexData != 0 && !indexData->isEmpty() ) { + item.setFlags( indexData->status().statusFlags() ); + + const quint64 uid = indexData->uid(); + if ( uid != 0 ) { + kDebug() << "item" << item.remoteId() << "has UID" << uid; + uidHash.insert( item.remoteId(), QString::number( uid ) ); + } + + const QStringList tagList = indexData->tagList(); + if ( !tagList.isEmpty() ) { + kDebug() << "item" << item.remoteId() << "has" + << tagList.count() << "tags:" << tagList; + tagListHash.insert( item.remoteId(), tagList ); + } + } + } + Akonadi::Item::Flags flags = md->maildir().readEntryFlags( entry ); + Q_FOREACH( const Akonadi::Item::Flag& flag, flags ) { + item.setFlag(flag); + } + + items << item; + } + + if ( md->hasIndexData() ) { + QVariant var; + + if ( !uidHash.isEmpty() ) { + var = QVariant::fromValue< QHash >( uidHash ); + job->setProperty( "remoteIdToIndexUid", var ); + } + + if ( !tagListHash.isEmpty() ) { + var = QVariant::fromValue< QHash >( tagListHash ); + job->setProperty( "remoteIdToTagList", var ); + } + } +} + +bool MixedMaildirStore::Private::fillItem( MBoxPtr &mbox, bool includeHeaders, bool includeBody, Item &item ) const +{ +// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Filling item" << item.remoteId() << "from MBox: includeBody=" << includeBody; + bool ok = false; + const quint64 offset = item.remoteId().toULongLong( &ok ); + if ( !ok || !mbox->isValidOffset( offset ) ) { + return false; + } + + item.setModificationTime( mbox->modificationTime() ); + + // TODO: size? + + if ( includeHeaders || includeBody ) { + KMime::Message::Ptr messagePtr( new KMime::Message() ); + if ( includeBody ) { + const QByteArray data = mbox->readRawEntry( offset ); + messagePtr->setContent( KMime::CRLFtoLF( data ) ); + } else { + const QByteArray data = mbox->readEntryHeaders( offset ); + messagePtr->setHead( KMime::CRLFtoLF( data ) ); + } + messagePtr->parse(); + + item.setPayload( messagePtr ); + } + return true; +} + +bool MixedMaildirStore::Private::fillItem( const MaildirPtr &md, bool includeHeaders, bool includeBody, Item &item ) const +{ +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Filling item" << item.remoteId() << "from Maildir: includeBody=" << includeBody;*/ + + const qint64 entrySize = md->maildir().size( item.remoteId() ); + if ( entrySize < 0 ) + return false; + + item.setSize( entrySize ); + item.setModificationTime( md->maildir().lastModified( item.remoteId() ) ); + + if ( includeHeaders || includeBody ) { + KMime::Message::Ptr messagePtr( new KMime::Message() ); + if ( includeBody ) { + const QByteArray data = md->readEntry( item.remoteId() ); + if ( data.isEmpty() ) { + return false; + } + + messagePtr->setContent( KMime::CRLFtoLF( data ) ); + } else { + const QByteArray data = md->readEntryHeaders( item.remoteId() ); + if ( data.isEmpty() ) { + return false; + } + + messagePtr->setHead( KMime::CRLFtoLF( data ) ); + } + messagePtr->parse(); + + item.setPayload( messagePtr ); + } + return true; +} + +void MixedMaildirStore::Private::updateContextHashes( const QString &oldPath, const QString &newPath ) +{ + //kDebug() << "oldPath=" << oldPath << "newPath=" << newPath; + const QString oldSubDirPath = Maildir::subDirPathForFolderPath( oldPath ); + const QString newSubDirPath = Maildir::subDirPathForFolderPath( newPath ); + + MBoxHash mboxes; + MBoxHash::const_iterator mboxIt = mMBoxes.constBegin(); + MBoxHash::const_iterator mboxEndIt = mMBoxes.constEnd(); + for ( ; mboxIt != mboxEndIt; ++mboxIt ) { + QString key = mboxIt.key(); + MBoxPtr mboxPtr = mboxIt.value(); + + if ( key == oldPath ) { + key = newPath; + } else if ( key.startsWith( oldSubDirPath ) ) { + if ( mboxPtr->hasIndexData() || mboxPtr->mRevision > 0 ) { + key.replace( oldSubDirPath, newSubDirPath ); + } else { + // if there is no index data yet, just discard this context + key.clear(); + } + } + + if ( !key.isEmpty() ) { + mboxPtr->updatePath( key ); + mboxes.insert( key, mboxPtr ); + } + } + //kDebug() << "mbox: old keys=" << mMBoxes.keys() << "new keys" << mboxes.keys(); + mMBoxes = mboxes; + + MaildirHash maildirs; + MaildirHash::const_iterator mdIt = mMaildirs.constBegin(); + MaildirHash::const_iterator mdEndIt = mMaildirs.constEnd(); + for ( ; mdIt != mdEndIt; ++mdIt ) { + QString key = mdIt.key(); + MaildirPtr mdPtr = mdIt.value(); + + if ( key == oldPath ) { + key = newPath; + } else if ( key.startsWith( oldSubDirPath ) ) { + if ( mdPtr->hasIndexData() ) { + key.replace( oldSubDirPath, newSubDirPath ); + } else { + // if there is no index data yet, just discard this context + key.clear(); + } + } + + if ( !key.isEmpty() ) { + mdPtr->updatePath( key ); + maildirs.insert( key, mdPtr ); + } + } + //kDebug() << "maildir: old keys=" << mMaildirs.keys() << "new keys" << maildirs.keys(); + mMaildirs = maildirs; +} + +bool MixedMaildirStore::Private::visit( FileStore::Job *job ) +{ + const QString message = i18nc( "@info:status", "Unhandled operation %1", QLatin1String(job->metaObject()->className()) ); + kError() << message; + q->notifyError( FileStore::Job::InvalidJobContext, message ); + return false; +} + +bool MixedMaildirStore::Private::visit( FileStore::CollectionCreateJob *job ) +{ + QString path; + QString errorText; + + const FolderType folderType = folderForCollection( job->targetParent(), path, errorText ); + if ( folderType == InvalidFolder ) { + errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2", + job->collection().name(), job->targetParent().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + const QString collectionName = job->collection().name().replace( QDir::separator(), QString() ); + Maildir md; + if ( folderType == MBoxFolder ) { + const QString subDirPath = Maildir::subDirPathForFolderPath( path ); + const QDir dir( subDirPath ); + const QFileInfo dirInfo( dir, collectionName ); + if ( dirInfo.exists() && !dirInfo.isDir() ) { + errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2", + job->collection().name(), job->targetParent().name() ); + kError() << errorText << "FolderType=" << folderType << ", dirInfo exists and it not a dir"; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( !dir.mkpath( collectionName ) ) { + errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2", + job->collection().name(), job->targetParent().name() ); + kError() << errorText << "FolderType=" << folderType << ", mkpath failed"; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + md = Maildir( dirInfo.absoluteFilePath(), false ); + if ( !md.create() ) { + errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2", + job->collection().name(), job->targetParent().name() ); + kError() << errorText << "FolderType=" << folderType << ", maildir create failed"; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + const MaildirPtr mdPtr( new MaildirContext( md ) ); + mMaildirs.insert( md.path(), mdPtr ); + } else { + Maildir parentMd( path, folderType == TopLevelFolder ); + if ( parentMd.addSubFolder( collectionName ).isEmpty() ) { + errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2", + job->collection().name(), job->targetParent().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + md = Maildir( parentMd.subFolder( collectionName ) ); + const MaildirPtr mdPtr( new MaildirContext( md ) ); + mMaildirs.insert( md.path(), mdPtr ); + } + + Collection collection = job->collection(); + collection.setRemoteId( collectionName ); + collection.setName( collectionName ); + collection.setParentCollection( job->targetParent() ); + fillMaildirCollectionDetails( md, collection ); + + q->notifyCollectionsProcessed( Collection::List() << collection ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::CollectionDeleteJob *job ) +{ + QString path; + QString errorText; + + const FolderType folderType = folderForCollection( job->collection(), path, errorText ); + if ( folderType == InvalidFolder ) { + errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2", + job->collection().name(), job->collection().parentCollection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + QString parentPath; + const FolderType parentFolderType = folderForCollection( job->collection().parentCollection(), parentPath, errorText ); + if ( parentFolderType == InvalidFolder ) { + errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2", + job->collection().name(), job->collection().parentCollection().name() ); + kError() << errorText << "Parent FolderType=" << parentFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( folderType == MBoxFolder ) { + if ( !QFile::remove( path ) ) { + errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2", + job->collection().name(), job->collection().parentCollection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } else { + if ( !KPIMUtils::removeDirAndContentsRecursively( path ) ) { + errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2", + job->collection().name(), job->collection().parentCollection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } + + const QString subDirPath = Maildir::subDirPathForFolderPath( path ); + KPIMUtils::removeDirAndContentsRecursively( subDirPath ); + + q->notifyCollectionsProcessed( Collection::List() << job->collection() ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::CollectionFetchJob *job ) +{ + QString path; + QString errorText; + const FolderType folderType = folderForCollection( job->collection(), path, errorText ); + + if ( folderType == InvalidFolder ) { + kError() << errorText << "collection:" << job->collection(); + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + Collection::List collections; + Collection collection = job->collection(); + if ( job->type() == FileStore::CollectionFetchJob::Base ) { + collection.setName( collection.remoteId() ); + if ( folderType == MBoxFolder ) { + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( path ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( path ) ) { + errorText = i18nc( "@info:status", "Failed to load MBox folder %1", path ); + kError() << errorText << "collection=" << collection; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code + return false; + } + + mbox->mCollection = collection; + mMBoxes.insert( path, mbox ); + } else { + mbox = findIt.value(); + } + + fillMBoxCollectionDetails( mbox, collection ); + } else { + const Maildir md( path, folderType == TopLevelFolder ); + fillMaildirCollectionDetails( md, collection ); + } + collections << collection; + } else { + // if the base is an mbox, use its sub folder dir like a top level maildir + if ( folderType == MBoxFolder ) { + path = Maildir::subDirPathForFolderPath( path ); + } + const Maildir md( path, folderType != MaildirFolder ); + fillMaildirTreeDetails( md, collection, collections, + job->type() == FileStore::CollectionFetchJob::Recursive ); + } + + if ( !collections.isEmpty() ) { + q->notifyCollectionsProcessed( collections ); + } + return true; +} + +static Collection updateMBoxCollectionTree( const Collection &collection, const Collection &oldParent, const Collection &newParent ) +{ + if ( collection == oldParent ) { + return newParent; + } + + if ( collection == Collection::root() ) { + return collection; + } + + Collection updatedCollection = collection; + updatedCollection.setParentCollection( updateMBoxCollectionTree( collection.parentCollection(), oldParent, newParent ) ); + + return updatedCollection; +} + +bool MixedMaildirStore::Private::visit( FileStore::CollectionModifyJob *job ) +{ + const Collection collection = job->collection(); + const QString collectionName = collection.name().replace( QDir::separator(), QString() ); + + // we also only do renames + if ( collection.remoteId() == collection.name() ) { + kWarning() << "CollectionModifyJob with name still identical to remoteId. Ignoring"; + return true; + } + + QString path; + QString errorText; + const FolderType folderType = folderForCollection( collection, path, errorText ); + if ( folderType == InvalidFolder ) { + errorText = i18nc( "@info:status", "Cannot rename folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + const QFileInfo fileInfo( path ); + const QFileInfo subDirInfo = Maildir::subDirPathForFolderPath( path ); + + QDir parentDir( path ); + parentDir.cdUp(); + + const QFileInfo targetFileInfo( parentDir, collectionName ); + const QFileInfo targetSubDirInfo = Maildir::subDirPathForFolderPath( targetFileInfo.absoluteFilePath() ); + + if ( targetFileInfo.exists() || ( subDirInfo.exists() && targetSubDirInfo.exists() ) ) { + errorText = i18nc( "@info:status", "Cannot rename folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + // if there is an index, make sure it is read before renaming + // do not rename index as it could already be out of date + bool indexInvalidated = false; + if ( folderType == MBoxFolder ) { + // TODO would be nice if getOrCreateMBoxPtr() could be used instead, like below for Maildir + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( path ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( path ) ) { + kWarning() << "Failed to load mbox" << path; + } + + mbox->mCollection = collection; + mMBoxes.insert( path, mbox ); + } else { + mbox = findIt.value(); + } + + mbox->readIndexData(); + indexInvalidated = mbox->hasIndexData(); + } else if ( folderType == MaildirFolder ) { + MaildirPtr md = getOrCreateMaildirPtr( path, false ); + + md->readIndexData(); + indexInvalidated = md->hasIndexData(); + } + + if ( !parentDir.rename( fileInfo.absoluteFilePath(), targetFileInfo.fileName() ) ) { + errorText = i18nc( "@info:status", "Cannot rename folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( subDirInfo.exists() ) { + if ( !parentDir.rename( subDirInfo.absoluteFilePath(), targetSubDirInfo.fileName() ) ) { + errorText = i18nc( "@info:status", "Cannot rename folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + + // try to recover the previous rename + parentDir.rename( targetFileInfo.absoluteFilePath(), fileInfo.fileName() ); + return false; + } + } + + // update context hashes + updateContextHashes( fileInfo.absoluteFilePath(), targetFileInfo.absoluteFilePath() ); + + Collection renamedCollection = collection; + + // when renaming top level folder, change path of store + if ( folderType == TopLevelFolder ) { + // backup caches, setTopLevelCollection() clears them + const MBoxHash mboxes = mMBoxes; + const MaildirHash maildirs = mMaildirs; + + q->setPath( targetFileInfo.absoluteFilePath() ); + + // restore caches + mMBoxes = mboxes; + mMaildirs = maildirs; + + renamedCollection = q->topLevelCollection(); + } else { + renamedCollection.setRemoteId( collectionName ); + renamedCollection.setName( collectionName ); + } + + // update collections in MBox contexts so they stay usable for purge + Q_FOREACH( const MBoxPtr &mbox, mMBoxes ) { + if ( mbox->mCollection.isValid() ) { + MBoxPtr updatedMBox = mbox; + updatedMBox->mCollection = updateMBoxCollectionTree( mbox->mCollection, collection, renamedCollection ); + } + } + + if ( indexInvalidated ) { + const QVariant var = QVariant::fromValue( Collection::List() << renamedCollection ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + q->notifyCollectionsProcessed( Collection::List() << renamedCollection ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::CollectionMoveJob *job ) +{ + QString errorText; + + const Collection moveCollection = job->collection(); + const Collection targetCollection = job->targetParent(); + + QString movePath; + const FolderType moveFolderType = folderForCollection( moveCollection, movePath, errorText ); + if ( moveFolderType == InvalidFolder || moveFolderType == TopLevelFolder ) { + errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3", + moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() ); + kError() << errorText << "FolderType=" << moveFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + +// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "moveCollection" << moveCollection.remoteId() +// << "movePath=" << movePath +// << "moveType=" << moveFolderType; + + QString targetPath; + const FolderType targetFolderType = folderForCollection( targetCollection, targetPath, errorText ); + if ( targetFolderType == InvalidFolder ) { + errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3", + moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + +// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "targetCollection" << targetCollection.remoteId() +// << "targetPath=" << targetPath +// << "targetType=" << targetFolderType; + + const QFileInfo targetSubDirInfo( Maildir::subDirPathForFolderPath( targetPath ) ); + + // if target is not the top level folder, make sure the sub folder directory exists + if ( targetFolderType != TopLevelFolder ) { + if ( !targetSubDirInfo.exists() ) { + QDir topDir( q->path() ); + if ( !topDir.mkpath( targetSubDirInfo.absoluteFilePath() ) ) { + errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3", + moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() ); + kError() << errorText << "MoveFolderType=" << moveFolderType + << "TargetFolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } + } + + bool indexInvalidated = false; + QString movedPath; + + if ( moveFolderType == MBoxFolder ) { + // TODO would be nice if getOrCreateMBoxPtr() could be used instead, like below for Maildir + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( movePath ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( movePath ) ) { + kWarning() << "Failed to load mbox" << movePath; + } + + mbox->mCollection = moveCollection; + mMBoxes.insert( movePath, mbox ); + } else { + mbox = findIt.value(); + } + + mbox->readIndexData(); + indexInvalidated = mbox->hasIndexData(); + + const QFileInfo moveFileInfo( movePath ); + const QFileInfo moveSubDirInfo( Maildir::subDirPathForFolderPath( movePath ) ); + const QFileInfo targetFileInfo( targetPath ); + + QDir targetDir( targetFolderType == TopLevelFolder ? + targetPath : Maildir::subDirPathForFolderPath( targetPath ) ); + if ( targetDir.exists( moveFileInfo.fileName() ) || + !targetDir.rename( moveFileInfo.absoluteFilePath(), moveFileInfo.fileName() ) ) { + errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3", + moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() ); + kError() << errorText << "MoveFolderType=" << moveFolderType + << "TargetFolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( moveSubDirInfo.exists() ) { + if ( targetDir.exists( moveSubDirInfo.fileName() ) || + !targetDir.rename( moveSubDirInfo.absoluteFilePath(), moveSubDirInfo.fileName() ) ) { + errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3", + moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() ); + kError() << errorText << "MoveFolderType=" << moveFolderType + << "TargetFolderType=" << targetFolderType; + + // try to revert the other rename + QDir sourceDir( moveFileInfo.absolutePath() ); + sourceDir.cdUp(); + sourceDir.rename( targetFileInfo.absoluteFilePath(), moveFileInfo.fileName() ); + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } + + movedPath = QFileInfo( targetDir, moveFileInfo.fileName() ).absoluteFilePath(); + } else { + MaildirPtr md = getOrCreateMaildirPtr( movePath, false ); + + md->readIndexData(); + indexInvalidated = md->hasIndexData(); + + Maildir moveMd( movePath, false ); + + // for moving purpose we can treat the MBox target's subDirPath like a top level maildir + Maildir targetMd; + if ( targetFolderType == MBoxFolder ) { + targetMd = Maildir( targetSubDirInfo.absoluteFilePath(), true ); + } else { + targetMd = Maildir( targetPath, targetFolderType == TopLevelFolder ); + } + + if ( !moveMd.moveTo( targetMd ) ) { + errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3", + moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() ); + kError() << errorText << "MoveFolderType=" << moveFolderType + << "TargetFolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + movedPath = targetMd.subFolder( moveCollection.remoteId() ).path(); + } + + // update context hashes + updateContextHashes( movePath, movedPath ); + + Collection movedCollection = moveCollection; + movedCollection.setParentCollection( targetCollection ); + + // update collections in MBox contexts so they stay usable for purge + Q_FOREACH( const MBoxPtr &mbox, mMBoxes ) { + if ( mbox->mCollection.isValid() ) { + MBoxPtr updatedMBox = mbox; + updatedMBox->mCollection = updateMBoxCollectionTree( mbox->mCollection, moveCollection, movedCollection ); + } + } + + if ( indexInvalidated ) { + const QVariant var = QVariant::fromValue( Collection::List() << movedCollection ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + q->notifyCollectionsProcessed( Collection::List() << movedCollection ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::ItemCreateJob *job ) +{ + QString path; + QString errorText; + + const FolderType folderType = folderForCollection( job->collection(), path, errorText ); + if ( folderType == InvalidFolder || folderType == TopLevelFolder ) { + errorText = i18nc( "@info:status", "Cannot add emails to folder %1", + job->collection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + Item item = job->item(); + + if ( folderType == MBoxFolder ) { + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( path ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( path ) ) { + errorText = i18nc( "@info:status", "Cannot add emails to folder %1", + job->collection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + mbox->mCollection = job->collection(); + mMBoxes.insert( path, mbox ); + } else { + mbox = findIt.value(); + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mbox->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( mbox->hasIndexData() ) { + const QVariant var = QVariant::fromValue( Collection::List() << job->collection() ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + qint64 result = mbox->appendEntry( item.payload() ); + if ( result < 0 ) { + errorText = i18nc( "@info:status", "Cannot add emails to folder %1", + job->collection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + mbox->save(); + item.setRemoteId( QString::number( result ) ); + } else { + MaildirPtr mdPtr; + MaildirHash::const_iterator findIt = mMaildirs.constFind( path ); + if ( findIt == mMaildirs.constEnd() ) { + mdPtr = MaildirPtr( new MaildirContext( path, false ) ); + mMaildirs.insert( path, mdPtr ); + } else { + mdPtr = findIt.value(); + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mdPtr->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( mdPtr->hasIndexData() ) { + const QVariant var = QVariant::fromValue( Collection::List() << job->collection() ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + const QString result = mdPtr->addEntry( item.payload()->encodedContent() ); + if ( result.isEmpty() ) { + errorText = i18nc( "@info:status", "Cannot add emails to folder %1", + job->collection().name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + item.setRemoteId( result ); + } + + item.setParentCollection( job->collection() ); + q->notifyItemsProcessed( Item::List() << item ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::ItemDeleteJob *job ) +{ + const Item item = job->item(); + const Collection collection = item.parentCollection(); + QString path; + QString errorText; + + const FolderType folderType = folderForCollection( collection, path, errorText ); + if ( folderType == InvalidFolder || folderType == TopLevelFolder ) { + errorText = i18nc( "@info:status", "Cannot remove emails from folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( folderType == MBoxFolder ) { + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( path ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( path ) ) { + errorText = i18nc( "@info:status", "Cannot remove emails from folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + mMBoxes.insert( path, mbox ); + } else { + mbox = findIt.value(); + } + + bool ok = false; + qint64 offset = item.remoteId().toLongLong( &ok ); + if ( !ok || offset < 0 || !mbox->isValidOffset( offset ) ) { + errorText = i18nc( "@info:status", "Cannot remove emails from folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + mbox->mCollection = collection; + mbox->deleteEntry( offset ); + job->setProperty( "compactStore", true ); + } else { + MaildirPtr mdPtr; + MaildirHash::const_iterator findIt = mMaildirs.constFind( path ); + if ( findIt == mMaildirs.constEnd() ) { + mdPtr = MaildirPtr( new MaildirContext( path, false ) ); + + mMaildirs.insert( path, mdPtr ); + } else { + mdPtr = findIt.value(); + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mdPtr->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( mdPtr->hasIndexData() ) { + const QVariant var = QVariant::fromValue( Collection::List() << collection ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + if ( !mdPtr->removeEntry( item.remoteId() ) ) { + errorText = i18nc( "@info:status", "Cannot remove emails from folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } + + q->notifyItemsProcessed( Item::List() << item ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::ItemFetchJob *job ) +{ + ItemFetchScope scope = job->fetchScope(); + const bool includeBody = scope.fullPayload() || + scope.payloadParts().contains( MessagePart::Body ); + const bool includeHeaders = scope.payloadParts().contains( MessagePart::Header ) || + scope.payloadParts().contains( MessagePart::Envelope ); + + const bool fetchSingleItem = job->collection().remoteId().isEmpty(); + const Collection collection = fetchSingleItem ? job->item().parentCollection() : job->collection(); + + QString path; + QString errorText; + Q_ASSERT( !collection.remoteId().isEmpty() ); + const FolderType folderType = folderForCollection( collection, path, errorText ); + + if ( folderType == InvalidFolder ) { + kError() << errorText << "collection:" << job->collection(); + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( folderType == MBoxFolder ) { + MBoxHash::iterator findIt = mMBoxes.find( path ); + if ( findIt == mMBoxes.end() || !fetchSingleItem ) { + MBoxPtr mbox = findIt != mMBoxes.end() ? findIt.value() : MBoxPtr( new MBoxContext ); + if ( !mbox->load( path ) ) { + errorText = i18nc( "@info:status", "Failed to load MBox folder %1", path ); + kError() << errorText << "collection=" << collection; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code + if ( findIt != mMBoxes.end() ) { + mMBoxes.erase( findIt ); + } + return false; + } + + if ( findIt == mMBoxes.end() ) { + findIt = mMBoxes.insert( path, mbox ); + } + } + + Item::List items; + if ( fetchSingleItem ) { + items << job->item(); + } else { + listCollection( job, findIt.value(), collection, items ); + } + + Item::List::iterator it = items.begin(); + Item::List::iterator endIt = items.end(); + for ( ; it != endIt; ++it ) { + if ( !fillItem( findIt.value(), includeHeaders, includeBody, *it ) ) { + const QString errorText = + i18nc( "@info:status", "Error while reading mails from folder %1", collection.name() ); + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code + kError() << "Failed to read item" << (*it).remoteId() << "in MBox file" << path; + return false; + } + } + + if ( !items.isEmpty() ) { + q->notifyItemsProcessed( items ); + } + } else { + MaildirPtr mdPtr; + MaildirHash::const_iterator mdIt = mMaildirs.constFind( path ); + if ( mdIt == mMaildirs.constEnd() ) { + mdPtr = MaildirPtr( new MaildirContext( path, folderType == TopLevelFolder ) ); + mMaildirs.insert( path, mdPtr ); + } else { + mdPtr = mdIt.value(); + } + + if ( !mdPtr->isValid( errorText ) ) { + errorText = i18nc( "@info:status", "Failed to load Maildirs folder %1", path ); + kError() << errorText << "collection=" << collection; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code + return false; + } + + Item::List items; + if ( fetchSingleItem ) { + items << job->item(); + } else { + listCollection( job, mdPtr, collection, items ); + } + + Item::List::iterator it = items.begin(); + Item::List::iterator endIt = items.end(); + for ( ; it != endIt; ++it ) { + if ( !fillItem( mdPtr, includeHeaders, includeBody, *it ) ) { + const QString errorText = + i18nc( "@info:status", "Error while reading mails from folder %1", collection.name() ); + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code + kError() << "Failed to read item" << (*it).remoteId() << "in Maildir" << path; + return false; + } + } + + if ( !items.isEmpty() ) { + q->notifyItemsProcessed( items ); + } + } + + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::ItemModifyJob *job ) +{ + const QSet parts = job->parts(); + bool payloadChanged = false; + bool flagsChanged = false; + Q_FOREACH( const QByteArray &part, parts ) { + if ( part.startsWith( "PLD:" ) ) { + payloadChanged = true; + } + if ( part.contains( "FLAGS" ) ) { + flagsChanged = true; + } + } + + const bool nothingChanged = ( !payloadChanged && !flagsChanged ); + const bool payloadChangedButIgnored = payloadChanged && job->ignorePayload(); + const bool ignoreModifyIfValid = nothingChanged || + ( payloadChangedButIgnored && !flagsChanged ); + + Item item = job->item(); + const Collection collection = item.parentCollection(); + QString path; + QString errorText; + + const FolderType folderType = folderForCollection( collection, path, errorText ); + if ( folderType == InvalidFolder || folderType == TopLevelFolder ) { + errorText = i18nc( "@info:status", "Cannot modify emails in folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( folderType == MBoxFolder ) { + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( path ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( path ) ) { + errorText = i18nc( "@info:status", "Cannot modify emails in folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + mMBoxes.insert( path, mbox ); + } else { + mbox = findIt.value(); + } + + bool ok = false; + qint64 offset = item.remoteId().toLongLong( &ok ); + if ( !ok || offset < 0 || !mbox->isValidOffset( offset ) ) { + errorText = i18nc( "@info:status", "Cannot modify emails in folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + // if we can ignore payload, or we have nothing else to change, then we are finished + if ( ignoreModifyIfValid ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "ItemModifyJob for item" << item.remoteId() + << "in collection" << collection.remoteId() + << "skipped: nothing of interest changed (" << nothingChanged + << ") or only payload changed but should be ignored (" + << ( payloadChanged && !flagsChanged && job->ignorePayload() ) + << "). Modified parts:" << parts; + q->notifyItemsProcessed( Item::List() << job->item() ); + return true; + } + + // mbox can only change payload, ignore any other change + if ( !payloadChanged ) { + q->notifyItemsProcessed( Item::List() << item ); + return true; + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mbox->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( mbox->hasIndexData() ) { + const QVariant var = QVariant::fromValue( Collection::List() << collection ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + qint64 newOffset = mbox->appendEntry( item.payload() ); + if ( newOffset < 0 ) { + errorText = i18nc( "@info:status", "Cannot modify emails in folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( newOffset > 0 ) { + mbox->mCollection = collection; + mbox->deleteEntry( offset ); + job->setProperty( "compactStore", true ); + } + mbox->save(); + item.setRemoteId( QString::number( newOffset ) ); + } else { + MaildirPtr mdPtr; + MaildirHash::const_iterator findIt = mMaildirs.constFind( path ); + if ( findIt == mMaildirs.constEnd() ) { + mdPtr = MaildirPtr( new MaildirContext( path, false ) ); + mMaildirs.insert( path, mdPtr ); + } else { + mdPtr = findIt.value(); + } + + if ( !mdPtr->isValidEntry( item.remoteId() ) ) { + errorText = i18nc( "@info:status", "Cannot modify emails in folder %1", + collection.name() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + // if we can ignore payload, or we have nothing else to change, then we are finished + if ( ignoreModifyIfValid ) { + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "ItemModifyJob for item" << item.remoteId() + << "in collection" << collection.remoteId() + << "skipped: nothing of interest changed (" << nothingChanged + << ") or only payload changed but should be ignored (" + << ( payloadChanged && !flagsChanged && job->ignorePayload() ) + << "). Modified parts:" << parts; + q->notifyItemsProcessed( Item::List() << job->item() ); + return true; + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mdPtr->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( mdPtr->hasIndexData() ) { + const QVariant var = QVariant::fromValue( Collection::List() << collection ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + QString newKey = item.remoteId(); + if ( flagsChanged ) { + Maildir md( mdPtr->maildir() ); + newKey = md.changeEntryFlags( item.remoteId(), item.flags() ); + if ( newKey.isEmpty() ) { + errorText = i18nc( "@info:status", "Cannot modify emails in folder %1. %2", + collection.name(), md.lastError() ); + kError() << errorText << "FolderType=" << folderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + item.setRemoteId( newKey ); + } + + if ( payloadChanged ) { + mdPtr->writeEntry( newKey, item.payload()->encodedContent() ); + } + } + + q->notifyItemsProcessed( Item::List() << item ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::ItemMoveJob *job ) +{ + QString errorText; + + QString sourcePath; + const Collection sourceCollection = job->item().parentCollection(); + const FolderType sourceFolderType = folderForCollection( sourceCollection, sourcePath, errorText ); + if ( sourceFolderType == InvalidFolder || sourceFolderType == TopLevelFolder ) { + errorText = i18nc( "@info:status", "Cannot move emails from folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + +// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "sourceCollection" << sourceCollection.remoteId() +// << "sourcePath=" << sourcePath +// << "sourceType=" << sourceFolderType; + + QString targetPath; + const Collection targetCollection = job->targetParent(); + const FolderType targetFolderType = folderForCollection( targetCollection, targetPath, errorText ); + if ( targetFolderType == InvalidFolder || targetFolderType == TopLevelFolder ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + +// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "targetCollection" << targetCollection.remoteId() +// << "targetPath=" << targetPath +// << "targetType=" << targetFolderType; + + Item item = job->item(); + + if ( sourceFolderType == MBoxFolder ) { +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "source is MBox";*/ + bool ok= false; + quint64 offset = item.remoteId().toULongLong( &ok ); + if ( !ok ) { + errorText = i18nc( "@info:status", "Cannot move emails from folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( sourcePath ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( sourcePath ) ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + mbox->mCollection = sourceCollection; + mMBoxes.insert( sourcePath, mbox ); + } else { + mbox = findIt.value(); + } + + if ( !mbox->isValidOffset( offset ) ) { + errorText = i18nc( "@info:status", "Cannot move emails from folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( !item.hasPayload() || + !item.loadedPayloadParts().contains( MessagePart::Body ) ) { + if ( !fillItem( mbox, true, true, item ) ) { + errorText = i18nc( "@info:status", "Cannot move email from folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } + + Collection::List collections; + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mbox->readIndexData(); + + if ( mbox->hasIndexData() ) { + collections << sourceCollection; + } + + if ( targetFolderType == MBoxFolder ) { +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is MBox";*/ + MBoxPtr targetMBox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( targetPath ); + if ( findIt == mMBoxes.constEnd() ) { + targetMBox = MBoxPtr( new MBoxContext ); + if ( !targetMBox->load( targetPath ) ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + targetMBox->mCollection = targetCollection; + mMBoxes.insert( targetPath, targetMBox ); + } else { + targetMBox = findIt.value(); + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + targetMBox->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( targetMBox->hasIndexData() ) { + collections << targetCollection; + } + + qint64 remoteId = targetMBox->appendEntry( item.payload() ); + if ( remoteId < 0 ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + if ( !targetMBox->save() ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + item.setRemoteId( QString::number( remoteId ) ); + } else { +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is Maildir";*/ + MaildirPtr targetMdPtr = getOrCreateMaildirPtr( targetPath, false ); + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + targetMdPtr->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( targetMdPtr->hasIndexData() ) { + collections << targetCollection; + } + + const QString remoteId = targetMdPtr->addEntry( mbox->readRawEntry( offset ) ); + if ( remoteId.isEmpty() ) { + errorText = i18nc( "@info:status", "Cannot move email from folder %1 to folder %2", + sourceCollection.name(), targetCollection.name() ); + kError() << errorText << "SourceFolderType=" << sourceFolderType << "TargetFolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + item.setRemoteId( remoteId ); + } + + if ( !collections.isEmpty() ) { + const QVariant var = QVariant::fromValue( collections ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + mbox->mCollection = sourceCollection; + mbox->deleteEntry( offset ); + job->setProperty( "compactStore", true ); + } else { +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "source is Maildir";*/ + MaildirPtr sourceMdPtr = getOrCreateMaildirPtr( sourcePath, false ); + + if ( !sourceMdPtr->isValidEntry( item.remoteId() ) ) { + errorText = i18nc( "@info:status", "Cannot move email from folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + Collection::List collections; + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + sourceMdPtr->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( sourceMdPtr->hasIndexData() ) { + collections << sourceCollection; + } + + if ( targetFolderType == MBoxFolder ) { +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is MBox";*/ + if ( !item.hasPayload() || + !item.loadedPayloadParts().contains( MessagePart::Body ) ) { + if ( !fillItem( sourceMdPtr, true, true, item ) ) { + errorText = i18nc( "@info:status", "Cannot move email from folder %1", + sourceCollection.name() ); + kError() << errorText << "FolderType=" << sourceFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + } + + MBoxPtr mbox; + MBoxHash::const_iterator findIt = mMBoxes.constFind( targetPath ); + if ( findIt == mMBoxes.constEnd() ) { + mbox = MBoxPtr( new MBoxContext ); + if ( !mbox->load( targetPath ) ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + mbox->mCollection = targetCollection; + mMBoxes.insert( targetPath, mbox ); + } else { + mbox = findIt.value(); + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mbox->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( mbox->hasIndexData() ) { + collections << targetCollection; + } + + const qint64 remoteId = mbox->appendEntry( item.payload() ); + if ( remoteId < 0 ) { + errorText = i18nc( "@info:status", "Cannot move emails to folder %1", + targetCollection.name() ); + kError() << errorText << "FolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + sourceMdPtr->removeEntry( item.remoteId() ); + + mbox->save(); + item.setRemoteId( QString::number( remoteId ) ); + } else { +/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is Maildir";*/ + MaildirPtr targetMdPtr = getOrCreateMaildirPtr( targetPath, false ); + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + targetMdPtr->readIndexData(); + + // if there is index data now, we let the job creator know that the on-disk index + // became invalid + if ( targetMdPtr->hasIndexData() ) { + collections << targetCollection; + } + + const QString remoteId = sourceMdPtr->moveEntryTo( item.remoteId(), *targetMdPtr ); + if ( remoteId.isEmpty() ) { + errorText = i18nc( "@info:status", "Cannot move email from folder %1 to folder %2", + sourceCollection.name(), targetCollection.name() ); + kError() << errorText << "SourceFolderType=" << sourceFolderType << "TargetFolderType=" << targetFolderType; + q->notifyError( FileStore::Job::InvalidJobContext, errorText ); + return false; + } + + item.setRemoteId( remoteId ); + } + + if ( !collections.isEmpty() ) { + const QVariant var = QVariant::fromValue( collections ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + } + + item.setParentCollection( targetCollection ); + q->notifyItemsProcessed( Item::List() << item ); + return true; +} + +bool MixedMaildirStore::Private::visit( FileStore::StoreCompactJob *job ) +{ + Q_UNUSED( job ); + + Collection::List collections; + + MBoxHash::const_iterator it = mMBoxes.constBegin(); + MBoxHash::const_iterator endIt = mMBoxes.constEnd(); + for ( ; it != endIt; ++it ) { + MBoxPtr mbox = it.value(); + + if ( !mbox->hasDeletedOffsets() ) { + continue; + } + + // make sure to read the index (if available) before modifying the data, which would + // make the index invalid + mbox->readIndexData(); + + QList movedEntries; + const int result = mbox->purge( movedEntries ); + if ( result > 0 ) { + if ( movedEntries.count() > 0 ) { + qint64 revision = mbox->mCollection.remoteRevision().toLongLong(); + kDebug() << "purge of" << mbox->mCollection.name() << "caused item move: oldRevision=" + << revision << "(stored)," << mbox->mRevision << "(local)"; + revision = qMax( revision, mbox->mRevision ) + 1; + + const QString remoteRevision = QString::number( revision ); + + Collection collection = mbox->mCollection; + collection.attribute( Entity::AddIfMissing )->setRemoteRevision( remoteRevision ); + + q->notifyCollectionsProcessed( Collection::List() << collection ); + + mbox->mCollection.setRemoteRevision( remoteRevision ); + mbox->mRevision = revision; + } + + Item::List items; + Q_FOREACH( const KMBox::MBoxEntry::Pair &offsetPair, movedEntries ) { + const QString oldRemoteId( QString::number( offsetPair.first.messageOffset() ) ); + const QString newRemoteId( QString::number( offsetPair.second.messageOffset() ) ); + + Item item; + item.setRemoteId( oldRemoteId ); + item.setParentCollection( mbox->mCollection ); + item.attribute( Entity::AddIfMissing )->setRemoteId( newRemoteId ); + + items << item; + } + + // if there is index data, we let the job creator know that the on-disk index + // became invalid + if ( mbox->hasIndexData() ) { + collections << mbox->mCollection; + } + + if ( !items.isEmpty() ) { + q->notifyItemsProcessed( items ); + } + } + } + + if ( !collections.isEmpty() ) { + const QVariant var = QVariant::fromValue( collections ); + job->setProperty( "onDiskIndexInvalidated", var ); + } + + return true; +} + +MixedMaildirStore::MixedMaildirStore() : FileStore::AbstractLocalStore(), d( new Private( this ) ) +{ +} + +MixedMaildirStore::~MixedMaildirStore() +{ + delete d; +} + +void MixedMaildirStore::setTopLevelCollection( const Collection &collection ) +{ + QStringList contentMimeTypes; + contentMimeTypes << Collection::mimeType(); + + Collection::Rights rights; + // TODO check if read-only? + rights = Collection::CanCreateCollection | Collection::CanChangeCollection | Collection::CanDeleteCollection; + + CachePolicy cachePolicy; + cachePolicy.setInheritFromParent( false ); + cachePolicy.setLocalParts( QStringList() << QLatin1String(MessagePart::Envelope) ); + cachePolicy.setSyncOnDemand( true ); + cachePolicy.setCacheTimeout( 1 ); + + Collection modifiedCollection = collection; + modifiedCollection.setContentMimeTypes( contentMimeTypes ); + modifiedCollection.setRights( rights ); + modifiedCollection.setParentCollection( Collection::root() ); + modifiedCollection.setCachePolicy( cachePolicy ); + + // clear caches + d->mMBoxes.clear(); + d->mMaildirs.clear(); + + FileStore::AbstractLocalStore::setTopLevelCollection( modifiedCollection ); +} + +void MixedMaildirStore::processJob( FileStore::Job *job ) +{ + if ( !job->accept( d ) ) { + // check that an error has been set + if ( job->error() == 0 || job->errorString().isEmpty() ) { + kError() << "visitor did not set either error code or error string when returning false"; + Q_ASSERT( job->error() == 0 || job->errorString().isEmpty() ); + } + } else { + // check that no error has been set + if ( job->error() != 0 || !job->errorString().isEmpty() ) { + kError() << "visitor did set either error code or error string when returning true"; + Q_ASSERT( job->error() != 0 || !job->errorString().isEmpty() ); + } + } +} + +void MixedMaildirStore::checkCollectionMove( FileStore::CollectionMoveJob *job, int &errorCode, QString &errorText ) const +{ + // check if the target is not the collection itself or one if its children + Collection targetCollection = job->targetParent(); + while ( targetCollection.isValid() ) { + if ( targetCollection == job->collection() ) { + errorCode = FileStore::Job::InvalidJobContext; + errorText = i18nc( "@info:status", "Cannot move folder %1 into one of its own subfolder tree", job->collection().name() ); + return; + } + + targetCollection = targetCollection.parentCollection(); + } +} + +void MixedMaildirStore::checkItemCreate( FileStore::ItemCreateJob *job, int &errorCode, QString &errorText ) const +{ + if ( !job->item().hasPayload() ) { + errorCode = FileStore::Job::InvalidJobContext; + errorText = i18nc( "@info:status", "Cannot add email to folder %1 because there is no email content", job->collection().name() ); + } +} + +void MixedMaildirStore::checkItemModify( FileStore::ItemModifyJob *job, int &errorCode, QString &errorText ) const +{ + if ( !job->ignorePayload() && !job->item().hasPayload() ) { + errorCode = FileStore::Job::InvalidJobContext; + errorText = i18nc( "@info:status", "Cannot modify email in folder %1 because there is no email content", job->item().parentCollection().name() ); + } +} + +void MixedMaildirStore::checkItemFetch( FileStore::ItemFetchJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); + const bool fetchSingleItem = job->collection().remoteId().isEmpty(); + if ( fetchSingleItem ) { + Collection coll = job->item().parentCollection(); + Q_ASSERT( !coll.remoteId().isEmpty() ); + } +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.h b/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.h new file mode 100644 index 00000000..d5549f03 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 MIXEDMAILDIRSTORE_H +#define MIXEDMAILDIRSTORE_H + +#include "abstractlocalstore.h" + +class MixedMaildirStore : public Akonadi::FileStore::AbstractLocalStore +{ + Q_OBJECT + + public: + MixedMaildirStore(); + + ~MixedMaildirStore(); + + protected: + void setTopLevelCollection( const Akonadi::Collection &collection ); + void processJob( Akonadi::FileStore::Job *job ); + + void checkCollectionMove( Akonadi::FileStore::CollectionMoveJob *job, int &errorCode, QString &errorText ) const; + + void checkItemCreate( Akonadi::FileStore::ItemCreateJob *job, int &errorCode, QString &errorText ) const; + + void checkItemModify( Akonadi::FileStore::ItemModifyJob *job, int &errorCode, QString &errorText ) const; + + void checkItemFetch( Akonadi::FileStore::ItemFetchJob *job, int &errorCode, QString &errorText ) const; + + private: + class Private; + Private *const d; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.cpp b/kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.cpp new file mode 100644 index 00000000..126219ea --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.cpp @@ -0,0 +1,359 @@ +/* This file is part of the KDE project + Copyright (c) 2011 Kevin Krammer + + 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 "retrieveitemsjob.h" + +#include "mixedmaildirstore.h" + +#include "filestore/itemfetchjob.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Akonadi; + +enum { + MaxItemCreateJobs = 100, + MaxItemModifyJobs = 100 +}; + +class RetrieveItemsJob::Private +{ + RetrieveItemsJob *const q; + + public: + Private( RetrieveItemsJob *parent, const Collection &collection, MixedMaildirStore *store ) + : q( parent ), mCollection( collection ), mStore( store ), + mTransaction( 0 ), mHighestModTime( -1 ), mNumItemCreateJobs( 0 ), mNumItemModifyJobs( 0 ) + { + } + + TransactionSequence *transaction() + { + if ( !mTransaction ) { + mTransaction = new TransactionSequence( q ); + mTransaction->setAutomaticCommittingEnabled( false ); + connect( mTransaction, SIGNAL(result(KJob*)), + q, SLOT(transactionResult(KJob*)) ); + } + return mTransaction; + } + + public: + const Collection mCollection; + MixedMaildirStore *const mStore; + TransactionSequence *mTransaction; + + QHash mServerItemsByRemoteId; + + QQueue mNewItems; + QQueue mChangedItems; + Item::List mAvailableItems; + Item::List mItemsMarkedAsDeleted; + qint64 mHighestModTime; + int mNumItemCreateJobs; + int mNumItemModifyJobs; + + public: // slots + void akonadiFetchResult( KJob *job ); + void transactionResult( KJob *job ); + void storeListResult( KJob* ); + void processNewItem(); + void fetchNewResult( KJob* ); + void processChangedItem(); + void fetchChangedResult( KJob* ); + void itemCreateJobResult( KJob* ); + void itemModifyJobResult( KJob* ); +}; + +void RetrieveItemsJob::Private::itemCreateJobResult( KJob *job ) +{ + if ( job->error() ) { + kError() << "Error running ItemCreateJob: " << job->errorText(); + } + + mNumItemCreateJobs--; + QMetaObject::invokeMethod( q, "processNewItem", Qt::QueuedConnection ); +} + +void RetrieveItemsJob::Private::itemModifyJobResult( KJob *job ) +{ + if ( job->error() ) { + kError() << "Error running ItemModifyJob: " << job->errorText(); + } + + mNumItemModifyJobs--; + QMetaObject::invokeMethod( q, "processChangedItem", Qt::QueuedConnection ); +} + +void RetrieveItemsJob::Private::akonadiFetchResult( KJob *job ) +{ + if ( job->error() != 0 ) return; // handled by base class + + ItemFetchJob *itemFetch = qobject_cast( job ); + Q_ASSERT( itemFetch != 0 ); + + Item::List items = itemFetch->items(); + itemFetch->clearItems(); // save memory + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Akonadi fetch got" << items.count() << "items"; + + mServerItemsByRemoteId.reserve( items.size() ); + for ( int i = 0 ; i < items.count() ; ++i ) { + Item &item = items[i]; + // items without remoteId have not been written to the resource yet + if ( !item.remoteId().isEmpty() ) { + // set the parent collection (with all ancestors) in every item + item.setParentCollection( mCollection ); + mServerItemsByRemoteId.insert( item.remoteId(), item ); + } + } + + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "of which" << mServerItemsByRemoteId.count() << "have remoteId"; + + FileStore::ItemFetchJob *storeFetch = mStore->fetchItems( mCollection ); + // just basic items, no data + + connect( storeFetch, SIGNAL(result(KJob*)), q, SLOT(storeListResult(KJob*)) ); +} + +void RetrieveItemsJob::Private::storeListResult( KJob *job ) +{ + kDebug() << "storeList->error=" << job->error(); + FileStore::ItemFetchJob *storeList = qobject_cast( job ); + Q_ASSERT( storeList != 0 ); + + if ( storeList->error() != 0 ) { + q->setError( storeList->error() ); + q->setErrorText( storeList->errorText() ); + q->emitResult(); + return; + } + + // if some items have tags, we need to complete the retrieval and schedule tagging + // to a later time so we can then fetch the items to get their Akonadi URLs + // forward the property to this instance so the resource can take care of that + const QVariant var = storeList->property( "remoteIdToTagList" ); + if ( var.isValid() ) { + q->setProperty( "remoteIdToTagList", var ); + } + + const qint64 collectionTimestamp = mCollection.remoteRevision().toLongLong(); + + const Item::List storedItems = storeList->items(); + Q_FOREACH( const Item &item, storedItems ) { + // messages marked as deleted have been deleted from mbox files but never got purged + Akonadi::MessageStatus status; + status.setStatusFromFlags( item.flags() ); + if ( status.isDeleted() ) { + mItemsMarkedAsDeleted << item; + continue; + } + + mAvailableItems << item; + + const QHash::iterator it = mServerItemsByRemoteId.find( item.remoteId() ); + if ( it == mServerItemsByRemoteId.end() ) { + // item not in server items -> new + mNewItems << item; + } else { + // item both on server and in store, check modification time + const QDateTime modTime = item.modificationTime(); + if ( !modTime.isValid() || modTime.toMSecsSinceEpoch() > collectionTimestamp ) { + mChangedItems << it.value(); + } + + // remove from hash so only no longer existing items remain + mServerItemsByRemoteId.erase( it ); + } + } + + kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Store fetch got" << storedItems.count() << "items" + << "of which" << mNewItems.count() << "are new and" << mChangedItems.count() + << "are changed and" << mServerItemsByRemoteId.count() + << "need to be removed"; + + // all items remaining in mServerItemsByRemoteId are no longer in the store + + if ( !mServerItemsByRemoteId.isEmpty() ) { + ItemDeleteJob *deleteJob = new ItemDeleteJob( mServerItemsByRemoteId.values(), transaction() ); + transaction()->setIgnoreJobFailure( deleteJob ); + } + + processNewItem(); +} + +void RetrieveItemsJob::Private::processNewItem() +{ + if ( mNewItems.isEmpty() ) { + processChangedItem(); + return; + } + + const Item item = mNewItems.dequeue(); + FileStore::ItemFetchJob *storeFetch = mStore->fetchItem( item ); + storeFetch->fetchScope().fetchPayloadPart( MessagePart::Envelope ); + + connect( storeFetch, SIGNAL(result(KJob*)), q, SLOT(fetchNewResult(KJob*)) ); +} + +void RetrieveItemsJob::Private::fetchNewResult( KJob *job ) +{ + FileStore::ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob != 0 ); + + if ( fetchJob->items().count() != 1 ) { + const Item item = fetchJob->item(); + kWarning() << "Store fetch for new item" << item.remoteId() + << "in collection" << item.parentCollection().id() + << "," << item.parentCollection().remoteId() + << "did not return the expected item. error=" + << fetchJob->error() << "," << fetchJob->errorText(); + processNewItem(); + return; + } + + const Item item = fetchJob->items().first(); + const QDateTime modTime = item.modificationTime(); + if ( modTime.isValid() ) { + mHighestModTime = qMax( modTime.toMSecsSinceEpoch(), mHighestModTime ); + } + + ItemCreateJob *itemCreate = new ItemCreateJob( item, mCollection, transaction() ); + mNumItemCreateJobs++; + connect( itemCreate, SIGNAL(result(KJob*)), q, SLOT(itemCreateJobResult(KJob*)) ); + + if (mNumItemCreateJobs < MaxItemCreateJobs ) { + QMetaObject::invokeMethod( q, "processNewItem", Qt::QueuedConnection ); + } +} + +void RetrieveItemsJob::Private::processChangedItem() +{ + if ( mChangedItems.isEmpty() ) { + if ( !mTransaction ) { + // no jobs created here -> done + q->emitResult(); + return; + } + + if ( mHighestModTime > -1 ) { + Collection collection( mCollection ); + collection.setRemoteRevision( QString::number( mHighestModTime ) ); + CollectionModifyJob *job = new CollectionModifyJob( collection, transaction() ); + transaction()->setIgnoreJobFailure( job ); + } + transaction()->commit(); + return; + } + + const Item item = mChangedItems.dequeue(); + FileStore::ItemFetchJob *storeFetch = mStore->fetchItem( item ); + storeFetch->fetchScope().fetchPayloadPart( MessagePart::Envelope ); + + connect( storeFetch, SIGNAL(result(KJob*)), q, SLOT(fetchChangedResult(KJob*)) ); +} + +void RetrieveItemsJob::Private::fetchChangedResult( KJob *job ) +{ + FileStore::ItemFetchJob *fetchJob = qobject_cast( job ); + Q_ASSERT( fetchJob != 0 ); + + if ( fetchJob->items().count() != 1 ) { + const Item item = fetchJob->item(); + kWarning() << "Store fetch for changed item" << item.remoteId() + << "in collection" << item.parentCollection().id() + << "," << item.parentCollection().remoteId() + << "did not return the expected item. error=" + << fetchJob->error() << "," << fetchJob->errorText(); + processChangedItem(); + return; + } + + const Item item = fetchJob->items().first(); + const QDateTime modTime = item.modificationTime(); + if ( modTime.isValid() ) { + mHighestModTime = qMax( modTime.toMSecsSinceEpoch(), mHighestModTime ); + } + + ItemModifyJob *itemModify = new ItemModifyJob( item, transaction() ); + connect( itemModify, SIGNAL(result(KJob*)), q, SLOT(itemModifyJobResult(KJob*)) ); + mNumItemModifyJobs++; + if ( mNumItemModifyJobs < MaxItemModifyJobs ) { + QMetaObject::invokeMethod( q, "processChangedItem", Qt::QueuedConnection ); + } +} + +void RetrieveItemsJob::Private::transactionResult( KJob *job ) +{ + if ( job->error() != 0 ) return; // handled by base class + + q->emitResult(); +} + +RetrieveItemsJob::RetrieveItemsJob( const Akonadi::Collection &collection, MixedMaildirStore *store, QObject* parent ) + : Job( parent ), d( new Private( this, collection, store ) ) +{ + Q_ASSERT( d->mCollection.isValid() ); + Q_ASSERT( !d->mCollection.remoteId().isEmpty() ); + Q_ASSERT( d->mStore != 0 ); +} + +RetrieveItemsJob::~RetrieveItemsJob() +{ + delete d; +} + +Collection RetrieveItemsJob::collection() const +{ + return d->mCollection; +} + +Item::List RetrieveItemsJob::availableItems() const +{ + return d->mAvailableItems; +} + +Item::List RetrieveItemsJob::itemsMarkedAsDeleted() const +{ + return d->mItemsMarkedAsDeleted; +} + +void RetrieveItemsJob::doStart() +{ + ItemFetchJob *job = new Akonadi::ItemFetchJob( d->mCollection, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(akonadiFetchResult(KJob*)) ); +} + +#include "moc_retrieveitemsjob.cpp" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.h b/kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.h new file mode 100644 index 00000000..8bad5a45 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/retrieveitemsjob.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (c) 2011 Kevin Krammer + + 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 MIXEDMAILDIR_RETRIEVEITEMSJOB_H +#define MIXEDMAILDIR_RETRIEVEITEMSJOB_H + +#include +#include + +namespace Akonadi +{ + class Collection; +} + +class MixedMaildirStore; + +/** + * Used to implement ResourceBase::retrieveItems() for MixedMail Resource. + * This completely bypasses ItemSync in order to achieve maximum performance. + */ +class RetrieveItemsJob : public Akonadi::Job +{ + Q_OBJECT + public: + RetrieveItemsJob( const Akonadi::Collection &collection, MixedMaildirStore *store, QObject* parent = 0 ); + + ~RetrieveItemsJob(); + + Akonadi::Collection collection() const; + + Akonadi::Item::List availableItems() const; + + Akonadi::Item::List itemsMarkedAsDeleted() const; + + protected: + void doStart(); + + private: + class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void itemModifyJobResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void itemCreateJobResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void akonadiFetchResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void transactionResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void storeListResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void processNewItem() ) + Q_PRIVATE_SLOT( d, void fetchNewResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void processChangedItem() ) + Q_PRIVATE_SLOT( d, void fetchChangedResult( KJob* ) ) +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/settings.kcfgc b/kdepim-runtime/resources/mixedmaildir/settings.kcfgc new file mode 100644 index 00000000..789365e3 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/settings.kcfgc @@ -0,0 +1,8 @@ +File=mixedmaildirresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/mixedmaildir/settings.ui b/kdepim-runtime/resources/mixedmaildir/settings.ui new file mode 100644 index 00000000..8c60e103 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/settings.ui @@ -0,0 +1,69 @@ + + + Till Adam <adam@kde.org> + ConfigDialog + + + + 0 + 0 + 400 + 290 + + + + Mail Directory Settings + + + + + + Select the folder containing the maildir information: + + + + + + + + + + Open in read-only mode + + + + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/mixedmaildir/tests/CMakeLists.txt b/kdepim-runtime/resources/mixedmaildir/tests/CMakeLists.txt new file mode 100644 index 00000000..c81d79b1 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/CMakeLists.txt @@ -0,0 +1,206 @@ +if(${EXECUTABLE_OUTPUT_PATH}) + set( PREVIOUS_EXEC_OUTPUT_PATH ${EXECUTABLE_OUTPUT_PATH} ) +else() + set( PREVIOUS_EXEC_OUTPUT_PATH . ) +endif() +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +include_directories( + ${AKONADI_INCLUDE_DIR} + ${KDE4_INCLUDES} + ${KDEPIMLIBS_INCLUDE_DIR} + ${QT_INCLUDES} + ${Boost_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR}/../ + ${CMAKE_CURRENT_SOURCE_DIR} +) + +# test data +qt4_add_resources( testdata_generated_SRCS testdata.qrc ) + +set( testdata_SRCS + ${testdata_generated_SRCS} + testdatautil.cpp +) + +kde4_add_unit_test( testdatatest + TESTNAME mixedmaildir-testdatatest + testdatatest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + testdatatest + ${KDE4_KDECORE_LIBS} + ${QT_QTTEST_LIBRARY} + ${QT_QTGUI_LIBRARY} +) + +# put the libraries all tests link against into a variable and use it +# in target_link_libraries instead +set( common_link_libraries + kmindexreader + maildir + akonadi-filestore + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_KMBOX_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_KPIMUTILS_LIBS} + ${QT_QTTEST_LIBRARY} +) + +# test for overwritten methods +kde4_add_unit_test( templatemethodstest + TESTNAME mixedmaildir-templatemethodtest + ../mixedmaildirstore.cpp + templatemethodstest.cpp +) + +target_link_libraries( + templatemethodstest + ${common_link_libraries} +) + +# test for collection creation handling +kde4_add_unit_test( collectioncreatetest + TESTNAME mixedmaildir-collectioncreatetest + ../mixedmaildirstore.cpp + collectioncreatetest.cpp +) + +target_link_libraries( + collectioncreatetest + ${common_link_libraries} +) + +# test for collection deletion handling +kde4_add_unit_test( collectiondeletetest + TESTNAME mixedmaildir-collectiondeletetest + ../mixedmaildirstore.cpp + collectiondeletetest.cpp +) + +target_link_libraries( + collectiondeletetest + ${common_link_libraries} +) + +# test for collection fetching handling +kde4_add_unit_test( collectionfetchtest + TESTNAME mixedmaildir-collectionfetchtest + ../mixedmaildirstore.cpp + collectionfetchtest.cpp +) + +target_link_libraries( + collectionfetchtest + ${common_link_libraries} +) + +# test for collection modification handling +kde4_add_unit_test( collectionmodifytest + TESTNAME mixedmaildir-collectionmodifytest + ../mixedmaildirstore.cpp + collectionmodifytest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + collectionmodifytest + ${common_link_libraries} +) + +# test for collection move handling +kde4_add_unit_test( collectionmovetest + TESTNAME mixedmaildir-collectionmovetest + ../mixedmaildirstore.cpp + collectionmovetest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + collectionmovetest + ${common_link_libraries} +) + +# test for item creation handling +kde4_add_unit_test( itemcreatetest + TESTNAME mixedmaildir-itemcreatetest + ../mixedmaildirstore.cpp + itemcreatetest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + itemcreatetest + ${common_link_libraries} +) + +# test for item creation handling +kde4_add_unit_test( itemdeletetest + TESTNAME mixedmaildir-itemdeletetest + ../mixedmaildirstore.cpp + itemdeletetest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + itemdeletetest + ${common_link_libraries} +) + +# test for item retrieval handling +kde4_add_unit_test( itemfetchtest + TESTNAME mixedmaildir-itemfetchtest + ../mixedmaildirstore.cpp + itemfetchtest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + itemfetchtest + ${common_link_libraries} +) + +# test for item modification handling +kde4_add_unit_test( itemmodifytest + TESTNAME mixedmaildir-itemmodifytest + ../mixedmaildirstore.cpp + itemmodifytest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + itemmodifytest + ${common_link_libraries} +) + +# test for item move handling +kde4_add_unit_test( itemmovetest + TESTNAME mixedmaildir-itemmovetest + ../mixedmaildirstore.cpp + itemmovetest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + itemmovetest + ${common_link_libraries} +) + +# test for store compact handling +kde4_add_unit_test( storecompacttest + TESTNAME mixedmaildir-storecompacttest + ../mixedmaildirstore.cpp + storecompacttest.cpp + ${testdata_SRCS} +) + +target_link_libraries( + storecompacttest + ${common_link_libraries} +) diff --git a/kdepim-runtime/resources/mixedmaildir/tests/collectioncreatetest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/collectioncreatetest.cpp new file mode 100644 index 00000000..0ca656e2 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/collectioncreatetest.cpp @@ -0,0 +1,326 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "filestore/collectioncreatejob.h" + +#include "libmaildir/maildir.h" + +#include + +#include + +#include + +using namespace Akonadi; + +class CollectionCreateTest : public QObject +{ + Q_OBJECT + + public: + CollectionCreateTest() : QObject(), mStore( 0 ), mDir( 0 ) {} + ~CollectionCreateTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testCollectionProperties(); + void testEmptyDir(); + void testMaildirTree(); + void testMixedTree(); +}; + +void CollectionCreateTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void CollectionCreateTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void CollectionCreateTest::testCollectionProperties() +{ + mStore->setPath( mDir->name() ); + + FileStore::CollectionCreateJob *job = 0; + + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + job = mStore->createCollection( collection1, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection1 = job->collection(); + QCOMPARE( collection1.remoteId(), collection1.name() ); + + QCOMPARE( collection1.contentMimeTypes(), QStringList() << Collection::mimeType() << KMime::Message::mimeType() ); + + QCOMPARE( collection1.rights(), Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); +} + +void CollectionCreateTest::testEmptyDir() +{ + mStore->setPath( mDir->name() ); + + KPIM::Maildir topLevelMd( mStore->path(), true ); + + FileStore::CollectionCreateJob *job = 0; + + // test creating first level collections + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + job = mStore->createCollection( collection1, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection1 = job->collection(); + QVERIFY( !collection1.remoteId().isEmpty() ); + QVERIFY( collection1.parentCollection() == mStore->topLevelCollection() ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) ); + KPIM::Maildir md1 = topLevelMd.subFolder( collection1.remoteId() ); + QVERIFY( md1.isValid() ); + + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + job = mStore->createCollection( collection2, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection2 = job->collection(); + QVERIFY( !collection2.remoteId().isEmpty() ); + QVERIFY( collection2.parentCollection() == mStore->topLevelCollection() ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + KPIM::Maildir md2 = topLevelMd.subFolder( collection2.remoteId() ); + QVERIFY( md2.isValid() ); + + // test creating second level collections + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + job = mStore->createCollection( collection1_1, collection1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection1_1 = job->collection(); + QVERIFY( !collection1_1.remoteId().isEmpty() ); + QVERIFY( collection1_1.parentCollection() == collection1 ); + + QCOMPARE( md1.subFolderList(), QStringList() << QLatin1String( "collection1_1" ) ); + KPIM::Maildir md1_1 = md1.subFolder( collection1_1.remoteId() ); + QVERIFY( md1_1.isValid() ); + + Collection collection1_2; + collection1_2.setName( QLatin1String( "collection1_2" ) ); + job = mStore->createCollection( collection1_2, collection1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection1_2 = job->collection(); + QVERIFY( !collection1_2.remoteId().isEmpty() ); + QVERIFY( collection1_2.parentCollection() == collection1 ); + + QCOMPARE( md1.subFolderList(), QStringList() << QLatin1String( "collection1_1" ) << QLatin1String( "collection1_2" ) ); + KPIM::Maildir md1_2 = md1.subFolder( collection1_2.remoteId() ); + QVERIFY( md1_2.isValid() ); + + QCOMPARE( md2.subFolderList(), QStringList() ); +} + +void CollectionCreateTest::testMaildirTree() +{ + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid() ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + + KPIM::Maildir md1_2( md1.addSubFolder( "collection1_2" ), false ); + + mStore->setPath( mDir->name() ); + + FileStore::CollectionCreateJob *job = 0; + + // test creating first level collections + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + job = mStore->createCollection( collection1, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); // works because it already exists + QCOMPARE( job->error(), 0 ); + + collection1 = job->collection(); + QVERIFY( !collection1.remoteId().isEmpty() ); + QVERIFY( collection1.parentCollection() == mStore->topLevelCollection() ); + + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + job = mStore->createCollection( collection2, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection2 = job->collection(); + QVERIFY( !collection2.remoteId().isEmpty() ); + QVERIFY( collection2.parentCollection() == mStore->topLevelCollection() ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + KPIM::Maildir md2 = topLevelMd.subFolder( collection2.remoteId() ); + QVERIFY( md2.isValid() ); + + // test creating second level collections + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + job = mStore->createCollection( collection1_1, collection1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection1_1 = job->collection(); + QVERIFY( !collection1_1.remoteId().isEmpty() ); + QCOMPARE( collection1_1.parentCollection().remoteId(), QLatin1String( "collection1" ) ); + + QCOMPARE( md1.subFolderList(), QStringList() << QLatin1String( "collection1_1" ) << QLatin1String( "collection1_2" ) ); + KPIM::Maildir md1_1 = md1.subFolder( collection1_1.remoteId() ); + QVERIFY( md1_1.isValid() ); + + Collection collection1_2; + collection1_2.setName( QLatin1String( "collection1_2" ) ); + job = mStore->createCollection( collection1_2, collection1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); // works because it already exists + QCOMPARE( job->error(), 0 ); + + collection1_2 = job->collection(); + QVERIFY( !collection1_2.remoteId().isEmpty() ); + QCOMPARE( collection1_2.parentCollection().remoteId(), QLatin1String( "collection1" ) ); + + QCOMPARE( md2.subFolderList(), QStringList() ); +} + +void CollectionCreateTest::testMixedTree() +{ + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid() ); + + // simulate a first level MBox + QFileInfo fileInfo1( mDir->name(), QLatin1String( "collection1" ) ); + QFile file1( fileInfo1.absoluteFilePath() ); + file1.open( QIODevice::WriteOnly ); + file1.close(); + QVERIFY( fileInfo1.exists() ); + + mStore->setPath( mDir->name() ); + + FileStore::CollectionCreateJob *job = 0; + + // test creating first level collections + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + job = mStore->createCollection( collection1, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); // fails, there is an MBox with that name + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + collection1 = job->collection(); + QVERIFY( collection1.remoteId().isEmpty() ); + + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + job = mStore->createCollection( collection2, mStore->topLevelCollection() ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection2 = job->collection(); + QVERIFY( !collection2.remoteId().isEmpty() ); + QVERIFY( collection2.parentCollection() == mStore->topLevelCollection() ); + + // mbox does not show up as a maildir subfolder + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection2" ) ); + KPIM::Maildir md2 = topLevelMd.subFolder( collection2.remoteId() ); + QVERIFY( md2.isValid() ); + + // test creating second level collections inside mbox + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + job = mStore->createCollection( collection1_1, collection1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection1_1 = job->collection(); + QVERIFY( !collection1_1.remoteId().isEmpty() ); + QCOMPARE( collection1_1.parentCollection().remoteId(), QLatin1String( "collection1" ) ); + + // treat the MBox subdir path like a top level maildir + KPIM::Maildir md1( KPIM::Maildir::subDirPathForFolderPath( fileInfo1.absoluteFilePath() ), true ); + KPIM::Maildir md1_1 = md1.subFolder( collection1_1.remoteId() ); + QVERIFY( md1_1.isValid() ); + + QCOMPARE( md1.subFolderList(), QStringList() << QLatin1String( "collection1_1" ) ); +} + +QTEST_KDEMAIN( CollectionCreateTest, NoGUI ) + +#include "collectioncreatetest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/collectiondeletetest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/collectiondeletetest.cpp new file mode 100644 index 00000000..0de24289 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/collectiondeletetest.cpp @@ -0,0 +1,465 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "filestore/collectiondeletejob.h" + +#include "libmaildir/maildir.h" + +#include + +#include + +using namespace Akonadi; + +class CollectionDeleteTest : public QObject +{ + Q_OBJECT + + public: + CollectionDeleteTest() : QObject(), mStore( 0 ), mDir( 0 ) {} + ~CollectionDeleteTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testNonExisting(); + void testLeaves(); + void testSubTrees(); +}; + +void CollectionDeleteTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void CollectionDeleteTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void CollectionDeleteTest::testNonExisting() +{ + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid( false ) ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + KPIM::Maildir md1_2( md1.addSubFolder( "collection1_2" ), false ); + + KPIM::Maildir md2( topLevelMd.addSubFolder( "collection2" ), false ); + + // simulate mbox + QFileInfo fileInfo1( mDir->name(), QLatin1String( "collection3" ) ); + QFile file1( fileInfo1.absoluteFilePath() ); + file1.open( QIODevice::WriteOnly ); + file1.close(); + QVERIFY( fileInfo1.exists() ); + + // simulate mbox with empty subtree + QFileInfo fileInfo2( mDir->name(), QLatin1String( "collection4" ) ); + QFile file2( fileInfo2.absoluteFilePath() ); + file2.open( QIODevice::WriteOnly ); + file2.close(); + QVERIFY( fileInfo2.exists() ); + + QFileInfo subDirInfo2( KPIM::Maildir::subDirPathForFolderPath( fileInfo2.absoluteFilePath() ) ); + QDir topDir( mDir->name() ); + QVERIFY( topDir.mkpath( subDirInfo2.absoluteFilePath() ) ); + + mStore->setPath( mDir->name() ); + + FileStore::CollectionDeleteJob *job = 0; + + // test fail of deleting first level collection + Collection collection5; + collection5.setName( QLatin1String( "collection5" ) ); + collection5.setRemoteId( QLatin1String( "collection5" ) ); + collection5.setParentCollection( mStore->topLevelCollection() ); + job = mStore->deleteCollection( collection5 ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + QVERIFY( fileInfo1.exists() ); + + // test fail of deleting second level collection in maildir leaf parent + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Collection collection2_1; + collection2_1.setName( QLatin1String( "collection2_1" ) ); + collection2_1.setRemoteId( QLatin1String( "collection2_1" ) ); + collection2_1.setParentCollection( collection2 ); + job = mStore->deleteCollection( collection2_1 ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + + // test fail of deleting second level collection in maildir parent with subtree + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + collection1_1.setRemoteId( QLatin1String( "collection1_1" ) ); + collection1_1.setParentCollection( collection1 ); + job = mStore->deleteCollection( collection1_1 ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + QCOMPARE( md1.subFolderList(), QStringList() << QLatin1String( "collection1_2" ) ); + + // test fail of deleting second level collection in mbox leaf parent + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + Collection collection3_1; + collection3_1.setName( QLatin1String( "collection3_1" ) ); + collection3_1.setRemoteId( QLatin1String( "collection3_1" ) ); + collection3_1.setParentCollection( collection3 ); + job = mStore->deleteCollection( collection3_1 ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QVERIFY( fileInfo1.exists() ); + + // test fail of deleting second level collection in mbox parent with subtree + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + Collection collection4_1; + collection4_1.setName( QLatin1String( "collection4_1" ) ); + collection4_1.setRemoteId( QLatin1String( "collection4_1" ) ); + collection4_1.setParentCollection( collection4 ); + job = mStore->deleteCollection( collection4_1 ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QVERIFY( fileInfo2.exists() ); + QVERIFY( subDirInfo2.exists() ); + + // test fail of deleting second level collection with non existant parent + Collection collection5_1; + collection5_1.setName( QLatin1String( "collection5_1" ) ); + collection5_1.setRemoteId( QLatin1String( "collection5_1" ) ); + collection5_1.setParentCollection( collection5 ); + job = mStore->deleteCollection( collection5_1 ); + QVERIFY( job != 0 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + QVERIFY( fileInfo1.exists() ); + QCOMPARE( md1.subFolderList(), QStringList() << QLatin1String( "collection1_2" ) ); +} + +void CollectionDeleteTest::testLeaves() +{ + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid( false ) ); + + QDir topDir( mDir->name() ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + KPIM::Maildir md1_2( md1.addSubFolder( "collection1_2" ), false ); + + // simulate second level mbox in maildir parent + QFileInfo fileInfo1_1( KPIM::Maildir::subDirPathForFolderPath( md1.path() ), + QLatin1String( "collection1_1" ) ); + QFile file1_1( fileInfo1_1.absoluteFilePath() ); + file1_1.open( QIODevice::WriteOnly ); + file1_1.close(); + QVERIFY( fileInfo1_1.exists() ); + + KPIM::Maildir md2( topLevelMd.addSubFolder( "collection2" ), false ); + + // simulate first level mbox + QFileInfo fileInfo3( mDir->name(), QLatin1String( "collection3" ) ); + QFile file3( fileInfo3.absoluteFilePath() ); + file3.open( QIODevice::WriteOnly ); + file3.close(); + QVERIFY( fileInfo3.exists() ); + + // simulate first level mbox with subtree + QFileInfo fileInfo4( mDir->name(), QLatin1String( "collection4" ) ); + QFile file4( fileInfo4.absoluteFilePath() ); + file4.open( QIODevice::WriteOnly ); + file4.close(); + QVERIFY( fileInfo4.exists() ); + + QFileInfo subDirInfo4( KPIM::Maildir::subDirPathForFolderPath( fileInfo4.absoluteFilePath() ) ); + QVERIFY( topDir.mkpath( subDirInfo4.absoluteFilePath() ) ); + + KPIM::Maildir md4( subDirInfo4.absoluteFilePath(), true ); + KPIM::Maildir md4_1( md4.addSubFolder( "collection4_1" ), false ); + + // simulate second level mbox in mbox parent + QFileInfo fileInfo4_2( subDirInfo4.absoluteFilePath(), + QLatin1String( "collection4_2" ) ); + QFile file4_2( fileInfo4_2.absoluteFilePath() ); + file4_2.open( QIODevice::WriteOnly ); + file4_2.close(); + QVERIFY( fileInfo4_2.exists() ); + + mStore->setPath( mDir->name() ); + + FileStore::CollectionDeleteJob *job = 0; + + // test second level leaves in maildir parent + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + collection1_1.setRemoteId( QLatin1String( "collection1_1" ) ); + collection1_1.setParentCollection( collection1 ); + job = mStore->deleteCollection( collection1_1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + + Collection collection1_2; + collection1_2.setName( QLatin1String( "collection1_2" ) ); + collection1_2.setRemoteId( QLatin1String( "collection1_2" ) ); + collection1_2.setParentCollection( collection1 ); + job = mStore->deleteCollection( collection1_2 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( !md1_2.isValid( false ) ); + QCOMPARE( md1.subFolderList(), QStringList() ); + + // test second level leaves in mbox parent + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + Collection collection4_1; + collection4_1.setName( QLatin1String( "collection4_1" ) ); + collection4_1.setRemoteId( QLatin1String( "collection4_1" ) ); + collection4_1.setParentCollection( collection4 ); + job = mStore->deleteCollection( collection4_1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( !md4_1.isValid( false ) ); + QCOMPARE( md4.subFolderList(), QStringList() ); + + Collection collection4_2; + collection4_2.setName( QLatin1String( "collection4_2" ) ); + collection4_2.setRemoteId( QLatin1String( "collection4_2" ) ); + collection4_2.setParentCollection( collection4 ); + job = mStore->deleteCollection( collection4_2 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + fileInfo4_2.refresh(); + QVERIFY( !fileInfo4_2.exists() ); + + // test deleting of first level leaves + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->deleteCollection( collection2 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( !md2.isValid( false ) ); + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) ); + + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->deleteCollection( collection3 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + fileInfo3.refresh(); + QVERIFY( !fileInfo3.exists() ); + + // test deleting of first level leaves with empty subtrees + QFileInfo subDirInfo1( KPIM::Maildir::subDirPathForFolderPath( md1.path() ) ); + QVERIFY( subDirInfo1.exists() ); + + job = mStore->deleteCollection( collection1 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( !md1.isValid( false ) ); + subDirInfo1.refresh(); + QVERIFY( !subDirInfo1.exists() ); + QCOMPARE( topLevelMd.subFolderList(), QStringList() ); + + job = mStore->deleteCollection( collection4 ); + QVERIFY( job != 0 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + fileInfo4.refresh(); + QVERIFY( !fileInfo4.exists() ); + subDirInfo4.refresh(); + QVERIFY( !subDirInfo4.exists() ); +} + +void CollectionDeleteTest::testSubTrees() +{ + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid( false ) ); + + QDir topDir( mDir->name() ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + KPIM::Maildir md1_2( md1.addSubFolder( "collection1_2" ), false ); + + // simulate second level mbox in maildir parent + QFileInfo fileInfo1_1( KPIM::Maildir::subDirPathForFolderPath( md1.path() ), + QLatin1String( "collection1_1" ) ); + QFile file1_1( fileInfo1_1.absoluteFilePath() ); + file1_1.open( QIODevice::WriteOnly ); + file1_1.close(); + QVERIFY( fileInfo1_1.exists() ); + + // simulate first level mbox with subtree + QFileInfo fileInfo2( mDir->name(), QLatin1String( "collection2" ) ); + QFile file2( fileInfo2.absoluteFilePath() ); + file2.open( QIODevice::WriteOnly ); + file2.close(); + QVERIFY( fileInfo2.exists() ); + + QFileInfo subDirInfo2( KPIM::Maildir::subDirPathForFolderPath( fileInfo2.absoluteFilePath() ) ); + QVERIFY( topDir.mkpath( subDirInfo2.absoluteFilePath() ) ); + + KPIM::Maildir md2( subDirInfo2.absoluteFilePath(), true ); + KPIM::Maildir md2_1( md2.addSubFolder( "collection2_1" ), false ); + + // simulate second level mbox in mbox parent + QFileInfo fileInfo2_2( subDirInfo2.absoluteFilePath(), + QLatin1String( "collection2_2" ) ); + QFile file2_2( fileInfo2_2.absoluteFilePath() ); + file2_2.open( QIODevice::WriteOnly ); + file2_2.close(); + QVERIFY( fileInfo2_2.exists() ); + + mStore->setPath( mDir->name() ); + + FileStore::CollectionDeleteJob *job = 0; + + // test deleting maildir subtree + QFileInfo subDirInfo1( KPIM::Maildir::subDirPathForFolderPath( md1.path() ) ); + QVERIFY( subDirInfo1.exists() ); + + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->deleteCollection( collection1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( !md1.isValid( false ) ); + QVERIFY( !md1_2.isValid( false ) ); + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + + // test deleting mbox subtree + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->deleteCollection( collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + fileInfo2.refresh(); + QVERIFY( !fileInfo2.exists() ); + QVERIFY( !md2_1.isValid( false ) ); + fileInfo2_2.refresh(); + QVERIFY( !fileInfo2_2.exists() ); + QVERIFY( !subDirInfo2.exists() ); +} + +QTEST_KDEMAIN( CollectionDeleteTest, NoGUI ) + +#include "collectiondeletetest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/collectionfetchtest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/collectionfetchtest.cpp new file mode 100644 index 00000000..ce05de83 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/collectionfetchtest.cpp @@ -0,0 +1,471 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "filestore/collectionfetchjob.h" + +#include "libmaildir/maildir.h" + +#include + +#include + +#include + +#include + +using namespace Akonadi; + +static Collection::List collectionsFromSpy( QSignalSpy *spy ) { + Collection::List collections; + + QListIterator > it( *spy ); + while( it.hasNext() ) { + const QList invocation = it.next(); + Q_ASSERT( invocation.count() == 1 ); + + collections << invocation.first().value(); + } + + return collections; +} + +class CollectionFetchTest : public QObject +{ + Q_OBJECT + + public: + CollectionFetchTest() : QObject(), mStore( 0 ), mDir( 0 ) { + // for monitoring signals + qRegisterMetaType(); + } + + ~CollectionFetchTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testEmptyDir(); + void testMixedTree(); +}; + +void CollectionFetchTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void CollectionFetchTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void CollectionFetchTest::testEmptyDir() +{ + mStore->setPath( mDir->name() ); + + FileStore::CollectionFetchJob *job = 0; + QSignalSpy *spy = 0; + Collection::List collections; + + // test base fetch of top level collection + job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::Base ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QCOMPARE( spy->count(), 1 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 1 ); + QCOMPARE( collections.first(), mStore->topLevelCollection() ); + QCOMPARE( job->collections(), collections ); + + // test first level fetch of top level collection + job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::FirstLevel ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QCOMPARE( spy->count(), 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 0 ); + QCOMPARE( job->collections(), collections ); + + // test recursive fetch of top level collection + job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::Recursive ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QCOMPARE( spy->count(), 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 0 ); + QCOMPARE( job->collections(), collections ); + + // test fail of base fetching non existant collection + Collection collection; + collection.setName( QLatin1String( "collection" ) ); + collection.setRemoteId( QLatin1String( "collection" ) ); + collection.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchCollections( collection, FileStore::CollectionFetchJob::Base ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QCOMPARE( spy->count(), 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 0 ); + QCOMPARE( job->collections(), collections ); + + // test fail of first level fetching non existant collection + job = mStore->fetchCollections( collection, FileStore::CollectionFetchJob::FirstLevel ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QCOMPARE( spy->count(), 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 0 ); + QCOMPARE( job->collections(), collections ); + + // test fail of recursive fetching non existant collection + job = mStore->fetchCollections( collection, FileStore::CollectionFetchJob::FirstLevel ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QCOMPARE( spy->count(), 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 0 ); + QCOMPARE( job->collections(), collections ); +} + +void CollectionFetchTest::testMixedTree() +{ + QDir topDir( mDir->name() ); + + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid() ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + KPIM::Maildir md1_2( md1.addSubFolder( "collection1_2" ), false ); + KPIM::Maildir md1_2_1( md1_2.addSubFolder( "collection1_2_1" ), false ); + + // simulate second level mbox in maildir parent + QFileInfo fileInfo1_1( KPIM::Maildir::subDirPathForFolderPath( md1.path() ), + QLatin1String( "collection1_1" ) ); + QFile file1_1( fileInfo1_1.absoluteFilePath() ); + file1_1.open( QIODevice::WriteOnly ); + file1_1.close(); + QVERIFY( fileInfo1_1.exists() ); + + QFileInfo subDirInfo1_1( KPIM::Maildir::subDirPathForFolderPath( fileInfo1_1.absoluteFilePath() ) ); + QVERIFY( topDir.mkpath( subDirInfo1_1.absoluteFilePath() ) ); + KPIM::Maildir md1_1( subDirInfo1_1.absoluteFilePath(), true ); + KPIM::Maildir md1_1_1( md1_1.addSubFolder( "collection1_1_1" ), false ); + + // simulate third level mbox in mbox parent + QFileInfo fileInfo1_1_2( md1_1.path(), QLatin1String( "collection1_1_2" ) ); + QFile file1_1_2( fileInfo1_1_2.absoluteFilePath() ); + file1_1_2.open( QIODevice::WriteOnly ); + file1_1_2.close(); + QVERIFY( fileInfo1_1_2.exists() ); + + KPIM::Maildir md2( topLevelMd.addSubFolder( "collection2" ), false ); + + // simulate first level mbox + QFileInfo fileInfo3( mDir->name(), QLatin1String( "collection3" ) ); + QFile file3( fileInfo3.absoluteFilePath() ); + file3.open( QIODevice::WriteOnly ); + file3.close(); + QVERIFY( fileInfo3.exists() ); + + // simulate first level mbox with subtree + QFileInfo fileInfo4( mDir->name(), QLatin1String( "collection4" ) ); + QFile file4( fileInfo4.absoluteFilePath() ); + file4.open( QIODevice::WriteOnly ); + file4.close(); + QVERIFY( fileInfo4.exists() ); + + QFileInfo subDirInfo4( KPIM::Maildir::subDirPathForFolderPath( fileInfo4.absoluteFilePath() ) ); + QVERIFY( topDir.mkpath( subDirInfo4.absoluteFilePath() ) ); + + KPIM::Maildir md4( subDirInfo4.absoluteFilePath(), true ); + KPIM::Maildir md4_1( md4.addSubFolder( "collection4_1" ), false ); + + // simulate second level mbox in mbox parent + QFileInfo fileInfo4_2( subDirInfo4.absoluteFilePath(), + QLatin1String( "collection4_2" ) ); + QFile file4_2( fileInfo4_2.absoluteFilePath() ); + file4_2.open( QIODevice::WriteOnly ); + file4_2.close(); + QVERIFY( fileInfo4_2.exists() ); + + QSet firstLevelNames; + firstLevelNames << md1.name() << md2.name() << fileInfo3.fileName() << fileInfo4.fileName(); + + QSet secondLevelNames; + secondLevelNames << md1_2.name() << md4_1.name() + << fileInfo1_1.fileName() << fileInfo4_2.fileName(); + + QSet thirdLevelNames; + thirdLevelNames << md1_1_1.name() << fileInfo1_1_2.fileName() << md1_2_1.name(); + + mStore->setPath( mDir->name() ); + //mDir = 0; + + FileStore::CollectionFetchJob *job = 0; + QSignalSpy *spy = 0; + Collection::List collections; + + // test base fetch of top level collection + job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::Base ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QCOMPARE( spy->count(), 1 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), 1 ); + QCOMPARE( collections.first(), mStore->topLevelCollection() ); + QCOMPARE( job->collections(), collections ); + + // test first level fetch of top level collection + job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::FirstLevel ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QVERIFY( spy->count() > 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), firstLevelNames.count() ); + QCOMPARE( job->collections(), collections ); + + Q_FOREACH( const Collection &collection, collections ) { + QVERIFY( !collection.remoteId().isEmpty() ); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection.contentMimeTypes(), QStringList() << Collection::mimeType() << KMime::Message::mimeType() ); + + QCOMPARE( collection.rights(), Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + + QCOMPARE( collection.parentCollection(), mStore->topLevelCollection() ); + QVERIFY( firstLevelNames.contains( collection.name() ) ); + } + + // test recursive fetch of top level collection + job = mStore->fetchCollections( mStore->topLevelCollection(), FileStore::CollectionFetchJob::Recursive ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QVERIFY( spy->count() > 0 ); + + collections = collectionsFromSpy( spy ); + QCOMPARE( collections.count(), + firstLevelNames.count() + secondLevelNames.count() + thirdLevelNames.count() ); + QCOMPARE( job->collections(), collections ); + + Q_FOREACH( const Collection &collection, collections ) { + QVERIFY( !collection.remoteId().isEmpty() ); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection.contentMimeTypes(), QStringList() << Collection::mimeType() << KMime::Message::mimeType() ); + + QCOMPARE( collection.rights(), Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + + if ( firstLevelNames.contains( collection.name() ) ) { + QCOMPARE( collection.parentCollection(), mStore->topLevelCollection() ); + } else if ( secondLevelNames.contains( collection.name() ) ) { + QVERIFY( firstLevelNames.contains( collection.parentCollection().name() ) ); + QCOMPARE( collection.parentCollection().parentCollection(), mStore->topLevelCollection() ); + } else if ( thirdLevelNames.contains( collection.name() ) ) { + QVERIFY( secondLevelNames.contains( collection.parentCollection().name() ) ); + QCOMPARE( collection.parentCollection().parentCollection().parentCollection(), + mStore->topLevelCollection() ); + } + } + + // test base fetching all collections + Q_FOREACH( const Collection &collection, collections ) { + job = mStore->fetchCollections( collection, FileStore::CollectionFetchJob::Base ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + QCOMPARE( spy->count(), 1 ); + + const Collection::List list = collectionsFromSpy( spy ); + QCOMPARE( list.count(), 1 ); + QCOMPARE( list.first(), collection ); + QCOMPARE( job->collections(), list ); + + const Collection col = list.first(); + QVERIFY( !col.remoteId().isEmpty() ); + QCOMPARE( col.remoteId(), col.name() ); + QCOMPARE( col.contentMimeTypes(), QStringList() << Collection::mimeType() << KMime::Message::mimeType() ); + + QCOMPARE( col.rights(), Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + } + + // test first level fetching all collections + Q_FOREACH( const Collection &collection, collections ) { + job = mStore->fetchCollections( collection, FileStore::CollectionFetchJob::FirstLevel ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + const Collection::List list = collectionsFromSpy( spy ); + QCOMPARE( job->collections(), list ); + + Q_FOREACH( const Collection &childCollection, list ) { + QCOMPARE( childCollection.parentCollection(), collection ); + + QVERIFY( !childCollection.remoteId().isEmpty() ); + QCOMPARE( childCollection.remoteId(), childCollection.name() ); + QCOMPARE( childCollection.contentMimeTypes(), QStringList() << Collection::mimeType() << KMime::Message::mimeType() ); + + QCOMPARE( childCollection.rights(), Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + } + + if ( firstLevelNames.contains( collection.name() ) ) { + Q_FOREACH( const Collection &childCollection, list ) { + QVERIFY( secondLevelNames.contains( childCollection.name() ) ); + } + } else if ( secondLevelNames.contains( collection.name() ) ) { + Q_FOREACH( const Collection &childCollection, list ) { + QVERIFY( thirdLevelNames.contains( childCollection.name() ) ); + } + if ( collection.name() == md1_2.name() ) { + QCOMPARE( list.count(), 1 ); + QCOMPARE( list.first().name(), md1_2_1.name() ); + } else if ( collection.name() == fileInfo1_1.fileName() ) { + QCOMPARE( list.count(), 2 ); + } + } else { + QCOMPARE( list.count(), 0 ); + } + } + + // test recursive fetching all collections + Q_FOREACH( const Collection &collection, collections ) { + job = mStore->fetchCollections( collection, FileStore::CollectionFetchJob::Recursive ); + + spy = new QSignalSpy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + const Collection::List list = collectionsFromSpy( spy ); + QCOMPARE( job->collections(), list ); + + Q_FOREACH( const Collection &childCollection, list ) { + QVERIFY( childCollection.parentCollection() == collection || + childCollection.parentCollection().parentCollection() == collection ); + QVERIFY( !childCollection.remoteId().isEmpty() ); + QCOMPARE( childCollection.remoteId(), childCollection.name() ); + QCOMPARE( childCollection.contentMimeTypes(), QStringList() << Collection::mimeType() << KMime::Message::mimeType() ); + + QCOMPARE( childCollection.rights(), Collection::CanCreateItem | + Collection::CanChangeItem | + Collection::CanDeleteItem | + Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + } + + if ( firstLevelNames.contains( collection.name() ) ) { + Q_FOREACH( const Collection &childCollection, list ) { + QVERIFY( secondLevelNames.contains( childCollection.name() ) || + thirdLevelNames.contains( childCollection.name() ) ); + } + } else if ( secondLevelNames.contains( collection.name() ) ) { + Q_FOREACH( const Collection &childCollection, list ) { + QVERIFY( thirdLevelNames.contains( childCollection.name() ) ); + } + if ( collection.name() == md1_2.name() ) { + QCOMPARE( list.count(), 1 ); + QCOMPARE( list.first().name(), md1_2_1.name() ); + } else if ( collection.name() == fileInfo1_1.fileName() ) { + QCOMPARE( list.count(), 2 ); + } + } else { + QCOMPARE( list.count(), 0 ); + } + } +} + +QTEST_KDEMAIN( CollectionFetchTest, NoGUI ) + +#include "collectionfetchtest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/collectionmodifytest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/collectionmodifytest.cpp new file mode 100644 index 00000000..e2f1eeff --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/collectionmodifytest.cpp @@ -0,0 +1,628 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/collectionmodifyjob.h" +#include "filestore/itemfetchjob.h" + +#include "libmaildir/maildir.h" + +#include + +#include + +using namespace Akonadi; + +class CollectionModifyTest : public QObject +{ + Q_OBJECT + + public: + CollectionModifyTest() : QObject(), mStore( 0 ), mDir( 0 ) {} + + ~CollectionModifyTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testRename(); + void testIndexPreservation(); + void testIndexCacheUpdate(); +}; + +void CollectionModifyTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void CollectionModifyTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void CollectionModifyTest::testRename() +{ + QDir topDir( mDir->name() ); + QVERIFY( topDir.mkdir( QLatin1String( "topLevel" ) ) ); + QVERIFY( topDir.cd( QLatin1String( "topLevel" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + QVERIFY( topLevelMd.isValid( false ) ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + KPIM::Maildir md1_2( md1.addSubFolder( "collection1_2" ), false ); + + // simulate second level mbox in maildir parent + QFileInfo fileInfo1_1( KPIM::Maildir::subDirPathForFolderPath( md1.path() ), + QLatin1String( "collection1_1" ) ); + QFile file1_1( fileInfo1_1.absoluteFilePath() ); + file1_1.open( QIODevice::WriteOnly ); + file1_1.close(); + QVERIFY( fileInfo1_1.exists() ); + + KPIM::Maildir md2( topLevelMd.addSubFolder( "collection2" ), false ); + + // simulate first level mbox + QFileInfo fileInfo3( topDir.path(), QLatin1String( "collection3" ) ); + QFile file3( fileInfo3.absoluteFilePath() ); + file3.open( QIODevice::WriteOnly ); + file3.close(); + QVERIFY( fileInfo3.exists() ); + + // simulate first level mbox with subtree + QFileInfo fileInfo4( topDir.path(), QLatin1String( "collection4" ) ); + QFile file4( fileInfo4.absoluteFilePath() ); + file4.open( QIODevice::WriteOnly ); + file4.close(); + QVERIFY( fileInfo4.exists() ); + + QFileInfo subDirInfo4( KPIM::Maildir::subDirPathForFolderPath( fileInfo4.absoluteFilePath() ) ); + QVERIFY( topDir.mkpath( subDirInfo4.absoluteFilePath() ) ); + + KPIM::Maildir md4( subDirInfo4.absoluteFilePath(), true ); + KPIM::Maildir md4_1( md4.addSubFolder( "collection4_1" ), false ); + + // simulate second level mbox in mbox parent + QFileInfo fileInfo4_2( subDirInfo4.absoluteFilePath(), + QLatin1String( "collection4_2" ) ); + QFile file4_2( fileInfo4_2.absoluteFilePath() ); + file4_2.open( QIODevice::WriteOnly ); + file4_2.close(); + QVERIFY( fileInfo4_2.exists() ); + + mStore->setPath( topDir.path() ); + + FileStore::CollectionModifyJob *job = 0; + Collection collection; + + // test renaming top level collection + topDir.cdUp(); + QVERIFY( !topDir.exists( QLatin1String( "newTopLevel" ) ) ); + + Collection topLevelCollection = mStore->topLevelCollection(); + topLevelCollection.setName( QLatin1String( "newTopLevel" ) ); + job = mStore->modifyCollection( topLevelCollection ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( topDir.exists( QLatin1String( "newTopLevel" ) ) ); + QVERIFY( !topDir.exists( QLatin1String( "topLevel" ) ) ); + QVERIFY( topDir.cd( QLatin1String( "newTopLevel" ) ) ); + QCOMPARE( mStore->path(), topDir.path() ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), mStore->path() ); + QCOMPARE( collection, mStore->topLevelCollection() ); + + // test failure of renaming again + job = mStore->modifyCollection( topLevelCollection ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + QCOMPARE( collection.remoteId(), mStore->path() ); + QCOMPARE( collection, mStore->topLevelCollection() ); + + // adjust local handles + topLevelMd = KPIM::Maildir( topDir.path(), true ); + QVERIFY( topLevelMd.isValid( false ) ); + + md1 = topLevelMd.subFolder( "collection1" ); + QVERIFY( md1.isValid( false ) ); + md1_2 = md1.subFolder( "collection1_2" ); + + fileInfo1_1 = QFileInfo( KPIM::Maildir::subDirPathForFolderPath( md1.path() ), + QLatin1String( "collection1_1" ) ); + QVERIFY( fileInfo1_1.exists() ); + + md2 = topLevelMd.subFolder( "collection2" ); + + fileInfo3 = QFileInfo( topDir.path(), QLatin1String( "collection3" ) ); + QVERIFY( fileInfo3.exists() ); + + fileInfo4 = QFileInfo( topDir.path(), QLatin1String( "collection4" ) ); + QVERIFY( fileInfo4.exists() ); + + subDirInfo4 = QFileInfo( KPIM::Maildir::subDirPathForFolderPath( fileInfo4.absoluteFilePath() ) ); + QVERIFY( subDirInfo4.exists() ); + + md4 = KPIM::Maildir( subDirInfo4.absoluteFilePath(), true ); + QVERIFY( md4.isValid( false ) ); + md4_1 = md4.subFolder( "collection4_1" ); + + fileInfo4_2 = QFileInfo( subDirInfo4.absoluteFilePath(), + QLatin1String( "collection4_2" ) ); + QVERIFY( fileInfo4_2.exists() ); + + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2" ) ); + + // test rename first level maildir leaf + Collection collection2; + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + collection2.setName( QLatin1String( "collection2_renamed" ) ); + + job = mStore->modifyCollection( collection2 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection, collection2 ); + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2_renamed" ) ); + QVERIFY( !md2.isValid( false ) ); + md2 = topLevelMd.subFolder( collection.remoteId() ); + QVERIFY( md2.isValid( false ) ); + + // test failure of renaming again + job = mStore->modifyCollection( collection2 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1" ) << QLatin1String( "collection2_renamed" ) ); + QVERIFY( md2.isValid( false ) ); + + // test renaming of first level mbox leaf + Collection collection3; + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + collection3.setName( QLatin1String( "collection3_renamed" ) ); + + job = mStore->modifyCollection( collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection, collection3 ); + fileInfo3.refresh(); + QVERIFY( !fileInfo3.exists() ); + fileInfo3 = QFileInfo( topDir.path(), collection.remoteId() ); + QVERIFY( fileInfo3.exists() ); + + // test failure of renaming again + job = mStore->modifyCollection( collection3 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + fileInfo3.refresh(); + QVERIFY( fileInfo3.exists() ); + + // test renaming second level maildir in mbox parent + Collection collection4; + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + collection4.setName( QLatin1String( "collection4" ) ); + + Collection collection4_1; + collection4_1.setRemoteId( QLatin1String( "collection4_1" ) ); + collection4_1.setParentCollection( collection4 ); + collection4_1.setName( QLatin1String( "collection4_1_renamed" ) ); + + job = mStore->modifyCollection( collection4_1 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection, collection4_1 ); + QCOMPARE( md4.subFolderList(), QStringList() << QLatin1String( "collection4_1_renamed" ) ); + QVERIFY( !md4_1.isValid( false ) ); + md4_1 = md4.subFolder( collection.remoteId() ); + QVERIFY( md4_1.isValid( false ) ); + + // test failure of renaming again + job = mStore->modifyCollection( collection4_1 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + QCOMPARE( md4.subFolderList(), QStringList() << QLatin1String( "collection4_1_renamed" ) ); + QVERIFY( md4_1.isValid( false ) ); + + // test renaming of second level mbox in mbox parent + Collection collection4_2; + collection4_2.setRemoteId( QLatin1String( "collection4_2" ) ); + collection4_2.setParentCollection( collection4 ); + collection4_2.setName( QLatin1String( "collection4_2_renamed" ) ); + + job = mStore->modifyCollection( collection4_2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection, collection4_2 ); + fileInfo4_2.refresh(); + QVERIFY( !fileInfo4_2.exists() ); + fileInfo4_2 = QFileInfo( md4.path(), collection.remoteId() ); + QVERIFY( fileInfo4_2.exists() ); + + // test failure of renaming again + job = mStore->modifyCollection( collection4_2 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + fileInfo4_2.refresh(); + QVERIFY( fileInfo4_2.exists() ); + + // test renaming of maildir with subtree + Collection collection1; + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + collection1.setName( QLatin1String( "collection1_renamed" ) ); + + job = mStore->modifyCollection( collection1 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection, collection1 ); + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1_renamed" ) << QLatin1String( "collection2_renamed" ) ); + QVERIFY( !md1.isValid( false ) ); + md1 = topLevelMd.subFolder( collection.remoteId() ); + QVERIFY( md1.isValid( false ) ); + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + QVERIFY( !md1_2.isValid( false ) ); + fileInfo1_1 = QFileInfo( KPIM::Maildir::subDirPathForFolderPath( md1.path() ), + QLatin1String( "collection1_1" ) ); + QVERIFY( fileInfo1_1.exists() ); + md1_2 = md1.subFolder( QLatin1String( "collection1_2" ) ); + QVERIFY( md1_2.isValid( false ) ); + + // test failure of renaming again + job = mStore->modifyCollection( collection1 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + QCOMPARE( topLevelMd.subFolderList(), QStringList() << QLatin1String( "collection1_renamed" ) << QLatin1String( "collection2_renamed" ) ); + QVERIFY( md2.isValid( false ) ); + QVERIFY( fileInfo1_1.exists() ); + QVERIFY( md1_2.isValid( false ) ); + + // test renaming of mbox with subtree + collection4.setName( QLatin1String( "collection4_renamed" ) ); + job = mStore->modifyCollection( collection4 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection.name() ); + QCOMPARE( collection, collection4 ); + fileInfo4.refresh(); + QVERIFY( !fileInfo4.exists() ); + fileInfo4 = QFileInfo( topDir.path(), collection.remoteId() ); + QVERIFY( fileInfo4.exists() ); + md4 = KPIM::Maildir( KPIM::Maildir::subDirPathForFolderPath( fileInfo4.absoluteFilePath() ), true ); + QVERIFY( md4.isValid( false ) ); + + QVERIFY( !md4_1.isValid( false ) ); + fileInfo4_2.refresh(); + QVERIFY( !fileInfo4_2.exists() ); + md4_1 = md4.subFolder( QLatin1String( "collection4_1_renamed" ) ); + QVERIFY( md4_1.isValid( false ) ); + fileInfo4_2 = QFileInfo( md4.path(), QLatin1String( "collection4_2_renamed" ) ); + QVERIFY( fileInfo4_2.exists() ); + + // test failure of renaming again + job = mStore->modifyCollection( collection4 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int) FileStore::Job::InvalidJobContext ); + fileInfo4.refresh(); + QVERIFY( fileInfo4.exists() ); +} + +void CollectionModifyTest::testIndexPreservation() +{ + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), mDir->name(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), mDir->name(), QLatin1String( "collection2" ) ) ); + + mStore->setPath( mDir->name() ); + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + FileStore::CollectionModifyJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + QVariant var; + Collection::List collections; + Item::List items; + + QMap flagCounts; + + // test renaming mbox + Collection collection1; + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + collection1.setName( QLatin1String( "collection1_renamed" ) ); + + job = mStore->modifyCollection( collection1 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + const QFileInfo indexFileInfo1( mDir->name(), QLatin1String( ".collection1_renamed.index" ) ); + QVERIFY( !indexFileInfo1.exists() ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collections.first() ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + + // test renaming maildir + Collection collection2; + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + collection2.setName( QLatin1String( "collection2_renamed" ) ); + + job = mStore->modifyCollection( collection2 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection2 ); + + const QFileInfo indexFileInfo2( mDir->name(), QLatin1String( ".collection2_renamed.index" ) ); + QVERIFY( !indexFileInfo2.exists() ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collections.first() ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + + flagCounts.clear(); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); +} + +void CollectionModifyTest::testIndexCacheUpdate() +{ + KPIM::Maildir topLevelMd( mDir->name(), true ); + QVERIFY( topLevelMd.isValid( false ) ); + + KPIM::Maildir md1( topLevelMd.addSubFolder( "collection1" ), false ); + + // simulate first level mbox + QFileInfo fileInfo2( mDir->name(), QLatin1String( "collection2" ) ); + QFile file2( fileInfo2.absoluteFilePath() ); + file2.open( QIODevice::WriteOnly ); + file2.close(); + QVERIFY( fileInfo2.exists() ); + + const QString colSubDir1 = KPIM::Maildir::subDirPathForFolderPath( md1.path() ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), colSubDir1, QLatin1String( "collection1_1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), colSubDir1, QLatin1String( "collection1_2" ) ) ); + + const QString colSubDir2 = KPIM::Maildir::subDirPathForFolderPath( fileInfo2.absoluteFilePath() ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), colSubDir2, QLatin1String( "collection2_1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), colSubDir2, QLatin1String( "collection2_2" ) ) ); + + mStore->setPath( mDir->name() ); + + FileStore::CollectionModifyJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + Collection collection; + Item::List items; + QMap flagCounts; + + // preparation: load all second level items to make sure respective index data is cached + Collection collection1; + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + collection1.setName( QLatin1String( "collection1" ) ); + + Collection collection1_1; + collection1_1.setRemoteId( QLatin1String( "collection1_1" ) ); + collection1_1.setParentCollection( collection1 ); + collection1_1.setName( QLatin1String( "collection1_1" ) ); + + itemFetch = mStore->fetchItems( collection1_1 ); + QVERIFY( itemFetch->exec() ); + + Collection collection1_2; + collection1_2.setRemoteId( QLatin1String( "collection1_2" ) ); + collection1_2.setParentCollection( collection1 ); + collection1_2.setName( QLatin1String( "collection1_2" ) ); + + itemFetch = mStore->fetchItems( collection1_2 ); + QVERIFY( itemFetch->exec() ); + Collection collection2; + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + collection2.setName( QLatin1String( "collection2" ) ); + + Collection collection2_1; + collection2_1.setRemoteId( QLatin1String( "collection2_1" ) ); + collection2_1.setParentCollection( collection2 ); + collection2_1.setName( QLatin1String( "collection2_1" ) ); + + itemFetch = mStore->fetchItems( collection2_1 ); + QVERIFY( itemFetch->exec() ); + Collection collection2_2; + collection2_2.setRemoteId( QLatin1String( "collection2_2" ) ); + collection2_2.setParentCollection( collection2 ); + collection2_2.setName( QLatin1String( "collection2_2" ) ); + + itemFetch = mStore->fetchItems( collection2_2 ); + QVERIFY( itemFetch->exec() ); + + // test renaming the maildir parent + collection1.setName( QLatin1String( "collection1_renamed" ) ); + + job = mStore->modifyCollection( collection1 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + + // get the items of the children and check the flags (see data/README) + collection1_1.setParentCollection( collection ); + itemFetch = mStore->fetchItems( collection1_1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + collection1_2.setParentCollection( collection ); + itemFetch = mStore->fetchItems( collection1_2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test renaming the mbox parent + collection2.setName( QLatin1String( "collection2_renamed" ) ); + + job = mStore->modifyCollection( collection2 ); + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + + // get the items of the children and check the flags (see data/README) + collection2_1.setParentCollection( collection ); + itemFetch = mStore->fetchItems( collection2_1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + collection2_2.setParentCollection( collection ); + itemFetch = mStore->fetchItems( collection2_2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); +} + +QTEST_KDEMAIN( CollectionModifyTest, NoGUI ) + +#include "collectionmodifytest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/collectionmovetest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/collectionmovetest.cpp new file mode 100644 index 00000000..b2e7358c --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/collectionmovetest.cpp @@ -0,0 +1,1999 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/collectionmovejob.h" +#include "filestore/itemfetchjob.h" + +#include "libmaildir/maildir.h" + +#include + +#include + +using namespace Akonadi; + +class CollectionMoveTest : public QObject +{ + Q_OBJECT + + public: + CollectionMoveTest() : QObject(), mStore( 0 ), mDir( 0 ) {} + + ~CollectionMoveTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testMoveToTopLevel(); + void testMoveToMaildir(); + void testMoveToMBox(); +}; + +void CollectionMoveTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void CollectionMoveTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void CollectionMoveTest::testMoveToTopLevel() +{ + QDir topDir( mDir->name() ); + + // top level dir + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QFileInfo fileInfo1( topDir, QLatin1String( "collection1" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QFileInfo fileInfo2( topDir, QLatin1String( "collection2" ) ); + + // first level maildir parent + QDir subDir1 = topDir; + QVERIFY( subDir1.mkdir( QLatin1String( ".collection1.directory" ) ) ); + QVERIFY( subDir1.cd( QLatin1String( ".collection1.directory" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir1.path(), QLatin1String( "collection1_1" ) ) ); + QFileInfo fileInfo1_1( subDir1.path(), QLatin1String( "collection1_1" ) ); + QVERIFY( fileInfo1_1.exists() ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir1.path(), QLatin1String( "collection1_2" ) ) ); + QFileInfo fileInfo1_2( subDir1.path(), QLatin1String( "collection1_2" ) ); + QVERIFY( fileInfo1_2.exists() ); + + // first level mbox parent + QDir subDir2 = topDir; + QVERIFY( subDir2.mkdir( QLatin1String( ".collection2.directory" ) ) ); + QVERIFY( subDir2.cd( QLatin1String( ".collection2.directory" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir2.path(), QLatin1String( "collection2_1" ) ) ); + QFileInfo fileInfo2_1( subDir2.path(), QLatin1String( "collection2_1" ) ); + QVERIFY( fileInfo2_1.exists() ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir2.path(), QLatin1String( "collection2_2" ) ) ); + QFileInfo fileInfo2_2( subDir2.path(), QLatin1String( "collection2_2" ) ); + QVERIFY( fileInfo2_2.exists() ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::CollectionMoveJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + Collection collection; + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item::List items; + QMap flagCounts; + + // test moving maildir from maildir parent + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + collection1_1.setRemoteId( QLatin1String( "collection1_1" ) ); + collection1_1.setParentCollection( collection1 ); + + job = mStore->moveCollection( collection1_1, mStore->topLevelCollection() ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), mStore->topLevelCollection() ); + + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( topDir.path(), collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test moving mbox from maildir parent + Collection collection1_2; + collection1_2.setName( QLatin1String( "collection1_2" ) ); + collection1_2.setRemoteId( QLatin1String( "collection1_2" ) ); + collection1_2.setParentCollection( collection1 ); + + job = mStore->moveCollection( collection1_2, mStore->topLevelCollection() ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), mStore->topLevelCollection() ); + + fileInfo1_2.refresh(); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( topDir.path(), collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test moving mbox from mbox parent + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Collection collection2_1; + collection2_1.setName( QLatin1String( "collection2_1" ) ); + collection2_1.setRemoteId( QLatin1String( "collection2_1" ) ); + collection2_1.setParentCollection( collection2 ); + + job = mStore->moveCollection( collection2_1, mStore->topLevelCollection() ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection2_1.remoteId() ); + QCOMPARE( collection.parentCollection(), mStore->topLevelCollection() ); + + fileInfo2_1.refresh(); + QVERIFY( !fileInfo2_1.exists() ); + fileInfo2_1 = QFileInfo( topDir.path(), collection.remoteId() ); + QVERIFY( fileInfo2_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test moving maildir from mbox parent + Collection collection2_2; + collection2_2.setName( QLatin1String( "collection2_2" ) ); + collection2_2.setRemoteId( QLatin1String( "collection2_2" ) ); + collection2_2.setParentCollection( collection2 ); + + job = mStore->moveCollection( collection2_2, mStore->topLevelCollection() ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection2_2.remoteId() ); + QCOMPARE( collection.parentCollection(), mStore->topLevelCollection() ); + + fileInfo2_2.refresh(); + QVERIFY( !fileInfo2_2.exists() ); + fileInfo2_2 = QFileInfo( topDir.path(), collection.remoteId() ); + QVERIFY( fileInfo2_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); +} + +void CollectionMoveTest::testMoveToMaildir() +{ + QDir topDir( mDir->name() ); + + // top level dir + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QFileInfo fileInfo1( topDir, QLatin1String( "collection1" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QFileInfo fileInfo2( topDir, QLatin1String( "collection2" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection3" ) ) ); + QFileInfo fileInfo3( topDir, QLatin1String( "collection3" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection4" ) ) ); + QFileInfo fileInfo4( topDir, QLatin1String( "collection4" ) ); + + // first level maildir parent + QDir subDir1 = topDir; + QVERIFY( subDir1.mkdir( QLatin1String( ".collection1.directory" ) ) ); + QVERIFY( subDir1.cd( QLatin1String( ".collection1.directory" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir1.path(), QLatin1String( "collection1_1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir1.path(), QLatin1String( "collection1_2" ) ) ); + + // first level mbox parent + QDir subDir4 = topDir; + QVERIFY( subDir4.mkdir( QLatin1String( ".collection4.directory" ) ) ); + QVERIFY( subDir4.cd( QLatin1String( ".collection4.directory" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir4.path(), QLatin1String( "collection4_1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir4.path(), QLatin1String( "collection4_2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir4.path(), QLatin1String( "collection4_3" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir4.path(), QLatin1String( "collection4_4" ) ) ); + + // target maildir + KPIM::Maildir topLevelMd( topDir.path(), true ); + KPIM::Maildir targetMd( topLevelMd.addSubFolder( QLatin1String( "target" ) ), false ); + QVERIFY( targetMd.isValid() ); + QDir subDirTarget; + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::CollectionMoveJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + Collection collection; + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item::List items; + QMap flagCounts; + + Collection target; + target.setName( QLatin1String( "target" ) ); + target.setRemoteId( QLatin1String( "target" ) ); + target.setParentCollection( mStore->topLevelCollection() ); + + // test move leaf maildir into sibling + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveCollection( collection2, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + subDirTarget = topDir; + QVERIFY( subDirTarget.cd( QLatin1String( ".target.directory" ) ) ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection2.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo2.refresh(); + QVERIFY( !fileInfo2.exists() ); + fileInfo2 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move leaf mbox into sibling + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveCollection( collection3, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection3.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo3.refresh(); + QVERIFY( !fileInfo3.exists() ); + fileInfo3 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo3.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move maildir with subtree into sibling + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + // load sub collection index data to check for correct cache updates + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + collection1_1.setRemoteId( QLatin1String( "collection1_1" ) ); + collection1_1.setParentCollection( collection1 ); + itemFetch = mStore->fetchItems( collection1_1 ); + QVERIFY( itemFetch->exec() ); + + Collection collection1_2; + collection1_2.setName( QLatin1String( "collection1_2" ) ); + collection1_2.setRemoteId( QLatin1String( "collection1_2" ) ); + collection1_2.setParentCollection( collection1 ); + itemFetch = mStore->fetchItems( collection1_2 ); + QVERIFY( itemFetch->exec() ); + + job = mStore->moveCollection( collection1, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo1.refresh(); + QVERIFY( !fileInfo1.exists() ); + fileInfo1 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1.exists() ); + QVERIFY( !subDir1.exists() ); + subDir1 = subDirTarget; + QVERIFY( subDir1.cd( QLatin1String( ".collection1.directory" ) ) ); + QCOMPARE( subDir1.entryList( QStringList() << QLatin1String( "collection*" ) ), + QStringList() << QLatin1String( "collection1_1" ) << QLatin1String( "collection1_2" ) ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // check for children cache path updates + collection1.setParentCollection( target ); + collection1_1.setParentCollection( collection1 ); + collection1_2.setParentCollection( collection1 ); + + itemFetch = mStore->fetchItems( collection1_1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + itemFetch = mStore->fetchItems( collection1_2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move mbox with subtree into sibling + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + // load sub collection index data to check for correct cache updates + Collection collection4_1; + collection4_1.setName( QLatin1String( "collection4_1" ) ); + collection4_1.setRemoteId( QLatin1String( "collection4_1" ) ); + collection4_1.setParentCollection( collection4 ); + itemFetch = mStore->fetchItems( collection4_1 ); + QVERIFY( itemFetch->exec() ); + + Collection collection4_2; + collection4_2.setName( QLatin1String( "collection4_2" ) ); + collection4_2.setRemoteId( QLatin1String( "collection4_2" ) ); + collection4_2.setParentCollection( collection4 ); + itemFetch = mStore->fetchItems( collection4_2 ); + QVERIFY( itemFetch->exec() ); + + job = mStore->moveCollection( collection4, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo4.refresh(); + QVERIFY( !fileInfo4.exists() ); + fileInfo4 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo4.exists() ); + QVERIFY( !subDir4.exists() ); + subDir4 = subDirTarget; + QVERIFY( subDir4.cd( QLatin1String( ".collection4.directory" ) ) ); + QCOMPARE( subDir4.entryList( QStringList() << QLatin1String( "collection*" ) ), + QStringList() << QLatin1String( "collection4_1" ) << QLatin1String( "collection4_2" ) + << QLatin1String( "collection4_3" ) << QLatin1String( "collection4_4" ) + ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // check for children cache path updates + collection4.setParentCollection( target ); + collection4_1.setParentCollection( collection4 ); + collection4_2.setParentCollection( collection4 ); + + itemFetch = mStore->fetchItems( collection4_1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + itemFetch = mStore->fetchItems( collection4_2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to parent's sibling + collection2.setParentCollection( target ); + + job = mStore->moveCollection( collection1_1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QDir subDir2 = subDirTarget; + QVERIFY( subDir2.cd( QLatin1String( ".collection2.directory" ) ) ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), collection2 ); + + QFileInfo fileInfo1_1( subDir1, collection.remoteId() ); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( subDir2, collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to parent's sibling + job = mStore->moveCollection( collection1_2, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), collection2 ); + + QFileInfo fileInfo1_2( subDir1, collection.remoteId() ); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( subDir2, collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to parent's sibling + job = mStore->moveCollection( collection4_1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4_1.remoteId() ); + QCOMPARE( collection.parentCollection(), collection2 ); + + QFileInfo fileInfo4_1( subDir4, collection.remoteId() ); + QVERIFY( !fileInfo4_1.exists() ); + fileInfo4_1 = QFileInfo( subDir2, collection.remoteId() ); + QVERIFY( fileInfo4_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to parent's sibling + job = mStore->moveCollection( collection4_2, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4_2.remoteId() ); + QCOMPARE( collection.parentCollection(), collection2 ); + + QFileInfo fileInfo4_2( subDir4, collection.remoteId() ); + QVERIFY( !fileInfo4_2.exists() ); + fileInfo4_2 = QFileInfo( subDir2, collection.remoteId() ); + QVERIFY( fileInfo4_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to grandparent + collection1_1.setParentCollection( collection2 ); + + job = mStore->moveCollection( collection1_1, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to grandparent + collection1_2.setParentCollection( collection2 ); + job = mStore->moveCollection( collection1_2, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo1_2.refresh(); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to grandparent + Collection collection4_3; + collection4_3.setName( QLatin1String( "collection4_3" ) ); + collection4_3.setRemoteId( QLatin1String( "collection4_3" ) ); + collection4_3.setParentCollection( collection4 ); + + job = mStore->moveCollection( collection4_3, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4_3.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + QFileInfo fileInfo4_3( subDir4, collection.remoteId() ); + QVERIFY( !fileInfo4_3.exists() ); + fileInfo4_3 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo4_3.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to grandparent + Collection collection4_4; + collection4_4.setName( QLatin1String( "collection4_4" ) ); + collection4_4.setRemoteId( QLatin1String( "collection4_4" ) ); + collection4_4.setParentCollection( collection4 ); + + job = mStore->moveCollection( collection4_4, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4_4.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + QFileInfo fileInfo4_4( subDir4, collection.remoteId() ); + QVERIFY( !fileInfo4_4.exists() ); + fileInfo4_4 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo4_4.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from maildir to grandchild + collection1_1.setParentCollection( target ); + + job = mStore->moveCollection( collection1_1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), collection2 ); + + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( subDir2, collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from maildir to grandchild + collection1_2.setParentCollection( target ); + + job = mStore->moveCollection( collection1_2, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), collection2 ); + + fileInfo1_2.refresh(); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( subDir2, collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); +} + +void CollectionMoveTest::testMoveToMBox() +{ + QDir topDir( mDir->name() ); + + // top level dir + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QFileInfo fileInfo1( topDir, QLatin1String( "collection1" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QFileInfo fileInfo2( topDir, QLatin1String( "collection2" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection3" ) ) ); + QFileInfo fileInfo3( topDir, QLatin1String( "collection3" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection4" ) ) ); + QFileInfo fileInfo4( topDir, QLatin1String( "collection4" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection5" ) ) ); + QFileInfo fileInfo5( topDir, QLatin1String( "collection5" ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection6" ) ) ); + QFileInfo fileInfo6( topDir, QLatin1String( "collection6" ) ); + + // first level maildir parent + QDir subDir1 = topDir; + QVERIFY( subDir1.mkdir( QLatin1String( ".collection1.directory" ) ) ); + QVERIFY( subDir1.cd( QLatin1String( ".collection1.directory" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir1.path(), QLatin1String( "collection1_1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir1.path(), QLatin1String( "collection1_2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir1.path(), QLatin1String( "collection1_3" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir1.path(), QLatin1String( "collection1_4" ) ) ); + + // first level mbox parent + QDir subDir4 = topDir; + QVERIFY( subDir4.mkdir( QLatin1String( ".collection4.directory" ) ) ); + QVERIFY( subDir4.cd( QLatin1String( ".collection4.directory" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), subDir4.path(), QLatin1String( "collection4_1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), subDir4.path(), QLatin1String( "collection4_2" ) ) ); + + // target mbox + QFileInfo fileInfoTarget( topDir.path(), QLatin1String( "target" ) ); + QFile fileTarget( fileInfoTarget.absoluteFilePath() ); + QVERIFY( fileTarget.open( QIODevice::WriteOnly ) ); + fileTarget.close(); + QVERIFY( fileInfoTarget.exists() ); + + QDir subDirTarget; + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::CollectionMoveJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + Collection collection; + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item::List items; + QMap flagCounts; + + Collection target; + target.setName( QLatin1String( "target" ) ); + target.setRemoteId( QLatin1String( "target" ) ); + target.setParentCollection( mStore->topLevelCollection() ); + + // test move leaf maildir into sibling + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveCollection( collection2, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + subDirTarget = topDir; + QVERIFY( subDirTarget.cd( QLatin1String( ".target.directory" ) ) ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection2.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo2.refresh(); + QVERIFY( !fileInfo2.exists() ); + fileInfo2 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move leaf mbox into sibling + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveCollection( collection3, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection3.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo3.refresh(); + QVERIFY( !fileInfo3.exists() ); + fileInfo3 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo3.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move leaf mbox into sibling without subtree + Collection collection5; + collection5.setName( QLatin1String( "collection5" ) ); + collection5.setRemoteId( QLatin1String( "collection5" ) ); + collection5.setParentCollection( mStore->topLevelCollection() ); + + Collection collection6; + collection6.setName( QLatin1String( "collection6" ) ); + collection6.setRemoteId( QLatin1String( "collection6" ) ); + collection6.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveCollection( collection5, collection6 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection5.remoteId() ); + QCOMPARE( collection.parentCollection(), collection6 ); + + fileInfo5.refresh(); + QVERIFY( !fileInfo5.exists() ); + QDir subDir6 = topDir; + QVERIFY( subDir6.cd( QLatin1String( ".collection6.directory" ) ) ); + fileInfo5 = QFileInfo( subDir6, collection.remoteId() ); + QVERIFY( fileInfo5.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move maildir with subtree into sibling + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + // load sub collection index data to check for correct cache updates + Collection collection1_1; + collection1_1.setName( QLatin1String( "collection1_1" ) ); + collection1_1.setRemoteId( QLatin1String( "collection1_1" ) ); + collection1_1.setParentCollection( collection1 ); + itemFetch = mStore->fetchItems( collection1_1 ); + QVERIFY( itemFetch->exec() ); + + Collection collection1_2; + collection1_2.setName( QLatin1String( "collection1_2" ) ); + collection1_2.setRemoteId( QLatin1String( "collection1_2" ) ); + collection1_2.setParentCollection( collection1 ); + itemFetch = mStore->fetchItems( collection1_2 ); + QVERIFY( itemFetch->exec() ); + + job = mStore->moveCollection( collection1, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo1.refresh(); + QVERIFY( !fileInfo1.exists() ); + fileInfo1 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1.exists() ); + QVERIFY( !subDir1.exists() ); + subDir1 = subDirTarget; + QVERIFY( subDir1.cd( QLatin1String( ".collection1.directory" ) ) ); + QCOMPARE( subDir1.entryList( QStringList() << QLatin1String( "collection*" ) ), + QStringList() << QLatin1String( "collection1_1" ) << QLatin1String( "collection1_2" ) + << QLatin1String( "collection1_3" ) << QLatin1String( "collection1_4" ) + ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // check for children cache path updates + collection1.setParentCollection( target ); + collection1_1.setParentCollection( collection1 ); + collection1_2.setParentCollection( collection1 ); + + itemFetch = mStore->fetchItems( collection1_1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + itemFetch = mStore->fetchItems( collection1_2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // test move mbox with subtree into sibling + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + // load sub collection index data to check for correct cache updates + Collection collection4_1; + collection4_1.setName( QLatin1String( "collection4_1" ) ); + collection4_1.setRemoteId( QLatin1String( "collection4_1" ) ); + collection4_1.setParentCollection( collection4 ); + itemFetch = mStore->fetchItems( collection4_1 ); + QVERIFY( itemFetch->exec() ); + + Collection collection4_2; + collection4_2.setName( QLatin1String( "collection4_2" ) ); + collection4_2.setRemoteId( QLatin1String( "collection4_2" ) ); + collection4_2.setParentCollection( collection4 ); + itemFetch = mStore->fetchItems( collection4_2 ); + QVERIFY( itemFetch->exec() ); + + job = mStore->moveCollection( collection4, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo4.refresh(); + QVERIFY( !fileInfo4.exists() ); + fileInfo4 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo4.exists() ); + QVERIFY( !subDir4.exists() ); + subDir4 = subDirTarget; + QVERIFY( subDir4.cd( QLatin1String( ".collection4.directory" ) ) ); + QCOMPARE( subDir4.entryList( QStringList() << QLatin1String( "collection*" ) ), + QStringList() << QLatin1String( "collection4_1" ) << QLatin1String( "collection4_2" ) + ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // check for children cache path updates + collection4.setParentCollection( target ); + collection4_1.setParentCollection( collection4 ); + collection4_2.setParentCollection( collection4 ); + + itemFetch = mStore->fetchItems( collection4_1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + itemFetch = mStore->fetchItems( collection4_2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to parent's sibling + collection3.setParentCollection( target ); + + job = mStore->moveCollection( collection1_1, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QDir subDir3 = subDirTarget; + QVERIFY( subDir3.cd( QLatin1String( ".collection3.directory" ) ) ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), collection3 ); + + QFileInfo fileInfo1_1( subDir1, collection.remoteId() ); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( subDir3, collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to parent's sibling + job = mStore->moveCollection( collection1_2, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), collection3 ); + + QFileInfo fileInfo1_2( subDir1, collection.remoteId() ); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( subDir3, collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to parent's sibling + job = mStore->moveCollection( collection4_1, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4_1.remoteId() ); + QCOMPARE( collection.parentCollection(), collection3 ); + + QFileInfo fileInfo4_1( subDir4, collection.remoteId() ); + QVERIFY( !fileInfo4_1.exists() ); + fileInfo4_1 = QFileInfo( subDir3, collection.remoteId() ); + QVERIFY( fileInfo4_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to parent's sibling + job = mStore->moveCollection( collection4_2, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection4_2.remoteId() ); + QCOMPARE( collection.parentCollection(), collection3 ); + + QFileInfo fileInfo4_2( subDir4, collection.remoteId() ); + QVERIFY( !fileInfo4_2.exists() ); + fileInfo4_2 = QFileInfo( subDir3, collection.remoteId() ); + QVERIFY( fileInfo4_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to grandparent + collection1_1.setParentCollection( collection3 ); + + job = mStore->moveCollection( collection1_1, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent mbox to grandparent + collection1_2.setParentCollection( collection3 ); + job = mStore->moveCollection( collection1_2, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + fileInfo1_2.refresh(); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to grandparent + Collection collection1_3; + collection1_3.setName( QLatin1String( "collection1_3" ) ); + collection1_3.setRemoteId( QLatin1String( "collection1_3" ) ); + collection1_3.setParentCollection( collection1 ); + + job = mStore->moveCollection( collection1_3, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_3.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + QFileInfo fileInfo1_3( subDir1, collection.remoteId() ); + QVERIFY( !fileInfo1_3.exists() ); + fileInfo1_3 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1_3.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from parent maildir to grandparent + Collection collection1_4; + collection1_4.setName( QLatin1String( "collection1_4" ) ); + collection1_4.setRemoteId( QLatin1String( "collection1_4" ) ); + collection1_4.setParentCollection( collection1 ); + + job = mStore->moveCollection( collection1_4, target ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_4.remoteId() ); + QCOMPARE( collection.parentCollection(), target ); + + QFileInfo fileInfo1_4( subDir1, collection.remoteId() ); + QVERIFY( !fileInfo1_4.exists() ); + fileInfo1_4 = QFileInfo( subDirTarget, collection.remoteId() ); + QVERIFY( fileInfo1_4.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from mbox to grandchild + collection1_1.setParentCollection( target ); + + job = mStore->moveCollection( collection1_1, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_1.remoteId() ); + QCOMPARE( collection.parentCollection(), collection3 ); + + fileInfo1_1.refresh(); + QVERIFY( !fileInfo1_1.exists() ); + fileInfo1_1 = QFileInfo( subDir3, collection.remoteId() ); + QVERIFY( fileInfo1_1.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + // move from maildir to grandchild + collection1_2.setParentCollection( target ); + + job = mStore->moveCollection( collection1_2, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collection = job->collection(); + QCOMPARE( collection.remoteId(), collection1_2.remoteId() ); + QCOMPARE( collection.parentCollection(), collection3 ); + + fileInfo1_2.refresh(); + QVERIFY( !fileInfo1_2.exists() ); + fileInfo1_2 = QFileInfo( subDir3, collection.remoteId() ); + QVERIFY( fileInfo1_2.exists() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 4 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); +} + +QTEST_KDEMAIN( CollectionMoveTest, NoGUI ) + +#include "collectionmovetest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/.dimap.index b/kdepim-runtime/resources/mixedmaildir/tests/data/.dimap.index new file mode 100644 index 0000000000000000000000000000000000000000..11d05bdd66190833b3285a115b012c858f0b8832 GIT binary patch literal 1441 zcmbu9T~|^;6oy}mvWz~oyRJ^ln^u}}5KeHHVFsdxTBzK#NDd#$r%<5(%YV|3=q`Uk z&kPGO!*a=*b!PUOGqd-eXWl)tPI^6GslH0p3x3bZy3vp~rF)Wk*~m4^L6yp zuDV4Zls+-jR);m=+swQW-Uj;?urKV>Tb1z8(*nLa{aAOcuLtxmC0Aj+QJfYd-w?S) zSQ}B4nJCs8*}o&_^nKO`6e8tGYZ)g~*81^lb&ag}yFq(sErq;mEFOSS?P+EGzj)2Trr>#Y&8Ect%-jH-s1QSb=X5 zzfEGz5hURUKt4Ppx78JeQm?Aj6J?a7ssP`4ULw_GlK?Jucr6&T%%XfL!)IK z;Hilg^HjhIHI{}#gSiB^Q$WEnRQi#b%|2& literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/.maildir-tagged.index b/kdepim-runtime/resources/mixedmaildir/tests/data/.maildir-tagged.index new file mode 100644 index 0000000000000000000000000000000000000000..297fb6e0fc24a75a2298bec759ccf9ce07f9b3fd GIT binary patch literal 1525 zcmchX+fEu$6o&s=s@9&=HmT{=B)zbSmO2OscO`g0@obCAO~XN89HhX2Qm*?-eUm;! z@AMJ;W;_JqB$XSoGkdRDYu3N_`v0}(qmkaqS=G<$wc<&~$e59Z=!C{4bu!`U0PF83 z(lge1T#6{JnxeX3P0G}Xx+*EFZJnznM=71j)|&QQ1UFkET_2=0-=51N+rVxr$CqdL zns14}69_i(x_0oTK#%Kmgvd+j8y*LAL*`RFpTw7USV8O_EPYiO)Q*DSw{G?yB}aO} z6BZ{9X~{#6VVR0Cg5J8sdkEX6who@We(*l8gXd%hG)qBap2egI&-!z@vB~*}ZZlqa zmcjg*M|#5pXX~)h04)n&j^?z=Y;n3{9jZ#i(NE40*O3yFWCRqT>?zM;()7L6qeDt< z{ZfXL7O6G?LP@)-fLH*l3OfhvSivq2tb+xEn`DN-YHOeKZ#>XpaR@q;v_K_cnCa8u zy^jw69-~7=r&Lvk@f@gKcu&KT%YBksi|7)guXph5-RR)VN$%-S#kxA9p=H?DDn3 z-n-~r&@x~)>U3%u<+}Uwa|gNgOpRLMG+{pH>S{*Ryt*%97^BIH}N67 z(?{@|J*3!VObv~hbnVqWd#_phTmN1Yb~;n2)}~VRVyS6oV~JU3NQ07^+4)h6(dm;O zGEPbxD(bxox>Qm%Rdk`2V#=$kLzR`0uT|b3%hh%eA=oXkUE@+(C0((yE*t-xC>~Eq z=P%8w2gdee-%2O#wbS^2tkn|HJvBAW{);gFBeU}fHZHP>Yk}{(#yb38WbM2X%p~wu zv`6N%O6dzLC$;mKeU@I4?tt-vU_+tzYxBcX<85h6&x~hdpYxN3jeUW&WHF2wFPI!{ zd*wTF0j*V?=@Z{RA`729)^x3X&5_}ZvT|T}qR%?M4>|JOg;|4&*oyZ?@m+U{zH-p4 z>n5c4I)dK!pWW!8ryL9tJXzTII;FOG6y;$vC@cjp2PPhrmP68kO1hBVK%F;4yhf{@ zmWf)1dW9HAkgh>#2htV%-bv5>2hs~2dOhSE2W0Gpq{kvi|7er8DSnhrAzjf|PV7Up zz-?%ulr$7hnM-mHiqv5d)*qSK4oM^3NYeE?O*g5WiwYcabcqv#Z426YuFDllY|t+} z>bHKsZqT9WSZ}2Np)(|*9Q3n7XJ>d5LHt{r_}@AM#8aI6490oLH0X+J*ecO;D6z(y RhN16L-CJ#2h&xK#zX4%sp1%M9 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/.mbox-tagged.index b/kdepim-runtime/resources/mixedmaildir/tests/data/.mbox-tagged.index new file mode 100644 index 0000000000000000000000000000000000000000..a427d517784481dbdf7ad00c0911ccf0046cf2ff GIT binary patch literal 1317 zcmb`G+fG_R6o$XHyC-|LO?tQ@=|!8^T0td}-qao_c(w;xxdTN(!J-?G`YwHpzDXaV zcl8nau?c9RiISMfo>`e;{cGmqUr%GR^My+FWp;b5)Q;uilViytt^w`b#BkucbOA2A zzJMxaKCr_QW32L?I$ue%$2Z2A;uSfP6xrev8y$sCHki&6n59IE3fshtcPUVFl-RxR z`2*a4=YE~08fiz7@S-O-;fuctX_%L|bxpOs9~ z`_$wyeb$x1nNO#fk*h)va4=#7Zfa2V+W99nOc@ zqdv1=MqOqcoW=~g90HeaD8(_-BcfjR*HuMSgQ; zCe&)`&MY&>yfuD4oF4W$9r)%hr@v?MR+&w2Sl2@ZCiFmPHx#8Xr7sdPNgE|ubZ5EC zw9{9oFm3DAl%187YD%5B6!+zt*WxXm7Lt+m79R)omi@Y5Ud#I~s+mze#pW@l@$+GN t*spIN_n7`Z3)8GG+mT}4w}*C7+PNa%62(lvWd!$6R1}c0A^U836)sGZNCe(+7iESD?f*N7T6>#si1c zxZ#*rGHkHL8XgY!6uBbbVd!Lo(wT=OIbJAG!4hterL2*oF>6lsD~`jpcAB(wW_>_A zONgzT7ZvWwW0$-b0;1w-ebpK=Tw^AVDu!6pMTQO9Rty{T{&9A$TKb#Tm@vpvoYaIV zO=-Q8vbcsP#Wkv`<ioDH~7-oWLa)D_4--)no%Fs^;K2R JxZM8a-zRH*Om+YO literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/.mbox.index b/kdepim-runtime/resources/mixedmaildir/tests/data/.mbox.index new file mode 100644 index 0000000000000000000000000000000000000000..f28eb9ecc8df956c840a46c7d1f256aa01b0dc41 GIT binary patch literal 1257 zcmb`G+fEZv6ox-wLA6#MU79S*=LDyrg%b*kX5$%z-B8$D;f;v9?VeWD`j>_=MF8# zEBdNedj&pvQm5fJV||`H#BA-j5&dIvJZIAkO-9Vsh->vnxdtP9SsU=ci(HJnr`mzY z`}zCrJ8Sti;kx?AgKK(l-N#;6>}=ANv*Q|{t(E7%dU_o>?Z^bI2dS{>Ys zJPkt~FX-v;%dpO{xBX&<7R!bilV&VSZNt1ZYYkf|#lm^`L3fzl=`j1Q7c=AVG-eQU z2;5HMa7;%T34D|6zC5Z-OLs#}m1Mc5jw=4~MTwO3|6fPr-C=UO!{pHZVkW=NqN5U9 z_EA$W1t$F8b=ALW$5U!x%>L7QA#o#xBr=x%7Fxee`f9LSU-8R1;XW^H%i(QH3?X&K5q_t&PuBeuZZ|V`t6wB>73>?Ih G^mhb4K8Nc7 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/README b/kdepim-runtime/resources/mixedmaildir/tests/data/README new file mode 100644 index 00000000..bd8bb436 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/README @@ -0,0 +1,38 @@ +Description of contained Test data +================================== + +maildir/dimap message filenames that contained ':' had it replaced with '_' + +1) dimap: 4 messages + index + - message 1 flagged important + - message 2 flagged unread + - message 3 flagged new, empty body + - message 4 flagged task, empty subject + +2) maildir: 4 messages + index + - message 1 flagged important + - message 2 flagged unread + - message 3 flagged new, empty body + - message 4 flagged task, empty subject + +3) mbox: 4 messages + index + - message 1 flagged unread + - message 2 flagged task, empty subject + - message 3 flagged new, empty body + - message 4 flagged important + +4) maildir-tagged: 4 messages + index + - message 1 flagged important, tag1 + - message 2 flagged unread, tag2 + - message 3 flagged new, empty body, tag 3 + - message 4 flagged task, empty subject + +5) mbox-tagged: 4 messages + index + - message 1 flagged unread, tag2 + - message 2 flagged task, empty subject + - message 3 flagged new, empty body, tag 3 + - message 4 flagged important, tag1 + +6) mbox-unpurged: 4 messages + index with entries for the first two + - simulating an unpurged mbox: all messages in mbox, no index entries for + the two messages in the middle diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.LUBVK b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.LUBVK new file mode 100644 index 00000000..cf6113d5 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.LUBVK @@ -0,0 +1,46 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx102) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 5074095366D + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id DEC1195366F + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id C668A95366D + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 9EEB53103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 8367A3103EEE + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 2 +Date: Sat, 24 Jul 2010 15:51:16 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.16855.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: U +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: +X-UID: 10 + +Body of Test 2 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.RTmAd_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.RTmAd_2,S new file mode 100644 index 00000000..761648af --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.RTmAd_2,S @@ -0,0 +1,45 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:52:14 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx036) with SMTP; 24 Jul 2010 15:52:14 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 3536595366D + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 101AC95366A + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id ED25794D9DA + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id C2DA93103EEB + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id AE2D13103EE6 + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Date: Sat, 24 Jul 2010 15:52:00 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241552.01232.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iM2SSXdnXxuLgAcAvPC3Xr5Z6OPcGoHnWX0YgscMEgMNa/UP6 + 0PsZjBktxR5hqB4N3jaDTD+60EUFg8bPz1GGvD1YActDYJympMTApbcs85zthOivOU3SH3UvpiHN + TRN8A==V1; +Status: RO +X-Status: RK +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: +X-UID: 11 + +Body of Test 4 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.g8PCJ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.g8PCJ new file mode 100644 index 00000000..066bbf12 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.g8PCJ @@ -0,0 +1,46 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx106) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id A6DEC95366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 6305495366F + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id 4D0C995366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 21F263103EEB + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 06BF13103EE6 + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 3 +Date: Sat, 24 Jul 2010 15:51:36 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.37547.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: +X-UID: 12 + + diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.qs6V9_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.qs6V9_2,S new file mode 100644 index 00000000..dcaa004f --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/cur/1279980064.4595.qs6V9_2,S @@ -0,0 +1,48 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:41 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx033) with SMTP; 24 Jul 2010 15:51:41 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id BD28395366A + for ; Sat, 24 Jul 2010 15:51:40 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id CA62095366E + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id B5E0195366A + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 754B03103EEB + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 545963103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 1 +Date: Sat, 24 Jul 2010 15:50:45 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241550.46907.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: RG +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: +X-UID: 13 + +Body +of +Test 1 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/new/.keep b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/tmp/.keep b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/dimap/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.6qZsA_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.6qZsA_2,S new file mode 100644 index 00000000..c9040b6e --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.6qZsA_2,S @@ -0,0 +1,45 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:52:14 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx036) with SMTP; 24 Jul 2010 15:52:14 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 3536595366D + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 101AC95366A + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id ED25794D9DA + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id C2DA93103EEB + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id AE2D13103EE6 + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Date: Sat, 24 Jul 2010 15:52:00 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241552.01232.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iM2SSXdnXxuLgAcAvPC3Xr5Z6OPcGoHnWX0YgscMEgMNa/UP6 + 0PsZjBktxR5hqB4N3jaDTD+60EUFg8bPz1GGvD1YActDYJympMTApbcs85zthOivOU3SH3UvpiHN + TRN8A==V1; +Status: RO +X-Status: RK +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 4 + diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.Xdz3R_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.Xdz3R_2,S new file mode 100644 index 00000000..e6ac84a2 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.Xdz3R_2,S @@ -0,0 +1,46 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx102) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 5074095366D + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id DEC1195366F + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id C668A95366D + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 9EEB53103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 8367A3103EEE + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 2 +Date: Sat, 24 Jul 2010 15:51:16 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.16855.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: U +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 2 + diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.f0l49_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.f0l49_2,S new file mode 100644 index 00000000..8476dda2 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.f0l49_2,S @@ -0,0 +1,45 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx106) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id A6DEC95366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 6305495366F + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id 4D0C995366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 21F263103EEB + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 06BF13103EE6 + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 3 +Date: Sat, 24 Jul 2010 15:51:36 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.37547.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + + diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.kwx1b_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.kwx1b_2,S new file mode 100644 index 00000000..493ccd3c --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/cur/1279982188.18722.kwx1b_2,S @@ -0,0 +1,47 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:41 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx033) with SMTP; 24 Jul 2010 15:51:41 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id BD28395366A + for ; Sat, 24 Jul 2010 15:51:40 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id CA62095366E + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id B5E0195366A + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 754B03103EEB + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 545963103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 1 +Date: Sat, 24 Jul 2010 15:50:45 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241550.46907.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: RG +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body +of +Test 1 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/new/.keep b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/tmp/.keep b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir-tagged/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979617.4595.bwXSm b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979617.4595.bwXSm new file mode 100644 index 00000000..8476dda2 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979617.4595.bwXSm @@ -0,0 +1,45 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx106) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id A6DEC95366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 6305495366F + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id 4D0C995366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 21F263103EEB + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 06BF13103EE6 + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 3 +Date: Sat, 24 Jul 2010 15:51:36 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.37547.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + + diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.CStza_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.CStza_2,S new file mode 100644 index 00000000..347c199b --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.CStza_2,S @@ -0,0 +1,47 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:41 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx033) with SMTP; 24 Jul 2010 15:51:41 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id BD28395366A + for ; Sat, 24 Jul 2010 15:51:40 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id CA62095366E + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id B5E0195366A + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 754B03103EEB + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 545963103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 1 +Date: Sat, 24 Jul 2010 15:50:45 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241550.46907.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: R +X-KMail-EncryptionState: N +X-KMail-SignatureState: N +X-KMail-MDN-Sent: + +Body +of +Test 1 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.DUl0I_2,S b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.DUl0I_2,S new file mode 100644 index 00000000..ac6afa9c --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.DUl0I_2,S @@ -0,0 +1,44 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:52:14 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx036) with SMTP; 24 Jul 2010 15:52:14 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 3536595366D + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 101AC95366A + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id ED25794D9DA + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id C2DA93103EEB + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id AE2D13103EE6 + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Date: Sat, 24 Jul 2010 15:52:00 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241552.01232.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iM2SSXdnXxuLgAcAvPC3Xr5Z6OPcGoHnWX0YgscMEgMNa/UP6 + 0PsZjBktxR5hqB4N3jaDTD+60EUFg8bPz1GGvD1YActDYJympMTApbcs85zthOivOU3SH3UvpiHN + TRN8A==V1; +Status: RO +X-Status: R +X-KMail-EncryptionState: N +X-KMail-SignatureState: N +X-KMail-MDN-Sent: + +Body of Test 4 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.pY5ny b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.pY5ny new file mode 100644 index 00000000..e6fd345c --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/cur/1279979618.4595.pY5ny @@ -0,0 +1,45 @@ +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx102) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 5074095366D + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id DEC1195366F + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id C668A95366D + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 9EEB53103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 8367A3103EEE + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 2 +Date: Sat, 24 Jul 2010 15:51:16 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.16855.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 2 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/new/.keep b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/new/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/new/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/tmp/.keep b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/tmp/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/maildir/tmp/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/mbox b/kdepim-runtime/resources/mixedmaildir/tests/data/mbox new file mode 100644 index 00000000..b016d7b1 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/mbox @@ -0,0 +1,187 @@ +From kevin.krammer@demo.kolab.org Sat Jul 24 15:51:16 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx102) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 5074095366D + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id DEC1195366F + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id C668A95366D + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 9EEB53103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 8367A3103EEE + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 2 +Date: Sat, 24 Jul 2010 15:51:16 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.16855.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: U +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 2 + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:52:00 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:52:14 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx036) with SMTP; 24 Jul 2010 15:52:14 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 3536595366D + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 101AC95366A + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id ED25794D9DA + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id C2DA93103EEB + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id AE2D13103EE6 + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Date: Sat, 24 Jul 2010 15:52:00 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241552.01232.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iM2SSXdnXxuLgAcAvPC3Xr5Z6OPcGoHnWX0YgscMEgMNa/UP6 + 0PsZjBktxR5hqB4N3jaDTD+60EUFg8bPz1GGvD1YActDYJympMTApbcs85zthOivOU3SH3UvpiHN + TRN8A==V1; +Status: RO +X-Status: RK +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 4 + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:51:36 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx106) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id A6DEC95366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 6305495366F + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id 4D0C995366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 21F263103EEB + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 06BF13103EE6 + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 3 +Date: Sat, 24 Jul 2010 15:51:36 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.37547.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:50:45 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:41 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx033) with SMTP; 24 Jul 2010 15:51:41 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id BD28395366A + for ; Sat, 24 Jul 2010 15:51:40 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id CA62095366E + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id B5E0195366A + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 754B03103EEB + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 545963103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 1 +Date: Sat, 24 Jul 2010 15:50:45 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241550.46907.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: RG +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body +of +Test 1 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/mbox-tagged b/kdepim-runtime/resources/mixedmaildir/tests/data/mbox-tagged new file mode 100644 index 00000000..b016d7b1 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/mbox-tagged @@ -0,0 +1,187 @@ +From kevin.krammer@demo.kolab.org Sat Jul 24 15:51:16 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx102) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 5074095366D + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id DEC1195366F + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id C668A95366D + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 9EEB53103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 8367A3103EEE + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 2 +Date: Sat, 24 Jul 2010 15:51:16 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.16855.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: U +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 2 + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:52:00 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:52:14 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx036) with SMTP; 24 Jul 2010 15:52:14 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 3536595366D + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 101AC95366A + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id ED25794D9DA + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id C2DA93103EEB + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id AE2D13103EE6 + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Date: Sat, 24 Jul 2010 15:52:00 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241552.01232.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iM2SSXdnXxuLgAcAvPC3Xr5Z6OPcGoHnWX0YgscMEgMNa/UP6 + 0PsZjBktxR5hqB4N3jaDTD+60EUFg8bPz1GGvD1YActDYJympMTApbcs85zthOivOU3SH3UvpiHN + TRN8A==V1; +Status: RO +X-Status: RK +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 4 + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:51:36 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx106) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id A6DEC95366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 6305495366F + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id 4D0C995366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 21F263103EEB + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 06BF13103EE6 + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 3 +Date: Sat, 24 Jul 2010 15:51:36 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.37547.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:50:45 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:41 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx033) with SMTP; 24 Jul 2010 15:51:41 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id BD28395366A + for ; Sat, 24 Jul 2010 15:51:40 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id CA62095366E + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id B5E0195366A + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 754B03103EEB + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 545963103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 1 +Date: Sat, 24 Jul 2010 15:50:45 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241550.46907.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: RG +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body +of +Test 1 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/data/mbox-unpurged b/kdepim-runtime/resources/mixedmaildir/tests/data/mbox-unpurged new file mode 100644 index 00000000..b016d7b1 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/data/mbox-unpurged @@ -0,0 +1,187 @@ +From kevin.krammer@demo.kolab.org Sat Jul 24 15:51:16 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx102) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 5074095366D + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id DEC1195366F + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id C668A95366D + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 9EEB53103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 8367A3103EEE + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 2 +Date: Sat, 24 Jul 2010 15:51:16 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.16855.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: U +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 2 + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:52:00 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:52:14 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx036) with SMTP; 24 Jul 2010 15:52:14 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 3536595366D + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 101AC95366A + for ; Sat, 24 Jul 2010 15:52:14 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id ED25794D9DA + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id C2DA93103EEB + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id AE2D13103EE6 + for ; Sat, 24 Jul 2010 15:52:13 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Date: Sat, 24 Jul 2010 15:52:00 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241552.01232.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iM2SSXdnXxuLgAcAvPC3Xr5Z6OPcGoHnWX0YgscMEgMNa/UP6 + 0PsZjBktxR5hqB4N3jaDTD+60EUFg8bPz1GGvD1YActDYJympMTApbcs85zthOivOU3SH3UvpiHN + TRN8A==V1; +Status: RO +X-Status: RK +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body of Test 4 + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:51:36 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:39 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx106) with SMTP; 24 Jul 2010 15:51:39 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id A6DEC95366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id 6305495366F + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id 4D0C995366A + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 21F263103EEB + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 06BF13103EE6 + for ; Sat, 24 Jul 2010 15:51:39 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 3 +Date: Sat, 24 Jul 2010 15:51:36 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241551.37547.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + + +From kevin.krammer@demo.kolab.org Sat Jul 24 15:50:45 2010 +Return-Path: +Delivered-To: GMX delivery to kevin.krammer@gmx.at +Received: (qmail invoked by alias); 24 Jul 2010 13:51:41 -0000 +Received: from aktaia.intevation.org (EHLO kolab.intevation.de) [212.95.126.10] + by mx0.gmx.net (mx033) with SMTP; 24 Jul 2010 15:51:41 +0200 +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id BD28395366A + for ; Sat, 24 Jul 2010 15:51:40 +0200 (CEST) +X-Virus-Scanned: by amavisd-new at intevation.de +Received: from localhost (localhost.localdomain [127.0.0.1]) + by kolab.intevation.de (Postfix) with ESMTP id CA62095366E + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from demo.kolab.org (demo.kolab.org [78.47.168.37]) + by kolab.intevation.de (Postfix) with ESMTP id B5E0195366A + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from localhost (localhost [127.0.0.1]) + by demo.kolab.org (Postfix) with ESMTP id 754B03103EEB + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +Received: from artemis.localnet (bergfex.jusinger.at [213.47.44.9]) + (Authenticated sender: kevin.krammer@demo.kolab.org) + by demo.kolab.org (Postfix) with ESMTP id 545963103EE6 + for ; Sat, 24 Jul 2010 15:51:38 +0200 (CEST) +From: Kevin Krammer +Organization: Kolab Demo +To: kevin.krammer@gmx.at +Subject: Test 1 +Date: Sat, 24 Jul 2010 15:50:45 +0200 +User-Agent: KMail/1.13.3 (Linux/2.6.33-grml; KDE/4.4.4; i686; ; ) +MIME-Version: 1.0 +Content-Type: Text/Plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Message-Id: <201007241550.46907.kevin.krammer@demo.kolab.org> +X-GMX-Antivirus: 0 (no virus found) +X-GMX-Antispam: 0 (Mail was not recognized as spam); + Detail=5D7Q89H36p5mOUCHqrP9iL/6eeEA7/vGXc8Az2zQ5lkNsVrprUw5ljr293uKsrVdFBp1i + k5eF2TKdovp4qtfEvYkwJlTyCJPnhg0xAPWnM7rhY/+LZUrte1UAr5224vU33NgWA8bmceIWcEOY + UsgKg==V1; +Status: RO +X-Status: RG +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Body +of +Test 1 diff --git a/kdepim-runtime/resources/mixedmaildir/tests/itemcreatetest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/itemcreatetest.cpp new file mode 100644 index 00000000..d5ebac21 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/itemcreatetest.cpp @@ -0,0 +1,535 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/itemcreatejob.h" +#include "filestore/itemfetchjob.h" + +#include "libmaildir/maildir.h" + +#include +#include + +#include +#include + +#include + +using namespace Akonadi; + +class ItemCreateTest : public QObject +{ + Q_OBJECT + + public: + ItemCreateTest() : QObject(), mStore( 0 ), mDir( 0 ) {} + + ~ItemCreateTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testExpectedFail(); + void testMBox(); + void testMaildir(); +}; + +void ItemCreateTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void ItemCreateTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void ItemCreateTest::testExpectedFail() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "data" ) ) ); + QDir dataDir = topDir; + QVERIFY( dataDir.cd( QLatin1String( "data" ) ) ); + KPIM::Maildir dataMd( dataDir.path(), false ); + QVERIFY( dataMd.isValid() ); + + const QStringList dataEntryList = dataMd.entryList(); + QCOMPARE( dataEntryList.count(), 4 ); + KMime::Message::Ptr msgPtr( new KMime::Message ); + msgPtr->setContent( KMime::CRLFtoLF( dataMd.readEntry( dataEntryList.first() ) ) ); + + QVERIFY( topDir.mkdir( QLatin1String( "store" ) ) ); + QVERIFY( topDir.cd( QLatin1String( "store" ) ) ); + mStore->setPath( topDir.path() ); + + FileStore::ItemCreateJob *job = 0; + + // test failure of adding item to top level collection + Item item; + item.setMimeType( KMime::Message::mimeType() ); + item.setPayload( msgPtr ); + + job = mStore->createItem( item, mStore->topLevelCollection() ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + // test failure of adding item to non existant collection + Collection collection; + collection.setName( QLatin1String( "collection" ) ); + collection.setRemoteId( QLatin1String( "collection" ) ); + collection.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->createItem( item, collection ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); +} + +void ItemCreateTest::testMBox() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "data" ) ) ); + QDir dataDir = topDir; + QVERIFY( dataDir.cd( QLatin1String( "data" ) ) ); + KPIM::Maildir dataMd( dataDir.path(), false ); + QVERIFY( dataMd.isValid() ); + + const QStringList dataEntryList = dataMd.entryList(); + QCOMPARE( dataEntryList.count(), 4 ); + KMime::Message::Ptr msgPtr1( new KMime::Message ); + msgPtr1->setContent( KMime::CRLFtoLF( dataMd.readEntry( dataEntryList.first() ) ) ); + KMime::Message::Ptr msgPtr2( new KMime::Message ); + msgPtr2->setContent( KMime::CRLFtoLF( dataMd.readEntry( dataEntryList.last() ) ) ); + + QVERIFY( topDir.mkdir( QLatin1String( "store" ) ) ); + QVERIFY( topDir.cd( QLatin1String( "store" ) ) ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection1" ) ) ); + + QFileInfo fileInfo1( topDir.path(), QLatin1String( "collection1" ) ); + KMBox::MBox mbox1; + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + QCOMPARE( (int)mbox1.entries().count(), 4 ); + const int size1 = fileInfo1.size(); + + // simulate empty mbox + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + QFile file2( fileInfo2.absoluteFilePath() ); + QVERIFY( file2.open( QIODevice::WriteOnly ) ); + file2.close(); + QVERIFY( file2.exists() ); + QCOMPARE( (int)file2.size(), 0 ); + + mStore->setPath( topDir.path() ); + + // common variables + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item::List items; + QMap flagCounts; + + FileStore::ItemCreateJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + + // test adding to empty mbox + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setId( KRandom::random() ); + item1.setMimeType( KMime::Message::mimeType() ); + item1.setPayload( msgPtr1 ); + + job = mStore->createItem( item1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + QCOMPARE( item.id(), item1.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.remoteId(), QLatin1String( "0" ) ); + QCOMPARE( item.parentCollection(), collection2 ); + + fileInfo2.refresh(); + QVERIFY( fileInfo2.size() > 0 ); + const int size2 = fileInfo2.size(); + + KMBox::MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + QCOMPARE( (int)mbox2.entries().count(), 1 ); + + Item item2; + item2.setId( KRandom::random() ); + item2.setMimeType( KMime::Message::mimeType() ); + item2.setPayload( msgPtr2 ); + + job = mStore->createItem( item2, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item2.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.remoteId(), QString::number( size2 + 1) ); + QCOMPARE( item.parentCollection(), collection2 ); + + fileInfo2.refresh(); + QVERIFY( fileInfo2.size() > 0 ); + + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + QCOMPARE( (int)mbox2.entries().count(), 2 ); + + // test adding to non-empty mbox + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->createItem( item1, collection1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item1.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.remoteId(), QString::number( size1 + 1 ) ); + QCOMPARE( item.parentCollection(), collection1 ); + + fileInfo1.refresh(); + QVERIFY( fileInfo1.size() > size1 ); + + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + QCOMPARE( (int)mbox1.entries().count(), 5 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 5 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + job = mStore->createItem( item2, collection1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item2.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.remoteId(), QString::number( size1 + 1 + size2 + 1 ) ); + QCOMPARE( item.parentCollection(), collection1 ); + + fileInfo1.refresh(); + QVERIFY( fileInfo1.size() > (size1 + size2 ) ); + + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + QCOMPARE( (int)mbox1.entries().count(), 6 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 6 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); +} + +void ItemCreateTest::testMaildir() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "data" ) ) ); + QDir dataDir = topDir; + QVERIFY( dataDir.cd( QLatin1String( "data" ) ) ); + KPIM::Maildir dataMd( dataDir.path(), false ); + QVERIFY( dataMd.isValid() ); + + const QStringList dataEntryList = dataMd.entryList(); + QCOMPARE( dataEntryList.count(), 4 ); + KMime::Message::Ptr msgPtr1( new KMime::Message ); + msgPtr1->setContent( KMime::CRLFtoLF( dataMd.readEntry( dataEntryList.first() ) ) ); + KMime::Message::Ptr msgPtr2( new KMime::Message ); + msgPtr2->setContent( KMime::CRLFtoLF( dataMd.readEntry( dataEntryList.last() ) ) ); + + QVERIFY( topDir.mkdir( QLatin1String( "store" ) ) ); + QVERIFY( topDir.cd( QLatin1String( "store" ) ) ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QVERIFY( md1.isValid() ); + + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + // simulate empty maildir + KPIM::Maildir md2( topLevelMd.addSubFolder( QLatin1String( "collection2" ) ), false ); + QVERIFY( md2.isValid() ); + + QSet entrySet2 = QSet::fromList( md2.entryList() ); + QCOMPARE( (int)entrySet2.count(), 0 ); + + mStore->setPath( topDir.path() ); + + // common variables + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item::List items; + QMap flagCounts; + + QSet entrySet; + QSet newIdSet; + QString newId; + + FileStore::ItemCreateJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + + // test adding to empty maildir + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setId( KRandom::random() ); + item1.setMimeType( KMime::Message::mimeType() ); + item1.setPayload( msgPtr1 ); + + job = mStore->createItem( item1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + QCOMPARE( item.id(), item1.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.parentCollection(), collection2 ); + + entrySet = QSet::fromList( md2.entryList() ); + QCOMPARE( (int)entrySet.count(), 1 ); + + newIdSet = entrySet.subtract( entrySet2 ); + QCOMPARE( (int)newIdSet.count(), 1 ); + + newId = newIdSet.values().first(); + QCOMPARE( item.remoteId(), newId ); + entrySet2 << newId; + QCOMPARE( (int)entrySet2.count(), 1 ); + + Item item2; + item2.setId( KRandom::random() ); + item2.setMimeType( KMime::Message::mimeType() ); + item2.setPayload( msgPtr2 ); + + job = mStore->createItem( item2, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item2.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.parentCollection(), collection2 ); + + entrySet = QSet::fromList( md2.entryList() ); + QCOMPARE( (int)entrySet.count(), 2 ); + + newIdSet = entrySet.subtract( entrySet2 ); + QCOMPARE( (int)newIdSet.count(), 1 ); + + newId = newIdSet.values().first(); + QCOMPARE( item.remoteId(), newId ); + entrySet2 << newId; + QCOMPARE( (int)entrySet2.count(), 2 ); + + // test adding to non-empty maildir + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->createItem( item1, collection1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item1.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.parentCollection(), collection1 ); + + entrySet = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet.count(), 5 ); + + newIdSet = entrySet.subtract( entrySet1 ); + QCOMPARE( (int)newIdSet.count(), 1 ); + + newId = newIdSet.values().first(); + QCOMPARE( item.remoteId(), newId ); + entrySet1 << newId; + QCOMPARE( (int)entrySet1.count(), 5 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 5 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + job = mStore->createItem( item2, collection1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item2.id() ); + QVERIFY( !item.remoteId().isEmpty() ); + QCOMPARE( item.parentCollection(), collection1 ); + + entrySet = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet.count(), 6 ); + + newIdSet = entrySet.subtract( entrySet1 ); + QCOMPARE( (int)newIdSet.count(), 1 ); + + newId = newIdSet.values().first(); + QCOMPARE( item.remoteId(), newId ); + entrySet1 << newId; + QCOMPARE( (int)entrySet1.count(), 6 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 6 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); +} + +QTEST_KDEMAIN( ItemCreateTest, NoGUI ) + +#include "itemcreatetest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/itemdeletetest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/itemdeletetest.cpp new file mode 100644 index 00000000..94f5026d --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/itemdeletetest.cpp @@ -0,0 +1,603 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/entitycompactchangeattribute.h" +#include "filestore/itemdeletejob.h" +#include "filestore/itemfetchjob.h" +#include "filestore/storecompactjob.h" + +#include "libmaildir/maildir.h" + +#include +#include + +#include +#include + +#include + +#include + +using namespace Akonadi; + +static Collection::List collectionsFromSpy( QSignalSpy *spy ) { + Collection::List collections; + + QListIterator > it( *spy ); + while( it.hasNext() ) { + const QList invocation = it.next(); + Q_ASSERT( invocation.count() == 1 ); + + collections << invocation.first().value(); + } + + return collections; +} + +static Item::List itemsFromSpy( QSignalSpy *spy ) { + Item::List items; + + QListIterator > it( *spy ); + while( it.hasNext() ) { + const QList invocation = it.next(); + Q_ASSERT( invocation.count() == 1 ); + + items << invocation.first().value(); + } + + return items; +} + +static bool fullEntryCompare( const KMBox::MBoxEntry &a, const KMBox::MBoxEntry &b ) +{ + return a.messageOffset() == b.messageOffset() && + a.separatorSize() == b.separatorSize() && + a.messageSize() == b.messageSize(); +} + +class ItemDeleteTest : public QObject +{ + Q_OBJECT + + public: + ItemDeleteTest() : QObject(), mStore( 0 ), mDir( 0 ) { + // for monitoring signals + qRegisterMetaType(); + qRegisterMetaType(); + } + + ~ItemDeleteTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testMaildir(); + void testMBox(); + void testCachePreservation(); + void testExpectedFailure(); +}; + +void ItemDeleteTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void ItemDeleteTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void ItemDeleteTest::testMaildir() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QVERIFY( md1.isValid() ); + + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemDeleteJob *job = 0; + QSet entrySet; + QSet delIdSet; + QString delId; + + // test deleting one message + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setId( KRandom::random() ); + item1.setRemoteId( entrySet1.values().first() ); + item1.setParentCollection( collection1 ); + + job = mStore->deleteItem( item1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + QCOMPARE( item.id(), item1.id() ); + + entrySet = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet.count(), 3 ); + + delIdSet = entrySet1.subtract( entrySet ); + QCOMPARE( (int)delIdSet.count(), 1 ); + + delId = delIdSet.values().first(); + QCOMPARE( delId, entrySet1.values().first() ); + QCOMPARE( delId, item.remoteId() ); + + // test failure of deleting again + job = mStore->deleteItem( item1 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); +} + +void ItemDeleteTest::testMBox() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection1" ) ) ); + + QFileInfo fileInfo1( topDir.path(), QLatin1String( "collection1" ) ); + KMBox::MBox mbox1; + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + KMBox::MBoxEntry::List entryList1 = mbox1.entries(); + QCOMPARE( (int)entryList1.count(), 4 ); + int size1 = fileInfo1.size(); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemDeleteJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + FileStore::StoreCompactJob *storeCompact = 0; + + Item::List items; + Collection::List collections; + KMBox::MBoxEntry::List entryList; + + QSignalSpy *collectionsSpy = 0; + QSignalSpy *itemsSpy = 0; + + QVariant var; + + // test deleting last item in mbox + // file stays untouched, message still accessible through MBox, but item gone + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Item item4; + item4.setMimeType( KMime::Message::mimeType() ); + item4.setId( KRandom::random() ); + item4.setRemoteId( QString::number( entryList1.value( 3 ).messageOffset() ) ); + item4.setParentCollection( collection1 ); + + job = mStore->deleteItem( item4 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + QCOMPARE( item.id(), item4.id() ); + + fileInfo1.refresh(); + QCOMPARE( (int) fileInfo1.size(), size1 ); + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + entryList = mbox1.entries(); + QCOMPARE( entryList.count(), entryList1.count() ); + QCOMPARE( entryList.value( 3 ).messageOffset(), entryList1.value( 3 ).messageOffset() ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + itemFetch = mStore->fetchItems( collection1 ); + + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 3 ); + QCOMPARE( items.value( 0 ).remoteId(), QString::number( entryList1.value( 0 ).messageOffset() ) ); + QCOMPARE( items.value( 1 ).remoteId(), QString::number( entryList1.value( 1 ).messageOffset() ) ); + QCOMPARE( items.value( 2 ).remoteId(), QString::number( entryList1.value( 2 ).messageOffset() ) ); + + // test that the item is purged from the file on store compaction + // last item purging does not change any others + storeCompact = mStore->compactStore(); + + collectionsSpy = new QSignalSpy( storeCompact, SIGNAL(collectionsChanged(Akonadi::Collection::List)) ); + itemsSpy = new QSignalSpy( storeCompact, SIGNAL(itemsChanged(Akonadi::Item::List)) ); + + QVERIFY( storeCompact->exec() ); + QCOMPARE( storeCompact->error(), 0 ); + + collections = storeCompact->changedCollections(); + QCOMPARE( collections.count(), 0 ); + items = storeCompact->changedItems(); + QCOMPARE( items.count(), 0 ); + + QCOMPARE( collectionsFromSpy( collectionsSpy ), collections ); + QCOMPARE( itemsFromSpy( itemsSpy ), items ); + + fileInfo1.refresh(); + QVERIFY( fileInfo1.size() < size1 ); + size1 = fileInfo1.size(); + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + entryList = mbox1.entries(); + entryList1.pop_back(); + QVERIFY( std::equal( entryList1.begin(), entryList1.end(), entryList.begin(), fullEntryCompare ) ); + + // test deleting item somewhere between first and last + // again, file stays untouched, message still accessible through MBox, but item gone + Item item2; + item2.setMimeType( KMime::Message::mimeType() ); + item2.setId( KRandom::random() ); + item2.setRemoteId( QString::number( entryList1.value( 1 ).messageOffset() ) ); + item2.setParentCollection( collection1 ); + + job = mStore->deleteItem( item2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item2.id() ); + + fileInfo1.refresh(); + QCOMPARE( (int) fileInfo1.size(), size1 ); + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + entryList = mbox1.entries(); + QCOMPARE( entryList.count(), entryList1.count() ); + QCOMPARE( entryList.value( 1 ).messageOffset(), entryList1.value( 1 ).messageOffset() ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + itemFetch = mStore->fetchItems( collection1 ); + + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 2 ); + QCOMPARE( items.value( 0 ).remoteId(), QString::number( entryList1.value( 0 ).messageOffset() ) ); + QCOMPARE( items.value( 1 ).remoteId(), QString::number( entryList1.value( 2 ).messageOffset() ) ); + + // test that the item is purged from the file on store compaction + // non-last item purging changes all items after it + storeCompact = mStore->compactStore(); + + collectionsSpy = new QSignalSpy( storeCompact, SIGNAL(collectionsChanged(Akonadi::Collection::List)) ); + itemsSpy = new QSignalSpy( storeCompact, SIGNAL(itemsChanged(Akonadi::Item::List)) ); + + QVERIFY( storeCompact->exec() ); + QCOMPARE( storeCompact->error(), 0 ); + + collections = storeCompact->changedCollections(); + QCOMPARE( collections.count(), 1 ); + items = storeCompact->changedItems(); + QCOMPARE( items.count(), 1 ); + + QCOMPARE( collectionsFromSpy( collectionsSpy ), collections ); + QCOMPARE( itemsFromSpy( itemsSpy ), items ); + + Item item3; + item3.setRemoteId( QString::number( entryList1.value( 2 ).messageOffset() ) ); + + item = items.first(); + QCOMPARE( item3.remoteId(), item.remoteId() ); + + QVERIFY( item.hasAttribute() ); + FileStore::EntityCompactChangeAttribute *attribute = + item.attribute(); + + QString newRemoteId = attribute->remoteId(); + QVERIFY( !newRemoteId.isEmpty() ); + + fileInfo1.refresh(); + QVERIFY( fileInfo1.size() < size1 ); + size1 = fileInfo1.size(); + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + entryList = mbox1.entries(); + QCOMPARE( QString::number( entryList.value( 1 ).messageOffset() ), newRemoteId ); + + entryList1.removeAt( 1 ); + QCOMPARE( entryList1.count(), entryList.count() ); + QCOMPARE( QString::number( entryList1.value( 1 ).messageOffset() ), item3.remoteId() ); +} + +void ItemDeleteTest::testCachePreservation() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QVERIFY( md1.isValid() ); + + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + KMBox::MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + KMBox::MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item::List items; + QMap flagCounts; + + FileStore::ItemDeleteJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + + // test deleting from maildir + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setId( KRandom::random() ); + item1.setRemoteId( entrySet1.values().first() ); + item1.setParentCollection( collection1 ); + + job = mStore->deleteItem( item1 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + QCOMPARE( item.id(), item1.id() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection1 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 3 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + // TODO since we don't know which message we've deleted, we can only check if some flags are present + int flagCountTotal = 0; + Q_FOREACH( int count, flagCounts ) { + flagCountTotal += count; + } + QVERIFY( flagCountTotal > 0 ); + flagCounts.clear(); + + // test deleting from mbox + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Item item2; + item2.setMimeType( KMime::Message::mimeType() ); + item2.setId( KRandom::random() ); + item2.setRemoteId( QString::number( entryList2.value( 1 ).messageOffset() ) ); + item2.setParentCollection( collection2 ); + + job = mStore->deleteItem( item2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + QCOMPARE( item.id(), item2.id() ); + + // at this point no change has been written to disk yet, so index and mbox file are + // still in sync + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( !var.isValid() ); + + FileStore::StoreCompactJob *storeCompact = mStore->compactStore(); + + QVERIFY( storeCompact->exec() ); + QCOMPARE( storeCompact->error(), 0 ); + + // check for index preservation + var = storeCompact->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection2 ); + + // get the items and check the flags (see data/README) + itemFetch = mStore->fetchItems( collection2 ); + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + items = itemFetch->items(); + QCOMPARE( (int)items.count(), 3 ); + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + // we've deleted message 2, it flagged TODO and seen + QCOMPARE( flagCounts[ "\\SEEN" ], 1 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + flagCounts.clear(); +} + +void ItemDeleteTest::testExpectedFailure() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QVERIFY( md1.isValid() ); + + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + KMBox::MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + KMBox::MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemDeleteJob *job = 0; + FileStore::ItemFetchJob *itemFetch = 0; + FileStore::StoreCompactJob *storeCompact = 0; + + // test failure of fetching an item previously deleted from maildir + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Item item1_1; + item1_1.setRemoteId( entrySet1.values().first() ); + item1_1.setParentCollection( collection1 ); + + job = mStore->deleteItem( item1_1 ); + + QVERIFY( job->exec() ); + + itemFetch = mStore->fetchItem( item1_1 ); + + QVERIFY( !itemFetch->exec() ); + QCOMPARE( itemFetch->error(), (int)FileStore::Job::InvalidJobContext ); + + // test failure of deleting an item from maildir again + job = mStore->deleteItem( item1_1 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + // test failure of fetching an item previously deleted from mbox + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Item item2_1; + item2_1.setRemoteId( QString::number( entryList2.value( 0 ).messageOffset() ) ); + item2_1.setParentCollection( collection2 ); + + job = mStore->deleteItem( item2_1 ); + + QVERIFY( job->exec() ); + + itemFetch = mStore->fetchItem( item2_1 ); + + QVERIFY( !itemFetch->exec() ); + QCOMPARE( itemFetch->error(), (int)FileStore::Job::InvalidJobContext ); + + // test failure of deleting an item from mbox again + job = mStore->deleteItem( item2_1 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + // compact store and check that offset 0 is a valid remoteId again, but + // offset of other items (e.f. item 4) are no longer valid (moved to the front of the file) + storeCompact = mStore->compactStore(); + + QVERIFY( storeCompact->exec() ); + + itemFetch = mStore->fetchItem( item2_1 ); + + QVERIFY( itemFetch->exec() ); + + Item item4_1; + item4_1.setRemoteId( QString::number( entryList2.value( 3 ).messageOffset() ) ); + item4_1.setParentCollection( collection2 ); + + itemFetch = mStore->fetchItem( item4_1 ); + + QVERIFY( !itemFetch->exec() ); + QCOMPARE( itemFetch->error(), (int)FileStore::Job::InvalidJobContext ); +} + +QTEST_KDEMAIN( ItemDeleteTest, NoGUI ) + +#include "itemdeletetest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/itemfetchtest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/itemfetchtest.cpp new file mode 100644 index 00000000..5372ce58 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/itemfetchtest.cpp @@ -0,0 +1,1157 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/itemcreatejob.h" +#include "filestore/itemfetchjob.h" + +#include "libmaildir/maildir.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace Akonadi; +using namespace KMBox; + +static Item::List itemsFromSpy( QSignalSpy *spy ) { + Item::List items; + + QListIterator > it( *spy ); + while( it.hasNext() ) { + const QList invocation = it.next(); + Q_ASSERT( invocation.count() == 1 ); + + items << invocation.first().value(); + } + + return items; +} + +// copied from mail serializer plugin, Copyright (c) 2007 Till Adam +static QSet messageParts( const KMime::Message::Ptr &msgPtr ) +{ + QSet set; + // FIXME: we actually want "has any header" here, but the kmime api doesn't offer that yet + if ( msgPtr->hasContent() || msgPtr->hasHeader( "Message-ID" ) ) { + set << MessagePart::Envelope << MessagePart::Header; + if ( !msgPtr->body().isEmpty() || !msgPtr->contents().isEmpty() ) { + set << MessagePart::Body; + } + } + return set; +} + +// needed to sort maildir directory entries by filename which is their +// remoteId. tagListHash.contains tests below need sorting of entries. +static bool itemLessThanByRemoteId(const Item &item1, const Item &item2) +{ + return item1.remoteId() < item2.remoteId(); +} + +class ItemFetchTest : public QObject +{ + Q_OBJECT + + public: + ItemFetchTest() + : QObject(), mStore( 0 ), mDir( 0 ), mIndexFilePattern( QLatin1String( ".%1.index" ) ) { + // for monitoring signals + qRegisterMetaType(); + qRegisterMetaType(); + } + + ~ItemFetchTest() { + delete mStore; + delete mDir; + } + + QString indexFile( const QString &folder ) const { + return mIndexFilePattern.arg( folder ); + } + + QString indexFile( const QFileInfo &folderFileInfo ) const { + return QFileInfo( folderFileInfo.absolutePath(), + mIndexFilePattern.arg( folderFileInfo.fileName() ) ).absoluteFilePath(); + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + const QString mIndexFilePattern; + + private Q_SLOTS: + void init(); + void cleanup(); + void testListingMaildir(); + void testListingMBox(); + void testSingleItemFetchMaildir(); + void testSingleItemFetchMBox(); +}; + +void ItemFetchTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void ItemFetchTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void ItemFetchTest::testListingMaildir() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir-tagged" ), topDir.path(), QLatin1String( "collection3" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "dimap" ), topDir.path(), QLatin1String( "collection4" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir-tagged" ), topDir.path(), QLatin1String( "collection5" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + QFileInfo indexFileInfo1( indexFile( QFileInfo( md1.path() ) ) ); + QVERIFY( QFile::remove( indexFileInfo1.absoluteFilePath() ) ); + + KPIM::Maildir md2 = topLevelMd.subFolder( QLatin1String( "collection2" ) ); + QSet entrySet2 = QSet::fromList( md2.entryList() ); + QCOMPARE( (int)entrySet2.count(), 4 ); + + KPIM::Maildir md3 = topLevelMd.subFolder( QLatin1String( "collection3" ) ); + QSet entrySet3 = QSet::fromList( md3.entryList() ); + QCOMPARE( (int)entrySet3.count(), 4 ); + + KPIM::Maildir md4 = topLevelMd.subFolder( QLatin1String( "collection4" ) ); + QSet entrySet4 = QSet::fromList( md4.entryList() ); + QCOMPARE( (int)entrySet4.count(), 4 ); + + KPIM::Maildir md5 = topLevelMd.subFolder( QLatin1String( "collection5" ) ); + QSet entrySet5 = QSet::fromList( md5.entryList() ); + QCOMPARE( (int)entrySet5.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemFetchJob *job = 0; + + QSignalSpy *spy = 0; + Item::List items; + + QHash uidHash; + const QVariant varUidHash = QVariant::fromValue< QHash >( uidHash ); + QHash tagListHash; + const QVariant varTagListHash = QVariant::fromValue< QHash >( tagListHash ); + QVariant var; + + QSet entrySet; + QMap flagCounts; + + // test listing maildir without index + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection1 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + entrySet = entrySet1; + QVERIFY( entrySet.remove( items[ 0 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 1 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 2 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 3 ].remoteId() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection1 ); + QCOMPARE( items[ 1 ].parentCollection(), collection1 ); + QCOMPARE( items[ 2 ].parentCollection(), collection1 ); + QCOMPARE( items[ 3 ].parentCollection(), collection1 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + // no flags from maildir file name, no advanced flags without index + QCOMPARE( flagCounts.count(), 0 ); + QCOMPARE( flagCounts[ "\\SEEN" ], 0 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 0 ); + QCOMPARE( flagCounts[ "$TODO" ], 0 ); + flagCounts.clear(); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing empty mbox without index + Q_FOREACH( const QString &entry, entrySet1 ) { + QVERIFY( md1.removeEntry( entry ) ); + } + QCOMPARE( md1.entryList().count(), 0 ); + + job = mStore->fetchItems( collection1 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 0 ); + QCOMPARE( spy->count(), 0 ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing maildir with index + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection2 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + entrySet = entrySet2; + QVERIFY( entrySet.remove( items[ 0 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 1 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 2 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 3 ].remoteId() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection2 ); + QCOMPARE( items[ 1 ].parentCollection(), collection2 ); + QCOMPARE( items[ 2 ].parentCollection(), collection2 ); + QCOMPARE( items[ 3 ].parentCollection(), collection2 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + // see data/README + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing empty maildir with index + Q_FOREACH( const QString &entry, entrySet2 ) { + QVERIFY( md2.removeEntry( entry ) ); + } + QCOMPARE( md2.entryList().count(), 0 ); + + job = mStore->fetchItems( collection2 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 0 ); + QCOMPARE( spy->count(), 0 ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing maildir with index which has tags + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection3 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + entrySet = entrySet3; + QVERIFY( entrySet.remove( items[ 0 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 1 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 2 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 3 ].remoteId() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection3 ); + QCOMPARE( items[ 1 ].parentCollection(), collection3 ); + QCOMPARE( items[ 2 ].parentCollection(), collection3 ); + QCOMPARE( items[ 3 ].parentCollection(), collection3 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + // see data/README + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + + // 2x \SEEN flags: 2x from index, none from file name + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( var.isValid() ); + + // tagListHash.contains tests below needs sorting of entries, + // but libmaildir does not sort for performance reasons. + // TODO: Check should not depend on any specific ordering. + qSort(items.begin(), items.end(), itemLessThanByRemoteId); + + tagListHash = var.value< QHash >(); + QCOMPARE( (int)tagListHash.count(), 3 ); + QVERIFY( !tagListHash.contains( items[ 0 ].remoteId() ) ); + QVERIFY( !tagListHash.value( items[ 1 ].remoteId() ).toString().isEmpty() ); + QVERIFY( !tagListHash.value( items[ 2 ].remoteId() ).toString().isEmpty() ); + QVERIFY( !tagListHash.value( items[ 3 ].remoteId() ).toString().isEmpty() ); + + // test listing maildir with index which contains IMAP UIDs (dimap cache directory) + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection4 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + entrySet = entrySet4; + QVERIFY( entrySet.remove( items[ 0 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 1 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 2 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 3 ].remoteId() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection4 ); + QCOMPARE( items[ 1 ].parentCollection(), collection4 ); + QCOMPARE( items[ 2 ].parentCollection(), collection4 ); + QCOMPARE( items[ 3 ].parentCollection(), collection4 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + // see data/README + Q_FOREACH( const Item &item, items ) { + Q_FOREACH( const QByteArray &flag, item.flags() ) { + ++flagCounts[ flag ]; + } + } + QCOMPARE( flagCounts[ "\\SEEN" ], 2 ); + QCOMPARE( flagCounts[ "\\FLAGGED" ], 1 ); + QCOMPARE( flagCounts[ "$TODO" ], 1 ); + flagCounts.clear(); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + var = job->property( "remoteIdToIndexUid" ); + QVERIFY( var.isValid() ); + + uidHash = var.value< QHash >(); + QCOMPARE( (int)uidHash.count(), 4 ); + bool ok = false; + QVERIFY( !uidHash.value( items[ 0 ].remoteId() ).toString().toInt( &ok) >= 0 && ok ); + QVERIFY( !uidHash.value( items[ 1 ].remoteId() ).toString().toInt( &ok) >= 0 && ok ); + QVERIFY( !uidHash.value( items[ 2 ].remoteId() ).toString().toInt( &ok) >= 0 && ok ); + QVERIFY( !uidHash.value( items[ 3 ].remoteId() ).toString().toInt( &ok) >= 0 && ok ); + + // test listing maildir with index but newer modification date than index's one + const QByteArray data5 = md5.readEntry( entrySet5.values().first() ); + QVERIFY( !data5.isEmpty() ); + + QTest::qSleep( 1000 ); + + QString newRemoteId = md5.addEntry( data5 ); + QVERIFY( !newRemoteId.isEmpty() ); + + entrySet = QSet::fromList( md5.entryList() ); + entrySet.remove( newRemoteId ); + QCOMPARE( entrySet, entrySet5 ); + QFileInfo fileInfo5( md5.path(), QLatin1String( "new" ) ); + QFileInfo indexFileInfo5 = indexFile( QFileInfo( md5.path() ) ); + QVERIFY( fileInfo5.lastModified() > indexFileInfo5.lastModified() ); + + Collection collection5; + collection5.setName( QLatin1String( "collection5" ) ); + collection5.setRemoteId( QLatin1String( "collection5" ) ); + collection5.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection5 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 5 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + entrySet = entrySet5; + entrySet << newRemoteId; + QVERIFY( entrySet.remove( items[ 0 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 1 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 2 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 3 ].remoteId() ) ); + QVERIFY( entrySet.remove( items[ 4 ].remoteId() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection5 ); + QCOMPARE( items[ 1 ].parentCollection(), collection5 ); + QCOMPARE( items[ 2 ].parentCollection(), collection5 ); + QCOMPARE( items[ 3 ].parentCollection(), collection5 ); + QCOMPARE( items[ 4 ].parentCollection(), collection5 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + QVERIFY( !items[ 4 ].hasPayload() ); + + // not flags from index, no flags from file names + QCOMPARE( items[ 0 ].flags(), QSet() ); + QCOMPARE( items[ 1 ].flags(), QSet() ); + QCOMPARE( items[ 2 ].flags(), QSet() ); + QCOMPARE( items[ 3 ].flags(), QSet() ); + QCOMPARE( items[ 4 ].flags(), QSet() ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); +} + +void ItemFetchTest::testListingMBox() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox-tagged" ), topDir.path(), QLatin1String( "collection3" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox-unpurged" ), topDir.path(), QLatin1String( "collection4" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox-tagged" ), topDir.path(), QLatin1String( "collection5" ) ) ); + + QFileInfo fileInfo1( topDir.path(), QLatin1String( "collection1" ) ); + MBox mbox1; + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + MBoxEntry::List entryList1 = mbox1.entries(); + QCOMPARE( (int)entryList1.count(), 4 ); + + QFileInfo indexFileInfo1 = indexFile( fileInfo1 ); + QVERIFY( QFile::remove( indexFileInfo1.absoluteFilePath() ) ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + QFileInfo fileInfo3( topDir.path(), QLatin1String( "collection3" ) ); + MBox mbox3; + QVERIFY( mbox3.load( fileInfo3.absoluteFilePath() ) ); + MBoxEntry::List entryList3 = mbox3.entries(); + QCOMPARE( (int)entryList3.count(), 4 ); + + QFileInfo fileInfo4( topDir.path(), QLatin1String( "collection4" ) ); + MBox mbox4; + QVERIFY( mbox4.load( fileInfo4.absoluteFilePath() ) ); + MBoxEntry::List entryList4 = mbox4.entries(); + QCOMPARE( (int)entryList4.count(), 4 ); + + QFileInfo fileInfo5( topDir.path(), QLatin1String( "collection5" ) ); + MBox mbox5; + QVERIFY( mbox5.load( fileInfo5.absoluteFilePath() ) ); + MBoxEntry::List entryList5 = mbox5.entries(); + QCOMPARE( (int)entryList5.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemFetchJob *job = 0; + + QSignalSpy *spy = 0; + Item::List items; + + QHash tagListHash; + const QVariant varTagListHash = QVariant::fromValue< QHash >( tagListHash ); + QVariant var; + + // test listing mbox without index + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection1 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + QCOMPARE( items[ 0 ].remoteId(), QString::number( entryList1[ 0 ].messageOffset() ) ); + QCOMPARE( items[ 1 ].remoteId(), QString::number( entryList1[ 1 ].messageOffset() ) ); + QCOMPARE( items[ 2 ].remoteId(), QString::number( entryList1[ 2 ].messageOffset() ) ); + QCOMPARE( items[ 3 ].remoteId(), QString::number( entryList1[ 3 ].messageOffset() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection1 ); + QCOMPARE( items[ 1 ].parentCollection(), collection1 ); + QCOMPARE( items[ 2 ].parentCollection(), collection1 ); + QCOMPARE( items[ 3 ].parentCollection(), collection1 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + QCOMPARE( items[ 0 ].flags(), QSet() ); + QCOMPARE( items[ 1 ].flags(), QSet() ); + QCOMPARE( items[ 2 ].flags(), QSet() ); + QCOMPARE( items[ 3 ].flags(), QSet() ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing empty mbox without index + QFile file1( fileInfo1.absoluteFilePath() ); + QVERIFY( file1.open( QIODevice::WriteOnly | QIODevice::Truncate ) ); + file1.close(); + QCOMPARE( (int)file1.size(), 0 ); + + job = mStore->fetchItems( collection1 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 0 ); + QCOMPARE( spy->count(), 0 ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing mbox with index + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection2 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + QCOMPARE( items[ 0 ].remoteId(), QString::number( entryList2[ 0 ].messageOffset() ) ); + QCOMPARE( items[ 1 ].remoteId(), QString::number( entryList2[ 1 ].messageOffset() ) ); + QCOMPARE( items[ 2 ].remoteId(), QString::number( entryList2[ 2 ].messageOffset() ) ); + QCOMPARE( items[ 3 ].remoteId(), QString::number( entryList2[ 3 ].messageOffset() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection2 ); + QCOMPARE( items[ 1 ].parentCollection(), collection2 ); + QCOMPARE( items[ 2 ].parentCollection(), collection2 ); + QCOMPARE( items[ 3 ].parentCollection(), collection2 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + // see data/README + QCOMPARE( items[ 0 ].flags(), QSet() ); + QCOMPARE( items[ 1 ].flags(), QSet() << "\\SEEN" << "$TODO" ); + QCOMPARE( items[ 2 ].flags(), QSet() ); + QCOMPARE( items[ 3 ].flags(), QSet() << "\\SEEN" << "\\FLAGGED" ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing empty mbox with index + QFile file2( fileInfo2.absoluteFilePath() ); + QVERIFY( file2.open( QIODevice::WriteOnly | QIODevice::Truncate ) ); + file2.close(); + QCOMPARE( (int)file2.size(), 0 ); + + job = mStore->fetchItems( collection2 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 0 ); + QCOMPARE( spy->count(), 0 ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing mbox with index which has tags + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection3 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + QCOMPARE( items[ 0 ].remoteId(), QString::number( entryList3[ 0 ].messageOffset() ) ); + QCOMPARE( items[ 1 ].remoteId(), QString::number( entryList3[ 1 ].messageOffset() ) ); + QCOMPARE( items[ 2 ].remoteId(), QString::number( entryList3[ 2 ].messageOffset() ) ); + QCOMPARE( items[ 3 ].remoteId(), QString::number( entryList3[ 3 ].messageOffset() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection3 ); + QCOMPARE( items[ 1 ].parentCollection(), collection3 ); + QCOMPARE( items[ 2 ].parentCollection(), collection3 ); + QCOMPARE( items[ 3 ].parentCollection(), collection3 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + // see data/README + QCOMPARE( items[ 0 ].flags(), QSet() ); + QCOMPARE( items[ 1 ].flags(), QSet() << "\\SEEN" << "$TODO" ); + QCOMPARE( items[ 2 ].flags(), QSet() ); + QCOMPARE( items[ 3 ].flags(), QSet() << "\\SEEN" << "\\FLAGGED" ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( var.isValid() ); + + tagListHash = var.value< QHash >(); + QCOMPARE( (int)tagListHash.count(), 3 ); + QVERIFY( !tagListHash.value( items[ 0 ].remoteId() ).toString().isEmpty() ); + QVERIFY( !tagListHash.contains( items[ 1 ].remoteId() ) ); + QVERIFY( !tagListHash.value( items[ 2 ].remoteId() ).toString().isEmpty() ); + QVERIFY( !tagListHash.value( items[ 3 ].remoteId() ).toString().isEmpty() ); + + // test listing mbox with index and unpurged messages (in mbox but not in index) + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection4 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + QCOMPARE( items[ 0 ].remoteId(), QString::number( entryList4[ 0 ].messageOffset() ) ); + QCOMPARE( items[ 1 ].remoteId(), QString::number( entryList4[ 1 ].messageOffset() ) ); + QCOMPARE( items[ 2 ].remoteId(), QString::number( entryList4[ 2 ].messageOffset() ) ); + QCOMPARE( items[ 3 ].remoteId(), QString::number( entryList4[ 3 ].messageOffset() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection4 ); + QCOMPARE( items[ 1 ].parentCollection(), collection4 ); + QCOMPARE( items[ 2 ].parentCollection(), collection4 ); + QCOMPARE( items[ 3 ].parentCollection(), collection4 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + // see data/README + QCOMPARE( items[ 0 ].flags(), QSet() << "\\SEEN" ); + QCOMPARE( items[ 1 ].flags(), QSet() << "\\DELETED" ); + QCOMPARE( items[ 2 ].flags(), QSet() << "\\DELETED" ); + QCOMPARE( items[ 3 ].flags(), QSet() << "\\SEEN" ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test listing mbox with index but newer modification date than index's one + QFile file5( fileInfo5.absoluteFilePath() ); + QVERIFY( file5.open( QIODevice::ReadOnly ) ); + const QByteArray data5 = file5.readAll(); + file5.close(); + + QTest::qSleep( 1000 ); + + QVERIFY( file5.open( QIODevice::WriteOnly ) ); + file5.write( data5 ); + file5.close(); + + QCOMPARE( file5.size(), fileInfo5.size() ); + fileInfo5.refresh(); + QFileInfo indexFileInfo5 = indexFile( fileInfo5 ); + QVERIFY( fileInfo5.lastModified() > indexFileInfo5.lastModified() ); + + Collection collection5; + collection5.setName( QLatin1String( "collection5" ) ); + collection5.setRemoteId( QLatin1String( "collection5" ) ); + collection5.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->fetchItems( collection5 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 4 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + QCOMPARE( items[ 0 ].remoteId(), QString::number( entryList5[ 0 ].messageOffset() ) ); + QCOMPARE( items[ 1 ].remoteId(), QString::number( entryList5[ 1 ].messageOffset() ) ); + QCOMPARE( items[ 2 ].remoteId(), QString::number( entryList5[ 2 ].messageOffset() ) ); + QCOMPARE( items[ 3 ].remoteId(), QString::number( entryList5[ 3 ].messageOffset() ) ); + + QCOMPARE( items[ 0 ].parentCollection(), collection5 ); + QCOMPARE( items[ 1 ].parentCollection(), collection5 ); + QCOMPARE( items[ 2 ].parentCollection(), collection5 ); + QCOMPARE( items[ 3 ].parentCollection(), collection5 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + + QCOMPARE( items[ 0 ].flags(), QSet() ); + QCOMPARE( items[ 1 ].flags(), QSet() ); + QCOMPARE( items[ 2 ].flags(), QSet() ); + QCOMPARE( items[ 3 ].flags(), QSet() ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( !var.isValid() ); + + // test that a new message in an mbox with index it not marked as deleted + KMime::Message::Ptr msgPtr( new KMime::Message ); + msgPtr->subject()->from7BitString( "Test 5" ); + msgPtr->to()->from7BitString( "kevin.krammer@gmx.at" ); + msgPtr->assemble(); + + Item item3_5; + item3_5.setMimeType( KMime::Message::mimeType() ); + item3_5.setPayload( msgPtr ); + + FileStore::ItemCreateJob *itemCreate = mStore->createItem( item3_5, collection3 ); + QVERIFY( itemCreate->exec() ); + + item3_5 = itemCreate->item(); + QVERIFY( !item3_5.remoteId().isEmpty() ); + + job = mStore->fetchItems( collection3 ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 5 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + QCOMPARE( items[ 0 ].remoteId(), QString::number( entryList3[ 0 ].messageOffset() ) ); + QCOMPARE( items[ 1 ].remoteId(), QString::number( entryList3[ 1 ].messageOffset() ) ); + QCOMPARE( items[ 2 ].remoteId(), QString::number( entryList3[ 2 ].messageOffset() ) ); + QCOMPARE( items[ 3 ].remoteId(), QString::number( entryList3[ 3 ].messageOffset() ) ); + QCOMPARE( items[ 4 ].remoteId(), item3_5.remoteId() ); + + QCOMPARE( items[ 0 ].parentCollection(), collection3 ); + QCOMPARE( items[ 1 ].parentCollection(), collection3 ); + QCOMPARE( items[ 2 ].parentCollection(), collection3 ); + QCOMPARE( items[ 3 ].parentCollection(), collection3 ); + QCOMPARE( items[ 4 ].parentCollection(), collection3 ); + + QVERIFY( !items[ 0 ].hasPayload() ); + QVERIFY( !items[ 1 ].hasPayload() ); + QVERIFY( !items[ 2 ].hasPayload() ); + QVERIFY( !items[ 3 ].hasPayload() ); + QVERIFY( !items[ 4 ].hasPayload() ); + + // see data/README + QCOMPARE( items[ 0 ].flags(), QSet() ); + QCOMPARE( items[ 1 ].flags(), QSet() << "\\SEEN" << "$TODO" ); + QCOMPARE( items[ 2 ].flags(), QSet() ); + QCOMPARE( items[ 3 ].flags(), QSet() << "\\SEEN" << "\\FLAGGED" ); + QCOMPARE( items[ 4 ].flags(), QSet() ); + + var = job->property( "remoteIdToTagList" ); + QVERIFY( var.isValid() ); +} + +void ItemFetchTest::testSingleItemFetchMaildir() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QStringList entryList1 = md1.entryList(); + QCOMPARE( (int)entryList1.count(), 4 ); + + KRandomSequence randomSequence; + QStringList randomList1 = entryList1; + randomSequence.randomize( randomList1 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemFetchJob *job = 0; + + QSignalSpy *spy = 0; + Item::List items; + + // test fetching from maildir, headers only + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Q_FOREACH( const QString &entry, randomList1 ) { + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( entry ); + item1.setParentCollection( collection1 ); + + job = mStore->fetchItem( item1 ); + job->fetchScope().fetchPayloadPart( MessagePart::Header ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 1 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + Item item = items.first(); + QCOMPARE( item, item1 ); + QVERIFY( item.hasPayload() ); + + KMime::Message::Ptr msgPtr = item.payload(); + QVERIFY( msgPtr != 0 ); + + const QSet parts = messageParts( msgPtr ); + QVERIFY( !parts.isEmpty() ); + QVERIFY( parts.contains( MessagePart::Header ) ); + QVERIFY( !parts.contains( MessagePart::Body ) ); + } + + // test fetching from maildir, including body + randomSequence.randomize( randomList1 ); + Q_FOREACH( const QString &entry, randomList1 ) { + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( entry ); + item1.setParentCollection( collection1 ); + + job = mStore->fetchItem( item1 ); + job->fetchScope().fetchPayloadPart( MessagePart::Header ); + job->fetchScope().fetchPayloadPart( MessagePart::Body ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 1 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + Item item = items.first(); + QCOMPARE( item, item1 ); + QVERIFY( item.hasPayload() ); + + KMime::Message::Ptr msgPtr = item.payload(); + QVERIFY( msgPtr != 0 ); + + const QSet parts = messageParts( msgPtr ); + QVERIFY( !parts.isEmpty() ); + QVERIFY( parts.contains( MessagePart::Header ) ); + QVERIFY( parts.contains( MessagePart::Body ) ); + } + + // test fetching from maildir, just specifying full payload + randomSequence.randomize( randomList1 ); + Q_FOREACH( const QString &entry, randomList1 ) { + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( entry ); + item1.setParentCollection( collection1 ); + + job = mStore->fetchItem( item1 ); + job->fetchScope().fetchFullPayload( true ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 1 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + Item item = items.first(); + QCOMPARE( item, item1 ); + QVERIFY( item.hasPayload() ); + + KMime::Message::Ptr msgPtr = item.payload(); + QVERIFY( msgPtr != 0 ); + + const QSet parts = messageParts( msgPtr ); + QVERIFY( !parts.isEmpty() ); + QVERIFY( parts.contains( MessagePart::Header ) ); + QVERIFY( parts.contains( MessagePart::Body ) ); + } +} + +void ItemFetchTest::testSingleItemFetchMBox() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection1" ) ) ); + // one message has no body + const QByteArray messageIdOfEmptyBodyMsg = "201007241551.37547.kevin.krammer@demo.kolab.org"; + + QFileInfo fileInfo1( topDir.path(), QLatin1String( "collection1" ) ); + MBox mbox1; + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + MBoxEntry::List entryList1 = mbox1.entries(); + QCOMPARE( (int)entryList1.count(), 4 ); + + KRandomSequence randomSequence; + MBoxEntry::List randomList1 = entryList1; + randomSequence.randomize( randomList1 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemFetchJob *job = 0; + + QSignalSpy *spy = 0; + Item::List items; + + // test fetching from mbox, headers only + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Q_FOREACH( const MBoxEntry &entry, randomList1 ) { + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( QString::number( entry.messageOffset() ) ); + item1.setParentCollection( collection1 ); + + job = mStore->fetchItem( item1 ); + job->fetchScope().fetchPayloadPart( MessagePart::Header ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 1 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + Item item = items.first(); + QCOMPARE( item, item1 ); + QVERIFY( item.hasPayload() ); + + KMime::Message::Ptr msgPtr = item.payload(); + QVERIFY( msgPtr != 0 ); + + const QSet parts = messageParts( msgPtr ); + QVERIFY( !parts.isEmpty() ); + QVERIFY( parts.contains( MessagePart::Header ) ); + QVERIFY( !parts.contains( MessagePart::Body ) ); + } + + // test fetching from mbox, including body + randomSequence.randomize( randomList1 ); + Q_FOREACH( const MBoxEntry &entry, randomList1 ) { + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( QString::number( entry.messageOffset() ) ); + item1.setParentCollection( collection1 ); + + job = mStore->fetchItem( item1 ); + job->fetchScope().fetchPayloadPart( MessagePart::Header ); + job->fetchScope().fetchPayloadPart( MessagePart::Body ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 1 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + Item item = items.first(); + QCOMPARE( item, item1 ); + QVERIFY( item.hasPayload() ); + + KMime::Message::Ptr msgPtr = item.payload(); + QVERIFY( msgPtr != 0 ); + + const QSet parts = messageParts( msgPtr ); + qDebug() << msgPtr->messageID()->identifier(); + QVERIFY( !parts.isEmpty() ); + QVERIFY( parts.contains( MessagePart::Header ) ); + if ( msgPtr->messageID()->identifier() == messageIdOfEmptyBodyMsg ) + QVERIFY( !parts.contains( MessagePart::Body ) ); + else + QVERIFY( parts.contains( MessagePart::Body ) ); + } + + // test fetching from mbox, just specifying full payload + randomSequence.randomize( randomList1 ); + Q_FOREACH( const MBoxEntry &entry, randomList1 ) { + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( QString::number( entry.messageOffset() ) ); + item1.setParentCollection( collection1 ); + + job = mStore->fetchItem( item1 ); + job->fetchScope().fetchFullPayload( true ); + + spy = new QSignalSpy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + items = job->items(); + QCOMPARE( (int)items.count(), 1 ); + QCOMPARE( itemsFromSpy( spy ), items ); + + Item item = items.first(); + QCOMPARE( item, item1 ); + QVERIFY( item.hasPayload() ); + + KMime::Message::Ptr msgPtr = item.payload(); + QVERIFY( msgPtr != 0 ); + + const QSet parts = messageParts( msgPtr ); + QVERIFY( !parts.isEmpty() ); + QVERIFY( parts.contains( MessagePart::Header ) ); + if ( msgPtr->messageID()->identifier() == messageIdOfEmptyBodyMsg ) + QVERIFY( !parts.contains( MessagePart::Body ) ); + else + QVERIFY( parts.contains( MessagePart::Body ) ); + } +} + +QTEST_KDEMAIN( ItemFetchTest, NoGUI ) + +#include "itemfetchtest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/itemmodifytest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/itemmodifytest.cpp new file mode 100644 index 00000000..4653a359 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/itemmodifytest.cpp @@ -0,0 +1,670 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + Copyright (C) 2011 Kevin Krammer, kevin.krammer@gmx.at + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/itemfetchjob.h" +#include "filestore/itemmodifyjob.h" +#include "filestore/storecompactjob.h" + +#include "libmaildir/maildir.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +using namespace Akonadi; +using namespace KMBox; + +static bool fullEntryCompare( const MBoxEntry &a, const MBoxEntry &b ) +{ + return a.messageOffset() == b.messageOffset() && + a.separatorSize() == b.separatorSize() && + a.messageSize() == b.messageSize(); +} + +class ItemModifyTest : public QObject +{ + Q_OBJECT + + public: + ItemModifyTest() + : QObject(), mStore( 0 ), mDir( 0 ) {} + + ~ItemModifyTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testExpectedFail(); + void testIgnorePayload(); + void testModifyPayload(); + void testModifyFlags(); + void testModifyFlagsAndPayload(); +}; + +void ItemModifyTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void ItemModifyTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void ItemModifyTest::testExpectedFail() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + QSet entrySet2; + Q_FOREACH( const MBoxEntry &entry, entryList2 ) { + entrySet2 << entry.messageOffset(); + } + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemModifyJob *job = 0; + + // test failure of modifying a non-existant maildir entry + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + QString remoteId1; + do { + remoteId1 = KRandom::randomString( 20 ); + } while ( entrySet1.contains( remoteId1 ) ); + + KMime::Message::Ptr msgPtr( new KMime::Message ); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( remoteId1 ); + item1.setParentCollection( collection1 ); + item1.setPayload( msgPtr ); + + job = mStore->modifyItem( item1 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QSet entrySet = QSet::fromList( md1.entryList() ); + QCOMPARE( entrySet, entrySet1 ); + + // test failure of modifying a non-existant mbox entry + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + qint64 remoteId2; + do { + remoteId2 = KRandom::random(); + } while ( entrySet2.contains( remoteId2 ) ); + + Item item2; + item2.setMimeType( KMime::Message::mimeType() ); + item2.setRemoteId( QString::number( remoteId2 ) ); + item2.setParentCollection( collection2 ); + item2.setPayload( msgPtr ); + + job = mStore->modifyItem( item2 ); + + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QVERIFY( mbox2.load( mbox2.fileName() ) ); + MBoxEntry::List entryList = mbox2.entries(); + QVERIFY( std::equal( entryList.begin(), entryList.end(), entryList2.begin(), fullEntryCompare ) ); +} + +void ItemModifyTest::testIgnorePayload() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QStringList entryList1 = md1.entryList(); + QCOMPARE( (int)entryList1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemModifyJob *job = 0; + + // test failure of modifying a non-existant maildir entry + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + const QByteArray data1 = md1.readEntry( entryList1.first() ); + + KMime::Message::Ptr msgPtr( new KMime::Message ); + msgPtr->subject()->from7BitString( "Modify Test" ); + msgPtr->assemble(); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( entryList1.first() ); + item1.setParentCollection( collection1 ); + item1.setPayload( msgPtr ); + + job = mStore->modifyItem( item1 ); + job->setIgnorePayload( true ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QCOMPARE( md1.entryList(), entryList1 ); + QCOMPARE( md1.readEntry( entryList1.first() ), data1 ); + + // test failure of modifying a non-existant mbox entry + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + const QByteArray data2 = mbox2.readRawMessage( MBoxEntry( 0 ) ); + + Item item2; + item2.setMimeType( KMime::Message::mimeType() ); + item2.setRemoteId( QLatin1String( "0" ) ); + item2.setParentCollection( collection2 ); + item2.setPayload( msgPtr ); + + job = mStore->modifyItem( item2 ); + job->setIgnorePayload( true ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QVERIFY( mbox2.load( mbox2.fileName() ) ); + QCOMPARE( mbox2.entries(), entryList2 ); + QCOMPARE( mbox2.readRawMessage( MBoxEntry( 0 ) ), data2 ); +} + +void ItemModifyTest::testModifyPayload() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QStringList entryList1 = md1.entryList(); + QCOMPARE( (int)entryList1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemModifyJob *job = 0; + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + + // test modifying a maildir entry's header + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + const QByteArray data1 = md1.readEntry( entryList1.first() ); + + KMime::Message::Ptr msgPtr( new KMime::Message ); + msgPtr->setContent( KMime::CRLFtoLF( data1 ) ); + msgPtr->subject()->from7BitString( "Modify Test" ); + msgPtr->assemble(); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( entryList1.first() ); + item1.setParentCollection( collection1 ); + item1.setPayload( msgPtr ); + + job = mStore->modifyItem( item1 ); + // changing subject, so indicate a header change + job->setParts( QSet() << QByteArray( "PLD:" ) + MessagePart::Header ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + QCOMPARE( md1.entryList(), entryList1 ); + + QCOMPARE( md1.readEntry( entryList1.first() ).size(), + msgPtr->encodedContent().size() ); + QCOMPARE( md1.readEntry( entryList1.first() ), msgPtr->encodedContent() ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // test modifying an mbox entry's header + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + const QByteArray data2 = mbox2.readRawMessage( MBoxEntry( 0 ) ); + + msgPtr->setContent( KMime::CRLFtoLF( data2 ) ); + msgPtr->subject()->from7BitString( "Modify Test" ); + msgPtr->assemble(); + + Item item2; + item2.setMimeType( KMime::Message::mimeType() ); + item2.setRemoteId( QLatin1String( "0" ) ); + item2.setParentCollection( collection2 ); + item2.setPayload( msgPtr ); + + job = mStore->modifyItem( item2 ); + // changing subject, so indicate a header change + job->setParts( QSet() << QByteArray( "PLD:" ) + MessagePart::Header ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + + QVERIFY( mbox2.load( mbox2.fileName() ) ); + MBoxEntry::List entryList = mbox2.entries(); + QCOMPARE( (int)entryList.count(), 5 ); // mbox file not purged yet + QCOMPARE( entryList.last().messageOffset(), item.remoteId().toULongLong() ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection2 ); + + FileStore::ItemFetchJob *itemFetch = mStore->fetchItem( item2 ); + QVERIFY( !itemFetch->exec() ); // item at old offset gone + + FileStore::StoreCompactJob *storeCompact = mStore->compactStore(); + QVERIFY( storeCompact->exec() ); + + QVERIFY( mbox2.load( mbox2.fileName() ) ); + entryList = mbox2.entries(); + QCOMPARE( (int)entryList.count(), 4 ); + + QCOMPARE( mbox2.readRawMessage( entryList.last() ), msgPtr->encodedContent() ); +} + +void ItemModifyTest::testModifyFlags() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QStringList entryList1 = md1.entryList(); + QCOMPARE( (int)entryList1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + QCryptographicHash cryptoHash( QCryptographicHash::Sha1 ); + + QFile file2( fileInfo2.absoluteFilePath() ); + QVERIFY( file2.open( QIODevice::ReadOnly ) ); + cryptoHash.addData( file2.readAll() ); + const QByteArray mbox2Sha1 = cryptoHash.result(); + + file2.close(); + cryptoHash.reset(); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemModifyJob *job = 0; + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + KMime::Message::Ptr msgPtr( new KMime::Message ); + + // test modifying a flag of a maildir items changes the entry name but not the + // message contents + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + // check that the \SEEN flag is not set yet + QVERIFY( !md1.readEntryFlags( entryList1.first() ).contains( "\\SEEN" ) ); + + const QByteArray data1 = md1.readEntry( entryList1.first() ); + + msgPtr->setContent( KMime::CRLFtoLF( data1 ) ); + msgPtr->subject()->from7BitString( "Modify Test" ); + msgPtr->assemble(); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( entryList1.first() ); + item1.setParentCollection( collection1 ); + item1.setPayload( msgPtr ); + item1.setFlag( "\\SEEN" ); + + job = mStore->modifyItem( item1 ); + // setting \SEEN, so indicate a flags change + job->setParts( QSet() << "FLAGS" ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + + // returned item should contain the change + QVERIFY( item.flags().contains( "\\SEEN" ) ); + + // remote ID has changed + QVERIFY( item.remoteId() != entryList1.first() ); + QVERIFY( !md1.entryList().contains( entryList1.first() ) ); + + // no change in number of entries, one difference + QStringList entryList3 = md1.entryList(); + QCOMPARE( entryList3.count(), entryList1.count() ); + Q_FOREACH( const QString &oldEntry, entryList1 ) { + entryList3.removeAll( oldEntry ); + } + QCOMPARE( entryList3.count(), 1 ); + + // no change to data + QCOMPARE( md1.readEntry( entryList3.first() ), data1 ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // fetch new item, check flag + item1 = Item(); + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( entryList3.first() ); + item1.setParentCollection( collection1 ); + + FileStore::ItemFetchJob *itemFetch = mStore->fetchItem( item1 ); + + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + QCOMPARE( itemFetch->items().count(), 1 ); + QEXPECT_FAIL( "", "ItemFetch handling needs to be fixed to also fetch flags", Continue ); + QVERIFY( itemFetch->items()[ 0 ].flags().contains( "\\SEEN" ) ); + + // test modifying flags of an mbox item "succeeds" (no error) but does not change + // anything in store or on disk + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + const QByteArray data2 = mbox2.readRawMessage( MBoxEntry( 0 ) ); + + msgPtr->setContent( KMime::CRLFtoLF( data2 ) ); + msgPtr->subject()->from7BitString( "Modify Test" ); + msgPtr->assemble(); + + Item item2; + item2.setMimeType( KMime::Message::mimeType() ); + item2.setRemoteId( QLatin1String( "0" ) ); + item2.setParentCollection( collection2 ); + item2.setPayload( msgPtr ); + item2.setFlag( "\\SEEN" ); + + job = mStore->modifyItem( item2 ); + // setting \SEEN, so indicate a flags change + job->setParts( QSet() << "FLAGS" ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + item = job->item(); + + // returned item should contain the change + QVERIFY( item.flags().contains( "\\SEEN" ) ); + + // mbox not changed + QVERIFY( mbox2.load( mbox2.fileName() ) ); + MBoxEntry::List entryList = mbox2.entries(); + QCOMPARE( (int)entryList.count(), 4 ); + + var = job->property( "compactStore" ); + QVERIFY( !var.isValid() ); + + // file not modified + QVERIFY( file2.open( QIODevice::ReadOnly ) ); + cryptoHash.addData( file2.readAll() ); + QCOMPARE( cryptoHash.result(), mbox2Sha1 ); + + file2.close(); + cryptoHash.reset(); + + // check index preservation is not triggered + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( !var.isValid() ); + +} + + +void ItemModifyTest::testModifyFlagsAndPayload() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QStringList entryList1 = md1.entryList(); + QCOMPARE( (int)entryList1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemModifyJob *job = 0; + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + KMime::Message::Ptr msgPtr( new KMime::Message ); + + // test modifying a flag of a maildir items changes the entry name but not the + // message contents + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + // check that the \SEEN flag is not set yet + QVERIFY( !md1.readEntryFlags( entryList1.first() ).contains( "\\SEEN" ) ); + + const QByteArray data1 = md1.readEntry( entryList1.first() ); + + msgPtr->setContent( KMime::CRLFtoLF( data1 ) ); + msgPtr->subject()->from7BitString( "Modify Test" ); + msgPtr->assemble(); + + Item item1; + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( entryList1.first() ); + item1.setParentCollection( collection1 ); + item1.setPayload( msgPtr ); + item1.setFlag( "\\SEEN" ); + + job = mStore->modifyItem( item1 ); + // setting \SEEN so indicate a flags change and + // setting new subject so indicate a payload change + job->setParts( QSet() << "FLAGS" + << QByteArray( "PLD:" ) + MessagePart::Header ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + Item item = job->item(); + + // returned item should contain the change + QVERIFY( item.flags().contains( "\\SEEN" ) ); + + // remote ID has changed + QVERIFY( item.remoteId() != entryList1.first() ); + QVERIFY( !md1.entryList().contains( entryList1.first() ) ); + + // no change in number of entries, one difference + QStringList entryList3 = md1.entryList(); + QCOMPARE( entryList3.count(), entryList1.count() ); + Q_FOREACH( const QString &oldEntry, entryList1 ) { + entryList3.removeAll( oldEntry ); + } + QCOMPARE( entryList3.count(), 1 ); + + // data changed + QCOMPARE( md1.readEntry( entryList3.first() ), msgPtr->encodedContent() ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // fetch new item, check flag + item1 = Item(); + item1.setMimeType( KMime::Message::mimeType() ); + item1.setRemoteId( entryList3.first() ); + item1.setParentCollection( collection1 ); + + FileStore::ItemFetchJob *itemFetch = mStore->fetchItem( item1 ); + itemFetch->fetchScope().fetchFullPayload(); + + QVERIFY( itemFetch->exec() ); + QCOMPARE( itemFetch->error(), 0 ); + + QCOMPARE( itemFetch->items().count(), 1 ); + Item fetchedItem = itemFetch->items().first(); + QEXPECT_FAIL( "", "ItemFetch handling needs to be fixed to also fetch flags", Continue ); + QVERIFY( fetchedItem.flags().contains( "\\SEEN" ) ); + + QVERIFY( fetchedItem.hasPayload() ); + KMime::Message::Ptr fetchedMsgPtr = fetchedItem.payload(); + QCOMPARE( msgPtr->encodedContent(), fetchedMsgPtr->encodedContent() ); + + + // TODO test for mbox. +} + +QTEST_KDEMAIN( ItemModifyTest, NoGUI ) + +#include "itemmodifytest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/itemmovetest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/itemmovetest.cpp new file mode 100644 index 00000000..687d474a --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/itemmovetest.cpp @@ -0,0 +1,673 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/entitycompactchangeattribute.h" +#include "filestore/itemfetchjob.h" +#include "filestore/itemmovejob.h" +#include "filestore/storecompactjob.h" + +#include "libmaildir/maildir.h" + +#include +#include + +#include +#include + +#include + +#include + +using namespace Akonadi; +using namespace KMBox; + +static bool fullEntryCompare( const MBoxEntry &a, const MBoxEntry &b ) +{ + return a.messageOffset() == b.messageOffset() && + a.separatorSize() == b.separatorSize() && + a.messageSize() == b.messageSize(); +} + +static quint64 changedOffset( const Item &item ) { + Q_ASSERT( item.hasAttribute() ); + + const QString remoteId = item.attribute()->remoteId(); + Q_ASSERT( !remoteId.isEmpty() ); + + bool ok = false; + const quint64 result = remoteId.toULongLong( &ok ); + Q_ASSERT( ok ); + + return result; +} + +class ItemMoveTest : public QObject +{ + Q_OBJECT + + public: + ItemMoveTest() + : QObject(), mStore( 0 ), mDir( 0 ) {} + + ~ItemMoveTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testExpectedFail(); + void testMaildirItem(); + void testMBoxItem(); +}; + +void ItemMoveTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void ItemMoveTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void ItemMoveTest::testExpectedFail() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + QSet entrySet2; + Q_FOREACH( const MBoxEntry &entry, entryList2 ) { + entrySet2 << entry.messageOffset(); + } + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemMoveJob *job = 0; + + // test failure of moving from a non-existant collection + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + Item item3; + item3.setRemoteId( QLatin1String( "item3" ) ); + item3.setParentCollection( collection3 ); + + job = mStore->moveItem( item3, collection1 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // test failure of moving from maildir to non-existant collection + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( entrySet1.values().first() ); + item1.setParentCollection( collection1 ); + + job = mStore->moveItem( item1, collection3 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // test failure of moving from mbox to non-existant collection + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Item item2; + item2.setId( KRandom::random() ); + item2.setRemoteId( QLatin1String( "0" ) ); + item2.setParentCollection( collection2 ); + + job = mStore->moveItem( item1, collection3 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List tmpEntryList = mbox2.entries(); + QVERIFY( std::equal( tmpEntryList.begin(), tmpEntryList.end(), entryList2.begin(), fullEntryCompare ) ); + + // test failure of moving from maildir to top level collection + job = mStore->moveItem( item1, mStore->topLevelCollection() ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // test failure of moving from mbox to top level collection + job = mStore->moveItem( item1, mStore->topLevelCollection() ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + tmpEntryList = mbox2.entries(); + QVERIFY( std::equal( tmpEntryList.begin(), tmpEntryList.end(), entryList2.begin(), fullEntryCompare ) ); + + // test failure of moving a non-existant maildir entry + QString remoteId1; + do { + remoteId1 = KRandom::randomString( 20 ); + } while ( entrySet1.contains( remoteId1 ) ); + + item1.setRemoteId( remoteId1 ); + + job = mStore->moveItem( item1, collection2 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + tmpEntryList = mbox2.entries(); + QVERIFY( std::equal( tmpEntryList.begin(), tmpEntryList.end(), entryList2.begin(), fullEntryCompare ) ); + + // test failure of moving a non-existant mbox entry + quint64 remoteId2; + do { + remoteId2 = KRandom::random(); + } while ( entrySet2.contains( remoteId2 ) ); + + item2.setRemoteId( QString::number( remoteId2 ) ); + + job = mStore->moveItem( item2, collection1 ); + QVERIFY( !job->exec() ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + tmpEntryList = mbox2.entries(); + QVERIFY( std::equal( tmpEntryList.begin(), tmpEntryList.end(), entryList2.begin(), fullEntryCompare ) ); +} + +void ItemMoveTest::testMaildirItem() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection5" ) ) ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md1 = topLevelMd.subFolder( QLatin1String( "collection1" ) ); + QSet entrySet1 = QSet::fromList( md1.entryList() ); + QCOMPARE( (int)entrySet1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + KPIM::Maildir md3( topLevelMd.addSubFolder( QLatin1String( "collection3" ) ), false ); + QVERIFY( md3.isValid() ); + QSet entrySet3 = QSet::fromList( md3.entryList() ); + QCOMPARE( (int)entrySet3.count(), 0 ); + + QFileInfo fileInfo4( topDir.path(), QLatin1String( "collection4" ) ); + QFile file4( fileInfo4.absoluteFilePath() ); + QVERIFY( file4.open( QIODevice::WriteOnly ) ); + file4.close(); + fileInfo4.refresh(); + QVERIFY( fileInfo4.exists() ); + MBox mbox4; + QVERIFY( mbox4.load( fileInfo4.absoluteFilePath() ) ); + MBoxEntry::List entryList4 = mbox4.entries(); + QCOMPARE( (int)entryList4.count(), 0 ); + + KPIM::Maildir md5 = topLevelMd.subFolder( QLatin1String( "collection5" ) ); + QSet entrySet5 = QSet::fromList( md5.entryList() ); + QCOMPARE( (int)entrySet5.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemMoveJob *job = 0; + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item movedItem; + MBoxEntry::List entryList; + + // test moving to an empty maildir + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( entrySet1.values().first() ); + item1.setParentCollection( collection1 ); + + job = mStore->moveItem( item1, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection3 ); + + entrySet3 << movedItem.remoteId(); + QCOMPARE( QSet::fromList( md3.entryList() ), entrySet3 ); + entrySet1.remove( item1.remoteId() ); + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // test moving to a non empty maildir + item1.setRemoteId( entrySet1.values().first() ); + + Collection collection5; + collection5.setName( QLatin1String( "collection5" ) ); + collection5.setRemoteId( QLatin1String( "collection5" ) ); + collection5.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveItem( item1, collection5 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection5 ); + + entrySet5 << movedItem.remoteId(); + QCOMPARE( QSet::fromList( md5.entryList() ), entrySet5 ); + entrySet1.remove( item1.remoteId() ); + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 2 ); + QCOMPARE( collections, Collection::List() << collection1 << collection5 ); + + // test moving to an empty mbox + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + item1.setRemoteId( entrySet1.values().first() ); + + job = mStore->moveItem( item1, collection4 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection4 ); + + QVERIFY( mbox4.load( mbox4.fileName() ) ); + entryList = mbox4.entries(); + QCOMPARE( (int)entryList.count(), 1 ); + + QCOMPARE( entryList.last().messageOffset(), movedItem.remoteId().toULongLong() ); + entrySet1.remove( item1.remoteId() ); + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + // test moving to a non empty mbox + item1.setRemoteId( entrySet1.values().first() ); + + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveItem( item1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection2 ); + + QVERIFY( mbox2.load( mbox2.fileName() ) ); + entryList = mbox2.entries(); + QCOMPARE( (int)entryList.count(), 5 ); + + QCOMPARE( entryList.last().messageOffset(), movedItem.remoteId().toULongLong() ); + entrySet1.remove( item1.remoteId() ); + QCOMPARE( QSet::fromList( md1.entryList() ), entrySet1 ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 2 ); + QCOMPARE( collections, Collection::List() << collection1 << collection2 ); +} + +void ItemMoveTest::testMBoxItem() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection5" ) ) ); + + QFileInfo fileInfo1( topDir.path(), QLatin1String( "collection1" ) ); + MBox mbox1; + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + MBoxEntry::List entryList1 = mbox1.entries(); + QCOMPARE( (int)entryList1.count(), 4 ); + + KPIM::Maildir topLevelMd( topDir.path(), true ); + + KPIM::Maildir md2 = topLevelMd.subFolder( QLatin1String( "collection2" ) ); + QSet entrySet2 = QSet::fromList( md2.entryList() ); + QCOMPARE( (int)entrySet2.count(), 4 ); + + KPIM::Maildir md3( topLevelMd.addSubFolder( QLatin1String( "collection3" ) ), false ); + QVERIFY( md3.isValid() ); + QSet entrySet3 = QSet::fromList( md3.entryList() ); + QCOMPARE( (int)entrySet3.count(), 0 ); + + QFileInfo fileInfo4( topDir.path(), QLatin1String( "collection4" ) ); + QFile file4( fileInfo4.absoluteFilePath() ); + QVERIFY( file4.open( QIODevice::WriteOnly ) ); + file4.close(); + fileInfo4.refresh(); + QVERIFY( fileInfo4.exists() ); + MBox mbox4; + QVERIFY( mbox4.load( fileInfo4.absoluteFilePath() ) ); + MBoxEntry::List entryList4 = mbox4.entries(); + QCOMPARE( (int)entryList4.count(), 0 ); + + QFileInfo fileInfo5( topDir.path(), QLatin1String( "collection5" ) ); + MBox mbox5; + QVERIFY( mbox5.load( fileInfo5.absoluteFilePath() ) ); + MBoxEntry::List entryList5 = mbox5.entries(); + QCOMPARE( (int)entryList5.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::ItemMoveJob *job = 0; + FileStore::StoreCompactJob *compactStore = 0; + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + Collection::List collections; + Item movedItem; + MBoxEntry::List entryList; + Item::List items; + + // test moving to an empty maildir + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setId( KRandom::random() ); + item1.setRemoteId( QString::number( entryList1.first().messageOffset() ) ); + item1.setParentCollection( collection1 ); + + job = mStore->moveItem( item1, collection3 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection3 ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + compactStore = mStore->compactStore(); + QVERIFY( compactStore->exec() ); + + items = compactStore->changedItems(); + QCOMPARE( (int)items.count(), 3 ); + + entrySet3 << movedItem.remoteId(); + QCOMPARE( QSet::fromList( md3.entryList() ), entrySet3 ); + + entryList1.removeAt( 0 ); + entryList1[ 0 ] = MBoxEntry( changedOffset( items[ 0 ] ) ); + entryList1[ 1 ] = MBoxEntry( changedOffset( items[ 1 ] ) ); + entryList1[ 2 ] = MBoxEntry( changedOffset( items[ 2 ] ) ); + QVERIFY( mbox1.load( mbox1.fileName() ) ); + QCOMPARE( mbox1.entries(), entryList1 ); + + // test moving to a non empty mbox + Collection collection5; + collection5.setName( QLatin1String( "collection5" ) ); + collection5.setRemoteId( QLatin1String( "collection5" ) ); + collection5.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveItem( item1, collection5 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection5 ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 2 ); + QCOMPARE( collections, Collection::List() << collection1 << collection5 ); + + compactStore = mStore->compactStore(); + QVERIFY( compactStore->exec() ); + + items = compactStore->changedItems(); + QCOMPARE( (int)items.count(), 2 ); + + QVERIFY( mbox5.load( mbox5.fileName() ) ); + QCOMPARE( mbox5.entries().count(), entryList5.count() + 1 ); + QCOMPARE( mbox5.entries().last().messageOffset(), movedItem.remoteId().toULongLong() ); + + entryList1.removeAt( 0 ); + entryList1[ 0 ] = MBoxEntry( changedOffset( items[ 0 ] ) ); + entryList1[ 1 ] = MBoxEntry( changedOffset( items[ 1 ] ) ); + QVERIFY( mbox1.load( mbox1.fileName() ) ); + QCOMPARE( mbox1.entries(), entryList1 ); + + // test moving to an empty mbox + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveItem( item1, collection4 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection4 ); + + QVERIFY( mbox4.load( mbox4.fileName() ) ); + entryList = mbox4.entries(); + QCOMPARE( (int)entryList.count(), 1 ); + + QCOMPARE( entryList.last().messageOffset(), movedItem.remoteId().toULongLong() ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections.first(), collection1 ); + + compactStore = mStore->compactStore(); + QVERIFY( compactStore->exec() ); + + items = compactStore->changedItems(); + QCOMPARE( (int)items.count(), 1 ); + + QCOMPARE( mbox4.entries().count(), entryList4.count() + 1 ); + QCOMPARE( mbox4.entries().last().messageOffset(), movedItem.remoteId().toULongLong() ); + + entryList1.removeAt( 0 ); + entryList1[ 0 ] = MBoxEntry( changedOffset( items[ 0 ] ) ); + QVERIFY( mbox1.load( mbox1.fileName() ) ); + QCOMPARE( mbox1.entries(), entryList1 ); + + // test moving to a non empty maildir + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + job = mStore->moveItem( item1, collection2 ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + movedItem = job->item(); + QCOMPARE( movedItem.id(), item1.id() ); + QCOMPARE( movedItem.parentCollection(), collection2 ); + + QSet entrySet = QSet::fromList( md2.entryList() ); + QCOMPARE( (int)entrySet.count(), 5 ); + + QVERIFY( entrySet.contains( movedItem.remoteId() ) ); + + var = job->property( "compactStore" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.type(), QVariant::Bool ); + QCOMPARE( var.toBool(), true ); + + // check for index preservation + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 2 ); + QCOMPARE( collections.first(), collection1 ); + + compactStore = mStore->compactStore(); + QVERIFY( compactStore->exec() ); + + items = compactStore->changedItems(); + QCOMPARE( (int)items.count(), 0 ); + + entryList1.removeAt( 0 ); + QVERIFY( mbox1.load( mbox1.fileName() ) ); + const MBoxEntry::List newEntryList = mbox1.entries(); + QVERIFY( std::equal( newEntryList.begin(), newEntryList.end(), entryList1.begin(), fullEntryCompare ) ); +} + +QTEST_KDEMAIN( ItemMoveTest, NoGUI ) + +#include "itemmovetest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/storecompacttest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/storecompacttest.cpp new file mode 100644 index 00000000..a454f5d6 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/storecompacttest.cpp @@ -0,0 +1,395 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "testdatautil.h" + +#include "filestore/entitycompactchangeattribute.h" +#include "filestore/itemdeletejob.h" +#include "filestore/storecompactjob.h" + +#include + +#include +#include + +#include + +#include + +using namespace Akonadi; +using namespace KMBox; + +static Collection::List collectionsFromSpy( QSignalSpy *spy ) { + Collection::List collections; + + QListIterator > it( *spy ); + while( it.hasNext() ) { + const QList invocation = it.next(); + Q_ASSERT( invocation.count() == 1 ); + + collections << invocation.first().value(); + } + + return collections; +} + +static Item::List itemsFromSpy( QSignalSpy *spy ) { + Item::List items; + + QListIterator > it( *spy ); + while( it.hasNext() ) { + const QList invocation = it.next(); + Q_ASSERT( invocation.count() == 1 ); + + items << invocation.first().value(); + } + + return items; +} + +static bool fullEntryCompare( const MBoxEntry &a, const MBoxEntry &b ) +{ + return a.messageOffset() == b.messageOffset() && + a.separatorSize() == b.separatorSize() && + a.messageSize() == b.messageSize(); +} + +static quint64 changedOffset( const Item &item ) { + Q_ASSERT( item.hasAttribute() ); + + const QString remoteId = item.attribute()->remoteId(); + Q_ASSERT( !remoteId.isEmpty() ); + + bool ok = false; + const quint64 result = remoteId.toULongLong( &ok ); + Q_ASSERT( ok ); + + return result; +} + +class StoreCompactTest : public QObject +{ + Q_OBJECT + + public: + StoreCompactTest() : QObject(), mStore( 0 ), mDir( 0 ) { + // for monitoring signals + qRegisterMetaType(); + qRegisterMetaType(); + } + + ~StoreCompactTest() { + delete mStore; + delete mDir; + } + + private: + MixedMaildirStore *mStore; + KTempDir *mDir; + + private Q_SLOTS: + void init(); + void cleanup(); + void testCompact(); +}; + +void StoreCompactTest::init() +{ + mStore = new MixedMaildirStore; + + mDir = new KTempDir; + QVERIFY( mDir->exists() ); +} + +void StoreCompactTest::cleanup() +{ + delete mStore; + mStore = 0; + delete mDir; + mDir = 0; +} + +void StoreCompactTest::testCompact() +{ + QDir topDir( mDir->name() ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection1" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection2" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection3" ) ) ); + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), topDir.path(), QLatin1String( "collection4" ) ) ); + + QFileInfo fileInfo1( topDir.path(), QLatin1String( "collection1" ) ); + MBox mbox1; + QVERIFY( mbox1.load( fileInfo1.absoluteFilePath() ) ); + MBoxEntry::List entryList1 = mbox1.entries(); + QCOMPARE( (int)entryList1.count(), 4 ); + + QFileInfo fileInfo2( topDir.path(), QLatin1String( "collection2" ) ); + MBox mbox2; + QVERIFY( mbox2.load( fileInfo2.absoluteFilePath() ) ); + MBoxEntry::List entryList2 = mbox2.entries(); + QCOMPARE( (int)entryList2.count(), 4 ); + + QFileInfo fileInfo3( topDir.path(), QLatin1String( "collection3" ) ); + MBox mbox3; + QVERIFY( mbox3.load( fileInfo3.absoluteFilePath() ) ); + MBoxEntry::List entryList3 = mbox3.entries(); + QCOMPARE( (int)entryList3.count(), 4 ); + + QFileInfo fileInfo4( topDir.path(), QLatin1String( "collection4" ) ); + MBox mbox4; + QVERIFY( mbox4.load( fileInfo4.absoluteFilePath() ) ); + MBoxEntry::List entryList4 = mbox4.entries(); + QCOMPARE( (int)entryList4.count(), 4 ); + + mStore->setPath( topDir.path() ); + + // common variables + FileStore::CollectionFetchJob *collectionFetch = 0; + FileStore::ItemDeleteJob *itemDelete = 0; + FileStore::StoreCompactJob *job = 0; + + Collection::List collections; + Item::List items; + + QSignalSpy *collectionSpy = 0; + QSignalSpy *itemSpy = 0; + + MBoxEntry::List entryList; + Collection collection; + FileStore::EntityCompactChangeAttribute *attribute = 0; + + const QVariant colListVar = QVariant::fromValue( Collection::List() ); + QVariant var; + + // test compact after delete from the end of an mbox + Collection collection1; + collection1.setName( QLatin1String( "collection1" ) ); + collection1.setRemoteId( QLatin1String( "collection1" ) ); + collection1.setParentCollection( mStore->topLevelCollection() ); + + Item item1; + item1.setRemoteId( QString::number( entryList1.last().messageOffset() ) ); + item1.setParentCollection( collection1 ); + + itemDelete = mStore->deleteItem( item1 ); + + QVERIFY( itemDelete->exec() ); + + job = mStore->compactStore(); + + collectionSpy = new QSignalSpy( job, SIGNAL(collectionsChanged(Akonadi::Collection::List)) ); + itemSpy = new QSignalSpy( job, SIGNAL(itemsChanged(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collections = job->changedCollections(); + items = job->changedItems(); + + QCOMPARE( collections.count(), 0 ); + QCOMPARE( items.count(), 0 ); + + QCOMPARE( collectionSpy->count(), 0 ); + QCOMPARE( itemSpy->count(), 0 ); + + QVERIFY( mbox1.load( mbox1.fileName() ) ); + entryList = mbox1.entries(); + entryList1.pop_back(); + QVERIFY( std::equal( entryList.begin(), entryList.end(), entryList1.begin(), fullEntryCompare ) ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections, Collection::List() << collection1 ); + + // test compact after delete from before the end of an mbox + Collection collection2; + collection2.setName( QLatin1String( "collection2" ) ); + collection2.setRemoteId( QLatin1String( "collection2" ) ); + collection2.setParentCollection( mStore->topLevelCollection() ); + + Item item2; + item2.setRemoteId( QString::number( entryList2.first().messageOffset() ) ); + item2.setParentCollection( collection2 ); + + itemDelete = mStore->deleteItem( item2 ); + + QVERIFY( itemDelete->exec() ); + + job = mStore->compactStore(); + + collectionSpy = new QSignalSpy( job, SIGNAL(collectionsChanged(Akonadi::Collection::List)) ); + itemSpy = new QSignalSpy( job, SIGNAL(itemsChanged(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collections = job->changedCollections(); + items = job->changedItems(); + + QCOMPARE( collections.count(), 1 ); + QCOMPARE( items.count(), 3 ); + + QCOMPARE( collectionSpy->count(), 1 ); + QCOMPARE( itemSpy->count(), 1 ); + + QCOMPARE( collectionsFromSpy( collectionSpy ), collections ); + QCOMPARE( itemsFromSpy( itemSpy ), items ); + + collection = collections.first(); + QCOMPARE( collection, collection2 ); + QVERIFY( collection.hasAttribute() ); + attribute = collection.attribute(); + QCOMPARE( attribute->remoteRevision().toInt(), collection2.remoteRevision().toInt() + 1 ); + + QVERIFY( mbox2.load( mbox2.fileName() ) ); + entryList = mbox2.entries(); + + entryList2.pop_front(); + for ( int i = 0; i < items.count(); ++i ) { + entryList2[ i ] = MBoxEntry( changedOffset( items[ i ] ) ); + } + QCOMPARE( entryList, entryList2 ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 1 ); + QCOMPARE( collections, Collection::List() << collection2 ); + + collectionFetch = mStore->fetchCollections( collection2, FileStore::CollectionFetchJob::Base ); + + QVERIFY( collectionFetch->exec() ); + + collections = collectionFetch->collections(); + QCOMPARE( (int)collections.count(), 1 ); + + collection = collections.first(); + QCOMPARE( collection, collection2 ); + QCOMPARE( collection.remoteRevision(), attribute->remoteRevision() ); + + // test compact after delete from before the end of more than one mbox + Collection collection3; + collection3.setName( QLatin1String( "collection3" ) ); + collection3.setRemoteId( QLatin1String( "collection3" ) ); + collection3.setParentCollection( mStore->topLevelCollection() ); + + Item item3; + item3.setRemoteId( QString::number( entryList3.first().messageOffset() ) ); + item3.setParentCollection( collection3 ); + + itemDelete = mStore->deleteItem( item3 ); + + QVERIFY( itemDelete->exec() ); + + Collection collection4; + collection4.setName( QLatin1String( "collection4" ) ); + collection4.setRemoteId( QLatin1String( "collection4" ) ); + collection4.setParentCollection( mStore->topLevelCollection() ); + + Item item4; + item4.setRemoteId( QString::number( entryList3.value( 1 ).messageOffset() ) ); + item4.setParentCollection( collection4 ); + + itemDelete = mStore->deleteItem( item4 ); + + QVERIFY( itemDelete->exec() ); + + job = mStore->compactStore(); + + collectionSpy = new QSignalSpy( job, SIGNAL(collectionsChanged(Akonadi::Collection::List)) ); + itemSpy = new QSignalSpy( job, SIGNAL(itemsChanged(Akonadi::Item::List)) ); + + QVERIFY( job->exec() ); + QCOMPARE( job->error(), 0 ); + + collections = job->changedCollections(); + items = job->changedItems(); + + QCOMPARE( collections.count(), 2 ); + QCOMPARE( items.count(), 5 ); + + QCOMPARE( collectionSpy->count(), 2 ); + QCOMPARE( itemSpy->count(), 2 ); + + QCOMPARE( collectionsFromSpy( collectionSpy ), collections ); + QCOMPARE( itemsFromSpy( itemSpy ), items ); + + QHash compactedCollections; + Q_FOREACH ( const Collection &col, collections ) { + compactedCollections.insert( col.remoteId(), col ); + } + QCOMPARE( compactedCollections.count(), 2 ); + + QVERIFY( compactedCollections.contains( collection3.remoteId() ) ); + collection = compactedCollections[ collection3.remoteId() ]; + QCOMPARE( collection, collection3 ); + QVERIFY( collection.hasAttribute() ); + attribute = collection.attribute(); + QCOMPARE( attribute->remoteRevision().toInt(), collection3.remoteRevision().toInt() + 1 ); + + QVERIFY( mbox3.load( mbox3.fileName() ) ); + entryList = mbox3.entries(); + + entryList3.pop_front(); + for ( int i = 0; i < entryList3.count(); ++i ) { + entryList3[ i ] = MBoxEntry( changedOffset( items.first() ) ); + items.pop_front(); + } + QCOMPARE( entryList, entryList3 ); + + QVERIFY( compactedCollections.contains( collection4.remoteId() ) ); + collection = compactedCollections[ collection4.remoteId() ]; + QCOMPARE( collection, collection4 ); + QVERIFY( collection.hasAttribute() ); + attribute = collection.attribute(); + QCOMPARE( attribute->remoteRevision().toInt(), collection4.remoteRevision().toInt() + 1 ); + + QVERIFY( mbox4.load( mbox4.fileName() ) ); + entryList = mbox4.entries(); + + entryList4.removeAt( 1 ); + for ( int i = 0; i < items.count(); ++i ) { + entryList4[ i + 1 ] = MBoxEntry( changedOffset( items[ i ] ) ); + } + QCOMPARE( entryList, entryList4 ); + + var = job->property( "onDiskIndexInvalidated" ); + QVERIFY( var.isValid() ); + QCOMPARE( var.userType(), colListVar.userType() ); + + collections = var.value(); + QCOMPARE( (int)collections.count(), 2 ); + QCOMPARE( collections, Collection::List() << collection3 << collection4 ); +} + +QTEST_KDEMAIN( StoreCompactTest, NoGUI ) + +#include "storecompacttest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/templatemethodstest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/templatemethodstest.cpp new file mode 100644 index 00000000..f4ebf259 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/templatemethodstest.cpp @@ -0,0 +1,232 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "mixedmaildirstore.h" + +#include "collectioncreatejob.h" +#include "collectiondeletejob.h" +#include "collectionfetchjob.h" +#include "collectionmodifyjob.h" +#include "collectionmovejob.h" +#include "itemcreatejob.h" +#include "itemdeletejob.h" +#include "itemfetchjob.h" +#include "itemmodifyjob.h" +#include "itemmovejob.h" +#include "sessionimpls_p.h" +#include "storecompactjob.h" + +#include +#include + +#include + +#include +#include + +#include + +using namespace Akonadi; + +class TestStore : public MixedMaildirStore +{ + Q_OBJECT + + public: + TestStore() : mLastCheckedJob( 0 ), mErrorCode( 0 ) {} + + public: + Collection mTopLevelCollection; + + mutable FileStore::Job *mLastCheckedJob; + mutable int mErrorCode; + mutable QString mErrorText; + + protected: + void setTopLevelCollection( const Collection &collection ) + { + MixedMaildirStore::setTopLevelCollection( collection ); + } + + void checkCollectionMove( FileStore::CollectionMoveJob *job, int &errorCode, QString &errorText ) const + { + MixedMaildirStore::checkCollectionMove( job, errorCode, errorText ); + + mLastCheckedJob = job; + mErrorCode = errorCode; + mErrorText = errorText; + } + + void checkItemCreate( FileStore::ItemCreateJob *job, int &errorCode, QString &errorText ) const + { + MixedMaildirStore::checkItemCreate( job, errorCode, errorText ); + + mLastCheckedJob = job; + mErrorCode = errorCode; + mErrorText = errorText; + } +}; + +class TemplateMethodsTest : public QObject +{ + Q_OBJECT + + public: + TemplateMethodsTest() : QObject(), mStore( 0 ) {} + ~TemplateMethodsTest() { delete mStore; } + + private: + KTempDir mDir; + TestStore *mStore; + + private Q_SLOTS: + void init(); + void testSetTopLevelCollection(); + void testMoveCollection(); + void testCreateItem(); +}; + +void TemplateMethodsTest::init() +{ + delete mStore; + mStore = new TestStore; + QVERIFY( mDir.exists() ); +} + +void TemplateMethodsTest::testSetTopLevelCollection() +{ + const QString file = KRandom::randomString( 10 ); + const QString path = mDir.name() + file; + + mStore->setPath( path ); + + // check the adjustments on the top level collection + const Collection collection = mStore->topLevelCollection(); + QCOMPARE( collection.contentMimeTypes(), QStringList() << Collection::mimeType() ); + QCOMPARE( collection.rights(), Collection::CanCreateCollection | + Collection::CanChangeCollection | + Collection::CanDeleteCollection ); + const CachePolicy cachePolicy = collection.cachePolicy(); + QVERIFY( !cachePolicy.inheritFromParent() ); + QCOMPARE( cachePolicy.localParts(), QStringList() << MessagePart::Envelope ); + QVERIFY( cachePolicy.syncOnDemand() ); +} + +void TemplateMethodsTest::testMoveCollection() +{ + mStore->setPath( mDir.name() ); + + FileStore::CollectionMoveJob *job = 0; + + // test moving into itself + Collection collection( KRandom::random() ); + collection.setParentCollection( mStore->topLevelCollection() ); + collection.setRemoteId( "collection" ); + job = mStore->moveCollection( collection, collection ); + QVERIFY( job != 0 ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( mStore->mErrorCode, job->error() ); + QCOMPARE( mStore->mErrorText, job->errorText() ); + + QVERIFY( !job->exec() ); + + // test moving into child + Collection childCollection( collection.id() + 1 ); + childCollection.setParentCollection( collection ); + childCollection.setRemoteId( "child" ); + job = mStore->moveCollection( collection, childCollection ); + QVERIFY( job != 0 ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( mStore->mErrorCode, job->error() ); + QCOMPARE( mStore->mErrorText, job->errorText() ); + + QVERIFY( !job->exec() ); + + // test moving into grand child child + Collection grandChildCollection( collection.id() + 2 ); + grandChildCollection.setParentCollection( childCollection ); + grandChildCollection.setRemoteId( "grandchild" ); + job = mStore->moveCollection( collection, grandChildCollection ); + QVERIFY( job != 0 ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( mStore->mErrorCode, job->error() ); + QCOMPARE( mStore->mErrorText, job->errorText() ); + + QVERIFY( !job->exec() ); + + // test moving into unrelated collection + Collection otherCollection( collection.id() + KRandom::random() ); + otherCollection.setParentCollection( mStore->topLevelCollection() ); + otherCollection.setRemoteId( "other" ); + job = mStore->moveCollection( collection, otherCollection ); + QVERIFY( job != 0 ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + QCOMPARE( mStore->mLastCheckedJob, job ); + + // collections don't exist in the store, so processing still fails + QVERIFY( !job->exec() ); +} + +void TemplateMethodsTest::testCreateItem() +{ + mStore->setPath( mDir.name() ); + + Collection collection( KRandom::random() ); + collection.setParentCollection( mStore->topLevelCollection() ); + collection.setRemoteId( "collection" ); + + FileStore::ItemCreateJob *job = 0; + + // test item without payload + Item item( KMime::Message::mimeType() ); + job = mStore->createItem( item, collection ); + QVERIFY( job != 0 ); + QCOMPARE( job->error(), (int)FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( mStore->mErrorCode, job->error() ); + QCOMPARE( mStore->mErrorText, job->errorText() ); + + QVERIFY( !job->exec() ); + + // test item with payload + item.setPayloadFromData( "Subject: dummy payload\n\nwith some content" ); + job = mStore->createItem( item, collection ); + QVERIFY( job != 0 ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + QCOMPARE( mStore->mLastCheckedJob, job ); + + // collections don't exist in the store, so processing still fails + QVERIFY( !job->exec() ); +} + +QTEST_KDEMAIN( TemplateMethodsTest, NoGUI ) + +#include "templatemethodstest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/testdata.qrc b/kdepim-runtime/resources/mixedmaildir/tests/testdata.qrc new file mode 100644 index 00000000..1c0332e4 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/testdata.qrc @@ -0,0 +1,31 @@ + + + + data/mbox + data/.mbox.index + + data/mbox-tagged + data/.mbox-tagged.index + + data/mbox-unpurged + data/.mbox-unpurged.index + + data/maildir/cur/1279979618.4595.CStza_2,S + data/maildir/cur/1279979618.4595.pY5ny + data/maildir/cur/1279979617.4595.bwXSm + data/maildir/cur/1279979618.4595.DUl0I_2,S + data/.maildir.index + + data/maildir-tagged/cur/1279982188.18722.6qZsA_2,S + data/maildir-tagged/cur/1279982188.18722.Xdz3R_2,S + data/maildir-tagged/cur/1279982188.18722.f0l49_2,S + data/maildir-tagged/cur/1279982188.18722.kwx1b_2,S + data/.maildir-tagged.index + + data/dimap/cur/1279980064.4595.qs6V9_2,S + data/dimap/cur/1279980064.4595.LUBVK + data/dimap/cur/1279980064.4595.g8PCJ + data/dimap/cur/1279980064.4595.RTmAd_2,S + data/.dimap.index + + diff --git a/kdepim-runtime/resources/mixedmaildir/tests/testdatatest.cpp b/kdepim-runtime/resources/mixedmaildir/tests/testdatatest.cpp new file mode 100644 index 00000000..04a770de --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/testdatatest.cpp @@ -0,0 +1,106 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "testdatautil.h" + +#include + +#include + +class TestDataTest : public QObject +{ + Q_OBJECT + public: + TestDataTest() {} + + private Q_SLOTS: + void testResources(); + void testInstall(); +}; + +void TestDataTest::testResources() +{ + const QStringList testDataNames = TestDataUtil::testDataNames(); + QCOMPARE( testDataNames, QStringList() << QLatin1String( "dimap" ) + << QLatin1String( "maildir" ) + << QLatin1String( "maildir-tagged" ) + << QLatin1String( "mbox" ) + << QLatin1String( "mbox-tagged" ) + << QLatin1String( "mbox-unpurged" ) ); + + Q_FOREACH( const QString testDataName, testDataNames ) { + if ( testDataName.startsWith( QLatin1String( "mbox" ) ) ) { + QVERIFY( TestDataUtil::folderType( testDataName ) == TestDataUtil::MBoxFolder ); + } else { + QVERIFY( TestDataUtil::folderType( testDataName ) == TestDataUtil::MaildirFolder ); + } + } + + // TODO check contents? +} + +void TestDataTest::testInstall() +{ + KTempDir dir; + QDir installDir( dir.name() ); + QDir curDir; + + const QString indexFilePattern = QLatin1String( ".%1.index" ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox" ), dir.name(), QLatin1String( "mbox1" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "mbox1" ) ) ); + QVERIFY( installDir.exists( indexFilePattern.arg( QLatin1String( "mbox1" ) ) ) ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "mbox-tagged" ), dir.name(), QLatin1String( "mbox2" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "mbox2" ) ) ); + QVERIFY( installDir.exists( indexFilePattern.arg( QLatin1String( "mbox2" ) ) ) ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir" ), dir.name(), QLatin1String( "md1" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md1" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md1/new" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md1/cur" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md1/tmp" ) ) ); + QVERIFY( installDir.exists( indexFilePattern.arg( QLatin1String( "md1" ) ) ) ); + + curDir = installDir; + curDir.cd( QLatin1String( "md1" ) ); + curDir.cd( QLatin1String( "cur" ) ); + curDir.setFilter( QDir::Files ); + QCOMPARE( (int)curDir.count(), 4 ); + + QVERIFY( TestDataUtil::installFolder( QLatin1String( "maildir-tagged" ), dir.name(), QLatin1String( "md2" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md2" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md2/new" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md2/cur" ) ) ); + QVERIFY( installDir.exists( QLatin1String( "md2/tmp" ) ) ); + QVERIFY( installDir.exists( indexFilePattern.arg( QLatin1String( "md2" ) ) ) ); + + curDir = installDir; + curDir.cd( QLatin1String( "md2" ) ); + curDir.cd( QLatin1String( "cur" ) ); + curDir.setFilter( QDir::Files ); + QCOMPARE( (int)curDir.count(), 4 ); +} + +#include "testdatatest.moc" + +QTEST_KDEMAIN( TestDataTest, NoGUI ) + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/testdatautil.cpp b/kdepim-runtime/resources/mixedmaildir/tests/testdatautil.cpp new file mode 100644 index 00000000..58f6f186 --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/testdatautil.cpp @@ -0,0 +1,202 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "testdatautil.h" + +#include + +#include +#include + +using namespace TestDataUtil; + +// use this instead of QFile::copy() because we want overwrite in case it exists +static bool copyFile( const QString &sourceFileName, const QString &targetFileName ) +{ + QFile sourceFile( sourceFileName ); + QFile targetFile( targetFileName ); + + if ( !sourceFile.open( QIODevice::ReadOnly ) ) { + kError() << "Cannot open source file" << sourceFileName; + return false; + } + + if ( !targetFile.open( QIODevice::WriteOnly ) ) { + kError() << "Cannot open target file" << targetFileName; + return false; + } + + return targetFile.write( sourceFile.readAll() ) != -1; +} + +static bool copyFiles( const QDir &sourceDir, const QDir &targetDir ) +{ + const QStringList files = sourceDir.entryList( QStringList(), QDir::Files ); + Q_FOREACH( const QString &file, files ) { + const QFileInfo sourceFileInfo( sourceDir, file ); + const QFileInfo targetFileInfo( targetDir, file ); + if ( !copyFile( sourceFileInfo.absoluteFilePath(), targetFileInfo.absoluteFilePath() ) ) { + kError() << "Failed to copy" << sourceFileInfo.absoluteFilePath() + << "to" << targetFileInfo.absoluteFilePath(); + return false; + } + } + + return true; +} + +FolderType TestDataUtil::folderType( const QString &testDataName ) +{ + const QDir dir( QLatin1String( ":/data" ) ); + const QString indexFilePattern = QLatin1String( ".%1.index" ); + + if ( !dir.exists( testDataName ) || !dir.exists( indexFilePattern.arg( testDataName ) ) ) { + return InvalidFolder; + } + + const QFileInfo fileInfo( dir, testDataName ); + return ( fileInfo.isDir() ? MaildirFolder : MBoxFolder ); +} + +QStringList TestDataUtil::testDataNames() +{ + const QDir dir( QLatin1String( ":/data" ) ); + const QFileInfoList dirEntries = dir.entryInfoList(); + + const QString indexFilePattern = QLatin1String( ".%1.index" ); + + QStringList result; + Q_FOREACH( const QFileInfo& fileInfo, dirEntries ) { + if ( dir.exists( indexFilePattern.arg( fileInfo.fileName() ) ) ) { + result << fileInfo.fileName(); + } + } + + result.sort(); + return result; +} + +bool TestDataUtil::installFolder( const QString &testDataName, const QString &installPath, const QString &folderName ) +{ + const FolderType type = TestDataUtil::folderType( testDataName ); + if ( type == InvalidFolder ) { + kError() << "testDataName" << testDataName << "is not a valid mail folder type"; + return false; + } + + if ( !QDir::current().mkpath( installPath ) ) { + kError() << "Couldn't create installPath" << installPath; + return false; + } + + const QDir installDir( installPath ); + const QFileInfo installFileInfo( installDir, folderName ); + if ( installDir.exists( folderName ) ) { + switch ( type ) { + case MaildirFolder: + if ( !installFileInfo.isDir() ) { + kError() << "Target file name" << folderName << "already exists but is not a directory"; + return false; + } + break; + + case MBoxFolder: + if ( !installFileInfo.isFile() ) { + kError() << "Target file name" << folderName << "already exists but is not a directory"; + return false; + } + break; + + default: + // already handled at beginning + Q_ASSERT( false ); + return false; + } + } + + const QDir testDataDir( QLatin1String( ":/data" ) ); + + switch ( type ) { + case MaildirFolder: { + const QString subPathPattern = QLatin1String( "%1/%2" ); + if ( !installDir.mkpath( subPathPattern.arg( folderName, QLatin1String( "new" ) ) ) || + !installDir.mkpath( subPathPattern.arg( folderName, QLatin1String( "cur" ) ) ) || + !installDir.mkpath( subPathPattern.arg( folderName, QLatin1String( "tmp" ) ) ) ) { + kError() << "Couldn't create maildir directory structure"; + return false; + } + + QDir sourceDir = testDataDir; + QDir targetDir = installDir; + + sourceDir.cd( testDataName ); + targetDir.cd( folderName ); + + if ( sourceDir.cd( QLatin1String( "new" ) ) ) { + targetDir.cd( QLatin1String( "new" ) ); + if ( !copyFiles( sourceDir, targetDir ) ) { + return false; + } + sourceDir.cdUp(); + targetDir.cdUp(); + } + + if ( sourceDir.cd( QLatin1String( "cur" ) ) ) { + targetDir.cd( QLatin1String( "cur" ) ); + if ( !copyFiles( sourceDir, targetDir ) ) { + return false; + } + sourceDir.cdUp(); + targetDir.cdUp(); + } + + if ( sourceDir.cd( QLatin1String( "tmp" ) ) ) { + targetDir.cd( QLatin1String( "tmp" ) ); + if ( !copyFiles( sourceDir, targetDir ) ) { + return false; + } + } + break; + } + + case MBoxFolder: { + const QFileInfo mboxFileInfo( testDataDir, testDataName ); + if ( !copyFile( mboxFileInfo.absoluteFilePath(), installFileInfo.absoluteFilePath() ) ) { + kError() << "Failed to copy" << mboxFileInfo.absoluteFilePath() + << "to" << installFileInfo.absoluteFilePath(); + return false; + } + break; + } + + default: + // already handled at beginning + Q_ASSERT( false ); + return false; + } + + const QString indexFilePattern = QLatin1String( ".%1.index" ); + const QFileInfo indexFileInfo( testDataDir, indexFilePattern.arg( testDataName ) ); + const QFileInfo indexInstallFileInfo( installDir, indexFilePattern.arg( folderName ) ); + + return copyFile( indexFileInfo.absoluteFilePath(), indexInstallFileInfo.absoluteFilePath() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/mixedmaildir/tests/testdatautil.h b/kdepim-runtime/resources/mixedmaildir/tests/testdatautil.h new file mode 100644 index 00000000..43de869b --- /dev/null +++ b/kdepim-runtime/resources/mixedmaildir/tests/testdatautil.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 TESTDATAUTIL_H +#define TESTDATAUTIL_H + +class QString; +class QStringList; + +namespace TestDataUtil +{ + enum FolderType { + InvalidFolder, + MaildirFolder, + MBoxFolder + }; + + FolderType folderType( const QString &testDataName ); + + QStringList testDataNames(); + + bool installFolder( const QString &testDataName, const QString &installPath, const QString &folderName ); +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/nntp/CMakeLists.txt b/kdepim-runtime/resources/nntp/CMakeLists.txt new file mode 100644 index 00000000..2ae61312 --- /dev/null +++ b/kdepim-runtime/resources/nntp/CMakeLists.txt @@ -0,0 +1,36 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} +) + +set( nntpresource_SRCS + nntpcollectionattribute.cpp + nntpresource.cpp + configdialog.cpp + settings.cpp +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +install( FILES nntpresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kcfg_generate_dbus_interface( ${CMAKE_CURRENT_SOURCE_DIR}/nntpresource.kcfg org.kde.Akonadi.NNTP.Settings ) + +qt4_add_dbus_adaptor( nntpresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.NNTP.Settings.xml settings.h Settings +) +kde4_add_ui_files(nntpresource_SRCS configdialog.ui) +kde4_add_kcfg_files(nntpresource_SRCS settingsbase.kcfgc) +kde4_add_executable(akonadi_nntp_resource ${nntpresource_SRCS}) + + +if (Q_WS_MAC) + set_target_properties(akonadi_nntp_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_nntp_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.NNTP") + set_target_properties(akonadi_nntp_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi NNTP Resource") +endif () + +target_link_libraries(akonadi_nntp_resource ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_AKONADI_KMIME_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${QT_QTCORE_LIBRARY}) + +install(TARGETS akonadi_nntp_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/nntp/Messages.sh b/kdepim-runtime/resources/nntp/Messages.sh new file mode 100644 index 00000000..501da0fd --- /dev/null +++ b/kdepim-runtime/resources/nntp/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_nntp_resource.pot diff --git a/kdepim-runtime/resources/nntp/configdialog.cpp b/kdepim-runtime/resources/nntp/configdialog.cpp new file mode 100644 index 00000000..6d7d6aa9 --- /dev/null +++ b/kdepim-runtime/resources/nntp/configdialog.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2008 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 "configdialog.h" +#include "settings.h" + +#include + +ConfigDialog::ConfigDialog(QWidget * parent) : + KDialog( parent ) +{ + ui.setupUi( mainWidget() ); + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); + ui.password->setText( Settings::self()->password() ); + ui.kcfg_MaxDownload->setSuffix( ki18np( " article", " articles" ) ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); +} + +void ConfigDialog::save() +{ + if ( ui.kcfg_StorePassword->isChecked() ) + Settings::self()->setPassword( ui.password->text() ); + mManager->updateSettings(); +} + diff --git a/kdepim-runtime/resources/nntp/configdialog.h b/kdepim-runtime/resources/nntp/configdialog.h new file mode 100644 index 00000000..61348705 --- /dev/null +++ b/kdepim-runtime/resources/nntp/configdialog.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2008 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +#include "ui_configdialog.h" + +class KConfigDialogManager; + +class ConfigDialog : public KDialog +{ + Q_OBJECT + public: + explicit ConfigDialog( QWidget *parent = 0 ); + + private slots: + void save(); + + private: + Ui::ConfigDialog ui; + KConfigDialogManager* mManager; +}; + +#endif diff --git a/kdepim-runtime/resources/nntp/configdialog.ui b/kdepim-runtime/resources/nntp/configdialog.ui new file mode 100644 index 00000000..3e99be9c --- /dev/null +++ b/kdepim-runtime/resources/nntp/configdialog.ui @@ -0,0 +1,399 @@ + + Volker Krause <vkrause@kde.org> + ConfigDialog + + + + 0 + 0 + 400 + 513 + + + + + + + true + + + 0 + + + + General + + + + + + &Name: + + + kcfg_Name + + + + + + + + + + &Server: + + + kcfg_Server + + + + + + + + + + Port: + + + + + + + + + + Qt::Horizontal + + + + 181 + 20 + + + + + + + + Encryption + + + + + + + + &None + + + + + + + &SSL + + + + + + + &TLS + + + + + + + + + Qt::Horizontal + + + + 191 + 20 + + + + + + + + false + + + &Check Server + + + + + + + + + + Authentication + + + + + + Server requires authentication + + + + + + + false + + + &Username: + + + kcfg_UserName + + + + + + + false + + + + + + + false + + + &Password: + + + password + + + + + + + false + + + true + + + + + + + false + + + Store password in KWallet + + + + + + + + + + Qt::Vertical + + + + 317 + 41 + + + + + + + + + Advanced + + + + + + Articles + + + + + + Download at most: + + + + + + + 1 + + + + + + + Qt::Horizontal + + + + 16 + 20 + + + + + + + + + + + Newsgroups + + + + + + Create flat newsgroup hierarchy by default + + + + + + + + + + Qt::Vertical + + + + 20 + 241 + + + + + + + + + + + + + KButtonGroup + QGroupBox +
kbuttongroup.h
+ 1 +
+ + KIntNumInput + QWidget +
knuminput.h
+
+ + KLineEdit + QLineEdit +
klineedit.h
+
+ + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + + + kcfg_RequiresAuthentication + toggled(bool) + kcfg_UserName + setEnabled(bool) + + + 83 + 196 + + + 127 + 227 + + + + + kcfg_RequiresAuthentication + toggled(bool) + password + setEnabled(bool) + + + 59 + 206 + + + 105 + 269 + + + + + kcfg_RequiresAuthentication + toggled(bool) + label_5 + setEnabled(bool) + + + 48 + 213 + + + 46 + 232 + + + + + kcfg_RequiresAuthentication + toggled(bool) + label_4 + setEnabled(bool) + + + 140 + 201 + + + 83 + 265 + + + + + kcfg_RequiresAuthentication + toggled(bool) + kcfg_StorePassword + setEnabled(bool) + + + 232 + 209 + + + 242 + 305 + + + + +
diff --git a/kdepim-runtime/resources/nntp/nntpcollectionattribute.cpp b/kdepim-runtime/resources/nntp/nntpcollectionattribute.cpp new file mode 100644 index 00000000..6aba4b34 --- /dev/null +++ b/kdepim-runtime/resources/nntp/nntpcollectionattribute.cpp @@ -0,0 +1,61 @@ +/* + 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 "nntpcollectionattribute.h" + +#include + +NntpCollectionAttribute::NntpCollectionAttribute() + : Attribute(), + mLastArticleId( 0 ) +{ +} + +QByteArray NntpCollectionAttribute::type() const +{ + return "NNTP"; +} + +NntpCollectionAttribute * NntpCollectionAttribute::clone() const +{ + NntpCollectionAttribute *attr = new NntpCollectionAttribute(); + attr->mLastArticleId = mLastArticleId; + return attr; +} + +QByteArray NntpCollectionAttribute::serialized() const +{ + QByteArray data = QByteArray::number( mLastArticleId ); + return data; +} + +void NntpCollectionAttribute::deserialize(const QByteArray & data) +{ + mLastArticleId = data.toInt(); +} + +int NntpCollectionAttribute::lastArticle() const +{ + return mLastArticleId; +} + +void NntpCollectionAttribute::setLastArticle(int last) +{ + mLastArticleId = last; +} diff --git a/kdepim-runtime/resources/nntp/nntpcollectionattribute.h b/kdepim-runtime/resources/nntp/nntpcollectionattribute.h new file mode 100644 index 00000000..c874b9ef --- /dev/null +++ b/kdepim-runtime/resources/nntp/nntpcollectionattribute.h @@ -0,0 +1,53 @@ +/* + 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_NNTPCOLLECTIONATTRIBUTE_H +#define AKONADI_NNTPCOLLECTIONATTRIBUTE_H + +#include + +/** + Collection attribute to store information needed for incremental + collection updates. +*/ +class NntpCollectionAttribute : public Akonadi::Attribute +{ + public: + NntpCollectionAttribute(); + QByteArray type() const; + NntpCollectionAttribute* clone() const; + QByteArray serialized() const; + void deserialize( const QByteArray &data ); + + /** + Returns the serial number of the last fetched article. + */ + int lastArticle() const; + + /** + Sets the serial number of the last fetched serial number. + @param last Serial number of the last fetched article. + */ + void setLastArticle( int last ); + + private: + qint32 mLastArticleId; +}; + +#endif diff --git a/kdepim-runtime/resources/nntp/nntpresource.cpp b/kdepim-runtime/resources/nntp/nntpresource.cpp new file mode 100644 index 00000000..5b19bf33 --- /dev/null +++ b/kdepim-runtime/resources/nntp/nntpresource.cpp @@ -0,0 +1,335 @@ +/* + 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 "nntpresource.h" +#include "nntpcollectionattribute.h" +#include "configdialog.h" +#include "settings.h" +#include "settingsadaptor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +typedef boost::shared_ptr MessagePtr; + +using namespace Akonadi; + +NntpResource::NntpResource(const QString & id) + : ResourceBase( id ) +{ + AttributeFactory::registerAttribute(); + changeRecorder()->fetchCollection( true ); + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); +} + +NntpResource::~ NntpResource() +{ +} + +bool NntpResource::retrieveItem(const Akonadi::Item& item, const QSet &parts) +{ + Q_UNUSED( parts ); + KIO::Job* job = KIO::storedGet( KUrl( item.remoteId() ), KIO::NoReload, KIO::HideProgressInfo ); + setupKioJob( job ); + connect( job, SIGNAL(result(KJob*)), SLOT(fetchArticleResult(KJob*)) ); + return true; +} + +void NntpResource::retrieveCollections() +{ + remoteCollections.clear(); + Collection rootCollection; + rootCollection.setParentCollection( Collection::root() ); + rootCollection.setRemoteId( baseUrl().url() ); + rootCollection.setName( Settings::self()->name() ); + CachePolicy policy; + policy.setInheritFromParent( false ); + policy.setSyncOnDemand( true ); + policy.setLocalParts( QStringList( MessagePart::Envelope ) ); + rootCollection.setCachePolicy( policy ); + QStringList contentTypes; + contentTypes << Collection::mimeType(); + rootCollection.setContentMimeTypes( contentTypes ); + remoteCollections << rootCollection; + + KUrl url = baseUrl(); + QDate lastList = Settings::self()->lastGroupList().date(); + if ( lastList.isValid() ) { + mIncremental = true; + url.addQueryItem( "since", QString( "%1%2%3 000000" ) + .arg( lastList.year() % 100, 2, 10, QChar( '0' ) ) + .arg( lastList.month(), 2, 10, QChar( '0' ) ) + .arg( lastList.day(), 2, 10, QChar( '0' ) ) ); + } else { + mIncremental = false; + } + + KIO::ListJob* job = KIO::listDir( url, KIO::HideProgressInfo, true ); + setupKioJob( job ); + connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), + SLOT(listGroups(KIO::Job*,KIO::UDSEntryList)) ); + connect( job, SIGNAL(result(KJob*)), SLOT(listGroupsResult(KJob*)) ); +} + +void NntpResource::retrieveItems( const Akonadi::Collection & col ) +{ + if ( !(col.contentMimeTypes().count() == 1 && col.contentMimeTypes().first() == "message/news" ) ) { + // not a newsgroup, skip it + itemsRetrievalDone(); + return; + } + + KUrl url = baseUrl(); + url.setPath( col.remoteId() ); + + NntpCollectionAttribute *attr = col.attribute(); + if ( attr && attr->lastArticle() > 0 ) + url.addQueryItem( "first", QString::number( attr->lastArticle() + 1 ) ); + else + url.addQueryItem( "max", QString::number( Settings::self()->maxDownload() ) ); + + KIO::ListJob* job = KIO::listDir( url, KIO::HideProgressInfo, true ); + setupKioJob( job ); + connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), + SLOT(listGroup(KIO::Job*,KIO::UDSEntryList)) ); + connect( job, SIGNAL(result(KJob*)), SLOT(listGroupResult(KJob*)) ); +} + +void NntpResource::listGroups(KIO::Job * job, const KIO::UDSEntryList & list) +{ + Q_UNUSED( job ); + QString name; + QStringList contentTypes; + contentTypes << "message/news"; + for( KIO::UDSEntryList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it ) { + name = (*it).stringValue( KIO::UDSEntry::UDS_NAME ); + if ( name.isEmpty() ) + continue; + + Collection c; + c.setRemoteId( name ); + c.setContentMimeTypes( contentTypes ); + + if ( Settings::self()->flatHierarchy() ) { + c.setName( name ); + c.parentCollection().setRemoteId( baseUrl().url() ); + } else { + const QStringList path = name.split( '.' ); + Q_ASSERT( !path.isEmpty() ); + c.setName( path.last() ); + c.parentCollection().setRemoteId( findParent( path ) ); + } + + remoteCollections << c; + } +} + +void NntpResource::listGroupsResult(KJob * job) +{ + if ( job->error() ) { + emit error( job->errorString() ); + return; + } else { + Settings::self()->setLastGroupList( QDateTime::currentDateTime() ); + } + if ( mIncremental ) + collectionsRetrievedIncremental( remoteCollections, Collection::List() ); + else + collectionsRetrieved( remoteCollections ); +} + +void NntpResource::listGroup(KIO::Job * job, const KIO::UDSEntryList & list) +{ + Q_UNUSED( job ); + foreach ( const KIO::UDSEntry &entry, list ) { + KUrl url = baseUrl(); + url.setPath( currentCollection().remoteId() + '/' + entry.stringValue( KIO::UDSEntry::UDS_NAME ) ); + Item item; + item.setRemoteId( url.url() ); + item.setMimeType( "message/news" ); + + KMime::NewsArticle *art = new KMime::NewsArticle(); + foreach ( uint field, entry.listFields() ) { + if ( field >= KIO::UDSEntry::UDS_EXTRA && field <= KIO::UDSEntry::UDS_EXTRA_END ) { + const QString value = entry.stringValue( field ); + int pos = value.indexOf( ':' ); + if ( pos >= value.length() - 1 ) + continue; // value is empty + const QString hdrName = value.left( pos ); + const QString hdrValue = value.right( value.length() - ( hdrName.length() + 2 ) ); + + if ( hdrName == "Subject" ) { + art->subject()->from7BitString( hdrValue.toLatin1() ); + if ( art->subject()->isEmpty() ) + art->subject()->fromUnicodeString( i18n( "no subject" ), art->defaultCharset() ); + } else if ( hdrName == "From" ) { + art->from()->from7BitString( hdrValue.toLatin1() ); + } else if ( hdrName == "Date" ) { + art->date()->from7BitString( hdrValue.toLatin1() ); + } else if ( hdrName == "Message-ID" ) { + art->messageID()->from7BitString( hdrValue.simplified().toLatin1() ); + } else if ( hdrName == "References" ) { + if ( !hdrValue.isEmpty() ) + art->references()->from7BitString( hdrValue.toLatin1() ); + } else if ( hdrName == "Lines" ) { + art->lines()->setNumberOfLines( hdrValue.toInt() ); + } else { + // optional extra headers + art->setHeader( new KMime::Headers::Generic( hdrName.toLatin1(), art, hdrValue.toLatin1() ) ); + } + } + } + + item.setPayload( MessagePtr( art ) ); + ItemCreateJob *append = new ItemCreateJob( item, currentCollection() ); + // TODO: check error + Q_UNUSED( append ) + } +} + +void NntpResource::listGroupResult(KJob * job) +{ + if ( job->error() ) { + emit error( job->errorString() ); + } else { + // store last serial number + Collection col = currentCollection(); + NntpCollectionAttribute *attr = col.attribute( Collection::AddIfMissing ); + KIO::Job *j = static_cast( job ); + if ( j->metaData().contains( "LastSerialNumber" ) ) + attr->setLastArticle( j->metaData().value( "LastSerialNumber" ).toInt() ); + CollectionModifyJob *modify = new CollectionModifyJob( col ); + // TODO: check result signal + Q_UNUSED( modify ) + } + + itemsRetrievalDone(); +} + +KUrl NntpResource::baseUrl() const +{ + KUrl url; + if ( Settings::self()->encryption() == Settings::SSL ) + url.setProtocol( "nntps" ); + else + url.setProtocol( "nntp" ); + url.setHost( Settings::self()->server() ); + url.setPort( Settings::self()->port() ); + if ( Settings::self()->requiresAuthentication() ) { + url.setUser( Settings::self()->userName() ); + url.setPass( Settings::self()->password() ); + } + return url; +} + +void NntpResource::fetchArticleResult(KJob * job) +{ + if ( job->error() ) { + emit error( job->errorString() ); + return; + } + KIO::StoredTransferJob *j = static_cast( job ); + KMime::Message *msg = new KMime::Message(); + msg->setContent( KMime::CRLFtoLF( j->data() ) ); + msg->parse(); + Item item = currentItem(); + item.setMimeType( "message/news" ); + item.setPayload( MessagePtr( msg ) ); + itemRetrieved( item ); +} + +void NntpResource::configure( WId windowId ) +{ + ConfigDialog dlg; + if ( windowId ) { + KWindowSystem::setMainWindow( &dlg, windowId ); + } + dlg.setWindowIcon( KIcon( "message-news" ) ); + if ( dlg.exec() ) { + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + + if ( !Settings::self()->name().isEmpty() ) + setName( Settings::self()->name() ); +} + +void NntpResource::setupKioJob(KIO::Job * job) const +{ + Q_ASSERT( job ); + if ( Settings::self()->encryption() == Settings::TLS ) + job->addMetaData( "tls", "on" ); + else + job->addMetaData( "tls", "off" ); + // TODO connect percent and status message signals to something +} + +QString NntpResource::findParent(const QStringList & _path) +{ + QStringList path = _path; + path.removeLast(); + if ( path.isEmpty() ) + return baseUrl().url(); + QString rid = path.join( "." ); + foreach ( const Collection &col, remoteCollections ) + if ( col.remoteId() == rid ) + return col.remoteId(); + Collection parent; + parent.setRemoteId( rid ); + QStringList ct; + ct << Collection::mimeType(); + parent.setContentMimeTypes( ct ); + parent.parentCollection().setRemoteId( findParent( path ) ); + parent.setName( path.last() ); + remoteCollections << parent; + return parent.remoteId(); +} + +void NntpResource::collectionChanged(const Akonadi::Collection & collection) +{ + if ( collection.remoteId() == baseUrl().url() && !collection.name().isEmpty() ) { + Settings::self()->setName( collection.name() ); + setName( collection.name() ); + } + changeCommitted( collection ); +} + +AKONADI_RESOURCE_MAIN( NntpResource ) + diff --git a/kdepim-runtime/resources/nntp/nntpresource.desktop b/kdepim-runtime/resources/nntp/nntpresource.desktop new file mode 100644 index 00000000..a6be87b2 --- /dev/null +++ b/kdepim-runtime/resources/nntp/nntpresource.desktop @@ -0,0 +1,99 @@ +[Desktop Entry] +Name=Usenet Newsgroups (NNTP) +Name[ar]=المجموعات الإخبارية يوزنت (NNTP) +Name[bs]=Usenet grupe za novosti (NNTP) +Name[ca]=Grups de notícies d'Usenet (NNTP) +Name[ca@valencia]=Grups de notícies d'Usenet (NNTP) +Name[cs]=Diskuzní skupiny (NNTP) +Name[da]=Usenet-nyhedsgrupper (NNTP) +Name[de]=Usenet Newsgruppen (NNTP) +Name[el]=Ομάδες συζήτησης Usenet (NNTP) +Name[en_GB]=Usenet Newsgroups (NNTP) +Name[es]=Grupos de noticias Usenet (NNTP) +Name[et]=Useneti uudistegrupid (NNTP) +Name[fi]=Usenet-keskusteluryhmät (NNTP) +Name[fr]=Forum de discussions « usenet » (NNTP) +Name[ga]=Grúpaí Nuachta Usenet (NNTP) +Name[gl]=Grupos de noticias de Usenet (NNTP) +Name[hu]=Usenet-hírcsoportok (NNTP) +Name[ia]=Gruppos de novas in Usenet (NNTP) +Name[it]=Gruppi di discussione Usenet (NNTP) +Name[ja]=Usenet ニュースグループ (NNTP) +Name[kk]=Usenet жаңалық топтары (NNTP) +Name[km]=Usenet Newsgroups (NNTP) +Name[ko]=유즈넷 뉴스그룹 (NNTP) +Name[lt]=Usenet naujienų grupÄ—s (NNTP) +Name[lv]=Usenet intereÅ¡grupas (NNTP) +Name[nb]=Njusgrupper (NNTP) +Name[nds]=Usenet-Narichtenkrinken (NNTP) +Name[nl]=Usenet-nieuwsgroepen (NNTP) +Name[nn]=Temagrupper (NNTP) +Name[pl]=Grupy Usenet (NNTP) +Name[pt]=Grupos de Notícias da Usenet (NNTP) +Name[pt_BR]=Grupos de notícias da Usenet (NNTP) +Name[ro]=Grupuri de È™tiri Usenet (NNTP) +Name[ru]=Телеконференции Usenet (NNTP) +Name[sk]=Diskusné skupiny Usenet (NNTP) +Name[sl]=NoviÄarske skupine (NNTP) +Name[sr]=Јузнетове групе веÑти (ÐÐТП) +Name[sr@ijekavian]=Јузнетове групе вијеÑти (ÐÐТП) +Name[sr@ijekavianlatin]=Usenetove grupe vijesti (NNTP) +Name[sr@latin]=Usenetove grupe vesti (NNTP) +Name[sv]=Usenet-nyhetsgrupper (NNTP) +Name[tr]=Usenet Haber Grubu (NNTP) +Name[uk]=Групи новин Usenet (NNTP) +Name[x-test]=xxUsenet Newsgroups (NNTP)xx +Name[zh_CN]=Usenet 新闻组(NNTP) +Name[zh_TW]=Usenet æ–°èžç¾¤çµ„(NNTP) +Comment=Makes it possible to read articles from a news server +Comment[ar]=تمكÙÙ† قراءة المقالات من خادم الأخبار +Comment[bs]=Omogućava Äitanje Älanaka sa servera novosti +Comment[ca]=Facilita la lectura d'articles des d'un servidor de notícies +Comment[ca@valencia]=Facilita la lectura d'articles des d'un servidor de notícies +Comment[cs]=Umožňuje Äíst příspÄ›vky z diskuzního serveru +Comment[da]=Muliggør løsning af artikler fra en nyhedsserver +Comment[de]=Lesen von Artikeln von einem News-Server +Comment[el]=Καθιστά δυνατή την ανάγνωση άÏθÏων από έναν διακομιστή νέων +Comment[en_GB]=Makes it possible to read articles from a news server +Comment[es]=Hace posible leer artículos desde un servidor de noticias +Comment[et]=Võimaldab lugeda artikleid uudisteserverist +Comment[fi]=Mahdollistaa keskusteluryhmäpalvelimen artikkelin lukemisen +Comment[fr]=Permet de lire des articles à partir d'un serveur de nouvelles +Comment[gl]=Permite ler artigos desde un servidor de novas. +Comment[hu]=Híreket tud letölteni NNTP-hírkiszolgálókról +Comment[ia]=Face lo possibile leger articulos de un servitor de novas +Comment[it]=Permette la lettura di articoli da un server di gruppi di discussione +Comment[ja]=ニュースサーãƒã‹ã‚‰è¨˜äº‹ã‚’読ã‚るよã†ã«ã—ã¾ã™ +Comment[kk]=Жаңалық Ñерверіндегі мақалаларды оқуға мүмкіндік береді +Comment[km]=ធ្វើ​ឲ្យ​អាច​អាន​ចំណងជើង​ពី​ម៉ាស៊ីន​បម្រើ​ពáŸážáŸŒáž˜áž¶áž“​បាន +Comment[ko]=뉴스 ì„œë²„ì˜ ê¸€ì„ ê°€ì ¸ì˜µë‹ˆë‹¤ +Comment[lt]=Sudaro galimybes skaityti straipsnius iÅ¡ naujienų serverio +Comment[lv]=Ä»auj lasÄ«t rakstus no news servera +Comment[nb]=Gjør det mulig Ã¥ lese artikler fra en newstjener +Comment[nds]=MIt dit Moduul laat sik Artikels vun Narichtenservers lesen +Comment[nl]=Maakt het mogelijk om artikelen van een nieuwsserver te halen +Comment[nn]=Gjer det mogleg Ã¥ lesa artiklar frÃ¥ ein temagruppetenar +Comment[pl]=Umożliwia czytanie wiadomoÅ›ci z serwerów niusów +Comment[pt]=Possibilita a leitura de artigos de um servidor de notícias +Comment[pt_BR]=Possibilita a leitura de artigos de um servidor de notícias +Comment[ro]=Face posibilă citirea articolelor de pe un server de È™tiri +Comment[ru]=Чтение Ñтатей Ñ Ñерверов телеконференций +Comment[sk]=Umožňuje Äítanie Älánkov zo servera noviniek +Comment[sl]=OmogoÄa branje sestavkov na strežniku z noviÄarskimi skupinami +Comment[sr]=Омогућава читање чланака Ñа Ñервера веÑти +Comment[sr@ijekavian]=Омогућава читање чланака Ñа Ñервера вијеÑти +Comment[sr@ijekavianlatin]=Omogućava Äitanje Älanaka sa servera vijesti +Comment[sr@latin]=Omogućava Äitanje Älanaka sa servera vesti +Comment[sv]=Gör det möjligt att läsa artiklar frÃ¥n en nyhetsserver +Comment[tr]=Bir haber sunucusundan haberleri okumak için bir araç +Comment[uk]=Робить можливим Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñтатей з Ñервера новин +Comment[x-test]=xxMakes it possible to read articles from a news serverxx +Comment[zh_CN]=å¯ä»¥ä»Žæ–°é—»ç»„æœåŠ¡å™¨ä¸Šè¯»å–文章 +Comment[zh_TW]=從新èžä¼ºæœå™¨è®€å–文章 +Type=AkonadiResource +Exec=akonadi_nntp_resource +Icon=message-news + +X-Akonadi-MimeTypes=message/news +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_nntp_resource diff --git a/kdepim-runtime/resources/nntp/nntpresource.h b/kdepim-runtime/resources/nntp/nntpresource.h new file mode 100644 index 00000000..a6826c32 --- /dev/null +++ b/kdepim-runtime/resources/nntp/nntpresource.h @@ -0,0 +1,73 @@ +/* + 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_NNTPRESOURCE_H +#define AKONADI_NNTPRESOURCE_H + +#include + +#include +#include + +class NntpResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + explicit NntpResource( const QString &id ); + ~NntpResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + void collectionChanged( const Akonadi::Collection &collection ); + + private: + /** + Returns the base url used for all KIO operations, containing + protocol, hostname and port. + */ + KUrl baseUrl() const; + + void setupKioJob( KIO::Job *job ) const; + + QString findParent( const QStringList &_path ); + + private slots: + void listGroups( KIO::Job* job, const KIO::UDSEntryList &list ); + void listGroupsResult( KJob* job ); + + void listGroup( KIO::Job* job, const KIO::UDSEntryList &list ); + void listGroupResult( KJob* job ); + + void fetchArticleResult( KJob* job ); + + private: + Akonadi::Collection::List remoteCollections; + + bool mIncremental; +}; + +#endif diff --git a/kdepim-runtime/resources/nntp/nntpresource.kcfg b/kdepim-runtime/resources/nntp/nntpresource.kcfg new file mode 100644 index 00000000..f221d16c --- /dev/null +++ b/kdepim-runtime/resources/nntp/nntpresource.kcfg @@ -0,0 +1,83 @@ + + + + + + + + + + + + 119 + + + + + None + + + + + + + + + + + + + + + + + + + false + + + + + + + + false + + + + + + + + + 5 + + + + + + + + true + + + + true + + + + true + + + + + + + + + + diff --git a/kdepim-runtime/resources/nntp/settings.cpp b/kdepim-runtime/resources/nntp/settings.cpp new file mode 100644 index 00000000..2fb46320 --- /dev/null +++ b/kdepim-runtime/resources/nntp/settings.cpp @@ -0,0 +1,59 @@ +/* + Copyright (c) 2008 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 "settings.h" + +#include + +class SettingsHelper +{ + public: + SettingsHelper() : q(0) {} + ~SettingsHelper() { delete q; } + Settings *q; +}; + +K_GLOBAL_STATIC(SettingsHelper, s_globalSettings) + +Settings *Settings::self() +{ + if ( !s_globalSettings->q ) { + new Settings; + s_globalSettings->q->readConfig(); + } + + return s_globalSettings->q; +} + +Settings::Settings() : SettingsBase() +{ + Q_ASSERT( !s_globalSettings->q ); + s_globalSettings->q = this; +} + +QString Settings::password() const +{ + return mPassword; +} + +void Settings::setPassword(const QString & password) +{ + mPassword = password; +} + diff --git a/kdepim-runtime/resources/nntp/settings.h b/kdepim-runtime/resources/nntp/settings.h new file mode 100644 index 00000000..253f7a3d --- /dev/null +++ b/kdepim-runtime/resources/nntp/settings.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2008 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 SETTINGS_H +#define SETTINGS_H + +#include "settingsbase.h" + +class Settings : public SettingsBase +{ + public: + Settings(); + static Settings *self(); + + // TODO: temporary, wallet support + QString password() const; + void setPassword( const QString &password ); + + private: + QString mPassword; +}; + +#endif diff --git a/kdepim-runtime/resources/nntp/settingsbase.kcfgc b/kdepim-runtime/resources/nntp/settingsbase.kcfgc new file mode 100644 index 00000000..05491f29 --- /dev/null +++ b/kdepim-runtime/resources/nntp/settingsbase.kcfgc @@ -0,0 +1,8 @@ +File=nntpresource.kcfg +ClassName=SettingsBase +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true diff --git a/kdepim-runtime/resources/openxchange/CMakeLists.txt b/kdepim-runtime/resources/openxchange/CMakeLists.txt new file mode 100644 index 00000000..62be1af7 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/CMakeLists.txt @@ -0,0 +1,60 @@ +set( openxchangeresource_SRCS + oxa/connectiontestjob.cpp + oxa/contactutils.cpp + oxa/davmanager.cpp + oxa/davutils.cpp + oxa/folder.cpp + oxa/foldercreatejob.cpp + oxa/folderdeletejob.cpp + oxa/foldermodifyjob.cpp + oxa/foldermovejob.cpp + oxa/folderrequestjob.cpp + oxa/foldersrequestdeltajob.cpp + oxa/foldersrequestjob.cpp + oxa/folderutils.cpp + oxa/incidenceutils.cpp + oxa/object.cpp + oxa/objectcreatejob.cpp + oxa/objectdeletejob.cpp + oxa/objectmodifyjob.cpp + oxa/objectmovejob.cpp + oxa/objectrequestjob.cpp + oxa/objectsrequestdeltajob.cpp + oxa/objectsrequestjob.cpp + oxa/objectutils.cpp + oxa/oxutils.cpp + oxa/oxerrors.cpp + oxa/updateusersjob.cpp + oxa/user.cpp + oxa/users.cpp + oxa/useridrequestjob.cpp + oxa/usersrequestjob.cpp + + configdialog.cpp + openxchangeresource.cpp +) + +kde4_add_ui_files( openxchangeresource_SRCS configdialog.ui ) + +install( FILES openxchangeresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_kcfg_files(openxchangeresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/openxchangeresource.kcfg org.kde.Akonadi.OpenXchange.Settings) +qt4_add_dbus_adaptor(openxchangeresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.OpenXchange.Settings.xml settings.h Settings +) + +kde4_add_executable(akonadi_openxchange_resource RUN_UNINSTALLED ${openxchangeresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_openxchange_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_openxchange_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.OpenXchange") + set_target_properties(akonadi_openxchange_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi OpenXchange Resource") +endif () + + +target_link_libraries(akonadi_openxchange_resource ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_KABC_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_KCALCORE_LIBS} ${KDEPIMLIBS_AKONADI_CONTACT_LIBS} ) + +install(TARGETS akonadi_openxchange_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) + +add_subdirectory(icons) diff --git a/kdepim-runtime/resources/openxchange/Messages.sh b/kdepim-runtime/resources/openxchange/Messages.sh new file mode 100644 index 00000000..3f07585a --- /dev/null +++ b/kdepim-runtime/resources/openxchange/Messages.sh @@ -0,0 +1,3 @@ +#!/bin/sh +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_openxchange_resource.pot diff --git a/kdepim-runtime/resources/openxchange/configdialog.cpp b/kdepim-runtime/resources/openxchange/configdialog.cpp new file mode 100644 index 00000000..9404bf69 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/configdialog.cpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2009 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. +*/ + +#include "configdialog.h" + +#include "oxa/connectiontestjob.h" +#include "settings.h" +#include "ui_configdialog.h" + +#include +#include +#include +#include +#include + +ConfigDialog::ConfigDialog( WId windowId ) + : KDialog() +{ + if ( windowId ) + KWindowSystem::setMainWindow( this, windowId ); + + setButtons( Ok | Cancel | User1 ); + setButtonText( User1, i18n( "About..." ) ); + + setCaption( i18n( "Open-Xchange Configuration" ) ); + + Ui::ConfigDialog ui; + ui.setupUi( mainWidget() ); + + ui.kcfg_BaseUrl->setWhatsThis( i18n( "Enter the http or https URL to your Open-Xchange installation here." ) ); + ui.kcfg_Username->setWhatsThis( i18n( "Enter the username of your Open-Xchange account here." ) ); + ui.kcfg_Password->setWhatsThis( i18n( "Enter the password of your Open-Xchange account here." ) ); + + mServerEdit = ui.kcfg_BaseUrl; + mUserEdit = ui.kcfg_Username; + mPasswordEdit = ui.kcfg_Password; + mCheckConnectionButton = ui.checkConnectionButton; + + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); + connect( this, SIGNAL(user1Clicked()), this, SLOT(showAboutDialog()) ); + connect( mServerEdit, SIGNAL(textChanged(QString)), SLOT(updateButtonState()) ); + connect( mUserEdit, SIGNAL(textChanged(QString)), SLOT(updateButtonState()) ); + connect( mCheckConnectionButton, SIGNAL(clicked()), SLOT(checkConnection()) ); + + setInitialSize( QSize( 410, 200 ) ); +} + +void ConfigDialog::save() +{ + mManager->updateSettings(); + Settings::self()->writeConfig(); +} + +void ConfigDialog::showAboutDialog() +{ + KAboutData aboutData( "ox", "", ki18n( "Open-Xchange" ), "0.1", + ki18n( "Akonadi Open-Xchange Resource" ), + KAboutData::License_LGPL, + ki18n( "(c) 2009 by Tobias Koenig (credativ GmbH)" ) ); + aboutData.addAuthor( ki18n( "Tobias Koenig" ), ki18n( "Current maintainer" ), "tokoe@kde.org" ); + aboutData.addCredit( ki18n( "credativ GmbH" ), ki18n( "Funded and supported" ), 0, "http://www.credativ.com" ); + + KAboutApplicationDialog dlg( &aboutData, this ); + dlg.exec(); +} + +void ConfigDialog::updateButtonState() +{ + mCheckConnectionButton->setEnabled( !mServerEdit->text().isEmpty() && !mUserEdit->text().isEmpty() ); +} + +void ConfigDialog::checkConnection() +{ + OXA::ConnectionTestJob *job = new OXA::ConnectionTestJob( mServerEdit->text(), mUserEdit->text(), + mPasswordEdit->text(), this ); + connect( job, SIGNAL(result(KJob*)), SLOT(checkConnectionJobFinished(KJob*)) ); + job->start(); + + QApplication::setOverrideCursor( Qt::WaitCursor ); +} + +void ConfigDialog::checkConnectionJobFinished( KJob *job ) +{ + QApplication::restoreOverrideCursor(); + + if ( job->error() ) { + KMessageBox::error( this, i18n( "Unable to connect: %1", job->errorText() ), i18n( "Connection error" ) ); + } else { + KMessageBox::information( this, i18n( "Tested connection successfully." ), i18n( "Connection success" ) ); + } +} diff --git a/kdepim-runtime/resources/openxchange/configdialog.h b/kdepim-runtime/resources/openxchange/configdialog.h new file mode 100644 index 00000000..854d3537 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/configdialog.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +class KConfigDialogManager; +class KJob; +class KLineEdit; + +class ConfigDialog : public KDialog +{ + Q_OBJECT + + public: + explicit ConfigDialog( WId windowId ); + + private Q_SLOTS: + void save(); + void showAboutDialog(); + void updateButtonState(); + void checkConnection(); + void checkConnectionJobFinished( KJob* ); + + private: + KConfigDialogManager *mManager; + KLineEdit *mServerEdit; + KLineEdit *mUserEdit; + KLineEdit *mPasswordEdit; + QPushButton *mCheckConnectionButton; +}; + +#endif diff --git a/kdepim-runtime/resources/openxchange/configdialog.ui b/kdepim-runtime/resources/openxchange/configdialog.ui new file mode 100644 index 00000000..62c2de3d --- /dev/null +++ b/kdepim-runtime/resources/openxchange/configdialog.ui @@ -0,0 +1,143 @@ + + + ConfigDialog + + + + 0 + 0 + 410 + 200 + + + + Open-Xchange Configuration + + + + + + Connection + + + false + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + Server URL: + + + + + + + The URL of the Open-Xchange server, should be something like https://myserver.org/ + + + The URL of the Open-Xchange server, should be something like https://myserver.org/ + + + https://myserver.org + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + Username: + + + + + + + The username that is used to log into the Open-Xchange server + + + The username that is used to log into the Open-Xchange server + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + Password: + + + + + + + The password that is used to log into the Open-Xchange server + + + The password that is used to log into the Open-Xchange server + + + QLineEdit::Password + + + + + + + false + + + Test Connection... + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/openxchange/icons/CMakeLists.txt b/kdepim-runtime/resources/openxchange/icons/CMakeLists.txt new file mode 100644 index 00000000..02e3c9ec --- /dev/null +++ b/kdepim-runtime/resources/openxchange/icons/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons(${ICON_INSTALL_DIR}) diff --git a/kdepim-runtime/resources/openxchange/icons/hi128-app-ox.png b/kdepim-runtime/resources/openxchange/icons/hi128-app-ox.png new file mode 100644 index 0000000000000000000000000000000000000000..20b83dda319b29f2287b289c83bcf9c69890374c GIT binary patch literal 4840 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 NbTth$DiD^T{{uk35=#I8 literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/openxchange/icons/hi16-app-ox.png b/kdepim-runtime/resources/openxchange/icons/hi16-app-ox.png new file mode 100644 index 0000000000000000000000000000000000000000..294859c22109518f93b99a61d9da74394596e469 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJXMsm#F#`jC2M9BYomkoi6wC?m z32_C|lG^rC+ICVp_EH8O(%RNCI(Bj<-tvad3P!Gqu33t%*-EZi$|kPLR>8_n$;wVC z%FZduZrLiXnJR8sDnN#NwyJA}nnR45W30MkoVs(Ox_hR&N0x?DoTh7vrdx`ZSGJBv zs*X>dpGv(V&_2eJAirP+hi5m^fE;g67sn6@$*l)l85;~3ST-Kk%+r1M`~3-h$3ymQ zPBBl!*>^}SliiuO=T8amhny3?oTiqZ*sS=Pd)s+&g^de)jJK(t5If@GGgGN8zD6;f W`G(G4whKUG89ZJ6T-G@yGywp5-cii} literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resources/openxchange/icons/hi32-app-ox.png b/kdepim-runtime/resources/openxchange/icons/hi32-app-ox.png new file mode 100644 index 0000000000000000000000000000000000000000..b3f21c97a331326789361fc114dd4b3a6c5658b1 GIT binary patch literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoCO|{#S9Gm9U#moc4BE8Q1D@Z zPlzj!meaFSG;mOK$x?L5R&>o&bj?z91u}A!jNOz>Jd{j4l}tU9T(Xp0GnHI{WVW)Y zr?N|?vMUf}DFeA~naXZiDy|tSZkcN4UTRhWYSsa2?wRUVzUo%~>ed13Hh~&8ftuzX znqIk@Uin(yxmrF2+IGR(-Z|Rdx!OKJl&9kursI*Scrc0~GYh*YVBQ zbqv?_OxN|y(DlyN^~u%s&C~VG*YnQS^T{@FOEmOKG4#na_D(kTNj0(eHuH)z3obPG ziZl0)HwQ9;i%QB(mI6J=SQ6wH%;50sMjDWF&eO#)q~g}wb2t5%3`ATnE`D&=VykQ5 zoIpnPf_XQQoP3BY4}~Km4;3tOah{@h!WwZiY~c|u9=FiS&FXNimo|| zZkdX1*-9>%N-kMSEx!$?j)Jrh)lp0oms6;pV~l7C;nQXc?4e8I*4s3`7N%Aq7@mF;+pjRv|^! z;UzW!X*MDGHW6iZ-eGpZY4$;>4uRrEa0A?!hVUVL+7T0Yu>`A+Z^qmoEeZBZ;vj z$S;_|;n|HeAbXRii(^Q|t+&0`i$n|sT%{v;za9D_dgk1Wm)jx_WH+6Z^f_nwe2$M_ z_Ov<0zp_%Me&Bn+ZXaB%5_)&t?d2}2s{hTGPrvRuX~{Gd&w0;#-QLeV?6Nxj?26}? z#s6?vf8R0j`QF(z_trm1JYsvGsd3+I7w@Z1>N@}4mFM*vGsV9AoFG%0z3R}Tbxwve zli!se?V7XJV|%*lxD7Yevos3-k!Ly_;K!cf^5A0K z1@T0SrJ9y;{(tH=%BwLZqh_uD>tu0C*hvCEguH>I++>IKbCD}M2D@%50Ppi9rh Zd*=B~T{>%04lpViJYD@<);T3K0RZ>}Xc_=0@9XhDMIh>w)D1_QbeG(*wR98Z(C5HKr1Ss zVStbc;)ZNI62U2MEGk2s!f=ym6j|^w6-G9lg(?VG_#OG@vZ(?E-SBgRZ%Fea;1DLAdhPiVl85! z0y7nmv{HeM3i7CknTilpa*tT4h>a?-(14i+2pS*Y;**tBW5~6(8*843c)x8n<1EhU<(A3)&{|O5HLXihkzLZ1OzM) zutEp{Ar=N~V!${9HZx#?0b3Zr!~i%0m>Gaz0Mc5SVha<-nE+=ZCMH6XU`lYN#LR+C zEI_aj6AQsvh?#{DEGfZ`McD!w8{lkUW&?r^ENsNYM)1&BG*n{YfSfRiIZTR&%go_u z-g?v)Aw(lE+XiuFWMX<`Vn(DmJyM(zm7t0uDb9?7Golk!(TVD4iFzYY$B==Yh7bFGIQ4EeZO{CjlRZ^1)F8d>ESqW-%xu4Y#yuqD{3lq#rE8my_t_7e zw@!4)miQBm^Mdx)S@pN^y*DP|zy{T@sh5Y;cUv@XyePt2DfV)c$c=SnPONOc7a#O2 z?jP~8ChwyI8$>JppB8(iz7M0UZ?EcX8SB;7y?gkL{&rnO=Z@;Ry_*+WIE-nVsy;8; zAijGh(EBS#^0@QQ5B#!Y?9k7mS^!x3jZF4P2{^zU*&g`x^^!UiWHMAf0 z)&3Ul#W32r*kJb<vz z#NSwgDt3D`tbXp=a7)=Yu4?G$%`0Du?dbkQ*WTI183^jZJAMrNEB+8R5#M9%n8|Hw z-1+`XW!I_Lp``_%w|>FRoVb>I`tgwcwOJ?my!qD^Q4!Gx;xGSwH3m{tC8>@U4}0wE z37lz6JkHm3L>g1ri@wWe>p4bEzr$7~EgkVG2)MR3fX8-*c>LVzy6x4vz%r~hZ@7Ht zilNlKoy%=*+&%6rsvpAE@Mjh;Sju9wfm1aXD31>6M=;Ot@+vF24mdmC{$JG>bE{Ht z>*H?FCpvukEsGMnMYL-8xtcHjg`4?%6H**XTX;2J?LthhNeE|V^@luC4vv8vb_O2Y zBori=f~RaBY**+DY(}`^r&R*=EO+SZ7wRJY;k22d9c}aD?kld``8TC{da-oZ-8} + + 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 "openxchangeresource.h" + +#include "configdialog.h" +#include "settingsadaptor.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; + +class RemoteInformation +{ + public: + RemoteInformation( qlonglong objectId, OXA::Folder::Module module, const QString &lastModified ) + : mObjectId( objectId ), + mModule( module ), + mLastModified( lastModified ) + { + } + + inline qlonglong objectId() const + { + return mObjectId; + } + + inline OXA::Folder::Module module() const + { + return mModule; + } + + inline QString lastModified() const + { + return mLastModified; + } + + inline void setLastModified( const QString &lastModified ) + { + mLastModified = lastModified; + } + + static RemoteInformation load( const Entity &entity ) + { + const QStringList parts = entity.remoteRevision().split( QLatin1Char( ':' ), QString::KeepEmptyParts ); + + OXA::Folder::Module module = OXA::Folder::Unbound; + + if ( parts.count() > 0 ) { + if ( parts.at( 0 ) == QLatin1String( "calendar" ) ) + module = OXA::Folder::Calendar; + else if ( parts.at( 0 ) == QLatin1String( "contacts" ) ) + module = OXA::Folder::Contacts; + else if ( parts.at( 0 ) == QLatin1String( "tasks" ) ) + module = OXA::Folder::Tasks; + else + module = OXA::Folder::Unbound; + } + + QString lastModified = QLatin1String( "0" ); + if ( parts.count() > 1 ) + lastModified = parts.at( 1 ); + + return RemoteInformation( entity.remoteId().toLongLong(), module, lastModified ); + } + + void store( Entity &entity ) const + { + QString module; + switch ( mModule ) { + case OXA::Folder::Calendar: module = QLatin1String( "calendar" ); break; + case OXA::Folder::Contacts: module = QLatin1String( "contacts" ); break; + case OXA::Folder::Tasks: module = QLatin1String( "tasks" ); break; + case OXA::Folder::Unbound: break; + } + + QStringList parts; + parts.append( module ); + parts.append( mLastModified ); + + entity.setRemoteId( QString::number( mObjectId ) ); + entity.setRemoteRevision( parts.join( QLatin1String( ":" ) ) ); + } + + private: + qlonglong mObjectId; + OXA::Folder::Module mModule; + QString mLastModified; +}; + +class ObjectsLastSync +{ + public: + ObjectsLastSync() + { + if ( !Settings::self()->objectsLastSync().isEmpty() ) { + const QStringList pairs = Settings::self()->objectsLastSync().split( QLatin1Char( ':' ), QString::KeepEmptyParts ); + foreach ( const QString &pair, pairs ) { + const QStringList entry = pair.split( QLatin1Char( '=' ), QString::KeepEmptyParts ); + mObjectsMap.insert( entry.at( 0 ).toLongLong(), entry.at( 1 ).toULongLong() ); + } + } + } + + void save() + { + QStringList pairs; + + QMapIterator it( mObjectsMap ); + while ( it.hasNext() ) { + it.next(); + pairs.append( QString::number( it.key() ) + QLatin1Char( '=' ) + QString::number( it.value() ) ); + } + + Settings::self()->setObjectsLastSync( pairs.join( QLatin1String( ":" ) ) ); + Settings::self()->writeConfig(); + } + + qulonglong lastSync( qlonglong collectionId ) const + { + if ( !mObjectsMap.contains( collectionId ) ) + return 0; + + return mObjectsMap.value( collectionId ); + } + + void setLastSync( qlonglong collectionId, qulonglong timeStamp ) + { + mObjectsMap.insert( collectionId, timeStamp ); + } + + private: + QMap mObjectsMap; +}; + +static Collection::Rights folderPermissionsToCollectionRights( const OXA::Folder &folder ) +{ + const OXA::Folder::UserPermissions userPermissions = folder.userPermissions(); + + if ( !userPermissions.contains( OXA::Users::self()->currentUserId() ) ) { + // There are no rights given for us explicitly, so it is read-only + return Collection::ReadOnly; + } else { + const OXA::Folder::Permissions permissions = userPermissions.value( OXA::Users::self()->currentUserId() ); + Collection::Rights rights = Collection::ReadOnly; + switch ( permissions.folderPermission() ) { + case OXA::Folder::Permissions::FolderIsVisible: rights |= Collection::ReadOnly; break; + case OXA::Folder::Permissions::CreateObjects: rights |= Collection::CanCreateItem; break; + case OXA::Folder::Permissions::CreateSubfolders: // fallthrough + case OXA::Folder::Permissions::AdminPermission: rights |= (Collection::CanCreateItem | Collection::CanCreateCollection); break; + default: break; + } + + if ( permissions.objectWritePermission() != OXA::Folder::Permissions::NoWritePermission ) { + rights |= Collection::CanChangeItem; + rights |= Collection::CanChangeCollection; + } + + if ( permissions.objectDeletePermission() != OXA::Folder::Permissions::NoDeletePermission ) { + rights |= Collection::CanDeleteItem; + rights |= Collection::CanDeleteCollection; + } + + return rights; + } +} + +OpenXchangeResource::OpenXchangeResource( const QString &id ) + : ResourceBase( id ) +{ + // setup the resource + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + changeRecorder()->fetchCollection( true ); + changeRecorder()->itemFetchScope().fetchFullPayload( true ); + changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::Parent ); + changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::Parent ); + + setName( i18n( "Open-Xchange" ) ); + + OXA::Users::self()->init( identifier() ); + + KUrl baseUrl = Settings::self()->baseUrl(); + baseUrl.setUserName( Settings::self()->username() ); + baseUrl.setPassword( Settings::self()->password() ); + OXA::DavManager::self()->setBaseUrl( baseUrl ); + + // Create the standard collections. + // + // There exists special OX folders (e.g. private, public, shared) that are not + // returned by a normal webdav listing, therefor we create them manually here. + // This is possible because the remote ids of these folders are fixed values from 1 + // till 4. + mResourceCollection.setParentCollection( Collection::root() ); + const RemoteInformation resourceInformation( 0, OXA::Folder::Unbound, QString() ); + resourceInformation.store( mResourceCollection ); + mResourceCollection.setName( name() ); + mResourceCollection.setContentMimeTypes( QStringList() << Collection::mimeType() ); + mResourceCollection.setRights( Collection::ReadOnly ); + EntityDisplayAttribute *attribute = mResourceCollection.attribute( Collection::AddIfMissing ); + attribute->setIconName( QLatin1String( "ox" ) ); + + Collection privateFolder; + privateFolder.setParentCollection( mResourceCollection ); + const RemoteInformation privateFolderInformation( 1, OXA::Folder::Unbound, QString() ); + privateFolderInformation.store( privateFolder ); + privateFolder.setName( i18n( "Private Folder" ) ); + privateFolder.setContentMimeTypes( QStringList() << Collection::mimeType() ); + privateFolder.setRights( Collection::ReadOnly ); + + Collection publicFolder; + publicFolder.setParentCollection( mResourceCollection ); + const RemoteInformation publicFolderInformation( 2, OXA::Folder::Unbound, QString() ); + publicFolderInformation.store( publicFolder ); + publicFolder.setName( i18n( "Public Folder" ) ); + publicFolder.setContentMimeTypes( QStringList() << Collection::mimeType() ); + publicFolder.setRights( Collection::ReadOnly ); + + Collection sharedFolder; + sharedFolder.setParentCollection( mResourceCollection ); + const RemoteInformation sharedFolderInformation( 3, OXA::Folder::Unbound, QString() ); + sharedFolderInformation.store( sharedFolder ); + sharedFolder.setName( i18n( "Shared Folder" ) ); + sharedFolder.setContentMimeTypes( QStringList() << Collection::mimeType() ); + sharedFolder.setRights( Collection::ReadOnly ); + + Collection systemFolder; + systemFolder.setParentCollection( mResourceCollection ); + const RemoteInformation systemFolderInformation( 4, OXA::Folder::Unbound, QString() ); + systemFolderInformation.store( systemFolder ); + systemFolder.setName( i18n( "System Folder" ) ); + systemFolder.setContentMimeTypes( QStringList() << Collection::mimeType() ); + systemFolder.setRights( Collection::ReadOnly ); + + // TODO: set cache policy depending on sync behaviour + Akonadi::CachePolicy cachePolicy; + cachePolicy.setInheritFromParent( false ); + cachePolicy.setSyncOnDemand( false ); + cachePolicy.setCacheTimeout( -1 ); + cachePolicy.setIntervalCheckTime( 5 ); + mResourceCollection.setCachePolicy( cachePolicy ); + + mStandardCollectionsMap.insert( 0, mResourceCollection ); + mStandardCollectionsMap.insert( 1, privateFolder ); + mStandardCollectionsMap.insert( 2, publicFolder ); + mStandardCollectionsMap.insert( 3, sharedFolder ); + mStandardCollectionsMap.insert( 4, systemFolder ); + + mCollectionsMap = mStandardCollectionsMap; + + if ( Settings::self()->useIncrementalUpdates() ) + syncCollectionsRemoteIdCache(); +} + +OpenXchangeResource::~OpenXchangeResource() +{ +} + +void OpenXchangeResource::cleanup() +{ + // be nice and remove cache file when resource is removed + QFile::remove( OXA::Users::self()->cacheFilePath() ); + + QFile::remove( KStandardDirs::locateLocal( "config", Settings::self()->config()->name() ) ); + + ResourceBase::cleanup(); +} + +void OpenXchangeResource::aboutToQuit() +{ +} + +void OpenXchangeResource::configure( WId windowId ) +{ + const bool useIncrementalUpdates = Settings::self()->useIncrementalUpdates(); + + ConfigDialog dlg( windowId ); + dlg.setWindowIcon( KIcon( "ox" ) ); + if ( dlg.exec() ) { //krazy:exclude=crashy + + // if the user has changed the incremental update settings we have to do + // some additional initialization work + if ( useIncrementalUpdates != Settings::self()->useIncrementalUpdates() ) { + Settings::self()->setFoldersLastSync( 0 ); + Settings::self()->setObjectsLastSync( QString() ); + } + + Settings::self()->writeConfig(); + + clearCache(); + + KUrl baseUrl = Settings::self()->baseUrl(); + baseUrl.setUserName( Settings::self()->username() ); + baseUrl.setPassword( Settings::self()->password() ); + OXA::DavManager::self()->setBaseUrl( baseUrl ); + + // To find out the correct ACLs we need the uid of the user that + // logs in. For loading events and tasks we need a complete mapping of + // user id to name as well, so the mapping must be loaded as well. + // Both is done by UpdateUsersJob, so trigger it here before we continue + // with synchronization in onUpdateUsersJobFinished. + OXA::UpdateUsersJob *job = new OXA::UpdateUsersJob( this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onUpdateUsersJobFinished(KJob*)) ); + job->start(); + } else { + emit configurationDialogRejected(); + } +} + +void OpenXchangeResource::retrieveCollections() +{ + //qDebug("tokoe: retrieve collections called"); + if ( Settings::self()->useIncrementalUpdates() ) { + //qDebug( "lastSync=%llu", Settings::self()->foldersLastSync() ); + OXA::FoldersRequestDeltaJob *job = new OXA::FoldersRequestDeltaJob( Settings::self()->foldersLastSync(), this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFoldersRequestDeltaJobFinished(KJob*)) ); + job->start(); + } else { + OXA::FoldersRequestJob *job = new OXA::FoldersRequestJob( 0, OXA::FoldersRequestJob::Modified, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFoldersRequestJobFinished(KJob*)) ); + job->start(); + } +} + +void OpenXchangeResource::retrieveItems( const Akonadi::Collection &collection ) +{ + //qDebug( "tokoe: retrieveItems on %s called", qPrintable( collection.name() ) ); + const RemoteInformation remoteInformation = RemoteInformation::load( collection ); + + OXA::Folder folder; + folder.setObjectId( remoteInformation.objectId() ); + folder.setModule( remoteInformation.module() ); + + if ( Settings::self()->useIncrementalUpdates() ) { + ObjectsLastSync lastSyncInfo; + //qDebug( "lastSync=%llu", lastSyncInfo.lastSync( collection.id() ) ); + OXA::ObjectsRequestDeltaJob *job = new OXA::ObjectsRequestDeltaJob( folder, lastSyncInfo.lastSync( collection.id() ), this ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectsRequestDeltaJobFinished(KJob*)) ); + job->start(); + } else { + OXA::ObjectsRequestJob *job = new OXA::ObjectsRequestJob( folder, 0, OXA::ObjectsRequestJob::Modified, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectsRequestJobFinished(KJob*)) ); + job->start(); + } +} + +bool OpenXchangeResource::retrieveItem( const Akonadi::Item &item, const QSet& ) +{ + //qDebug( "tokoe: retrieveItem %lld called", item.id() ); + const RemoteInformation remoteInformation = RemoteInformation::load( item ); + + OXA::Object object; + object.setObjectId( remoteInformation.objectId() ); + object.setModule( remoteInformation.module() ); + + OXA::ObjectRequestJob *job = new OXA::ObjectRequestJob( object, this ); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectRequestJobFinished(KJob*)) ); + job->start(); + + return true; +} + +void OpenXchangeResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( collection ); + + OXA::Object object; + object.setFolderId( remoteInformation.objectId() ); + object.setModule( remoteInformation.module() ); + + if ( item.hasPayload() ) { + object.setContact( item.payload() ); + } else if ( item.hasPayload() ) { + object.setContactGroup( item.payload() ); + } else if ( item.hasPayload() ) { + object.setEvent( item.payload() ); + } else if ( item.hasPayload() ) { + object.setTask( item.payload() ); + } else { + Q_ASSERT( false ); + } + + OXA::ObjectCreateJob *job = new OXA::ObjectCreateJob( object, this ); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectCreateJobFinished(KJob*)) ); + job->start(); +} + +void OpenXchangeResource::itemChanged( const Akonadi::Item &item, const QSet& ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( item ); + const RemoteInformation parentRemoteInformation = RemoteInformation::load( item.parentCollection() ); + + OXA::Object object; + object.setObjectId( remoteInformation.objectId() ); + object.setModule( remoteInformation.module() ); + object.setFolderId( parentRemoteInformation.objectId() ); + object.setLastModified( remoteInformation.lastModified() ); + + if ( item.hasPayload() ) { + object.setContact( item.payload() ); + } else if ( item.hasPayload() ) { + object.setContactGroup( item.payload() ); + } else if ( item.hasPayload() ) { + object.setEvent( item.payload() ); + } else if ( item.hasPayload() ) { + object.setTask( item.payload() ); + } else { + Q_ASSERT( false ); + } + + OXA::ObjectModifyJob *job = new OXA::ObjectModifyJob( object, this ); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectModifyJobFinished(KJob*)) ); + job->start(); +} + +void OpenXchangeResource::itemRemoved( const Akonadi::Item &item ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( item ); + const RemoteInformation parentRemoteInformation = RemoteInformation::load( item.parentCollection() ); + + OXA::Object object; + object.setObjectId( remoteInformation.objectId() ); + object.setFolderId( parentRemoteInformation.objectId() ); + object.setModule( remoteInformation.module() ); + object.setLastModified( remoteInformation.lastModified() ); + + OXA::ObjectDeleteJob *job = new OXA::ObjectDeleteJob( object, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectDeleteJobFinished(KJob*)) ); + + job->start(); +} + +void OpenXchangeResource::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( item ); + const RemoteInformation parentRemoteInformation = RemoteInformation::load( collectionSource ); + const RemoteInformation newParentRemoteInformation = RemoteInformation::load( collectionDestination ); + + OXA::Object object; + object.setObjectId( remoteInformation.objectId() ); + object.setModule( remoteInformation.module() ); + object.setFolderId( parentRemoteInformation.objectId() ); + object.setLastModified( remoteInformation.lastModified() ); + + OXA::Folder destinationFolder; + destinationFolder.setObjectId( newParentRemoteInformation.objectId() ); + + OXA::ObjectMoveJob *job = new OXA::ObjectMoveJob( object, destinationFolder, this ); + job->setProperty( "item", QVariant::fromValue( item ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onObjectMoveJobFinished(KJob*)) ); + + job->start(); +} + +void OpenXchangeResource::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) +{ + const RemoteInformation parentRemoteInformation = RemoteInformation::load( parent ); + + OXA::Folder folder; + folder.setTitle( collection.name() ); + folder.setFolderId( parentRemoteInformation.objectId() ); + folder.setType( OXA::Folder::Private ); + + // the folder 'inherits' the module type of its parent collection + folder.setModule( parentRemoteInformation.module() ); + + // fill permissions + OXA::Folder::Permissions permissions; + permissions.setFolderPermission( OXA::Folder::Permissions::CreateSubfolders ); + permissions.setObjectReadPermission( OXA::Folder::Permissions::ReadOwnObjects ); + permissions.setObjectWritePermission( OXA::Folder::Permissions::WriteOwnObjects ); + permissions.setObjectDeletePermission( OXA::Folder::Permissions::DeleteOwnObjects ); + permissions.setAdminFlag( true ); + + // assign permissions to user + OXA::Folder::UserPermissions userPermissions; + userPermissions.insert( OXA::Users::self()->currentUserId(), permissions ); + + // set user permissions of folder + folder.setUserPermissions( userPermissions ); + + OXA::FolderCreateJob *job = new OXA::FolderCreateJob( folder, this ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFolderCreateJobFinished(KJob*)) ); + job->start(); +} + +void OpenXchangeResource::collectionChanged( const Akonadi::Collection &collection ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( collection ); + + // do not try to change the standard collections + if ( remoteInformation.objectId() >= 0 && remoteInformation.objectId() <= 4 ) { + changeCommitted( collection ); + return; + } + + const RemoteInformation parentRemoteInformation = RemoteInformation::load( collection.parentCollection() ); + + OXA::Folder folder; + folder.setObjectId( remoteInformation.objectId() ); + folder.setFolderId( parentRemoteInformation.objectId() ); + folder.setTitle( collection.name() ); + folder.setLastModified( remoteInformation.lastModified() ); + + OXA::FolderModifyJob *job = new OXA::FolderModifyJob( folder, this ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFolderModifyJobFinished(KJob*)) ); +} + +void OpenXchangeResource::collectionRemoved( const Akonadi::Collection &collection ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( collection ); + + OXA::Folder folder; + folder.setObjectId( remoteInformation.objectId() ); + folder.setLastModified( remoteInformation.lastModified() ); + + OXA::FolderDeleteJob *job = new OXA::FolderDeleteJob( folder, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFolderDeleteJobFinished(KJob*)) ); + + job->start(); +} + +void OpenXchangeResource::collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ) +{ + const RemoteInformation remoteInformation = RemoteInformation::load( collection ); + const RemoteInformation parentRemoteInformation = RemoteInformation::load( collectionSource ); + const RemoteInformation newParentRemoteInformation = RemoteInformation::load( collectionDestination ); + + OXA::Folder folder; + folder.setObjectId( remoteInformation.objectId() ); + folder.setFolderId( parentRemoteInformation.objectId() ); + + OXA::Folder destinationFolder; + destinationFolder.setObjectId( newParentRemoteInformation.objectId() ); + + OXA::FolderMoveJob *job = new OXA::FolderMoveJob( folder, destinationFolder, this ); + job->setProperty( "collection", QVariant::fromValue( collection ) ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFolderMoveJobFinished(KJob*)) ); + + job->start(); +} + +//// job result slots + +void OpenXchangeResource::onUpdateUsersJobFinished( KJob *job ) +{ + if ( job->error() ) { + // This might be an indication that we can not connect to the server... + emit status( Broken, i18n( "Unable to connect to server" ) ); + return; + } + + if ( Settings::self()->useIncrementalUpdates() ) + syncCollectionsRemoteIdCache(); + + // now we have all user information, so continue synchronization + synchronize(); + emit configurationDialogAccepted(); +} + +void OpenXchangeResource::onObjectsRequestJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::ObjectsRequestJob *requestJob = qobject_cast( job ); + Q_ASSERT( requestJob ); + + Item::List items; + + const OXA::Object::List objects = requestJob->objects(); + foreach ( const OXA::Object &object, objects ) { + Item item; + switch ( object.module() ) { + case OXA::Folder::Contacts: + if ( !object.contact().isEmpty() ) { + item.setMimeType( KABC::Addressee::mimeType() ); + item.setPayload( object.contact() ); + } else { + item.setMimeType( KABC::ContactGroup::mimeType() ); + item.setPayload( object.contactGroup() ); + } + break; + case OXA::Folder::Calendar: + item.setMimeType( KCalCore::Event::eventMimeType() ); + item.setPayload( object.event() ); + break; + case OXA::Folder::Tasks: + item.setMimeType( KCalCore::Todo::todoMimeType() ); + item.setPayload( object.task() ); + break; + case OXA::Folder::Unbound: + Q_ASSERT( false ); + break; + } + const RemoteInformation remoteInformation( object.objectId(), object.module(), object.lastModified() ); + remoteInformation.store( item ); + + items.append( item ); + } + + itemsRetrieved( items ); +} + +void OpenXchangeResource::onObjectsRequestDeltaJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::ObjectsRequestDeltaJob *requestJob = qobject_cast( job ); + Q_ASSERT( requestJob ); + + const Akonadi::Collection collection = requestJob->property( "collection" ).value(); + + ObjectsLastSync lastSyncInfo; + + qulonglong objectsLastSync = lastSyncInfo.lastSync( collection.id() ); + + Item::List changedItems; + + const OXA::Object::List modifiedObjects = requestJob->modifiedObjects(); + foreach ( const OXA::Object &object, modifiedObjects ) { + Item item; + switch ( object.module() ) { + case OXA::Folder::Contacts: + if ( !object.contact().isEmpty() ) { + item.setMimeType( KABC::Addressee::mimeType() ); + item.setPayload( object.contact() ); + } else { + item.setMimeType( KABC::ContactGroup::mimeType() ); + item.setPayload( object.contactGroup() ); + } + break; + case OXA::Folder::Calendar: + item.setMimeType( KCalCore::Event::eventMimeType() ); + item.setPayload( object.event() ); + break; + case OXA::Folder::Tasks: + item.setMimeType( KCalCore::Todo::todoMimeType() ); + item.setPayload( object.task() ); + break; + case OXA::Folder::Unbound: + Q_ASSERT( false ); + break; + } + const RemoteInformation remoteInformation( object.objectId(), object.module(), object.lastModified() ); + remoteInformation.store( item ); + + // the value of objectsLastSync is determined by the maximum last modified value + // of the added or changed objects + objectsLastSync = qMax( objectsLastSync, object.lastModified().toULongLong() ); + + changedItems.append( item ); + } + + Item::List removedItems; + + const OXA::Object::List deletedObjects = requestJob->deletedObjects(); + foreach ( const OXA::Object &object, deletedObjects ) { + Item item; + + const RemoteInformation remoteInformation( object.objectId(), object.module(), object.lastModified() ); + remoteInformation.store( item ); + + removedItems.append( item ); + } + + if ( objectsLastSync != lastSyncInfo.lastSync( collection.id() ) ) { + // according to the OX developers we should subtract one millisecond from the + // maximum last modified value to cover multiple changes that might have been + // done in the same millisecond to the data on the server + lastSyncInfo.setLastSync( collection.id(), objectsLastSync - 1 ); + lastSyncInfo.save(); + } + + //qDebug( "changedObjects=%d removedObjects=%d", modifiedObjects.count(), deletedObjects.count() ); + //qDebug( "changedItems=%d removedItems=%d", changedItems.count(), removedItems.count() ); + itemsRetrievedIncremental( changedItems, removedItems ); +} + +void OpenXchangeResource::onObjectRequestJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::ObjectRequestJob *requestJob = qobject_cast( job ); + Q_ASSERT( requestJob ); + + const OXA::Object object = requestJob->object(); + + Item item = job->property( "item" ).value(); + + const RemoteInformation remoteInformation( object.objectId(), object.module(), object.lastModified() ); + remoteInformation.store( item ); + + switch ( object.module() ) { + case OXA::Folder::Contacts: + if ( !object.contact().isEmpty() ) { + item.setMimeType( KABC::Addressee::mimeType() ); + item.setPayload( object.contact() ); + } else { + item.setMimeType( KABC::ContactGroup::mimeType() ); + item.setPayload( object.contactGroup() ); + } + break; + case OXA::Folder::Calendar: + item.setMimeType( KCalCore::Event::eventMimeType() ); + item.setPayload( object.event() ); + break; + case OXA::Folder::Tasks: + item.setMimeType( KCalCore::Todo::todoMimeType() ); + item.setPayload( object.task() ); + break; + case OXA::Folder::Unbound: + Q_ASSERT( false ); + break; + } + + itemRetrieved( item ); +} + +void OpenXchangeResource::onObjectCreateJobFinished( KJob *job ) +{ + if ( job->error() ) { + QString errorText = job->errorText(); + if ( job->error() == KJob::UserDefinedError ) { + switch ( OXA::OXErrors::getEditErrorID( job->errorText() ) ) { + case OXA::OXErrors::ConcurrentModification : errorText = i18n( "The object was edited by another participant in the meantime. Please check." ); break; + case OXA::OXErrors::ObjectNotFound : errorText = i18n( "Object not found. Maybe it was deleted by another participant in the meantime." ); break; + case OXA::OXErrors::NoPermissionForThisAction : errorText = i18n( "You don't have the permission to perform this action on this object." ); break; + case OXA::OXErrors::ConflictsDetected : errorText = i18n( "A conflict detected. Please check if there are other objects in conflict with this one." ); break; + case OXA::OXErrors::MissingMandatoryFields : errorText = i18n( "A mandatory data field is missing. Please check. Otherwise contact your administrator." ); break; + case OXA::OXErrors::AppointmentConflicts : errorText = i18n( "An appointment conflict detected.\nPlease check if there are other appointments in conflict with this one." ); break; + case OXA::OXErrors::InternalServerError : errorText = i18n( "Internal server error. Please contact your administrator." ); break; + case OXA::OXErrors::EditErrorUndefined : + default : ; + } + } + cancelTask( errorText ); + return; + } + + OXA::ObjectCreateJob *createJob = qobject_cast( job ); + Q_ASSERT( createJob ); + + const OXA::Object object = createJob->object(); + + Item item = job->property( "item" ).value(); + + const RemoteInformation remoteInformation( object.objectId(), object.module(), object.lastModified() ); + remoteInformation.store( item ); + + changeCommitted( item ); +} + +void OpenXchangeResource::onObjectModifyJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::ObjectModifyJob *modifyJob = qobject_cast( job ); + Q_ASSERT( modifyJob ); + + const OXA::Object object = modifyJob->object(); + + Item item = job->property( "item" ).value(); + + // update last_modified property + RemoteInformation remoteInformation = RemoteInformation::load( item ); + remoteInformation.setLastModified( object.lastModified() ); + remoteInformation.store( item ); + + changeCommitted( item ); +} + +void OpenXchangeResource::onObjectMoveJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::ObjectMoveJob *moveJob = qobject_cast( job ); + Q_ASSERT( moveJob ); + + const OXA::Object object = moveJob->object(); + + Item item = job->property( "item" ).value(); + + // update last_modified property + RemoteInformation remoteInformation = RemoteInformation::load( item ); + remoteInformation.setLastModified( object.lastModified() ); + remoteInformation.store( item ); + + changeCommitted( item ); +} + +void OpenXchangeResource::onObjectDeleteJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + changeProcessed(); +} + +static Collection folderToCollection( const OXA::Folder &folder, const Collection &parentCollection ) +{ + Collection collection; + + collection.setParentCollection( parentCollection ); + + const RemoteInformation remoteInformation( folder.objectId(), folder.module(), folder.lastModified() ); + remoteInformation.store( collection ); + + // set a unique name to make Akonadi happy + collection.setName( folder.title() + '_' + QUuid::createUuid().toString() ); + + EntityDisplayAttribute *attribute = collection.attribute( Collection::AddIfMissing ); + attribute->setDisplayName( folder.title() ); + + QStringList mimeTypes; + mimeTypes.append( Collection::mimeType() ); + switch ( folder.module() ) { + case OXA::Folder::Calendar: + mimeTypes.append( KCalCore::Event::eventMimeType() ); + attribute->setIconName( QString::fromLatin1( "view-calendar" ) ); + break; + case OXA::Folder::Contacts: + mimeTypes.append( KABC::Addressee::mimeType() ); + mimeTypes.append( KABC::ContactGroup::mimeType() ); + attribute->setIconName( QString::fromLatin1( "view-pim-contacts" ) ); + break; + case OXA::Folder::Tasks: + mimeTypes.append( KCalCore::Todo::todoMimeType() ); + attribute->setIconName( QString::fromLatin1( "view-pim-tasks" ) ); + break; + case OXA::Folder::Unbound: + break; + } + + collection.setContentMimeTypes( mimeTypes ); + collection.setRights( folderPermissionsToCollectionRights( folder ) ); + + return collection; +} + +void OpenXchangeResource::onFoldersRequestJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::FoldersRequestJob *requestJob = qobject_cast( job ); + Q_ASSERT( requestJob ); + + Collection::List collections; + + // add the standard collections + collections << mStandardCollectionsMap.values(); + + QMap remoteIdMap( mStandardCollectionsMap ); + + // add the folders from the server + OXA::Folder::List folders = requestJob->folders(); + while ( !folders.isEmpty() ) { + const OXA::Folder folder = folders.takeFirst(); + if ( remoteIdMap.contains( folder.folderId() ) ) { + // we have the parent collection created already + const Collection collection = folderToCollection( folder, remoteIdMap.value( folder.folderId() ) ); + remoteIdMap.insert( folder.objectId(), collection ); + collections.append( collection ); + } else { + // we have to wait until the parent folder has been created + folders.append( folder ); + qDebug() << "Error: parent folder id" << folder.folderId() << "of folder" << folder.title() << "is unknown"; + } + } + + collectionsRetrieved( collections ); +} + +void OpenXchangeResource::onFoldersRequestDeltaJobFinished( KJob *job ) +{ + //qDebug( "onFoldersRequestDeltaJobFinished mCollectionsMap.count() = %d", mCollectionsMap.count() ); + + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::FoldersRequestDeltaJob *requestJob = qobject_cast( job ); + Q_ASSERT( requestJob ); + + Collection::List changedCollections; + + // add the standard collections + changedCollections << mStandardCollectionsMap.values(); + + qulonglong foldersLastSync = Settings::self()->foldersLastSync(); + + // add the new or modified folders from the server + OXA::Folder::List modifiedFolders = requestJob->modifiedFolders(); + while ( !modifiedFolders.isEmpty() ) { + const OXA::Folder folder = modifiedFolders.takeFirst(); + if ( mCollectionsMap.contains( folder.folderId() ) ) { + // we have the parent collection created already + const Collection collection = folderToCollection( folder, mCollectionsMap.value( folder.folderId() ) ); + mCollectionsMap.insert( folder.objectId(), collection ); + changedCollections.append( collection ); + + // the value of foldersLastSync is determined by the maximum last modified value + // of the added or changed folders + foldersLastSync = qMax( foldersLastSync, folder.lastModified().toULongLong() ); + + } else { + // we have to wait until the parent folder has been created + modifiedFolders.append( folder ); + qDebug() << "Error: parent folder id" << folder.folderId() << "of folder" << folder.title() << "is unknown"; + } + } + + Collection::List removedCollections; + + // add the deleted folders from the server + OXA::Folder::List deletedFolders = requestJob->deletedFolders(); + foreach ( const OXA::Folder &folder, deletedFolders ) { + Collection collection; + collection.setRemoteId( QString::number( folder.objectId() ) ); + + removedCollections.append( collection ); + } + + if ( foldersLastSync != Settings::self()->foldersLastSync() ) { + // according to the OX developers we should subtract one millisecond from the + // maximum last modified value to cover multiple changes that might have been + // done in the same millisecond to the data on the server + Settings::self()->setFoldersLastSync( foldersLastSync - 1 ); + Settings::self()->writeConfig(); + } + + //qDebug( "changedFolders=%d removedFolders=%d", modifiedFolders.count(), deletedFolders.count() ); + //qDebug( "changedCollections=%d removedCollections=%d", changedCollections.count(), removedCollections.count() ); + collectionsRetrievedIncremental( changedCollections, removedCollections ); +} + +void OpenXchangeResource::onFolderCreateJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::FolderCreateJob *createJob = qobject_cast( job ); + Q_ASSERT( createJob ); + + const OXA::Folder folder = createJob->folder(); + + Collection collection = job->property( "collection" ).value(); + + const RemoteInformation remoteInformation( folder.objectId(), folder.module(), folder.lastModified() ); + remoteInformation.store( collection ); + + // set matching icon + EntityDisplayAttribute *attribute = collection.attribute( Collection::AddIfMissing ); + switch ( folder.module() ) { + case OXA::Folder::Calendar: + attribute->setIconName( QString::fromLatin1( "view-calendar" ) ); + break; + case OXA::Folder::Contacts: + attribute->setIconName( QString::fromLatin1( "view-pim-contacts" ) ); + break; + case OXA::Folder::Tasks: + attribute->setIconName( QString::fromLatin1( "view-pim-tasks" ) ); + break; + case OXA::Folder::Unbound: + break; + } + + changeCommitted( collection ); +} + +void OpenXchangeResource::onFolderModifyJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::FolderModifyJob *modifyJob = qobject_cast( job ); + Q_ASSERT( modifyJob ); + + const OXA::Folder folder = modifyJob->folder(); + + Collection collection = job->property( "collection" ).value(); + + // update last_modified property + RemoteInformation remoteInformation = RemoteInformation::load( collection ); + remoteInformation.setLastModified( folder.lastModified() ); + remoteInformation.store( collection ); + + changeCommitted( collection ); +} + +void OpenXchangeResource::onFolderMoveJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + OXA::FolderMoveJob *moveJob = qobject_cast( job ); + Q_ASSERT( moveJob ); + + const OXA::Folder folder = moveJob->folder(); + + Collection collection = job->property( "collection" ).value(); + + // update last_modified property + RemoteInformation remoteInformation = RemoteInformation::load( collection ); + remoteInformation.setLastModified( folder.lastModified() ); + remoteInformation.store( collection ); + + changeCommitted( collection ); +} + +void OpenXchangeResource::onFolderDeleteJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelTask( job->errorText() ); + return; + } + + changeProcessed(); +} + +/** + * For incremental updates we need a mapping between the folder id + * and the collection object for all collections of this resource, + * so that we can find out the right parent collection in + * onFoldersRequestDeltaJob(). + * + * Therefor we trigger this method when the resource is started and + * configured to use incremental sync. + */ +void OpenXchangeResource::syncCollectionsRemoteIdCache() +{ + mCollectionsMap.clear(); + + // copy the standard collections + mCollectionsMap = mStandardCollectionsMap; + + CollectionFetchJob *job = new CollectionFetchJob( mResourceCollection, CollectionFetchJob::Recursive, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(onFetchResourceCollectionsFinished(KJob*)) ); +} + +void OpenXchangeResource::onFetchResourceCollectionsFinished( KJob *job ) +{ + if ( job->error() ) { + qDebug() << "Error: Unable to fetch resource collections:" << job->errorText(); + return; + } + + const CollectionFetchJob *fetchJob = qobject_cast( job ); + + // copy the remaining collections of the resource + const Collection::List collections = fetchJob->collections(); + foreach ( const Collection &collection, collections ) + mCollectionsMap.insert( collection.remoteId().toULongLong(), collection ); +} + +AKONADI_RESOURCE_MAIN( OpenXchangeResource ) + diff --git a/kdepim-runtime/resources/openxchange/openxchangeresource.desktop b/kdepim-runtime/resources/openxchange/openxchangeresource.desktop new file mode 100644 index 00000000..e4ad5c0c --- /dev/null +++ b/kdepim-runtime/resources/openxchange/openxchangeresource.desktop @@ -0,0 +1,88 @@ +[Desktop Entry] +Name=Open-Xchange Groupware Server +Name[bg]=Сървър Open-Xchange Groupware +Name[bs]=Open-Xchange grupni server +Name[ca]=Servidor de treball en grup Open-Xchange +Name[ca@valencia]=Servidor de treball en grup Open-Xchange +Name[cs]=Server Open-Xchange Groupware +Name[da]=Open-Xchange groupware-server +Name[de]=Open-Xchange-Groupware-Server +Name[el]=ΕξυπηÏετητής Groupware OpenXchange +Name[en_GB]=Open-Xchange Groupware Server +Name[es]=Servidor de trabajo en grupo Open-Xchange +Name[et]=Open-Xchange'i grupitöö server +Name[fi]=Open-Xchange-työryhmäpalvelin +Name[fr]=Serveur de logiciels de collaboration Open-Xchange +Name[ga]=Freastalaí Groupware Open-Xchange +Name[gl]=Servidor de traballo en grupo OpenXchange +Name[hu]=Open-Xchange csoportmunka-kiszolgáló +Name[ia]=Servitor de Open-Xchange Groupware +Name[it]=Server di groupware Open-Xchange +Name[ja]=Open-Xchange グループウェアサーム+Name[kk]=Open-Xchange топтық Ñ–Ñ Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ Open-Xchange Groupware +Name[ko]=Open-Xchange 그룹웨어 서버 +Name[lt]=Open-Xchange grupinio darbo serveris +Name[lv]=Open-Xchange grupprogrammatÅ«ras serveris +Name[nb]=OpenXchange-tjener +Name[nds]=OpenXchange-Arbeitkoppelserver +Name[nl]=Open-Xchange-groupware-server +Name[nn]=Open-Xchange-tenar +Name[pa]=Open-Xchange ਗਰà©à©±à¨ªà¨µà©‡à¨…ਰ ਸਰਵਰ +Name[pl]=Serwer Open-Xchange Groupware +Name[pt]=Servidor de 'Groupware' Open-Xchange +Name[pt_BR]=Servidor groupware Open-Xchange +Name[ru]=Сервер ÑовмеÑтной работы Open-Xchange +Name[sk]=Groupware Server Open-Xchange +Name[sl]=Strežnik za skupinsko delo Open-Xchange +Name[sr]=Опен‑екÑчејнџов групверÑки Ñервер +Name[sr@ijekavian]=Опен‑екÑчејнџов групверÑки Ñервер +Name[sr@ijekavianlatin]=Open‑Xchangeov grupverski server +Name[sr@latin]=Open‑Xchangeov grupverski server +Name[sv]=Open-xchange grupprogramserver +Name[tr]=Open-Xchange Groupware Sunucusu +Name[uk]=Сервер групової роботи Open-Xchange +Name[x-test]=xxOpen-Xchange Groupware Serverxx +Name[zh_CN]=Open-Xchange 群件æœåŠ¡å™¨ +Name[zh_TW]=Open-Xchange 群組伺æœå™¨ +Comment="Provides access to the appointments, tasks, and contacts of an Open-Xchange groupware server." +Comment[bs]="ObezbjeÄ‘uje pristup zakazanim obavezama, zadacima i kontaktima za Open-Xchange grupni server." +Comment[ca]=Proporciona accés a les cites, tasques i contactes emmagatzemats en un servidor de treball en grup Open-Xchange. +Comment[ca@valencia]="Proporciona l'accés a les cites, tasques i contactes emmagatzemats en un servidor de treball en grup Open-Xchange." +Comment[da]="Giver adgang til aftaler, opgaver og kontakter pÃ¥ en Open-Xchange groupware-server." +Comment[de]="Ermöglicht den Zugriff auf Termine, Aufgaben und Kontakte, die auf einem Open-Xchange-Server gespeichert sind." +Comment[el]="ΠαÏέχει Ï€Ïόσβαση σε ÏαντεβοÏ, εÏγασίες και επαφές ενός εξυπηÏετητή groupware Open-Xchange" +Comment[en_GB]="Provides access to the appointments, tasks, and contacts of an Open-Xchange groupware server." +Comment[es]=«Proporciona acceso a las citas, tareas y contactos almacenados en un servidor de colaboración Open-Xchange». +Comment[et]="Võimaldab kasutada Open-Xchange'i grupitöö serveri kohtumisi, ülesandeid ja kontakte." +Comment[fi]="Tarjoaa pääsyn Open-Xchange-työryhmäpalvelimen tapaamis-, tehtävä- ja yhteystietoihin." +Comment[fr]=« Fournit l'accès aux contacts, aux rendez-vous et aux tâches stockés sur un serveur Open-Xchange de logiciels de collaboration. » +Comment[gl]=«Fornece acceso ás citas, tarefas e contactos almacenados nun servidor de traballo en grupo Open-Xchange.» +Comment[hu]=„Hozzáférést biztosít egy Open-Xchange csoportmunka kiszolgálón tárolt találkozókhoz, feladatokhoz és névjegyekhez.†+Comment[it]="Consente l'accesso ad appuntamenti, attività e contatti di un server di groupware Open-Xchange." +Comment[ko]="Open-Xchange 그룹웨어 ì„œë²„ì˜ ì•½ì†, í•  ì¼, ì—°ë½ì²˜ 정보를 가져옵니다." +Comment[lt]=„Suteikia prieigÄ… prie susitikimų, užduoÄių ir kontaktų Open-Xchange serveryje grupinio darbo serveryje.“ +Comment[nb]=«Gir tilgang til avtaler, oppgaver og kontakter lagret pÃ¥ en Open-Xchange gruppevare-tjener.» +Comment[nds]="Stellt Togriep op Terminen, Opgaven un Kontakten op en Open-Xchange-Arbeitkoppelserver praat." +Comment[nl]="Geeft toegang tot afspraken, taken en contactpersonen op een Open-Xchange-groupware-server." +Comment[pl]="Zapewnia dostÄ™p do spotkaÅ„, zadaÅ„ i kontaktów serwera groupware Open-Xchange." +Comment[pt]=Oferece o acesso aos compromissos, tarefas e contactos guardados num servidor de 'groupware' Open-Xchange. +Comment[pt_BR]="Fornece acesso aos compromissos, tarefas e contatos de um servidor groupware Open-Xchange." +Comment[ru]="ДоÑтуп к вÑтречам, задачам и контактам на Ñервере ÑовмеÑтной работы Open-Xchange" +Comment[sk]="Poskytuje prístup k schôdzkam, úlohám a kontaktom groupware servera Open-Xchange." +Comment[sr]="Омогућава приÑтуп ÑаÑтанцима, поÑловима и контактима Ñа Опен‑екÑчејнџовог групверÑког Ñервера." +Comment[sr@ijekavian]="Омогућава приÑтуп ÑаÑтанцима, поÑловима и контактима Ñа Опен‑екÑчејнџовог групверÑког Ñервера." +Comment[sr@ijekavianlatin]="Omogućava pristup sastancima, poslovima i kontaktima sa Open‑Xchangeovog grupverskog servera." +Comment[sr@latin]="Omogućava pristup sastancima, poslovima i kontaktima sa Open‑Xchangeovog grupverskog servera." +Comment[sv]="Ger tillgÃ¥ng till möten, uppgifter och kontakter lagrade pÃ¥ en Open-Xchange grupprogramserver." +Comment[tr]="Open-Xchange groupware sunucusundaki toplantı, yapılacak iÅŸ ve kiÅŸilere eriÅŸimi saÄŸlar." +Comment[uk]="Ðадає доÑтуп до запиÑів зуÑтрічей, завдань Ñ– контактів, Ñкі зберігаютьÑÑ Ð½Ð° Ñервері групової роботи Open-Xchange." +Comment[x-test]=xx"Provides access to the appointments, tasks, and contacts of an Open-Xchange groupware server."xx +Comment[zh_TW]=「æ供存å–儲存在 Open-Xchange 群組伺æœå™¨ä¸Šçš„約會ã€å·¥ä½œèˆ‡è¯çµ¡äººçš„功能。〠+Type=AkonadiResource +Exec=akonadi_openxchange_resource +Icon=ox + +X-Akonadi-MimeTypes=text/directory,text/calendar,application/x-vnd.kde.contactgroup,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_openxchange_resource diff --git a/kdepim-runtime/resources/openxchange/openxchangeresource.h b/kdepim-runtime/resources/openxchange/openxchangeresource.h new file mode 100644 index 00000000..a6491987 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/openxchangeresource.h @@ -0,0 +1,88 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef OPENXCHANGERESOURCE_H +#define OPENXCHANGERESOURCE_H + +#include + +class OpenXchangeResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV2 +{ + Q_OBJECT + + public: + explicit OpenXchangeResource( const QString &id ); + ~OpenXchangeResource(); + + virtual void cleanup(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + virtual void aboutToQuit(); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &collection ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + virtual void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + + virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + virtual void collectionChanged( const Akonadi::Collection &collection ); + // do not hide the other variant, use implementation from base class + // which just forwards to the one above + using Akonadi::AgentBase::ObserverV2::collectionChanged; + virtual void collectionRemoved( const Akonadi::Collection &collection ); + virtual void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource, + const Akonadi::Collection &collectionDestination ); + + private Q_SLOTS: + void onUpdateUsersJobFinished( KJob* ); + void onFoldersRequestJobFinished( KJob* ); + void onFoldersRequestDeltaJobFinished( KJob* ); + void onFolderCreateJobFinished( KJob* ); + void onFolderModifyJobFinished( KJob* ); + void onFolderMoveJobFinished( KJob* ); + void onFolderDeleteJobFinished( KJob* ); + + void onObjectsRequestJobFinished( KJob* ); + void onObjectsRequestDeltaJobFinished( KJob* ); + void onObjectRequestJobFinished( KJob* ); + void onObjectCreateJobFinished( KJob* ); + void onObjectModifyJobFinished( KJob* ); + void onObjectMoveJobFinished( KJob* ); + void onObjectDeleteJobFinished( KJob* ); + + void onFetchResourceCollectionsFinished( KJob* ); + + private: + void syncCollectionsRemoteIdCache(); + QMap mCollectionsMap; + + Akonadi::Collection mResourceCollection; + QMap mStandardCollectionsMap; +}; + +#endif diff --git a/kdepim-runtime/resources/openxchange/openxchangeresource.kcfg b/kdepim-runtime/resources/openxchange/openxchangeresource.kcfg new file mode 100644 index 00000000..66a114d8 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/openxchangeresource.kcfg @@ -0,0 +1,35 @@ + + + + + + + The URL of the Open-Xchange server, should be something like https://myserver.org/ + The URL of the Open-Xchange server, should be something like https://myserver.org/ + + + + The username that is used to log into the Open-Xchange server + The username that is used to log into the Open-Xchange server + + + + The password that is used to log into the Open-Xchange server + The password that is used to log into the Open-Xchange server + + + true + + Use incremental updates instead of reloading all data from the server each time + Use incremental updates instead of reloading all data from the server each time + + + 0 + + + + diff --git a/kdepim-runtime/resources/openxchange/oxa/connectiontestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/connectiontestjob.cpp new file mode 100644 index 00000000..c44351d0 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/connectiontestjob.cpp @@ -0,0 +1,77 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "connectiontestjob.h" + +#include +#include + +using namespace OXA; + +ConnectionTestJob::ConnectionTestJob( const QString &url, const QString &user, const QString &password, QObject *parent ) + : KJob( parent ), mUrl( url ), mUser( user ), mPassword( password ) +{ +} + +void ConnectionTestJob::start() +{ + const KUrl url( mUrl + QString::fromLatin1( "/ajax/login?action=login&name=%1&password=%2" ).arg( mUser ).arg( mPassword ) ); + + KJob *job = KIO::storedGet( url, KIO::Reload, KIO::HideProgressInfo ); + connect( job, SIGNAL(result(KJob*)), SLOT(httpJobFinished(KJob*)) ); +} + +void ConnectionTestJob::httpJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::StoredTransferJob *transferJob = qobject_cast( job ); + Q_ASSERT( transferJob ); + + const QString data = QString::fromUtf8( transferJob->data() ); + + // on success data contains something like: {"session":"e530578bca504aa89738fadde9e44b3d","random":"ac9090d2cc284fed926fa3c7e316c43b"} + // on failure data contains something like: {"category":1,"error_params":[],"error":"Invalid credentials.","error_id":"-1529642166-37","code":"LGI-0006"} + const int index = data.indexOf( QLatin1String( "\"session\":\"" ) ); + if ( index == -1 ) { // error case + const int errorIndex = data.indexOf( QLatin1String( "\"error\":\"" ) ); + const QString errorText = data.mid( errorIndex + 9, data.indexOf( QLatin1Char( '"' ), errorIndex + 10 ) - errorIndex - 9 ); + + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } else { // success case + const QString sessionId = data.mid( index + 11, 33 ); // I assume here the session id is always 32 characters long :} + + // logout correctly... + const KUrl url( mUrl + QString::fromLatin1( "/ajax/login?action=logout&session=%1" ).arg( sessionId ) ); + KIO::storedGet( url, KIO::Reload, KIO::HideProgressInfo ); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/connectiontestjob.h b/kdepim-runtime/resources/openxchange/oxa/connectiontestjob.h new file mode 100644 index 00000000..df9e37b8 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/connectiontestjob.h @@ -0,0 +1,49 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_CONNECTIONTESTJOB_H +#define OXA_CONNECTIONTESTJOB_H + +#include + +namespace OXA { + +class ConnectionTestJob : public KJob +{ + Q_OBJECT + + public: + ConnectionTestJob( const QString &url, const QString &user, const QString &password, QObject *parent = 0 ); + + virtual void start(); + + private Q_SLOTS: + void httpJobFinished( KJob* ); + + private: + QString mUrl; + QString mUser; + QString mPassword; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/contactutils.cpp b/kdepim-runtime/resources/openxchange/oxa/contactutils.cpp new file mode 100644 index 00000000..1d505b42 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/contactutils.cpp @@ -0,0 +1,424 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "contactutils.h" + +#include "davutils.h" +#include "oxutils.h" +#include "users.h" + +#include + +#include +#include + +using namespace OXA; + +void OXA::ContactUtils::parseContact( const QDomElement &propElement, Object &object ) +{ + bool isDistributionList = false; + QDomElement distributionListElement = propElement.firstChildElement( QLatin1String("distributionlist_flag") ); + if ( !distributionListElement.isNull() ) { + if ( OXUtils::readBoolean( distributionListElement.text() ) == true ) + isDistributionList = true; + } + + if ( isDistributionList ) { + KABC::ContactGroup contactGroup; + + QDomElement element = propElement.firstChildElement(); + while ( !element.isNull() ) { + const QString tagName = element.tagName(); + const QString text = OXUtils::readString( element.text() ); + + if ( tagName == QLatin1String( "displayname" ) ) { + contactGroup.setName( text ); + } else if ( tagName == QLatin1String( "distributionlist" ) ) { + QDomElement emailElement = element.firstChildElement(); + while ( !emailElement.isNull() ) { + const QString tagName = emailElement.tagName(); + const QString text = OXUtils::readString( emailElement.text() ); + + if ( tagName == QLatin1String( "email" ) ) { + const int emailField = OXUtils::readNumber( emailElement.attribute( QLatin1String( "emailfield" ) ) ); + if ( emailField == 0 ) { // internal data + KABC::ContactGroup::Data data; + data.setName( OXUtils::readString( emailElement.attribute( QLatin1String( "displayname" ) ) ) ); + data.setEmail( text ); + + contactGroup.append( data ); + } else { // external reference + // we convert them to internal data, seems like a more stable approach + KABC::ContactGroup::Data data; + const qlonglong uid = OXUtils::readNumber( emailElement.attribute( QLatin1String( "id" ) ) ); + + const User user = Users::self()->lookupUid( uid ); + if ( user.isValid() ) { + data.setName( user.name() ); + data.setEmail( user.email() ); + } else { + // fallback: use the data from the element + data.setName( OXUtils::readString( emailElement.attribute( QLatin1String( "displayname" ) ) ) ); + data.setEmail( text ); + } + + contactGroup.append( data ); + } + } + + emailElement = emailElement.nextSiblingElement(); + } + } + element = element.nextSiblingElement(); + } + + object.setContactGroup( contactGroup ); + } else { + KABC::Addressee contact; + KABC::Address homeAddress( KABC::Address::Home ); + KABC::Address workAddress( KABC::Address::Work ); + KABC::Address otherAddress( KABC::Address::Dom ); + + QDomElement element = propElement.firstChildElement(); + while ( !element.isNull() ) { + const QString tagName = element.tagName(); + const QString text = OXUtils::readString( element.text() ); + + // name + if ( tagName == QLatin1String( "title" ) ) { + contact.setTitle( text ); + } else if ( tagName == QLatin1String( "first_name" ) ) { + contact.setGivenName( text ); + } else if ( tagName == QLatin1String( "second_name" ) ) { + contact.setAdditionalName( text ); + } else if ( tagName == QLatin1String( "last_name" ) ) { + contact.setFamilyName( text ); + } else if ( tagName == QLatin1String( "suffix" ) ) { + contact.setSuffix( text ); + } else if ( tagName == QLatin1String( "displayname" ) ) { + contact.setFormattedName( text ); + } else if ( tagName == QLatin1String( "nickname" ) ) { + contact.setNickName( text ); + // dates + } else if ( tagName == QLatin1String( "birthday" ) ) { + contact.setBirthday( OXUtils::readDateTime( element.text() ) ); + } else if ( tagName == QLatin1String( "anniversary" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-Anniversary" ), OXUtils::readDateTime( element.text() ).toString( Qt::ISODate ) ); + } else if ( tagName == QLatin1String( "spouse_name" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-SpousesName" ), text ); + // addresses + } else if ( tagName == QLatin1String( "street" ) ) { + homeAddress.setStreet( text ); + } else if ( tagName == QLatin1String( "postal_code" ) ) { + homeAddress.setPostalCode( text ); + } else if ( tagName == QLatin1String( "city" ) ) { + homeAddress.setLocality( text ); + } else if ( tagName == QLatin1String( "country" ) ) { + homeAddress.setCountry( text ); + } else if ( tagName == QLatin1String( "state" ) ) { + homeAddress.setRegion( text ); + } else if ( tagName == QLatin1String( "business_street" ) ) { + workAddress.setStreet( text ); + } else if ( tagName == QLatin1String( "business_postal_code" ) ) { + workAddress.setPostalCode( text ); + } else if ( tagName == QLatin1String( "business_city" ) ) { + workAddress.setLocality( text ); + } else if ( tagName == QLatin1String( "business_country" ) ) { + workAddress.setCountry( text ); + } else if ( tagName == QLatin1String( "business_state" ) ) { + workAddress.setRegion( text ); + } else if ( tagName == QLatin1String( "second_street" ) ) { + otherAddress.setStreet( text ); + } else if ( tagName == QLatin1String( "second_postal_code" ) ) { + otherAddress.setPostalCode( text ); + } else if ( tagName == QLatin1String( "second_city" ) ) { + otherAddress.setLocality( text ); + } else if ( tagName == QLatin1String( "second_country" ) ) { + otherAddress.setCountry( text ); + } else if ( tagName == QLatin1String( "second_state" ) ) { + otherAddress.setRegion( text ); + } else if ( tagName == QLatin1String( "defaultaddress" ) ) { + const int number = text.toInt(); + if ( number == 1 ) + workAddress.setType( workAddress.type() | KABC::Address::Pref ); + else if ( number == 2 ) + homeAddress.setType( homeAddress.type() | KABC::Address::Pref ); + else if ( number == 3 ) + otherAddress.setType( otherAddress.type() | KABC::Address::Pref ); + // further information + } else if ( tagName == QLatin1String( "note" ) ) { + contact.setNote( text ); + } else if ( tagName == QLatin1String( "url" ) ) { + contact.setUrl( text ); + } else if ( tagName == QLatin1String( "image1" ) ) { + const QByteArray data = text.toUtf8(); + contact.setPhoto( KABC::Picture( QImage::fromData( QByteArray::fromBase64( data ) ) ) ); + // company information + } else if ( tagName == QLatin1String( "company" ) ) { + contact.setOrganization( text ); + } else if ( tagName == QLatin1String( "department" ) ) { + contact.setDepartment( text ); + } else if ( tagName == QLatin1String( "assistants_name" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-AssistantsName" ), text ); + } else if ( tagName == QLatin1String( "managers_name" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-ManagersName" ), text ); + } else if ( tagName == QLatin1String( "position" ) ) { + contact.setRole( text ); + } else if ( tagName == QLatin1String( "profession" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-Profession" ), text ); + } else if ( tagName == QLatin1String( "room_number" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-Office" ), text ); + // communication + } else if ( tagName == QLatin1String( "email1" ) ) { + contact.insertEmail( text, true ); + } else if ( tagName == QLatin1String( "email2" ) || + tagName == QLatin1String( "email3" ) ) { + contact.insertEmail( text ); + } else if ( tagName == QLatin1String( "mobile1" ) ) { + contact.insertPhoneNumber( KABC::PhoneNumber( text, KABC::PhoneNumber::Cell ) ); + } else if ( tagName == QLatin1String( "instant_messenger" ) ) { + contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-IMAddress" ), text ); + } else if ( tagName.startsWith( QLatin1String( "phone_" ) ) ) { + KABC::PhoneNumber number; + number.setNumber( text ); + bool supportedType = false; + + if ( tagName.endsWith( QLatin1String( "_business" ) ) ) { + number.setType( KABC::PhoneNumber::Work ); + supportedType = true; + } else if ( tagName.endsWith( QLatin1String( "_home" ) ) ) { + number.setType( KABC::PhoneNumber::Home ); + supportedType = true; + } else if ( tagName.endsWith( QLatin1String( "_other" ) ) ) { + number.setType( KABC::PhoneNumber::Voice ); + supportedType = true; + } else if ( tagName.endsWith( QLatin1String( "_car" ) ) ) { + number.setType( KABC::PhoneNumber::Car ); + supportedType = true; + } + + if ( supportedType ) + contact.insertPhoneNumber( number ); + } else if ( tagName.startsWith( QLatin1String( "fax_" ) ) ) { + KABC::PhoneNumber number; + number.setNumber( text ); + bool supportedType = false; + + if ( tagName.endsWith( QLatin1String( "_business" ) ) ) { + number.setType( KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ); + supportedType = true; + } else if ( tagName.endsWith( QLatin1String( "_home" ) ) ) { + number.setType( KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ); + supportedType = true; + } else if ( tagName.endsWith( QLatin1String( "_other" ) ) ) { + number.setType( KABC::PhoneNumber::Fax | KABC::PhoneNumber::Voice ); + supportedType = true; + } + + if ( supportedType ) + contact.insertPhoneNumber( number ); + } else if ( tagName == QLatin1String( "pager" ) ) { + contact.insertPhoneNumber( KABC::PhoneNumber( text, KABC::PhoneNumber::Pager ) ); + } else if ( tagName == QLatin1String( "categories" ) ) { + contact.setCategories( text.split( QRegExp( QLatin1String( ",\\s*" ) ) ) ); + } + + element = element.nextSiblingElement(); + } + + if ( !homeAddress.isEmpty() ) + contact.insertAddress( homeAddress ); + if ( !workAddress.isEmpty() ) + contact.insertAddress( workAddress ); + if ( !otherAddress.isEmpty() ) + contact.insertAddress( otherAddress ); + + object.setContact( contact ); + } +} + +void OXA::ContactUtils::addContactElements( QDomDocument &document, QDomElement &propElement, const Object &object, void *preloadedData ) +{ + if ( !object.contact().isEmpty() ) { + // it is a contact payload + + const KABC::Addressee contact = object.contact(); + + // name + DAVUtils::addOxElement( document, propElement, QLatin1String( "title" ), OXUtils::writeString( contact.title() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "first_name" ), OXUtils::writeString( contact.givenName() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "second_name" ), OXUtils::writeString( contact.additionalName() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "last_name" ), OXUtils::writeString( contact.familyName() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "suffix" ), OXUtils::writeString( contact.suffix() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "displayname" ), OXUtils::writeString( contact.formattedName() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "nickname" ), OXUtils::writeString( contact.nickName() ) ); + + // dates + if ( contact.birthday().date().isValid() ) + DAVUtils::addOxElement( document, propElement, QLatin1String( "birthday" ), OXUtils::writeDate( contact.birthday().date() ) ); + else + DAVUtils::addOxElement( document, propElement, QLatin1String( "birthday" ) ); + + // since QDateTime::to/fromString() doesn't carry timezone information, we have to fake it here + const QDate anniversary = QDate::fromString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-Anniversary" ) ), Qt::ISODate ); + if ( anniversary.isValid() ) + DAVUtils::addOxElement( document, propElement, QLatin1String( "anniversary" ), OXUtils::writeDate( anniversary ) ); + else + DAVUtils::addOxElement( document, propElement, QLatin1String( "anniversary" ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "spouse_name" ), OXUtils::writeString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-SpousesName" ) ) ) ); + + // addresses + const KABC::Address homeAddress = contact.address( KABC::Address::Home ); + if ( !homeAddress.isEmpty() ) { + DAVUtils::addOxElement( document, propElement, QLatin1String( "street" ), OXUtils::writeString( homeAddress.street() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "postal_code" ), OXUtils::writeString( homeAddress.postalCode() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "city" ), OXUtils::writeString( homeAddress.locality() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "state" ), OXUtils::writeString( homeAddress.region() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "country" ), OXUtils::writeString( homeAddress.country() ) ); + } + const KABC::Address workAddress = contact.address( KABC::Address::Work ); + if ( !workAddress.isEmpty() ) { + DAVUtils::addOxElement( document, propElement, QLatin1String( "business_street" ), OXUtils::writeString( workAddress.street() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "business_postal_code" ), OXUtils::writeString( workAddress.postalCode() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "business_city" ), OXUtils::writeString( workAddress.locality() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "business_state" ), OXUtils::writeString( workAddress.region() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "business_country" ), OXUtils::writeString( workAddress.country() ) ); + } + const KABC::Address otherAddress = contact.address( KABC::Address::Dom ); + if ( !otherAddress.isEmpty() ) { + DAVUtils::addOxElement( document, propElement, QLatin1String( "second_street" ), OXUtils::writeString( otherAddress.street() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "second_postal_code" ), OXUtils::writeString( otherAddress.postalCode() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "second_city" ), OXUtils::writeString( otherAddress.locality() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "second_state" ), OXUtils::writeString( otherAddress.region() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "second_country" ), OXUtils::writeString( otherAddress.country() ) ); + } + + // further information + DAVUtils::addOxElement( document, propElement, QLatin1String( "note" ), OXUtils::writeString( contact.note() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "url" ), OXUtils::writeString( contact.url().url() ) ); + + // image + const KABC::Picture photo = contact.photo(); + if ( !photo.data().isNull() ) { + QByteArray imageData; + QBuffer buffer( &imageData ); + buffer.open( QIODevice::WriteOnly ); + + QString contentType; + if ( !photo.data().hasAlphaChannel() ) { + photo.data().save( &buffer, "JPEG" ); + contentType = QLatin1String( "image/jpg" ); + } else { + photo.data().save( &buffer, "PNG" ); + contentType = QLatin1String( "image/png" ); + } + + buffer.close(); + + DAVUtils::addOxElement( document, propElement, QLatin1String( "image1" ), QString::fromLatin1( imageData.toBase64() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "image_content_type" ), contentType ); + } else { + DAVUtils::addOxElement( document, propElement, QLatin1String( "image1" ) ); + } + + // company information + DAVUtils::addOxElement( document, propElement, QLatin1String( "company" ), OXUtils::writeString( contact.organization() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "department" ), OXUtils::writeString( contact.department() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "assistants_name" ), OXUtils::writeString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-AssistantsName" ) ) ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "managers_name" ), OXUtils::writeString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-ManagersName" ) ) ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "position" ), OXUtils::writeString( contact.role() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "profession" ), OXUtils::writeString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-Profession" ) ) ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "room_number" ), OXUtils::writeString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-Office" ) ) ) ); + + // communication + const QStringList emails = contact.emails(); + for ( int i = 0; i < 3 && i < emails.count(); ++i ) { + DAVUtils::addOxElement( document, propElement, QString::fromLatin1( "email%1" ).arg( i + 1 ), OXUtils::writeString( emails.at( i ) ) ); + } + + DAVUtils::addOxElement( document, propElement, QLatin1String( "mobile1" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Cell ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "instant_messenger" ), OXUtils::writeString( contact.custom( QLatin1String( "KADDRESSBOOK" ), QLatin1String( "X-IMAddress" ) ) ) ); + + DAVUtils::addOxElement( document, propElement, QLatin1String( "phone_business" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Work ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "phone_home" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Home ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "phone_other" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Voice ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "phone_car" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Car ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "fax_business" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "fax_home" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ).number() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "fax_other" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Fax | KABC::PhoneNumber::Voice ).number() ) ); + + DAVUtils::addOxElement( document, propElement, QLatin1String( "pager" ), OXUtils::writeString( contact.phoneNumber( KABC::PhoneNumber::Pager ).number() ) ); + + DAVUtils::addOxElement( document, propElement, QLatin1String( "categories" ), OXUtils::writeString( contact.categories().join( QLatin1String( "," ) ) ) ); + } else { + // it is a distribution list payload + + const KABC::ContactGroup contactGroup = object.contactGroup(); + + DAVUtils::addOxElement( document, propElement, QLatin1String( "displayname" ), OXUtils::writeString( contactGroup.name() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "last_name" ), OXUtils::writeString( contactGroup.name() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "distributionlist_flag" ), OXUtils::writeBoolean( true ) ); + + QDomElement distributionList = DAVUtils::addOxElement( document, propElement, QLatin1String( "distributionlist" ) ); + + if ( preloadedData ) { + // the contact group contains contact references that has been preloaded + KABC::Addressee::List *contacts = static_cast( preloadedData ); + foreach ( const KABC::Addressee &contact, *contacts ) { + QDomElement email = DAVUtils::addOxElement( document, distributionList, QLatin1String( "email" ), + OXUtils::writeString( contact.preferredEmail() ) ); + + DAVUtils::setOxAttribute( email, QLatin1String( "folder_id" ), OXUtils::writeNumber( 0 ) ); + DAVUtils::setOxAttribute( email, QLatin1String( "emailfield" ), OXUtils::writeNumber( 0 ) ); + DAVUtils::setOxAttribute( email, QLatin1String( "id" ), OXUtils::writeNumber( 0 ) ); + DAVUtils::setOxAttribute( email, QLatin1String( "displayname" ), OXUtils::writeString( contact.realName() ) ); + } + + delete contacts; + } else { + // the contact group contains only internal contact data + for ( uint i = 0; i < contactGroup.dataCount(); ++i ) { + const KABC::ContactGroup::Data &data = contactGroup.data( i ); + QDomElement email = DAVUtils::addOxElement( document, distributionList, QLatin1String( "email" ), + OXUtils::writeString( data.email() ) ); + + DAVUtils::setOxAttribute( email, QLatin1String( "folder_id" ), OXUtils::writeNumber( 0 ) ); + DAVUtils::setOxAttribute( email, QLatin1String( "emailfield" ), OXUtils::writeNumber( 0 ) ); + DAVUtils::setOxAttribute( email, QLatin1String( "id" ), OXUtils::writeNumber( 0 ) ); + DAVUtils::setOxAttribute( email, QLatin1String( "displayname" ), OXUtils::writeString( data.name() ) ); + } + } + } +} + +KJob* OXA::ContactUtils::preloadJob( const Object &object ) +{ + Akonadi::ContactGroupExpandJob *job = new Akonadi::ContactGroupExpandJob( object.contactGroup() ); + return job; +} + +void* OXA::ContactUtils::preloadData( const Object&, KJob *job ) +{ + Akonadi::ContactGroupExpandJob *expandJob = qobject_cast( job ); + Q_ASSERT( expandJob ); + + return new KABC::Addressee::List( expandJob->contacts() ); +} diff --git a/kdepim-runtime/resources/openxchange/oxa/contactutils.h b/kdepim-runtime/resources/openxchange/oxa/contactutils.h new file mode 100644 index 00000000..882f91ad --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/contactutils.h @@ -0,0 +1,57 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_CONTACTUTILS_H +#define OXA_CONTACTUTILS_H + +#include "object.h" + +class KJob; + +class QDomDocument; +class QDomElement; + +namespace OXA { + +/** + * Namespace that contains helper methods for handling contacts. + * + * @author Tobias Koenig + */ +namespace ContactUtils +{ + /** + * Parses the XML tree under @p propElement and fills the contact data of @p object. + */ + void parseContact( const QDomElement &propElement, Object &object ); + + /** + * Adds the contact data of @p object to the @p document under the @p propElement. + */ + void addContactElements( QDomDocument &document, QDomElement &propElement, const Object &object, void *preloadedData ); + + KJob* preloadJob( const Object &object ); + void* preloadData( const Object &object, KJob *job ); +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/davmanager.cpp b/kdepim-runtime/resources/openxchange/oxa/davmanager.cpp new file mode 100644 index 00000000..5043b15c --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/davmanager.cpp @@ -0,0 +1,72 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "davmanager.h" + +#include + +#include + +using namespace OXA; + +DavManager* DavManager::mSelf = 0; + +DavManager::DavManager() +{ +} + +DavManager::~DavManager() +{ +} + +DavManager* DavManager::self() +{ + if ( !mSelf ) + mSelf = new DavManager(); + + return mSelf; +} + +void DavManager::setBaseUrl( const KUrl &url ) +{ + mBaseUrl = url; +} + +KUrl DavManager::baseUrl() const +{ + return mBaseUrl; +} + +KIO::DavJob* DavManager::createFindJob( const QString &path, const QDomDocument &document ) const +{ + KUrl url( mBaseUrl ); + url.setPath( path ); + + return KIO::davPropFind( url, document, "0", KIO::HideProgressInfo ); +} + +KIO::DavJob* DavManager::createPatchJob( const QString &path, const QDomDocument &document ) const +{ + KUrl url( mBaseUrl ); + url.setPath( path ); + + return KIO::davPropPatch( url, document, KIO::HideProgressInfo ); +} diff --git a/kdepim-runtime/resources/openxchange/oxa/davmanager.h b/kdepim-runtime/resources/openxchange/oxa/davmanager.h new file mode 100644 index 00000000..d010fd9a --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/davmanager.h @@ -0,0 +1,95 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_DAVMANAGER_H +#define OXA_DAVMANAGER_H + +#include + +namespace KIO { +class DavJob; +} + +class QDomDocument; + +namespace OXA { + +/** + * @short A class that manages DAV specific information. + * + * The DavManager stores the base url of the DAV service that + * shall be accessed and provides factory methods for creating + * DAV find and patch jobs. + * + * @author Tobias Koenig + */ +class DavManager +{ + public: + /** + * Destroys the DAV manager. + */ + ~DavManager(); + + /** + * Returns the global instance of the DAV manager. + */ + static DavManager* self(); + + /** + * Sets the base @p url the DAV manager should use. + */ + void setBaseUrl( const KUrl &url ); + + /** + * Returns the base url the DAV manager uses. + */ + KUrl baseUrl() const; + + /** + * Returns a new DAV find job. + * + * @param path The path that is appended to the base url. + * @param document The request XML document. + */ + KIO::DavJob* createFindJob( const QString &path, const QDomDocument &document ) const; + + /** + * Returns a new DAV patch job. + * + * @param path The path that is appended to the base url. + * @param document The request XML document. + */ + KIO::DavJob* createPatchJob( const QString &path, const QDomDocument &document ) const; + + private: + /** + * Creates a new DAV manager. + */ + DavManager(); + + KUrl mBaseUrl; + static DavManager* mSelf; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/davutils.cpp b/kdepim-runtime/resources/openxchange/oxa/davutils.cpp new file mode 100644 index 00000000..d5fbc354 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/davutils.cpp @@ -0,0 +1,72 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "davutils.h" + +using namespace OXA; + +QDomElement DAVUtils::addDavElement( QDomDocument &document, QDomNode &parentNode, const QString &tag ) +{ + const QDomElement element = document.createElementNS( QLatin1String( "DAV:" ), QLatin1String( "D:" ) + tag ); + parentNode.appendChild( element ); + + return element; +} + +QDomElement DAVUtils::addOxElement( QDomDocument &document, QDomNode &parentNode, const QString &tag, const QString &text ) +{ + QDomElement element = document.createElementNS( QLatin1String( "http://www.open-xchange.org" ), QLatin1String( "ox:" ) + tag ); + + if ( !text.isEmpty() ) { + const QDomText textNode = document.createTextNode( text ); + element.appendChild( textNode ); + } + + parentNode.appendChild( element ); + + return element; +} + +void DAVUtils::setOxAttribute( QDomElement &element, const QString &name, const QString &value ) +{ + element.setAttributeNS( QLatin1String( "http://www.open-xchange.org" ), QLatin1String( "ox:" ) + name, value ); +} + +bool DAVUtils::davErrorOccurred( const QDomDocument &document, QString &errorText, QString &errorStatus ) +{ + const QDomElement documentElement = document.documentElement(); + const QDomNodeList propStats = documentElement.elementsByTagNameNS( QLatin1String( "DAV:" ), + QLatin1String( "propstat" ) ); + + for ( int i = 0; i < propStats.count(); ++i ) { + const QDomElement propStat = propStats.at( i ).toElement(); + const QDomElement status = propStat.firstChildElement( QLatin1String( "status" ) ); + const QDomElement description = propStat.firstChildElement( QLatin1String( "responsedescription" ) ); + + if ( status.text() != QLatin1String( "200" ) ) { + errorText = description.text(); + errorStatus = status.text(); + return true; + } + } + + return false; +} diff --git a/kdepim-runtime/resources/openxchange/oxa/davutils.h b/kdepim-runtime/resources/openxchange/oxa/davutils.h new file mode 100644 index 00000000..ea663d46 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/davutils.h @@ -0,0 +1,69 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_DAVUTILS_H +#define OXA_DAVUTILS_H + +#include +#include +#include +#include + +namespace OXA { + +/** + * Namespace that contains methods for creating or modifying DAV XML documents. + * + * @author Tobias Koenig + */ +namespace DAVUtils +{ + /** + * Adds a new element with the given @p tag inside the DAV namespace under @p parentNode + * to the @p document. + * + * @return The newly added element. + */ + QDomElement addDavElement( QDomDocument &document, QDomNode &parentNode, const QString &tag ); + + /** + * Adds a new element with the given @p tag and @p value inside the OX namespace under @p parentNode + * to the @p document. + * + * @return The newly added element. + */ + QDomElement addOxElement( QDomDocument &document, QDomNode &parentNode, const QString &tag, const QString &text = QString() ); + + /** + * Sets the attribute of @p element inside the OX namespace with the given @p name to @p value. + */ + void setOxAttribute( QDomElement &element, const QString &name, const QString &value ); + + /** + * Checks whether the response @p document contains an error message. + * If so, @c true is returned, @p errorText set to the error message and @p errorStatus set to error status. + */ + bool davErrorOccurred( const QDomDocument &document, QString &errorText, QString &errorStatus ); +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/folder.cpp b/kdepim-runtime/resources/openxchange/oxa/folder.cpp new file mode 100644 index 00000000..fbe982ae --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folder.cpp @@ -0,0 +1,199 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "folder.h" + +using namespace OXA; + +Folder::Permissions::Permissions() + : mFolderPermission( NoPermission ), + mObjectReadPermission( NoReadPermission ), + mObjectWritePermission( NoWritePermission ), + mObjectDeletePermission( NoDeletePermission ), + mAdminFlag( false ) +{ +} + +void Folder::Permissions::setFolderPermission( FolderPermission permission ) +{ + mFolderPermission = permission; +} + +Folder::Permissions::FolderPermission Folder::Permissions::folderPermission() const +{ + return mFolderPermission; +} + +void Folder::Permissions::setObjectReadPermission( ObjectReadPermission permission ) +{ + mObjectReadPermission = permission; +} + +Folder::Permissions::ObjectReadPermission Folder::Permissions::objectReadPermission() const +{ + return mObjectReadPermission; +} + +void Folder::Permissions::setObjectWritePermission( ObjectWritePermission permission ) +{ + mObjectWritePermission = permission; +} + +Folder::Permissions::ObjectWritePermission Folder::Permissions::objectWritePermission() const +{ + return mObjectWritePermission; +} + +void Folder::Permissions::setObjectDeletePermission( ObjectDeletePermission permission ) +{ + mObjectDeletePermission = permission; +} + +Folder::Permissions::ObjectDeletePermission Folder::Permissions::objectDeletePermission() const +{ + return mObjectDeletePermission; +} + +void Folder::Permissions::setAdminFlag( bool value ) +{ + mAdminFlag = value; +} + +bool Folder::Permissions::adminFlag() const +{ + return mAdminFlag; +} + + +Folder::Folder() + : mObjectId( -1 ), mFolderId( -1 ) +{ +} + +void Folder::setObjectStatus( ObjectStatus status ) +{ + mObjectStatus = status; +} + +Folder::ObjectStatus Folder::objectStatus() const +{ + return mObjectStatus; +} + +void Folder::setTitle( const QString &title ) +{ + mTitle = title; +} + +QString Folder::title() const +{ + return mTitle; +} + +void Folder::setType( Type type ) +{ + mType = type; +} + +Folder::Type Folder::type() const +{ + return mType; +} + +void Folder::setModule( Module module ) +{ + mModule = module; +} + +Folder::Module Folder::module() const +{ + return mModule; +} + +void Folder::setObjectId( qlonglong id ) +{ + mObjectId = id; +} + +qlonglong Folder::objectId() const +{ + return mObjectId; +} + +void Folder::setFolderId( qlonglong id ) +{ + mFolderId = id; +} + +qlonglong Folder::folderId() const +{ + return mFolderId; +} + +void Folder::setIsDefaultFolder( bool value ) +{ + mIsDefaultFolder = value; +} + +bool Folder::isDefaultFolder() const +{ + return mIsDefaultFolder; +} + +void Folder::setOwner( qlonglong id ) +{ + mOwner = id; +} + +qlonglong Folder::owner() const +{ + return mOwner; +} + +void Folder::setLastModified( const QString &timeStamp ) +{ + mLastModified = timeStamp; +} + +QString Folder::lastModified() const +{ + return mLastModified; +} + +void Folder::setUserPermissions( const UserPermissions &permissions ) +{ + mUserPermissions = permissions; +} + +Folder::UserPermissions Folder::userPermissions() const +{ + return mUserPermissions; +} + +void Folder::setGroupPermissions( const GroupPermissions &permissions ) +{ + mGroupPermissions = permissions; +} + +Folder::GroupPermissions Folder::groupPermissions() const +{ + return mGroupPermissions; +} diff --git a/kdepim-runtime/resources/openxchange/oxa/folder.h b/kdepim-runtime/resources/openxchange/oxa/folder.h new file mode 100644 index 00000000..2a82623a --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folder.h @@ -0,0 +1,204 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDER_H +#define OXA_FOLDER_H + +#include +#include +#include + +namespace OXA { + +/** + * @short A class that contains information about folders on the OX server. + * + * @author Tobias Koenig + */ +class Folder +{ + public: + /** + * Describes a list of folders. + */ + typedef QList List; + + /** + * Describes the status of the folder. + */ + enum ObjectStatus + { + Created, ///< The folder has been created or modified. + Deleted ///< The folder has been deleted. + }; + + /** + * Describes the visibility type of the folder. + */ + enum Type + { + Public, ///< The folder is visible for all users. + Private ///< The folder is only visible for the owner. + }; + + /** + * Describes the module the folder belongs to. + */ + enum Module + { + Unbound, ///< The folder is only a structural folder. + Calendar, ///< The folder contains events. + Contacts, ///< The folder contains contacts. + Tasks ///< The folder contains tasks. + }; + + /** + * Describes the permissions a user or group can have on + * a folder. + */ + class Permissions + { + public: + /** + * Describes the permissions on folder objects. + */ + enum FolderPermission + { + NoPermission = 0, ///< No permissions. + FolderIsVisible = 2, ///< The folder can be read. + CreateObjects = 4, ///< Objects can be created in the folder. + CreateSubfolders = 8, ///< Subfolders can be created in the folder. + AdminPermission = 128 ///< Permissions can be changed. + }; + + /** + * Describes the read permissions on other objects. + */ + enum ObjectReadPermission + { + NoReadPermission = 0, ///< The objects can not be read. + ReadOwnObjects = 2, ///< Only own objects can be read. + ReadAllObjects = 4, ///< All objects can be read. + AdminReadPermission = 128 + }; + + /** + * Describes the write permissions on other objects. + */ + enum ObjectWritePermission + { + NoWritePermission = 0, ///< The objects can not be written. + WriteOwnObjects = 2, ///< Only own objects can be written. + WriteAllObjects = 4, ///< All objects can be written. + AdminWritePermission = 128 + }; + + /** + * Describes the delete permissions on other objects. + */ + enum ObjectDeletePermission + { + NoDeletePermission = 0, ///< The objects can not be deleted. + DeleteOwnObjects = 2, ///< Only own objects can be deleted. + DeleteAllObjects = 4, ///< All objects can be deleted. + AdminDeletePermission = 128 + }; + + Permissions(); + + void setFolderPermission( FolderPermission permission ); + FolderPermission folderPermission() const; + + void setObjectReadPermission( ObjectReadPermission permission ); + ObjectReadPermission objectReadPermission() const; + + void setObjectWritePermission( ObjectWritePermission permission ); + ObjectWritePermission objectWritePermission() const; + + void setObjectDeletePermission( ObjectDeletePermission permission ); + ObjectDeletePermission objectDeletePermission() const; + + void setAdminFlag( bool value ); + bool adminFlag() const; + + private: + FolderPermission mFolderPermission; + ObjectReadPermission mObjectReadPermission; + ObjectWritePermission mObjectWritePermission; + ObjectDeletePermission mObjectDeletePermission; + bool mAdminFlag; + }; + + typedef QMap UserPermissions; + typedef QMap GroupPermissions; + + Folder(); + + void setObjectStatus( ObjectStatus status ); + ObjectStatus objectStatus() const; + + void setTitle( const QString &title ); + QString title() const; + + void setType( Type type ); + Type type() const; + + void setModule( Module module ); + Module module() const; + + void setObjectId( qlonglong id ); + qlonglong objectId() const; + + void setFolderId( qlonglong id ); + qlonglong folderId() const; + + void setIsDefaultFolder( bool value ); + bool isDefaultFolder() const; + + void setOwner( qlonglong id ); + qlonglong owner() const; + + void setLastModified( const QString &timeStamp ); + QString lastModified() const; + + void setUserPermissions( const UserPermissions &permissions ); + UserPermissions userPermissions() const; + + void setGroupPermissions( const GroupPermissions &permissions ); + GroupPermissions groupPermissions() const; + + private: + ObjectStatus mObjectStatus; + QString mTitle; + Type mType; + Module mModule; + qlonglong mObjectId; + qlonglong mFolderId; + bool mIsDefaultFolder; + qlonglong mOwner; + QString mLastModified; + UserPermissions mUserPermissions; + GroupPermissions mGroupPermissions; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/foldercreatejob.cpp b/kdepim-runtime/resources/openxchange/oxa/foldercreatejob.cpp new file mode 100644 index 00000000..e1f2539e --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldercreatejob.cpp @@ -0,0 +1,96 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "foldercreatejob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "folderutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +FolderCreateJob::FolderCreateJob( const Folder &folder, QObject *parent ) + : KJob( parent ), mFolder( folder ) +{ +} + +void FolderCreateJob::start() +{ + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + + FolderUtils::addFolderElements( document, prop, mFolder ); + + const QString path = QLatin1String( "/servlet/webdav.folders" ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Folder FolderCreateJob::folder() const +{ + return mFolder; +} + +void FolderCreateJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + + QDomElement element = prop.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "object_id" ) ) + mFolder.setObjectId( OXUtils::readNumber( element.text() ) ); + else if ( element.tagName() == QLatin1String( "last_modified" ) ) + mFolder.setLastModified( OXUtils::readString( element.text() ) ); + + element = element.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/foldercreatejob.h b/kdepim-runtime/resources/openxchange/oxa/foldercreatejob.h new file mode 100644 index 00000000..5b7e2632 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldercreatejob.h @@ -0,0 +1,68 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERCREATEJOB_H +#define OXA_FOLDERCREATEJOB_H + +#include + +#include "folder.h" + +namespace OXA { + +/** + * @short A job that creates a new folder on the OX server. + * + * @author Tobias Koenig + */ +class FolderCreateJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new folder create job. + * + * @param folder The folder to create. + * @param parent The parent object. + */ + explicit FolderCreateJob( const Folder &folder, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the updated folder that has been created. + */ + Folder folder() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Folder mFolder; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/folderdeletejob.cpp b/kdepim-runtime/resources/openxchange/oxa/folderdeletejob.cpp new file mode 100644 index 00000000..79def2ab --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folderdeletejob.cpp @@ -0,0 +1,78 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "folderdeletejob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "oxutils.h" + +#include + +#include + +using namespace OXA; + +FolderDeleteJob::FolderDeleteJob( const Folder &folder, QObject *parent ) + : KJob( parent ), mFolder( folder ) +{ +} + +void FolderDeleteJob::start() +{ + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mFolder.objectId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "method" ), OXUtils::writeString( QLatin1String( "DELETE" ) ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "last_modified" ), OXUtils::writeString( mFolder.lastModified() ) ); + + const QString path = QLatin1String( "/servlet/webdav.folders" ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +void FolderDeleteJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/folderdeletejob.h b/kdepim-runtime/resources/openxchange/oxa/folderdeletejob.h new file mode 100644 index 00000000..76e9a26d --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folderdeletejob.h @@ -0,0 +1,65 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERDELETEJOB_H +#define OXA_FOLDERDELETEJOB_H + +#include "folder.h" + +#include + +namespace OXA { + +/** + * @short A job that deletes a folder on the OX server. + * + * @author Tobias Koenig + */ +class FolderDeleteJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new folder delete job. + * + * @param folder The folder to delete. + * @param parent The parent object. + * + * @note The folder needs the objectId, folderId and lastModified property set. + */ + explicit FolderDeleteJob( const Folder &folder, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Folder mFolder; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.cpp b/kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.cpp new file mode 100644 index 00000000..08cabb8f --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.cpp @@ -0,0 +1,95 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "foldermodifyjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +FolderModifyJob::FolderModifyJob( const Folder &folder, QObject *parent ) + : KJob( parent ), mFolder( folder ) +{ +} + +void FolderModifyJob::start() +{ + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "title" ), OXUtils::writeString( mFolder.title() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mFolder.objectId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder_id" ), OXUtils::writeNumber( mFolder.folderId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "last_modified" ), OXUtils::writeString( mFolder.lastModified() ) ); + + const QString path = QLatin1String( "/servlet/webdav.folders" ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Folder FolderModifyJob::folder() const +{ + return mFolder; +} + +void FolderModifyJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + + QDomElement element = prop.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "last_modified" ) ) + mFolder.setLastModified( OXUtils::readString( element.text() ) ); + + element = element.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.h b/kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.h new file mode 100644 index 00000000..7615c053 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldermodifyjob.h @@ -0,0 +1,70 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERMODIFYJOB_H +#define OXA_FOLDERMODIFYJOB_H + +#include + +#include "folder.h" + +namespace OXA { + +/** + * @short A job that modifies a folder on the OX server. + * + * @author Tobias Koenig + */ +class FolderModifyJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new folder modify job. + * + * @param folder The folder to modify. + * @param parent The parent object. + * + * @note The folder needs at least the objectId and lastModified property set. + */ + explicit FolderModifyJob( const Folder &folder, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the updated folder that has been modified. + */ + Folder folder() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Folder mFolder; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/foldermovejob.cpp b/kdepim-runtime/resources/openxchange/oxa/foldermovejob.cpp new file mode 100644 index 00000000..b0175578 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldermovejob.cpp @@ -0,0 +1,95 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "foldermovejob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +FolderMoveJob::FolderMoveJob( const Folder &folder, const Folder &destinationFolder, QObject *parent ) + : KJob( parent ), mFolder( folder ), mDestinationFolder( destinationFolder ) +{ +} + +void FolderMoveJob::start() +{ + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mFolder.objectId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder_id" ), OXUtils::writeNumber( mFolder.folderId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "last_modified" ), OXUtils::writeString( mFolder.lastModified() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder" ), OXUtils::writeNumber( mDestinationFolder.objectId() ) ); + + const QString path = QLatin1String( "/servlet/webdav.folders" ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Folder FolderMoveJob::folder() const +{ + return mFolder; +} + +void FolderMoveJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + + QDomElement element = prop.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "last_modified" ) ) + mFolder.setLastModified( OXUtils::readString( element.text() ) ); + + element = element.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/foldermovejob.h b/kdepim-runtime/resources/openxchange/oxa/foldermovejob.h new file mode 100644 index 00000000..fd465246 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldermovejob.h @@ -0,0 +1,73 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERMOVEJOB_H +#define OXA_FOLDERMOVEJOB_H + +#include + +#include "folder.h" + +namespace OXA { + +/** + * @short A job that moves a folder on the OX server. + * + * @author Tobias Koenig + */ +class FolderMoveJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new folder move job. + * + * @param folder The folder to move. + * @param destinationFolder The new parent folder. + * @param parent The parent object. + * + * @note The folder needs the objectId, folderId and lastModified property set, the + * destinationFolder the objectId property. + */ + FolderMoveJob( const Folder &folder, const Folder &destinationFolder, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the updated folder that has been moved. + */ + Folder folder() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Folder mFolder; + Folder mDestinationFolder; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/folderrequestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/folderrequestjob.cpp new file mode 100644 index 00000000..5b76913e --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folderrequestjob.cpp @@ -0,0 +1,85 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "folderrequestjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "folderutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +FolderRequestJob::FolderRequestJob( const Folder &folder, QObject *parent ) + : KJob( parent ), mFolder( folder ) +{ +} + +void FolderRequestJob::start() +{ + QDomDocument document; + QDomElement multistatus = DAVUtils::addDavElement( document, document, QLatin1String( "multistatus" ) ); + QDomElement prop = DAVUtils::addDavElement( document, multistatus, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mFolder.objectId() ) ); + + const QString path = QLatin1String( "/servlet/webdav.folders" ); + + KIO::DavJob *job = DavManager::self()->createFindJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Folder FolderRequestJob::folder() const +{ + return mFolder; +} + +void FolderRequestJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + mFolder = FolderUtils::parseFolder( prop ); + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/folderrequestjob.h b/kdepim-runtime/resources/openxchange/oxa/folderrequestjob.h new file mode 100644 index 00000000..5a33cf6f --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folderrequestjob.h @@ -0,0 +1,70 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERREQUESTJOB_H +#define OXA_FOLDERREQUESTJOB_H + +#include + +#include "folder.h" + +namespace OXA { + +/** + * @short A job that requests a folder from the OX server. + * + * @author Tobias Koenig + */ +class FolderRequestJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new folder request job. + * + * @param folder The folder to request. + * @param parent The parent object. + * + * @note The folder needs the objectId property set. + */ + explicit FolderRequestJob( const Folder &folder, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the requested folder. + */ + Folder folder() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Folder mFolder; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.cpp b/kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.cpp new file mode 100644 index 00000000..e03a9874 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.cpp @@ -0,0 +1,93 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2010 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. +*/ + +#include "foldersrequestdeltajob.h" + +#include "foldersrequestjob.h" + +#include + +using namespace OXA; + +FoldersRequestDeltaJob::FoldersRequestDeltaJob( qulonglong lastSync, QObject *parent ) + : KJob( parent ), mLastSync( lastSync ), mJobFinishedCount( 0 ) +{ +} + +void FoldersRequestDeltaJob::start() +{ + FoldersRequestJob *modifiedJob = new FoldersRequestJob( mLastSync, FoldersRequestJob::Modified, this ); + connect( modifiedJob, SIGNAL(result(KJob*)), SLOT(fetchModifiedJobFinished(KJob*)) ); + modifiedJob->start(); + + FoldersRequestJob *deletedJob = new FoldersRequestJob( mLastSync, FoldersRequestJob::Deleted, this ); + connect( deletedJob, SIGNAL(result(KJob*)), SLOT(fetchDeletedJobFinished(KJob*)) ); + deletedJob->start(); +} + +Folder::List FoldersRequestDeltaJob::modifiedFolders() const +{ + return mModifiedFolders; +} + +Folder::List FoldersRequestDeltaJob::deletedFolders() const +{ + return mDeletedFolders; +} + +void FoldersRequestDeltaJob::fetchModifiedJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + const FoldersRequestJob *requestJob = qobject_cast( job ); + + mModifiedFolders << requestJob->folders(); + + mJobFinishedCount++; + + if ( mJobFinishedCount == 2 ) + emitResult(); +} + +void FoldersRequestDeltaJob::fetchDeletedJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + const FoldersRequestJob *requestJob = qobject_cast( job ); + + mDeletedFolders << requestJob->folders(); + + mJobFinishedCount++; + + if ( mJobFinishedCount == 2 ) + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.h b/kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.h new file mode 100644 index 00000000..34eeacb9 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldersrequestdeltajob.h @@ -0,0 +1,78 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2010 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. +*/ + +#ifndef OXA_FOLDERSREQUESTDELTAJOB_H +#define OXA_FOLDERSREQUESTDELTAJOB_H + +#include + +#include "folder.h" + +namespace OXA { + +/** + * @short A job that requests the delta for folders changes from the OX server. + * + * @author Tobias Koenig + */ +class FoldersRequestDeltaJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new folders request delta job. + * + * @param lastSync The timestamp of the last sync. Only added, modified and deleted folders + * after this date will be requested. 0 will request all available folders. + * @param parent The parent object. + */ + explicit FoldersRequestDeltaJob( qulonglong lastSync, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of all added and modified folders. + */ + Folder::List modifiedFolders() const; + + /** + * Returns the list of all deleted folders. + */ + Folder::List deletedFolders() const; + + private Q_SLOTS: + void fetchModifiedJobFinished( KJob* ); + void fetchDeletedJobFinished( KJob* ); + + private: + qulonglong mLastSync; + Folder::List mModifiedFolders; + Folder::List mDeletedFolders; + int mJobFinishedCount; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.cpp new file mode 100644 index 00000000..d6b20e6d --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.cpp @@ -0,0 +1,94 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "foldersrequestjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "folderutils.h" +#include "oxutils.h" + +#include + +#include + +using namespace OXA; + +FoldersRequestJob::FoldersRequestJob( qulonglong lastSync, Mode mode, QObject *parent ) + : KJob( parent ), mLastSync( lastSync ), mMode( mode ) +{ +} + +void FoldersRequestJob::start() +{ + QDomDocument document; + QDomElement multistatus = DAVUtils::addDavElement( document, document, QLatin1String( "multistatus" ) ); + QDomElement prop = DAVUtils::addDavElement( document, multistatus, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "lastsync" ), OXUtils::writeNumber( mLastSync ) ); + if ( mMode == Modified ) + DAVUtils::addOxElement( document, prop, QLatin1String( "objectmode" ), QLatin1String( "MODIFIED" ) ); + else + DAVUtils::addOxElement( document, prop, QLatin1String( "objectmode" ), QLatin1String( "DELETED" ) ); + + const QString path = QLatin1String( "/servlet/webdav.folders" ); + + KIO::DavJob *job = DavManager::self()->createFindJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Folder::List FoldersRequestJob::folders() const +{ + return mFolders; +} + +void FoldersRequestJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + while ( !response.isNull() ) { + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + mFolders.append( FolderUtils::parseFolder( prop ) ); + response = response.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.h b/kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.h new file mode 100644 index 00000000..b1760149 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/foldersrequestjob.h @@ -0,0 +1,81 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERSREQUESTJOB_H +#define OXA_FOLDERSREQUESTJOB_H + +#include + +#include "folder.h" + +namespace OXA { + +/** + * @short A job that requests all folders from the OX server. + * + * @author Tobias Koenig + */ +class FoldersRequestJob : public KJob +{ + Q_OBJECT + + public: + /** + * Describes the mode of the request job. + */ + enum Mode + { + Modified, ///< Fetches all new and modified folders + Deleted ///< Fetches all deleted folders + }; + + /** + * Creates a new folders request job. + * + * @param lastSync The timestamp of the last sync. Only added, modified or deleted folders + * after this date will be requested. 0 will request all available folders. + * @param mode The mode of folders to request. + * @param parent The parent object. + */ + explicit FoldersRequestJob( qulonglong lastSync = 0, Mode mode = Modified, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of all requested folders. + */ + Folder::List folders() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + qulonglong mLastSync; + Mode mMode; + Folder::List mFolders; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/folderutils.cpp b/kdepim-runtime/resources/openxchange/oxa/folderutils.cpp new file mode 100644 index 00000000..6167f6f5 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folderutils.cpp @@ -0,0 +1,185 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "folderutils.h" + +#include "davutils.h" +#include "folder.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +static void createFolderPermissions( const Folder &folder, QDomDocument &document, QDomElement &permissions ) +{ + { + const Folder::UserPermissions userPermissions = folder.userPermissions(); + Folder::UserPermissions::ConstIterator it = userPermissions.constBegin(); + while ( it != userPermissions.constEnd() ) { + QDomElement user = DAVUtils::addOxElement( document, permissions, QLatin1String( "user" ), + OXUtils::writeNumber( it.key() ) ); + DAVUtils::setOxAttribute( user, QLatin1String( "folderpermission" ), + OXUtils::writeNumber( it.value().folderPermission() ) ); + DAVUtils::setOxAttribute( user, QLatin1String( "objectreadpermission" ), + OXUtils::writeNumber( it.value().objectReadPermission() ) ); + DAVUtils::setOxAttribute( user, QLatin1String( "objectwritepermission" ), + OXUtils::writeNumber( it.value().objectWritePermission() ) ); + DAVUtils::setOxAttribute( user, QLatin1String( "objectdeletepermission" ), + OXUtils::writeNumber( it.value().objectDeletePermission() ) ); + DAVUtils::setOxAttribute( user, QLatin1String( "admin_flag" ), + OXUtils::writeBoolean( it.value().adminFlag() ) ); + + ++it; + } + } + + { + const Folder::GroupPermissions groupPermissions = folder.groupPermissions(); + Folder::GroupPermissions::ConstIterator it = groupPermissions.constBegin(); + while ( it != groupPermissions.constEnd() ) { + QDomElement group = DAVUtils::addOxElement( document, permissions, QLatin1String( "group" ), + OXUtils::writeNumber( it.key() ) ); + DAVUtils::setOxAttribute( group, QLatin1String( "folderpermission" ), + OXUtils::writeNumber( it.value().folderPermission() ) ); + DAVUtils::setOxAttribute( group, QLatin1String( "objectreadpermission" ), + OXUtils::writeNumber( it.value().objectReadPermission() ) ); + DAVUtils::setOxAttribute( group, QLatin1String( "objectwritepermission" ), + OXUtils::writeNumber( it.value().objectWritePermission() ) ); + DAVUtils::setOxAttribute( group, QLatin1String( "objectdeletepermission" ), + OXUtils::writeNumber( it.value().objectDeletePermission() ) ); + DAVUtils::setOxAttribute( group, QLatin1String( "admin_flag" ), + OXUtils::writeBoolean( it.value().adminFlag() ) ); + + ++it; + } + } +} + +static void parseFolderPermissions( const QDomElement &permissions, Folder &folder ) +{ + Folder::UserPermissions userPermissions; + Folder::GroupPermissions groupPermissions; + + QDomElement element = permissions.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "user" ) ) { + Folder::Permissions permissions; + permissions.setFolderPermission( (Folder::Permissions::FolderPermission)OXUtils::readNumber( element.attribute( QLatin1String( "folderpermission" ), QLatin1String( "0" ) ) ) ); + permissions.setObjectReadPermission( (Folder::Permissions::ObjectReadPermission)OXUtils::readNumber( element.attribute( QLatin1String( "objectreadpermission" ), QLatin1String( "0" ) ) ) ); + permissions.setObjectWritePermission( (Folder::Permissions::ObjectWritePermission)OXUtils::readNumber( element.attribute( QLatin1String( "objectwritepermission" ), QLatin1String( "0" ) ) ) ); + permissions.setObjectDeletePermission( (Folder::Permissions::ObjectDeletePermission)OXUtils::readNumber( element.attribute( QLatin1String( "objectdeletepermission" ), QLatin1String( "0" ) ) ) ); + permissions.setAdminFlag( OXUtils::readBoolean( element.attribute( QLatin1String( "admin_flag" ), QLatin1String( "false" ) ) ) ); + + userPermissions.insert( OXUtils::readNumber( element.text() ), permissions ); + } else if ( element.tagName() == QLatin1String( "group" ) ) { + Folder::Permissions permissions; + permissions.setFolderPermission( (Folder::Permissions::FolderPermission)OXUtils::readNumber( element.attribute( QLatin1String( "folderpermission" ), QLatin1String( "0" ) ) ) ); + permissions.setObjectReadPermission( (Folder::Permissions::ObjectReadPermission)OXUtils::readNumber( element.attribute( QLatin1String( "objectreadpermission" ), QLatin1String( "0" ) ) ) ); + permissions.setObjectWritePermission( (Folder::Permissions::ObjectWritePermission)OXUtils::readNumber( element.attribute( QLatin1String( "objectwritepermission" ), QLatin1String( "0" ) ) ) ); + permissions.setObjectDeletePermission( (Folder::Permissions::ObjectDeletePermission)OXUtils::readNumber( element.attribute( QLatin1String( "objectdeletepermission" ), QLatin1String( "0" ) ) ) ); + permissions.setAdminFlag( OXUtils::readBoolean( element.attribute( QLatin1String( "admin_flag" ), QLatin1String( "false" ) ) ) ); + + groupPermissions.insert( OXUtils::readNumber( element.text() ), permissions ); + } + + element = element.nextSiblingElement(); + } + + folder.setUserPermissions( userPermissions ); + folder.setGroupPermissions( groupPermissions ); +} + +Folder OXA::FolderUtils::parseFolder( const QDomElement &propElement ) +{ + Folder folder; + + QDomElement element = propElement.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "object_status" ) ) { + const QString content = OXUtils::readString( element.text() ); + if ( content == QLatin1String( "CREATE" ) ) + folder.setObjectStatus( Folder::Created ); + else if ( content == QLatin1String( "DELETE" ) ) + folder.setObjectStatus( Folder::Deleted ); + else + Q_ASSERT( false ); + } else if ( element.tagName() == QLatin1String( "title" ) ) { + folder.setTitle( OXUtils::readString( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "owner" ) ) { + folder.setOwner( OXUtils::readNumber( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "module" ) ) { + const QString content = OXUtils::readString( element.text() ); + if ( content == QLatin1String( "calendar" ) ) + folder.setModule( Folder::Calendar ); + else if ( content == QLatin1String( "contact" ) ) + folder.setModule( Folder::Contacts ); + else if ( content == QLatin1String( "task" ) ) + folder.setModule( Folder::Tasks ); + else + folder.setModule( Folder::Unbound ); + } else if ( element.tagName() == QLatin1String( "type" ) ) { + const QString content = OXUtils::readString( element.text() ); + if ( content == QLatin1String( "public" ) ) + folder.setType( Folder::Public ); + else if ( content == QLatin1String( "private" ) ) + folder.setType( Folder::Private ); + else + Q_ASSERT( false ); + } else if ( element.tagName() == QLatin1String( "defaultfolder" ) ) { + folder.setIsDefaultFolder( OXUtils::readBoolean( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "last_modified" ) ) { + folder.setLastModified( OXUtils::readString( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "object_id" ) ) { + folder.setObjectId( OXUtils::readNumber( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "folder_id" ) ) { + folder.setFolderId( OXUtils::readNumber( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "permissions" ) ) { + parseFolderPermissions( element, folder ); + } + + element = element.nextSiblingElement(); + } + + return folder; +} + +void OXA::FolderUtils::addFolderElements( QDomDocument &document, QDomElement &propElement, const Folder &folder ) +{ + DAVUtils::addOxElement( document, propElement, QLatin1String( "title" ), OXUtils::writeString( folder.title() ) ); + DAVUtils::addOxElement( document, propElement, QLatin1String( "folder_id" ), OXUtils::writeNumber( folder.folderId() ) ); + + const QString type = (folder.type() == Folder::Public ? QLatin1String( "public" ) : QLatin1String( "private" )); + DAVUtils::addOxElement( document, propElement, QLatin1String( "type" ), OXUtils::writeString( type ) ); + + QString module; + switch ( folder.module() ) { + case Folder::Calendar: module = QLatin1String( "calendar" ); break; + case Folder::Contacts: module = QLatin1String( "contact" ); break; + case Folder::Tasks: module = QLatin1String( "task" ); break; + default: break; + } + DAVUtils::addOxElement( document, propElement, QLatin1String( "module" ), OXUtils::writeString( module ) ); + + QDomElement permissions = DAVUtils::addOxElement( document, propElement, QLatin1String( "permissions" ) ); + createFolderPermissions( folder, document, permissions ); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/folderutils.h b/kdepim-runtime/resources/openxchange/oxa/folderutils.h new file mode 100644 index 00000000..8e56703b --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/folderutils.h @@ -0,0 +1,52 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_FOLDERUTILS_H +#define OXA_FOLDERUTILS_H + +class QDomDocument; +class QDomElement; + +namespace OXA { + +class Folder; + +/** + * Namespace that contains helper methods for handling folders. + * + * @author Tobias Koenig + */ +namespace FolderUtils +{ + /** + * Parses the XML tree under @p propElement and return the folder. + */ + Folder parseFolder( const QDomElement &propElement ); + + /** + * Adds the @p folder data to the @p document under the @p propElement. + */ + void addFolderElements( QDomDocument &document, QDomElement &propElement, const Folder &folder ); +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/incidenceutils.cpp b/kdepim-runtime/resources/openxchange/oxa/incidenceutils.cpp new file mode 100644 index 00000000..1f0592b3 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/incidenceutils.cpp @@ -0,0 +1,566 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "incidenceutils.h" + +#include "davmanager.h" +#include "davutils.h" +#include "oxutils.h" +#include "users.h" + +#include +#include + +#include + +#include +#include + +using namespace OXA; + +static void parseMembersAttribute( const QDomElement &element, + const KCalCore::Incidence::Ptr &incidence ) +{ + incidence->clearAttendees(); + + for ( QDomElement child = element.firstChildElement(); !child.isNull(); child = child.nextSiblingElement() ) { + if ( child.tagName() == QLatin1String( "user" ) ) { + const QString uid = child.text(); + + const User user = Users::self()->lookupUid( uid.toLongLong() ); + + QString name; + QString email; + KCalCore::Attendee::Ptr attendee = incidence->attendeeByUid( uid ); + if ( !user.isValid() ) { + if ( attendee ) + continue; + + name = uid; + email = uid + '@' + KUrl( DavManager::self()->baseUrl() ).host(); + } else { + name = user.name(); + email = user.email(); + } + + if ( attendee ) { + attendee->setName( name ); + attendee->setEmail( email ); + } else { + attendee = KCalCore::Attendee::Ptr( new KCalCore::Attendee( name, email ) ); + attendee->setUid( uid ); + incidence->addAttendee( attendee ); + } + + const QString status = child.attribute( "confirm" ); + if ( !status.isEmpty() ) { + if ( status == QLatin1String( "accept" ) ) { + attendee->setStatus( KCalCore::Attendee::Accepted ); + } else if ( status == QLatin1String( "decline" ) ) { + attendee->setStatus( KCalCore::Attendee::Declined ); + } else { + attendee->setStatus( KCalCore::Attendee::NeedsAction ); + } + } + } + } +} + +static void parseIncidenceAttribute( const QDomElement &element, + const KCalCore::Incidence::Ptr &incidence ) +{ + const QString tagName = element.tagName(); + const QString text = OXUtils::readString( element.text() ); + + if ( tagName == QLatin1String( "title" ) ) { + incidence->setSummary( text ); + } else if ( tagName == QLatin1String( "note" ) ) { + incidence->setDescription( text ); + } else if ( tagName == QLatin1String( "alarm" ) ) { + const int minutes = OXUtils::readNumber( element.text() ); + if ( minutes != 0 ) { + KCalCore::Alarm::List alarms = incidence->alarms(); + KCalCore::Alarm::Ptr alarm; + if ( alarms.isEmpty() ) + alarm = incidence->newAlarm(); + else + alarm = alarms.first(); + + if ( alarm->type() == KCalCore::Alarm::Invalid ) + alarm->setType( KCalCore::Alarm::Display ); + + KCalCore::Duration duration( minutes * -60 ); + alarm->setStartOffset( duration ); + alarm->setEnabled( true ); + } else { + // 0 reminder -> disable alarm + incidence->clearAlarms(); + } + } else if ( tagName == QLatin1String( "created_by" ) ) { + const User user = Users::self()->lookupUid( OXUtils::readNumber( element.text() ) ); + incidence->setOrganizer( KCalCore::Person::Ptr( new KCalCore::Person( user.name(), user.email() ) ) ); + } else if ( tagName == QLatin1String( "participants" ) ) { + parseMembersAttribute( element, incidence ); + } else if ( tagName == QLatin1String( "private_flag" ) ) { + if ( OXUtils::readBoolean( element.text() ) == true ) + incidence->setSecrecy( KCalCore::Incidence::SecrecyPrivate ); + else + incidence->setSecrecy( KCalCore::Incidence::SecrecyPublic ); + } else if ( tagName == QLatin1String( "categories" ) ) { + incidence->setCategories( text.split( QRegExp( QLatin1String( ",\\s*" ) ) ) ); + } +} + +static void parseEventAttribute( const QDomElement &element, + const KCalCore::Event::Ptr &event ) +{ + const QString tagName = element.tagName(); + const QString text = OXUtils::readString( element.text() ); + + if ( tagName == QLatin1String( "start_date" ) ) { + KDateTime dateTime = KDateTime( OXUtils::readDateTime( element.text() ), KDateTime::UTC ); + if ( event->allDay() ) + dateTime.setDateOnly( true ); + + event->setDtStart( dateTime ); + + } else if ( tagName == QLatin1String( "end_date" ) ) { + KDateTime dateTime = KDateTime( OXUtils::readDateTime( element.text() ), KDateTime::UTC ); + if ( event->allDay() ) + dateTime = dateTime.addSecs( -1 ); + + event->setDtEnd( dateTime ); + + } else if ( tagName == QLatin1String( "location" ) ) { + event->setLocation( text ); + } +} + +static void parseTodoAttribute( const QDomElement &element, + const KCalCore::Todo::Ptr &todo ) +{ + const QString tagName = element.tagName(); + const QString text = OXUtils::readString( element.text() ); + + if ( tagName == QLatin1String( "start_date" ) ) { + const KDateTime dateTime = KDateTime( OXUtils::readDateTime( element.text() ), KDateTime::UTC ); + if ( dateTime.isValid() ) { + todo->setDtStart( dateTime ); + } + } else if ( tagName == QLatin1String( "end_date" ) ) { + const KDateTime dateTime = KDateTime( OXUtils::readDateTime( element.text() ), KDateTime::UTC ); + if ( dateTime.isValid() ) { + todo->setDtDue( dateTime ); + } + } else if ( tagName == QLatin1String( "priority" ) ) { + const int priorityNumber = OXUtils::readNumber( element.text() ); + if ( priorityNumber < 1 || priorityNumber > 3 ) { + qDebug() << "Unknown priority:" << text; + } else { + int priority; + switch ( priorityNumber ) { + case 1: + priority = 9; + break; + case 2: + priority = 5; + break; + case 3: + priority = 1; + break; + } + todo->setPriority( priority ); + } + } else if ( tagName == QLatin1String( "percent_completed" ) ) { + todo->setPercentComplete( OXUtils::readNumber( element.text() ) ); + } +} + +static void parseRecurrence( const QDomElement &element, + const KCalCore::Incidence::Ptr &incidence ) +{ + QString type; + + int dailyValue = -1; + QDateTime endDate; + + int weeklyValue = -1; + QBitArray days( 7 ); // days, starting with monday + bool daysSet = false; + + int monthlyValueDay = -1; + int monthlyValueMonth = -1; + + int yearlyValueDay = -1; + int yearlyMonth = -1; + + int monthly2Recurrency = 0; + int monthly2ValueMonth = -1; + + int yearly2Recurrency = 0; + int yearly2Day = 0; + int yearly2Month = -1; + + KCalCore::DateList deleteExceptions; + + for ( QDomElement child = element.firstChildElement(); !child.isNull(); child = child.nextSiblingElement() ) { + const QString tagName = child.tagName(); + const QString text = OXUtils::readString( child.text() ); + + if ( tagName == QLatin1String( "recurrence_type" ) ) { + type = text; + } else if ( tagName == QLatin1String( "interval" ) ) { + dailyValue = text.toInt(); + weeklyValue = text.toInt(); + monthlyValueMonth = text.toInt(); + monthly2ValueMonth = text.toInt(); + } else if ( tagName == QLatin1String( "days" ) ) { + int tmp = text.toInt(); // OX encodes days binary: 1=Su, 2=Mo, 4=Tu, ... + for ( int i = 0; i < 7; ++i ) { + if ( tmp & (1 << i) ) + days.setBit( (i + 6) % 7 ); + } + daysSet = true; + } else if ( tagName == QLatin1String( "day_in_month" ) ) { + monthlyValueDay = text.toInt(); + monthly2Recurrency = text.toInt(); + yearlyValueDay = text.toInt(); + yearly2Recurrency = text.toInt(); + } else if ( tagName == QLatin1String( "month" ) ) { + yearlyMonth = text.toInt() + 1; // starts at 0 + yearly2Month = text.toInt() + 1; + } else if ( ( tagName == QLatin1String( "deleteexceptions" ) ) || ( tagName == QLatin1String( "changeexceptions" ) ) ) { + const QStringList exceptionDates = text.split( QLatin1String( "," ) ); + foreach ( const QString &date, exceptionDates ) + deleteExceptions.append( OXUtils::readDate( date ) ); + } else if ( tagName == QLatin1String( "until" ) ) { + endDate = OXUtils::readDateTime( child.text() ); + } + //TODO: notification + } + + if ( daysSet && type == QLatin1String( "monthly" ) ) + type = QLatin1String( "monthly2" ); // HACK: OX doesn't cleanly distinguish between monthly and monthly2 + if ( daysSet && type == QLatin1String( "yearly" ) ) + type = QLatin1String( "yearly2" ); + + KCalCore::Recurrence *recurrence = incidence->recurrence(); + + if ( type == QLatin1String( "daily" ) ) { + recurrence->setDaily( dailyValue ); + } else if ( type == QLatin1String( "weekly" ) ) { + recurrence->setWeekly( weeklyValue, days ); + } else if ( type == QLatin1String( "monthly" ) ) { + recurrence->setMonthly( monthlyValueMonth ); + recurrence->addMonthlyDate( monthlyValueDay ); + } else if ( type == QLatin1String( "yearly" ) ) { + recurrence->setYearly( 1 ); + recurrence->addYearlyDate( yearlyValueDay ); + recurrence->addYearlyMonth( yearlyMonth ); + } else if ( type == QLatin1String( "monthly2" ) ) { + recurrence->setMonthly( monthly2ValueMonth ); + QBitArray _days( 7 ); + if ( daysSet ) + _days = days; + else + _days.setBit( incidence->dtStart().date().dayOfWeek() ); + recurrence->addMonthlyPos( monthly2Recurrency, _days ); + } else if ( type == QLatin1String( "yearly2" ) ) { + recurrence->setYearly( 1 ); + recurrence->addYearlyMonth( yearly2Month ); + QBitArray _days( 7 ); + if ( daysSet ) + _days = days; + else + _days.setBit( ( yearly2Day + 5 ) % 7 ); + recurrence->addYearlyPos( yearly2Recurrency, _days ); + } + + if ( endDate.isValid() ) + recurrence->setEndDate( endDate.date() ); + + recurrence->setExDates( deleteExceptions ); +} + + +static void createIncidenceAttributes( QDomDocument &document, QDomElement &parent, + const KCalCore::Incidence::Ptr &incidence ) +{ + DAVUtils::addOxElement( document, parent, QLatin1String( "title" ), OXUtils::writeString( incidence->summary() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "note" ), OXUtils::writeString( incidence->description() ) ); + + if ( incidence->attendeeCount() > 0 ) { + QDomElement members = DAVUtils::addOxElement( document, parent, QLatin1String( "participants" ) ); + const KCalCore::Attendee::List attendees = incidence->attendees(); + foreach ( const KCalCore::Attendee::Ptr attendee, attendees ) { + const User user = Users::self()->lookupEmail( attendee->email() ); + + if ( !user.isValid() ) + continue; + + QString status; + switch ( attendee->status() ) { + case KCalCore::Attendee::Accepted: status = QLatin1String( "accept" ); break; + case KCalCore::Attendee::Declined: status = QLatin1String( "decline" ); break; + default: status = QLatin1String( "none" ); break; + } + + QDomElement element = DAVUtils::addOxElement( document, members, QLatin1String( "user" ), OXUtils::writeNumber( user.uid() ) ); + DAVUtils::setOxAttribute( element, "confirm", status ); + } + } + + if ( incidence->secrecy() == KCalCore::Incidence::SecrecyPublic ) + DAVUtils::addOxElement( document, parent, QLatin1String( "private_flag" ), OXUtils::writeBoolean( false ) ); + else + DAVUtils::addOxElement( document, parent, QLatin1String( "private_flag" ), OXUtils::writeBoolean( true ) ); + + // set reminder as the number of minutes to the start of the event + const KCalCore::Alarm::List alarms = incidence->alarms(); + if ( !alarms.isEmpty() && alarms.first()->hasStartOffset() && alarms.first()->enabled() ) { + DAVUtils::addOxElement( document, parent, QLatin1String( "alarm_flag" ), OXUtils::writeBoolean( true ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "alarm" ), OXUtils::writeNumber( (-1) * alarms.first()->startOffset().asSeconds() / 60 ) ); + } else { + DAVUtils::addOxElement( document, parent, QLatin1String( "alarm_flag" ), OXUtils::writeBoolean( false ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "alarm" ), QLatin1String( "0" ) ); + } + + // categories + DAVUtils::addOxElement( document, parent, QLatin1String( "categories" ), OXUtils::writeString( incidence->categories().join( QLatin1String( ", " ) ) ) ); +} + +static void createEventAttributes( QDomDocument &document, QDomElement &parent, + const KCalCore::Event::Ptr &event ) +{ + if ( event->allDay() ) { + DAVUtils::addOxElement( document, parent, QLatin1String( "start_date" ), OXUtils::writeDate( event->dtStart().date() ) ); + if ( event->hasEndDate() ) DAVUtils::addOxElement( document, parent, QLatin1String( "end_date" ), OXUtils::writeDate( event->dtEnd().date() ) ); + } else { + DAVUtils::addOxElement( document, parent, QLatin1String( "start_date" ), OXUtils::writeDateTime( event->dtStart().dateTime() ) ); + if ( event->hasEndDate() ) DAVUtils::addOxElement( document, parent, QLatin1String( "end_date" ), OXUtils::writeDateTime( event->dtEnd().dateTime() ) ); + } + + if ( !event->hasEndDate() ) DAVUtils::addOxElement( document, parent, QLatin1String( "end_date" ) ); + + DAVUtils::addOxElement( document, parent, QLatin1String( "location" ), OXUtils::writeString( event->location() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "full_time" ), OXUtils::writeBoolean( event->allDay() ) ); + + if ( event->transparency() == KCalCore::Event::Transparent ) { + DAVUtils::addOxElement( document, parent, QLatin1String( "shown_as" ), OXUtils::writeNumber( 4 ) ); + } else if ( event->transparency() == KCalCore::Event::Opaque ) { + DAVUtils::addOxElement( document, parent, QLatin1String( "shown_as" ), OXUtils::writeNumber( 1 ) ); + } + +} + +static void createTaskAttributes( QDomDocument &document, QDomElement &parent, + const KCalCore::Todo::Ptr &todo ) +{ + if ( todo->hasStartDate() ) + DAVUtils::addOxElement( document, parent, QLatin1String( "start_date" ), OXUtils::writeDateTime( todo->dtStart().dateTime() ) ); + else + DAVUtils::addOxElement( document, parent, QLatin1String( "start_date" ) ); + + if ( todo->hasDueDate() ) + DAVUtils::addOxElement( document, parent, QLatin1String( "end_date" ), OXUtils::writeDateTime( todo->dtDue().dateTime() ) ); + else + DAVUtils::addOxElement( document, parent, QLatin1String( "end_date" ) ); + + QString priority; + switch ( todo->priority() ) { + case 9: + case 8: + priority = QLatin1String( "1" ); + break; + case 2: + case 1: + priority = QLatin1String( "3" ); + break; + default: + priority = QLatin1String( "2" ); + break; + } + DAVUtils::addOxElement( document, parent, QLatin1String( "priority" ), priority ); + + DAVUtils::addOxElement( document, parent, QLatin1String( "percent_completed" ), OXUtils::writeNumber( todo->percentComplete() ) ); +} + +static void createRecurrenceAttributes( QDomDocument &document, QDomElement &parent, + const KCalCore::Incidence::Ptr &incidence ) +{ + if ( !incidence->recurs() ) { + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "none" ) ); + return; + } + + const KCalCore::Recurrence *recurrence = incidence->recurrence(); + int monthOffset = -1; + switch ( recurrence->recurrenceType() ) { + case KCalCore::Recurrence::rDaily: + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "daily" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "interval" ), OXUtils::writeNumber( recurrence->frequency() ) ); + break; + case KCalCore::Recurrence::rWeekly: + { + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "weekly" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "interval" ), OXUtils::writeNumber( recurrence->frequency() ) ); + + int days = 0; + for ( int i = 0; i < 7; ++i ) { + if ( recurrence->days()[i] ) + days += 1 << ( ( i + 1 ) % 7 ); + } + + DAVUtils::addOxElement( document, parent, QLatin1String( "days" ), OXUtils::writeNumber( days ) ); + } + break; + case KCalCore::Recurrence::rMonthlyDay: + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "monthly" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "interval" ), OXUtils::writeNumber( recurrence->frequency() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "day_in_month" ), OXUtils::writeNumber( recurrence->monthDays().first() ) ); + break; + case KCalCore::Recurrence::rMonthlyPos: + { + const KCalCore::RecurrenceRule::WDayPos wdp = recurrence->monthPositions().first(); + + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "monthly" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "interval" ), OXUtils::writeNumber( recurrence->frequency() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "days" ), OXUtils::writeNumber( 1 << wdp.day() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "day_in_month" ), OXUtils::writeNumber( wdp.pos() ) ); + } + break; + case KCalCore::Recurrence::rYearlyMonth: + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "yearly" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "interval" ), QLatin1String( "1" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "day_in_month" ), OXUtils::writeNumber( recurrence->yearDates().first() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "month" ), OXUtils::writeNumber( recurrence->yearMonths().first() + monthOffset ) ); + break; + case KCalCore::Recurrence::rYearlyPos: + { + const KCalCore::RecurrenceRule::WDayPos wdp = recurrence->monthPositions().first(); + + DAVUtils::addOxElement( document, parent, QLatin1String( "recurrence_type" ), QLatin1String( "yearly" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "interval" ), QLatin1String( "1" ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "days" ), OXUtils::writeNumber( 1 << wdp.day() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "day_in_month" ), OXUtils::writeNumber( wdp.pos() ) ); + DAVUtils::addOxElement( document, parent, QLatin1String( "month" ), OXUtils::writeNumber( recurrence->yearMonths().first() + monthOffset ) ); + } + break; + default: + qDebug() << "unsupported recurrence type:" << recurrence->recurrenceType(); + } + + if ( recurrence->endDateTime().isValid() ) + DAVUtils::addOxElement( document, parent, QLatin1String( "until" ), OXUtils::writeDateTime( recurrence->endDateTime().dateTime() ) ); + else + DAVUtils::addOxElement( document, parent, QLatin1String( "until" ) ); + + // delete exceptions + const KCalCore::DateList exceptionList = recurrence->exDates(); + + QStringList dates; + foreach ( const QDate &date, exceptionList ) + dates.append( OXUtils::writeDate( date ) ); + + DAVUtils::addOxElement( document, parent, QLatin1String( "deleteexceptions" ), dates.join( QLatin1String( "," ) ) ); + + //TODO: changeexceptions + +} + +void OXA::IncidenceUtils::parseEvent( const QDomElement &propElement, Object &object ) +{ + KCalCore::Event::Ptr event( new KCalCore::Event ); + + const QDomElement fullTimeElement = propElement.firstChildElement( "full_time" ); + if ( !fullTimeElement.isNull() ) + event->setAllDay( OXUtils::readBoolean( fullTimeElement.text() ) ); + + const QDomElement ShowAsElement = propElement.firstChildElement( "shown_as" ); + if ( !ShowAsElement.isNull() ) { + int showAs = OXUtils::readNumber( ShowAsElement.text() ); + switch (showAs) { + case 1 : event->setTransparency(KCalCore::Event::Transparent); break; + case 4 : event->setTransparency(KCalCore::Event::Opaque); break; + default : event->setTransparency(KCalCore::Event::Opaque); + } + } + + bool doesRecur = false; + const QDomElement recurrenceTypeElement = propElement.firstChildElement( "recurrence_type" ); + if ( !recurrenceTypeElement.isNull() && recurrenceTypeElement.text() != QLatin1String( "none" ) ) + doesRecur = true; + + QDomElement element = propElement.firstChildElement(); + while ( !element.isNull() ) { + parseIncidenceAttribute( element, event ); + parseEventAttribute( element, event ); + + element = element.nextSiblingElement(); + } + + if ( doesRecur ) + parseRecurrence( propElement, event ); + else + event->recurrence()->unsetRecurs(); + + object.setEvent( KCalCore::Incidence::Ptr( event ) ); +} + +void OXA::IncidenceUtils::parseTask( const QDomElement &propElement, Object &object ) +{ + KCalCore::Todo::Ptr todo( new KCalCore::Todo ); + todo->setSecrecy( KCalCore::Incidence::SecrecyPrivate ); + + bool doesRecur = false; + const QDomElement recurrenceTypeElement = propElement.firstChildElement( "recurrence_type" ); + if ( !recurrenceTypeElement.isNull() && recurrenceTypeElement.text() != QLatin1String( "none" ) ) + doesRecur = true; + + QDomElement element = propElement.firstChildElement(); + while ( !element.isNull() ) { + parseIncidenceAttribute( element, todo ); + parseTodoAttribute( element, todo ); + + element = element.nextSiblingElement(); + } + + if ( doesRecur ) + parseRecurrence( propElement, todo ); + else + todo->recurrence()->unsetRecurs(); + + object.setTask( KCalCore::Incidence::Ptr( todo ) ); +} + +void OXA::IncidenceUtils::addEventElements( QDomDocument &document, QDomElement &propElement, const Object &object ) +{ + createIncidenceAttributes( document, propElement, object.event() ); + createEventAttributes( document, propElement, object.event().staticCast() ); + createRecurrenceAttributes( document, propElement, object.event() ); +} + +void OXA::IncidenceUtils::addTaskElements( QDomDocument &document, QDomElement &propElement, const Object &object ) +{ + createIncidenceAttributes( document, propElement, object.task() ); + createTaskAttributes( document, propElement, object.task().staticCast() ); + createRecurrenceAttributes( document, propElement, object.task() ); +} diff --git a/kdepim-runtime/resources/openxchange/oxa/incidenceutils.h b/kdepim-runtime/resources/openxchange/oxa/incidenceutils.h new file mode 100644 index 00000000..1bf4dc7b --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/incidenceutils.h @@ -0,0 +1,60 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_INCIDENCEUTILS_H +#define OXA_INCIDENCEUTILS_H + +#include "object.h" + +class QDomDocument; +class QDomElement; + +namespace OXA { + +/** + * Namespace that contains helper methods for handling events and tasks. + */ +namespace IncidenceUtils +{ + /** + * Parses the XML tree under @p propElement and fills the event data of @p object. + */ + void parseEvent( const QDomElement &propElement, Object &object ); + + /** + * Parses the XML tree under @p propElement and fills the task data of @p object. + */ + void parseTask( const QDomElement &propElement, Object &object ); + + /** + * Adds the event data of @p object to the @p document under the @p propElement. + */ + void addEventElements( QDomDocument &document, QDomElement &propElement, const Object &object ); + + /** + * Adds the task data of @p object to the @p document under the @p propElement. + */ + void addTaskElements( QDomDocument &document, QDomElement &propElement, const Object &object ); +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/object.cpp b/kdepim-runtime/resources/openxchange/oxa/object.cpp new file mode 100644 index 00000000..ad3e827c --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/object.cpp @@ -0,0 +1,123 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "object.h" + +using namespace OXA; + +Object::Object() + : mObjectStatus( Created ), mObjectId( -1 ), mFolderId( -1 ) +{ +} + +void Object::setObjectStatus( ObjectStatus status ) +{ + mObjectStatus = status; +} + +Object::ObjectStatus Object::objectStatus() const +{ + return mObjectStatus; +} + +void Object::setObjectId( qlonglong id ) +{ + mObjectId = id; +} + +qlonglong Object::objectId() const +{ + return mObjectId; +} + +void Object::setFolderId( qlonglong id ) +{ + mFolderId = id; +} + +qlonglong Object::folderId() const +{ + return mFolderId; +} + +void Object::setLastModified( const QString &timeStamp ) +{ + mLastModified = timeStamp; +} + +QString Object::lastModified() const +{ + return mLastModified; +} + +void Object::setModule( Folder::Module module ) +{ + mModule = module; +} + +Folder::Module Object::module() const +{ + return mModule; +} + +void Object::setContact( const KABC::Addressee &contact ) +{ + mModule = Folder::Contacts; + mContact = contact; +} + +KABC::Addressee Object::contact() const +{ + return mContact; +} + +void Object::setContactGroup( const KABC::ContactGroup &group ) +{ + mModule = Folder::Contacts; + mContactGroup = group; +} + +KABC::ContactGroup Object::contactGroup() const +{ + return mContactGroup; +} + +void Object::setEvent( const KCalCore::Incidence::Ptr &event ) +{ + mModule = Folder::Calendar; + mEvent = event; +} + +KCalCore::Incidence::Ptr Object::event() const +{ + return mEvent; +} + +void Object::setTask( const KCalCore::Incidence::Ptr &task ) +{ + mModule = Folder::Tasks; + mTask = task; +} + +KCalCore::Incidence::Ptr Object::task() const +{ + return mTask; +} diff --git a/kdepim-runtime/resources/openxchange/oxa/object.h b/kdepim-runtime/resources/openxchange/oxa/object.h new file mode 100644 index 00000000..f93ce723 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/object.h @@ -0,0 +1,96 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECT_H +#define OXA_OBJECT_H + +#include "folder.h" + +#include +#include +#include + +#include +#include + +namespace OXA { + +class Object +{ + public: + /** + * Describes a list of objects. + */ + typedef QList List; + + /** + * Describes the status of the object. + */ + enum ObjectStatus + { + Created, ///< The object has been created or modified. + Deleted ///< The object has been deleted. + }; + + Object(); + + void setObjectStatus( ObjectStatus status ); + ObjectStatus objectStatus() const; + + void setObjectId( qlonglong id ); + qlonglong objectId() const; + + void setFolderId( qlonglong id ); + qlonglong folderId() const; + + void setLastModified( const QString &timeStamp ); + QString lastModified() const; + + void setModule( Folder::Module module ); + Folder::Module module() const; + + void setContact( const KABC::Addressee &contact ); + KABC::Addressee contact() const; + + void setContactGroup( const KABC::ContactGroup &group ); + KABC::ContactGroup contactGroup() const; + + void setEvent( const KCalCore::Incidence::Ptr &event ); + KCalCore::Incidence::Ptr event() const; + + void setTask( const KCalCore::Incidence::Ptr &task ); + KCalCore::Incidence::Ptr task() const; + + private: + ObjectStatus mObjectStatus; + qlonglong mObjectId; + qlonglong mFolderId; + QString mLastModified; + Folder::Module mModule; + KABC::Addressee mContact; + KABC::ContactGroup mContactGroup; + KCalCore::Incidence::Ptr mEvent; + KCalCore::Incidence::Ptr mTask; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectcreatejob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectcreatejob.cpp new file mode 100644 index 00000000..a655d556 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectcreatejob.cpp @@ -0,0 +1,119 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectcreatejob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "objectutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +ObjectCreateJob::ObjectCreateJob( const Object &object, QObject *parent ) + : KJob( parent ), mObject( object ) +{ +} + +void ObjectCreateJob::start() +{ + if ( ObjectUtils::needsPreloading( mObject ) ) { + KJob *job = ObjectUtils::preloadJob( mObject ); + connect( job, SIGNAL(result(KJob*)), SLOT(preloadingJobFinished(KJob*)) ); + job->start(); + } else { + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + + ObjectUtils::addObjectElements( document, prop, mObject ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); + } +} + +Object ObjectCreateJob::object() const +{ + return mObject; +} + +void ObjectCreateJob::preloadingJobFinished( KJob *job ) +{ + void *preloadedData = ObjectUtils::preloadData( mObject, job ); + + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + + ObjectUtils::addObjectElements( document, prop, mObject, preloadedData ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *davJob = DavManager::self()->createPatchJob( path, document ); + connect( davJob, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +void ObjectCreateJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + + QDomElement element = prop.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "object_id" ) ) + mObject.setObjectId( OXUtils::readNumber( element.text() ) ); + else if ( element.tagName() == QLatin1String( "last_modified" ) ) + mObject.setLastModified( OXUtils::readString( element.text() ) ); + + element = element.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectcreatejob.h b/kdepim-runtime/resources/openxchange/oxa/objectcreatejob.h new file mode 100644 index 00000000..a4141f99 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectcreatejob.h @@ -0,0 +1,52 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTCREATEJOB_H +#define OXA_OBJECTCREATEJOB_H + +#include + +#include "object.h" + +namespace OXA { + +class ObjectCreateJob : public KJob +{ + Q_OBJECT + + public: + explicit ObjectCreateJob( const Object &object, QObject *parent = 0 ); + + virtual void start(); + + Object object() const; + + private Q_SLOTS: + void preloadingJobFinished( KJob* ); + void davJobFinished( KJob* ); + + private: + Object mObject; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectdeletejob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectdeletejob.cpp new file mode 100644 index 00000000..530ed628 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectdeletejob.cpp @@ -0,0 +1,78 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectdeletejob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "objectutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +ObjectDeleteJob::ObjectDeleteJob( const Object &object, QObject *parent ) + : KJob( parent ), mObject( object ) +{ +} + +void ObjectDeleteJob::start() +{ + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mObject.objectId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder_id" ), OXUtils::writeNumber( mObject.folderId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "method" ), OXUtils::writeString( QLatin1String( "DELETE" ) ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "last_modified" ), OXUtils::writeString( mObject.lastModified() ) ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +void ObjectDeleteJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectdeletejob.h b/kdepim-runtime/resources/openxchange/oxa/objectdeletejob.h new file mode 100644 index 00000000..a0188717 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectdeletejob.h @@ -0,0 +1,49 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTDELETEJOB_H +#define OXA_OBJECTDELETEJOB_H + +#include "object.h" + +#include + +namespace OXA { + +class ObjectDeleteJob : public KJob +{ + Q_OBJECT + + public: + explicit ObjectDeleteJob( const Object &object, QObject *parent = 0 ); + + virtual void start(); + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Object mObject; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.cpp new file mode 100644 index 00000000..2d84be9e --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.cpp @@ -0,0 +1,117 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectmodifyjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "objectutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +ObjectModifyJob::ObjectModifyJob( const Object &object, QObject *parent ) + : KJob( parent ), mObject( object ) +{ +} + +void ObjectModifyJob::start() +{ + if ( ObjectUtils::needsPreloading( mObject ) ) { + KJob *job = ObjectUtils::preloadJob( mObject ); + connect( job, SIGNAL(result(KJob*)), SLOT(preloadingJobFinished(KJob*)) ); + job->start(); + } else { + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + + ObjectUtils::addObjectElements( document, prop, mObject ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); + } +} + +Object ObjectModifyJob::object() const +{ + return mObject; +} + +void ObjectModifyJob::preloadingJobFinished( KJob *job ) +{ + void *preloadedData = ObjectUtils::preloadData( mObject, job ); + + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + + ObjectUtils::addObjectElements( document, prop, mObject, preloadedData ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *davJob = DavManager::self()->createPatchJob( path, document ); + connect( davJob, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +void ObjectModifyJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + + QDomElement element = prop.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "last_modified" ) ) + mObject.setLastModified( OXUtils::readString( element.text() ) ); + + element = element.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.h b/kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.h new file mode 100644 index 00000000..ebb85cca --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectmodifyjob.h @@ -0,0 +1,52 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTMODIFYJOB_H +#define OXA_OBJECTMODIFYJOB_H + +#include + +#include "object.h" + +namespace OXA { + +class ObjectModifyJob : public KJob +{ + Q_OBJECT + + public: + explicit ObjectModifyJob( const Object &object, QObject *parent = 0 ); + + virtual void start(); + + Object object() const; + + private Q_SLOTS: + void preloadingJobFinished( KJob* ); + void davJobFinished( KJob* ); + + private: + Object mObject; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectmovejob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectmovejob.cpp new file mode 100644 index 00000000..715d0c32 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectmovejob.cpp @@ -0,0 +1,96 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectmovejob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "objectutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +ObjectMoveJob::ObjectMoveJob( const Object &object, const Folder &destinationFolder, QObject *parent ) + : KJob( parent ), mObject( object ), mDestinationFolder( destinationFolder ) +{ +} + +void ObjectMoveJob::start() +{ + QDomDocument document; + QDomElement propertyupdate = DAVUtils::addDavElement( document, document, QLatin1String( "propertyupdate" ) ); + QDomElement set = DAVUtils::addDavElement( document, propertyupdate, QLatin1String( "set" ) ); + QDomElement prop = DAVUtils::addDavElement( document, set, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mObject.objectId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder_id" ), OXUtils::writeNumber( mObject.folderId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "last_modified" ), OXUtils::writeString( mObject.lastModified() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder" ), OXUtils::writeNumber( mDestinationFolder.objectId() ) ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *job = DavManager::self()->createPatchJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Object ObjectMoveJob::object() const +{ + return mObject; +} + +void ObjectMoveJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + + QDomElement element = prop.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "last_modified" ) ) + mObject.setLastModified( OXUtils::readString( element.text() ) ); + + element = element.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectmovejob.h b/kdepim-runtime/resources/openxchange/oxa/objectmovejob.h new file mode 100644 index 00000000..401d8c92 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectmovejob.h @@ -0,0 +1,53 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTMOVEJOB_H +#define OXA_OBJECTMOVEJOB_H + +#include + +#include "folder.h" +#include "object.h" + +namespace OXA { + +class ObjectMoveJob : public KJob +{ + Q_OBJECT + + public: + ObjectMoveJob( const Object &object, const Folder &destinationFolder, QObject *parent = 0 ); + + virtual void start(); + + Object object() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Object mObject; + Folder mDestinationFolder; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectrequestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectrequestjob.cpp new file mode 100644 index 00000000..77f62d95 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectrequestjob.cpp @@ -0,0 +1,85 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectrequestjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "objectutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +ObjectRequestJob::ObjectRequestJob( const Object &object, QObject *parent ) + : KJob( parent ), mObject( object ) +{ +} + +void ObjectRequestJob::start() +{ + QDomDocument document; + QDomElement multistatus = DAVUtils::addDavElement( document, document, QLatin1String( "multistatus" ) ); + QDomElement prop = DAVUtils::addDavElement( document, multistatus, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "object_id" ), OXUtils::writeNumber( mObject.objectId() ) ); + + const QString path = ObjectUtils::davPath( mObject.module() ); + + KIO::DavJob *job = DavManager::self()->createFindJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Object ObjectRequestJob::object() const +{ + return mObject; +} + +void ObjectRequestJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + mObject = ObjectUtils::parseObject( prop, mObject.module() ); + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectrequestjob.h b/kdepim-runtime/resources/openxchange/oxa/objectrequestjob.h new file mode 100644 index 00000000..0a86dcdb --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectrequestjob.h @@ -0,0 +1,51 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTREQUESTJOB_H +#define OXA_OBJECTREQUESTJOB_H + +#include + +#include "object.h" + +namespace OXA { + +class ObjectRequestJob : public KJob +{ + Q_OBJECT + + public: + explicit ObjectRequestJob( const Object &object, QObject *parent = 0 ); + + virtual void start(); + + Object object() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Object mObject; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.cpp new file mode 100644 index 00000000..a5918293 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.cpp @@ -0,0 +1,93 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2010 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. +*/ + +#include "objectsrequestdeltajob.h" + +#include "objectsrequestjob.h" + +#include + +using namespace OXA; + +ObjectsRequestDeltaJob::ObjectsRequestDeltaJob( const Folder &folder, qulonglong lastSync, QObject *parent ) + : KJob( parent ), mFolder( folder ), mLastSync( lastSync ), mJobFinishedCount( 0 ) +{ +} + +void ObjectsRequestDeltaJob::start() +{ + ObjectsRequestJob *modifiedJob = new ObjectsRequestJob( mFolder, mLastSync, ObjectsRequestJob::Modified, this ); + connect( modifiedJob, SIGNAL(result(KJob*)), SLOT(fetchModifiedJobFinished(KJob*)) ); + modifiedJob->start(); + + ObjectsRequestJob *deletedJob = new ObjectsRequestJob( mFolder, mLastSync, ObjectsRequestJob::Deleted, this ); + connect( deletedJob, SIGNAL(result(KJob*)), SLOT(fetchDeletedJobFinished(KJob*)) ); + deletedJob->start(); +} + +Object::List ObjectsRequestDeltaJob::modifiedObjects() const +{ + return mModifiedObjects; +} + +Object::List ObjectsRequestDeltaJob::deletedObjects() const +{ + return mDeletedObjects; +} + +void ObjectsRequestDeltaJob::fetchModifiedJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + const ObjectsRequestJob *requestJob = qobject_cast( job ); + + mModifiedObjects << requestJob->objects(); + + mJobFinishedCount++; + + if ( mJobFinishedCount == 2 ) + emitResult(); +} + +void ObjectsRequestDeltaJob::fetchDeletedJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + const ObjectsRequestJob *requestJob = qobject_cast( job ); + + mDeletedObjects << requestJob->objects(); + + mJobFinishedCount++; + + if ( mJobFinishedCount == 2 ) + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.h b/kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.h new file mode 100644 index 00000000..54130d76 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectsrequestdeltajob.h @@ -0,0 +1,80 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2010 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. +*/ + +#ifndef OXA_OBJECTSREQUESTDELTAJOB_H +#define OXA_OBJECTSREQUESTDELTAJOB_H + +#include + +#include "object.h" + +namespace OXA { + +/** + * @short A job that requests the delta for objects changes from the OX server. + * + * @author Tobias Koenig + */ +class ObjectsRequestDeltaJob : public KJob +{ + Q_OBJECT + + public: + /** + * Creates a new objects request delta job. + * + * @param folder The folder the objects shall be request from. + * @param lastSync The timestamp of the last sync. Only added, modified and deleted objects + * after this date will be requested. 0 will request all available objects. + * @param parent The parent object. + */ + ObjectsRequestDeltaJob( const Folder &folder, qulonglong lastSync, QObject *parent = 0 ); + + /** + * Starts the job. + */ + virtual void start(); + + /** + * Returns the list of all added and modified objects. + */ + Object::List modifiedObjects() const; + + /** + * Returns the list of all deleted objects. + */ + Object::List deletedObjects() const; + + private Q_SLOTS: + void fetchModifiedJobFinished( KJob* ); + void fetchDeletedJobFinished( KJob* ); + + private: + Folder mFolder; + qulonglong mLastSync; + Object::List mModifiedObjects; + Object::List mDeletedObjects; + int mJobFinishedCount; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.cpp new file mode 100644 index 00000000..ee273e73 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.cpp @@ -0,0 +1,95 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectsrequestjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "objectutils.h" +#include "oxutils.h" + +#include + +#include + +using namespace OXA; + +ObjectsRequestJob::ObjectsRequestJob( const Folder &folder, qulonglong lastSync, Mode mode, QObject *parent ) + : KJob( parent ), mFolder( folder ), mLastSync( lastSync ), mMode( mode ) +{ +} + +void ObjectsRequestJob::start() +{ + QDomDocument document; + QDomElement multistatus = DAVUtils::addDavElement( document, document, QLatin1String( "multistatus" ) ); + QDomElement prop = DAVUtils::addDavElement( document, multistatus, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "folder_id" ), OXUtils::writeNumber( mFolder.objectId() ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "lastsync" ), OXUtils::writeNumber( mLastSync ) ); + if ( mMode == Modified ) + DAVUtils::addOxElement( document, prop, QLatin1String( "objectmode" ), QLatin1String( "MODIFIED" ) ); + else + DAVUtils::addOxElement( document, prop, QLatin1String( "objectmode" ), QLatin1String( "DELETED" ) ); + + const QString path = ObjectUtils::davPath( mFolder.module() ); + + KIO::DavJob *job = DavManager::self()->createFindJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); +} + +Object::List ObjectsRequestJob::objects() const +{ + return mObjects; +} + +void ObjectsRequestJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QString errorText, errorStatus; + if ( DAVUtils::davErrorOccurred( document, errorText, errorStatus ) ) { + setError( UserDefinedError ); + setErrorText( errorText ); + emitResult(); + return; + } + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + while ( !response.isNull() ) { + const QDomNodeList props = response.elementsByTagName( "prop" ); + const QDomElement prop = props.at( 0 ).toElement(); + mObjects.append( ObjectUtils::parseObject( prop, mFolder.module() ) ); + response = response.nextSiblingElement(); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.h b/kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.h new file mode 100644 index 00000000..05ad10a5 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectsrequestjob.h @@ -0,0 +1,73 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTSREQUESTJOB_H +#define OXA_OBJECTSREQUESTJOB_H + +#include + +#include "folder.h" +#include "object.h" + +namespace OXA { + +class ObjectsRequestJob : public KJob +{ + Q_OBJECT + + public: + /** + * Describes the mode of the request job. + */ + enum Mode + { + Modified, ///< Fetches all new and modified objects + Deleted ///< Fetches all deleted objects + }; + + /** + * Creates a new objects request job. + * + * @param folder The folder the objects shall be request from. + * @param lastSync The timestamp of the last sync. Only added, modified or deleted objects + * after this date will be requested. 0 will request all available objects. + * @param mode The mode of objects to request. + * @param parent The parent object. + */ + explicit ObjectsRequestJob( const Folder &folder, qulonglong lastSync = 0, Mode mode = Modified, QObject *parent = 0 ); + + virtual void start(); + + Object::List objects() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + Folder mFolder; + qulonglong mLastSync; + Mode mMode; + Object::List mObjects; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/objectutils.cpp b/kdepim-runtime/resources/openxchange/oxa/objectutils.cpp new file mode 100644 index 00000000..fcbb1da5 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectutils.cpp @@ -0,0 +1,128 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "objectutils.h" + +#include "contactutils.h" +#include "incidenceutils.h" +#include "davutils.h" +#include "oxutils.h" + +#include +#include + +using namespace OXA; + +Object OXA::ObjectUtils::parseObject( const QDomElement &propElement, Folder::Module module ) +{ + Object object; + + QDomElement element = propElement.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "last_modified" ) ) { + object.setLastModified( OXUtils::readString( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "object_id" ) ) { + object.setObjectId( OXUtils::readNumber( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "folder_id" ) ) { + object.setFolderId( OXUtils::readNumber( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "object_status" ) ) { + const QString content = OXUtils::readString( element.text() ); + if ( content == QLatin1String( "CREATE" ) ) + object.setObjectStatus( Object::Created ); + else if ( content == QLatin1String( "DELETE" ) ) + object.setObjectStatus( Object::Deleted ); + else + Q_ASSERT( false ); + } + + element = element.nextSiblingElement(); + } + + switch ( module ) { + case Folder::Contacts: ContactUtils::parseContact( propElement, object ); break; + case Folder::Calendar: IncidenceUtils::parseEvent( propElement, object ); break; + case Folder::Tasks: IncidenceUtils::parseTask( propElement, object ); break; + case Folder::Unbound: Q_ASSERT( false ); break; + } + + return object; +} + +void OXA::ObjectUtils::addObjectElements( QDomDocument &document, QDomElement &propElement, const Object &object, void *preloadedData ) +{ + if ( object.objectId() != -1 ) + DAVUtils::addOxElement( document, propElement, QLatin1String( "object_id" ), OXUtils::writeNumber( object.objectId() ) ); + if ( object.folderId() != -1 ) + DAVUtils::addOxElement( document, propElement, QLatin1String( "folder_id" ), OXUtils::writeNumber( object.folderId() ) ); + if ( !object.lastModified().isEmpty() ) + DAVUtils::addOxElement( document, propElement, QLatin1String( "last_modified" ), OXUtils::writeString( object.lastModified() ) ); + + switch ( object.module() ) { + case Folder::Contacts: ContactUtils::addContactElements( document, propElement, object, preloadedData ); break; + case Folder::Calendar: IncidenceUtils::addEventElements( document, propElement, object ); break; + case Folder::Tasks: IncidenceUtils::addTaskElements( document, propElement, object ); break; + case Folder::Unbound: Q_ASSERT( false ); break; + } +} + +bool OXA::ObjectUtils::needsPreloading( const Object &object ) +{ + if ( object.module() == Folder::Contacts ) { + if ( object.contactGroup().contactReferenceCount() != 0 ) // we have to resolve these entries first + return true; + } + + return false; +} + +KJob* OXA::ObjectUtils::preloadJob( const Object &object ) +{ + if ( object.module() == Folder::Contacts ) { + if ( object.contactGroup().contactReferenceCount() != 0 ) { + return ContactUtils::preloadJob( object ); + } + } + + return 0; +} + +void* OXA::ObjectUtils::preloadData( const Object &object, KJob *job ) +{ + if ( object.module() == Folder::Contacts ) { + if ( object.contactGroup().contactReferenceCount() != 0 ) { + return ContactUtils::preloadData( object, job ); + } + } + + return 0; +} + +QString OXA::ObjectUtils::davPath( Folder::Module module ) +{ + switch ( module ) { + case Folder::Contacts: return QLatin1String( "/servlet/webdav.contacts" ); break; + case Folder::Calendar: return QLatin1String( "/servlet/webdav.calendar" ); break; + case Folder::Tasks: return QLatin1String( "/servlet/webdav.tasks" ); break; + case Folder::Unbound: Q_ASSERT( false ); return QString(); break; + } + + return QString(); +} diff --git a/kdepim-runtime/resources/openxchange/oxa/objectutils.h b/kdepim-runtime/resources/openxchange/oxa/objectutils.h new file mode 100644 index 00000000..83293f29 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/objectutils.h @@ -0,0 +1,69 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OBJECTUTILS_H +#define OXA_OBJECTUTILS_H + +#include "folder.h" +#include "object.h" + +class KJob; + +class QDomDocument; +class QDomElement; + +namespace OXA { + +namespace ObjectUtils +{ + Object parseObject( const QDomElement &propElement, Folder::Module module ); + void addObjectElements( QDomDocument &document, QDomElement &propElement, const Object &object, void *preloadedData = 0 ); + + /** + * Returns the dav path that is used for the given @p module. + */ + QString davPath( Folder::Module module ); + + /** + * On some actions (e.g. creating or modifiying items) we have to preload + * data asynchronously. The following methods allow to do that in a generic way. + */ + + /** + * Checks whether the @p object needs preloading of data. + */ + bool needsPreloading( const Object &object ); + + /** + * Creates a preloading job for the @p object. + */ + KJob* preloadJob( const Object &object ); + + /** + * Converts the data loaded by the preloading @p job into pointer + * that will be passed to addObjectElements later on. + */ + void* preloadData( const Object &object, KJob *job ); +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/oxerrors.cpp b/kdepim-runtime/resources/openxchange/oxa/oxerrors.cpp new file mode 100644 index 00000000..5ae9f8a2 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/oxerrors.cpp @@ -0,0 +1,48 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2012 Marco Nelles + + 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 "oxerrors.h" + +using namespace OXA; + +OXErrors::EditErrorID OXErrors::getEditErrorID( const QString& errorText ) +{ + int b1Pos = errorText.indexOf( '[' ); + int b2Pos = errorText.indexOf( ']' ); + QString errorID = errorText.mid( b1Pos+1, b2Pos-b1Pos-1 ); + + bool ok; + int eid = errorID.toInt( &ok ); + if ( !ok ) return OXErrors::EditErrorUndefined; + + switch ( eid ) { + case 1000 : return OXErrors::ConcurrentModification; + case 1001 : return OXErrors::ObjectNotFound; + case 1002 : return OXErrors::NoPermissionForThisAction; + case 1003 : return OXErrors::ConflictsDetected; + case 1004 : return OXErrors::MissingMandatoryFields; + case 1006 : return OXErrors::AppointmentConflicts; + case 1500 : return OXErrors::InternalServerError; + default : ; + } + + return OXErrors::EditErrorUndefined; +} diff --git a/kdepim-runtime/resources/openxchange/oxa/oxerrors.h b/kdepim-runtime/resources/openxchange/oxa/oxerrors.h new file mode 100644 index 00000000..3028079b --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/oxerrors.h @@ -0,0 +1,57 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2012 Marco Nelles + + 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 OXA_OXERRORS_H +#define OXA_OXERRORS_H + +#include + +namespace OXA { + +/** + * Namespace that contains methods for handling OX errors. + * + * @author Marco Nelles + */ +namespace OXErrors +{ + + enum EditErrorID { + EditErrorUndefined = 0, + ConcurrentModification, + ObjectNotFound, + NoPermissionForThisAction, + ConflictsDetected, + MissingMandatoryFields, + AppointmentConflicts, + InternalServerError + }; + + /** + * Parse error id from edit error text string @p errorText + */ + EditErrorID getEditErrorID( const QString& errorText ); + +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/oxutils.cpp b/kdepim-runtime/resources/openxchange/oxa/oxutils.cpp new file mode 100644 index 00000000..95571af2 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/oxutils.cpp @@ -0,0 +1,138 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "oxutils.h" + +#include + +using namespace OXA; + +QString OXUtils::writeBoolean( bool value ) +{ + return (value ? QLatin1String( "true" ) : QLatin1String( "false" )); +} + +QString OXUtils::writeNumber( qlonglong value ) +{ + return QString::number( value ); +} + +QString OXUtils::writeString( const QString &value ) +{ + QStringList lines = value.split( '\n' ); + + for ( int i = 0; i < lines.count(); ++i ) + { + lines[i].replace( '\\', "\\\\" ); + lines[i].replace( '"', "\\\"" ); + } + + return lines.join( "\n" ); +} + +QString OXUtils::writeName( const QString &value ) +{ + //TODO: assert on invalid names + return value; +} + +QString OXUtils::writeDateTime( const QDateTime &value ) +{ + QString result; + + //workaround, as QDateTime does not support negative time_t values + QDateTime Time_t_S( QDate( 1970, 1, 1 ), QTime( 0, 0, 0 ), Qt::UTC); + + if ( value < Time_t_S ) { + + result = QString::number( Time_t_S.secsTo( value ) ); + + } else { + + result = QString::number( value.toUTC().toTime_t() ); + + } + + return QString( result + QLatin1String( "000" ) ); + +} + +QString OXUtils::writeDate( const QDate &value ) +{ + return writeDateTime( QDateTime( value, QTime( 0, 0, 0 ), Qt::UTC ) ); +} + +bool OXUtils::readBoolean( const QString &text ) +{ + if ( text == QLatin1String( "true" ) ) + return true; + else if ( text == QLatin1String( "false" ) ) + return false; + else { + Q_ASSERT( false ); + return false; + } +} + +qlonglong OXUtils::readNumber( const QString &text ) +{ + return text.toLongLong(); +} + +QString OXUtils::readString( const QString &text ) +{ + QString value( text ); + value.replace( "\\\"", "\"" ); + value.replace( "\\\\", "\\" ); + + return value; +} + +QString OXUtils::readName( const QString &text ) +{ + return text; +} + +QDateTime OXUtils::readDateTime( const QString &text ) +{ + // remove the trailing '000', they exceed the integer dimension + const int ticks = text.mid( 0, text.length() - 3 ).toLongLong(); + + //workaround, as QDateTime does not support negative time_t values + QDateTime value; + if ( ticks < 0 ) { + + value.setTime_t( 0 ); + value = value.addSecs( ticks ); + + } else { + + value.setTime_t( ticks ); + + } + + return value; +} + +QDate OXUtils::readDate( const QString &text ) +{ + return readDateTime( text ).date(); +} diff --git a/kdepim-runtime/resources/openxchange/oxa/oxutils.h b/kdepim-runtime/resources/openxchange/oxa/oxutils.h new file mode 100644 index 00000000..e561039c --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/oxutils.h @@ -0,0 +1,51 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_OXUTILS_H +#define OXA_OXUTILS_H + +#include +#include +#include + + +namespace OXA { + +namespace OXUtils +{ + QString writeBoolean( bool value ); + QString writeNumber( qlonglong value ); + QString writeString( const QString &value ); + QString writeName( const QString &value ); + QString writeDateTime( const QDateTime &value ); + QString writeDate( const QDate &value ); + + bool readBoolean( const QString &text ); + qlonglong readNumber( const QString &text ); + QString readString( const QString &text ); + QString readName( const QString &text ); + QDateTime readDateTime( const QString &text ); + QDate readDate( const QString &text ); +} + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/updateusersjob.cpp b/kdepim-runtime/resources/openxchange/oxa/updateusersjob.cpp new file mode 100644 index 00000000..9d9a6cc6 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/updateusersjob.cpp @@ -0,0 +1,93 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "updateusersjob.h" + +#include "useridrequestjob.h" +#include "users.h" +#include "usersrequestjob.h" + +using namespace OXA; + +UpdateUsersJob::UpdateUsersJob( QObject *parent ) + : KJob( parent ), mUserIdRequestFinished( false ), mUsersRequestFinished( false ), mUserId( -1 ) +{ +} + +void UpdateUsersJob::start() +{ + UserIdRequestJob *userIdJob = new UserIdRequestJob( this ); + connect( userIdJob, SIGNAL(result(KJob*)), SLOT(userIdRequestJobFinished(KJob*)) ); + + UsersRequestJob *usersJob = new UsersRequestJob( this ); + connect( usersJob, SIGNAL(result(KJob*)), SLOT(usersRequestJobFinished(KJob*)) ); + + userIdJob->start(); + usersJob->start(); +} + +void UpdateUsersJob::userIdRequestJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + } else { + mUserIdRequestFinished = true; + + UserIdRequestJob *requestJob = qobject_cast( job ); + mUserId = requestJob->userId(); + + finish(); + } +} + +void UpdateUsersJob::usersRequestJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + } else { + mUsersRequestFinished = true; + + UsersRequestJob *requestJob = qobject_cast( job ); + mUsers = requestJob->users(); + + finish(); + } +} + +void UpdateUsersJob::finish() +{ + // check if both sub-jobs have finished + if ( !(mUserIdRequestFinished && mUsersRequestFinished) ) + return; + + if ( error() ) { + emitResult(); + return; + } + + Users::self()->setCurrentUserId( mUserId ); + Users::self()->setUsers( mUsers ); + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/updateusersjob.h b/kdepim-runtime/resources/openxchange/oxa/updateusersjob.h new file mode 100644 index 00000000..962ce096 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/updateusersjob.h @@ -0,0 +1,55 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_UPDATEUSERSJOB_H +#define OXA_UPDATEUSERSJOB_H + +#include + +#include "user.h" + +namespace OXA { + +class UpdateUsersJob : public KJob +{ + Q_OBJECT + + public: + explicit UpdateUsersJob( QObject *parent = 0 ); + + virtual void start(); + + private Q_SLOTS: + void userIdRequestJobFinished( KJob* ); + void usersRequestJobFinished( KJob* ); + + private: + void finish(); + + bool mUserIdRequestFinished; + bool mUsersRequestFinished; + User::List mUsers; + qlonglong mUserId; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/user.cpp b/kdepim-runtime/resources/openxchange/oxa/user.cpp new file mode 100644 index 00000000..df004dea --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/user.cpp @@ -0,0 +1,64 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "user.h" + +using namespace OXA; + +User::User() + : mUid( -1 ) +{ +} + +bool User::isValid() const +{ + return (mUid != -1); +} + +void User::setUid( qlonglong uid ) +{ + mUid = uid; +} + +qlonglong User::uid() const +{ + return mUid; +} + +void User::setEmail( const QString &email ) +{ + mEmail = email; +} + +QString User::email() const +{ + return mEmail; +} + +void User::setName( const QString &name ) +{ + mName = name; +} + +QString User::name() const +{ + return mName; +} diff --git a/kdepim-runtime/resources/openxchange/oxa/user.h b/kdepim-runtime/resources/openxchange/oxa/user.h new file mode 100644 index 00000000..e066341c --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/user.h @@ -0,0 +1,56 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_USER_H +#define OXA_USER_H + +#include +#include + +namespace OXA { + +class User +{ + public: + typedef QList List; + + User(); + + bool isValid() const; + + void setUid( qlonglong uid ); + qlonglong uid() const; + + void setEmail( const QString &email ); + QString email() const; + + void setName( const QString &name ); + QString name() const; + + private: + qlonglong mUid; + QString mEmail; + QString mName; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/useridrequestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/useridrequestjob.cpp new file mode 100644 index 00000000..3fe550c1 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/useridrequestjob.cpp @@ -0,0 +1,78 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "useridrequestjob.h" + +#include "foldersrequestjob.h" +#include "davmanager.h" + +#include + +using namespace OXA; + +UserIdRequestJob::UserIdRequestJob( QObject *parent ) + : KJob( parent ), mUserId( -1 ) +{ +} + +void UserIdRequestJob::start() +{ + FoldersRequestJob *job = new FoldersRequestJob( 0, FoldersRequestJob::Modified, this ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); + + job->start(); +} + +qlonglong UserIdRequestJob::userId() const +{ + return mUserId; +} + +void UserIdRequestJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + FoldersRequestJob *requestJob = qobject_cast( job ); + Q_ASSERT( requestJob ); + + const Folder::List folders = requestJob->folders(); + foreach ( const Folder &folder, folders ) { + if ( folder.folderId() == 1 ) { + // Found folder with 'Private Folders' as parent, so the owner must + // be the user that is currently logged in. + mUserId = folder.owner(); + break; + } + } + + if ( mUserId == -1 ) { + setError( UserDefinedError ); + setErrorText( "No private folder found" ); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/useridrequestjob.h b/kdepim-runtime/resources/openxchange/oxa/useridrequestjob.h new file mode 100644 index 00000000..7d567814 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/useridrequestjob.h @@ -0,0 +1,49 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_USERIDREQUESTJOB_H +#define OXA_USERIDREQUESTJOB_H + +#include + +namespace OXA { + +class UserIdRequestJob : public KJob +{ + Q_OBJECT + + public: + explicit UserIdRequestJob( QObject *parent = 0 ); + + virtual void start(); + + qlonglong userId() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + qlonglong mUserId; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/users.cpp b/kdepim-runtime/resources/openxchange/oxa/users.cpp new file mode 100644 index 00000000..b70bbfb5 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/users.cpp @@ -0,0 +1,154 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "users.h" + +#include + +#include + +using namespace OXA; + +Users* Users::mSelf = 0; + +Users::Users() + : mCurrentUserId( -1 ) +{ +} + +Users::~Users() +{ +} + +Users* Users::self() +{ + if ( !mSelf ) + mSelf = new Users(); + + return mSelf; +} + +void Users::init( const QString &identifier ) +{ + mIdentifier = identifier; + + loadFromCache(); +} + +qlonglong Users::currentUserId() const +{ + return mCurrentUserId; +} + +User Users::lookupUid( qlonglong uid ) const +{ + return mUsers.value( uid ); +} + +User Users::lookupEmail( const QString &email ) const +{ + QMapIterator it( mUsers ); + while ( it.hasNext() ) { + it.next(); + + if ( it.value().email() == email ) + return it.value(); + } + + return User(); +} + +QString Users::cacheFilePath() const +{ + return KStandardDirs::locateLocal( "data", "akonadi/openxchangeresource_" + mIdentifier ); +} + +void Users::setCurrentUserId( qlonglong id ) +{ + mCurrentUserId = id; + + saveToCache(); +} + +void Users::setUsers( const User::List &users ) +{ + mUsers.clear(); + + foreach ( const User &user, users ) + mUsers.insert( user.uid(), user ); + + saveToCache(); +} + +void Users::loadFromCache() +{ + QFile cacheFile( cacheFilePath() ); + if ( !cacheFile.open( QIODevice::ReadOnly ) ) + return; + + QDataStream stream( &cacheFile ); + stream.setVersion( QDataStream::Qt_4_6 ); + + mUsers.clear(); + + stream >> mCurrentUserId; + + qulonglong count; + stream >> count; + + qlonglong uid; + QString name; + QString email; + for ( qulonglong i = 0; i < count; ++i ) { + stream >> uid >> name >> email; + + User user; + user.setUid( uid ); + user.setName( name ); + user.setEmail( email ); + mUsers.insert( user.uid(), user ); + } +} + +void Users::saveToCache() +{ + QFile cacheFile( cacheFilePath() ); + if ( !cacheFile.open( QIODevice::WriteOnly ) ) + return; + + QDataStream stream( &cacheFile ); + stream.setVersion( QDataStream::Qt_4_6 ); + + // write current user id + stream << mCurrentUserId; + + // write number of users + stream << (qulonglong)mUsers.count(); + + // write uid, name and email address for each user + QMapIterator it( mUsers ); + while ( it.hasNext() ) { + it.next(); + + stream << it.value().uid() << it.value().name() << it.value().email(); + } +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/users.h b/kdepim-runtime/resources/openxchange/oxa/users.h new file mode 100644 index 00000000..e4d10089 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/users.h @@ -0,0 +1,70 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_USERS_H +#define OXA_USERS_H + +#include "user.h" + +#include +#include + + +namespace OXA { + +class Users : public QObject +{ + Q_OBJECT + + public: + ~Users(); + + static Users* self(); + + void init( const QString &identifier ); + + qlonglong currentUserId() const; + + User lookupUid( qlonglong uid ) const; + User lookupEmail( const QString &email ) const; + + QString cacheFilePath() const; + + private: + friend class UpdateUsersJob; + + Users(); + void setCurrentUserId( qlonglong ); + void setUsers( const User::List& ); + + void loadFromCache(); + void saveToCache(); + + qlonglong mCurrentUserId; + QMap mUsers; + QString mIdentifier; + + static Users* mSelf; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/oxa/usersrequestjob.cpp b/kdepim-runtime/resources/openxchange/oxa/usersrequestjob.cpp new file mode 100644 index 00000000..9576febd --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/usersrequestjob.cpp @@ -0,0 +1,100 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#include "usersrequestjob.h" + +#include "davmanager.h" +#include "davutils.h" +#include "oxutils.h" + +#include + +using namespace OXA; + +UsersRequestJob::UsersRequestJob( QObject *parent ) + : KJob( parent ) +{ +} + +void UsersRequestJob::start() +{ + QDomDocument document; + QDomElement multistatus = DAVUtils::addDavElement( document, document, QLatin1String( "multistatus" ) ); + QDomElement prop = DAVUtils::addDavElement( document, multistatus, QLatin1String( "prop" ) ); + DAVUtils::addOxElement( document, prop, QLatin1String( "user" ), QLatin1String( "*" ) ); + + const QString path = QLatin1String( "/servlet/webdav.groupuser" ); + + KIO::DavJob *job = DavManager::self()->createFindJob( path, document ); + connect( job, SIGNAL(result(KJob*)), SLOT(davJobFinished(KJob*)) ); + + job->start(); +} + +User::List UsersRequestJob::users() const +{ + return mUsers; +} + +void UsersRequestJob::davJobFinished( KJob *job ) +{ + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + emitResult(); + return; + } + + KIO::DavJob *davJob = qobject_cast( job ); + + const QDomDocument document = davJob->response(); + + QDomElement multistatus = document.documentElement(); + QDomElement response = multistatus.firstChildElement( QLatin1String( "response" ) ); + QDomElement propstat = response.firstChildElement( QLatin1String( "propstat" ) ); + QDomElement prop = propstat.firstChildElement( QLatin1String( "prop" ) ); + QDomElement users = prop.firstChildElement( QLatin1String( "users" ) ); + + QDomElement userElement = users.firstChildElement( QLatin1String( "user" ) ); + while ( !userElement.isNull() ) { + User user; + + QDomElement element = userElement.firstChildElement(); + while ( !element.isNull() ) { + if ( element.tagName() == QLatin1String( "uid" ) ) { + user.setUid( OXUtils::readNumber( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "email1" ) ) { + user.setEmail( OXUtils::readString( element.text() ) ); + } else if ( element.tagName() == QLatin1String( "displayname" ) ) { + user.setName( OXUtils::readString( element.text() ) ); + } + + element = element.nextSiblingElement(); + } + + mUsers.append( user ); + + userElement = userElement.nextSiblingElement( QLatin1String( "user" ) ); + } + + emitResult(); +} + diff --git a/kdepim-runtime/resources/openxchange/oxa/usersrequestjob.h b/kdepim-runtime/resources/openxchange/oxa/usersrequestjob.h new file mode 100644 index 00000000..5fe92607 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/oxa/usersrequestjob.h @@ -0,0 +1,51 @@ +/* + This file is part of oxaccess. + + Copyright (c) 2009 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. +*/ + +#ifndef OXA_USERSREQUESTJOB_H +#define OXA_USERSREQUESTJOB_H + +#include + +#include "user.h" + +namespace OXA { + +class UsersRequestJob : public KJob +{ + Q_OBJECT + + public: + explicit UsersRequestJob( QObject *parent = 0 ); + + virtual void start(); + + User::List users() const; + + private Q_SLOTS: + void davJobFinished( KJob* ); + + private: + User::List mUsers; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/openxchange/settings.kcfgc b/kdepim-runtime/resources/openxchange/settings.kcfgc new file mode 100644 index 00000000..17793a89 --- /dev/null +++ b/kdepim-runtime/resources/openxchange/settings.kcfgc @@ -0,0 +1,7 @@ +File=openxchangeresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +GlobalEnums=true diff --git a/kdepim-runtime/resources/pop3/CMakeLists.txt b/kdepim-runtime/resources/pop3/CMakeLists.txt new file mode 100644 index 00000000..d7bd1a8c --- /dev/null +++ b/kdepim-runtime/resources/pop3/CMakeLists.txt @@ -0,0 +1,72 @@ +project(pop3) + +if (XSLTPROC_EXECUTABLE) + # generates a D-Bus interface description from a KConfigXT file + macro( kcfg_generate_dbus_interface _kcfg _name ) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml + COMMAND ${XSLTPROC_EXECUTABLE} --stringparam interfaceName ${_name} + ${KDEPIMLIBS_DATA_DIR}/akonadi-kde/kcfg2dbus.xsl + ${_kcfg} + > ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml + DEPENDS ${KDEPIMLIBS_DATA_DIR}/akonadi-kde/kcfg2dbus.xsl + ${_kcfg} + ) + endmacro() +endif () + +include_directories( + ${KDE4_INCLUDES} + ${KDEPIMLIBS_INCLUDE_DIRS} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( pop3resource_SRCS + pop3resource.cpp + accountdialog.cpp + jobs.cpp + settings.cpp + pop3resourceattribute.cpp +) + +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5266) + +install( FILES pop3resource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files( pop3resource_SRCS popsettings.ui) +kde4_add_kcfg_files(pop3resource_SRCS settingsbase.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/settings.kcfg org.kde.Akonadi.POP3.Settings) +qt4_add_dbus_adaptor(pop3resource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.POP3.Settings.xml settings.h Settings +) + +kde4_add_executable(akonadi_pop3_resource RUN_UNINSTALLED ${pop3resource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_pop3_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_pop3_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.POP3") + set_target_properties(akonadi_pop3_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi POP3 Resource") +endif () + + +target_link_libraries( akonadi_pop3_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDEPIMLIBS_AKONADI_KMIME_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${KDEPIMLIBS_MAILTRANSPORT_LIBS} + ${KDEPIMLIBS_KPIMUTILS_LIBS} +) + +add_subdirectory( tests ) +add_subdirectory( wizard ) + +install(TARGETS akonadi_pop3_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/pop3/Messages.sh b/kdepim-runtime/resources/pop3/Messages.sh new file mode 100644 index 00000000..c41e8c83 --- /dev/null +++ b/kdepim-runtime/resources/pop3/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui *.kcfg >> rc.cpp +$XGETTEXT *.cpp -o $podir/akonadi_pop3_resource.pot diff --git a/kdepim-runtime/resources/pop3/TODO b/kdepim-runtime/resources/pop3/TODO new file mode 100644 index 00000000..5f6a1ca9 --- /dev/null +++ b/kdepim-runtime/resources/pop3/TODO @@ -0,0 +1,49 @@ +==== +TODO +==== + +BUGS +---- + +- additional \n in item, see the test +- config dialog: enter key selects collection, and not the Ok button +- not storing password does not work + +FEATURES +-------- + +- filter on server +- delete on server +- fix "include in manual mail check", should be something in the akonadi libs +- migration from old KMail accounts + -> now DeleteLater group + -> store-passwd -> store_passwd (also: use_ssl, use_tls and all the others +- progressbar integration for client apps, especially the old SSL icon stuff +- get rid of pop3 ioslave +- help (there is a a help button in the config dialog) + -> maybe copy over most of KMail's POP3 help + +CLEANUP +------- + +- api documentation (also for fakeserver) +- clean up cmakefiles, remove code duplication with kdepimlibs +- kconfigxt: labels, tooltips, whatsthis etc + +UNIT TESTS +---------- + - test ssl/tls + - test with disabled cache + - all leave on server rules + -> leave the last x messages + -> leave the last x MB + -> every leave rule mixed + - test cancel mail + seenuidlist + - weird bugs in svn log of old kmail + - add fails -> second check will still work + - correct error messages passed from pop3 server? + - byte stuffing + - pipelining + - abort requested during mail check + -> no duplicate messages on next mail check, already added mails added to the seen + UID list diff --git a/kdepim-runtime/resources/pop3/accountdialog.cpp b/kdepim-runtime/resources/pop3/accountdialog.cpp new file mode 100644 index 00000000..76cf92b1 --- /dev/null +++ b/kdepim-runtime/resources/pop3/accountdialog.cpp @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2000 Espen Sand, espen@kde.org + * Copyright 2009 Thomas McGuire + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + * Copyright (C) 2010 Casey Link + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +// Local includes +#include "accountdialog.h" +#include "pop3resource.h" +#include "settings.h" +#include "settingsadaptor.h" + +// KDEPIMLIBS includes +#include +#include +#include +#include +#include +#include + +// KDELIBS includes +#include +#include +#include +#include +#include +#include + +using namespace MailTransport; +using namespace Akonadi; +using namespace KWallet; + +namespace { + +class BusyCursorHelper : public QObject +{ +public: + inline BusyCursorHelper( QObject *parent ) + : QObject( parent ) + { +#ifndef QT_NO_CURSOR + qApp->setOverrideCursor( Qt::BusyCursor ); +#endif + } + + inline ~BusyCursorHelper() + { +#ifndef QT_NO_CURSOR + qApp->restoreOverrideCursor(); +#endif + } +}; + +} + + +AccountDialog::AccountDialog( POP3Resource *parentResource, WId parentWindow ) + : KDialog(), + mParentResource( parentResource ), + mServerTest( 0 ), + mValidator( this ), + mWallet( 0 ) +{ + KWindowSystem::setMainWindow( this, parentWindow ); + setWindowIcon( KIcon( QLatin1String("network-server") ) ); + setWindowTitle( i18n( "POP3 Account Settings" ) ); + setButtons( Ok|Cancel ); + mValidator.setRegExp( QRegExp( QLatin1String("[A-Za-z0-9-_:.]*") ) ); + + setupWidgets(); + loadSettings(); +} + +AccountDialog::~AccountDialog() +{ + delete mWallet; + mWallet = 0; + delete mServerTest; + mServerTest = 0; +} + +void AccountDialog::setupWidgets() +{ + QWidget *page = new QWidget( this ); + setupUi( page ); + setMainWidget( page ); + + // only letters, digits, '-', '.', ':' (IPv6) and '_' (for Windows + // compatibility) are allowed + hostEdit->setValidator( &mValidator ); + intervalSpin->setSuffix( ki18np( " minute", " minutes" ) ); + + intervalSpin->setRange( ResourceSettings::self()->minimumCheckInterval(), 10000, 1 ); + + connect( leaveOnServerCheck, SIGNAL(clicked()), + this, SLOT(slotLeaveOnServerClicked()) ); + connect( leaveOnServerDaysCheck, SIGNAL(toggled(bool)), + this, SLOT(slotEnableLeaveOnServerDays(bool)) ); + connect( leaveOnServerDaysSpin, SIGNAL(valueChanged(int)), + SLOT(slotLeaveOnServerDaysChanged(int))); + connect( leaveOnServerCountCheck, SIGNAL(toggled(bool)), + this, SLOT(slotEnableLeaveOnServerCount(bool)) ); + connect( leaveOnServerCountSpin, SIGNAL(valueChanged(int)), + SLOT(slotLeaveOnServerCountChanged(int))); + connect( leaveOnServerSizeCheck, SIGNAL(toggled(bool)), + this, SLOT(slotEnableLeaveOnServerSize(bool)) ); + + connect(filterOnServerSizeSpin, SIGNAL(valueChanged(int)), + SLOT(slotFilterOnServerSizeChanged(int))); + connect( filterOnServerCheck, SIGNAL(toggled(bool)), + filterOnServerSizeSpin, SLOT(setEnabled(bool)) ); + connect( filterOnServerCheck, SIGNAL(clicked()), + this, SLOT(slotFilterOnServerClicked()) ); + + connect( checkCapabilities, SIGNAL(clicked()), + SLOT(slotCheckPopCapabilities()) ); + encryptionButtonGroup = new QButtonGroup(); + encryptionButtonGroup->addButton( encryptionNone, + Transport::EnumEncryption::None ); + encryptionButtonGroup->addButton( encryptionSSL, + Transport::EnumEncryption::SSL ); + encryptionButtonGroup->addButton( encryptionTLS, + Transport::EnumEncryption::TLS ); + + connect( encryptionButtonGroup, SIGNAL(buttonClicked(int)), + SLOT(slotPopEncryptionChanged(int)) ); + connect( intervalCheck, SIGNAL(toggled(bool)), + this, SLOT(slotEnablePopInterval(bool)) ); + + populateDefaultAuthenticationOptions(); + + folderRequester->setMimeTypeFilter( + QStringList() << QLatin1String( "message/rfc822" ) ); + folderRequester->setFrameStyle( QFrame::NoFrame ); + folderRequester->setAccessRightsFilter( Akonadi::Collection::CanCreateItem ); + folderRequester->changeCollectionDialogOptions( Akonadi::CollectionDialog::AllowToCreateNewChildCollection ); + + connect( usePipeliningCheck, SIGNAL(clicked()), + SLOT(slotPipeliningClicked()) ); + + connect(KGlobalSettings::self(),SIGNAL(kdisplayFontChanged()), + SLOT(slotFontChanged())); + + // FIXME: Hide widgets which are not supported yet + filterOnServerCheck->hide(); + filterOnServerSizeSpin->hide(); +} + +void AccountDialog::loadSettings() +{ + if ( mParentResource->name() == mParentResource->identifier() ) + mParentResource->setName( i18n( "POP3 Account") ); + + nameEdit->setText( mParentResource->name() ); + nameEdit->setFocus(); + loginEdit->setText( !Settings::self()->login().isEmpty() ? Settings::self()->login() : + KUser().loginName() ); + + hostEdit->setText( + !Settings::self()->host().isEmpty() ? Settings::self()->host() : + KEMailSettings().getSetting( KEMailSettings::InServer ) ); + hostEdit->setText( Settings::self()->host() ); + portEdit->setValue( Settings::self()->port() ); + precommand->setText( Settings::self()->precommand() ); + usePipeliningCheck->setChecked( Settings::self()->pipelining() ); + leaveOnServerCheck->setChecked( Settings::self()->leaveOnServer() ); + leaveOnServerDaysCheck->setEnabled( Settings::self()->leaveOnServer() ); + leaveOnServerDaysCheck->setChecked( Settings::self()->leaveOnServerDays() >= 1 ); + leaveOnServerDaysSpin->setValue( Settings::self()->leaveOnServerDays() >= 1 ? + Settings::self()->leaveOnServerDays() : 7 ); + leaveOnServerCountCheck->setEnabled( Settings::self()->leaveOnServer() ); + leaveOnServerCountCheck->setChecked( Settings::self()->leaveOnServerCount() >= 1 ); + leaveOnServerCountSpin->setValue( Settings::self()->leaveOnServerCount() >= 1 ? + Settings::self()->leaveOnServerCount() : 100 ); + leaveOnServerSizeCheck->setEnabled( Settings::self()->leaveOnServer() ); + leaveOnServerSizeCheck->setChecked( Settings::self()->leaveOnServerSize() >= 1 ); + leaveOnServerSizeSpin->setValue( Settings::self()->leaveOnServerSize() >= 1 ? + Settings::self()->leaveOnServerSize() : 10 ); + filterOnServerCheck->setChecked( Settings::self()->filterOnServer() ); + filterOnServerSizeSpin->setValue( Settings::self()->filterCheckSize() ); + intervalCheck->setChecked( Settings::self()->intervalCheckEnabled() ); + intervalSpin->setValue( Settings::self()->intervalCheckInterval() ); + intervalSpin->setEnabled( Settings::self()->intervalCheckEnabled() ); + + const int authenticationMethod = Settings::self()->authenticationMethod(); + authCombo->setCurrentIndex( authCombo->findData( authenticationMethod ) ); + encryptionNone->setChecked( !Settings::self()->useSSL() && !Settings::self()->useTLS() ); + encryptionSSL->setChecked( Settings::self()->useSSL() ); + encryptionTLS->setChecked( Settings::self()->useTLS() ); + + slotEnableLeaveOnServerDays( leaveOnServerDaysCheck->isEnabled() ? + Settings::self()->leaveOnServerDays() >= 1 : 0); + slotEnableLeaveOnServerCount( leaveOnServerCountCheck->isEnabled() ? + Settings::self()->leaveOnServerCount() >= 1 : 0); + slotEnableLeaveOnServerSize( leaveOnServerSizeCheck->isEnabled() ? + Settings::self()->leaveOnServerSize() >= 1 : 0); + + // We need to fetch the collection, as the CollectionRequester needs the name + // of it to work correctly + Collection targetCollection( Settings::self()->targetCollection() ); + if ( targetCollection.isValid() ) { + CollectionFetchJob *fetchJob = new CollectionFetchJob( targetCollection, + CollectionFetchJob::Base, + this ); + connect( fetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), + this, SLOT(targetCollectionReceived(Akonadi::Collection::List)) ); + } + else { + // FIXME: This is a bit duplicated from POP3Resource... + + // No target collection set in the config? Try requesting a default inbox + SpecialMailCollectionsRequestJob *requestJob = new SpecialMailCollectionsRequestJob( this ); + requestJob->requestDefaultCollection( SpecialMailCollections::Inbox ); + requestJob->start(); + connect ( requestJob, SIGNAL(result(KJob*)), + this, SLOT(localFolderRequestJobFinished(KJob*)) ); + } + + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), winId(), + Wallet::Asynchronous ); + if ( mWallet ) { + connect( mWallet, SIGNAL(walletOpened(bool)), + this, SLOT(walletOpenedForLoading(bool)) ); + } else { + passwordEdit->setClickMessage( i18n( "Wallet disabled in system settings" ) ); + } + passwordEdit->setEnabled( false ); + passwordLabel->setEnabled( false ); +} + +void AccountDialog::walletOpenedForLoading( bool success ) +{ + if ( success ) { + if ( mWallet->isOpen() ) { + passwordEdit->setEnabled( true ); + passwordLabel->setEnabled( true ); + } + if ( mWallet->isOpen() && mWallet->hasFolder( QLatin1String("pop3") ) ) { + QString password; + mWallet->setFolder( QLatin1String("pop3") ); + mWallet->readPassword( mParentResource->identifier(), password ); + passwordEdit->setText( password ); + mInitalPassword = password; + } else { + kWarning() << "Wallet not open or doesn't have pop3 folder."; + } + } + else { + kWarning() << "Failed to open wallet for loading the password."; + } + + const bool walletError = !success || !mWallet->isOpen(); + if ( walletError ) { + passwordEdit->setClickMessage( i18n( "Unable to open wallet" ) ); + } +} + +void AccountDialog::walletOpenedForSaving( bool success ) +{ + if ( success ) { + if ( mWallet && mWallet->isOpen() ) { + + // Remove the password from the wallet if the user doesn't want to store it + if ( passwordEdit->text().isEmpty() && mWallet->hasFolder( QLatin1String("pop3") ) ) { + mWallet->setFolder( QLatin1String("pop3") ); + mWallet->removeEntry( mParentResource->identifier() ); + } + + // Store the password in the wallet if the user wants that + else if ( !passwordEdit->text().isEmpty() ) { + if ( !mWallet->hasFolder( QLatin1String("pop3") ) ) { + mWallet->createFolder( QLatin1String("pop3") ); + } + mWallet->setFolder( QLatin1String("pop3") ); + mWallet->writePassword( mParentResource->identifier(), passwordEdit->text() ); + } + + mParentResource->clearCachedPassword(); + } + else { + kWarning() << "Wallet not open."; + } + } + else { + // Should we alert the user here? + kWarning() << "Failed to open wallet for saving the password."; + } + + delete mWallet; + mWallet = 0; + accept(); +} + +void AccountDialog::slotLeaveOnServerClicked() +{ + const bool state = leaveOnServerCheck->isChecked(); + leaveOnServerDaysCheck->setEnabled( state ); + leaveOnServerCountCheck->setEnabled( state ); + leaveOnServerSizeCheck->setEnabled( state ); + if ( state ) { + if ( leaveOnServerDaysCheck->isChecked() ) { + slotEnableLeaveOnServerDays( state ); + } + if ( leaveOnServerCountCheck->isChecked() ) { + slotEnableLeaveOnServerCount( state ); + } + if ( leaveOnServerSizeCheck->isChecked() ) { + slotEnableLeaveOnServerSize( state ); + } + } else { + slotEnableLeaveOnServerDays( state ); + slotEnableLeaveOnServerCount( state ); + slotEnableLeaveOnServerSize( state ); + } + if ( mServerTest && !mServerTest->capabilities().contains( ServerTest::UIDL ) && + leaveOnServerCheck->isChecked() ) { + KMessageBox::information( topLevelWidget(), + i18n("The server does not seem to support unique " + "message numbers, but this is a " + "requirement for leaving messages on the " + "server.\n" + "Since some servers do not correctly " + "announce their capabilities you still " + "have the possibility to turn leaving " + "fetched messages on the server on.") ); + } +} + +void AccountDialog::slotFilterOnServerClicked() +{ + if ( mServerTest && !mServerTest->capabilities().contains( ServerTest::Top ) && + filterOnServerCheck->isChecked() ) { + KMessageBox::information( topLevelWidget(), + i18n("The server does not seem to support " + "fetching message headers, but this is a " + "requirement for filtering messages on the " + "server.\n" + "Since some servers do not correctly " + "announce their capabilities you still " + "have the possibility to turn filtering " + "messages on the server on.") ); + } +} + +void AccountDialog::slotPipeliningClicked() +{ + if (usePipeliningCheck->isChecked()) + KMessageBox::information( topLevelWidget(), + i18n("Please note that this feature can cause some POP3 servers " + "that do not support pipelining to send corrupted mail;\n" + "this is configurable, though, because some servers support pipelining " + "but do not announce their capabilities. To check whether your POP3 server " + "announces pipelining support use the \"Check What the Server " + "Supports\" button at the bottom of the dialog;\n" + "if your server does not announce it, but you want more speed, then " + "you should do some testing first by sending yourself a batch " + "of mail and downloading it."), QString(), + QLatin1String("pipelining")); +} + + +void AccountDialog::slotPopEncryptionChanged( int id ) +{ + kDebug() << "setting port"; + // adjust port + if ( id == Transport::EnumEncryption::SSL || portEdit->value() == 995 ) + portEdit->setValue( ( id == Transport::EnumEncryption::SSL ) ? 995 : 110 ); + + kDebug() << "port set "; + enablePopFeatures(); // removes invalid auth options from the combobox +} + +void AccountDialog::slotCheckPopCapabilities() +{ + if ( hostEdit->text().isEmpty() ) + { + KMessageBox::sorry( this, i18n( "Please specify a server and port on " + "the General tab first." ) ); + return; + } + delete mServerTest; + mServerTest = new ServerTest( this ); + BusyCursorHelper *busyCursorHelper = new BusyCursorHelper( mServerTest ); + mServerTest->setProgressBar( checkCapabilitiesProgress ); + enableButtonOk( false ); + checkCapabilitiesStack->setCurrentIndex( 1 ); + Transport::EnumEncryption::type encryptionType; + if ( encryptionSSL->isChecked() ) + encryptionType = Transport::EnumEncryption::SSL; + else + encryptionType = Transport::EnumEncryption::None; + mServerTest->setPort( encryptionType, portEdit->value() ); + mServerTest->setServer( hostEdit->text() ); + mServerTest->setProtocol( QLatin1String("pop") ); + connect( mServerTest, SIGNAL(finished(QList)), + this, SLOT(slotPopCapabilities(QList)) ); + connect( mServerTest, SIGNAL(finished(QList)), + busyCursorHelper, SLOT(deleteLater()) ); + + mServerTest->start(); + mServerTestFailed = false; +} + +void AccountDialog::slotPopCapabilities( const QList &encryptionTypes ) +{ + checkCapabilitiesStack->setCurrentIndex( 0 ); + enableButtonOk( true ); + + // if both fail, popup a dialog + if ( !mServerTest->isNormalPossible() && !mServerTest->isSecurePossible() ) + KMessageBox::sorry( this, i18n( "Unable to connect to the server, please verify the server address." ) ); + + // If the servertest did not find any useable authentication modes, assume the + // connection failed and don't disable any of the radioboxes. + if ( encryptionTypes.isEmpty() ) { + mServerTestFailed = true; + return; + } + + encryptionNone->setEnabled( encryptionTypes.contains( Transport::EnumEncryption::None ) ); + encryptionSSL->setEnabled( encryptionTypes.contains( Transport::EnumEncryption::SSL ) ); + encryptionTLS->setEnabled( encryptionTypes.contains( Transport::EnumEncryption::TLS ) ); + + usePipeliningCheck->setChecked( mServerTest->capabilities().contains( ServerTest::Pipelining ) ); + + checkHighest( encryptionButtonGroup ); +} + + +void AccountDialog::enablePopFeatures() +{ + if ( !mServerTest || mServerTestFailed ) + return; + + QList supportedAuths; + if ( encryptionButtonGroup->checkedId() == Transport::EnumEncryption::None ) + supportedAuths = mServerTest->normalProtocols(); + if ( encryptionButtonGroup->checkedId() == Transport::EnumEncryption::SSL ) + supportedAuths = mServerTest->secureProtocols(); + if ( encryptionButtonGroup->checkedId() == Transport::EnumEncryption::TLS ) + supportedAuths = mServerTest->tlsProtocols(); + + authCombo->clear(); + foreach( int prot, supportedAuths ) { + authCombo->addItem( Transport::authenticationTypeString( prot ) , prot ); + } + + if ( mServerTest && !mServerTest->capabilities().contains( ServerTest::Pipelining ) && + usePipeliningCheck->isChecked() ) { + usePipeliningCheck->setChecked( false ); + KMessageBox::information( topLevelWidget(), + i18n("The server does not seem to support " + "pipelining; therefore, this option has " + "been disabled.\n" + "Since some servers do not correctly " + "announce their capabilities you still " + "have the possibility to turn pipelining " + "on. But please note that this feature can " + "cause some POP servers that do not " + "support pipelining to send corrupt " + "messages. So before using this feature " + "with important mail you should first " + "test it by sending yourself a larger " + "number of test messages which you all " + "download in one go from the POP " + "server.") ); + } + + if ( mServerTest && !mServerTest->capabilities().contains( ServerTest::UIDL ) && + leaveOnServerCheck->isChecked() ) { + leaveOnServerCheck->setChecked( false ); + KMessageBox::information( topLevelWidget(), + i18n("The server does not seem to support unique " + "message numbers, but this is a " + "requirement for leaving messages on the " + "server; therefore, this option has been " + "disabled.\n" + "Since some servers do not correctly " + "announce their capabilities you still " + "have the possibility to turn leaving " + "fetched messages on the server on.") ); + } + + if ( mServerTest && !mServerTest->capabilities().contains( ServerTest::Top ) && + filterOnServerCheck->isChecked() ) { + filterOnServerCheck->setChecked( false ); + KMessageBox::information( topLevelWidget(), + i18n("The server does not seem to support " + "fetching message headers, but this is a " + "requirement for filtering messages on the " + "server; therefore, this option has been " + "disabled.\n" + "Since some servers do not correctly " + "announce their capabilities you still " + "have the possibility to turn filtering " + "messages on the server on.") ); + } +} + +static void addAuthenticationItem( QComboBox *combo, + int authenticationType ) +{ + combo->addItem( Transport::authenticationTypeString( authenticationType ), + QVariant( authenticationType ) ); +} + +void AccountDialog::populateDefaultAuthenticationOptions() +{ + authCombo->clear(); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::CLEAR ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::LOGIN ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::PLAIN ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::CRAM_MD5 ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::DIGEST_MD5 ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::NTLM ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::GSSAPI ); + addAuthenticationItem( authCombo, Transport::EnumAuthenticationType::APOP ); +} + + +void AccountDialog::slotLeaveOnServerDaysChanged ( int value ) +{ + leaveOnServerDaysSpin->setSuffix( i18np(" day", " days", value) ); +} + + +void AccountDialog::slotLeaveOnServerCountChanged ( int value ) +{ + leaveOnServerCountSpin->setSuffix( i18np(" message", " messages", value) ); +} + + +void AccountDialog::slotFilterOnServerSizeChanged ( int value ) +{ + filterOnServerSizeSpin->setSuffix( i18np(" byte", " bytes", value) ); +} + +void AccountDialog::checkHighest( QButtonGroup *btnGroup ) +{ + QListIterator it( btnGroup->buttons() ); + it.toBack(); + while ( it.hasPrevious() ) { + QAbstractButton *btn = it.previous(); + if ( btn && btn->isEnabled() ) { + btn->animateClick(); + return; + } + } +} + +void AccountDialog::slotButtonClicked( int button ) +{ + switch( button ) { + case Ok: + saveSettings(); + + // Don't call accept() yet, saveSettings() triggers an asnychronous wallet operation, + // which will call accept() when it is finished + break; + case Cancel: + reject(); + return; + } +} + +void AccountDialog::saveSettings() +{ + mParentResource->setName( nameEdit->text() ); + + Settings::self()->setIntervalCheckEnabled( intervalCheck->checkState() == Qt::Checked ); + Settings::self()->setIntervalCheckInterval( intervalSpin->value() ); + Settings::self()->setHost( hostEdit->text().trimmed() ); + Settings::self()->setPort( portEdit->value() ); + Settings::self()->setLogin( loginEdit->text().trimmed() ); + Settings::self()->setPrecommand( precommand->text() ); + Settings::self()->setUseSSL( encryptionSSL->isChecked() ); + Settings::self()->setUseTLS( encryptionTLS->isChecked() ); + Settings::self()->setAuthenticationMethod( authCombo->itemData( authCombo->currentIndex() ).toInt() ); + Settings::self()->setPipelining( usePipeliningCheck->isChecked() ); + Settings::self()->setLeaveOnServer( leaveOnServerCheck->isChecked() ); + Settings::self()->setLeaveOnServerDays( leaveOnServerCheck->isChecked() ? + ( leaveOnServerDaysCheck->isChecked() ? + leaveOnServerDaysSpin->value() : -1 ) : 0); + Settings::self()->setLeaveOnServerCount( leaveOnServerCheck->isChecked() ? + ( leaveOnServerCountCheck->isChecked() ? + leaveOnServerCountSpin->value() : -1 ) : 0 ); + Settings::self()->setLeaveOnServerSize( leaveOnServerCheck->isChecked() ? + ( leaveOnServerSizeCheck->isChecked() ? + leaveOnServerSizeSpin->value() : -1 ) : 0 ); + Settings::self()->setFilterOnServer( filterOnServerCheck->isChecked() ); + Settings::self()->setFilterCheckSize (filterOnServerSizeSpin->value() ); + Settings::self()->setTargetCollection( folderRequester->collection().id() ); + Settings::self()->writeConfig(); + + // Now, either save the password or delete it from the wallet. For both, we need + // to open it. + const bool userChangedPassword = mInitalPassword != passwordEdit->text(); + const bool userWantsToDeletePassword = + passwordEdit->text().isEmpty() && userChangedPassword; + + if ( ( !passwordEdit->text().isEmpty() && userChangedPassword ) || + userWantsToDeletePassword ) { + kDebug() << mWallet << mWallet->isOpen(); + if ( mWallet && mWallet->isOpen() ) { + // wallet is already open + walletOpenedForSaving( true ); + } else { + // we need to open the wallet + kDebug() << "we need to open the wallet"; + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), winId(), + Wallet::Asynchronous ); + if ( mWallet ) { + connect( mWallet, SIGNAL(walletOpened(bool)), + this, SLOT(walletOpenedForSaving(bool)) ); + } else { + accept(); + } + } + } + else { + accept(); + } +} + +void AccountDialog::slotEnableLeaveOnServerDays( bool state ) +{ + if ( state && !leaveOnServerDaysCheck->isEnabled() ) + return; + leaveOnServerDaysSpin->setEnabled( state ); +} + +void AccountDialog::slotEnableLeaveOnServerCount( bool state ) +{ + if ( state && !leaveOnServerCountCheck->isEnabled() ) + return; + leaveOnServerCountSpin->setEnabled( state ); + return; +} + +void AccountDialog::slotEnableLeaveOnServerSize( bool state ) +{ + if ( state && !leaveOnServerSizeCheck->isEnabled() ) + return; + leaveOnServerSizeSpin->setEnabled( state ); + return; +} + +void AccountDialog::slotEnablePopInterval( bool state ) +{ + intervalSpin->setEnabled( state ); + intervalLabel->setEnabled( state ); +} + +void AccountDialog::slotFontChanged( void ) +{ + QFont titleFont( titleLabel->font() ); + titleFont.setBold( true ); + titleLabel->setFont(titleFont); +} + +void AccountDialog::targetCollectionReceived( Akonadi::Collection::List collections ) +{ + folderRequester->setCollection( collections.first() ); +} + +void AccountDialog::localFolderRequestJobFinished( KJob *job ) +{ + if ( !job->error() ) { + Collection targetCollection = SpecialMailCollections::self()->defaultCollection( SpecialMailCollections::Inbox ); + Q_ASSERT( targetCollection.isValid() ); + folderRequester->setCollection( targetCollection ); + } +} + diff --git a/kdepim-runtime/resources/pop3/accountdialog.h b/kdepim-runtime/resources/pop3/accountdialog.h new file mode 100644 index 00000000..387a5afe --- /dev/null +++ b/kdepim-runtime/resources/pop3/accountdialog.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2000 Espen Sand, espen@kde.org + * Copyright 2009 Thomas McGuire + * Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company + * Copyright (C) 2010 Casey Link + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _ACCOUNT_DIALOG_H_ +#define _ACCOUNT_DIALOG_H_ + +#include "ui_popsettings.h" + +namespace MailTransport { +class ServerTest; +} +namespace KWallet { + class Wallet; +} + +class POP3Resource; +class KJob; + +class AccountDialog : public KDialog, private Ui::PopPage +{ + Q_OBJECT + + public: + AccountDialog( POP3Resource *parentResource, WId parentWindow ); + virtual ~AccountDialog(); + + private slots: + virtual void slotButtonClicked( int button ); + void slotEnablePopInterval( bool state ); + void slotFontChanged(); + void slotLeaveOnServerClicked(); + void slotEnableLeaveOnServerDays( bool state ); + void slotEnableLeaveOnServerCount( bool state ); + void slotEnableLeaveOnServerSize( bool state ); + void slotFilterOnServerClicked(); + void slotPipeliningClicked(); + void slotPopEncryptionChanged(int); + void slotCheckPopCapabilities(); + void slotPopCapabilities(const QList & ); + void slotLeaveOnServerDaysChanged( int value ); + void slotLeaveOnServerCountChanged( int value ); + void slotFilterOnServerSizeChanged( int value ); + + void targetCollectionReceived( Akonadi::Collection::List collections ); + void localFolderRequestJobFinished( KJob *job ); + void walletOpenedForLoading( bool success ); + void walletOpenedForSaving( bool success ); + private: + void setupWidgets(); + void loadSettings(); + void saveSettings(); + void checkHighest( QButtonGroup * ); + void enablePopFeatures(); + void populateDefaultAuthenticationOptions(); + + private: + POP3Resource *mParentResource; + QButtonGroup *encryptionButtonGroup; + MailTransport::ServerTest *mServerTest; + QRegExpValidator mValidator; + bool mServerTestFailed; + KWallet::Wallet *mWallet; + QString mInitalPassword; +}; + +#endif diff --git a/kdepim-runtime/resources/pop3/jobs.cpp b/kdepim-runtime/resources/pop3/jobs.cpp new file mode 100644 index 00000000..43ecf128 --- /dev/null +++ b/kdepim-runtime/resources/pop3/jobs.cpp @@ -0,0 +1,507 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "jobs.h" +#include "settings.h" + +#include + +#include +#include +#include +#include + +POPSession::POPSession( const QString &password ) + : mCurrentJob(0), mPassword( password ) +{ + KIO::Scheduler::connect( + SIGNAL(slaveError(KIO::Slave*,int,QString)), this, + SLOT(slotSlaveError(KIO::Slave*,int,QString)) ); +} + +POPSession::~POPSession() +{ + closeSession(); +} + +void POPSession::slotSlaveError( KIO::Slave *slave , int errorCode, + const QString &errorMessage ) +{ + Q_UNUSED( slave ); + kWarning() << "Got a slave error:" << errorMessage; + + if ( slave != mSlave ) + return; + + if ( errorCode == KIO::ERR_SLAVE_DIED ) + mSlave = 0; + + // Explicitly disconnect the slave if the connection went down + if ( errorCode == KIO::ERR_CONNECTION_BROKEN && mSlave ) { + KIO::Scheduler::disconnectSlave( mSlave ); + mSlave = 0; + } + + if ( !mCurrentJob ) { + emit slaveError( errorCode, errorMessage ); + } + else { + // Let the job deal with the problem + mCurrentJob->slaveError( errorCode, errorMessage ); + } +} + +void POPSession::setCurrentJob( SlaveBaseJob *job ) +{ + mCurrentJob = job; +} + +KIO::MetaData POPSession::slaveConfig() const +{ + KIO::MetaData m; + + m.insert( QLatin1String("progress"), QLatin1String("off") ); + m.insert( QLatin1String("tls"), Settings::self()->useTLS() ? QLatin1String("on") : QLatin1String("off") ); + m.insert( QLatin1String("pipelining"), ( Settings::self()->pipelining() ) ? QLatin1String("on") : QLatin1String("off") ); + int type = Settings::self()->authenticationMethod(); + switch( type ) { + case MailTransport::Transport::EnumAuthenticationType::PLAIN: + case MailTransport::Transport::EnumAuthenticationType::LOGIN: + case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5: + case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5: + case MailTransport::Transport::EnumAuthenticationType::NTLM: + case MailTransport::Transport::EnumAuthenticationType::GSSAPI: + m.insert( QLatin1String("auth"), QLatin1String("SASL") ); + m.insert( QLatin1String("sasl"), authenticationToString( type ) ); + break; + case MailTransport::Transport::EnumAuthenticationType::CLEAR: + m.insert( QLatin1String("auth"), QLatin1String("USER") ); + break; + default: + m.insert( QLatin1String("auth"), authenticationToString( type ) ); + break; + } + return m; +} + +QString POPSession::authenticationToString( int type ) const +{ + switch ( type ) { + case MailTransport::Transport::EnumAuthenticationType::LOGIN: + return QLatin1String("LOGIN"); + case MailTransport::Transport::EnumAuthenticationType::PLAIN: + return QLatin1String("PLAIN"); + case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5: + return QLatin1String("CRAM-MD5"); + case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5: + return QLatin1String("DIGEST-MD5"); + case MailTransport::Transport::EnumAuthenticationType::GSSAPI: + return QLatin1String("GSSAPI"); + case MailTransport::Transport::EnumAuthenticationType::NTLM: + return QLatin1String("NTLM"); + case MailTransport::Transport::EnumAuthenticationType::CLEAR: + return QLatin1String("USER"); + case MailTransport::Transport::EnumAuthenticationType::APOP: + return QLatin1String("APOP"); + default: + break; + } + return QString(); +} + +KUrl POPSession::getUrl() const +{ + KUrl url; + + if ( Settings::self()->useSSL() ) + url.setProtocol( QLatin1String("pop3s") ); + else + url.setProtocol( QLatin1String("pop3") ); + + url.setUser( Settings::self()->login() ); + url.setPass( mPassword ); + url.setHost( Settings::self()->host() ); + url.setPort( Settings::self()->port() ); + return url; +} + +bool POPSession::connectSlave() +{ + mSlave = KIO::Scheduler::getConnectedSlave( getUrl(), slaveConfig() ); + return mSlave != 0; +} + +void POPSession::abortCurrentJob() +{ + if ( mCurrentJob ) { + mCurrentJob->kill( KJob::Quietly ); + mCurrentJob = 0; + } +} + +void POPSession::closeSession() +{ + if ( mSlave ) { + KIO::Scheduler::disconnectSlave( mSlave ); + } +} + +KIO::Slave* POPSession::getSlave() const +{ + return mSlave; +} + + + + + +static QByteArray cleanupListRespone( const QByteArray &response ) +{ + QByteArray ret = response.simplified(); // Workaround for Maillennium POP3/UNIBOX + + // Get rid of the null terminating character, if it exists + if ( ret.size() > 0 && ret.at( ret.size() - 1 ) == 0 ) + ret.chop( 1 ); + return ret; +} + +static QString intListToString( const QList &intList ) +{ + QString idList; + foreach( int id, intList ) + idList += QString::number( id ) + QLatin1Char(','); + idList.chop( 1 ); + return idList; +} + + + + +SlaveBaseJob::SlaveBaseJob( POPSession *POPSession ) + : mJob( 0 ), + mPOPSession( POPSession ) +{ + mPOPSession->setCurrentJob( this ); +} + +SlaveBaseJob::~SlaveBaseJob() +{ + // Don't do that here, the job might be destroyed after another one was started + // and therefore overwrite the current job + //mPOPSession->setCurrentJob( 0 ); +} + +bool SlaveBaseJob::doKill() +{ + if ( mJob ) + return mJob->kill(); + else + return KJob::doKill(); +} + +void SlaveBaseJob::slotSlaveResult( KJob *job ) +{ + mPOPSession->setCurrentJob( 0 ); + if ( job->error() ) { + setError( job->error() ); + setErrorText( job->errorText() ); + } + emitResult(); + mJob = 0; +} + +void SlaveBaseJob::slotSlaveData( KIO::Job *job, const QByteArray &data ) +{ + Q_UNUSED( job ); + kWarning() << "Got unexpected slave data:" << data.data(); +} + +void SlaveBaseJob::slaveError( int errorCode, const QString &errorMessage ) +{ + // The slave experienced some problem while running our job. + // Just treat this as an error. + // Derived jobs can do something more sophisticated here + setError( errorCode ); + setErrorText( errorMessage); + emitResult(); + mJob = 0; +} + +void SlaveBaseJob::connectJob() +{ + connect( mJob, SIGNAL(data(KIO::Job*,QByteArray)), + SLOT(slotSlaveData(KIO::Job*,QByteArray)) ); + connect( mJob, SIGNAL(result(KJob*)), + SLOT(slotSlaveResult(KJob*)) ); +} + +void SlaveBaseJob::startJob( const QString &path) +{ + KUrl url = mPOPSession->getUrl(); + url.setPath( path ); + mJob = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo ); + KIO::Scheduler::assignJobToSlave( mPOPSession->getSlave(), mJob ); + connectJob(); +} + +QString SlaveBaseJob::errorString() const +{ + if ( mJob ) { + return mJob->errorString(); + } else { + return KJob::errorString(); + } +} + + +LoginJob::LoginJob( POPSession *popSession ) + : SlaveBaseJob( popSession ) +{ +} + +void LoginJob::start() +{ + // This will create a connected slave, which means it will also try to login. + KIO::Scheduler::connect( SIGNAL(slaveConnected(KIO::Slave*)), + this, SLOT(slaveConnected(KIO::Slave*))); + if ( !mPOPSession->connectSlave() ) { + setError ( KJob::UserDefinedError ); + setErrorText( i18n( "Unable to create POP3 slave, aborting mail check." ) ); + emitResult(); + } +} + +void LoginJob::slaveConnected( KIO::Slave *slave ) +{ + if ( slave != mPOPSession->getSlave() ) { + // Odd, not our slave... + return; + } + + // Yeah it connected, so login was sucessful! + emitResult(); +} + +void LoginJob::slaveError( int errorCode, const QString &errorMessage ) +{ + setError( errorCode ); + setErrorText( errorMessage ); + mErrorString = KIO::buildErrorString( errorCode, errorMessage ); + emitResult(); +} + +QString LoginJob::errorString() const +{ + return mErrorString; +} + +ListJob::ListJob( POPSession *popSession ) + : SlaveBaseJob( popSession ) +{ +} + +void ListJob::start() +{ + startJob( QLatin1String("/index") ); +} + +void ListJob::slotSlaveData( KIO::Job *job, const QByteArray &data ) +{ + Q_UNUSED( job ); + + // Silly slave, why are you sending us empty data? + if ( data.isEmpty() ) + return; + + QByteArray cleanData = cleanupListRespone( data ); + const int space = cleanData.indexOf( ' ' ); + + if ( space > 0 ) { + QByteArray lengthString = cleanData.mid( space + 1 ); + const int spaceInLengthPos = lengthString.indexOf( ' ' ); + if ( spaceInLengthPos != -1 ) + lengthString.truncate( spaceInLengthPos ); + const int length = lengthString.toInt(); + + QByteArray idString = cleanData.left( space ); + + bool idIsNumber; + int id = QString::fromLatin1( idString ).toInt( &idIsNumber ); + if ( idIsNumber ) + mIdList.insert( id, length ); + else + kWarning() << "Got non-integer ID as part of the LIST response, ignoring" + << idString.data(); + } + else { + kWarning() << "Got invalid LIST response:" << data.data(); + } +} + +QMap ListJob::idList() const +{ + return mIdList; +} + +UIDListJob::UIDListJob( POPSession *popSession ) + : SlaveBaseJob( popSession ) +{ +} + +void UIDListJob::start() +{ + startJob( QLatin1String("/uidl") ); +} + +void UIDListJob::slotSlaveData( KIO::Job *job, const QByteArray &data ) +{ + Q_UNUSED( job ); + + // Silly slave, why are you sending us empty data? + if ( data.isEmpty() ) + return; + + QByteArray cleanData = cleanupListRespone( data ); + const int space = cleanData.indexOf( ' ' ); + + if ( space <= 0 ) { + kWarning() << "Invalid response to the UIDL command:" << data.data(); + kWarning() << "Ignoring this entry."; + } + else { + QByteArray idString = cleanData.left( space ); + QByteArray uidString = cleanData.mid( space + 1 ); + bool idIsNumber; + int id = QString::fromLatin1( idString ).toInt( &idIsNumber ); + if ( idIsNumber ) { + const QString uidQString = QString::fromLatin1( uidString ); + if ( !uidQString.isEmpty() ) { + mUidList.insert( id, uidQString ); + mIdList.insert( uidQString, id ); + } + else { + kWarning() << "Got invalid/empty UID from the UIDL command:" + << uidString.data(); + kWarning() << "The whole response was:" << data.data(); + } + } + else { + kWarning() << "Got invalid ID from the UIDL command:" << idString.data(); + kWarning() << "The whole response was:" << data.data(); + } + } +} + +QMap UIDListJob::uidList() const +{ + return mUidList; +} + +QMap UIDListJob::idList() const +{ + return mIdList; +} + +DeleteJob::DeleteJob( POPSession *popSession ) + : SlaveBaseJob( popSession ) +{ +} + +void DeleteJob::setDeleteIds( const QList ids ) +{ + mIdsToDelete = ids; +} + +void DeleteJob::start() +{ + startJob( QLatin1String("/remove/") + intListToString( mIdsToDelete ) ); +} + +QList DeleteJob::deletedIDs() const +{ + // FIXME : The slave doesn't tell us which of the IDs were actually deleted, we + // just assume all of them here + return mIdsToDelete; +} + +QuitJob::QuitJob( POPSession *popSession ) + : SlaveBaseJob( popSession ) +{ +} + +void QuitJob::start() +{ + startJob( QLatin1String("/commit") ); +} + +FetchJob::FetchJob ( POPSession *session ) + : SlaveBaseJob( session ), + mBytesDownloaded( 0 ), + mTotalBytesToDownload( 0 ), + mDataCounter( 0 ) +{ +} + +void FetchJob::setFetchIds( const QList ids, QList sizes ) +{ + mIdsPendingDownload = ids; + foreach( int size, sizes ) + mTotalBytesToDownload += size; +} + +void FetchJob::start() +{ + startJob( QLatin1String("/download/") + intListToString( mIdsPendingDownload ) ); + setTotalAmount( KJob::Bytes, mTotalBytesToDownload ); +} + +void FetchJob::connectJob() +{ + SlaveBaseJob::connectJob(); + connect( mJob, SIGNAL(infoMessage(KJob*,QString,QString)), + SLOT(slotInfoMessage(KJob*,QString,QString)) ); +} + +void FetchJob::slotSlaveData( KIO::Job *job, const QByteArray &data ) +{ + Q_UNUSED( job ); + mCurrentMessage += data; + mBytesDownloaded += data.size(); + mDataCounter++; + if ( mDataCounter % 5 == 0 ) { + setProcessedAmount( KJob::Bytes, mBytesDownloaded ); + } +} + +void FetchJob::slotInfoMessage( KJob *job, const QString &infoMessage, const QString & ) +{ + Q_UNUSED( job ); + if ( infoMessage != QLatin1String("message complete") ) + return; + + KMime::Message::Ptr msg( new KMime::Message ); + msg->setContent( KMime::CRLFtoLF( mCurrentMessage ) ); + msg->parse(); + + mCurrentMessage.clear(); + const int idOfCurrentMessage = mIdsPendingDownload.takeFirst(); + emit messageFinished( idOfCurrentMessage, msg ); +} + + diff --git a/kdepim-runtime/resources/pop3/jobs.h b/kdepim-runtime/resources/pop3/jobs.h new file mode 100644 index 00000000..a1801783 --- /dev/null +++ b/kdepim-runtime/resources/pop3/jobs.h @@ -0,0 +1,207 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 JOBS_H +#define JOBS_H + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace KIO +{ + class Slave; + class Job; + class TransferJob; +} + +class SlaveBaseJob; + +class POPSession : public QObject +{ + Q_OBJECT +public: + explicit POPSession( const QString &password ); + ~POPSession(); + bool connectSlave(); + + void abortCurrentJob(); + void closeSession(); + + KIO::Slave* getSlave() const; + KUrl getUrl() const; + + // Sets the current SlaveBaseJob that is using the POPSession. + // If there is a job, all slave errors will be forwared to that job + void setCurrentJob( SlaveBaseJob *job ); + +private slots: + void slotSlaveError( KIO::Slave *slave , int, const QString & ); + +signals: + + // An error occurred within the slave. If there is a current job, this + // signal is not emitted, as the job deals with it. + void slaveError( int errorCode, const QString &errorMessage ); + +private: + KIO::MetaData slaveConfig() const; + QString authenticationToString( int type ) const; + + QPointer mSlave; + SlaveBaseJob *mCurrentJob; + QString mPassword; +}; + + +class SlaveBaseJob : public KJob +{ + Q_OBJECT + +public: + explicit SlaveBaseJob( POPSession *POPSession ); + ~SlaveBaseJob(); + + virtual void slaveError( int errorCode, const QString &errorMessage ); + +protected slots: + virtual void slotSlaveData( KIO::Job *job, const QByteArray &data ); + virtual void slotSlaveResult( KJob *job ); + +protected: + virtual QString errorString() const; + virtual bool doKill(); + void startJob( const QString &path ); + virtual void connectJob(); + + KIO::TransferJob *mJob; + POPSession *mPOPSession; +}; + +class LoginJob : public SlaveBaseJob +{ + Q_OBJECT +public: + LoginJob( POPSession *popSession ); + virtual void start(); + +protected: + virtual QString errorString() const; + +private slots: + void slaveConnected( KIO::Slave *slave ); + +private: + virtual void slaveError( int errorCode, const QString &errorMessage ); + + QString mErrorString; +}; + + +class ListJob : public SlaveBaseJob +{ + Q_OBJECT +public: + ListJob( POPSession *popSession ); + QMap idList() const; + virtual void start(); + +private: + virtual void slotSlaveData( KIO::Job *job, const QByteArray &data ); + +private: + QMap mIdList; +}; + + +class UIDListJob : public SlaveBaseJob +{ + Q_OBJECT +public: + UIDListJob( POPSession *popSession ); + QMap uidList() const; + QMap idList() const; + virtual void start(); + +private: + virtual void slotSlaveData( KIO::Job *job, const QByteArray &data ); + + QMap mUidList; + QMap mIdList; +}; + +class DeleteJob : public SlaveBaseJob +{ + Q_OBJECT +public: + DeleteJob( POPSession *popSession ); + void setDeleteIds( const QList ids ); + virtual void start(); + QList deletedIDs() const; + +private: + + QList mIdsToDelete; +}; + +class QuitJob : public SlaveBaseJob +{ + Q_OBJECT + +public: + QuitJob( POPSession *popSession ); + virtual void start(); +}; + + +class FetchJob : public SlaveBaseJob +{ + Q_OBJECT +public: + + FetchJob( POPSession *session ); + void setFetchIds( const QList ids, QList sizes ); + virtual void start(); + +private slots: + void slotInfoMessage( KJob *job, const QString &infoMessage, const QString & ); + +signals: + void messageFinished( int id, KMime::Message::Ptr message ); + +private: + + virtual void connectJob(); + virtual void slotSlaveData( KIO::Job *job, const QByteArray &data ); + + QList mIdsPendingDownload; + QByteArray mCurrentMessage; + int mBytesDownloaded; + int mTotalBytesToDownload; + uint mDataCounter; +}; + + +#endif // JOBS_H diff --git a/kdepim-runtime/resources/pop3/metatype.h b/kdepim-runtime/resources/pop3/metatype.h new file mode 100644 index 00000000..5a43472b --- /dev/null +++ b/kdepim-runtime/resources/pop3/metatype.h @@ -0,0 +1,29 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 POP3_METATYPE_H +#define POP3_METATYPE_H + +#include +#include + +#include + +Q_DECLARE_METATYPE(QList) + +#endif diff --git a/kdepim-runtime/resources/pop3/pop3resource.cpp b/kdepim-runtime/resources/pop3/pop3resource.cpp new file mode 100644 index 00000000..4a1bd439 --- /dev/null +++ b/kdepim-runtime/resources/pop3/pop3resource.cpp @@ -0,0 +1,1039 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "pop3resource.h" +#include "accountdialog.h" +#include "settings.h" +#include "jobs.h" +#include "pop3resourceattribute.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace Akonadi; +using namespace MailTransport; +using namespace KWallet; + +POP3Resource::POP3Resource( const QString &id ) + : ResourceBase( id ), + mState( Idle ), + mPopSession( 0 ), + mAskAgain( false ), + mIntervalTimer( new QTimer( this ) ), + mTestLocalInbox( false ), + mWallet( 0 ) +{ + Akonadi::AttributeFactory::registerAttribute(); + setNeedsNetwork( true ); + Settings::self()->setResourceId( identifier() ); + resetState(); + + connect( this, SIGNAL(abortRequested()), + this, SLOT(slotAbortRequested()) ); + connect( mIntervalTimer, SIGNAL(timeout()), + this, SLOT(intervalCheckTriggered()) ); + connect( this, SIGNAL(reloadConfiguration()), SLOT(configurationChanged()) ); +} + +POP3Resource::~POP3Resource() +{ + Settings::self()->writeConfig(); + delete mWallet; + mWallet = 0; +} + +void POP3Resource::configurationChanged() +{ + Settings::self()->writeConfig(); + updateIntervalTimer(); +} + +void POP3Resource::updateIntervalTimer() +{ + if ( Settings::self()->intervalCheckEnabled() && mState == Idle ) { + mIntervalTimer->start( Settings::self()->intervalCheckInterval() * 1000 * 60 ); + } + else { + mIntervalTimer->stop(); + } +} + +void POP3Resource::intervalCheckTriggered() +{ + Q_ASSERT( mState == Idle ); + if ( isOnline() ) { + kDebug() << "Starting interval mail check."; + startMailCheck(); + mIntervalCheckInProgress = true; + } else { + mIntervalTimer->start(); + } +} + +void POP3Resource::aboutToQuit() +{ + if ( mState != Idle ) + cancelSync( i18n( "Mail check aborted." ) ); +} + +void POP3Resource::slotAbortRequested() +{ + if ( mState != Idle ) + cancelSync( i18n( "Mail check was canceled manually." ), false /* no error */ ); +} + +void POP3Resource::configure( WId windowId ) +{ + QPointer accountDialog( new AccountDialog( this, windowId ) ); + if ( accountDialog->exec() == QDialog::Accepted ) { + updateIntervalTimer(); + emit configurationDialogAccepted(); + } + else { + emit configurationDialogRejected(); + } + + delete accountDialog; +} + +void POP3Resource::retrieveItems( const Akonadi::Collection &collection ) +{ + Q_UNUSED( collection ); + kWarning() << "This should never be called, we don't have a collection!"; +} + +bool POP3Resource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( item ); + Q_UNUSED( parts ); + kWarning() << "This should never be called, we don't have any item!"; + return false; +} + +QString POP3Resource::buildLabelForPasswordDialog( const QString &detailedError ) const +{ + QString queryText = i18n( "Please enter the username and password for account '%1'.", + agentName() ); + queryText += QLatin1String("
") + detailedError; + return queryText; +} + +void POP3Resource::walletOpenedForLoading( bool success ) +{ + bool passwordLoaded = success; + if ( success ) { + if ( mWallet && mWallet->isOpen() && mWallet->hasFolder( QLatin1String("pop3") ) ) { + mWallet->setFolder( QLatin1String("pop3") ); + if ( mWallet->hasEntry( identifier() ) ) + mWallet->readPassword( identifier(), mPassword ); + else + passwordLoaded = false; + } + else { + passwordLoaded = false; + } + } + delete mWallet; + mWallet = 0; + + if ( !passwordLoaded ) { + QString queryText = buildLabelForPasswordDialog( + i18n( "You are asked here because the password could not be loaded from the wallet." ) ); + showPasswordDialog( queryText ); + } + else { + advanceState( Connect ); + } +} + +void POP3Resource::walletOpenedForSaving( bool success ) +{ + if ( success ) { + if ( mWallet && mWallet->isOpen() ) { + if ( !mWallet->hasFolder( QLatin1String("pop3") ) ) { + mWallet->createFolder( QLatin1String("pop3") ); + } + mWallet->setFolder( QLatin1String("pop3") ); + mWallet->writePassword( identifier(), mPassword ); + } + } + else + kWarning() << "Unable to write the password to the wallet."; + + delete mWallet; + mWallet = 0; + finish(); +} + + +void POP3Resource::showPasswordDialog( const QString &queryText ) +{ + QPointer dlg = + new KPasswordDialog( + 0, + KPasswordDialog::ShowUsernameLine ); + dlg->setModal( true ); + dlg->setUsername( Settings::self()->login() ); + dlg->setPassword( mPassword ); + dlg->setPrompt( queryText ); + dlg->setCaption( name() ); + dlg->addCommentLine( i18n( "Account:" ), name() ); + + bool gotIt = false; + if ( dlg->exec() ) { + mPassword = dlg->password(); + Settings::self()->setLogin( dlg->username() ); + Settings::self()->writeConfig(); + if ( !dlg->password().isEmpty() ) { + mSavePassword = true; + } + + mAskAgain = false; + advanceState( Connect ); + gotIt = true; + } + delete dlg; + if ( !gotIt ) { + cancelSync( i18n( "No username and password supplied." ) ); + } +} + +void POP3Resource::advanceState( State nextState ) +{ + mState = nextState; + doStateStep(); +} + +void POP3Resource::doStateStep() +{ + switch ( mState ) + { + case Idle: + { + Q_ASSERT( false ); + kWarning() << "State machine should not be called in idle state!"; + break; + } + case FetchTargetCollection: + { + kDebug() << "================ Starting state FetchTargetCollection =========="; + emit status( Running, i18n( "Preparing transmission from \"%1\".", name() ) ); + Collection targetCollection( Settings::self()->targetCollection() ); + if ( !targetCollection.isValid() ) { + // No target collection set in the config? Try requesting a default inbox + SpecialMailCollectionsRequestJob *requestJob = new SpecialMailCollectionsRequestJob( this ); + requestJob->requestDefaultCollection( SpecialMailCollections::Inbox ); + requestJob->start(); + connect ( requestJob, SIGNAL(result(KJob*)), + this, SLOT(localFolderRequestJobFinished(KJob*)) ); + } + else { + CollectionFetchJob *fetchJob = new CollectionFetchJob( targetCollection, + CollectionFetchJob::Base ); + fetchJob->start(); + connect( fetchJob, SIGNAL(result(KJob*)), + this, SLOT(targetCollectionFetchJobFinished(KJob*)) ); + } + break; + } + case Precommand: + { + kDebug() << "================ Starting state Precommand ====================="; + if ( !Settings::self()->precommand().isEmpty() ) { + PrecommandJob *precommandJob = new PrecommandJob( Settings::self()->precommand(), this ); + connect( precommandJob, SIGNAL(result(KJob*)), + this, SLOT(precommandResult(KJob*)) ); + precommandJob->start(); + emit status( Running, i18n( "Executing precommand." ) ); + } + else { + advanceState( RequestPassword ); + } + break; + } + case RequestPassword: + { + kDebug() << "================ Starting state RequestPassword ================"; + + // Don't show any wallet or password prompts when we are unit-testing + if ( !Settings::self()->unitTestPassword().isEmpty() ) { + mPassword = Settings::self()->unitTestPassword(); + advanceState( Connect ); + break; + } + + const bool passwordNeeded = Settings::self()->authenticationMethod() != MailTransport::Transport::EnumAuthenticationType::GSSAPI; + const bool loadPasswordFromWallet = !mAskAgain && passwordNeeded && !Settings::self()->login().isEmpty() && + mPassword.isEmpty(); + if ( loadPasswordFromWallet ) { + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), winIdForDialogs(), + Wallet::Asynchronous ); + } + if ( loadPasswordFromWallet && mWallet ) { + connect( mWallet, SIGNAL(walletOpened(bool)), + this, SLOT(walletOpenedForLoading(bool)) ); + } + else if ( passwordNeeded && ( mPassword.isEmpty() || mAskAgain ) ) { + QString detail; + if ( mAskAgain ) + detail = i18n( "You are asked here because the previous login was not successful." ); + else if ( Settings::self()->login().isEmpty() ) + detail = i18n( "You are asked here because the username you supplied is empty." ); + else if ( !mWallet ) + detail = i18n( "You are asked here because the wallet password storage is disabled." ); + + showPasswordDialog( buildLabelForPasswordDialog( detail ) ); + } + else { + // No password needed or using previous password, go on with Connect + advanceState( Connect ); + } + + break; + } + case Connect: + { + kDebug() << "================ Starting state Connect ========================"; + Q_ASSERT( !mPopSession ); + mPopSession = new POPSession( mPassword ); + connect( mPopSession, SIGNAL(slaveError(int,QString)), + this, SLOT(slotSessionError(int,QString)) ); + advanceState( Login ); + break; + } + case Login: + { + kDebug() << "================ Starting state Login =========================="; + + LoginJob *loginJob = new LoginJob( mPopSession ); + connect( loginJob, SIGNAL(result(KJob*)), + this, SLOT(loginJobResult(KJob*)) ); + loginJob->start(); + break; + } + case List: + { + kDebug() << "================ Starting state List ==========================="; + emit status( Running, i18n( "Fetching mail listing." ) ); + ListJob *listJob = new ListJob( mPopSession ); + connect( listJob, SIGNAL(result(KJob*)), + this, SLOT(listJobResult(KJob*)) ); + listJob->start(); + } + break; + case UIDList: + { + kDebug() << "================ Starting state UIDList ========================"; + UIDListJob *uidListJob = new UIDListJob( mPopSession ); + connect( uidListJob, SIGNAL(result(KJob*)), + this, SLOT(uidListJobResult(KJob*)) ); + uidListJob->start(); + } + break; + case Download: + { + kDebug() << "================ Starting state Download ======================="; + FetchJob *fetchJob = new FetchJob( mPopSession ); + + // Determine which mails we want to download. Those are all mails which are + // currently on ther server, minus the ones we have already downloaded (we + // remember which UIDs we have downloaded in the settings) + QList idsToDownload = mIdsToSizeMap.keys(); + const QList UIDsOnServer = mIdsToUidsMap.values(); + const QList alreadyDownloadedUIDs = Settings::self()->seenUidList(); + foreach( const QString &uidOnServer, UIDsOnServer ) { + if ( alreadyDownloadedUIDs.contains( uidOnServer ) ) { + const int idOfUIDOnServer = mUidsToIdsMap.value( uidOnServer, -1 ); + Q_ASSERT( idOfUIDOnServer != -1 ); + idsToDownload.removeAll( idOfUIDOnServer ); + } + } + mIdsToDownload = idsToDownload; + kDebug() << "We are going to download" << mIdsToDownload.size() << "messages"; + + // For proper progress, the job needs to know the sizes of the messages, so + // put them into a list here + QList sizesOfMessagesToDownload; + foreach( int id, idsToDownload ) { + sizesOfMessagesToDownload.append( mIdsToSizeMap.value( id ) ); + } + + fetchJob->setFetchIds( idsToDownload, sizesOfMessagesToDownload ); + connect( fetchJob, SIGNAL(result(KJob*)), + this, SLOT(fetchJobResult(KJob*)) ); + connect( fetchJob, SIGNAL(messageFinished(int,KMime::Message::Ptr)), + this, SLOT(messageFinished(int,KMime::Message::Ptr)) ); + connect( fetchJob, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), + this, SLOT(messageDownloadProgress(KJob*,KJob::Unit,qulonglong)) ); + fetchJob->start(); + } + break; + case Save: + { + kDebug() << "================ Starting state Save ==========================="; + kDebug() << mPendingCreateJobs.size() << "item create jobs are pending"; + if ( mPendingCreateJobs.size() > 0 ) + emit status( Running, i18n( "Saving downloaded messages." ) ); + + // It can happen that the create job map is empty, for example if there was no + // mail to download or if all ItemCreateJob's finished before reaching this + // stage + if ( mPendingCreateJobs.isEmpty() ) { + advanceState( Delete ); + } + } + break; + case Delete: + { + kDebug() << "================ Starting state Delete ========================="; + QList idsToKill = idsToDelete(); + if ( !idsToKill.isEmpty() ) { + emit status( Running, i18n( "Deleting messages from the server.") ); + DeleteJob *deleteJob = new DeleteJob( mPopSession ); + deleteJob->setDeleteIds( idsToKill ); + connect( deleteJob, SIGNAL(result(KJob*)), + this, SLOT(deleteJobResult(KJob*)) ); + deleteJob->start(); + } + else { + advanceState( Quit ); + } + } + break; + case Quit: + { + kDebug() << "================ Starting state Quit ==========================="; + QuitJob *quitJob = new QuitJob( mPopSession ); + connect( quitJob, SIGNAL(result(KJob*)), + this, SLOT(quitJobResult(KJob*)) ); + quitJob->start(); + } + break; + case SavePassword: + { + kDebug() << "================ Starting state SavePassword ==================="; + if ( !mSavePassword ) + finish(); + else { + kDebug() << "Writing password back to the wallet."; + emit status( Running, i18n( "Saving password to the wallet." ) ); + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), winIdForDialogs(), + Wallet::Asynchronous ); + if ( mWallet ) { + connect( mWallet, SIGNAL(walletOpened(bool)), + this, SLOT(walletOpenedForSaving(bool)) ); + } else { + finish(); + } + } + break; + } + } +} + +void POP3Resource::localFolderRequestJobFinished( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Error while trying to get the local inbox folder, " + "aborting mail check." ) + QLatin1Char('\n') + job->errorString() ); + return; + } + if ( mTestLocalInbox ) { + KMessageBox::information(0, + i18n("The folder you deleted was associated with the account " + "%1 which delivered mail into it. The folder the account " + "delivers new mail into was reset to the main Inbox folder.", name())); + } + mTestLocalInbox = false; + + mTargetCollection = SpecialMailCollections::self()->defaultCollection( SpecialMailCollections::Inbox ); + Q_ASSERT( mTargetCollection.isValid() ); + advanceState( Precommand ); +} + +void POP3Resource::targetCollectionFetchJobFinished( KJob *job ) +{ + if ( job->error() ) { + if ( !mTestLocalInbox ) { + mTestLocalInbox = true; + Settings::self()->setTargetCollection( -1 ); + advanceState( FetchTargetCollection ); + return; + } else { + cancelSync( i18n( "Error while trying to get the folder for incoming mail, " + "aborting mail check." ) + QLatin1Char('\n') + job->errorString() ); + mTestLocalInbox = false; + return; + } + } + mTestLocalInbox = false; + Akonadi::CollectionFetchJob *fetchJob = + dynamic_cast( job ); + Q_ASSERT( fetchJob ); + Q_ASSERT( fetchJob->collections().size() <= 1 ); + + if ( fetchJob->collections().isEmpty() ) { + cancelSync( i18n( "Could not find folder for incoming mail, aborting mail check.") ); + return; + } + else { + mTargetCollection = fetchJob->collections().first(); + advanceState( Precommand ); + } +} + +void POP3Resource::precommandResult( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Error while executing precommand." ) + + QLatin1Char('\n') + job->errorString() ); + return; + } + else { + advanceState( RequestPassword ); + } +} + +void POP3Resource::loginJobResult( KJob *job ) +{ + if ( job->error() ) { + kDebug() << job->error() << job->errorText(); + if ( job->error() == KIO::ERR_COULD_NOT_LOGIN ) + mAskAgain = true; + cancelSync( i18n( "Unable to login to the server %1.", Settings::self()->host() ) + + QLatin1Char('\n') + job->errorString() ); + } + else { + advanceState( List ); + } +} + +void POP3Resource::listJobResult( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Error while getting the list of messages on the server." ) + + QLatin1Char('\n') + job->errorString() ); + } + else { + ListJob *listJob = dynamic_cast( job ); + Q_ASSERT( listJob ); + mIdsToSizeMap = listJob->idList(); + kDebug() << "IdsToSizeMap:" << mIdsToSizeMap; + advanceState( UIDList ); + } +} + +void POP3Resource::uidListJobResult( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Error while getting list of unique mail identifiers from the server." ) + + QLatin1Char('\n') + job->errorString() ); + } + else { + UIDListJob *listJob = dynamic_cast( job ); + Q_ASSERT( listJob ); + mIdsToUidsMap = listJob->uidList(); + mUidsToIdsMap = listJob->idList(); + kDebug() << "IdToUidMap:" << mIdsToUidsMap; + kDebug() << "UidToIdMap:" << mUidsToIdsMap; + + mUidListValid = !mIdsToUidsMap.isEmpty() || mIdsToSizeMap.isEmpty(); + if ( Settings::self()->leaveOnServer() && !mUidListValid ) { + // FIXME: this needs a proper parent window + KMessageBox::sorry( 0, + i18n( "Your POP3 server (Account: %1) does not support " + "the UIDL command: this command is required to determine, in a reliable way, " + "which of the mails on the server KMail has already seen before;\n" + "the feature to leave the mails on the server will therefore not " + "work properly.", name() ) ); + } + + advanceState( Download ); + } +} + +void POP3Resource::fetchJobResult( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Error while fetching mails from the server." ) + + QLatin1Char('\n') + job->errorString() ); + return; + } + else { + kDebug() << "Downloaded" << mDownloadedIDs.size() << "mails"; + + if ( !mIdsToDownload.isEmpty() ) { + kWarning() << "We did not download all messages, there are still some remaining " + "IDs, even though we requested their download:" << mIdsToDownload; + } + + advanceState( Save ); + } +} + +void POP3Resource::messageFinished( int messageId, KMime::Message::Ptr message ) +{ + if ( mState != Download ) { + // This can happen if the slave does not get notified in time about the fact + // that the job was killed + return; + } + + //kDebug() << "Got message" << messageId + // << "with subject" << message->subject()->asUnicodeString(); + + Akonadi::Item item; + item.setMimeType( QLatin1String("message/rfc822") ); + item.setPayload( message ); + + Pop3ResourceAttribute *attr = item.attribute( Akonadi::Entity::AddIfMissing ); + attr->setPop3AccountName( identifier() ); + // update status flags + if ( KMime::isSigned( message.get() ) ) + item.setFlag( Akonadi::MessageFlags::Signed ); + if ( KMime::isEncrypted( message.get() ) ) + item.setFlag( Akonadi::MessageFlags::Encrypted ); + if ( KMime::isInvitation( message.get() ) ) + item.setFlag( Akonadi::MessageFlags::HasInvitation ); + if ( KMime::hasAttachment( message.get() ) ) + item.setFlag( Akonadi::MessageFlags::HasAttachment ); + + ItemCreateJob *itemCreateJob = new ItemCreateJob( item, mTargetCollection ); + + mPendingCreateJobs.insert( itemCreateJob, messageId ); + connect( itemCreateJob, SIGNAL(result(KJob*)), + this, SLOT(itemCreateJobResult(KJob*)) ); + + mDownloadedIDs.append( messageId ); + mIdsToDownload.removeAll( messageId ); +} + +void POP3Resource::messageDownloadProgress( KJob *job, KJob::Unit unit, qulonglong totalBytes ) +{ + Q_UNUSED( totalBytes ); + Q_UNUSED( unit ); + Q_ASSERT( unit == KJob::Bytes ); + QString statusMessage; + const int totalMessages = mIdsToDownload.size() + mDownloadedIDs.size(); + int bytesRemainingOnServer = 0; + foreach( const QString &alreadyDownloadedUID, Settings::self()->seenUidList() ) { + const int alreadyDownloadedID = mUidsToIdsMap.value( alreadyDownloadedUID, -1 ); + if ( alreadyDownloadedID != -1 ) + bytesRemainingOnServer += mIdsToSizeMap.value( alreadyDownloadedID ); + } + + if ( Settings::self()->leaveOnServer() && bytesRemainingOnServer > 0 ) { + + statusMessage = i18n( "Fetching message %1 of %2 (%3 of %4 KB) for %5 " + "(%6 KB remain on the server).", + mDownloadedIDs.size() + 1, totalMessages, + job->processedAmount( KJob::Bytes ) / 1024, + job->totalAmount( KJob::Bytes ) / 1024, name(), + bytesRemainingOnServer / 1024 ); + } + else { + statusMessage = i18n( "Fetching message %1 of %2 (%3 of %4 KB) for %5", + mDownloadedIDs.size() +1, totalMessages, + job->processedAmount( KJob::Bytes ) / 1024, + job->totalAmount( KJob::Bytes ) / 1024, name() ); + } + emit status( Running, statusMessage ); + emit percent( job->percent() ); +} + +void POP3Resource::itemCreateJobResult( KJob *job ) +{ + if ( mState != Download && mState != Save ) { + // This can happen if the slave does not get notified in time about the fact + // that the job was killed + return; + } + + ItemCreateJob *createJob = dynamic_cast( job ); + Q_ASSERT( createJob ); + + if ( job->error() ) { + cancelSync( i18n( "Unable to store downloaded mails." ) + + QLatin1Char('\n') + job->errorString() ); + return; + } + + const int idOfMessageJustCreated = mPendingCreateJobs.value( createJob, -1 ); + Q_ASSERT( idOfMessageJustCreated != -1 ); + mPendingCreateJobs.remove( createJob ); + mIDsStored.append( idOfMessageJustCreated ); + //kDebug() << "Just stored message with ID" << idOfMessageJustCreated + // << "on the Akonadi server"; + + // Have all create jobs finished? Go to the next state, then + if ( mState == Save && mPendingCreateJobs.isEmpty() ) { + advanceState( Delete ); + } +} + +int POP3Resource::idToTime( int id ) const +{ + const QString uid = mIdsToUidsMap.value( id ); + if ( !uid.isEmpty() ) { + const int index = Settings::self()->seenUidList().indexOf( uid ); + if ( index != -1 ) + return Settings::self()->seenUidTimeList().at( index ); + } + + // If we don't find any mail, either we have no UID, or it is not in the seen UID + // list. In that case, we assume that the mail is new, i.e. from now + return time( 0 ); +} + +int POP3Resource::idOfOldestMessage( QList &idList ) const +{ + int timeOfOldestMessage = time( 0 ) + 999; + int idOfOldestMessage = -1; + foreach( int id, idList ) { + const int idTime = idToTime( id ); + if ( idTime < timeOfOldestMessage ) { + timeOfOldestMessage = idTime; + idOfOldestMessage = id; + } + } + Q_ASSERT( idList.isEmpty() || idOfOldestMessage != -1 ); + return idOfOldestMessage; +} + +QList POP3Resource::idsToDelete() const +{ + QList idsToDeleteFromServer = mIdsToSizeMap.keys(); + QList idsToSave; + + // Don't attempt to delete messages that weren't downloaded correctly + foreach( int idNotDownloaded, mIdsToDownload ) + idsToDeleteFromServer.removeAll( idNotDownloaded ); + + // By default, we delete all messages. But if we have "leave on server" + // rules, we can save some messages. + if ( Settings::self()->leaveOnServer() && !idsToDeleteFromServer.isEmpty() ) { + + // If the time-limited leave rule is checked, add the newer messages to + // the list of messages to keep + if ( Settings::self()->leaveOnServerDays() > 0 ) { + const int secondsPerDay = 86400; + time_t timeLimit = time( 0 ) - ( secondsPerDay * Settings::self()->leaveOnServerDays() ); + foreach( int idToDelete, idsToDeleteFromServer ) { + const int msgTime = idToTime( idToDelete ); + if ( msgTime >= timeLimit ) { + idsToSave.append( idToDelete ); + } + else { + kDebug() << "Message" << idToDelete << "is too old and will be deleted."; + } + } + } + + // Otherwise, add all messages to the list of messages to keep - this may + // be reduced in the following number-limited leave rule and size-limited + // leave rule checks + else { + foreach ( int idToDelete, idsToDeleteFromServer ) { + idsToSave.append( idToDelete ); + } + } + + // + // Delete more old messages if there are more than mLeaveOnServerCount + // + if ( Settings::self()->leaveOnServerCount() > 0 ) { + const int numToDelete = idsToSave.count() - Settings::self()->leaveOnServerCount(); + if ( numToDelete > 0 && numToDelete < idsToSave.count() ) { + // Get rid of the first numToDelete messages + for ( int i = 0; i < numToDelete; i++ ) { + idsToSave.removeAll( idOfOldestMessage( idsToSave ) ); + } + } + else if ( numToDelete >= idsToSave.count() ) + idsToSave.clear(); + } + + // + // Delete more old messages until we're under mLeaveOnServerSize MBs + // + if ( Settings::self()->leaveOnServerSize() > 0 ) { + const qint64 limitInBytes = Settings::self()->leaveOnServerSize() * ( 1024 * 1024 ); + qint64 sizeOnServerAfterDeletion = 0; + foreach( int id, idsToSave ) { + sizeOnServerAfterDeletion += mIdsToSizeMap.value( id ); + } + while ( sizeOnServerAfterDeletion > limitInBytes ) { + int oldestId = idOfOldestMessage( idsToSave ); + idsToSave.removeAll( oldestId ); + sizeOnServerAfterDeletion -= mIdsToSizeMap.value( oldestId ); + } + } + + // + // Now save the messages from deletion + // + foreach( int idToSave, idsToSave ) { + idsToDeleteFromServer.removeAll( idToSave ); + } + } + + kDebug() << "Going to delete" << idsToDeleteFromServer.size() << idsToDeleteFromServer; + return idsToDeleteFromServer; +} + +void POP3Resource::deleteJobResult( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Failed to delete the messages from the server.") + + QLatin1Char('\n') + job->errorString() ); + return; + } + + DeleteJob *deleteJob = dynamic_cast( job ); + Q_ASSERT( deleteJob ); + mDeletedIDs = deleteJob->deletedIDs(); + + // Remove all deleted messages from the list of already downloaded messages, + // as it is no longer necessary to store them (they just waste space) + QList seenUIDs = Settings::self()->seenUidList(); + QList timeOfSeenUids = Settings::self()->seenUidTimeList(); + Q_ASSERT( seenUIDs.size() == timeOfSeenUids.size() ); + foreach( int deletedId, mDeletedIDs ) { + QString deletedUID = mIdsToUidsMap.value( deletedId ); + if ( !deletedUID.isEmpty() ) { + int index = seenUIDs.indexOf( deletedUID ); + if ( index != -1 ) { + // TEST + kDebug() << "Removing UID" << deletedUID << "from the seen UID list, as it was deleted."; + seenUIDs.removeAt( index ); + timeOfSeenUids.removeAt( index ); + } + } + } + Settings::self()->setSeenUidList( seenUIDs ); + Settings::self()->setSeenUidTimeList( timeOfSeenUids ); + Settings::self()->writeConfig(), + + advanceState( Quit ); +} + +void POP3Resource::finish() +{ + kDebug() << "================= Mail check finished. ============================="; + saveSeenUIDList(); + if ( !mIntervalCheckInProgress ) + collectionsRetrieved( Akonadi::Collection::List() ); + if ( mDownloadedIDs.isEmpty() ) + emit status( Idle, i18n( "Finished mail check, no message downloaded." ) ); + else + emit status( Idle, i18np( "Finished mail check, 1 message downloaded.", + "Finished mail check, %1 messages downloaded.", + mDownloadedIDs.size() ) ); + + resetState(); +} + +void POP3Resource::quitJobResult( KJob *job ) +{ + if ( job->error() ) { + cancelSync( i18n( "Unable to complete the mail fetch." ) + + QLatin1Char('\n') + job->errorString() ); + return; + } + + advanceState( SavePassword ); +} + +void POP3Resource::slotSessionError( int errorCode, const QString &errorMessage ) +{ + kWarning() << "Error in our session, unrelated to a currently running job!"; + cancelSync( KIO::buildErrorString( errorCode, errorMessage ) ); +} + +void POP3Resource::saveSeenUIDList() +{ + QList seenUIDs = Settings::self()->seenUidList(); + QList timeOfSeenUIDs = Settings::self()->seenUidTimeList(); + + // + // Find the messages that we have successfully stored, but did not actually get + // deleted. + // Those messages, we have to remember, so we don't download them again. + // + QList idsOfMessagesDownloadedButNotDeleted = mIDsStored; + foreach( int deletedId, mDeletedIDs ) + idsOfMessagesDownloadedButNotDeleted.removeAll( deletedId ); + QList uidsOfMessagesDownloadedButNotDeleted; + foreach( int id, idsOfMessagesDownloadedButNotDeleted ) { + QString uid = mIdsToUidsMap.value( id ); + if ( !uid.isEmpty() ) { + uidsOfMessagesDownloadedButNotDeleted.append( uid ); + } + } + Q_ASSERT( seenUIDs.size() == timeOfSeenUIDs.size() ); + foreach( const QString &uid, uidsOfMessagesDownloadedButNotDeleted ) { + if ( !seenUIDs.contains( uid ) ) { + seenUIDs.append( uid ); + timeOfSeenUIDs.append( time( 0 ) ); + } + } + + // + // If we have a valid UID list from the server, delete those UIDs that are in + // the seenUidList but are not on the server. + // This can happen if someone else than this resource deleted the mails from the + // server which we kept here. + // + if ( mUidListValid ) { + QList::iterator uidIt = seenUIDs.begin(); + QList::iterator timeIt = timeOfSeenUIDs.begin(); + while ( uidIt != seenUIDs.end() ) { + const QString curSeenUID = *uidIt; + if ( !mUidsToIdsMap.contains( curSeenUID ) ) { + // Ok, we have a UID in the seen UID list that is not anymore on the server. + // Therefore remove it from the seen UID list, it is not needed there anymore, + // it just wastes space. + uidIt = seenUIDs.erase( uidIt ); + timeIt = timeOfSeenUIDs.erase( timeIt ); + } + else { + ++uidIt; + ++timeIt; + } + } + } + else + kWarning() << "UID list from server is not valid."; + + + // + // Now save it in the settings + // + kDebug() << "The seen UID list has" << seenUIDs.size() << "entries"; + Settings::self()->setSeenUidList( seenUIDs ); + Settings::self()->setSeenUidTimeList( timeOfSeenUIDs ); + Settings::self()->writeConfig(); +} + +void POP3Resource::cancelSync( const QString &errorMessage, bool error ) +{ + if ( error ) { + cancelTask( errorMessage ); + kWarning() << "============== ERROR DURING POP3 SYNC =========================="; + kWarning() << errorMessage; + } + else { + kDebug() << "Canceled the sync, but no error."; + cancelTask(); + } + saveSeenUIDList(); + resetState(); +} + +void POP3Resource::resetState() +{ + mState = Idle; + mTargetCollection = Collection( -1 ); + mIdsToSizeMap.clear(); + mIdsToUidsMap.clear(); + mUidsToIdsMap.clear(); + mDownloadedIDs.clear(); + mIdsToDownload.clear(); + mPendingCreateJobs.clear(); + mIDsStored.clear(); + mDeletedIDs.clear(); + mUidListValid = false; + mIntervalCheckInProgress = false; + mSavePassword = false; + updateIntervalTimer(); + delete mWallet; + mWallet = 0; + + if ( mPopSession ) { + // Closing the POP session means the KIO slave will get disconnected, which + // automatically issues the QUIT command. + // Delete the POP session later, otherwise the scheduler makes us crash + mPopSession->abortCurrentJob(); + mPopSession->deleteLater(); + mPopSession = 0; + } +} + +void POP3Resource::startMailCheck() +{ + resetState(); + mIntervalTimer->stop(); + emit percent( 0 ); // Otherwise the value from the last sync is taken + advanceState( FetchTargetCollection ); +} + +void POP3Resource::retrieveCollections() +{ + if ( mState == Idle ) { + startMailCheck(); + } + else { + cancelSync( + i18n( "Mail check already in progress, unable to start a second check." ) ); + } +} + +void POP3Resource::clearCachedPassword() +{ + mPassword.clear(); +} + +void POP3Resource::cleanup() +{ + if (mWallet && mWallet->isOpen() && mWallet->hasFolder( QLatin1String("pop3") ) ) { + mWallet->setFolder( QLatin1String("pop3") ); + if ( mWallet->hasEntry( identifier() ) ) + mWallet->removeEntry(identifier()); + } +} + +void POP3Resource::doSetOnline( bool online ) +{ + ResourceBase::doSetOnline( online ); + if ( online ) { + emit status( Idle, i18n("Ready") ); + } else { + if ( mState != Idle ) { + cancelSync( i18n( "Mail check aborted after going offline." ), false /* no error */ ); + } + emit status( Idle, i18n("Offline") ); + delete mWallet; + mWallet = 0; + clearCachedPassword(); + } +} + +AKONADI_RESOURCE_MAIN( POP3Resource ) diff --git a/kdepim-runtime/resources/pop3/pop3resource.desktop b/kdepim-runtime/resources/pop3/pop3resource.desktop new file mode 100644 index 00000000..ee01329d --- /dev/null +++ b/kdepim-runtime/resources/pop3/pop3resource.desktop @@ -0,0 +1,102 @@ +[Desktop Entry] +Name=POP3 E-Mail Server +Name[bg]=ПощенÑки Ñървър POP3 +Name[bs]=POP3 E-Mail Server +Name[ca]=Servidor de correu POP3 +Name[ca@valencia]=Servidor de correu POP3 +Name[cs]=E-mailový POP3 server +Name[da]=POP3 e-mail-server +Name[de]=POP3-E-Mail-Server +Name[el]=ΕξυπηÏετητής ηλ.ταχυδÏομείου POP3 +Name[en_GB]=POP3 E-Mail Server +Name[es]=Servidor de correo POP-3 +Name[et]=POP3 e-posti server +Name[fi]=POP3-sähköpostipalvelin +Name[fr]=Serveur de courriers électroniques POP3 +Name[ga]=Freastalaí Ríomhphoist POP3 +Name[gl]=Servidor de correo electrónico de POP3 +Name[hu]=POP3 e-mail kiszolgáló +Name[ia]=Servitor POP3 de e-posta +Name[it]=Server di posta POP3 +Name[ja]=POP3 メールサーム+Name[kk]=POP3 Ñл.пошта Ñервері +Name[km]=ម៉ាស៊ីន​បម្រើ​អ៊ីមែល POP3 +Name[ko]=POP3 ì´ë©”ì¼ ì„œë²„ +Name[lt]=POP3 el. paÅ¡to serveris +Name[lv]=POP3 e-pasta serveris +Name[nb]=POP3 E-post-tjener +Name[nds]=POP3-Nettpostserver +Name[nl]=POP3 e-mailserver +Name[nn]=POP3-basert e-posttenar +Name[pa]=POP3 ਈਮੇਲ ਸਰਵਰ +Name[pl]=Serwer pocztowy POP3 +Name[pt]=Servidor de E-Mail POP3 +Name[pt_BR]=Servidor de e-mails POP3 +Name[ro]=Server de poÈ™tă POP3 +Name[ru]=Почтовый Ñервер POP3 +Name[sk]=POP3 poÅ¡tový server +Name[sl]=E-poÅ¡tni strežnik POP3 +Name[sr]=ПОП3 Ñервер е‑поште +Name[sr@ijekavian]=ПОП3 Ñервер е‑поште +Name[sr@ijekavianlatin]=POP3 server e‑poÅ¡te +Name[sr@latin]=POP3 server e‑poÅ¡te +Name[sv]=POP3 e-postserver +Name[tr]=POP3 E-Posta Sunucu +Name[uk]=Сервер пошти POP3 +Name[x-test]=xxPOP3 E-Mail Serverxx +Name[zh_CN]=POP3 电å­é‚®ä»¶æœåŠ¡å™¨ +Name[zh_TW]=POP3 信件伺æœå™¨ +Comment=Connects to a POP3 e-mail server +Comment[bg]=Свързване Ñ Ð¿Ð¾Ñ‰ÐµÐ½Ñки Ñървър POP3 +Comment[bs]=POvezuje se na POP3 e-mail server +Comment[ca]=Connecta a un servidor de correu POP3 +Comment[ca@valencia]=Connecta a un servidor de correu POP3 +Comment[cs]=PÅ™ipojí se na e-mailový POP3 server +Comment[da]=Forbinder til en POP3 e-mail-server +Comment[de]=Verbindet zu einem POP3-E-Mail-Server. +Comment[el]=Συνδέεται σε έναν εξυπηÏετητή ηλ.ταχυδÏομείου POP3. +Comment[en_GB]=Connects to a POP3 e-mail server +Comment[es]=Conecta a un servidor de correo POP3 +Comment[et]=Ãœhendumine POP3 e-posti serveriga +Comment[fi]=Yhdistää POP3-sähköpostipalvelimeen. +Comment[fr]=Se connecte à un serveur de courriers électroniques POP3 +Comment[ga]=Déanann sé seo ceangal le freastalaí ríomhphoist POP3 +Comment[gl]=Conéctase a un servidor de correo POP3 +Comment[hu]=Kapcsolódás egy POP3 e-mail kiszolgálóhoz +Comment[ia]=Connecte a un servitor POP3 de e-posta +Comment[it]=Si collega ad un server di posta POP3. +Comment[ja]=POP3 ã®ãƒ¡ãƒ¼ãƒ«ã‚µãƒ¼ãƒã«æŽ¥ç¶šã—ã¾ã™ã€‚ +Comment[kk]=POP3 Ñл.пошта Ñерверімен Ð±Ð°Ð¹Ð»Ð°Ð½Ñ‹Ñ Ò›Ò±Ñ€Ñƒ +Comment[km]=ážáž—្ជាប់​ទៅកាន់​ម៉ាស៊ីន​បម្រើ​អ៊ីមែល POP3 +Comment[ko]=POP3 ì´ë©”ì¼ ì„œë²„ì— ì—°ê²°í•¨ +Comment[lt]=Jungiasi prie POP3 el. paÅ¡to serverio +Comment[lv]=PieslÄ“dzas POP3 e-pasta serverim +Comment[nb]=Kobler til en POP3 e-posttjener. +Comment[nds]=Stellt en Verbinnen na en POP3-Nettpostserver op. +Comment[nl]=Maakt verbinding met een POP3 e-mailserver. +Comment[nn]=Koplar til ein POP3-basert e-posttenar +Comment[pa]=POP3 ਈਮੇਲ ਸਰਵਰ ਨਾਲ ਕà©à¨¨à©ˆà¨•à¨Ÿ ਕਰਦਾ ਹੈ। +Comment[pl]=ÅÄ…czy z serwerem poczty POP3 +Comment[pt]=Liga-se a um servidor de e-mail em POP3 +Comment[pt_BR]=Conecta a um servidor de e-mails POP3 +Comment[ro]=Se conectează la un server de poÈ™tă POP3 +Comment[ru]=Подключение к почтовому Ñерверу POP3 +Comment[sk]=Pripojí sa na POP3 poÅ¡tový server +Comment[sl]=Povezava z e-poÅ¡tnim strežnikom POP3 +Comment[sr]=Повезује Ñе на ПОП3 Ñервер е‑поште +Comment[sr@ijekavian]=Повезује Ñе на ПОП3 Ñервер е‑поште +Comment[sr@ijekavianlatin]=Povezuje se na POP3 server e‑poÅ¡te +Comment[sr@latin]=Povezuje se na POP3 server e‑poÅ¡te +Comment[sv]=Ansluter till en POP3 e-postserver. +Comment[tr]=POP3 e-posta sunucusuna baÄŸlanır +Comment[uk]=Ð’Ñтановлює Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· поштовим Ñервером POP3. +Comment[x-test]=xxConnects to a POP3 e-mail serverxx +Comment[zh_CN]=连接到一个 POP3 电å­é‚®ä»¶æœåŠ¡å™¨ +Comment[zh_TW]=連線到 POP3 信件伺æœå™¨ +Type=AkonadiResource +Exec=akonadi_pop3_resource +Icon=network-server + +X-Akonadi-MimeTypes=message/rfc822 +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_pop3_resource diff --git a/kdepim-runtime/resources/pop3/pop3resource.h b/kdepim-runtime/resources/pop3/pop3resource.h new file mode 100644 index 00000000..f228c9c6 --- /dev/null +++ b/kdepim-runtime/resources/pop3/pop3resource.h @@ -0,0 +1,188 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 POP3RESOURCE_H +#define POP3RESOURCE_H + +#include +#include +#include + +namespace Akonadi { +class ItemCreateJob; +} +namespace KWallet { +class Wallet; +} + +class POPSession; +class QTimer; + +class POP3Resource : public Akonadi::ResourceBase, + public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + POP3Resource( const QString &id ); + ~POP3Resource(); + + void clearCachedPassword(); + + void cleanup(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + + virtual void aboutToQuit(); + virtual void doSetOnline( bool online ); + + private Q_SLOTS: + + void slotAbortRequested(); + void intervalCheckTriggered(); + void configurationChanged(); + + // Error unrelated to a state + void slotSessionError( int errorCode, const QString &errorMessage ); + + // For state FetchTargetCollection + void targetCollectionFetchJobFinished( KJob *job ); + void localFolderRequestJobFinished( KJob *job ); + + // For state Precommand + void precommandResult( KJob *job ); + + // For state RequestPassword + void walletOpenedForLoading( bool success ); + + // For state Login + void loginJobResult( KJob *job ); + + // For state List + void listJobResult( KJob *job ); + + // For state UIDList + void uidListJobResult( KJob *job ); + + // For state Download + void messageFinished( int messageId, KMime::Message::Ptr message ); + void fetchJobResult( KJob *job ); + void messageDownloadProgress( KJob *job, KJob::Unit unit, qulonglong totalBytes ); + + // For state Save + void itemCreateJobResult( KJob *job ); + + // For state Delete + void deleteJobResult( KJob *job ); + + // For state Quit + void quitJobResult( KJob *job ); + + // For state SavePassword + void walletOpenedForSaving( bool success ); + + private: + + enum State { + Idle, + FetchTargetCollection, + Precommand, + RequestPassword, + Connect, + Login, + List, + UIDList, + Download, + Save, + Delete, + Quit, + SavePassword + }; + + void resetState(); + void doStateStep(); + void advanceState( State nextState ); + void cancelSync( const QString &errorMessage, bool error = true ); + void saveSeenUIDList(); + QList idsToDelete() const; + int idToTime( int id ) const; + int idOfOldestMessage( QList &idList ) const; + void startMailCheck(); + void updateIntervalTimer(); + void showPasswordDialog( const QString &queryText ); + QString buildLabelForPasswordDialog( const QString &detailedError ) const; + void finish(); + + State mState; + Akonadi::Collection mTargetCollection; + POPSession *mPopSession; + bool mAskAgain; + QTimer *mIntervalTimer; + bool mIntervalCheckInProgress; + QString mPassword; + bool mSavePassword; + bool mTestLocalInbox; + KWallet::Wallet *mWallet; + + // Maps IDs on the server to message sizes on the server + QMap mIdsToSizeMap; + + // Maps IDs on the server to UIDs on the server. + // This can be empty, if the server doesn't support UIDL + QMap mIdsToUidsMap; + + // Maps UIDs on the server to IDs on the server. + // This can be empty, if the server doesn't support UIDL + QMap mUidsToIdsMap; + + // Whether we actually received a valid UID list from the server + bool mUidListValid; + + // IDs of messages that we have successfully downloaded. This does _not_ mean + // that the messages corresponding to the IDs are stored in Akonadi yet + QList mDownloadedIDs; + + // IDs of messages that we want to download and that we have started the + // FetchJob with. After the FetchJob, this should be empty, except if there + // was some error + QList mIdsToDownload; + + // After downloading a message, we store it in Akonadi by using an ItemCreateJob. + // This map stores the currently running ItemCreateJob's and their corresponding + // POP3 IDs. + // When an ItemCreateJob finished, it is removed from this map. + // The Save state waits until this map becomes empty. + QMap mPendingCreateJobs; + + // List of message IDs that were successfully stored in Akonadi + QList mIDsStored; + + // List of message IDs that were successfully deleted + QList mDeletedIDs; + +}; + +#endif diff --git a/kdepim-runtime/resources/pop3/pop3resourceattribute.cpp b/kdepim-runtime/resources/pop3/pop3resourceattribute.cpp new file mode 100644 index 00000000..54c7a955 --- /dev/null +++ b/kdepim-runtime/resources/pop3/pop3resourceattribute.cpp @@ -0,0 +1,82 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "pop3resourceattribute.h" + +#include +#include +#include + +class Pop3ResourceAttributePrivate +{ +public: + Pop3ResourceAttributePrivate() + { + } + QString accountName; +}; + +Pop3ResourceAttribute::Pop3ResourceAttribute() + : d(new Pop3ResourceAttributePrivate) +{ +} + +Pop3ResourceAttribute::~Pop3ResourceAttribute() +{ + delete d; +} + +Pop3ResourceAttribute *Pop3ResourceAttribute::clone() const +{ + Pop3ResourceAttribute *attr = new Pop3ResourceAttribute(); + attr->setPop3AccountName(pop3AccountName()); + return attr; +} + +QByteArray Pop3ResourceAttribute::type() const +{ + static const QByteArray sType( "pop3resourceattribute" ); + return sType; +} + +QByteArray Pop3ResourceAttribute::serialized() const +{ + QByteArray result; + QDataStream s( &result, QIODevice::WriteOnly ); + s << pop3AccountName(); + return result; +} + +void Pop3ResourceAttribute::deserialize( const QByteArray &data ) +{ + QDataStream s( data ); + QString value; + s >> value; + d->accountName = value; +} + +QString Pop3ResourceAttribute::pop3AccountName() const +{ + return d->accountName; +} + +void Pop3ResourceAttribute::setPop3AccountName(const QString &accountName) +{ + d->accountName = accountName; +} + diff --git a/kdepim-runtime/resources/pop3/pop3resourceattribute.h b/kdepim-runtime/resources/pop3/pop3resourceattribute.h new file mode 100644 index 00000000..f65f1fd5 --- /dev/null +++ b/kdepim-runtime/resources/pop3/pop3resourceattribute.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2013, 2014 Montel Laurent + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef POP3RESOURCEATTRIBUTE_H +#define POP3RESOURCEATTRIBUTE_H + +#include + +class Pop3ResourceAttributePrivate; +class Pop3ResourceAttribute : public Akonadi::Attribute +{ +public: + Pop3ResourceAttribute(); + ~Pop3ResourceAttribute(); + + /* reimpl */ + Pop3ResourceAttribute *clone() const; + QByteArray type() const; + QByteArray serialized() const; + void deserialize( const QByteArray &data ); + + QString pop3AccountName() const; + void setPop3AccountName(const QString &accountName); + +private: + friend class Pop3ResourceAttributePrivate; + Pop3ResourceAttributePrivate * const d; +}; + + +#endif // POP3RESOURCEATTRIBUTE_H diff --git a/kdepim-runtime/resources/pop3/popsettings.ui b/kdepim-runtime/resources/pop3/popsettings.ui new file mode 100644 index 00000000..2d6a1e5b --- /dev/null +++ b/kdepim-runtime/resources/pop3/popsettings.ui @@ -0,0 +1,646 @@ + + + PopPage + + + + 0 + 0 + 451 + 557 + + + + + + + + 75 + true + + + + Account Type: POP Account + + + + + + + + + + + 0 + 0 + + + + 0 + + + false + + + + General + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + Mail Checking Options + + + + + + If active, the POP3 account will be checked for new mail every x minutes + + + Enable &interval mail checking + + + + + + + + + Check mail interval: + + + + + + + 5 + + + 1 + + + 10000000 + + + + + + + + + + + + + + + Account Information + + + + QFormLayout::ExpandingFieldsGrow + + + + + Your Internet Service Provider gave you a <em>user name</em> which is used to authenticate you with their servers. It usually is the first part of your email address (the part before <em>@</em>). + + + Account &name: + + + nameEdit + + + + + + + Name displayed in the list of accounts + + + Account name: This defines the name displayed in the account list. + + + true + + + + + + + Incoming mail &server: + + + hostEdit + + + + + + + Address of the mail POP3 server + + + The address of the POP3 server, e.g. pop3.yourprovider.org. You should get this address from your mail provider. + + + true + + + + + + + Username: + + + loginEdit + + + + + + + The username that identifies you against the mail server + + + Your Internet Service Provider gave you a <em>user name</em> which is used to authenticate you with their servers. It usually is the first part of your email address (the part before <em>@</em>). + + + true + + + + + + + P&assword: + + + passwordEdit + + + + + + + Password for access to the mail server + + + Password: The password given to you by your mail provider. + + + QLineEdit::Password + + + true + + + + + + + + + + + Advanced + + + + + + POP Settings + + + + + + If checked the message is not deleted from the mail server + + + Check this option if you want to fetch only copies of the mails and leave the original mails on the server. + + + Lea&ve fetched messages on the server + + + + + + + + 0 + 0 + + + + The original message is deleted from the server after x days + + + Days to leave messages on the server: + + + + + + + false + + + 1 + + + 0 + + + 365 + + + days + + + + + + + Only the x most recent messages are kept on the server + + + Check this option if you want to only keep the x most recent messages on the server and delete all older. + + + Number of messages to keep: + + + + + + + false + + + 100 + + + 0 + + + 999999 + + + messages + + + + + + + Keep most recent messages within the quota and delete oldest + + + If active, most recent messages are kept until the quota is reached and oldest messages are deleted. + + + Maximum megabytes to keep: + + + + + + + false + + + 10 + + + 1 + + + 999999 + + + MB + + + + + + + If you select this option, POP Filters will be used to decide what to do with messages. You can then select to download, delete or keep them on the server. + + + &Filter messages larger than: + + + + + + + false + + + If you select this option, POP Filters will be used to decide what to do with messages. You can then select to download, delete or keep them on the server. + + + 50000 + + + 1 + + + 10000000 + + + 100 + + + bytes + + + + + + + &Use pipelining for faster mail download + + + + + + + QLayout::SetMinimumSize + + + 4 + + + + + Destination folder: + + + + + + + Pre-com&mand: + + + precommand + + + + + + + + 0 + 0 + + + + Command that is executed before checking mail + + + true + + + true + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'HandelGotDLig'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">SSL/TLS</span><span style=" font-family:'Sans Serif';"> is safe IMAP over port 993;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">STARTTLS</span><span style=" font-family:'Sans Serif';"> will operate on port 143 and switch to a secure connection directly after connecting;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">None</span><span style=" font-family:'Sans Serif';"> will connect to port 143 but not switch to a secure connection. This setting is not recommended.</span></p></body></html> + + + Connection Settings + + + + + + 0 + + + + + + + Auto Detect + + + + + + + + + + + 0 + + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Encryption: + + + + + + + + + None + + + true + + + + + + + SSL/TLS + + + + + + + STARTTLS + + + + + + + + + Authentication: + + + + + + + + + + Port: + + + + + + + 1 + + + 65534 + + + 110 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+ 1 +
+ + KSeparator + QFrame +
kseparator.h
+ 1 +
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KIntNumInput + QWidget +
knuminput.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+ + Akonadi::CollectionRequester + QFrame +
Akonadi/CollectionRequester
+ 1 +
+
+ + tabWidget + nameEdit + hostEdit + loginEdit + passwordEdit + intervalCheck + intervalSpin + leaveOnServerCheck + leaveOnServerDaysCheck + leaveOnServerDaysSpin + leaveOnServerCountCheck + leaveOnServerCountSpin + leaveOnServerSizeCheck + leaveOnServerSizeSpin + filterOnServerCheck + filterOnServerSizeSpin + usePipeliningCheck + precommand + checkCapabilities + encryptionNone + encryptionSSL + encryptionTLS + portEdit + authCombo + + + +
diff --git a/kdepim-runtime/resources/pop3/settings.cpp b/kdepim-runtime/resources/pop3/settings.cpp new file mode 100644 index 00000000..36cdcf65 --- /dev/null +++ b/kdepim-runtime/resources/pop3/settings.cpp @@ -0,0 +1,82 @@ +/* Copyright 2010 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "settings.h" +#include "settingsadaptor.h" + +#include +#include + +class SettingsHelper +{ + public: + SettingsHelper() : q( 0 ) {} + ~SettingsHelper() { + kWarning() << q; + delete q; + q = 0; + } + Settings *q; +}; + +K_GLOBAL_STATIC( SettingsHelper, s_globalSettings ) + +Settings *Settings::self() +{ + if ( !s_globalSettings->q ) { + new Settings; + s_globalSettings->q->readConfig(); + } + return s_globalSettings->q; +} + +Settings::Settings() +{ + Q_ASSERT( !s_globalSettings->q ); + s_globalSettings->q = this; + + new SettingsAdaptor( this ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), this, + QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents ); +} + +void Settings::setWindowId( WId id ) +{ + mWinId = id; +} + +void Settings::setResourceId( const QString &resourceIdentifier ) +{ + mResourceId = resourceIdentifier; +} + +void Settings::setPassword( const QString& password ) +{ + using namespace KWallet; + Wallet *wallet = Wallet::openWallet( Wallet::NetworkWallet(), mWinId, + Wallet::Synchronous ); + if ( wallet && wallet->isOpen() ) { + if ( !wallet->hasFolder( QLatin1String("pop3") ) ) + wallet->createFolder( QLatin1String("pop3") ); + wallet->setFolder( QLatin1String("pop3") ); + wallet->writePassword( mResourceId, password ); + } else { + kWarning() << "Unable to open wallet!"; + } + delete wallet; +} diff --git a/kdepim-runtime/resources/pop3/settings.h b/kdepim-runtime/resources/pop3/settings.h new file mode 100644 index 00000000..d2bf03cb --- /dev/null +++ b/kdepim-runtime/resources/pop3/settings.h @@ -0,0 +1,47 @@ +/* Copyright 2010 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 SETTINGS_H +#define SETTINGS_H + +#include "settingsbase.h" + +#include + +/** + * Extended settings class that allows setting the password over dbus, which is used by the + * wizard. + */ +class Settings : public SettingsBase +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.POP3.Wallet" ) + public: + Settings(); + void setWindowId( WId id ); + void setResourceId( const QString &resourceIdentifier ); + static Settings *self(); + + public slots: + Q_SCRIPTABLE void setPassword( const QString &password ); + private: + WId mWinId; + QString mResourceId; +}; + +#endif diff --git a/kdepim-runtime/resources/pop3/settings.kcfg b/kdepim-runtime/resources/pop3/settings.kcfg new file mode 100644 index 00000000..6cff7861 --- /dev/null +++ b/kdepim-runtime/resources/pop3/settings.kcfg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + 110 + + + + 7 + + + + false + + + + false + + + + false + + + + false + + + + -1 + + + + -1 + + + + -1 + + + + false + + + + 50000 + + + + -1 + + + + + + false + + + 5 + + + + + + + + + + + + + + + + + diff --git a/kdepim-runtime/resources/pop3/settingsbase.kcfgc b/kdepim-runtime/resources/pop3/settingsbase.kcfgc new file mode 100644 index 00000000..2f8e8d50 --- /dev/null +++ b/kdepim-runtime/resources/pop3/settingsbase.kcfgc @@ -0,0 +1,8 @@ +File=settings.kcfg +ClassName=SettingsBase +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +IncludeFiles=metatype.h +GlobalEnums=true diff --git a/kdepim-runtime/resources/pop3/tests/CMakeLists.txt b/kdepim-runtime/resources/pop3/tests/CMakeLists.txt new file mode 100644 index 00000000..5efb9baa --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/CMakeLists.txt @@ -0,0 +1,58 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${Boost_INCLUDE_DIR} + ${AKONADI_INCLUDE_DIR} + ${KDEPIMLIBS_INCLUDE_DIRS} +) + +# Stolen from kdepimlibs/akonadi/tests +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +macro(add_akonadi_isolated_test _source) + get_filename_component(_targetName ${_source} NAME_WE) + set(_srcList ${_source} fakeserver/fakeserver.cpp) + + kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/../settings.kcfg org.kde.Akonadi.POP3.Settings) + set(pop3settingsinterface_xml ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.POP3.Settings.xml) + kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/../../maildir/maildirresource.kcfg org.kde.Akonadi.Maildir.Settings) + set(maildirsettingsinterface_xml ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Maildir.Settings.xml) + set_source_files_properties(${pop3settingsinterface_xml} PROPERTIES INCLUDE "../metatype.h") + qt4_add_dbus_interface(_srcList ${pop3settingsinterface_xml} pop3settings) + qt4_add_dbus_interface(_srcList ${maildirsettingsinterface_xml} maildirsettings) + # add the dbus interace to every test (easier than adding to particular tests only) + #qt4_add_dbus_interface(_srcList ../org.kde.krss.xml krssinterface) + + kde4_add_executable(${_targetName} TEST ${_srcList}) + target_link_libraries(${_targetName} + ${QT_QTTEST_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_KMIME_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ) + + # based on kde4_add_unit_test + if (WIN32) + get_target_property( _loc ${_targetName} LOCATION ) + set(_executable ${_loc}.bat) + else () + set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_targetName}) + endif () + if (UNIX) + set(_executable ${_executable}.shell) + endif () + + find_program(_testrunner akonaditest) + + if (KDEPIM_RUN_ISOLATED_TESTS) + add_test( ${_targetName} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config.xml ${_executable} ) + endif () +endmacro(add_akonadi_isolated_test) + +add_akonadi_isolated_test(pop3test.cpp) diff --git a/kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.cpp b/kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.cpp new file mode 100644 index 00000000..22010455 --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.cpp @@ -0,0 +1,295 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Thomas McGuire + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// Own +#include "fakeserver.h" + +// Qt +#include +#include +#include + +// KDE +#include + +FakeServerThread::FakeServerThread( QObject *parent ) + : QThread( parent ), + mServer( 0 ) +{ +} + +void FakeServerThread::run() +{ + mServer = new FakeServer(); + + // Run forever, until someone from the outside calls quit() on us and quits the + // event loop + exec(); + + delete mServer; + mServer = 0; +} + +FakeServer *FakeServerThread::server() const +{ + Q_ASSERT( mServer != 0 ); + return mServer; +} + +FakeServer::FakeServer( QObject* parent ) + : QObject( parent ), + mConnections( 0 ), + mProgress( 0 ), + mGotDisconnected( false ) +{ + mTcpServer = new QTcpServer(); + if ( !mTcpServer->listen( QHostAddress( QHostAddress::LocalHost ), 5989 ) ) { + kFatal() << "Unable to start the server"; + } + + connect( mTcpServer, SIGNAL(newConnection()), + this, SLOT(newConnection()) ); +} + +FakeServer::~FakeServer() +{ + if ( mConnections > 0 ) + disconnect( mTcpServerConnection, SIGNAL(readyRead()), + this, SLOT(dataAvailable()) ); + + delete mTcpServer; + mTcpServer = 0; +} + +QByteArray FakeServer::parseDeleteMark( const QByteArray &expectedData, + const QByteArray &dataReceived ) +{ + // Only called from parseResponse(), which is already thread-safe + + const QByteArray deleteMark = QString::fromLatin1( "%DELE%" ).toUtf8(); + if ( expectedData.contains( deleteMark ) ) { + Q_ASSERT( !mAllowedDeletions.isEmpty() ); + for ( int i = 0; i < mAllowedDeletions.size(); i++ ) { + QByteArray substituted = expectedData; + substituted = substituted.replace( deleteMark, mAllowedDeletions[i] ); + if ( substituted == dataReceived ) { + mAllowedDeletions.removeAt( i ); + return substituted; + } + } + qWarning() << "Received:" << dataReceived.data() + << "\nExpected:" << expectedData.data(); + Q_ASSERT_X( false, "FakeServer::parseDeleteMark", "Unable to substitute data!" ); + return QByteArray(); + } + else return expectedData; +} + +QByteArray FakeServer::parseRetrMark( const QByteArray &expectedData, + const QByteArray &dataReceived ) +{ + // Only called from parseResponse(), which is already thread-safe + + const QByteArray retrMark = QString::fromLatin1( "%RETR%" ).toUtf8(); + if ( expectedData.contains( retrMark ) ) { + Q_ASSERT( !mAllowedRetrieves.isEmpty() ); + for ( int i = 0; i < mAllowedRetrieves.size(); i++ ) { + QByteArray substituted = expectedData; + substituted = substituted.replace( retrMark, mAllowedRetrieves[i] ); + if ( substituted == dataReceived ) { + mAllowedRetrieves.removeAt( i ); + return substituted; + } + } + qWarning() << "Received:" << dataReceived.data() + << "\nExpected:" << expectedData.data(); + Q_ASSERT_X( false, "FakeServer::parseRetrMark", "Unable to substitute data!" ); + return QByteArray(); + } + else return expectedData; +} + +QByteArray FakeServer::parseResponse( const QByteArray& expectedData, + const QByteArray& dataReceived ) +{ + // Only called from dataAvailable, which is already thread-safe + + QByteArray result; + result = parseDeleteMark( expectedData, dataReceived ); + if ( result != expectedData ) + return result; + else return parseRetrMark( expectedData,dataReceived ); +} + +/* +// Used only for the debug output in dataAvailable() +static QByteArray removeCRLF( const QByteArray &ba ) +{ + QByteArray returnArray = ba; + returnArray.replace( "\r\n", QByteArray() ); + return returnArray; +} +*/ + +void FakeServer::dataAvailable() +{ + QMutexLocker locker( &mMutex ); + mProgress++; + + // We got data, so we better expect it and have an answer! + Q_ASSERT( !mReadData.isEmpty() ); + Q_ASSERT( !mWriteData.isEmpty() ); + + const QByteArray data = mTcpServerConnection->readAll(); + //qDebug() << "Got data:" << removeCRLF( data ); + const QByteArray expected( mReadData.takeFirst() ); + //qDebug() << "Expected data:" << removeCRLF( expected ); + const QByteArray reallyExpected = parseResponse( expected, data ); + //qDebug() << "Really expected:" << removeCRLF( reallyExpected ); + + Q_ASSERT( data == reallyExpected ); + + QByteArray toWrite = mWriteData.takeFirst(); + //qDebug() << "Going to write data:" << removeCRLF( toWrite ); + const bool allWritten = mTcpServerConnection->write( toWrite ) == toWrite.size(); + Q_ASSERT( allWritten ); + Q_UNUSED( allWritten ); + const bool flushed = mTcpServerConnection->flush(); + Q_ASSERT( flushed ); + Q_UNUSED( flushed ); +} + +void FakeServer::newConnection() +{ + QMutexLocker locker( &mMutex ); + Q_ASSERT( mConnections == 0 ); + mConnections++; + mGotDisconnected = false; + + mTcpServerConnection = mTcpServer->nextPendingConnection(); + mTcpServerConnection->write( QByteArray( "+OK Initech POP3 server ready.\r\n" ) ); + connect( mTcpServerConnection, SIGNAL(readyRead()), this, SLOT(dataAvailable()) ); + connect( mTcpServerConnection, SIGNAL(disconnected()), this, SLOT(slotDisconnected()) ); +} + +void FakeServer::slotDisconnected() +{ + QMutexLocker locker( &mMutex ); + mConnections--; + mGotDisconnected = true; + Q_ASSERT( mConnections == 0 ); + Q_ASSERT( mAllowedDeletions.isEmpty() ); + Q_ASSERT( mAllowedRetrieves.isEmpty() ); + Q_ASSERT( mReadData.isEmpty() ); + Q_ASSERT( mWriteData.isEmpty() ); + emit disconnected(); +} + +void FakeServer::setAllowedDeletions( const QString &deleteIds ) +{ + QMutexLocker locker( &mMutex ); + mAllowedDeletions.clear(); + QStringList ids = deleteIds.split( QLatin1Char(','), QString::SkipEmptyParts ); + foreach( const QString &id, ids ) { + mAllowedDeletions.append( id.toUtf8() ); + } +} + +void FakeServer::setAllowedRetrieves( const QString &retrieveIds ) +{ + QMutexLocker locker( &mMutex ); + mAllowedRetrieves.clear(); + QStringList ids = retrieveIds.split( QLatin1Char(','), QString::SkipEmptyParts ); + foreach( const QString &id, ids ) { + mAllowedRetrieves.append( id.toUtf8() ); + } +} + +void FakeServer::setMails( const QList &mails) +{ + QMutexLocker locker( &mMutex ); + mMails = mails; +} + +void FakeServer::setNextConversation( const QString& conversation, + const QList &exceptions ) +{ + QMutexLocker locker( &mMutex ); + + Q_ASSERT( mReadData.isEmpty() ); + Q_ASSERT( mWriteData.isEmpty() ); + Q_ASSERT( !conversation.isEmpty() ); + + mGotDisconnected = false; + QStringList lines = conversation.split( QLatin1String("\r\n"), QString::SkipEmptyParts ); + Q_ASSERT( lines.first().startsWith( QLatin1String("C:") ) ); + + enum Mode { Client, Server }; + Mode mode = Client; + + const QByteArray mailSizeMarker = QString::fromLatin1( "%MAILSIZE%" ).toLatin1(); + const QByteArray mailMarker = QString::fromLatin1( "%MAIL%" ).toLatin1(); + int sizeIndex = 0; + int mailIndex = 0; + + foreach( const QString &line, lines ) { + + QByteArray lineData( line.toUtf8() ); + + if ( lineData.contains( mailSizeMarker ) ) { + Q_ASSERT( mMails.size() > sizeIndex ); + lineData.replace( mailSizeMarker, + QString::number( mMails[sizeIndex++].size() ).toLatin1() ); + } + if ( lineData.contains( mailMarker ) ) { + while( exceptions.contains( mailIndex + 1 ) ) + mailIndex++; + Q_ASSERT( mMails.size() > mailIndex ); + lineData.replace( mailMarker, mMails[mailIndex++] ); + } + + if ( lineData.startsWith( "S: " ) ) { + mWriteData.append( lineData.mid( 3 ) + "\r\n" ); + mode = Server; + } + else if ( line.startsWith( QLatin1String("C: ") ) ) { + mReadData.append( lineData.mid( 3 ) + "\r\n" ); + mode = Client; + } + else { + switch ( mode ) { + case Server: mWriteData.last() += ( lineData + "\r\n" ); break; + case Client: mReadData.last() += ( lineData + "\r\n" ); break; + } + } + } +} + +int FakeServer::progress() const +{ + QMutexLocker locker( &mMutex ); + return mProgress; +} + +bool FakeServer::gotDisconnected() const +{ + QMutexLocker locker( &mMutex ); + return mGotDisconnected; +} + diff --git a/kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.h b/kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.h new file mode 100644 index 00000000..dc7d099f --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/fakeserver/fakeserver.h @@ -0,0 +1,106 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Thomas McGuire + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#ifndef FAKESERVER_H +#define FAKESERVER_H + +#include +#include +#include +#include +#include + +class FakeServer : public QObject +{ + Q_OBJECT + +public: + FakeServer( QObject* parent = 0 ); + ~FakeServer(); + + void setNextConversation( const QString &conversation, + const QList &exceptions = QList() ); + void setAllowedDeletions( const QString &deleteIds ); + void setAllowedRetrieves( const QString &retrieveIds ); + void setMails( const QList &mails ); + + // This is kind of a hack: The POP3 test needs to know when the POP3 client + // disconnects from the server. Normally, we could just use a QSignalSpy on the + // disconnected() signal, but that is not thread-safe. Therefore this hack with the + // state variable mGotDisconnected + bool gotDisconnected() const; + + // Returns an integer that is incremented each time the POP3 server receives some + // data + int progress() const; + +Q_SIGNALS: + void disconnected(); + +private Q_SLOTS: + + void newConnection(); + void dataAvailable(); + void slotDisconnected(); + +private: + + QByteArray parseDeleteMark( const QByteArray& expectedData, const QByteArray& dataReceived ); + QByteArray parseRetrMark( const QByteArray& expectedData, const QByteArray& dataReceived ); + QByteArray parseResponse( const QByteArray& expectedData, const QByteArray& dataReceived ); + + QList mReadData; + QList mWriteData; + QList mAllowedDeletions; + QList mAllowedRetrieves; + QList mMails; + QTcpServer *mTcpServer; + QTcpSocket* mTcpServerConnection; + int mConnections; + int mProgress; + bool mGotDisconnected; + + // We use one big mutex to protect everything + // There shouldn't be deadlocks, as there are only 2 places where the functions + // are called: From the KTcpSocket signals, which are triggered by the POP3 ioslave, + // and from the actual test. + mutable QMutex mMutex; +}; + +class FakeServerThread : public QThread +{ + Q_OBJECT + +public: + + explicit FakeServerThread( QObject *parent ); + virtual void run(); + + // Returns the FakeServer use. Be careful when using this and make sure + // the methods you use are actually thread-safe!! + // This should however be the case because the FakeServer uses one big mutex + // to protect everything. + FakeServer *server() const; + +private: + + FakeServer *mServer; +}; + +#endif + diff --git a/kdepim-runtime/resources/pop3/tests/pop3test.cpp b/kdepim-runtime/resources/pop3/tests/pop3test.cpp new file mode 100644 index 00000000..f6b0f2ab --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/pop3test.cpp @@ -0,0 +1,914 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "pop3test.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +QTEST_AKONADIMAIN( Pop3Test, NoGUI ) + +using namespace Akonadi; + +void Pop3Test::initTestCase() +{ + QVERIFY( Akonadi::Control::start() ); + + // switch all resources offline to reduce interference from them + foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) + agent.setIsOnline( false ); + + /* + qDebug() << "==========================================================="; + qDebug() << "============ Stopping for debugging ======================="; + qDebug() << "==========================================================="; + kill( qApp->applicationPid(), SIGSTOP ); + */ + + // + // Create the maildir and pop3 resources + // + AgentType maildirType = AgentManager::self()->type( QLatin1String("akonadi_maildir_resource") ); + AgentInstanceCreateJob *agentCreateJob = new AgentInstanceCreateJob( maildirType ); + QVERIFY( agentCreateJob->exec() ); + mMaildirIdentifier = agentCreateJob->instance().identifier(); + + AgentType popType = AgentManager::self()->type( QLatin1String("akonadi_pop3_resource") ); + agentCreateJob = new AgentInstanceCreateJob( popType ); + QVERIFY( agentCreateJob->exec() ); + mPop3Identifier = agentCreateJob->instance().identifier(); + + // + // Configure the maildir resource + // + QString maildirRootPath = KGlobal::dirs()->saveLocation( "data", QLatin1String("tester"), false ) + QLatin1String("maildirtest"); + mMaildirPath = maildirRootPath + QLatin1String("/new"); + mMaildirSettingsInterface = new OrgKdeAkonadiMaildirSettingsInterface( + QLatin1String("org.freedesktop.Akonadi.Resource." )+ mMaildirIdentifier, + QLatin1String("/Settings"), QDBusConnection::sessionBus(), this ); + QDBusReply setPathReply = mMaildirSettingsInterface->setPath( maildirRootPath ); + QVERIFY( setPathReply.isValid() ); + AgentManager::self()->instance( mMaildirIdentifier ).reconfigure(); + QDBusReply getPathReply = mMaildirSettingsInterface->path(); + QCOMPARE( getPathReply.value(), maildirRootPath ); + AgentManager::self()->instance( mMaildirIdentifier ).synchronize(); + + // + // Find the root maildir collection + // + bool found = false; + QTime time; + time.start(); + while ( !found ) { + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); + QVERIFY( job->exec() ); + Collection::List collections = job->collections(); + foreach( const Collection &col, collections ) { + if ( col.resource() == AgentManager::self()->instance( mMaildirIdentifier ).identifier() ) { + mMaildirCollection = col; + found = true; + break; + } + } + + QVERIFY( time.elapsed() < 10 * 1000 ); // maildir should not need more than 10 secs to sync + } + + // + // Start the fake POP3 server + // + mFakeServerThread = new FakeServerThread( this ); + mFakeServerThread->start(); + QTest::qWait( 100 ); + QVERIFY( mFakeServerThread->server() != 0 ); + + // + // Configure the pop3 resource + // + mPOP3SettingsInterface = new OrgKdeAkonadiPOP3SettingsInterface( + QLatin1String("org.freedesktop.Akonadi.Resource.") + mPop3Identifier, + QLatin1String("/Settings"), QDBusConnection::sessionBus(), this ); + + QDBusReply reply0 = mPOP3SettingsInterface->port(); + QVERIFY( reply0.isValid() ); + QVERIFY( reply0.value() == 110 ); + + mPOP3SettingsInterface->setPort( 5989 ); + AgentManager::self()->instance( mPop3Identifier ).reconfigure(); + QDBusReply reply = mPOP3SettingsInterface->port(); + QVERIFY( reply.isValid() ); + QVERIFY( reply.value() == 5989 ); + + mPOP3SettingsInterface->setHost( QLatin1String("localhost") ); + AgentManager::self()->instance( mPop3Identifier ).reconfigure(); + QDBusReply reply2 = mPOP3SettingsInterface->host(); + QVERIFY( reply2.isValid() ); + QVERIFY( reply2.value() == QLatin1String("localhost") ); + mPOP3SettingsInterface->setLogin( QLatin1String("HansWurst") ); + AgentManager::self()->instance( mPop3Identifier ).reconfigure(); + QDBusReply reply3 = mPOP3SettingsInterface->login(); + QVERIFY( reply3.isValid() ); + QVERIFY( reply3.value() == QLatin1String("HansWurst") ); + + mPOP3SettingsInterface->setUnitTestPassword( QLatin1String("Geheim") ); + AgentManager::self()->instance( mPop3Identifier ).reconfigure(); + QDBusReply reply4 = mPOP3SettingsInterface->unitTestPassword(); + QVERIFY( reply4.isValid() ); + QVERIFY( reply4.value() == QLatin1String("Geheim") ); + + mPOP3SettingsInterface->setTargetCollection( mMaildirCollection.id() ); + AgentManager::self()->instance( mPop3Identifier ).reconfigure(); + QDBusReply reply5 = mPOP3SettingsInterface->targetCollection(); + QVERIFY( reply5.isValid() ); + QVERIFY( reply5.value() == mMaildirCollection.id() ); +} + +void Pop3Test::cleanupTestCase() +{ + // Post the "quit" event to the thread's event loop, then wait until the thread + // is finished (it finishes when the event loop finishes) + QMetaObject::invokeMethod( mFakeServerThread, "quit", Qt::QueuedConnection ); + if ( !mFakeServerThread->wait( 10000 ) ) + qWarning() << "The fake server thread has not yet finished, what is wrong!?"; +} + +static const QByteArray simpleMail1 = + "From: \"Bill Lumbergh\" \r\n" + "To: \"Peter Gibbons\" \r\n" + "Subject: TPS Reports - New Cover Sheets\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: text/plain\r\n" + "Date: Mon, 23 Mar 2009 18:04:05 +0300\r\n" + "\r\n" + "Hi, Peter. What's happening? We need to talk about your TPS reports.\r\n"; + +static const QByteArray simpleMail2 = + "From: \"Amy McCorkell\" \r\n" + "To: gov.palin@yaho.com\r\n" + "Subject: HI SARAH\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: text/plain\r\n" + "Date: Mon, 23 Mar 2009 18:04:05 +0300\r\n" + "\r\n" + "Hey Sarah,\r\n" + "bla bla bla bla bla\r\n"; + +static const QByteArray simpleMail3 = + "From: chunkylover53@aol.com\r\n" + "To: tylerdurden@paperstreetsoapcompany.com\r\n" + "Subject: ILOVEYOU\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: text/plain\r\n" + "Date: Mon, 23 Mar 2009 18:04:05 +0300\r\n" + "\r\n" + "kindly check the attached LOVELETTER coming from me.\r\n"; + +static const QByteArray simpleMail4 = + "From: karl@aol.com\r\n" + "To: lenny@aol.com\r\n" + "Subject: Who took the donuts?\r\n" + "\r\n" + "Hi Lenny, do you know who took all the donuts?\r\n"; + +static const QByteArray simpleMail5 = + "From: foo@bar.com\r\n" + "To: bar@foo.com\r\n" + "Subject: Hello\r\n" + "\r\n" + "Hello World!!\r\n"; + +void Pop3Test::cleanupMaildir( Akonadi::Item::List items ) +{ + // Delete all mails so the maildir is clean for the next test + if ( !items.isEmpty() ) { + ItemDeleteJob *job = new ItemDeleteJob( items ); + QVERIFY( job->exec() ); + } + + QTime time; + time.start(); + int lastCount = -1; + forever { + qApp->processEvents(); + QTest::qWait( 500 ); + QDir maildir( mMaildirPath ); + maildir.refresh(); + int curCount = maildir.entryList( QDir::Files | QDir::NoDotAndDotDot ).count(); + + // Restart the timer when a mail arrives, as it shows that the maildir resource is + // still alive and kicking. + if ( curCount != lastCount ) { + time.restart(); + lastCount = curCount; + } + + if ( curCount == 0 ) + break; + + QVERIFY( time.elapsed() < 60000 || time.elapsed() > 80000000 ); + } +} + +void Pop3Test::checkMailsInMaildir( const QList &mails ) +{ + // Now, test that all mails actually ended up in the maildir. Since the maildir resource + // might be slower, give it a timeout so it can write the files to disk + QTime time; + time.start(); + int lastCount = -1; + forever { + qApp->processEvents(); + QTest::qWait( 500 ); + QDir maildir( mMaildirPath ); + maildir.refresh(); + int curCount = static_cast( maildir.entryList( QDir::Files | QDir::NoDotAndDotDot ).count() ); + + // Restart the timer when a mail arrives, as it shows that the maildir resource is + // still alive and kicking. + if ( curCount != lastCount ) { + time.start(); + lastCount = curCount; + } + + if ( curCount == mails.count() ) { + break; + } + QVERIFY( static_cast( maildir.entryList( QDir::NoDotAndDotDot ).count() ) <= mails.count() ); + QVERIFY( time.elapsed() < 60000 || time.elapsed() > 80000000 ); + } + + // TODO: check file contents as well or is this overkill? +} + +Akonadi::Item::List Pop3Test::checkMailsOnAkonadiServer( const QList &mails ) +{ + // The fake server got disconnected, which means the pop3 resource has entered the QUIT + // stage. That means all messages should be on the server now, so test that. + ItemFetchJob *job = new ItemFetchJob( mMaildirCollection ); + job->fetchScope().fetchFullPayload(); + Q_ASSERT( job->exec() ); + Item::List items = job->items(); + Q_ASSERT( mails.size() == items.size() ); + + QSet ourMailBodies; + QSet itemMailBodies; + + foreach( const Item &item, items ) { + KMime::Message::Ptr itemMail = item.payload(); + QByteArray itemMailBody = itemMail->body(); + + // For some reason, the body in the maildir has one additional newline. + // Get rid of this so we can compare them. + // FIXME: is this a bug? Find out where the newline comes from! + itemMailBody.chop( 1 ); + itemMailBodies.insert( itemMailBody ); + } + + foreach( const QByteArray &mail, mails ) { + KMime::Message::Ptr ourMail( new KMime::Message() ); + ourMail->setContent( KMime::CRLFtoLF( mail ) ); + ourMail->parse(); + QByteArray ourMailBody = ourMail->body(); + ourMailBodies.insert( ourMailBody ); + } + + Q_ASSERT( ourMailBodies == itemMailBodies ); + return items; +} + +void Pop3Test::syncAndWaitForFinish() +{ + AgentManager::self()->instance( mPop3Identifier ).synchronize(); + + // The pop3 resource, ioslave and the fakeserver are all in different processes or threads. + // We simply wait until the FakeServer got disconnected or until a timeout. + // Since POP3 fetching can take longer, we reset the timeout timer when the FakeServer + // does some processing. + QTime time; + time.start(); + int lastProgress = -1; + forever { + qApp->processEvents(); + + // Finish correctly when the connection got closed + if ( mFakeServerThread->server()->gotDisconnected() ) { + break; + } + + // Reset the timeout when the server is working + const int newProgress = mFakeServerThread->server()->progress(); + if ( newProgress != lastProgress ) { + time.restart(); + lastProgress = newProgress; + } + + // Assert when nothing happens for a certain timeout, that indicates something went + // wrong and is stucked somewhere + if ( time.elapsed() >= 60000 ) { + Q_ASSERT_X( false, "poptest", "FakeServer timed out." ); + break; + } + } +} + +QString Pop3Test::loginSequence() const +{ + return + QLatin1String("C: USER HansWurst\r\n" + "S: +OK May I have your password, please?\r\n" + "C: PASS Geheim\r\n" + "S: +OK Mailbox locked and ready\r\n"); +} + +QString Pop3Test::retrieveSequence( const QList &mails, + const QList &exceptions ) const +{ + QString result; + for( int i = 1; i <= mails.size(); i++ ) { + if ( !exceptions.contains( i ) ) { + result += QLatin1String( + "C: RETR %RETR%\r\n" + "S: +OK Here is your spam\r\n" + "%MAIL%\r\n" + ".\r\n" ); + } + } + return result; +} + +QString Pop3Test::deleteSequence( int numToDelete ) const +{ + QString result; + for ( int i = 0; i < numToDelete; i++ ) { + result += + QLatin1String("C: DELE %DELE%\r\n" + "S: +OK message sent to /dev/null\r\n"); + } + return result; +} + +QString Pop3Test::quitSequence() const +{ + return + QLatin1String("C: QUIT\r\n" + "S: +OK Have a nice day.\r\n"); +} + +QString Pop3Test::listSequence( const QList &mails ) const +{ + QString result = QLatin1String("C: LIST\r\n" + "S: +OK You got new spam\r\n"); + for ( int i = 1; i <= mails.size(); i++ ) { + result += QString::fromLatin1( "%1 %MAILSIZE%\r\n" ).arg( i ); + } + result += QLatin1String(".\r\n"); + return result; +} + +QString Pop3Test::uidSequence( const QStringList& uids ) const +{ + QString result = QLatin1String("C: UIDL\r\n" + "S: +OK\r\n"); + for ( int i = 1; i <= uids.size(); i++ ) { + result += QString::fromLatin1( "%1 %2\r\n" ).arg( i ).arg( uids[i - 1] ); + } + result += QLatin1String(".\r\n"); + return result; +} + +static bool sortedEqual( const QStringList &list1, const QStringList &list2 ) +{ + QStringList sorted1 = list1; + sorted1.sort(); + QStringList sorted2 = list2; + sorted2.sort(); + + return qEqual( sorted1.begin(), sorted1.end(), sorted2.begin() ); +} + +void Pop3Test::lowerTimeOfSeenMail( const QString &uidOfMail, int secondsToLower ) +{ + const int index = mPOP3SettingsInterface->seenUidList().value().indexOf( uidOfMail ); + QList seenTimeList = mPOP3SettingsInterface->seenUidTimeList().value(); + int msgTime = seenTimeList.at( index ); + msgTime -= secondsToLower; + seenTimeList.replace( index, msgTime ); + mPOP3SettingsInterface->setSeenUidTimeList( seenTimeList ); +} + +void Pop3Test::testSimpleDownload() +{ + QList mails; + mails << simpleMail1 << simpleMail2 << simpleMail3; + QStringList uids; + uids << QLatin1String("UID1") << QLatin1String("UID2") << QLatin1String("UID3"); + mFakeServerThread->server()->setAllowedDeletions(QLatin1String("1,2,3")); + mFakeServerThread->server()->setAllowedRetrieves(QLatin1String("1,2,3")); + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + deleteSequence( mails.size() ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + cleanupMaildir( items ); +} + +void Pop3Test::testBigFetch() +{ + QList mails; + QStringList uids; + QString allowedRetrs; + for( int i = 0; i < 1000; i++ ) { + QByteArray newMail = simpleMail1; + newMail.append( QString::number( i + 1 ).toLatin1() ); + mails << newMail; + uids << QString::fromLatin1( "UID%1" ).arg( i + 1 ); + allowedRetrs += QString::number( i + 1 ) + QLatin1Char(','); + } + allowedRetrs.chop( 1 ); + + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves( allowedRetrs ); + mFakeServerThread->server()->setAllowedDeletions( allowedRetrs ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + deleteSequence( mails.size() ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + cleanupMaildir( items ); +} + +void Pop3Test::testSeenUIDCleanup() +{ + // + // First, fetch 3 normal mails, but leave them on the server. + // + mPOP3SettingsInterface->setLeaveOnServer( true ); + QList mails; + mails << simpleMail1 << simpleMail2 << simpleMail3; + QStringList uids; + uids << QLatin1String("UID1") << QLatin1String("UID2") << QLatin1String("UID3"); + mFakeServerThread->server()->setAllowedDeletions( QString() ); + mFakeServerThread->server()->setAllowedRetrieves( QLatin1String("1,2,3") ); + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + cleanupMaildir( items ); + + QVERIFY( sortedEqual( uids, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + // + // Now, pretend that the messages were removed from the server in the meantime + // by having no mails on the fake server. + // + mFakeServerThread->server()->setMails( QList() ); + mFakeServerThread->server()->setAllowedRetrieves( QString() ); + mFakeServerThread->server()->setAllowedDeletions( QString() ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( QList() ) + + uidSequence( QStringList() ) + + quitSequence() + ); + syncAndWaitForFinish(); + items = checkMailsOnAkonadiServer( QList() ); + checkMailsInMaildir( QList() ); + cleanupMaildir( items ); + + QVERIFY( mPOP3SettingsInterface->seenUidList().value().isEmpty() ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + mPOP3SettingsInterface->setLeaveOnServer(false ); +} + +void Pop3Test::testSimpleLeaveOnServer() +{ + mPOP3SettingsInterface->setLeaveOnServer( true ); + + QList mails; + mails << simpleMail1 << simpleMail2 << simpleMail3; + QStringList uids; + uids << QLatin1String("UID1") << QLatin1String("UID2") << QLatin1String("UID3"); + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves(QLatin1String("1,2,3")); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + + // The resource should have saved the UIDs of the seen messages + QVERIFY( sortedEqual( uids, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + foreach( int seenTime, mPOP3SettingsInterface->seenUidTimeList().value() ) { + // Those message were just downloaded from the fake server, so they are at maximum + // 10 minutes old (for slooooow running tests) + QVERIFY( seenTime >= time( 0 ) - 10 * 60 ); + } + + // + // OK, next mail check: We have to check that the old seen messages are not downloaded again, + // only new mails. + // + QList newMails( mails ); + newMails << simpleMail4; + QStringList newUids( uids ); + newUids << QLatin1String("newUID"); + QList idsToNotDownload; + idsToNotDownload << 1 << 2 << 3; + mFakeServerThread->server()->setMails( newMails ); + mFakeServerThread->server()->setAllowedRetrieves(QLatin1String("4")); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( newMails ) + + uidSequence( newUids ) + + retrieveSequence( newMails, idsToNotDownload ) + + quitSequence(), + idsToNotDownload + ); + + syncAndWaitForFinish(); + items = checkMailsOnAkonadiServer( newMails ); + checkMailsInMaildir( newMails ); + QVERIFY( sortedEqual( newUids, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + // + // Ok, next test: When turning off leaving on the server, all mails should be deleted, but + // none downloaded. + // + mPOP3SettingsInterface->setLeaveOnServer( false ); + + mFakeServerThread->server()->setAllowedDeletions(QLatin1String( "1,2,3,4") ); + mFakeServerThread->server()->setAllowedRetrieves( QString() ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( newMails ) + + uidSequence( newUids ) + + deleteSequence( newMails.size() ) + + quitSequence() + ); + + syncAndWaitForFinish(); + items = checkMailsOnAkonadiServer( newMails ); + checkMailsInMaildir( newMails ); + cleanupMaildir( items ); + QVERIFY( mPOP3SettingsInterface->seenUidList().value().isEmpty() ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); +} + +void Pop3Test::testTimeBasedLeaveRule() +{ + mPOP3SettingsInterface->setLeaveOnServer( true ); + mPOP3SettingsInterface->setLeaveOnServerDays( 2 ); + + // + // First download 3 mails and leave them on the server + // + QList mails; + mails << simpleMail1 << simpleMail2 << simpleMail3; + QStringList uids; + uids << QLatin1String("UID1") << QLatin1String("UID2") << QLatin1String("UID3"); + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves(QLatin1String("1,2,3")); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + + QVERIFY( sortedEqual( uids, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + // + // Now, modify the seenUidTimeList on the server for UID2 to pretend it + // was downloaded 3 days ago, which means it should be deleted. + // + lowerTimeOfSeenMail( QLatin1String("UID2"), 60 * 60 * 24 * 3 ); + + QList idsToNotDownload; + idsToNotDownload << 1 << 3; + mFakeServerThread->server()->setAllowedDeletions( QLatin1String("2") ); + mFakeServerThread->server()->setAllowedRetrieves( QString() ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + deleteSequence( 1 ) + + quitSequence(), + idsToNotDownload + ); + syncAndWaitForFinish(); + items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + cleanupMaildir( items ); + + uids.removeAll( QLatin1String("UID2") ); + QVERIFY( sortedEqual( uids, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + foreach( int seenTime, mPOP3SettingsInterface->seenUidTimeList().value() ) { + QVERIFY( seenTime >= time( 0 ) - 10 * 60 ); + } + + mPOP3SettingsInterface->setLeaveOnServer( false ); + mPOP3SettingsInterface->setLeaveOnServerDays( 0 ); + mPOP3SettingsInterface->setSeenUidTimeList( QList() ); + mPOP3SettingsInterface->setSeenUidList( QStringList() ); +} + +void Pop3Test::testCountBasedLeaveRule() +{ + mPOP3SettingsInterface->setLeaveOnServer( true ); + mPOP3SettingsInterface->setLeaveOnServerCount( 3 ); + + // + // First download 3 mails and leave them on the server + // + QList mails; + mails << simpleMail1 << simpleMail2 << simpleMail3; + QStringList uids; + uids << QLatin1String("UID1") << QLatin1String("UID2") << QLatin1String("UID3"); + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves(QLatin1String("1,2,3")); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + quitSequence() + ); + + syncAndWaitForFinish(); + checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + + // Make the 3 just downloaded mails appear older than they are + lowerTimeOfSeenMail( QLatin1String("UID1"), 60 * 60 * 24 * 2 ); + lowerTimeOfSeenMail( QLatin1String("UID2"), 60 * 60 * 24 * 1 ); + lowerTimeOfSeenMail( QLatin1String("UID3"), 60 * 60 * 24 * 3 ); + + // + // Now, download 2 more mails. Since only 3 mails are allowed to be left + // on the server, the oldest ones, UID1 and UID3, should be deleted + // + QList moreMails; + moreMails << simpleMail4 << simpleMail5; + QStringList moreUids; + moreUids << QLatin1String("UID4") << QLatin1String("UID5"); + mFakeServerThread->server()->setMails( mails + moreMails ); + mFakeServerThread->server()->setAllowedRetrieves( QLatin1String("4,5") ); + mFakeServerThread->server()->setAllowedDeletions( QLatin1String("1,3") ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails + moreMails ) + + uidSequence( uids + moreUids ) + + retrieveSequence( moreMails ) + + deleteSequence( 2 ) + + quitSequence(), QList() << 1 << 2 << 3 + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails + moreMails ); + checkMailsInMaildir( mails + moreMails ); + cleanupMaildir( items ); + + QStringList uidsLeft; + uidsLeft << QLatin1String("UID2") << QLatin1String("UID4") << QLatin1String("UID5"); + + QVERIFY( sortedEqual( uidsLeft, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + mPOP3SettingsInterface->setLeaveOnServer( false ); + mPOP3SettingsInterface->setLeaveOnServerCount( 0 ); + mPOP3SettingsInterface->setSeenUidTimeList( QList() ); + mPOP3SettingsInterface->setSeenUidList( QStringList() ); +} + +void Pop3Test::testSizeBasedLeaveRule() +{ + mPOP3SettingsInterface->setLeaveOnServer( true ); + mPOP3SettingsInterface->setLeaveOnServerSize( 10 ); // 10 MB + + // + // First download 3 mails and leave them on the server. + // + QList mails; + mails << simpleMail1 << simpleMail2 << simpleMail3; + QStringList uids; + uids << QLatin1String("UID1") << QLatin1String("UID2") << QLatin1String("UID3"); + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves(QLatin1String("1,2,3")); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + quitSequence() + ); + + syncAndWaitForFinish(); + checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + + // Make the 3 just downloaded mails appear older than they are + lowerTimeOfSeenMail( QLatin1String("UID1"), 60 * 60 * 24 * 2 ); + lowerTimeOfSeenMail( QLatin1String("UID2"), 60 * 60 * 24 * 1 ); + lowerTimeOfSeenMail( QLatin1String("UID3"), 60 * 60 * 24 * 3 ); + + // Now, do another mail check, but with no new mails on the server. + // Instead we let the server pretend that the mails have a fake size, + // each 7 MB. That means the two oldest get deleted, because the total + // mail size is over 10 MB with them. + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves( QString() ); + mFakeServerThread->server()->setAllowedDeletions( QLatin1String("1,3") ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + QLatin1String("C: LIST\r\n" + "S: +OK You got new spam\r\n" + "1 7340032\r\n" + "2 7340032\r\n" + "3 7340032\r\n" + ".\r\n") + + uidSequence( uids ) + + deleteSequence( 2 ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + cleanupMaildir( items ); + + QStringList uidsLeft; + uidsLeft << QLatin1String("UID2"); + + QVERIFY( sortedEqual( uidsLeft, mPOP3SettingsInterface->seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + mPOP3SettingsInterface->setLeaveOnServer( false ); + mPOP3SettingsInterface->setLeaveOnServerCount( 0 ); + mPOP3SettingsInterface->setLeaveOnServerSize( 0 ); + mPOP3SettingsInterface->setSeenUidTimeList( QList() ); + mPOP3SettingsInterface->setSeenUidList( QStringList() ); +} + +void Pop3Test::testMixedLeaveRules() +{ + mPOP3SettingsInterface->setLeaveOnServer( true ); + // + // Generate 10 mails + // + QList mails; + QStringList uids; + QString allowedRetrs; + for( int i = 0; i < 10; i++ ) { + QByteArray newMail = simpleMail1; + newMail.append( QString::number( i + 1 ).toLatin1() ); + mails << newMail; + uids << QString::fromLatin1( "UID%1" ).arg( i + 1 ); + allowedRetrs += QString::number( i + 1 ) + QLatin1Char(','); + } + allowedRetrs.chop( 1 ); + + // + // Now, download these 10 mails + // + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves( allowedRetrs ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + listSequence( mails ) + + uidSequence( uids ) + + retrieveSequence( mails ) + + quitSequence() + ); + + syncAndWaitForFinish(); + checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + + // Fake the time of the messages, UID1 is one day old, UID2 is two days old, etc + for ( int i = 1; i <= 10; i++ ) + lowerTimeOfSeenMail( QString::fromLatin1( "UID%1" ).arg( i ), 60 * 60 * 24 * i ); + + mPOP3SettingsInterface->setLeaveOnServer( true ); + mPOP3SettingsInterface->setLeaveOnServerSize( 25 ); // UID 4, 5 oldest here + mPOP3SettingsInterface->setLeaveOnServerCount( 5 ); // UID 6, 7 oldest here + mPOP3SettingsInterface->setLeaveOnServerDays( 7 ); // UID 8, 9 and 10 too old + + // Ok, now we do another mail check that only deletes stuff from the server. + // Above are the UIDs that should be deleted. + mFakeServerThread->server()->setMails( mails ); + mFakeServerThread->server()->setAllowedRetrieves( QString() ); + mFakeServerThread->server()->setAllowedDeletions( QLatin1String("4,5,6,7,8,9,10") ); + mFakeServerThread->server()->setNextConversation( + loginSequence() + + QLatin1String("C: LIST\r\n" + "S: +OK You got new spam\r\n" + "1 7340032\r\n" + "2 7340032\r\n" + "3 7340032\r\n" + "4 7340032\r\n" + "5 7340032\r\n" + "6 7340032\r\n" + "7 7340032\r\n" + "8 7340032\r\n" + "9 7340032\r\n" + "10 7340032\r\n" + ".\r\n") + + uidSequence( uids ) + + deleteSequence( 7 ) + + quitSequence() + ); + + syncAndWaitForFinish(); + Akonadi::Item::List items = checkMailsOnAkonadiServer( mails ); + checkMailsInMaildir( mails ); + cleanupMaildir( items ); + + QStringList uidsLeft; + uidsLeft << QLatin1String("UID1") << QLatin1String("UID2")<seenUidList().value() ) ); + QVERIFY( mPOP3SettingsInterface->seenUidTimeList().value().size() == + mPOP3SettingsInterface->seenUidList().value().size() ); + + mPOP3SettingsInterface->setLeaveOnServer( false ); + mPOP3SettingsInterface->setLeaveOnServerCount( 0 ); + mPOP3SettingsInterface->setLeaveOnServerSize( 0 ); + mPOP3SettingsInterface->setSeenUidTimeList( QList() ); + mPOP3SettingsInterface->setSeenUidList( QStringList() ); +} diff --git a/kdepim-runtime/resources/pop3/tests/pop3test.h b/kdepim-runtime/resources/pop3/tests/pop3test.h new file mode 100644 index 00000000..e25d6f1f --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/pop3test.h @@ -0,0 +1,73 @@ +/* Copyright 2009 Thomas McGuire + + 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 ) version 3 or, at the discretion of KDE e.V. + ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 POP3TEST_H +#define POP3TEST_H + +#include "fakeserver/fakeserver.h" +#include "pop3settings.h" +#include "maildirsettings.h" + +#include +#include + +#include +#include + +class Pop3Test : public QObject +{ + Q_OBJECT + + void replymMaildirSettingsInterface ( QString arg1 ); + private slots: + void initTestCase(); + void cleanupTestCase(); + void testSimpleDownload(); + void testSimpleLeaveOnServer(); + void testBigFetch(); + void testSeenUIDCleanup(); + void testTimeBasedLeaveRule(); + void testCountBasedLeaveRule(); + void testSizeBasedLeaveRule(); + void testMixedLeaveRules(); + + private: + void lowerTimeOfSeenMail( const QString &uidOfMail, int secondsToLower ); + void cleanupMaildir( Akonadi::Item::List items ); + void checkMailsInMaildir( const QList< QByteArray >& mails ); + Akonadi::Item::List checkMailsOnAkonadiServer( const QList &mails ); + void syncAndWaitForFinish(); + QString loginSequence() const; + QString retrieveSequence( const QList< QByteArray >& mails, + const QList &exceptions = QList() ) const; + QString deleteSequence( int numToDelete ) const; + QString quitSequence() const; + QString listSequence( const QList &mails ) const; + QString uidSequence( const QStringList &uids ) const; + + FakeServerThread *mFakeServerThread; + + OrgKdeAkonadiPOP3SettingsInterface *mPOP3SettingsInterface; + OrgKdeAkonadiMaildirSettingsInterface *mMaildirSettingsInterface; + Akonadi::Collection mMaildirCollection; + QString mPop3Identifier; + QString mMaildirIdentifier; + QString mMaildirPath; +}; + +#endif diff --git a/kdepim-runtime/resources/pop3/tests/unittestenv/config.xml b/kdepim-runtime/resources/pop3/tests/unittestenv/config.xml new file mode 100644 index 00000000..fa0eafe8 --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/unittestenv/config.xml @@ -0,0 +1,6 @@ + + kdehome + xdgconfig + xdglocal + true + diff --git a/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/apps/.keep b/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/apps/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/apps/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc b/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc new file mode 100644 index 00000000..1cac492a --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc @@ -0,0 +1,3 @@ +[ProcessedDefaults] +defaultaddressbook=done +defaultcalendar=done diff --git a/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/kdebugrc b/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/kdebugrc new file mode 100755 index 00000000..38181163 --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/unittestenv/kdehome/share/config/kdebugrc @@ -0,0 +1,103 @@ +[0] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[kbuildsycoca4] +AbortFatal=true +ErrorOutput=0 +FatalOutput=0 +InfoOutput=0 +WarnOutput=0 + +[kdecore (services)] +AbortFatal=true +ErrorOutput=0 +FatalOutput=0 +InfoOutput=0 +WarnOutput=0 + +[264] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[5250] +InfoOutput=2 + +[7009] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7011] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7012] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7014] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=0 +FatalFilename[$e]=kdebug.dbg +FatalOutput=0 +InfoFilename[$e]=kdebug.dbg +InfoOutput=0 +WarnFilename[$e]=kdebug.dbg +WarnOutput=0 + +[7021] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7105] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 diff --git a/kdepim-runtime/resources/pop3/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc b/kdepim-runtime/resources/pop3/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc new file mode 100644 index 00000000..7f738ce2 --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/unittestenv/xdgconfig/akonadi/akonadiserverrc @@ -0,0 +1,4 @@ +[%General] + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/resources/pop3/tests/unittestenv/xdglocal/.keep b/kdepim-runtime/resources/pop3/tests/unittestenv/xdglocal/.keep new file mode 100644 index 00000000..2f1d07a9 --- /dev/null +++ b/kdepim-runtime/resources/pop3/tests/unittestenv/xdglocal/.keep @@ -0,0 +1 @@ +force git to include this file diff --git a/kdepim-runtime/resources/pop3/wizard/CMakeLists.txt b/kdepim-runtime/resources/pop3/wizard/CMakeLists.txt new file mode 100644 index 00000000..638e2b5d --- /dev/null +++ b/kdepim-runtime/resources/pop3/wizard/CMakeLists.txt @@ -0,0 +1,2 @@ + +install ( FILES pop3wizard.desktop pop3wizard.es pop3wizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/pop3 ) diff --git a/kdepim-runtime/resources/pop3/wizard/Messages.sh b/kdepim-runtime/resources/pop3/wizard/Messages.sh new file mode 100644 index 00000000..2689f1fd --- /dev/null +++ b/kdepim-runtime/resources/pop3/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_pop3.pot +$XGETTEXT -kqsTr *.es -j -o $podir/accountwizard_pop3.pot diff --git a/kdepim-runtime/resources/pop3/wizard/pop3wizard.desktop b/kdepim-runtime/resources/pop3/wizard/pop3wizard.desktop new file mode 100644 index 00000000..10b81ab0 --- /dev/null +++ b/kdepim-runtime/resources/pop3/wizard/pop3wizard.desktop @@ -0,0 +1,105 @@ +[Desktop Entry] +Name=Pop3 +Name[bg]=Pop3 +Name[bs]=Pop3 +Name[ca]=Pop3 +Name[ca@valencia]=Pop3 +Name[cs]=Pop3 +Name[da]=POP3 +Name[de]=POP3 +Name[el]=Pop3 +Name[en_GB]=Pop3 +Name[es]=Pop3 +Name[et]=POP3 +Name[fi]=POP3 +Name[fr]=Pop3 +Name[ga]=POP3 +Name[gl]=Pop3 +Name[hr]=Pop3 +Name[hu]=POP3 +Name[ia]=POP3 +Name[it]=POP3 +Name[ja]=Pop3 +Name[kk]=Pop3 +Name[km]=Pop3 +Name[ko]=Pop3 +Name[lt]=Pop3 +Name[lv]=Pop3 +Name[nb]=Pop3 +Name[nds]=POP3 +Name[nl]=POP3 +Name[nn]=POP3 +Name[pa]=Pop3 +Name[pl]=Pop3 +Name[pt]=POP3 +Name[pt_BR]=POP3 +Name[ro]=Pop3 +Name[ru]=POP3 +Name[sk]=Pop3 +Name[sl]=POP3 +Name[sr]=ПОП3 +Name[sr@ijekavian]=ПОП3 +Name[sr@ijekavianlatin]=POP3 +Name[sr@latin]=POP3 +Name[sv]=Pop3 +Name[tr]=Pop3 +Name[uk]=POP3 +Name[x-test]=xxPop3xx +Name[zh_CN]=Pop3 +Name[zh_TW]=Pop3 +Icon=message-rfc822 +Comment=Pop3 account +Comment[bg]=Сметка POP3 +Comment[bs]=Pop3 nalog +Comment[ca]=Compte Pop3 +Comment[ca@valencia]=Compte Pop3 +Comment[cs]=Pop3 úÄet +Comment[da]=POP3-konto +Comment[de]=POP3-Postfach +Comment[el]=ΛογαÏιασμός pop3 +Comment[en_GB]=Pop3 account +Comment[es]=Cuenta Pop3 +Comment[et]=POP3 konto +Comment[fi]=POP3-tili +Comment[fr]=Compte POP3 +Comment[ga]=Cuntas POP3 +Comment[gl]=Conta de Pop3 +Comment[hr]=Pop3 raÄun +Comment[hu]=POP3 azonosító +Comment[ia]=Conto POP3 +Comment[it]=Account POP3 +Comment[ja]=Pop 3 アカウント +Comment[kk]=Pop3 тіркелгіÑÑ– +Comment[km]=គនណី Pop3 +Comment[ko]=POP3 계정 +Comment[lt]=Pop3 paskyra +Comment[lv]=Pop3 konts +Comment[nb]=Pop3 konto +Comment[nds]=POP3-Konto +Comment[nl]=POP3-account +Comment[nn]=POP3-konto +Comment[pa]=Popà©© ਅਕਾਊਂਟ +Comment[pl]=Konto Pop3 +Comment[pt]=Conta de POP3 +Comment[pt_BR]=Conta POP3 +Comment[ro]=Cont Pop3 +Comment[ru]=Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ POP3 +Comment[sk]=Pop3 úÄet +Comment[sl]=RaÄun POP3 +Comment[sr]=ПОП3 налог +Comment[sr@ijekavian]=ПОП3 налог +Comment[sr@ijekavianlatin]=POP3 nalog +Comment[sr@latin]=POP3 nalog +Comment[sv]=Pop3-konto +Comment[tr]=Pop3 hesabı +Comment[uk]=Обліковий Ð·Ð°Ð¿Ð¸Ñ POP3 +Comment[x-test]=xxPop3 accountxx +Comment[zh_CN]=Pop3 账户 +Comment[zh_TW]=Pop3 帳號 + +[Wizard] +Type=message/rfc822 +Script=pop3wizard.es + +[Translate] +Filename=accountwizard_pop3 diff --git a/kdepim-runtime/resources/pop3/wizard/pop3wizard.es b/kdepim-runtime/resources/pop3/wizard/pop3wizard.es new file mode 100644 index 00000000..81d511d0 --- /dev/null +++ b/kdepim-runtime/resources/pop3/wizard/pop3wizard.es @@ -0,0 +1,63 @@ +/* + Copyright (c) 2009 Montel Laurent + + 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. +*/ + +// add this function to trim user input of whitespace when needed +String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); }; + +var page = Dialog.addPage( "pop3wizard.ui", qsTr("Personal Settings") ); + +var userChangedServerAddress = false; + +function serverChanged( arg ) +{ + validateInput(); + if ( arg == "" ) { + userChangedServerAddress = false; + } else { + userChangedServerAddress = true; + } +} + +function validateInput() +{ + if ( page.widget().incommingAddress.text.trim() == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var pop3Res = SetupManager.createResource( "akonadi_pop3_resource" ); + pop3Res.setOption( "Host", page.widget().incommingAddress.text.trim() ); + pop3Res.setOption( "Login", page.widget().userName.text.trim() ); + pop3Res.setOption( "Password", SetupManager.password() ); + + var smtp = SetupManager.createTransport( "smtp" ); + smtp.setName( SetupManager.name() ); + smtp.setHost( page.widget().outgoingAddress.text.trim() ); + smtp.setEncryption( "NONE" ); + + SetupManager.execute(); +} + +page.widget().incommingAddress.textChanged.connect( serverChanged ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/pop3/wizard/pop3wizard.ui b/kdepim-runtime/resources/pop3/wizard/pop3wizard.ui new file mode 100644 index 00000000..4f623ea7 --- /dev/null +++ b/kdepim-runtime/resources/pop3/wizard/pop3wizard.ui @@ -0,0 +1,90 @@ + + + pop3Wizard + + + + 0 + 0 + 400 + 131 + + + + + + + + + + + Username: + + + + + + + Incoming server: + + + incommingAddress + + + + + + + Outgoing server: + + + outgoingAddress + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kdepim-runtime/resources/shared/CMakeLists.txt b/kdepim-runtime/resources/shared/CMakeLists.txt new file mode 100644 index 00000000..d467d40a --- /dev/null +++ b/kdepim-runtime/resources/shared/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(filestore) +add_subdirectory(tests) \ No newline at end of file diff --git a/kdepim-runtime/resources/shared/Messages.sh b/kdepim-runtime/resources/shared/Messages.sh new file mode 100644 index 00000000..798c5a2e --- /dev/null +++ b/kdepim-runtime/resources/shared/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` >> rc.cpp || exit 11 +$XGETTEXT *.cpp *.h -o $podir/akonadi_singlefile_resource.pot +rm -f rc.cpp diff --git a/kdepim-runtime/resources/shared/collectionannotationsattribute.cpp b/kdepim-runtime/resources/shared/collectionannotationsattribute.cpp new file mode 100644 index 00000000..b4df346f --- /dev/null +++ b/kdepim-runtime/resources/shared/collectionannotationsattribute.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 "collectionannotationsattribute.h" + +#include +#include +#include + +using namespace Akonadi; + +CollectionAnnotationsAttribute::CollectionAnnotationsAttribute() +{ +} + +CollectionAnnotationsAttribute::CollectionAnnotationsAttribute( const QMap &annotations ) + : mAnnotations( annotations ) +{ +} + +void CollectionAnnotationsAttribute::setAnnotations( const QMap &annotations ) +{ + mAnnotations = annotations; +} + +QMap CollectionAnnotationsAttribute::annotations() const +{ + return mAnnotations; +} + +QByteArray CollectionAnnotationsAttribute::type() const +{ + return "collectionannotations"; +} + +Akonadi::Attribute* CollectionAnnotationsAttribute::clone() const +{ + return new CollectionAnnotationsAttribute( mAnnotations ); +} + +QByteArray CollectionAnnotationsAttribute::serialized() const +{ + QByteArray result = ""; + + foreach ( const QByteArray &key, mAnnotations.keys() ) { + result+= key; + result+= ' '; + result+= mAnnotations[key]; + result+= " % "; // We use this separator as '%' is not allowed in keys or values + } + result.chop( 3 ); + + return result; +} + +void CollectionAnnotationsAttribute::deserialize( const QByteArray &data ) +{ + mAnnotations.clear(); + const QList lines = data.split( '%' ); + + for ( int i = 0; i < lines.size(); ++i ) { + QByteArray line = lines[i]; + if ( i != 0 && line.startsWith( ' ' ) ) + line = line.mid( 1 ); + if ( i != lines.size() - 1 && line.endsWith( ' ' ) ) + line.chop( 1 ); + if ( line.trimmed().isEmpty() ) + continue; + int wsIndex = line.indexOf( ' ' ); + if ( wsIndex > 0 ) { + const QByteArray key = line.mid( 0, wsIndex ); + const QByteArray value = line.mid( wsIndex+1 ); + mAnnotations[key] = value; + } else { + mAnnotations.insert( line, QByteArray() ); + } + } +} diff --git a/kdepim-runtime/resources/shared/collectionannotationsattribute.h b/kdepim-runtime/resources/shared/collectionannotationsattribute.h new file mode 100644 index 00000000..d07d3094 --- /dev/null +++ b/kdepim-runtime/resources/shared/collectionannotationsattribute.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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_COLLECTIONANNOTATIONSATTRIBUTE_H +#define AKONADI_COLLECTIONANNOTATIONSATTRIBUTE_H + +#include + +#include + +namespace Akonadi { + +class CollectionAnnotationsAttribute : public Akonadi::Attribute +{ + public: + CollectionAnnotationsAttribute(); + CollectionAnnotationsAttribute( const QMap &annotations ); + void setAnnotations( const QMap &annotations ); + QMap annotations() const; + virtual QByteArray type() const; + virtual Attribute *clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + + private: + QMap mAnnotations; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/collectionflagsattribute.cpp b/kdepim-runtime/resources/shared/collectionflagsattribute.cpp new file mode 100644 index 00000000..42c980bb --- /dev/null +++ b/kdepim-runtime/resources/shared/collectionflagsattribute.cpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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 "collectionflagsattribute.h" + +#include +#include + +using namespace Akonadi; + +CollectionFlagsAttribute::CollectionFlagsAttribute( const QList &flags ) + : mFlags( flags ) +{ +} + +void CollectionFlagsAttribute::setFlags( const QList &flags ) +{ + mFlags = flags; +} + +QList CollectionFlagsAttribute::flags() const +{ + return mFlags; +} + +QByteArray CollectionFlagsAttribute::type() const +{ + return "collectionflags"; +} + +Akonadi::Attribute* CollectionFlagsAttribute::clone() const +{ + return new CollectionFlagsAttribute( mFlags ); +} + +QByteArray CollectionFlagsAttribute::serialized() const +{ + QByteArray result; + + foreach ( const QByteArray &flag, mFlags ) { + result+= flag+' '; + } + result.chop( 1 ); + + return result; +} + +void CollectionFlagsAttribute::deserialize( const QByteArray &data ) +{ + mFlags = data.split( ' ' ); +} diff --git a/kdepim-runtime/resources/shared/collectionflagsattribute.h b/kdepim-runtime/resources/shared/collectionflagsattribute.h new file mode 100644 index 00000000..6c8a8c02 --- /dev/null +++ b/kdepim-runtime/resources/shared/collectionflagsattribute.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + + 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_COLLECTIONFLAGSATTRIBUTE_H +#define AKONADI_COLLECTIONFLAGSATTRIBUTE_H + +#include + +namespace Akonadi { + +class CollectionFlagsAttribute : public Akonadi::Attribute +{ + public: + explicit CollectionFlagsAttribute( const QList &flags = QList() ); + void setFlags( const QList &flags ); + QList flags() const; + virtual QByteArray type() const; + virtual Attribute *clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + + private: + QList mFlags; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/createandsettagsjob.cpp b/kdepim-runtime/resources/shared/createandsettagsjob.cpp new file mode 100644 index 00000000..31529750 --- /dev/null +++ b/kdepim-runtime/resources/shared/createandsettagsjob.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014 Christian Mollekopf + * + * 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 "createandsettagsjob.h" +#include +#include + +using namespace Akonadi; + +CreateAndSetTagsJob::CreateAndSetTagsJob(const Item& item, const Akonadi::Tag::List& tags, QObject* parent) +: KJob(parent), + mItem(item), + mTags(tags), + mCount(0) +{ + +} + +void CreateAndSetTagsJob::start() +{ + if (mTags.isEmpty()) { + emitResult(); + } + Q_FOREACH (const Akonadi::Tag &tag, mTags) { + Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(tag, this); + createJob->setMergeIfExisting(true); + connect(createJob, SIGNAL(result(KJob*)), this, SLOT(onCreateDone(KJob*))); + } +} + +void CreateAndSetTagsJob::onCreateDone(KJob *job) +{ + mCount++; + if (job->error()) { + kWarning() << "Failed to create tag " << job->errorString(); + } else { + Akonadi::TagCreateJob *createJob = static_cast(job); + mCreatedTags << createJob->tag(); + } + if (mCount == mTags.size()) { + Q_FOREACH (const Akonadi::Tag &tag, mCreatedTags) { + mItem.setTag(tag); + } + Akonadi::ItemModifyJob *modJob = new Akonadi::ItemModifyJob(mItem, this); + connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onModifyDone(KJob*))); + } +} + +void CreateAndSetTagsJob::onModifyDone(KJob *job) +{ + if (job->error()) { + kWarning() << "Failed to modify item " << job->errorString(); + setError(KJob::UserDefinedError); + } + emitResult(); +} diff --git a/kdepim-runtime/resources/shared/createandsettagsjob.h b/kdepim-runtime/resources/shared/createandsettagsjob.h new file mode 100644 index 00000000..62186b10 --- /dev/null +++ b/kdepim-runtime/resources/shared/createandsettagsjob.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 Christian Mollekopf + * + * 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 CREATEANDSETTAGSJOB_H +#define CREATEANDSETTAGSJOB_H + +#include +#include +#include + +class CreateAndSetTagsJob : public KJob +{ + Q_OBJECT +public: + explicit CreateAndSetTagsJob(const Akonadi::Item &item, const Akonadi::Tag::List &tags, QObject* parent = 0); + + virtual void start(); + +private Q_SLOTS: + void onCreateDone(KJob*); + void onModifyDone(KJob*); + +private: + Akonadi::Item mItem; + Akonadi::Tag::List mTags; + Akonadi::Tag::List mCreatedTags; + int mCount; +}; + +#endif diff --git a/kdepim-runtime/resources/shared/dirsettingsdialog.cpp b/kdepim-runtime/resources/shared/dirsettingsdialog.cpp new file mode 100644 index 00000000..dcc7e3f6 --- /dev/null +++ b/kdepim-runtime/resources/shared/dirsettingsdialog.cpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2009 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. +*/ + +#include "dirsettingsdialog.h" + +#include "settings.h" + +#include +#include + +#include + +using namespace Akonadi; + +SettingsDialog::SettingsDialog( WId windowId ) + : KDialog() +{ + ui.setupUi( mainWidget() ); + ui.kcfg_Path->setMode( KFile::LocalOnly | KFile::Directory ); + setButtons( Ok | Cancel ); + + if ( windowId ) + KWindowSystem::setMainWindow( this, windowId ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); + + connect( ui.kcfg_Path, SIGNAL(textChanged(QString)), SLOT(validate()) ); + connect( ui.kcfg_ReadOnly, SIGNAL(toggled(bool)), SLOT(validate()) ); + + QTimer::singleShot( 0, this, SLOT(validate()) ); + + ui.kcfg_Path->setUrl( KUrl( Settings::self()->path() ) ); + ui.kcfg_AutosaveInterval->setSuffix(ki18np(" minute", " minutes")); + mManager = new KConfigDialogManager( this, Settings::self() ); + mManager->updateWidgets(); +} + +void SettingsDialog::save() +{ + mManager->updateSettings(); + Settings::self()->setPath( ui.kcfg_Path->url().toLocalFile() ); + Settings::self()->writeConfig(); +} + +void SettingsDialog::validate() +{ + const KUrl currentUrl = ui.kcfg_Path->url(); + if ( currentUrl.isEmpty() ) { + enableButton( Ok, false ); + return; + } + + const QFileInfo file( currentUrl.toLocalFile() ); + if ( file.exists() && !file.isWritable() ) { + ui.kcfg_ReadOnly->setEnabled( false ); + ui.kcfg_ReadOnly->setChecked( true ); + } else { + ui.kcfg_ReadOnly->setEnabled( true ); + } + enableButton( Ok, true ); +} diff --git a/kdepim-runtime/resources/shared/dirsettingsdialog.h b/kdepim-runtime/resources/shared/dirsettingsdialog.h new file mode 100644 index 00000000..b362011f --- /dev/null +++ b/kdepim-runtime/resources/shared/dirsettingsdialog.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2009 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. +*/ + +#ifndef DIRSETTINGSDIALOG_H +#define DIRSETTINGSDIALOG_H + +#include "ui_settingsdialog.h" + +#include + +class KConfigDialogManager; + +namespace Akonadi { + +class SettingsDialog : public KDialog +{ + Q_OBJECT + public: + explicit SettingsDialog( WId windowId ); + + private Q_SLOTS: + void save(); + void validate(); + + private: + Ui::SettingsDialog ui; + KConfigDialogManager* mManager; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/filestore/CMakeLists.txt b/kdepim-runtime/resources/shared/filestore/CMakeLists.txt new file mode 100644 index 00000000..7ddb0f79 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/CMakeLists.txt @@ -0,0 +1,52 @@ +project(akonadi-filestore) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${AKONADI_INCLUDE_DIR} + ${KDE4_INCLUDES} + ${KDEPIMLIBS_INCLUDE_DIR} + ${QT_INCLUDES} + ${Boost_INCLUDE_DIR} +) + +include(KDE4Defaults) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +set(akonadi-filestore_SRCS + abstractlocalstore.cpp + collectioncreatejob.cpp + collectiondeletejob.cpp + collectionfetchjob.cpp + collectionmodifyjob.cpp + collectionmovejob.cpp + entitycompactchangeattribute.cpp + itemcreatejob.cpp + itemdeletejob.cpp + itemfetchjob.cpp + itemmodifyjob.cpp + itemmovejob.cpp + job.cpp + session.cpp + sessionimpls.cpp + storecompactjob.cpp +) + +kde4_add_library(akonadi-filestore ${LIBRARY_TYPE} ${akonadi-filestore_SRCS} ) + +target_link_libraries(akonadi-filestore + ${AKONADI_COMMON_LIBRARIES} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${KDEPIMLIBS_AKONADI_LIBS} + ${KDE4_KIO_LIBS} +) + +set_target_properties(akonadi-filestore PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) + +install(TARGETS akonadi-filestore ${INSTALL_TARGETS_DEFAULT_ARGS}) + +if (KDE4_BUILD_TESTS) + add_subdirectory( tests ) +endif () + diff --git a/kdepim-runtime/resources/shared/filestore/Messages.sh b/kdepim-runtime/resources/shared/filestore/Messages.sh new file mode 100644 index 00000000..2a8836db --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp *.h -o $podir/akonadi-filestore.pot diff --git a/kdepim-runtime/resources/shared/filestore/abstractlocalstore.cpp b/kdepim-runtime/resources/shared/filestore/abstractlocalstore.cpp new file mode 100644 index 00000000..ba94657d --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/abstractlocalstore.cpp @@ -0,0 +1,853 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "abstractlocalstore.h" + +#include "collectioncreatejob.h" +#include "collectiondeletejob.h" +#include "collectionfetchjob.h" +#include "collectionmodifyjob.h" +#include "collectionmovejob.h" +#include "itemcreatejob.h" +#include "itemdeletejob.h" +#include "itemfetchjob.h" +#include "itemmodifyjob.h" +#include "itemmovejob.h" +#include "sessionimpls_p.h" +#include "storecompactjob.h" + +#include +#include + +#include + +#include + +using namespace Akonadi; + +class JobProcessingAdaptor : public FileStore::Job::Visitor +{ + public: + explicit JobProcessingAdaptor( FileStore::AbstractJobSession *session ) + : mSession( session ) + { + } + + public: // Job::Visitor interface implementation + bool visit( FileStore::Job *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::CollectionCreateJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::CollectionDeleteJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::CollectionFetchJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::CollectionModifyJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::CollectionMoveJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::ItemCreateJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::ItemDeleteJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::ItemFetchJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::ItemModifyJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::ItemMoveJob *job ) { Q_UNUSED( job ); return false ; } + + bool visit( FileStore::StoreCompactJob *job ) { Q_UNUSED( job ); return false ; } + + protected: + FileStore::AbstractJobSession *mSession; +}; + +class TopLevelCollectionFetcher : public JobProcessingAdaptor +{ + public: + explicit TopLevelCollectionFetcher( FileStore::AbstractJobSession *session ) + : JobProcessingAdaptor( session ) + { + } + + void setTopLevelCollection( const Collection &collection ) + { + mTopLevelCollection = collection; + } + + public: + using JobProcessingAdaptor::visit; + + bool visit( FileStore::CollectionFetchJob *job ) + { + if ( job->type() == FileStore::CollectionFetchJob::Base && + job->collection().remoteId() == mTopLevelCollection.remoteId() ) { + mSession->notifyCollectionsReceived( job, Collection::List() << mTopLevelCollection ); + return true; + } + + return false; + } + + private: + Collection mTopLevelCollection; +}; + +class CollectionsProcessedNotifier : public JobProcessingAdaptor +{ + public: + explicit CollectionsProcessedNotifier( FileStore::AbstractJobSession *session ) + : JobProcessingAdaptor( session ) + { + } + + void setCollections( const Collection::List &collections ) + { + mCollections = collections; + } + + public: + using JobProcessingAdaptor::visit; + + bool visit( FileStore::CollectionCreateJob* job ) + { + Q_ASSERT( !mCollections.isEmpty() ); + if ( mCollections.count() > 1 ) { + kError() << "Processing collections for CollectionCreateJob " + "encountered more than one collection. Just processing the first one."; + } + + mSession->notifyCollectionCreated( job, mCollections[ 0 ] ); + return true; + } + + bool visit( FileStore::CollectionDeleteJob* job ) + { + Q_ASSERT( !mCollections.isEmpty() ); + if ( mCollections.count() > 1 ) { + kError() << "Processing collections for CollectionDeleteJob " + "encountered more than one collection. Just processing the first one."; + } + + mSession->notifyCollectionDeleted( job, mCollections[ 0 ] ); + return true; + } + + bool visit( FileStore::CollectionFetchJob* job ) + { + mSession->notifyCollectionsReceived( job, mCollections ); + return true; + } + + bool visit( FileStore::CollectionModifyJob* job ) + { + Q_ASSERT( !mCollections.isEmpty() ); + if ( mCollections.count() > 1 ) { + kError() << "Processing collections for CollectionModifyJob " + "encountered more than one collection. Just processing the first one."; + } + + mSession->notifyCollectionModified( job, mCollections[ 0 ] ); + return true; + } + + bool visit( FileStore::CollectionMoveJob* job ) + { + Q_ASSERT( !mCollections.isEmpty() ); + if ( mCollections.count() > 1 ) { + kError() << "Processing collections for CollectionMoveJob " + "encountered more than one collection. Just processing the first one."; + } + + mSession->notifyCollectionMoved( job, mCollections[ 0 ] ); + return true; + } + + bool visit( FileStore::StoreCompactJob* job ) + { + mSession->notifyCollectionsChanged( job, mCollections ); + return true; + } + + private: + Collection::List mCollections; +}; + +class ItemsProcessedNotifier : public JobProcessingAdaptor +{ + public: + explicit ItemsProcessedNotifier( FileStore::AbstractJobSession *session ) + : JobProcessingAdaptor( session ) + { + } + + void setItems( const Item::List &items ) + { + mItems = items; + } + + void clearItems() + { + mItems.clear(); + } + + public: + using JobProcessingAdaptor::visit; + + bool visit( FileStore::ItemCreateJob* job ) + { + Q_ASSERT( !mItems.isEmpty() ); + if ( mItems.count() > 1 ) { + kError() << "Processing items for ItemCreateJob encountered more than one item. " + "Just processing the first one."; + } + + mSession->notifyItemCreated( job, mItems[ 0 ] ); + return true; + } + + bool visit( FileStore::ItemFetchJob* job ) + { + mSession->notifyItemsReceived( job, mItems ); + return true; + } + + bool visit( FileStore::ItemModifyJob* job ) + { + Q_ASSERT( !mItems.isEmpty() ); + if ( mItems.count() > 1 ) { + kError() << "Processing items for ItemModifyJob encountered more than one item. " + "Just processing the first one."; + } + + mSession->notifyItemModified( job, mItems[ 0 ] ); + return true; + } + + bool visit( FileStore::ItemMoveJob* job ) + { + Q_ASSERT( !mItems.isEmpty() ); + if ( mItems.count() > 1 ) { + kError() << "Processing items for ItemMoveJob encountered more than one item. " + "Just processing the first one."; + } + + mSession->notifyItemMoved( job, mItems[ 0 ] ); + return true; + } + + bool visit( FileStore::StoreCompactJob* job ) + { + mSession->notifyItemsChanged( job, mItems ); + return true; + } + + private: + Item::List mItems; +}; + +class FileStore::AbstractLocalStore::Private +{ + AbstractLocalStore *const q; + + public: + explicit Private( FileStore::AbstractLocalStore *parent ) + : q( parent ), mSession( new FileStore::FiFoQueueJobSession( q ) ), mCurrentJob( 0 ), + mTopLevelCollectionFetcher( mSession ), mCollectionsProcessedNotifier( mSession ), + mItemsProcessedNotifier( mSession ) + { + } + + public: + QFileInfo mPathFileInfo; + Collection mTopLevelCollection; + + FileStore::AbstractJobSession *mSession; + FileStore::Job *mCurrentJob; + + TopLevelCollectionFetcher mTopLevelCollectionFetcher; + CollectionsProcessedNotifier mCollectionsProcessedNotifier; + ItemsProcessedNotifier mItemsProcessedNotifier; + + public: + void processJobs( const QList &jobs ); +}; + +void FileStore::AbstractLocalStore::Private::processJobs( const QList &jobs ) +{ + Q_FOREACH( FileStore::Job *job, jobs ) { + mCurrentJob = job; + + if ( job->error() == 0 ) { + if ( !job->accept( &mTopLevelCollectionFetcher ) ) { + q->processJob( job ); + } + } + mSession->emitResult( job ); + mCurrentJob = 0; + } +} + +FileStore::AbstractLocalStore::AbstractLocalStore() + : QObject(), d( new Private( this ) ) +{ + connect( d->mSession, SIGNAL(jobsReady(QList)), this, SLOT(processJobs(QList)) ); +} + +FileStore::AbstractLocalStore::~AbstractLocalStore() +{ + delete d; +} + +void FileStore::AbstractLocalStore::setPath( const QString &path ) +{ + QFileInfo pathFileInfo( path ); + if ( pathFileInfo.fileName().isEmpty() ) { + pathFileInfo = QFileInfo( pathFileInfo.path() ); + } + pathFileInfo.makeAbsolute(); + + if ( pathFileInfo.absoluteFilePath() == d->mPathFileInfo.absoluteFilePath() ) { + return; + } + + d->mPathFileInfo = pathFileInfo; + + Collection collection; + collection.setRemoteId( d->mPathFileInfo.absoluteFilePath() ); + collection.setName( d->mPathFileInfo.fileName() ); + + EntityDisplayAttribute *attribute = collection.attribute(); + if ( attribute != 0 ) { + attribute->setDisplayName( d->mPathFileInfo.fileName() ); + } + + setTopLevelCollection( collection ); +} + +QString FileStore::AbstractLocalStore::path() const +{ + return d->mPathFileInfo.absoluteFilePath(); +} + +Collection FileStore::AbstractLocalStore::topLevelCollection() const +{ + return d->mTopLevelCollection; +} + +FileStore::CollectionCreateJob *FileStore::AbstractLocalStore::createCollection( const Collection &collection, const Collection &targetParent ) +{ + FileStore::CollectionCreateJob *job = new FileStore::CollectionCreateJob( collection, targetParent, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( targetParent.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( targetParent.rights() & Collection::CanCreateCollection ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits folder creation in folder %1", targetParent.name() ); + kError() << message; + kError() << collection << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkCollectionCreate( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::CollectionDeleteJob *FileStore::AbstractLocalStore::deleteCollection( const Collection &collection ) +{ + FileStore::CollectionDeleteJob *job = new FileStore::CollectionDeleteJob( collection, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( collection.remoteId().isEmpty() || + collection.parentCollection().remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( collection.rights() & Collection::CanDeleteCollection ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits folder deletion in folder %1", collection.name() ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkCollectionDelete( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::CollectionFetchJob *FileStore::AbstractLocalStore::fetchCollections( const Collection &collection, FileStore::CollectionFetchJob::Type type ) const +{ + FileStore::CollectionFetchJob *job = new FileStore::CollectionFetchJob( collection, type, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection << "FetchType=" << type; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( collection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection << "FetchType=" << type; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkCollectionFetch( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::CollectionModifyJob *FileStore::AbstractLocalStore::modifyCollection( const Collection &collection ) +{ + FileStore::CollectionModifyJob *job = new FileStore::CollectionModifyJob( collection, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( collection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( collection.rights() & Collection::CanChangeCollection ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits folder modification in folder %1", collection.name() ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkCollectionModify( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::CollectionMoveJob *FileStore::AbstractLocalStore::moveCollection( const Collection &collection, const Collection &targetParent ) +{ + FileStore::CollectionMoveJob *job = new FileStore::CollectionMoveJob( collection, targetParent, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( collection.remoteId().isEmpty() || + collection.parentCollection().remoteId().isEmpty() || + targetParent.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( targetParent.rights() & Collection::CanCreateCollection ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits folder creation in folder %1", targetParent.name() ); + kError() << message; + kError() << collection << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkCollectionMove( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::ItemFetchJob *FileStore::AbstractLocalStore::fetchItems( const Collection &collection ) const +{ + FileStore::ItemFetchJob *job = new FileStore::ItemFetchJob( collection, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( collection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkItemFetch( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::ItemFetchJob *FileStore::AbstractLocalStore::fetchItem( const Item &item ) const +{ + FileStore::ItemFetchJob *job = new FileStore::ItemFetchJob( item, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( item.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given item identifier is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkItemFetch( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::ItemCreateJob *FileStore::AbstractLocalStore::createItem( const Item &item, const Collection &collection ) +{ + FileStore::ItemCreateJob *job = new FileStore::ItemCreateJob( item, collection, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << collection + << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ")"; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( collection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << collection + << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( collection.rights() & Collection::CanCreateItem ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits item creation in folder %1", collection.name() ); + kError() << message; + kError() << collection + << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkItemCreate( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::ItemModifyJob *FileStore::AbstractLocalStore::modifyItem( const Item &item ) +{ + FileStore::ItemModifyJob *job = new FileStore::ItemModifyJob( item, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( item.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given item identifier is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( item.parentCollection().rights() & Collection::CanChangeItem ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits item modification in folder %1", item.parentCollection().name() ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkItemModify( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::ItemDeleteJob *FileStore::AbstractLocalStore::deleteItem( const Item &item ) +{ + FileStore::ItemDeleteJob *job = new FileStore::ItemDeleteJob( item, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( item.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given item identifier is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( item.parentCollection().rights() & Collection::CanDeleteItem ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits item deletion in folder %1", item.parentCollection().name() ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")"; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkItemDelete( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::ItemMoveJob *FileStore::AbstractLocalStore::moveItem( const Item &item, const Collection &targetParent ) +{ + FileStore::ItemMoveJob *job = new FileStore::ItemMoveJob( item, targetParent, d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")" + << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } else if ( item.parentCollection().remoteId().isEmpty() || + targetParent.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given folder name is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")" + << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( targetParent.rights() & Collection::CanCreateItem ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits item creation in folder %1", targetParent.name() ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")" + << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( ( item.parentCollection().rights() & Collection::CanDeleteItem ) == 0 ) { + const QString message = i18nc( "@info:status", "Access control prohibits item deletion in folder %1", item.parentCollection().name() ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")" + << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } else if ( item.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Given item identifier is empty" ); + kError() << message; + kError() << "Item(remoteId=" << item.remoteId() << ", mimeType=" << item.mimeType() + << ", parentCollection=" << item.parentCollection().remoteId() << ")" + << targetParent; + d->mSession->setError( job, FileStore::Job::InvalidJobContext, message ); + } + + int errorCode = 0; + QString errorText; + checkItemMove( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::StoreCompactJob *FileStore::AbstractLocalStore::compactStore() +{ + FileStore::StoreCompactJob *job = new FileStore::StoreCompactJob( d->mSession ); + + if ( d->mTopLevelCollection.remoteId().isEmpty() ) { + const QString message = i18nc( "@info:status", "Configured storage location is empty" ); + kError() << message; + d->mSession->setError( job, FileStore::Job::InvalidStoreState, message ); + } + + int errorCode = 0; + QString errorText; + checkStoreCompact( job, errorCode, errorText ); + if ( errorCode != 0 ) { + d->mSession->setError( job, errorCode, errorText ); + } + + return job; +} + +FileStore::Job *FileStore::AbstractLocalStore::currentJob() const +{ + return d->mCurrentJob; +} + +void FileStore::AbstractLocalStore::notifyError( int errorCode, const QString &errorText ) const +{ + Q_ASSERT( d->mCurrentJob != 0); + + d->mSession->setError( d->mCurrentJob, errorCode, errorText ); +} + +void FileStore::AbstractLocalStore::notifyCollectionsProcessed( const Collection::List &collections ) const +{ + Q_ASSERT( d->mCurrentJob != 0); + + d->mCollectionsProcessedNotifier.setCollections( collections ); + d->mCurrentJob->accept( &( d->mCollectionsProcessedNotifier ) ); +} + +void FileStore::AbstractLocalStore::notifyItemsProcessed( const Item::List &items ) const +{ + Q_ASSERT( d->mCurrentJob != 0); + + d->mItemsProcessedNotifier.setItems( items ); + d->mCurrentJob->accept( &( d->mItemsProcessedNotifier ) ); + d->mItemsProcessedNotifier.clearItems(); // save memory +} + +void FileStore::AbstractLocalStore::setTopLevelCollection( const Collection &collection ) +{ + d->mTopLevelCollection = collection; + d->mTopLevelCollectionFetcher.setTopLevelCollection( collection ); +} + +void FileStore::AbstractLocalStore::checkCollectionCreate( FileStore::CollectionCreateJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkCollectionDelete( FileStore::CollectionDeleteJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkCollectionFetch( FileStore::CollectionFetchJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkCollectionModify( FileStore::CollectionModifyJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkCollectionMove( FileStore::CollectionMoveJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkItemCreate( FileStore::ItemCreateJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkItemDelete( FileStore::ItemDeleteJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkItemFetch( FileStore::ItemFetchJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkItemModify( FileStore::ItemModifyJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkItemMove( FileStore::ItemMoveJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +void FileStore::AbstractLocalStore::checkStoreCompact( FileStore::StoreCompactJob *job, int &errorCode, QString &errorText ) const +{ + Q_UNUSED( job ); + Q_UNUSED( errorCode ); + Q_UNUSED( errorText ); +} + +#include "moc_abstractlocalstore.cpp" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/abstractlocalstore.h b/kdepim-runtime/resources/shared/filestore/abstractlocalstore.h new file mode 100644 index 00000000..b597e34b --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/abstractlocalstore.h @@ -0,0 +1,126 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_ABSTRACTLOCALSTORE_H +#define AKONADI_FILESTORE_ABSTRACTLOCALSTORE_H + +#include "storeinterface.h" + +#include +#include + +#include + +template class QList; + +namespace Akonadi +{ + +namespace FileStore +{ + +/** + */ +class AKONADI_FILESTORE_EXPORT AbstractLocalStore : public QObject, public StoreInterface +{ + Q_OBJECT + + public: + AbstractLocalStore(); + ~AbstractLocalStore(); + + virtual void setPath( const QString &path ); + QString path() const; + + Collection topLevelCollection() const; + + CollectionCreateJob *createCollection( const Collection &collection, const Collection &targetParent ); + + CollectionFetchJob *fetchCollections( const Collection &collection, CollectionFetchJob::Type type = CollectionFetchJob::FirstLevel ) const; + + CollectionDeleteJob *deleteCollection( const Collection &collection ); + + CollectionModifyJob *modifyCollection( const Collection &collection ); + + CollectionMoveJob *moveCollection( const Collection &collection, const Collection &targetParent ); + + ItemFetchJob *fetchItems( const Collection &collection ) const; + + ItemFetchJob *fetchItem( const Item &item ) const; + + ItemCreateJob *createItem( const Item &item, const Collection &collection ); + + ItemModifyJob *modifyItem( const Item &item ); + + ItemDeleteJob *deleteItem( const Item &item ); + + ItemMoveJob *moveItem( const Item &item, const Collection &targetParent ); + + StoreCompactJob *compactStore(); + + protected: // job processing + virtual void processJob( Job *job ) = 0; + + Job *currentJob() const; + + void notifyError( int errorCode, const QString &errorText ) const; + + void notifyCollectionsProcessed( const Collection::List &collections ) const; + + void notifyItemsProcessed( const Item::List &items ) const; + + protected: // template methods + void setTopLevelCollection( const Collection &collection ); + + virtual void checkCollectionCreate( CollectionCreateJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkCollectionDelete( CollectionDeleteJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkCollectionFetch( CollectionFetchJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkCollectionModify( CollectionModifyJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkCollectionMove( CollectionMoveJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkItemCreate( ItemCreateJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkItemDelete( ItemDeleteJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkItemFetch( ItemFetchJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkItemModify( ItemModifyJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkItemMove( ItemMoveJob *job, int &errorCode, QString &errorText ) const; + + virtual void checkStoreCompact( StoreCompactJob *job, int &errorCode, QString &errorText ) const; + + private: + class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void processJobs( const QList &jobs ) ) +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/akonadi-filestore_export.h b/kdepim-runtime/resources/shared/filestore/akonadi-filestore_export.h new file mode 100644 index 00000000..736e7f36 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/akonadi-filestore_export.h @@ -0,0 +1,41 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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_FILESTORE_EXPORT_H +#define AKONADI_FILESTORE_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef AKONADI_FILESTORE_EXPORT +# if defined(KDEPIM_STATIC_LIBS) + /* No export/import for static libraries */ +# define AKONADI_FILESTORE_EXPORT +# elif defined(MAKE_AKONADI_FILESTORE_LIB) + /* We are building this library */ +# define AKONADI_FILESTORE_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define AKONADI_FILESTORE_EXPORT KDE_IMPORT +# endif +#endif + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectioncreatejob.cpp b/kdepim-runtime/resources/shared/filestore/collectioncreatejob.cpp new file mode 100644 index 00000000..38a3a62b --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectioncreatejob.cpp @@ -0,0 +1,80 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "collectioncreatejob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::CollectionCreateJob::Private +{ + public: + explicit Private( FileStore::CollectionCreateJob *parent ) + : mParent( parent ) + { + } + + public: + Collection mCollection; + Collection mTargetParent; + + private: + FileStore::CollectionCreateJob *mParent; +}; + +FileStore::CollectionCreateJob::CollectionCreateJob( const Collection &collection, const Collection &targetParent, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + Q_ASSERT( session != 0 ); + + d->mCollection = collection; + d->mTargetParent = targetParent; + + session->addJob( this ); +} + +FileStore::CollectionCreateJob::~CollectionCreateJob() +{ + delete d; +} + +Collection FileStore::CollectionCreateJob::collection() const +{ + return d->mCollection; +} + +Collection FileStore::CollectionCreateJob::targetParent() const +{ + return d->mTargetParent; +} + +bool FileStore::CollectionCreateJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::CollectionCreateJob::handleCollectionCreated( const Collection &collection ) +{ + d->mCollection = collection; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectioncreatejob.h b/kdepim-runtime/resources/shared/filestore/collectioncreatejob.h new file mode 100644 index 00000000..83572406 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectioncreatejob.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_COLLECTIONCREATEJOB_H +#define AKONADI_FILESTORE_COLLECTIONCREATEJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Collection; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT CollectionCreateJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit CollectionCreateJob( const Collection &collection, const Collection &targetParent, AbstractJobSession *session = 0 ); + + virtual ~CollectionCreateJob(); + + Collection collection() const; + + Collection targetParent() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleCollectionCreated( const Collection &collection ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectiondeletejob.cpp b/kdepim-runtime/resources/shared/filestore/collectiondeletejob.cpp new file mode 100644 index 00000000..ec6d60b7 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectiondeletejob.cpp @@ -0,0 +1,73 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "collectiondeletejob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::CollectionDeleteJob::Private +{ + public: + explicit Private( FileStore::CollectionDeleteJob *parent ) + : mParent( parent ) + { + } + + public: + Collection mCollection; + + private: + FileStore::CollectionDeleteJob *mParent; +}; + +FileStore::CollectionDeleteJob::CollectionDeleteJob( const Collection &collection, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + Q_ASSERT( session != 0 ); + + d->mCollection = collection; + + session->addJob( this ); +} + +FileStore::CollectionDeleteJob::~CollectionDeleteJob() +{ + delete d; +} + +Collection FileStore::CollectionDeleteJob::collection() const +{ + return d->mCollection; +} + +bool FileStore::CollectionDeleteJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::CollectionDeleteJob::handleCollectionDeleted( const Collection &collection ) +{ + d->mCollection = collection; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectiondeletejob.h b/kdepim-runtime/resources/shared/filestore/collectiondeletejob.h new file mode 100644 index 00000000..c7d2d67e --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectiondeletejob.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_COLLECTIONDELETEJOB_H +#define AKONADI_FILESTORE_COLLECTIONDELETEJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Collection; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT CollectionDeleteJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit CollectionDeleteJob( const Collection &collection, AbstractJobSession *session = 0 ); + + virtual ~CollectionDeleteJob(); + + Collection collection() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleCollectionDeleted( const Collection &collection ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectionfetchjob.cpp b/kdepim-runtime/resources/shared/filestore/collectionfetchjob.cpp new file mode 100644 index 00000000..135c2ea1 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectionfetchjob.cpp @@ -0,0 +1,103 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "collectionfetchjob.h" + +#include "session_p.h" + +#include + +using namespace Akonadi; + +class FileStore::CollectionFetchJob::Private +{ + public: + explicit Private( FileStore::CollectionFetchJob *parent ) + : mType( FileStore::CollectionFetchJob::Base ), + mParent( parent ) + { + } + + public: + FileStore::CollectionFetchJob::Type mType; + Collection mCollection; + + CollectionFetchScope mFetchScope; + + Collection::List mCollections; + + private: + FileStore::CollectionFetchJob *mParent; +}; + +FileStore::CollectionFetchJob::CollectionFetchJob( const Collection &collection, Type type, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + Q_ASSERT( session != 0 ); + + d->mType = type; + d->mCollection = collection; + + session->addJob( this ); +} + +FileStore::CollectionFetchJob::~CollectionFetchJob() +{ + delete d; +} + +FileStore::CollectionFetchJob::Type FileStore::CollectionFetchJob::type() const +{ + return d->mType; +} + +Collection FileStore::CollectionFetchJob::collection() const +{ + return d->mCollection; +} + +void FileStore::CollectionFetchJob::setFetchScope( const CollectionFetchScope &fetchScope ) +{ + d->mFetchScope = fetchScope; +} + +CollectionFetchScope &FileStore::CollectionFetchJob::fetchScope() +{ + return d->mFetchScope; +} + +Collection::List FileStore::CollectionFetchJob::collections() const +{ + return d->mCollections; +} + +bool FileStore::CollectionFetchJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::CollectionFetchJob::handleCollectionsReceived( const Collection::List &collections ) +{ + d->mCollections << collections; + + emit collectionsReceived( collections ); +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectionfetchjob.h b/kdepim-runtime/resources/shared/filestore/collectionfetchjob.h new file mode 100644 index 00000000..f6918c65 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectionfetchjob.h @@ -0,0 +1,83 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_COLLECTIONFETCHJOB_H +#define AKONADI_FILESTORE_COLLECTIONFETCHJOB_H + +#include "job.h" + +#include + +namespace Akonadi +{ + class CollectionFetchScope; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT CollectionFetchJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + enum Type + { + Base, + FirstLevel, + Recursive + }; + + explicit CollectionFetchJob( const Collection &collection, Type type = FirstLevel, AbstractJobSession *session = 0 ); + + virtual ~CollectionFetchJob(); + + Type type() const; + + Collection collection() const; + + void setFetchScope( const CollectionFetchScope &fetchScope ); + + CollectionFetchScope &fetchScope(); + + Collection::List collections() const; + + virtual bool accept( Visitor *visitor ); + + Q_SIGNALS: + void collectionsReceived( const Akonadi::Collection::List &items ); + + private: + void handleCollectionsReceived( const Akonadi::Collection::List &collections ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectionmodifyjob.cpp b/kdepim-runtime/resources/shared/filestore/collectionmodifyjob.cpp new file mode 100644 index 00000000..07d9d309 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectionmodifyjob.cpp @@ -0,0 +1,73 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "collectionmodifyjob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::CollectionModifyJob::Private +{ + public: + explicit Private( FileStore::CollectionModifyJob *parent ) + : mParent( parent ) + { + } + + public: + Collection mCollection; + + private: + FileStore::CollectionModifyJob *mParent; +}; + +FileStore::CollectionModifyJob::CollectionModifyJob( const Collection &collection, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + Q_ASSERT( session != 0 ); + + d->mCollection = collection; + + session->addJob( this ); +} + +FileStore::CollectionModifyJob::~CollectionModifyJob() +{ + delete d; +} + +Collection FileStore::CollectionModifyJob::collection() const +{ + return d->mCollection; +} + +bool FileStore::CollectionModifyJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::CollectionModifyJob::handleCollectionModified( const Collection &collection ) +{ + d->mCollection = collection; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectionmodifyjob.h b/kdepim-runtime/resources/shared/filestore/collectionmodifyjob.h new file mode 100644 index 00000000..143ff29d --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectionmodifyjob.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_COLLECTIONMODIFYJOB_H +#define AKONADI_FILESTORE_COLLECTIONMODIFYJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Collection; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT CollectionModifyJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit CollectionModifyJob( const Collection &collection, AbstractJobSession *session = 0 ); + + virtual ~CollectionModifyJob(); + + Collection collection() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleCollectionModified( const Collection &collection ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectionmovejob.cpp b/kdepim-runtime/resources/shared/filestore/collectionmovejob.cpp new file mode 100644 index 00000000..106489f6 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectionmovejob.cpp @@ -0,0 +1,80 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "collectionmovejob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::CollectionMoveJob::Private +{ + public: + explicit Private( FileStore::CollectionMoveJob *parent ) + : mParent( parent ) + { + } + + public: + Collection mCollection; + Collection mTargetParent; + + private: + FileStore::CollectionMoveJob *mParent; +}; + +FileStore::CollectionMoveJob::CollectionMoveJob( const Collection &collection, const Collection &targetParent, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + Q_ASSERT( session != 0 ); + + d->mCollection = collection; + d->mTargetParent = targetParent; + + session->addJob( this ); +} + +FileStore::CollectionMoveJob::~CollectionMoveJob() +{ + delete d; +} + +Collection FileStore::CollectionMoveJob::collection() const +{ + return d->mCollection; +} + +Collection FileStore::CollectionMoveJob::targetParent() const +{ + return d->mTargetParent; +} + +bool FileStore::CollectionMoveJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::CollectionMoveJob::handleCollectionMoved( const Collection &collection ) +{ + d->mCollection = collection; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/collectionmovejob.h b/kdepim-runtime/resources/shared/filestore/collectionmovejob.h new file mode 100644 index 00000000..a99abfcd --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/collectionmovejob.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_COLLECTIONMOVEJOB_H +#define AKONADI_FILESTORE_COLLECTIONMOVEJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Collection; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT CollectionMoveJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit CollectionMoveJob( const Collection &collection, const Collection &targetParent, AbstractJobSession *session = 0 ); + + virtual ~CollectionMoveJob(); + + Collection collection() const; + + Collection targetParent() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleCollectionMoved( const Collection &collection ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.cpp b/kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.cpp new file mode 100644 index 00000000..3484b499 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.cpp @@ -0,0 +1,112 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "entitycompactchangeattribute.h" + +#include + +using namespace Akonadi; + +class FileStore::EntityCompactChangeAttribute::Private +{ + FileStore::EntityCompactChangeAttribute *const q; + + public: + explicit Private( FileStore::EntityCompactChangeAttribute *parent ) : q( parent ) + { + } + + Private& operator=( const Private &other ) + { + if ( &other == this ) { + return *this; + } + + mRemoteId = other.mRemoteId; + mRemoteRev = other.mRemoteRev; + return *this; + } + + public: + QString mRemoteId; + QString mRemoteRev; +}; + +FileStore::EntityCompactChangeAttribute::EntityCompactChangeAttribute() + : Attribute(), d( new Private( this ) ) +{ +} + +FileStore::EntityCompactChangeAttribute::~EntityCompactChangeAttribute() +{ + delete d; +} + +void FileStore::EntityCompactChangeAttribute::setRemoteId( const QString &remoteId ) +{ + d->mRemoteId = remoteId; +} + +QString FileStore::EntityCompactChangeAttribute::remoteId() const +{ + return d->mRemoteId; +} + +void FileStore::EntityCompactChangeAttribute::setRemoteRevision( const QString &remoteRev ) +{ + d->mRemoteRev = remoteRev; +} + +QString FileStore::EntityCompactChangeAttribute::remoteRevision() const +{ + return d->mRemoteRev; +} + +QByteArray FileStore::EntityCompactChangeAttribute::type() const +{ + return "ENTITYCOMPACTCHANGE"; +} + +FileStore::EntityCompactChangeAttribute* FileStore::EntityCompactChangeAttribute::clone() const +{ + FileStore::EntityCompactChangeAttribute *copy = new FileStore::EntityCompactChangeAttribute(); + *(copy->d) = *d; + return copy; +} + +QByteArray FileStore::EntityCompactChangeAttribute::serialized() const +{ + QByteArray data; + QDataStream stream( &data, QIODevice::WriteOnly ); + + stream << d->mRemoteId; + stream << d->mRemoteRev; + + return data; +} + +void FileStore::EntityCompactChangeAttribute::deserialize( const QByteArray &data ) +{ + QDataStream stream( data ); + stream >> d->mRemoteId; + stream >> d->mRemoteRev; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.h b/kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.h new file mode 100644 index 00000000..33fb41c5 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/entitycompactchangeattribute.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_ENTITYCOMPACTCHANGEATTRIBUTE_H +#define AKONADI_FILESTORE_ENTITYCOMPACTCHANGEATTRIBUTE_H + +#include "akonadi-filestore_export.h" + +#include + +namespace Akonadi +{ + +namespace FileStore +{ + +class AKONADI_FILESTORE_EXPORT EntityCompactChangeAttribute : public Attribute +{ + public: + EntityCompactChangeAttribute(); + + ~EntityCompactChangeAttribute(); + + void setRemoteId( const QString &remoteId ); + + QString remoteId() const; + + void setRemoteRevision( const QString &remoteRev ); + + QString remoteRevision() const; + + public: /* reimpl */ + QByteArray type() const; + + EntityCompactChangeAttribute* clone() const; + + QByteArray serialized() const; + + void deserialize( const QByteArray &data ); + + private: + //@cond PRIVATE + class Private; + Private *const d; + //@endcond +}; + +} + +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemcreatejob.cpp b/kdepim-runtime/resources/shared/filestore/itemcreatejob.cpp new file mode 100644 index 00000000..d146b201 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemcreatejob.cpp @@ -0,0 +1,77 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "itemcreatejob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::ItemCreateJob::Private +{ + public: + explicit Private( FileStore::ItemCreateJob *parent ) + : mParent( parent ) + { + } + + public: + Item mItem; + Collection mCollection; + + private: + FileStore::ItemCreateJob *mParent; +}; + +FileStore::ItemCreateJob::ItemCreateJob( const Item &item, const Collection &collection, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + d->mItem = item; + d->mCollection = collection; + + session->addJob( this ); +} + +FileStore::ItemCreateJob::~ItemCreateJob() +{ + delete d; +} + +Collection FileStore::ItemCreateJob::collection() const +{ + return d->mCollection; +} + +Item FileStore::ItemCreateJob::item() const +{ + return d->mItem; +} + +bool FileStore::ItemCreateJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::ItemCreateJob::handleItemCreated( const Item &item ) +{ + d->mItem = item; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemcreatejob.h b/kdepim-runtime/resources/shared/filestore/itemcreatejob.h new file mode 100644 index 00000000..551d9521 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemcreatejob.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_ITEMCREATEJOB_H +#define AKONADI_FILESTORE_ITEMCREATEJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Collection; + class Item; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT ItemCreateJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit ItemCreateJob( const Item &item, const Collection &collection, AbstractJobSession *session = 0 ); + + virtual ~ItemCreateJob(); + + Collection collection() const; + + Item item() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleItemCreated( const Akonadi::Item &item ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemdeletejob.cpp b/kdepim-runtime/resources/shared/filestore/itemdeletejob.cpp new file mode 100644 index 00000000..bb8e38bf --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemdeletejob.cpp @@ -0,0 +1,70 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "itemdeletejob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::ItemDeleteJob::Private +{ + public: + explicit Private( FileStore::ItemDeleteJob *parent ) + : mParent( parent ) + { + } + + public: + Item mItem; + + private: + FileStore::ItemDeleteJob *mParent; +}; + +FileStore::ItemDeleteJob::ItemDeleteJob( const Item &item, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + d->mItem = item; + + session->addJob( this ); +} + +FileStore::ItemDeleteJob::~ItemDeleteJob() +{ + delete d; +} + +Item FileStore::ItemDeleteJob::item() const +{ + return d->mItem; +} + +bool FileStore::ItemDeleteJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::ItemDeleteJob::handleItemDeleted( const Item &item ) +{ + d->mItem = item; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemdeletejob.h b/kdepim-runtime/resources/shared/filestore/itemdeletejob.h new file mode 100644 index 00000000..11c9a280 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemdeletejob.h @@ -0,0 +1,63 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_ITEMDELETEJOB_H +#define AKONADI_FILESTORE_ITEMDELETEJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Item; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT ItemDeleteJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit ItemDeleteJob( const Item &item, AbstractJobSession *session = 0 ); + + virtual ~ItemDeleteJob(); + + Item item() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleItemDeleted( const Akonadi::Item &item ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemfetchjob.cpp b/kdepim-runtime/resources/shared/filestore/itemfetchjob.cpp new file mode 100644 index 00000000..c3ffabcb --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemfetchjob.cpp @@ -0,0 +1,107 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "itemfetchjob.h" + +#include "session_p.h" + +#include + +using namespace Akonadi; + +class FileStore::ItemFetchJob::Private +{ + public: + explicit Private( FileStore::ItemFetchJob *parent ) + : mParent( parent ) + { + } + + public: + ItemFetchScope mFetchScope; + + Item::List mItems; + + Collection mCollection; + Item mItem; + + private: + FileStore::ItemFetchJob *mParent; +}; + +FileStore::ItemFetchJob::ItemFetchJob( const Collection &collection, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + d->mCollection = collection; + + session->addJob( this ); +} + +FileStore::ItemFetchJob::ItemFetchJob( const Item &item, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + d->mItem = item; + + session->addJob( this ); +} + +FileStore::ItemFetchJob::~ItemFetchJob() +{ + delete d; +} + +Collection FileStore::ItemFetchJob::collection() const +{ + return d->mCollection; +} + +Item FileStore::ItemFetchJob::item() const +{ + return d->mItem; +} + +void FileStore::ItemFetchJob::setFetchScope( const ItemFetchScope &fetchScope ) +{ + d->mFetchScope = fetchScope; +} + +ItemFetchScope &FileStore::ItemFetchJob::fetchScope() +{ + return d->mFetchScope; +} + +Item::List FileStore::ItemFetchJob::items() const +{ + return d->mItems; +} + +bool FileStore::ItemFetchJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::ItemFetchJob::handleItemsReceived( const Item::List &items ) +{ + d->mItems << items; + + emit itemsReceived( items ); +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemfetchjob.h b/kdepim-runtime/resources/shared/filestore/itemfetchjob.h new file mode 100644 index 00000000..ba8da007 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemfetchjob.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_ITEMFETCHJOB_H +#define AKONADI_FILESTORE_ITEMFETCHJOB_H + +#include "job.h" + +#include + +namespace Akonadi +{ + class Collection; + class ItemFetchScope; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT ItemFetchJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit ItemFetchJob( const Collection &collection, AbstractJobSession *session = 0 ); + + explicit ItemFetchJob( const Item &item, AbstractJobSession *session = 0 ); + + virtual ~ItemFetchJob(); + + Collection collection() const; + + Item item() const; + + void setFetchScope( const ItemFetchScope &fetchScope ); + + ItemFetchScope &fetchScope(); + + Item::List items() const; + + virtual bool accept( Visitor *visitor ); + + Q_SIGNALS: + void itemsReceived( const Akonadi::Item::List &items ); + + private: + void handleItemsReceived( const Akonadi::Item::List &items ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemmodifyjob.cpp b/kdepim-runtime/resources/shared/filestore/itemmodifyjob.cpp new file mode 100644 index 00000000..add7ac3f --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemmodifyjob.cpp @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "itemmodifyjob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::ItemModifyJob::Private +{ + public: + explicit Private( FileStore::ItemModifyJob *parent ) + : mIgnorePayload( false ), mParent( parent ) + { + } + + public: + bool mIgnorePayload; + Item mItem; + QSet mParts; + + private: + FileStore::ItemModifyJob *mParent; +}; + +FileStore::ItemModifyJob::ItemModifyJob( const Item &item, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + d->mItem = item; + + session->addJob( this ); +} + +FileStore::ItemModifyJob::~ItemModifyJob() +{ + delete d; +} + +void FileStore::ItemModifyJob::setIgnorePayload( bool ignorePayload ) +{ + d->mIgnorePayload = ignorePayload; +} + +bool FileStore::ItemModifyJob::ignorePayload() const +{ + return d->mIgnorePayload; +} + +Item FileStore::ItemModifyJob::item() const +{ + return d->mItem; +} + +void FileStore::ItemModifyJob::setParts( const QSet& parts ) +{ + d->mParts = parts; +} + +const QSet& FileStore::ItemModifyJob::parts() const +{ + return d->mParts; +} + +bool FileStore::ItemModifyJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::ItemModifyJob::handleItemModified( const Item &item ) +{ + d->mItem = item; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemmodifyjob.h b/kdepim-runtime/resources/shared/filestore/itemmodifyjob.h new file mode 100644 index 00000000..81ef30d0 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemmodifyjob.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_ITEMMODIFYJOB_H +#define AKONADI_FILESTORE_ITEMMODIFYJOB_H + +#include "job.h" + +#include + +namespace Akonadi +{ + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT ItemModifyJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit ItemModifyJob( const Item &item, AbstractJobSession *session = 0 ); + + virtual ~ItemModifyJob(); + + void setIgnorePayload( bool ignorePayload ); + + bool ignorePayload() const; + + Item item() const; + + const QSet& parts() const; + void setParts( const QSet& parts ); + + virtual bool accept( Visitor *visitor ); + + private: + void handleItemModified( const Akonadi::Item &item ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemmovejob.cpp b/kdepim-runtime/resources/shared/filestore/itemmovejob.cpp new file mode 100644 index 00000000..075308c5 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemmovejob.cpp @@ -0,0 +1,78 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "itemmovejob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::ItemMoveJob::Private +{ + public: + explicit Private( FileStore::ItemMoveJob *parent ) + : mParent( parent ) + { + } + + public: + Item mItem; + Collection mTargetParent; + + private: + FileStore::ItemMoveJob *mParent; +}; + +FileStore::ItemMoveJob::ItemMoveJob( const Item &item, const Collection &targetParent, FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + d->mItem = item; + d->mTargetParent = targetParent; + + session->addJob( this ); +} + +FileStore::ItemMoveJob::~ItemMoveJob() +{ + delete d; +} + +Collection FileStore::ItemMoveJob::targetParent() const +{ + return d->mTargetParent; +} + +Item FileStore::ItemMoveJob::item() const +{ + return d->mItem; +} + +bool FileStore::ItemMoveJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +void FileStore::ItemMoveJob::handleItemMoved( const Item &item ) +{ + d->mItem = item; +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/itemmovejob.h b/kdepim-runtime/resources/shared/filestore/itemmovejob.h new file mode 100644 index 00000000..e697c632 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/itemmovejob.h @@ -0,0 +1,67 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_ITEMMOVEJOB_H +#define AKONADI_FILESTORE_ITEMMOVEJOB_H + +#include "job.h" + +namespace Akonadi +{ + class Collection; + class Item; + +namespace FileStore +{ + class AbstractJobSession; + +/** + */ +class AKONADI_FILESTORE_EXPORT ItemMoveJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + ItemMoveJob( const Item &item, const Collection &targetParent, AbstractJobSession *session = 0 ); + + virtual ~ItemMoveJob(); + + Collection targetParent() const; + + Item item() const; + + virtual bool accept( Visitor *visitor ); + + private: + void handleItemMoved( const Item &item ); + + private: + class Private; + Private *const d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/job.cpp b/kdepim-runtime/resources/shared/filestore/job.cpp new file mode 100644 index 00000000..f63e6322 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/job.cpp @@ -0,0 +1,59 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "job.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::Job::Private +{ + public: + explicit Private( FileStore::Job *parent ) + : mParent( parent ) + { + } + + private: + FileStore::Job *mParent; +}; + +FileStore::Job::Job( FileStore::AbstractJobSession *session ) + : KJob( session ), d( new Private( this ) ) +{ + setAutoDelete( true ); +} + +FileStore::Job::~Job() +{ + delete d; +} + +void FileStore::Job::start() +{ +} + +bool FileStore::Job::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/job.h b/kdepim-runtime/resources/shared/filestore/job.h new file mode 100644 index 00000000..b5fb9f9a --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/job.h @@ -0,0 +1,110 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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_FILESTORE_JOB_H +#define AKONADI_FILESTORE_JOB_H + +#include "akonadi-filestore_export.h" + +#include + +namespace Akonadi +{ + +namespace FileStore +{ + class AbstractJobSession; + class CollectionCreateJob; + class CollectionDeleteJob; + class CollectionFetchJob; + class CollectionModifyJob; + class CollectionMoveJob; + class ItemCreateJob; + class ItemDeleteJob; + class ItemFetchJob; + class ItemModifyJob; + class ItemMoveJob; + class StoreCompactJob; + +/** + */ +class AKONADI_FILESTORE_EXPORT Job : public KJob +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + class Visitor + { + public: + virtual ~Visitor() {} + + virtual bool visit( Job *job ) = 0; + + virtual bool visit( CollectionCreateJob *job ) = 0; + + virtual bool visit( CollectionDeleteJob *job ) = 0; + + virtual bool visit( CollectionFetchJob *job ) = 0; + + virtual bool visit( CollectionModifyJob *job ) = 0; + + virtual bool visit( CollectionMoveJob *job ) = 0; + + virtual bool visit( ItemCreateJob *job ) = 0; + + virtual bool visit( ItemDeleteJob *job ) = 0; + + virtual bool visit( ItemFetchJob *job ) = 0; + + virtual bool visit( ItemModifyJob *job ) = 0; + + virtual bool visit( ItemMoveJob *job ) = 0; + + virtual bool visit( StoreCompactJob *job ) = 0; + }; + + enum ErrorCodes + { + InvalidStoreState = KJob::UserDefinedError + 1, + InvalidJobContext + }; + + explicit Job( AbstractJobSession *session = 0 ); + + virtual ~Job(); + + virtual void start(); + + virtual bool accept( Visitor *visitor ); + + private: + class Private; + Private *d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/session.cpp b/kdepim-runtime/resources/shared/filestore/session.cpp new file mode 100644 index 00000000..366daaee --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/session.cpp @@ -0,0 +1,145 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "session_p.h" + +#include "collectioncreatejob.h" +#include "collectiondeletejob.h" +#include "collectionfetchjob.h" +#include "collectionmodifyjob.h" +#include "collectionmovejob.h" +#include "itemcreatejob.h" +#include "itemfetchjob.h" +#include "itemmodifyjob.h" +#include "itemmovejob.h" +#include "storecompactjob.h" + +using namespace Akonadi; + +FileStore::AbstractJobSession::AbstractJobSession( QObject *parent ) + : QObject( parent ) +{ +} + +FileStore::AbstractJobSession::~AbstractJobSession() +{ +} + +void FileStore::AbstractJobSession::notifyCollectionsReceived( FileStore::Job* job, const Collection::List &collections ) +{ + FileStore::CollectionFetchJob *fetchJob = dynamic_cast( job ); + if ( fetchJob != 0 ) { + fetchJob->handleCollectionsReceived( collections ); + } +} + +void FileStore::AbstractJobSession::notifyCollectionCreated( FileStore::Job *job, const Collection &collection ) +{ + FileStore::CollectionCreateJob *createJob = dynamic_cast( job ); + if ( createJob != 0 ) { + createJob->handleCollectionCreated( collection ); + } +} + +void FileStore::AbstractJobSession::notifyCollectionDeleted( FileStore::Job *job, const Collection &collection ) +{ + FileStore::CollectionDeleteJob *deleteJob = dynamic_cast( job ); + if ( deleteJob != 0 ) { + deleteJob->handleCollectionDeleted( collection ); + } +} + +void FileStore::AbstractJobSession::notifyCollectionModified( FileStore::Job *job, const Collection &collection ) +{ + FileStore::CollectionModifyJob *modifyJob = dynamic_cast( job ); + if ( modifyJob != 0 ) { + modifyJob->handleCollectionModified( collection ); + } +} + +void FileStore::AbstractJobSession::notifyCollectionMoved( FileStore::Job *job, const Collection &collection ) +{ + FileStore::CollectionMoveJob *moveJob = dynamic_cast( job ); + if ( moveJob != 0 ) { + moveJob->handleCollectionMoved( collection ); + } +} + +void FileStore::AbstractJobSession::notifyItemsReceived( FileStore::Job* job, const Item::List &items ) +{ + FileStore::ItemFetchJob *fetchJob = dynamic_cast( job ); + if ( fetchJob != 0 ) { + fetchJob->handleItemsReceived( items ); + } +} + +void FileStore::AbstractJobSession::notifyItemCreated( FileStore::Job *job, const Item &item ) +{ + FileStore::ItemCreateJob *createJob = dynamic_cast( job ); + if ( createJob != 0 ) { + createJob->handleItemCreated( item ); + } +} + +void FileStore::AbstractJobSession::notifyItemModified( FileStore::Job *job, const Item &item ) +{ + FileStore::ItemModifyJob *modifyJob = dynamic_cast( job ); + if ( modifyJob != 0 ) { + modifyJob->handleItemModified( item ); + } +} + +void FileStore::AbstractJobSession::notifyItemMoved( FileStore::Job *job, const Item &item ) +{ + FileStore::ItemMoveJob *moveJob = dynamic_cast( job ); + if ( moveJob != 0 ) { + moveJob->handleItemMoved( item ); + } +} + +void FileStore::AbstractJobSession::notifyCollectionsChanged( FileStore::Job *job, const Collection::List &collections ) +{ + FileStore::StoreCompactJob *compactJob = dynamic_cast( job ); + if ( compactJob != 0 ) { + compactJob->handleCollectionsChanged( collections ); + } +} + +void FileStore::AbstractJobSession::notifyItemsChanged( FileStore::Job *job, const Item::List &items ) +{ + FileStore::StoreCompactJob *compactJob = dynamic_cast( job ); + if ( compactJob != 0 ) { + compactJob->handleItemsChanged( items ); + } +} + +void FileStore::AbstractJobSession::setError( FileStore::Job *job, int errorCode, const QString& errorText ) +{ + job->setError( errorCode ); + job->setErrorText( errorText ); +} + +void FileStore::AbstractJobSession::emitResult( FileStore::Job *job ) +{ + removeJob( job ); + + job->emitResult(); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/session_p.h b/kdepim-runtime/resources/shared/filestore/session_p.h new file mode 100644 index 00000000..d878cd99 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/session_p.h @@ -0,0 +1,89 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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_FILESTORE_SESSION_P_H +#define AKONADI_FILESTORE_SESSION_P_H + +#include +#include + +#include + +namespace Akonadi +{ + +namespace FileStore +{ + +class Job; + +/** + */ +class AbstractJobSession : public QObject +{ + Q_OBJECT + + public: + explicit AbstractJobSession( QObject *parent = 0 ); + + virtual ~AbstractJobSession(); + + virtual void addJob( Job *job ) = 0; + + virtual void cancelAllJobs() = 0; + + void notifyCollectionsReceived( Job *job, const Collection::List &collections ); + + void notifyCollectionCreated( Job *job, const Collection &collection ); + + void notifyCollectionDeleted( Job *job, const Collection &collection ); + + void notifyCollectionModified( Job *job, const Collection &collection ); + + void notifyCollectionMoved( Job *job, const Collection &collection ); + + void notifyItemsReceived( Job *job, const Item::List &items ); + + void notifyItemCreated( Job *job, const Item &item ); + + void notifyItemModified( Job *job, const Item &item ); + + void notifyItemMoved( Job *job, const Item &item ); + + void notifyCollectionsChanged( Job *job, const Collection::List &collections ); + + void notifyItemsChanged( Job *job, const Item::List &items ); + + void setError( Job *job, int errorCode, const QString& errorText ); + + void emitResult( Job *job ); + + Q_SIGNALS: + void jobsReady( const QList &jobs ); + + protected: + virtual void removeJob( Job *job ) = 0; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/sessionimpls.cpp b/kdepim-runtime/resources/shared/filestore/sessionimpls.cpp new file mode 100644 index 00000000..6c3b98b4 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/sessionimpls.cpp @@ -0,0 +1,203 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "sessionimpls_p.h" + +#include "collectioncreatejob.h" +#include "collectiondeletejob.h" +#include "collectionfetchjob.h" +#include "collectionmodifyjob.h" +#include "collectionmovejob.h" +#include "itemcreatejob.h" +#include "itemdeletejob.h" +#include "itemfetchjob.h" +#include "itemmodifyjob.h" +#include "itemmovejob.h" +#include "storecompactjob.h" + +#include + +#include +#include + +using namespace Akonadi; + +class AbstractEnqueueVisitor : public FileStore::Job::Visitor +{ + public: + bool visit( FileStore::Job *job ) { + enqueue( job, "Job" ); + return true; + } + + bool visit( FileStore::CollectionCreateJob *job ) { + enqueue( job, "CollectionCreateJob" ); + return true; + } + + bool visit( FileStore::CollectionDeleteJob *job ) { + enqueue( job, "CollectionDeleteJob" ); + return true; + } + + bool visit( FileStore::CollectionFetchJob *job ) { + enqueue( job, "CollectionFetchJob" ); + return true; + } + + bool visit( FileStore::CollectionModifyJob *job ) { + enqueue( job, "CollectionModifyJob" ); + return true; + } + + bool visit( FileStore::CollectionMoveJob *job ) { + enqueue( job, "CollectionMoveJob" ); + return true; + } + + bool visit( FileStore::ItemCreateJob *job ) { + enqueue( job, "ItemCreateJob" ); + return true; + } + + bool visit( FileStore::ItemDeleteJob *job ) { + enqueue( job, "ItemDeleteJob" ); + return true; + } + + bool visit( FileStore::ItemFetchJob *job ) { + enqueue( job, "ItemFetchJob" ); + return true; + } + + bool visit( FileStore::ItemModifyJob *job ) { + enqueue( job, "ItemModifyJob" ); + return true; + } + + bool visit( FileStore::ItemMoveJob *job ) { + enqueue( job, "ItemMoveJob" ); + return true; + } + + bool visit( FileStore::StoreCompactJob *job ) { + enqueue( job, "StoreCompactJob" ); + return true; + } + + protected: + virtual void enqueue( FileStore::Job *job, const char* className ) = 0; +}; + +class FileStore::FiFoQueueJobSession::Private : public AbstractEnqueueVisitor +{ + public: + explicit Private( FileStore::FiFoQueueJobSession *parent ) + : mParent( parent ) + { + QObject::connect( &mJobRunTimer, SIGNAL(timeout()), mParent, SLOT(runNextJob()) ); + } + + void runNextJob() + { +/* kDebug() << "Queue with" << mJobQueue.count() << "entries";*/ + if ( mJobQueue.isEmpty() ) { + mJobRunTimer.stop(); + return; + } + + FileStore::Job *job = mJobQueue.dequeue(); + while ( job != 0 && job->error() != 0 ) { +/* kDebug() << "Dequeued job" << job << "has error (" + << job->error() << "," << job->errorText() << ")";*/ + mParent->emitResult( job ); + if ( !mJobQueue.isEmpty() ) { + job = mJobQueue.dequeue(); + } else { + job = 0; + } + } + + if ( job != 0 ) { +/* kDebug() << "Dequeued job" << job << "is ready";*/ + QList jobs; + jobs << job; + + emit mParent->jobsReady( jobs ); + } else { +/* kDebug() << "Queue now empty";*/ + mJobRunTimer.stop(); + } + } + + public: + QQueue mJobQueue; + + QTimer mJobRunTimer; + + protected: + virtual void enqueue( FileStore::Job *job, const char* className ) + { + Q_UNUSED( className ); + mJobQueue.enqueue( job ); + +// kDebug() << "adding" << className << ". Queue now with" +// << mJobQueue.count() << "entries"; + + mJobRunTimer.start( 0 ); + } + + private: + FileStore::FiFoQueueJobSession *mParent; +}; + +FileStore::FiFoQueueJobSession::FiFoQueueJobSession( QObject *parent ) + : FileStore::AbstractJobSession( parent ), d( new Private( this ) ) +{ +} + +FileStore::FiFoQueueJobSession::~FiFoQueueJobSession() +{ + cancelAllJobs(); + delete d; +} + +void FileStore::FiFoQueueJobSession::addJob( FileStore::Job *job ) +{ + job->accept( d ); +} + +void FileStore::FiFoQueueJobSession::cancelAllJobs() +{ + // KJob::kill() also deletes the job + foreach( FileStore::Job *job, d->mJobQueue ) { + job->kill( KJob::EmitResult ); + } + + d->mJobQueue.clear(); +} + +void FileStore::FiFoQueueJobSession::removeJob( FileStore::Job *job ) +{ + d->mJobQueue.removeAll( job ); +} + +#include "moc_sessionimpls_p.cpp" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/sessionimpls_p.h b/kdepim-runtime/resources/shared/filestore/sessionimpls_p.h new file mode 100644 index 00000000..22ff6ab8 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/sessionimpls_p.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_SESSIONIMPLS_P_H +#define AKONADI_FILESTORE_SESSIONIMPLS_P_H + +#include "session_p.h" + +namespace Akonadi +{ + +namespace FileStore +{ + +/** + */ +class FiFoQueueJobSession : public AbstractJobSession +{ + Q_OBJECT + + public: + explicit FiFoQueueJobSession( QObject *parent = 0 ); + + virtual ~FiFoQueueJobSession(); + + virtual void addJob( Job *job ); + + virtual void cancelAllJobs(); + + protected: + virtual void removeJob( Job *job ); + + private: + class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void runNextJob() ) +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/storecompactjob.cpp b/kdepim-runtime/resources/shared/filestore/storecompactjob.cpp new file mode 100644 index 00000000..d39af931 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/storecompactjob.cpp @@ -0,0 +1,80 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Kevin Krammer + + 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 "storecompactjob.h" + +#include "session_p.h" + +using namespace Akonadi; + +class FileStore::StoreCompactJob::Private +{ + public: + explicit Private( FileStore::StoreCompactJob *parent ) + : mParent( parent ) + { + } + + public: + FileStore::StoreCompactJob *mParent; + + Collection::List mCollections; + Item::List mItems; +}; + +FileStore::StoreCompactJob::StoreCompactJob( FileStore::AbstractJobSession *session ) + : FileStore::Job( session ), d( new Private( this ) ) +{ + session->addJob( this ); +} + +FileStore::StoreCompactJob::~StoreCompactJob() +{ + delete d; +} + +bool FileStore::StoreCompactJob::accept( FileStore::Job::Visitor *visitor ) +{ + return visitor->visit( this ); +} + +Item::List FileStore::StoreCompactJob::changedItems() const +{ + return d->mItems; +} + +Collection::List FileStore::StoreCompactJob::changedCollections() const +{ + return d->mCollections; +} + +void FileStore::StoreCompactJob::handleCollectionsChanged( const Collection::List &collections ) +{ + d->mCollections << collections; + emit collectionsChanged( collections ); +} + +void FileStore::StoreCompactJob::handleItemsChanged( const Item::List &items ) +{ + d->mItems << items; + emit itemsChanged( items ); +} + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/storecompactjob.h b/kdepim-runtime/resources/shared/filestore/storecompactjob.h new file mode 100644 index 00000000..2a5eccfe --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/storecompactjob.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_STORECOMPACTJOB_H +#define AKONADI_FILESTORE_STORECOMPACTJOB_H + +#include "job.h" + +#include +#include + +namespace Akonadi +{ + +namespace FileStore +{ + +/** + */ +class AKONADI_FILESTORE_EXPORT StoreCompactJob : public Job +{ + friend class AbstractJobSession; + + Q_OBJECT + + public: + explicit StoreCompactJob( AbstractJobSession *session = 0 ); + + virtual ~StoreCompactJob(); + + virtual bool accept( Visitor *visitor ); + + Item::List changedItems() const; + + Collection::List changedCollections() const; + + Q_SIGNALS: + void collectionsChanged( const Akonadi::Collection::List &collections ); + void itemsChanged( const Akonadi::Item::List &items ); + + private: + void handleCollectionsChanged( const Collection::List &collections ); + void handleItemsChanged( const Item::List &items ); + + private: + class Private; + Private *d; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/storeinterface.h b/kdepim-runtime/resources/shared/filestore/storeinterface.h new file mode 100644 index 00000000..9458dc9b --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/storeinterface.h @@ -0,0 +1,89 @@ +/* This file is part of the KDE project + Copyright (C) 2009,2010 Kevin Krammer + + 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_FILESTORE_STOREINTERFACE_H +#define AKONADI_FILESTORE_STOREINTERFACE_H + +#include "akonadi-filestore_export.h" + +// TODO not nice, collection fetch type should probably be in its own header +#include "collectionfetchjob.h" + +namespace Akonadi +{ + class Collection; + class Item; + +namespace FileStore +{ + class CollectionCreateJob; + class CollectionDeleteJob; + class CollectionFetchJob; + class CollectionModifyJob; + class CollectionMoveJob; + class ItemCreateJob; + class ItemDeleteJob; + class ItemFetchJob; + class ItemModifyJob; + class ItemMoveJob; + class StoreCompactJob; + +/** + */ +class AKONADI_FILESTORE_EXPORT StoreInterface +{ + public: + virtual ~StoreInterface() {} + + virtual Collection topLevelCollection() const = 0; + + virtual CollectionCreateJob *createCollection( const Collection &collection, const Collection &targetParent ) = 0; + + virtual CollectionFetchJob *fetchCollections( const Collection &collection, CollectionFetchJob::Type type = CollectionFetchJob::FirstLevel ) const = 0; + + virtual CollectionDeleteJob *deleteCollection( const Collection &collection ) = 0; + + virtual CollectionModifyJob *modifyCollection( const Collection &collection ) = 0; + + virtual CollectionMoveJob *moveCollection( const Collection &collection, const Collection &targetParent ) = 0; + + virtual ItemFetchJob *fetchItems( const Collection &collection ) const = 0; + + virtual ItemFetchJob *fetchItem( const Item &item ) const = 0; + + virtual ItemCreateJob *createItem( const Item &item, const Collection &collection ) = 0; + + virtual ItemModifyJob *modifyItem( const Item &item ) = 0; + + virtual ItemDeleteJob *deleteItem( const Item &item ) = 0; + + virtual ItemMoveJob *moveItem( const Item &item, const Collection &targetParent ) = 0; + + virtual StoreCompactJob *compactStore() = 0; + + protected: + virtual void setTopLevelCollection( const Collection &collection ) = 0; +}; + +} +} + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/filestore/tests/CMakeLists.txt b/kdepim-runtime/resources/shared/filestore/tests/CMakeLists.txt new file mode 100644 index 00000000..3652be20 --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +if(${EXECUTABLE_OUTPUT_PATH}) + set( PREVIOUS_EXEC_OUTPUT_PATH ${EXECUTABLE_OUTPUT_PATH} ) +else() + set( PREVIOUS_EXEC_OUTPUT_PATH . ) +endif() +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +include_directories( + ${AKONADI_INCLUDE_DIR} + ${KDE4_INCLUDES} + ${KDEPIMLIBS_INCLUDE_DIR} + ${QT_INCLUDES} + ${Boost_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR}/../ + ${CMAKE_CURRENT_SOURCE_DIR} +) + +kde4_add_unit_test( abstractlocalstoretest abstractlocalstoretest.cpp ) +target_link_libraries( + abstractlocalstoretest + akonadi-filestore + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTTEST_LIBRARY} +) diff --git a/kdepim-runtime/resources/shared/filestore/tests/abstractlocalstoretest.cpp b/kdepim-runtime/resources/shared/filestore/tests/abstractlocalstoretest.cpp new file mode 100644 index 00000000..74ed543f --- /dev/null +++ b/kdepim-runtime/resources/shared/filestore/tests/abstractlocalstoretest.cpp @@ -0,0 +1,969 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Author: Kevin Krammer, krake@kdab.com + + 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 "abstractlocalstore.h" + +#include "collectioncreatejob.h" +#include "collectiondeletejob.h" +#include "collectionfetchjob.h" +#include "collectionmodifyjob.h" +#include "collectionmovejob.h" +#include "itemcreatejob.h" +#include "itemdeletejob.h" +#include "itemfetchjob.h" +#include "itemmodifyjob.h" +#include "itemmovejob.h" +#include "sessionimpls_p.h" +#include "storecompactjob.h" + +#include + +#include + +using namespace Akonadi; +using namespace Akonadi::FileStore; + +class TestStore : public AbstractLocalStore +{ + Q_OBJECT + + public: + TestStore() : mLastCheckedJob( 0 ), mLastProcessedJob( 0 ), mErrorCode( 0 ) {} + + public: + mutable Akonadi::FileStore::Job *mLastCheckedJob; + Akonadi::FileStore::Job *mLastProcessedJob; + + Collection mTopLevelCollection; + + int mErrorCode; + QString mErrorText; + + protected: + void processJob( Akonadi::FileStore::Job *job ); + + protected: + void setTopLevelCollection( const Collection &collection ) + { + mTopLevelCollection = collection; + + Collection modifiedCollection = collection; + modifiedCollection.setContentMimeTypes( QStringList() << Collection::mimeType() ); + + AbstractLocalStore::setTopLevelCollection( modifiedCollection ); + } + + void checkCollectionCreate( Akonadi::FileStore::CollectionCreateJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkCollectionDelete( Akonadi::FileStore::CollectionDeleteJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkCollectionFetch( Akonadi::FileStore::CollectionFetchJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkCollectionModify( Akonadi::FileStore::CollectionModifyJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkCollectionMove( Akonadi::FileStore::CollectionMoveJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkItemCreate( Akonadi::FileStore::ItemCreateJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkItemDelete( Akonadi::FileStore::ItemDeleteJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkItemFetch( Akonadi::FileStore::ItemFetchJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkItemModify( Akonadi::FileStore::ItemModifyJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkItemMove( Akonadi::FileStore::ItemMoveJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } + + void checkStoreCompact( Akonadi::FileStore::StoreCompactJob *job, int &errorCode, QString &errorText ) const + { + mLastCheckedJob = job; + errorCode = mErrorCode; + errorText = mErrorText; + } +}; + +void TestStore::processJob( Akonadi::FileStore::Job *job ) +{ + mLastProcessedJob = job; + + QCOMPARE( currentJob(), job ); + QVERIFY( job->error() == 0 ); + + if ( mErrorCode != 0 ) { + notifyError( mErrorCode, mErrorText ); + } +} + +class AbstractLocalStoreTest : public QObject +{ + Q_OBJECT + + public: + AbstractLocalStoreTest() : QObject(), mStore( 0 ) {} + ~AbstractLocalStoreTest() { delete mStore; } + + private: + TestStore *mStore; + + private Q_SLOTS: + void init(); + void testSetPath(); + void testCreateCollection(); + void testDeleteCollection(); + void testFetchCollection(); + void testModifyCollection(); + void testMoveCollection(); + void testFetchItems(); + void testFetchItem(); + void testCreateItem(); + void testDeleteItem(); + void testModifyItem(); + void testMoveItem(); + void testCompactStore(); +}; + +void AbstractLocalStoreTest::init() +{ + delete mStore; + mStore = new TestStore; +} + +void AbstractLocalStoreTest::testSetPath() +{ + const QString file = KRandom::randomString( 10 ); + const QString path = QLatin1String( "/tmp/test/" ) + file; + + // check that setTopLevelCollection() has been called + mStore->setPath( path ); + QCOMPARE( mStore->mTopLevelCollection.remoteId(), path ); + + // check that the modified collection is the one returned by topLevelCollection() + QVERIFY( mStore->mTopLevelCollection.contentMimeTypes().isEmpty() ); + QCOMPARE( mStore->topLevelCollection().remoteId(), path ); + QCOMPARE( mStore->topLevelCollection().contentMimeTypes(), QStringList() << Collection::mimeType() ); + QCOMPARE( mStore->topLevelCollection().name(), file ); + + // check that calling with the same path again, does not call the template method + mStore->mTopLevelCollection = Collection(); + mStore->setPath( path ); + QVERIFY( mStore->mTopLevelCollection.remoteId().isEmpty() ); + QCOMPARE( mStore->topLevelCollection().remoteId(), path ); + QCOMPARE( mStore->topLevelCollection().contentMimeTypes(), QStringList() << Collection::mimeType() ); + QCOMPARE( mStore->topLevelCollection().name(), file ); + + // check that calling with a different path works like the first call + const QString file2 = KRandom::randomString( 10 ); + const QString path2 = QLatin1String( "/tmp/test2/" ) + file2; + + mStore->setPath( path2 ); + QCOMPARE( mStore->mTopLevelCollection.remoteId(), path2 ); + QCOMPARE( mStore->topLevelCollection().remoteId(), path2 ); + QCOMPARE( mStore->topLevelCollection().contentMimeTypes(), QStringList() << Collection::mimeType() ); + QCOMPARE( mStore->topLevelCollection().name(), file2 ); +} + +void AbstractLocalStoreTest::testCreateCollection() +{ + CollectionCreateJob *job = 0; + + // test without setPath() + job = mStore->createCollection( Collection(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid collections + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->createCollection( Collection(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId), but invalid target parent + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->createCollection( collection, Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collections + Collection targetParent; + targetParent.setRemoteId( QLatin1String( "/tmp/test2" ) ); + job = mStore->createCollection( collection, targetParent ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->createCollection( collection, targetParent ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +void AbstractLocalStoreTest::testDeleteCollection() +{ + CollectionDeleteJob *job = 0; + + // test without setPath() + job = mStore->deleteCollection( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid collection + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->deleteCollection( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with ivalid collection (has remoteId, but no parent collection remoteId) + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->deleteCollection( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId and parent collection remoteId) + Collection parentCollection; + parentCollection.setRemoteId( QLatin1String( "/tmp/test" ) ); + collection.setParentCollection( parentCollection ); + job = mStore->deleteCollection( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->deleteCollection( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +void AbstractLocalStoreTest::testFetchCollection() +{ + Akonadi::FileStore::CollectionFetchJob *job = 0; + + // test without setPath() + job = mStore->fetchCollections( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid collection + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->fetchCollections( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId) + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->fetchCollections( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->fetchCollections( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); + + // test fetch of top level collection only + collection.setRemoteId( mStore->topLevelCollection().remoteId() ); + job = mStore->fetchCollections( collection, Akonadi::FileStore::CollectionFetchJob::Base ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); // job not handed to subclass because it is full processed in base class + QCOMPARE( job->collections().count(), 1 ); + QCOMPARE( job->collections()[ 0 ], mStore->topLevelCollection() ); +} + +void AbstractLocalStoreTest::testModifyCollection() +{ + Akonadi::FileStore::CollectionModifyJob *job = 0; + + // test without setPath() + job = mStore->modifyCollection( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid item + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->modifyCollection( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId, but no parent remoteId) + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->modifyCollection( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test with potentially valid collection (has remoteId and parent remoteId) + Collection parentCollection; + parentCollection.setRemoteId( QLatin1String( "/tmp/test" ) ); + collection.setParentCollection( parentCollection ); + job = mStore->modifyCollection( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->modifyCollection( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); +} + +void AbstractLocalStoreTest::testMoveCollection() +{ + CollectionMoveJob *job = 0; + + // test without setPath() + job = mStore->moveCollection( Collection(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid collections + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->moveCollection( Collection(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId and parent remoteId), but invalid target parent + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + Collection parentCollection; + parentCollection.setRemoteId( QLatin1String( "/tmp/test" ) ); + collection.setParentCollection( parentCollection ); + job = mStore->moveCollection( collection, Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with invalid parent collection, but with potentially valid collection and target parent + collection.setParentCollection( Collection() ); + job = mStore->moveCollection( collection, parentCollection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collections + Collection targetParent; + targetParent.setRemoteId( QLatin1String( "/tmp/test2" ) ); + collection.setParentCollection( parentCollection ); + job = mStore->moveCollection( collection, targetParent ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->moveCollection( collection, targetParent ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); +} + +void AbstractLocalStoreTest::testFetchItems() +{ + ItemFetchJob *job = 0; + + // test without setPath() + job = mStore->fetchItems( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid collection + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->fetchItems( Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId) + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->fetchItems( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->fetchItems( collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +void AbstractLocalStoreTest::testFetchItem() +{ + ItemFetchJob *job = 0; + + // test without setPath() + job = mStore->fetchItem( Item() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid item + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->fetchItem( Item() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid item (has remoteId) + Item item; + item.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->fetchItem( item ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->fetchItem( item ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +void AbstractLocalStoreTest::testCreateItem() +{ + Akonadi::FileStore::ItemCreateJob *job = 0; + + // test without setPath() + job = mStore->createItem( Item(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid collection + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->createItem( Item(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid collection (has remoteId) + Collection collection; + collection.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->createItem( Item(), collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->createItem( Item(), collection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +void AbstractLocalStoreTest::testDeleteItem() +{ + ItemDeleteJob *job = 0; + + // test without setPath() + job = mStore->deleteItem( Item() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid item + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->deleteItem( Item() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid item (has remoteId) + Item item; + item.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->deleteItem( item ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->deleteItem( item ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +void AbstractLocalStoreTest::testModifyItem() +{ + Akonadi::FileStore::ItemModifyJob *job = 0; + + // test without setPath() + job = mStore->modifyItem( Item() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid item + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->modifyItem( Item() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid item (has remoteId) + Item item; + item.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + job = mStore->modifyItem( item ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->modifyItem( item ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); +} + +void AbstractLocalStoreTest::testMoveItem() +{ + ItemMoveJob *job = 0; + + // test without setPath() + job = mStore->moveItem( Item(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path but with invalid item and collection + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->moveItem( Item(), Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid item (has remoteId and parent remoteId), but invalid target parent + Item item; + item.setRemoteId( QLatin1String( "/tmp/test/foo" ) ); + Collection parentCollection; + parentCollection.setRemoteId( QLatin1String( "/tmp/test" ) ); + item.setParentCollection( parentCollection ); + job = mStore->moveItem( item, Collection() ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with invalid parent collection, but with potentially valid item and target parent + item.setParentCollection( Collection() ); + job = mStore->moveItem( item, parentCollection ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidJobContext ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with potentially valid item and collections + Collection targetParent; + targetParent.setRemoteId( QLatin1String( "/tmp/test2" ) ); + item.setParentCollection( parentCollection ); + job = mStore->moveItem( item, targetParent ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->moveItem( item, targetParent ); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); +} + +void AbstractLocalStoreTest::testCompactStore() +{ + StoreCompactJob *job = 0; + + // test without setPath() + job = mStore->compactStore(); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), (int)Akonadi::FileStore::Job::InvalidStoreState ); + QVERIFY( !job->errorText().isEmpty() ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + + // test with path + mStore->setPath( QLatin1String( "/tmp/test" ) ); + job = mStore->compactStore(); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), 0 ); + QVERIFY( job->errorText().isEmpty() ); + + QVERIFY( job->exec() ); + QCOMPARE( mStore->mLastProcessedJob, job ); + mStore->mLastProcessedJob = 0; + + // test template check method + mStore->mErrorCode = KRandom::random() + 1; + mStore->mErrorText = KRandom::randomString( 10 ); + + job = mStore->compactStore(); + QVERIFY( job != 0 ); + QCOMPARE( mStore->mLastCheckedJob, job ); + QCOMPARE( job->error(), mStore->mErrorCode ); + QCOMPARE( job->errorText(), mStore->mErrorText ); + + QVERIFY( !job->exec() ); + QVERIFY( mStore->mLastProcessedJob == 0 ); + mStore->mErrorCode = 0; + mStore->mErrorText = QString(); +} + +QTEST_KDEMAIN( AbstractLocalStoreTest, NoGUI ) + +#include "abstractlocalstoretest.moc" + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kdepim-runtime/resources/shared/getcredentialsjob.cpp b/kdepim-runtime/resources/shared/getcredentialsjob.cpp new file mode 100644 index 00000000..e4401567 --- /dev/null +++ b/kdepim-runtime/resources/shared/getcredentialsjob.cpp @@ -0,0 +1,104 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Olivares * + * * + * 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 of the License, or (at KDE e.V's discretion) 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 "getcredentialsjob.h" + +#include +#include +#include + +#include + +#include +#include + +GetCredentialsJob::GetCredentialsJob(const Accounts::AccountId &id, QObject *parent) +: KJob(parent) +, m_id(id) +, m_manager(new Accounts::Manager(this)) +{ + +} + +void GetCredentialsJob::start() +{ + QMetaObject::invokeMethod(this, "getCredentials", Qt::QueuedConnection); +} + +void GetCredentialsJob::setServiceType(const QString &serviceType) +{ + m_serviceType = serviceType; +} + +void GetCredentialsJob::getCredentials() +{ + Accounts::Account *acc = m_manager->account(m_id); + if (!acc) { + setError(-1); + setErrorText(i18n("Could not find account")); + emitResult(); + return; + } + Accounts::AccountService *service = new Accounts::AccountService(acc, m_manager->service(m_serviceType), this); + + Accounts::AuthData authData = service->authData(); + m_authData = authData.parameters(); + SignOn::Identity *identity = SignOn::Identity::existingIdentity(authData.credentialsId(), this); + + if (!identity) { + setError(-1); + setErrorText(i18n("Could not find credentials")); + emitResult(); + return; + } + + m_authData[QLatin1String("AccountUsername")] = acc->value(QLatin1String("username")).toString(); + QPointer authSession = identity->createSession(authData.method()); + + connect(authSession, SIGNAL(response(SignOn::SessionData)), + SLOT(sessionResponse(SignOn::SessionData))); + connect(authSession, SIGNAL(error(SignOn::Error)), + SLOT(sessionError(SignOn::Error))); + + authSession->process(authData.parameters(), authData.mechanism()); +} + +void GetCredentialsJob::sessionResponse(const SignOn::SessionData &data) +{ + m_sessionData = data; + emitResult(); +} + +void GetCredentialsJob::sessionError(const SignOn::Error &error) +{ + kDebug() << error.message(); + setError(-1); + setErrorText(error.message()); + emitResult(); +} + +Accounts::AccountId GetCredentialsJob::accountId() const +{ + return m_id; +} + +QVariantMap GetCredentialsJob::credentialsData() const +{ + return m_sessionData.toMap().unite(m_authData); +} diff --git a/kdepim-runtime/resources/shared/getcredentialsjob.h b/kdepim-runtime/resources/shared/getcredentialsjob.h new file mode 100644 index 00000000..693a031f --- /dev/null +++ b/kdepim-runtime/resources/shared/getcredentialsjob.h @@ -0,0 +1,61 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Olivares * + * * + * 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 of the License, or (at KDE e.V's discretion) 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 GET_CREDENTIALS_JOB_H +#define GET_CREDENTIALS_JOB_H + +#include + +#include +#include +#include + +namespace Accounts { + class Manager; +}; +namespace SignOn { + class Error; +}; + +class GetCredentialsJob : public KJob +{ + Q_OBJECT +public: + explicit GetCredentialsJob(const Accounts::AccountId &id, QObject *parent = 0); + virtual void start(); + + void setServiceType(const QString &serviceType); + + QVariantMap credentialsData() const; + Accounts::AccountId accountId() const; + +private Q_SLOTS: + void getCredentials(); + void sessionResponse(const SignOn::SessionData &data); + void sessionError(const SignOn::Error &error); + +private: + QString m_serviceType; + Accounts::AccountId m_id; + QVariantMap m_authData; + Accounts::Manager *m_manager; + SignOn::SessionData m_sessionData; +}; + +#endif //GET_CREDENTIALS_JOB_H diff --git a/kdepim-runtime/resources/shared/imapaclattribute.cpp b/kdepim-runtime/resources/shared/imapaclattribute.cpp new file mode 100644 index 00000000..ad4fe2cf --- /dev/null +++ b/kdepim-runtime/resources/shared/imapaclattribute.cpp @@ -0,0 +1,125 @@ +/* + Copyright (C) 2009 Kevin Ottens + + 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 "imapaclattribute.h" + +#include + +using namespace Akonadi; + +ImapAclAttribute::ImapAclAttribute() +{ +} + +ImapAclAttribute::ImapAclAttribute( const QMap &rights, + const QMap &oldRights ) + : mRights( rights ), mOldRights( oldRights ) +{ +} + +void ImapAclAttribute::setRights( const QMap &rights ) +{ + mOldRights = mRights; + mRights = rights; +} + +QMap ImapAclAttribute::rights() const +{ + return mRights; +} + +QMap ImapAclAttribute::oldRights() const +{ + return mOldRights; +} + +QByteArray ImapAclAttribute::type() const +{ + return "imapacl"; +} + +Akonadi::Attribute* ImapAclAttribute::clone() const +{ + return new ImapAclAttribute( mRights, mOldRights ); +} + +QByteArray ImapAclAttribute::serialized() const +{ + QByteArray result = ""; + + bool added = false; + foreach ( const QByteArray &id, mRights.keys() ) { + result+= id; + result+= ' '; + result+= KIMAP::Acl::rightsToString( mRights[id] ); + result+= " % "; // We use this separator as '%' is not allowed in keys or values + added = true; + } + + if ( added ) + result.chop( 3 ); + + result+= " %% "; + + added = false; + foreach ( const QByteArray &id, mOldRights.keys() ) { + result+= id; + result+= ' '; + result+= KIMAP::Acl::rightsToString( mOldRights[id] ); + result+= " % "; // We use this separator as '%' is not allowed in keys or values + added = true; + } + + if ( added ) + result.chop( 3 ); + + return result; +} + +static void fillRightsMap( const QList &rights, QMap &map ) +{ + foreach ( const QByteArray &right, rights ) { + const QByteArray trimmed = right.trimmed(); + const int wsIndex = trimmed.indexOf( ' ' ); + const QByteArray id = trimmed.mid( 0, wsIndex ).trimmed(); + if ( !id.isEmpty() ) { + const bool noValue = ( wsIndex == -1 ); + if ( noValue ) { + map[id] = KIMAP::Acl::None; + } else { + const QByteArray value = trimmed.mid( wsIndex + 1, right.length() - wsIndex ).trimmed(); + map[id] = KIMAP::Acl::rightsFromString( value ); + } + } + } +} + +void ImapAclAttribute::deserialize( const QByteArray &data ) +{ + mRights.clear(); + mOldRights.clear(); + const int pos = data.indexOf( " %% " ); + if ( pos == -1 ) + return; + + const QByteArray leftPart = data.left( pos ); + const QByteArray rightPart = data.mid( pos + 4 ); + fillRightsMap( leftPart.split( '%' ), mRights ); + fillRightsMap( rightPart.split( '%' ), mOldRights ); +} diff --git a/kdepim-runtime/resources/shared/imapaclattribute.h b/kdepim-runtime/resources/shared/imapaclattribute.h new file mode 100644 index 00000000..8e4b83cb --- /dev/null +++ b/kdepim-runtime/resources/shared/imapaclattribute.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2009 Kevin Ottens + + 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_IMAPACLATTRIBUTE_H +#define AKONADI_IMAPACLATTRIBUTE_H + +#include + +#include + +#include + +namespace Akonadi { + +class ImapAclAttribute : public Akonadi::Attribute +{ + public: + ImapAclAttribute(); + ImapAclAttribute( const QMap &rights, + const QMap &oldRights ); + void setRights( const QMap &rights ); + QMap rights() const; + QMap oldRights() const; + virtual QByteArray type() const; + virtual Attribute *clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + + private: + QMap mRights; + QMap mOldRights; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/imapquotaattribute.cpp b/kdepim-runtime/resources/shared/imapquotaattribute.cpp new file mode 100644 index 00000000..879e5cd4 --- /dev/null +++ b/kdepim-runtime/resources/shared/imapquotaattribute.cpp @@ -0,0 +1,193 @@ +/* + Copyright (C) 2009 Kevin Ottens + + 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 "imapquotaattribute.h" + +#include +#include +#include + +#include + +using namespace Akonadi; + +ImapQuotaAttribute::ImapQuotaAttribute() +{ +} + +Akonadi::ImapQuotaAttribute::ImapQuotaAttribute( const QList &roots, + const QList< QMap > &limits, + const QList< QMap > &usages ) + : mRoots( roots ), mLimits( limits ), mUsages( usages ) +{ + Q_ASSERT( roots.size()==limits.size() ); + Q_ASSERT( roots.size()==usages.size() ); +} + +void Akonadi::ImapQuotaAttribute::setQuotas( const QList &roots, + const QList< QMap > &limits, + const QList< QMap > &usages ) +{ + Q_ASSERT( roots.size()==limits.size() ); + Q_ASSERT( roots.size()==usages.size() ); + + mRoots = roots; + mLimits = limits; + mUsages = usages; +} + +QList Akonadi::ImapQuotaAttribute::roots() const +{ + return mRoots; +} + +QList< QMap > Akonadi::ImapQuotaAttribute::limits() const +{ + return mLimits; +} + +QList< QMap > Akonadi::ImapQuotaAttribute::usages() const +{ + return mUsages; +} + +QByteArray ImapQuotaAttribute::type() const +{ + return "imapquota"; +} + +Akonadi::Attribute* ImapQuotaAttribute::clone() const +{ + return new ImapQuotaAttribute( mRoots, mLimits, mUsages ); +} + +QByteArray ImapQuotaAttribute::serialized() const +{ + typedef QMap QuotaMap; + QByteArray result = ""; + + // First the roots list + foreach ( const QByteArray &root, mRoots ) { + result+=root+" % "; + } + result.chop( 3 ); + + result+= " %%%% "; // Members separator + + // Then the limit maps list + for ( int i=0; i limits = mLimits[i]; + foreach ( const QByteArray &key, limits.keys() ) { + result+= key; + result+= " % "; // We use this separator as '%' is not allowed in keys or values + result+= QByteArray::number( limits[key] ); + result+= " %% "; // Pairs separator + } + result.chop( 4 ); + result+= " %%% "; // Maps separator + } + result.chop( 5 ); + + result+= " %%%% "; // Members separator + + // Then the usage maps list + for ( int i=0; i usages = mUsages[i]; + foreach ( const QByteArray &key, usages.keys() ) { + result+= key; + result+= " % "; // We use this separator as '%' is not allowed in keys or values + result+= QByteArray::number( usages[key] ); + result+= " %% "; // Pairs separator + } + result.chop( 4 ); + result+= " %%% "; // Maps separator + } + result.chop( 5 ); + + return result; +} + +void ImapQuotaAttribute::deserialize( const QByteArray &data ) +{ + mRoots.clear(); + mLimits.clear(); + mUsages.clear(); + + // Nothing was saved. + if ( data.trimmed().isEmpty() ) { + return; + } + + QString string = QString::fromUtf8(data); // QByteArray has no proper split, so we're forced to convert to QString... + + QStringList members = string.split( QLatin1String("%%%%") ); + + // We expect exactly three members (roots, limits and usages), otherwise something is funky + if ( members.size() != 3 ) { + kWarning() << "We didn't find exactly three members in this quota serialization"; + return; + } + + QStringList roots = members[0].trimmed().split( QLatin1String(" % ") ); + foreach ( const QString &root, roots ) { + mRoots << root.trimmed().toUtf8(); + } + + QStringList allLimits = members[1].trimmed().split( QLatin1String("%%%") ); + + foreach ( const QString &limits, allLimits ) { + QMap limitsMap; + QStringList strLines = limits.split( QLatin1String("%%") ); + QList lines; + foreach ( const QString &strLine, strLines ) { + lines << strLine.trimmed().toUtf8(); + } + + foreach ( const QByteArray &line, lines ) { + QByteArray trimmed = line.trimmed(); + int wsIndex = trimmed.indexOf( '%' ); + const QByteArray key = trimmed.mid( 0, wsIndex ).trimmed(); + const QByteArray value = trimmed.mid( wsIndex+1, line.length()-wsIndex ).trimmed(); + limitsMap[key] = value.toLongLong(); + } + + mLimits << limitsMap; + } + + QStringList allUsages = members[2].trimmed().split( QLatin1String("%%%") ); + + foreach ( const QString &usages, allUsages ) { + QMap usagesMap; + QStringList strLines = usages.split( QLatin1String("%%") ); + QList lines; + foreach ( const QString &strLine, strLines ) { + lines << strLine.trimmed().toUtf8(); + } + + foreach ( const QByteArray &line, lines ) { + QByteArray trimmed = line.trimmed(); + int wsIndex = trimmed.indexOf( '%' ); + const QByteArray key = trimmed.mid( 0, wsIndex ).trimmed(); + const QByteArray value = trimmed.mid( wsIndex+1, line.length()-wsIndex ).trimmed(); + usagesMap[key] = value.toLongLong(); + } + + mUsages << usagesMap; + } +} diff --git a/kdepim-runtime/resources/shared/imapquotaattribute.h b/kdepim-runtime/resources/shared/imapquotaattribute.h new file mode 100644 index 00000000..ea574424 --- /dev/null +++ b/kdepim-runtime/resources/shared/imapquotaattribute.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2009 Kevin Ottens + + 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_IMAPQUOTAATTRIBUTE_H +#define AKONADI_IMAPQUOTAATTRIBUTE_H + +#include + +#include + +namespace Akonadi { + +class ImapQuotaAttribute : public Akonadi::Attribute +{ + public: + ImapQuotaAttribute(); + ImapQuotaAttribute( const QList &roots, + const QList< QMap > &limits, + const QList< QMap > &usages ); + + void setQuotas( const QList &roots, + const QList< QMap > &limits, + const QList< QMap > &usages ); + + QList roots() const; + QList< QMap > limits() const; + QList< QMap > usages() const; + + virtual QByteArray type() const; + virtual Attribute *clone() const; + virtual QByteArray serialized() const; + virtual void deserialize( const QByteArray &data ); + + private: + QList mRoots; + QList< QMap > mLimits; + QList< QMap > mUsages; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/singlefileresource.h b/kdepim-runtime/resources/shared/singlefileresource.h new file mode 100644 index 00000000..235993dc --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresource.h @@ -0,0 +1,365 @@ +/* + Copyright (c) 2008 Bertjan Broeksema + Copyright (c) 2008 Volker Krause + Copyright (c) 2010 David Jarvie + + 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_SINGLEFILERESOURCE_H +#define AKONADI_SINGLEFILERESOURCE_H + +#include "singlefileresourcebase.h" +#include "singlefileresourceconfigdialog.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace Akonadi +{ + +/** + * Base class for single file based resources. + */ +template +class SingleFileResource : public SingleFileResourceBase +{ + public: + SingleFileResource( const QString &id ) + : SingleFileResourceBase( id ) + , mSettings( new Settings( componentData().config() ) ) + { + // The resource needs network when the path refers to a non local file. + setNeedsNetwork( !KUrl( mSettings->path() ).isLocalFile() ); + } + ~SingleFileResource() + { + delete mSettings; + } + + /** + * Read changes from the backend file. + */ + void readFile( bool taskContext = false ) + { + if ( KDirWatch::self()->contains( mCurrentUrl.toLocalFile() ) ) + KDirWatch::self()->removeFile( mCurrentUrl.toLocalFile() ); + + if ( mSettings->path().isEmpty() ) { + const QString message = i18n( "No file selected." ); + kWarning() << message; + emit status( NotConfigured, i18n("The resource not configured yet") ); + if ( taskContext ) + cancelTask(); + return; + } + + mCurrentUrl = KUrl( mSettings->path() ); + if ( mCurrentHash.isEmpty() ) { + // First call to readFile() lets see if there is a hash stored in a + // cache file. If both are the same than there is no need to load the + // file and synchronize the resource. + mCurrentHash = loadHash(); + } + + if ( mCurrentUrl.isLocalFile() ) + { + if ( mSettings->displayName().isEmpty() + && ( name().isEmpty() || name() == identifier() ) && !mCurrentUrl.isEmpty() ) + setName( mCurrentUrl.fileName() ); + + // check if the file does not exist yet, if so, create it + if ( !QFile::exists( mCurrentUrl.toLocalFile() ) ) { + QFile f( mCurrentUrl.toLocalFile() ); + + // first create try to create the directory the file should be located in + QDir dir = QFileInfo(f).dir(); + if ( ! dir.exists() ) { + dir.mkpath( dir.path() ); + } + + if ( f.open( QIODevice::WriteOnly ) && f.resize( 0 ) ) { + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + } else { + const QString message = i18n( "Could not create file '%1'.", mCurrentUrl.prettyUrl() ); + kWarning() << message; + emit status( Broken, message ); + mCurrentUrl.clear(); + if ( taskContext ) + cancelTask(); + return; + } + } + + // Cache, because readLocalFile will clear mCurrentUrl on failure. + const QString localFileName = mCurrentUrl.toLocalFile(); + if ( !readLocalFile( mCurrentUrl.toLocalFile() ) ) { + const QString message = i18n( "Could not read file '%1'", localFileName ); + kWarning() << message; + emit status( Broken, message ); + if ( taskContext ) + cancelTask(); + return; + } + + if ( mSettings->monitorFile() ) + KDirWatch::self()->addFile( mCurrentUrl.toLocalFile() ); + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + } + else // !mCurrentUrl.isLocalFile() + { + if ( mDownloadJob ) + { + const QString message = i18n( "Another download is still in progress." ); + kWarning() << message; + emit error( message ); + if ( taskContext ) + cancelTask(); + return; + } + + if ( mUploadJob ) + { + const QString message = i18n( "Another file upload is still in progress." ); + kWarning() << message; + emit error( message ); + if ( taskContext ) + cancelTask(); + return; + } + + KGlobal::ref(); + + // NOTE: Test what happens with remotefile -> save, close before save is finished. + mDownloadJob = KIO::file_copy( mCurrentUrl, KUrl( cacheFile() ), -1, KIO::Overwrite | KIO::DefaultFlags | KIO::HideProgressInfo ); + connect( mDownloadJob, SIGNAL(result(KJob*)), + SLOT(slotDownloadJobResult(KJob*)) ); + connect( mDownloadJob, SIGNAL(percent(KJob*,ulong)), + SLOT(handleProgress(KJob*,ulong)) ); + + emit status( Running, i18n( "Downloading remote file." ) ); + } + + const QString display = mSettings->displayName(); + if ( !display.isEmpty() ) { + setName( display ); + } + } + + void writeFile( const QVariant &task_context ) + { + writeFile( task_context.canConvert() && task_context.toBool() ); + } + + /** + * Write changes to the backend file. + */ + void writeFile( bool taskContext = false ) + { + if ( mSettings->readOnly() ) { + const QString message = i18n( "Trying to write to a read-only file: '%1'.", mSettings->path() ); + kWarning() << message; + emit error( message ); + if ( taskContext ) + cancelTask(); + return; + } + + // We don't use the Settings::self()->path() here as that might have changed + // and in that case it would probably cause data lose. + if ( mCurrentUrl.isEmpty() ) { + const QString message = i18n( "No file specified." ); + kWarning() << message; + emit status( Broken, message ); + if ( taskContext ) + cancelTask(); + return; + } + + if ( mCurrentUrl.isLocalFile() ) { + KDirWatch::self()->stopScan(); + const bool writeResult = writeToFile( mCurrentUrl.toLocalFile() ); + // Update the hash so we can detect at fileChanged() if the file actually + // did change. + mCurrentHash = calculateHash( mCurrentUrl.toLocalFile() ); + saveHash( mCurrentHash ); + KDirWatch::self()->startScan(); + if ( !writeResult ) + { + kWarning() << "Error writing to file..."; + if ( taskContext ) + cancelTask(); + return; + } + emit status( Idle, i18nc( "@info:status", "Ready" ) ); + + } else { + // Check if there is a download or an upload in progress. + if ( mDownloadJob ) { + const QString message = i18n( "A download is still in progress." ); + kWarning() << message; + emit error( message ); + if ( taskContext ) + cancelTask(); + return; + } + + if ( mUploadJob ) { + const QString message = i18n( "Another file upload is still in progress." ); + kWarning() << message; + emit error( message ); + if ( taskContext ) + cancelTask(); + return; + } + + // Write te items to the locally cached file. + if ( !writeToFile( cacheFile() ) ) + { + kWarning() << "Error writing to file"; + if ( taskContext ) + cancelTask(); + return; + } + + // Update the hash so we can detect at fileChanged() if the file actually + // did change. + mCurrentHash = calculateHash( cacheFile() ); + saveHash( mCurrentHash ); + + KGlobal::ref(); + // Start a job to upload the locally cached file to the remote location. + mUploadJob = KIO::file_copy( KUrl( cacheFile() ), mCurrentUrl, -1, KIO::Overwrite | KIO::DefaultFlags | KIO::HideProgressInfo ); + connect( mUploadJob, SIGNAL(result(KJob*)), + SLOT(slotUploadJobResult(KJob*)) ); + connect( mUploadJob, SIGNAL(percent(KJob*,ulong)), + SLOT(handleProgress(KJob*,ulong)) ); + + emit status( Running, i18n( "Uploading cached file to remote location." ) ); + } + if ( taskContext ) + taskDone(); + } + + virtual void collectionChanged( const Collection &collection ) + { + QString newName; + if ( collection.hasAttribute() ) { + EntityDisplayAttribute *attr = collection.attribute(); + newName = attr->displayName(); + } + const QString oldName = mSettings->displayName(); + if ( newName != oldName ) { + mSettings->setDisplayName( newName ); + mSettings->writeConfig(); + } + SingleFileResourceBase::collectionChanged( collection ); + } + + virtual Collection rootCollection() const + { + Collection c; + c.setParentCollection( Collection::root() ); + c.setRemoteId( mSettings->path() ); + const QString display = mSettings->displayName(); + c.setName( display.isEmpty() ? identifier() : display ); + QStringList mimeTypes; + c.setContentMimeTypes( mSupportedMimetypes ); + if ( readOnly() ) { + c.setRights( Collection::CanChangeCollection ); + } else { + Collection::Rights rights; + rights |= Collection::CanChangeItem; + rights |= Collection::CanCreateItem; + rights |= Collection::CanDeleteItem; + rights |= Collection::CanChangeCollection; + c.setRights( rights ); + } + EntityDisplayAttribute* attr = c.attribute( Collection::AddIfMissing ); + attr->setDisplayName( name() ); + attr->setIconName( mCollectionIcon ); + return c; + } + + public Q_SLOTS: + /** + * Display the configuration dialog for the resource. + */ + void configure( WId windowId ) + { + QPointer > dlg + = new SingleFileResourceConfigDialog( windowId, mSettings ); + customizeConfigDialog( dlg ); + if ( dlg->exec() == QDialog::Accepted ) { + if ( dlg ) { // in case is got destroyed + configDialogAcceptedActions( dlg ); + } + reloadFile(); + synchronizeCollectionTree(); + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } + delete dlg; + } + + protected: + /** + * Implement in derived classes to customize the configuration dialog + * before it is displayed. + */ + virtual void customizeConfigDialog( SingleFileResourceConfigDialog* dlg ) + { + Q_UNUSED(dlg); + } + + /** + * Implement in derived classes to do things when the configuration dialog + * has been accepted, before reloadFile() is called. + */ + virtual void configDialogAcceptedActions( SingleFileResourceConfigDialog* dlg ) + { + Q_UNUSED(dlg); + } + + void retrieveCollections() + { + Collection::List list; + list << rootCollection(); + collectionsRetrieved( list ); + } + + bool readOnly() const + { + return mSettings->readOnly(); + } + + protected: + Settings *mSettings; +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/singlefileresourcebase.cpp b/kdepim-runtime/resources/shared/singlefileresourcebase.cpp new file mode 100644 index 00000000..b0f736a3 --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourcebase.cpp @@ -0,0 +1,280 @@ +/* + Copyright (c) 2008 Bertjan Broeksema + Copyright (c) 2008 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 "singlefileresourcebase.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Akonadi; + +SingleFileResourceBase::SingleFileResourceBase( const QString & id ) + : ResourceBase( id ), mDownloadJob( 0 ), mUploadJob( 0 ) +{ + connect( this, SIGNAL(reloadConfiguration()), SLOT(reloadFile()) ); + QTimer::singleShot( 0, this, SLOT(readFile()) ); + + changeRecorder()->itemFetchScope().fetchFullPayload(); + changeRecorder()->fetchCollection( true ); + + connect( changeRecorder(), SIGNAL(changesAdded()), SLOT(scheduleWrite()) ); + + connect( KDirWatch::self(), SIGNAL(dirty(QString)), SLOT(fileChanged(QString)) ); + connect( KDirWatch::self(), SIGNAL(created(QString)), SLOT(fileChanged(QString)) ); + + KGlobal::locale()->insertCatalog( QLatin1String("akonadi_singlefile_resource") ); +} + +KSharedConfig::Ptr SingleFileResourceBase::runtimeConfig() const +{ + return KSharedConfig::openConfig( name() + QLatin1String("rc"), KConfig::SimpleConfig, "cache" ); +} + +bool SingleFileResourceBase::readLocalFile( const QString &fileName ) +{ + const QByteArray newHash = calculateHash( fileName ); + if ( mCurrentHash != newHash ) { + if ( !mCurrentHash.isEmpty() ) { + // There was a hash stored in the config file or a chached one from + // a previous read and it is different from the hash we just read. + handleHashChange(); + } + + if ( !readFromFile( fileName ) ) { + mCurrentHash.clear(); + mCurrentUrl = KUrl(); // reset so we don't accidentally overwrite the file + return false; + } + + if ( mCurrentHash.isEmpty() ) { + // This is the very first time we read the file so make sure to store + // the hash as writeFile() might not be called at all (e.g in case of + // read only resources). + saveHash( newHash ); + } + + // Only synchronize when the contents of the file have changed wrt to + // the last time this file was read. Before we synchronize first + // clearCache is called to make sure that the cached items get the + // actual values as present in the file. + invalidateCache( rootCollection() ); + synchronize(); + } else { + // The hash didn't change, notify implementing resources about the + // actual file name that should be used when reading the file is + // necessary. + setLocalFileName( fileName ); + } + + mCurrentHash = newHash; + return true; +} + +void SingleFileResourceBase::setLocalFileName( const QString &fileName ) +{ + // Default implementation. + if ( !readFromFile( fileName ) ) { + mCurrentHash.clear(); + mCurrentUrl = KUrl(); // reset so we don't accidentally overwrite the file + return; + } +} + +QString SingleFileResourceBase::cacheFile() const +{ + return KStandardDirs::locateLocal( "cache", QLatin1String("akonadi/") + identifier() ); +} + +QByteArray SingleFileResourceBase::calculateHash( const QString &fileName ) const +{ + QFile file( fileName ); + if ( !file.exists() ) + return QByteArray(); + + if ( !file.open( QIODevice::ReadOnly ) ) + return QByteArray(); + + QCryptographicHash hash( QCryptographicHash::Md5 ); + qint64 blockSize = 512 * 1024; // Read blocks of 512K + + while ( !file.atEnd() ) { + hash.addData( file.read( blockSize ) ); + } + + file.close(); + + return hash.result(); +} + +void SingleFileResourceBase::handleHashChange() +{ + // Default implementation does nothing. + kDebug() << "The hash has changed."; +} + +QByteArray SingleFileResourceBase::loadHash() const +{ + KConfigGroup generalGroup( runtimeConfig(), "General" ); + return QByteArray::fromHex( generalGroup.readEntry( "hash", QByteArray() ) ); +} + +void SingleFileResourceBase::saveHash( const QByteArray &hash ) const +{ + KSharedConfig::Ptr config = runtimeConfig(); + KConfigGroup generalGroup( config, "General" ); + generalGroup.writeEntry( "hash", hash.toHex() ); + config->sync(); +} + +void SingleFileResourceBase::setSupportedMimetypes( const QStringList & mimeTypes, const QString &icon ) +{ + mSupportedMimetypes = mimeTypes; + mCollectionIcon = icon; +} + +void SingleFileResourceBase::collectionChanged( const Akonadi::Collection & collection ) +{ + const QString newName = collection.displayName(); + if ( collection.hasAttribute() ) { + EntityDisplayAttribute *attr = collection.attribute(); + if ( !attr->iconName().isEmpty() ) + mCollectionIcon = attr->iconName(); + } + + if ( newName != name() ) + setName( newName ); + + changeCommitted( collection ); +} + +void SingleFileResourceBase::reloadFile() +{ + // Update the network setting. + setNeedsNetwork( !mCurrentUrl.isEmpty() && !mCurrentUrl.isLocalFile() ); + + // if we have something loaded already, make sure we write that back in case + // the settings changed + if ( !mCurrentUrl.isEmpty() && !readOnly() ) + writeFile(); + + readFile(); + + // name or rights could have changed + synchronizeCollectionTree(); +} + +void SingleFileResourceBase::handleProgress( KJob *, unsigned long pct ) +{ + emit percent( pct ); +} + +void SingleFileResourceBase::fileChanged( const QString & fileName ) +{ + if ( fileName != mCurrentUrl.toLocalFile() ) + return; + + const QByteArray newHash = calculateHash( fileName ); + + // There is only a need to synchronize when the file was changed by another + // process. At this point we're sure that it is the file that the resource + // was configured for because of the check at the beginning of this function. + if ( newHash == mCurrentHash ) + return; + + if ( !mCurrentUrl.isEmpty() ) { + QString lostFoundFileName; + const KUrl prevUrl = mCurrentUrl; + int i = 0; + do { + lostFoundFileName = KStandardDirs::locateLocal( "data", identifier() + QDir::separator() + + prevUrl.fileName() + QLatin1Char('-') + QString::number( ++i ) ); + } while ( KStandardDirs::exists( lostFoundFileName ) ); + + // create the directory if it doesn't exist yet + QDir dir = QFileInfo(lostFoundFileName).dir(); + if ( !dir.exists() ) + dir.mkpath( dir.path() ); + + mCurrentUrl = KUrl( lostFoundFileName ); + writeFile(); + mCurrentUrl = prevUrl; + + const QString message = i18n( "The file '%1' was changed on disk. " + "As a precaution, a backup of its previous contents has been created at '%2'.", + prevUrl.prettyUrl(), KUrl( lostFoundFileName ).prettyUrl() ); + emit warning( message ); + } + + readFile(); + + // Notify resources, so that information bound to the file like indexes etc. + // can be updated. + handleHashChange(); + invalidateCache( rootCollection() ); + synchronize(); +} + +void SingleFileResourceBase::scheduleWrite() +{ + scheduleCustomTask(this, "writeFile", QVariant(true), ResourceBase::AfterChangeReplay); +} + +void SingleFileResourceBase::slotDownloadJobResult( KJob *job ) +{ + if ( job->error() && job->error() != KIO::ERR_DOES_NOT_EXIST ) { + const QString message = i18n( "Could not load file '%1'.", mCurrentUrl.prettyUrl() ); + kWarning() << message; + emit status( Broken, message ); + } else { + readLocalFile( KUrl( cacheFile() ).toLocalFile() ); + } + + mDownloadJob = 0; + KGlobal::deref(); + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); +} + +void SingleFileResourceBase::slotUploadJobResult( KJob *job ) +{ + if ( job->error() ) { + const QString message = i18n( "Could not save file '%1'.", mCurrentUrl.prettyUrl() ); + kWarning() << message; + emit status( Broken, message ); + } + + mUploadJob = 0; + KGlobal::deref(); + + emit status( Idle, i18nc( "@info:status", "Ready" ) ); +} + diff --git a/kdepim-runtime/resources/shared/singlefileresourcebase.h b/kdepim-runtime/resources/shared/singlefileresourcebase.h new file mode 100644 index 00000000..983ddbe6 --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourcebase.h @@ -0,0 +1,188 @@ +/* + Copyright (c) 2008 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_SINGLEFILERESOURCEBASE_H +#define AKONADI_SINGLEFILERESOURCEBASE_H + +#include + +#include +#include +#include + +namespace KIO { +class FileCopyJob; +class Job; +} + +namespace Akonadi +{ + +/** + * Base class for single file based resources. + * @see SingleFileResource + */ +class SingleFileResourceBase : public ResourceBase, public AgentBase::Observer +{ + Q_OBJECT + public: + explicit SingleFileResourceBase( const QString &id ); + + /** + * Set the mimetypes supported by this resource and an optional icon for the collection. + */ + void setSupportedMimetypes( const QStringList &mimeTypes, const QString &icon = QString() ); + + void collectionChanged( const Akonadi::Collection &collection ); + + public Q_SLOTS: + void reloadFile(); + + /* + * Read the current state from a file. This can happen + * from direct callers, or as part of a scheduled task. + * @p taskContext specifies whether the method is being + * called from within a task or not. + */ + virtual void readFile( bool taskContext = false ) = 0; + /* + * Writes the current state out to a file. This can happen + * from direct callers, or as part of a scheduled task. + * @p taskContext specifies whether the method is being + * called from within a task or not. + */ + virtual void writeFile( bool taskContext = false ) = 0; + + /* + * Same method as above, but uses a QVariant so it can + * be called from Akonadi::ResourceScheduler. + */ + virtual void writeFile( const QVariant &taskContext ) = 0; + + protected: + /** + * Returns a pointer to the KConfig object which is used to store runtime + * information of the resource. + */ + KSharedConfig::Ptr runtimeConfig() const; + + + /** + * Handles everything needed when the hash of a file has changed between the + * last write and the first read. This stores the new hash in a config file + * and notifies implementing resources to handle a hash change if the + * previous known hash was not empty. Finally this method clears the cache + * and calls synchronize. + * Returns true on succes, false otherwise. + */ + bool readLocalFile( const QString &fileName ); + + /** + * Reimplement to read your data from the given file. + * The file is always local, loading from the network is done + * automatically if needed. + */ + virtual bool readFromFile( const QString &fileName ) = 0; + + /** + * Reimplement to write your data to the given file. + * The file is always local, storing back to the network url is done + * automatically when needed. + */ + virtual bool writeToFile( const QString &fileName ) = 0; + + /** + * It is not always needed to parse the file when a resources is started. + * (e.g. When the hash of the file is the same as the last time the resource + * has written changes to the file). In this case setActualFileName is + * called so that the implementing resource does know which file to read + * when it actually needs to read the file. + * + * The default implementation will just call readFromFile( fileName ), so + * implementing resources will have to explictly reimplement this method to + * actually get any profit of this. + * + * @p fileName This will always be a path to a local file. + */ + virtual void setLocalFileName( const QString &fileName ); + + /** + * Generates the full path for the cache file in the case that a remote file + * is used. + */ + QString cacheFile() const; + + /** + * Calculates an MD5 hash for given file. If the file does not exists + * or the path is empty, this will return an empty QByteArray. + */ + QByteArray calculateHash( const QString &fileName ) const; + + /** + * This method is called when the hash of the file has changed between the + * last writeFile() and a readFile() call. This means that the file was + * changed by another program. + * + * Note: This method is not called when the last known hash is + * empty. In that case it is assumed that the file is loaded for the + * first time. + */ + virtual void handleHashChange(); + + /** + * Returns the hash that was stored to a cache file. + */ + QByteArray loadHash() const; + + /** + * Stores the given hash into a cache file. + */ + void saveHash( const QByteArray &hash ) const; + + /** + * Returns whether the resource can be written to. + */ + virtual bool readOnly() const = 0; + + /** + * Returns the collection of this resource. + */ + virtual Collection rootCollection() const = 0; + + protected: + KUrl mCurrentUrl; + QStringList mSupportedMimetypes; + QString mCollectionIcon; + KIO::FileCopyJob *mDownloadJob; + KIO::FileCopyJob *mUploadJob; + QByteArray mCurrentHash; + + protected Q_SLOTS: + void scheduleWrite(); /// Called when changes are added to the ChangeRecorder. + + private Q_SLOTS: + void handleProgress( KJob *, unsigned long ); + void fileChanged( const QString &fileName ); + void slotDownloadJobResult( KJob * ); + void slotUploadJobResult( KJob * ); +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/singlefileresourceconfigdialog.h b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog.h new file mode 100644 index 00000000..26c9426c --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2008 Bertjan Broeksema + Copyright (c) 2008 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_SINGLEFILERESOURCECONFIGDIALOG_H +#define AKONADI_SINGLEFILERESOURCECONFIGDIALOG_H + +#include "singlefileresourceconfigdialogbase.h" + +#include + +namespace Akonadi { + +/** + * Configuration dialog for single file resources. + */ +template +class SingleFileResourceConfigDialog : public SingleFileResourceConfigDialogBase +{ + Settings *mSettings; + + public: + explicit SingleFileResourceConfigDialog( WId windowId, Settings *settings ) + : SingleFileResourceConfigDialogBase( windowId ) + , mSettings( settings ) + { + ui.kcfg_Path->setUrl( KUrl( mSettings->path() ) ); + mManager = new KConfigDialogManager( this, mSettings ); + mManager->updateWidgets(); + } + + protected: + void save() + { + mManager->updateSettings(); + mSettings->setPath( ui.kcfg_Path->url().url() ); + mSettings->writeConfig(); + } +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/singlefileresourceconfigdialog.ui b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog.ui new file mode 100644 index 00000000..7793c40c --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog.ui @@ -0,0 +1,22 @@ + + + SingleFileResourceConfigDialog + + + + + + + + + + + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/resources/shared/singlefileresourceconfigdialog_desktop.ui b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog_desktop.ui new file mode 100644 index 00000000..38ff5f74 --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog_desktop.ui @@ -0,0 +1,181 @@ + + + SingleFileResourceConfigDialog + + + + 0 + 0 + 487 + 479 + + + + + + + 0 + + + + File + + + + + + Filename + + + + + + + + &Filename: + + + kcfg_Path + + + + + + + + + + + + Status: + + + + + + + Select the file whose contents should be represented by this resource. If the file does not exist, it will be created. A URL of a remote file can also be specified, but note that monitoring for file changes will not work in this case. + + + true + + + + + + + + + + Display Name + + + + + + + + &Name: + + + kcfg_DisplayName + + + + + + + + + + + + Enter the name used to identify this resource in displays. If not specified, the filename will be used. + + + true + + + + + + + + + + Access Rights + + + + + + Read only + + + + + + + If read-only mode is enabled, no changes will be written to the file selected above. Read-only mode will be automatically enabled if you do not have write access to the file or the file is on a remote server that does not support write access. + + + true + + + + + + + + + + Monitoring + + + + + + If file monitoring is enabled the resource will reload the file when changes are made by other programs. It also tries to create a backup in case of conflicts whenever possible. + + + true + + + + + + + Enable file &monitoring + + + + + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/resources/shared/singlefileresourceconfigdialog_mobile.ui b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog_mobile.ui new file mode 100644 index 00000000..03925fb1 --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourceconfigdialog_mobile.ui @@ -0,0 +1,108 @@ + + + SingleFileResourceConfigDialog + + + + 0 + 0 + 487 + 183 + + + + + + + + File + + + + + + + + &Filename: + + + kcfg_Path + + + + + + + + + + + + + + &Display name: + + + kcfg_DisplayName + + + + + + + + + + + + Read only + + + + + + + Enable file &monitoring + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+
+ + +
diff --git a/kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.cpp b/kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.cpp new file mode 100644 index 00000000..4e062f6a --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.cpp @@ -0,0 +1,229 @@ +/* + Copyright (c) 2008 Bertjan Broeksema + Copyright (c) 2008 Volker Krause + Copyright (c) 2010,2011 David Jarvie + + 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 "singlefileresourceconfigdialogbase.h" + +#include +#include +#include +#include +#include + +#include + +using namespace Akonadi; + +SingleFileResourceConfigDialogBase::SingleFileResourceConfigDialogBase( WId windowId ) : + KDialog(), + mManager( 0 ), + mStatJob( 0 ), + mAppendedWidget( 0 ), + mDirUrlChecked( false ), + mMonitorEnabled( true ), + mLocalFileOnly( false ) +{ + ui.setupUi( mainWidget() ); + ui.kcfg_Path->setMode( KFile::File ); +#ifndef KDEPIM_MOBILE_UI + ui.statusLabel->setText( QString() ); +#endif + + setButtons( Ok | Cancel ); + + if ( windowId ) + KWindowSystem::setMainWindow( this, windowId ); + + ui.ktabwidget->setTabBarHidden( true ); + + connect( this, SIGNAL(okClicked()), SLOT(save()) ); + + connect( ui.kcfg_Path, SIGNAL(textChanged(QString)), SLOT(validate()) ); + connect( ui.kcfg_MonitorFile, SIGNAL(toggled(bool)), SLOT(validate()) ); + ui.kcfg_Path->setFocus(); + QTimer::singleShot( 0, this, SLOT(validate()) ); + setMinimumSize(600, 540); + readConfig(); +} + +SingleFileResourceConfigDialogBase::~SingleFileResourceConfigDialogBase() +{ + writeConfig(); +} + +void SingleFileResourceConfigDialogBase::writeConfig() +{ + KConfigGroup group( KGlobal::config(), "SingleFileResourceConfigDialogBase" ); + group.writeEntry( "Size", size() ); +} + +void SingleFileResourceConfigDialogBase::readConfig() +{ + KConfigGroup group( KGlobal::config(), "SingleFileResourceConfigDialogBase" ); + const QSize sizeDialog = group.readEntry( "Size", QSize(600,540) ); + if ( sizeDialog.isValid() ) { + resize( sizeDialog ); + } +} + + +void SingleFileResourceConfigDialogBase::addPage( const QString &title, QWidget *page ) +{ + ui.ktabwidget->setTabBarHidden( false ); + ui.ktabwidget->addTab( page, title ); + mManager->addWidget( page ); + mManager->updateWidgets(); +} + +void SingleFileResourceConfigDialogBase::setFilter(const QString & filter) +{ + ui.kcfg_Path->setFilter( filter ); +} + +void SingleFileResourceConfigDialogBase::setMonitorEnabled(bool enable) +{ + mMonitorEnabled = enable; +#ifdef KDEPIM_MOBILE_UI + ui.kcfg_MonitorFile->setVisible( mMonitorEnabled ); +#else + ui.groupBox_MonitorFile->setVisible( mMonitorEnabled ); +#endif +} + +void SingleFileResourceConfigDialogBase::setUrl(const KUrl &url ) +{ + ui.kcfg_Path->setUrl( url ); +} + +KUrl SingleFileResourceConfigDialogBase::url() const +{ + return ui.kcfg_Path->url(); +} + +void SingleFileResourceConfigDialogBase::setLocalFileOnly( bool local ) +{ + mLocalFileOnly = local; + ui.kcfg_Path->setMode( mLocalFileOnly ? KFile::File | KFile::LocalOnly : KFile::File ); +} + +void SingleFileResourceConfigDialogBase::appendWidget( SingleFileValidatingWidget* widget ) +{ + widget->setParent( static_cast( ui.tab ) ); + ui.tabLayout->addWidget( widget ); + connect( widget, SIGNAL(changed()), SLOT(validate()) ); + mAppendedWidget = widget; +} + +void SingleFileResourceConfigDialogBase::validate() +{ + if ( mAppendedWidget && !mAppendedWidget->validate() ) { + enableButton( Ok, false ); + return; + } + + const KUrl currentUrl = ui.kcfg_Path->url(); + if ( currentUrl.isEmpty() ) { + enableButton( Ok, false ); + return; + } + + if ( currentUrl.isLocalFile() ) { + if ( mMonitorEnabled ) { + ui.kcfg_MonitorFile->setEnabled( true ); + } +#ifndef KDEPIM_MOBILE_UI + ui.statusLabel->setText( QString() ); +#endif + + // The read-only checkbox used to be disabled if the file is read-only, + // but it is then impossible to know at a later date if the file + // permissions change, whether the user actually wanted the resource to be + // read-only or not. So just leave the read-only checkbox untouched. + enableButton( Ok, true ); + } else { + // Not a local file. + if ( mLocalFileOnly ) { + enableButton( Ok, false ); + return; + } + if ( mMonitorEnabled ) { + ui.kcfg_MonitorFile->setEnabled( false ); + } +#ifndef KDEPIM_MOBILE_UI + ui.statusLabel->setText( i18nc( "@info:status", "Checking file information..." ) ); +#endif + + if ( mStatJob ) + mStatJob->kill(); + + mStatJob = KIO::stat( currentUrl, KIO::DefaultFlags | KIO::HideProgressInfo ); + mStatJob->setDetails( 2 ); // All details. + mStatJob->setSide( KIO::StatJob::SourceSide ); + + connect( mStatJob, SIGNAL(result(KJob*)), + SLOT(slotStatJobResult(KJob*)) ); + + // Allow the OK button to be disabled until the MetaJob is finished. + enableButton( Ok, false ); + } +} + +void SingleFileResourceConfigDialogBase::slotStatJobResult( KJob* job ) +{ + if ( job->error() == KIO::ERR_DOES_NOT_EXIST && !mDirUrlChecked ) { + // The file did not exist, so let's see if the directory the file should + // reside in supports writing. + const KUrl dirUrl = ui.kcfg_Path->url().upUrl(); + + mStatJob = KIO::stat( dirUrl, KIO::DefaultFlags | KIO::HideProgressInfo ); + mStatJob->setDetails( 2 ); // All details. + mStatJob->setSide( KIO::StatJob::SourceSide ); + + connect( mStatJob, SIGNAL(result(KJob*)), + SLOT(slotStatJobResult(KJob*)) ); + + // Make sure we don't check the whole path upwards. + mDirUrlChecked = true; + return; + } else if ( job->error() ) { + // It doesn't seem possible to read nor write from the location so leave the + // ok button disabled +#ifndef KDEPIM_MOBILE_UI + ui.statusLabel->setText( QString() ); +#endif + enableButton( Ok, false ); + mDirUrlChecked = false; + mStatJob = 0; + return; + } + +#ifndef KDEPIM_MOBILE_UI + ui.statusLabel->setText( QString() ); +#endif + enableButton( Ok, true ); + + mDirUrlChecked = false; + mStatJob = 0; +} + +SingleFileValidatingWidget::SingleFileValidatingWidget( QWidget* parent ) + : QWidget( parent ) +{ +} diff --git a/kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.h b/kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.h new file mode 100644 index 00000000..ef1813b9 --- /dev/null +++ b/kdepim-runtime/resources/shared/singlefileresourceconfigdialogbase.h @@ -0,0 +1,144 @@ +/* + Copyright (c) 2008 Bertjan Broeksema + Copyright (c) 2008 Volker Krause + Copyright (c) 2010,2011 David Jarvie + + 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_SINGLEFILERESOURCECONFIGDIALOGBASE_H +#define AKONADI_SINGLEFILERESOURCECONFIGDIALOGBASE_H + +#ifdef KDEPIM_MOBILE_UI +#include "ui_singlefileresourceconfigdialog_mobile.h" +#else +#include "ui_singlefileresourceconfigdialog_desktop.h" +#endif + +#include + +class KConfigDialogManager; +class KJob; + +namespace KIO { + class StatJob; +} + +namespace Akonadi { + +class SingleFileValidatingWidget; + +/** + * Base class for the configuration dialog for single file based resources. + * @see SingleFileResourceConfigDialog + */ +class SingleFileResourceConfigDialogBase : public KDialog +{ + Q_OBJECT + public: + explicit SingleFileResourceConfigDialogBase( WId windowId ); + ~SingleFileResourceConfigDialogBase(); + + /** + * Adds @param page to the tabwidget. This can be used to add custom + * settings for a specific single file resource. + */ + void addPage( const QString &title, QWidget *page ); + + /** + * Set file extension filter. + */ + void setFilter( const QString &filter ); + + /** + * Enable and show, or disable and hide, the monitor option. + * If the option is disabled, its value will not be saved. + * By default, the monitor option is enabled. + */ + void setMonitorEnabled( bool enable ); + + /** + * Return the file URL. + */ + KUrl url() const; + + /** + * Set the file URL. + */ + void setUrl( const KUrl& url ); + + /** + * Specify whether the file must be local. + * The default is to allow both local and remote files. + */ + void setLocalFileOnly( bool local ); + + /** + * Add a widget to the dialog. + */ + void appendWidget(SingleFileValidatingWidget* widget); + + protected Q_SLOTS: + virtual void save() = 0; + + protected: + Ui::SingleFileResourceConfigDialog ui; + KConfigDialogManager* mManager; + + private Q_SLOTS: + void validate(); + void slotStatJobResult( KJob * ); + + private: + void writeConfig(); + void readConfig(); + KIO::StatJob* mStatJob; + SingleFileValidatingWidget* mAppendedWidget; + bool mDirUrlChecked; + bool mMonitorEnabled; + bool mLocalFileOnly; +}; + +/** + * Base class for widgets added to SingleFileResourceConfigDialogBase + * using its appendWidget() method. + * + * Derived classes must implement validate() and emit changed() when + * appropriate. + */ +class SingleFileValidatingWidget : public QWidget +{ + Q_OBJECT + public: + explicit SingleFileValidatingWidget( QWidget* parent = 0 ); + + /** + * Return whether the widget's value is valid when the dialog is + * accepted. + */ + virtual bool validate() const = 0; + + signals: + /** + * Signal emitted when the widget's value changes in a way which + * might affect the result of validate(). + */ + void changed(); +}; + +} + +#endif diff --git a/kdepim-runtime/resources/shared/tests/CMakeLists.txt b/kdepim-runtime/resources/shared/tests/CMakeLists.txt new file mode 100644 index 00000000..f379ccdf --- /dev/null +++ b/kdepim-runtime/resources/shared/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +macro(_add_test _source) + set(_test ${_source}) + get_filename_component(_name ${_source} NAME_WE) + kde4_add_unit_test(${_name} TESTNAME akonadi-${_name} ${_test}) + target_link_libraries(${_name} ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} + ${AKONADI_COMMON_LIBRARIES} ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_KIMAP_LIBS}) +endmacro() + +_add_test( collectionannotationattributetest.cpp ) +_add_test( imapaclattributetest.cpp ) diff --git a/kdepim-runtime/resources/shared/tests/collectionannotationattributetest.cpp b/kdepim-runtime/resources/shared/tests/collectionannotationattributetest.cpp new file mode 100644 index 00000000..41fa7aa2 --- /dev/null +++ b/kdepim-runtime/resources/shared/tests/collectionannotationattributetest.cpp @@ -0,0 +1,85 @@ +/* + Copyright (C) 2009 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 +#include +#include "../collectionannotationsattribute.cpp" + +typedef QMap Annotation; +Q_DECLARE_METATYPE( Annotation ) + +using namespace Akonadi; + +class CollectionAnnotationAttributeTest : public QObject +{ + Q_OBJECT + private slots: + void testSerializeDeserialize_data() + { + QTest::addColumn( "annotation" ); + + Annotation a; + QTest::newRow( "empty" ) << a; + + a.insert( "/vendor/cmu/cyrus-imapd/lastpop", "" ); + QTest::newRow( "empty value, single key" ) << a; + + a.insert( "/vendor/cmu/cyrus-imapd/condstore", "false" ); + QTest::newRow( "empty value, two keys" ) << a; + + a.insert( "/vendor/cmu/cyrus-imapd/sharedseen", "false" ); + QTest::newRow( "empty value, three keys" ) << a; + + a.clear(); + a.insert( "vendor/cmu/cyrus-imapd/lastpop", " " ); + QTest::newRow( "space value, single key" ) << a; + + a.insert( "/vendor/cmu/cyrus-imapd/condstore", "false" ); + QTest::newRow( "space value, two keys" ) << a; + + a.insert( "/vendor/cmu/cyrus-imapd/sharedseen", "false" ); + QTest::newRow( "space value, three keys" ) << a; + } + + void testSerializeDeserialize() + { + QFETCH( Annotation, annotation ); + CollectionAnnotationsAttribute *attr1 = new CollectionAnnotationsAttribute(); + attr1->setAnnotations( annotation ); + QCOMPARE( attr1->annotations(), annotation ); + + CollectionAnnotationsAttribute *attr2 = new CollectionAnnotationsAttribute(); + attr2->deserialize( attr1->serialized() ); + QCOMPARE( attr2->annotations(), annotation ); + + CollectionAnnotationsAttribute *attr3 = new CollectionAnnotationsAttribute(); + attr3->setAnnotations( attr2->annotations() ); + QCOMPARE( attr3->serialized(), attr1->serialized() ); + + delete attr1; + delete attr2; + delete attr3; + } + +}; + +QTEST_KDEMAIN( CollectionAnnotationAttributeTest, NoGUI ) + +#include "collectionannotationattributetest.moc" + diff --git a/kdepim-runtime/resources/shared/tests/imapaclattributetest.cpp b/kdepim-runtime/resources/shared/tests/imapaclattributetest.cpp new file mode 100644 index 00000000..7e65a6f1 --- /dev/null +++ b/kdepim-runtime/resources/shared/tests/imapaclattributetest.cpp @@ -0,0 +1,110 @@ +/* + Copyright (C) 2010 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. +*/ + +#include +#include +#include "../imapaclattribute.cpp" + +using namespace Akonadi; + +typedef QMap ImapAcl; + +Q_DECLARE_METATYPE( ImapAcl ) + +class ImapAclAttributeTest : public QObject +{ + Q_OBJECT + + private Q_SLOTS: + + void testSerializeDeserialize_data() + { + QTest::addColumn( "rights" ); + QTest::addColumn( "serialized" ); + QTest::addColumn( "oldSerialized" ); + + ImapAcl acl; + QTest::newRow( "empty" ) << acl << QByteArray( " %% " ) << QByteArray( "testme@host l %% " ); + + acl.insert( "user@host", KIMAP::Acl::None ); + QTest::newRow( "none" ) << acl << QByteArray( "user@host %% " ) << QByteArray( "testme@host l %% user@host " ); + + acl.insert( "user@host", KIMAP::Acl::Lookup ); + QTest::newRow( "lookup" ) << acl << QByteArray( "user@host l %% " ) << QByteArray( "testme@host l %% user@host l" ); + + acl.insert( "user@host", KIMAP::Acl::Lookup | KIMAP::Acl::Read ); + QTest::newRow( "lookup/read" ) << acl << QByteArray( "user@host lr %% " ) << QByteArray( "testme@host l %% user@host lr" ); + + acl.insert( "otheruser@host", KIMAP::Acl::Lookup | KIMAP::Acl::Read ); + QTest::newRow( "lookup/read" ) << acl << QByteArray( "otheruser@host lr % user@host lr %% " ) + << QByteArray( "testme@host l %% otheruser@host lr % user@host lr" ); + } + + void testSerializeDeserialize() + { + QFETCH( ImapAcl, rights ); + QFETCH( QByteArray, serialized ); + QFETCH( QByteArray, oldSerialized ); + + ImapAclAttribute *attr = new ImapAclAttribute(); + attr->setRights( rights ); + QCOMPARE( attr->serialized(), serialized ); + + ImapAcl acl; + acl.insert( "testme@host", KIMAP::Acl::Lookup ); + attr->setRights( acl ); + + QCOMPARE( attr->serialized(), oldSerialized ); + + delete attr; + + ImapAclAttribute deserializeAttr; + deserializeAttr.deserialize( serialized ); + QCOMPARE( deserializeAttr.rights(), rights ); + } + + void testOldRights() + { + ImapAcl acls; + acls.insert( "first_user@host", KIMAP::Acl::Lookup | KIMAP::Acl::Read ); + acls.insert( "second_user@host", KIMAP::Acl::Lookup | KIMAP::Acl::Read ); + acls.insert( "third_user@host", KIMAP::Acl::Lookup | KIMAP::Acl::Read ); + + ImapAclAttribute *attr = new ImapAclAttribute(); + attr->setRights( acls ); + + ImapAcl oldAcls = acls; + + acls.remove( "first_user@host" ); + acls.remove( "third_user@host" ); + + attr->setRights( acls ); + + QCOMPARE( attr->oldRights(), oldAcls ); + + attr->setRights( acls ); + + QCOMPARE( attr->oldRights(), acls ); + delete attr; + } +}; + +QTEST_KDEMAIN( ImapAclAttributeTest, NoGUI ) + +#include "imapaclattributetest.moc" diff --git a/kdepim-runtime/resources/vcard/CMakeLists.txt b/kdepim-runtime/resources/vcard/CMakeLists.txt new file mode 100644 index 00000000..7b1ec091 --- /dev/null +++ b/kdepim-runtime/resources/vcard/CMakeLists.txt @@ -0,0 +1,38 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) +add_subdirectory( tests ) + +########### next target ############### + +set( vcardresource_SRCS + ${AKONADI_SINGLEFILERESOURCE_SHARED_SOURCES} + vcardresource.cpp +) + +install( FILES vcardresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_ui_files(vcardresource_SRCS ${AKONADI_SINGLEFILERESOURCE_SHARED_UI}) +kde4_add_kcfg_files(vcardresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/vcardresource.kcfg org.kde.Akonadi.VCard.Settings) +qt4_add_dbus_adaptor(vcardresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.VCard.Settings.xml settings.h Akonadi_VCard_Resource::Settings vcardsettingsadaptor VCardSettingsAdaptor +) + +kde4_add_plugin(akonadi_vcard_resource ${vcardresource_SRCS}) + +add_subdirectory( wizard ) + +if (Q_WS_MAC) + set_target_properties(akonadi_vcard_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_vcard_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.VCard") + set_target_properties(akonadi_vcard_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi VCard Resource") +endif () + +target_link_libraries(akonadi_vcard_resource ${KDEPIMLIBS_AKONADI_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDEPIMLIBS_KABC_LIBS} ${KDE4_KIO_LIBS}) + +install(TARGETS akonadi_vcard_resource DESTINATION ${PLUGIN_INSTALL_DIR}) + diff --git a/kdepim-runtime/resources/vcard/Messages.sh b/kdepim-runtime/resources/vcard/Messages.sh new file mode 100644 index 00000000..a766b04f --- /dev/null +++ b/kdepim-runtime/resources/vcard/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_vcard_resource.pot diff --git a/kdepim-runtime/resources/vcard/settings.kcfgc b/kdepim-runtime/resources/vcard/settings.kcfgc new file mode 100644 index 00000000..75de6cc8 --- /dev/null +++ b/kdepim-runtime/resources/vcard/settings.kcfgc @@ -0,0 +1,9 @@ +File=vcardresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=false +#IncludeFiles= +GlobalEnums=true +Namespace=Akonadi_VCard_Resource diff --git a/kdepim-runtime/resources/vcard/tests/CMakeLists.txt b/kdepim-runtime/resources/vcard/tests/CMakeLists.txt new file mode 100644 index 00000000..eb78b92e --- /dev/null +++ b/kdepim-runtime/resources/vcard/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/vcardtest.xml ${CMAKE_CURRENT_BINARY_DIR}/vcardtest.xml COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/vcardtest-readonly.xml ${CMAKE_CURRENT_BINARY_DIR}/vcardtest-readonly.xml COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/vcardtest.vcf ${CMAKE_CURRENT_BINARY_DIR}/vcardtest.vcf COPYONLY) + +akonadi_add_resourcetest( vcard-read vcardtest.js ) +akonadi_add_resourcetest( vcard-read-readonly vcardtest-readonly.js ) diff --git a/kdepim-runtime/resources/vcard/tests/vcardtest-readonly.js b/kdepim-runtime/resources/vcard/tests/vcardtest-readonly.js new file mode 100644 index 00000000..b776b80e --- /dev/null +++ b/kdepim-runtime/resources/vcard/tests/vcardtest-readonly.js @@ -0,0 +1,12 @@ +Resource.setType( "akonadi_vcard_resource" ); +Resource.setPathOption( "Path", "vcardtest.vcf" ); +Resource.setOption( "ReadOnly", true ); +Resource.create(); + +XmlOperations.setXmlFile( "vcardtest-readonly.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.setCollectionKey( "None" ); // we only expect one collection +XmlOperations.ignoreCollectionField( "Name" ); // name is the resource identifier and thus unpredictable +XmlOperations.ignoreCollectionField( "RemoteId" ); // remote id is the absolute path +XmlOperations.assertEqual(); + diff --git a/kdepim-runtime/resources/vcard/tests/vcardtest-readonly.xml b/kdepim-runtime/resources/vcard/tests/vcardtest-readonly.xml new file mode 100644 index 00000000..7f6d3b24 --- /dev/null +++ b/kdepim-runtime/resources/vcard/tests/vcardtest-readonly.xml @@ -0,0 +1,24 @@ + + + W + ("vcardtest.vcf" "office-address-book") + + +BEGIN:VCARD +EMAIL:vkrause@kde.org +FN:Volker Krause +GEO:52.500000;13.366667 +N:Krause;Volker;;; +NAME:Volker Krause +ORG:KDE +REV:2003-02-27T20:08:42Z +ROLE:Author of this file +TZ:+02:00 +UID:bb2slGmqxb +URL:http://www.akonadi-project.org +VERSION:3.0 +END:VCARD + + + + diff --git a/kdepim-runtime/resources/vcard/tests/vcardtest.js b/kdepim-runtime/resources/vcard/tests/vcardtest.js new file mode 100644 index 00000000..5be52ffa --- /dev/null +++ b/kdepim-runtime/resources/vcard/tests/vcardtest.js @@ -0,0 +1,11 @@ +Resource.setType( "akonadi_vcard_resource" ); +Resource.setPathOption( "Path", "vcardtest.vcf" ); +Resource.create(); + +XmlOperations.setXmlFile( "vcardtest.xml" ); +XmlOperations.setRootCollections( Resource.identifier() ); +XmlOperations.setCollectionKey( "None" ); // we only expect one collection +XmlOperations.ignoreCollectionField( "Name" ); // name is the resource identifier and thus unpredictable +XmlOperations.ignoreCollectionField( "RemoteId" ); // remote id is the absolute path +XmlOperations.assertEqual(); + diff --git a/kdepim-runtime/resources/vcard/tests/vcardtest.vcf b/kdepim-runtime/resources/vcard/tests/vcardtest.vcf new file mode 100644 index 00000000..0041488e --- /dev/null +++ b/kdepim-runtime/resources/vcard/tests/vcardtest.vcf @@ -0,0 +1,15 @@ +BEGIN:VCARD +EMAIL:vkrause@kde.org +FN:Volker Krause +GEO:52.500000;13.366667 +N:Krause;Volker;;; +NAME:Volker Krause +ORG:KDE +REV:2003-02-27T20:08:42Z +ROLE:Author of this file +TZ:+02:00 +UID:bb2slGmqxb +URL:http://www.akonadi-project.org +VERSION:3.0 +END:VCARD + diff --git a/kdepim-runtime/resources/vcard/tests/vcardtest.xml b/kdepim-runtime/resources/vcard/tests/vcardtest.xml new file mode 100644 index 00000000..b226de26 --- /dev/null +++ b/kdepim-runtime/resources/vcard/tests/vcardtest.xml @@ -0,0 +1,24 @@ + + + wcdW + ("vcardtest.vcf" "office-address-book") + + +BEGIN:VCARD +EMAIL:vkrause@kde.org +FN:Volker Krause +GEO:52.500000;13.366667 +N:Krause;Volker;;; +NAME:Volker Krause +ORG:KDE +REV:2003-02-27T20:08:42Z +ROLE:Author of this file +TZ:+02:00 +UID:bb2slGmqxb +URL:http://www.akonadi-project.org +VERSION:3.0 +END:VCARD + + + + diff --git a/kdepim-runtime/resources/vcard/vcardresource.cpp b/kdepim-runtime/resources/vcard/vcardresource.cpp new file mode 100644 index 00000000..eff46b2f --- /dev/null +++ b/kdepim-runtime/resources/vcard/vcardresource.cpp @@ -0,0 +1,189 @@ +/* + Copyright (c) 2007 Tobias Koenig + Copyright (c) 2008 Bertjan Broeksema + + 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 "vcardresource.h" +#include "vcardsettingsadaptor.h" +#include "singlefileresourceconfigdialog.h" + +#include +#include + +#include +#include +#include + +#include + +using namespace Akonadi; +using namespace Akonadi_VCard_Resource; + +VCardResource::VCardResource( const QString &id ) + : SingleFileResource( id ) +{ + setSupportedMimetypes( QStringList() << KABC::Addressee::mimeType(), QLatin1String("office-address-book") ); + + new VCardSettingsAdaptor( mSettings ); + DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Settings" ), + mSettings, QDBusConnection::ExportAdaptors ); +} + +VCardResource::~VCardResource() +{ + mAddressees.clear(); +} + +bool VCardResource::retrieveItem( const Akonadi::Item &item, const QSet &parts ) +{ + Q_UNUSED( parts ); + const QString rid = item.remoteId(); + if ( !mAddressees.contains( rid ) ) { + emit error( i18n( "Contact with uid '%1' not found." ,rid ) ); + return false; + } + Item i( item ); + i.setPayload( mAddressees.value( rid ) ); + itemRetrieved( i ); + return true; +} + +void VCardResource::aboutToQuit() +{ + if ( !mSettings->readOnly() ) + writeFile(); + mSettings->writeConfig(); +} + +void VCardResource::customizeConfigDialog( SingleFileResourceConfigDialog* dlg ) +{ + dlg->setWindowIcon( KIcon( QLatin1String("text-directory") ) ); + dlg->setFilter( QLatin1String("*.vcf|") + i18nc("Filedialog filter for *.vcf", "vCard Address Book File" ) ); + dlg->setCaption( i18n("Select Address Book") ); +} + +void VCardResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& ) +{ + KABC::Addressee addressee; + if ( item.hasPayload() ) + addressee = item.payload(); + + if ( !addressee.isEmpty() ) { + mAddressees.insert( addressee.uid(), addressee ); + + Item i( item ); + i.setRemoteId( addressee.uid() ); + changeCommitted( i ); + + scheduleWrite(); + } else { + changeProcessed(); + } +} + +void VCardResource::itemChanged( const Akonadi::Item &item, const QSet& ) +{ + KABC::Addressee addressee; + if ( item.hasPayload() ) + addressee = item.payload(); + + if ( !addressee.isEmpty() ) { + mAddressees.insert( addressee.uid(), addressee ); + + Item i( item ); + i.setRemoteId( addressee.uid() ); + changeCommitted( i ); + + scheduleWrite(); + } else { + changeProcessed(); + } +} + +void VCardResource::itemRemoved(const Akonadi::Item & item) +{ + if ( mAddressees.contains( item.remoteId() ) ) + mAddressees.remove( item.remoteId() ); + + scheduleWrite(); + + changeProcessed(); +} + +void VCardResource::retrieveItems( const Akonadi::Collection & col ) +{ + // VCard does not support folders so we can safely ignore the collection + Q_UNUSED( col ); + + Item::List items; + + // FIXME: Check if the KIO::Job is done and was successful, if so send the + // items, otherwise set a bool and in the result slot of the job send the + // items if the bool is set. + + foreach ( const KABC::Addressee &addressee, mAddressees ) { + Item item; + item.setRemoteId( addressee.uid() ); + item.setMimeType( KABC::Addressee::mimeType() ); + item.setPayload( addressee ); + items.append( item ); + } + + itemsRetrieved( items ); +} + +bool VCardResource::readFromFile( const QString &fileName ) +{ + mAddressees.clear(); + + QFile file( KUrl( fileName ).toLocalFile() ); + if ( !file.open( QIODevice::ReadOnly ) ) { + emit status( Broken, i18n( "Unable to open vCard file '%1'.", fileName ) ); + return false; + } + + const QByteArray data = file.readAll(); + file.close(); + + const KABC::Addressee::List list = mConverter.parseVCards( data ); + const int numberOfElementInList = list.count(); + for ( int i = 0; i < numberOfElementInList; ++i ) { + mAddressees.insert( list[ i ].uid(), list[ i ] ); + } + + return true; +} + +bool VCardResource::writeToFile( const QString &fileName ) +{ + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) { + emit status( Broken, i18n( "Unable to open vCard file '%1'.", fileName ) ); + return false; + } + + const QByteArray data = mConverter.createVCards( mAddressees.values() ); + + file.write( data ); + file.close(); + + return true; +} + +AKONADI_AGENT_FACTORY( VCardResource, akonadi_vcard_resource ) + diff --git a/kdepim-runtime/resources/vcard/vcardresource.desktop b/kdepim-runtime/resources/vcard/vcardresource.desktop new file mode 100644 index 00000000..60c904e1 --- /dev/null +++ b/kdepim-runtime/resources/vcard/vcardresource.desktop @@ -0,0 +1,88 @@ +[Desktop Entry] +Name=vCard File +Name[bs]=VCard datoteka +Name[ca]=Fitxer vCard +Name[ca@valencia]=Fitxer vCard +Name[cs]=Soubor s vizitkou +Name[da]=vCard-fil +Name[de]=vCard-Datei +Name[el]=VCard αÏχείο +Name[en_GB]=vCard File +Name[es]=Archivo vCard +Name[et]=vCard-fail +Name[fi]=vCard-tiedosto +Name[fr]=Fichier « vCard » +Name[ga]=Comhad v-Chárta +Name[gl]=Ficheiro vCard +Name[hu]=vCard fájl +Name[ia]=File VCard +Name[it]=File vCard +Name[kk]=VCard файлы +Name[ko]=vCard íŒŒì¼ +Name[lt]=VCard failas +Name[nb]=vCard-fil +Name[nds]=VCard-Datei +Name[nl]=vCard-bestand +Name[pl]=Plik vCard +Name[pt]=Ficheiro vCard +Name[pt_BR]=Arquivo vCard +Name[ru]=Файл vCard +Name[sk]=Súbor VCard +Name[sr]=В‑кард фајл +Name[sr@ijekavian]=В‑кард фајл +Name[sr@ijekavianlatin]=VCard fajl +Name[sr@latin]=VCard fajl +Name[sv]=vCard-fil +Name[tr]=VCard Dosyası +Name[ug]=vCard ھۆججىتى +Name[uk]=Файл vCard +Name[x-test]=xxvCard Filexx +Name[zh_CN]=vCard 文件 +Name[zh_TW]=vCard 檔案 +Comment=Loads data from a vCard file +Comment[bs]=UÄitava podatke iz VCard datoteke +Comment[ca]=Carrega les dades des d'un fitxer vCard +Comment[ca@valencia]=Carrega dades des d'un fitxer vCard +Comment[cs]=NaÄítá data ze souboru vizitek +Comment[da]=Indlæser data fra en vCard-fil +Comment[de]=Daten werden aus einer vCard-Datei geladen +Comment[el]=ΦοÏτώνει δεδομένα από ένα αÏχείο VCard +Comment[en_GB]=Loads data from a vCard file +Comment[es]=Carga datos de un archivo vCard +Comment[et]=Andmete laadimine vCard-failist +Comment[fi]=Lataa tietoa vCard-tiedostosta +Comment[fr]=Charge des données depuis un fichier au format « vCard » +Comment[ga]=Breiseán a luchtaíonn sonraí ó chomhad v-Chárta +Comment[gl]=Carga datos desde un ficheiro vCard +Comment[hu]=Adatokat tölt be egy vCard fájlból +Comment[ia]=Carga datos ex un file vCard +Comment[it]=Carica dati da un file vCard +Comment[kk]=VCard файлынан деректі алып береді +Comment[ko]=vCard 파ì¼ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=Ä®kelia duomenis iÅ¡ VCard failo +Comment[nb]=Laster data fra en vCard-fil +Comment[nds]=Laadt Daten ut en VCard-Datei +Comment[nl]=Laadt gegevens van een vCard-bestand +Comment[pl]=Wczytuje dane z plików vCard +Comment[pt]=Carrega os dados de um ficheiro vCard +Comment[pt_BR]=Carrega os dados de um arquivo vCard +Comment[ru]=Загрузка данных из файла vCard +Comment[sk]=NaÄíta dáta zo súboru vCard +Comment[sr]=Учитава податке из в‑кард фајла +Comment[sr@ijekavian]=Учитава податке из в‑кард фајла +Comment[sr@ijekavianlatin]=UÄitava podatke iz vCard fajla +Comment[sr@latin]=UÄitava podatke iz vCard fajla +Comment[sv]=Laddar data frÃ¥n en vCard-fil +Comment[tr]=Bir VCard dosyasından veri yükler +Comment[uk]=Завантажує дані з файла vCard +Comment[x-test]=xxLoads data from a vCard filexx +Comment[zh_CN]=从 vCard æ–‡ä»¶è½½å…¥æ•°æ® +Comment[zh_TW]=從 vCard 檔載入資料 +Type=AkonadiResource +Exec=akonadi_vcard_resource +Icon=text-directory + +X-Akonadi-MimeTypes=text/directory +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_vcard_resource +X-Akonadi-LaunchMethod=AgentServer diff --git a/kdepim-runtime/resources/vcard/vcardresource.h b/kdepim-runtime/resources/vcard/vcardresource.h new file mode 100644 index 00000000..75ca0e4d --- /dev/null +++ b/kdepim-runtime/resources/vcard/vcardresource.h @@ -0,0 +1,60 @@ +/* + 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. +*/ + +#ifndef VCARDRESOURCE_H +#define VCARDRESOURCE_H + +#include "singlefileresource.h" +#include "settings.h" + +#include +#include + +class VCardResource : public Akonadi::SingleFileResource +{ + Q_OBJECT + + public: + explicit VCardResource( const QString &id ); + ~VCardResource(); + + protected Q_SLOTS: + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + void retrieveItems( const Akonadi::Collection &col ); + + protected: + /** + * Customize the configuration dialog before it is displayed. + */ + virtual void customizeConfigDialog( Akonadi::SingleFileResourceConfigDialog* dlg ); + + bool readFromFile( const QString &fileName ); + bool writeToFile( const QString &fileName ); + virtual void aboutToQuit(); + + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + private: + QMap mAddressees; + KABC::VCardConverter mConverter; +}; + +#endif diff --git a/kdepim-runtime/resources/vcard/vcardresource.kcfg b/kdepim-runtime/resources/vcard/vcardresource.kcfg new file mode 100644 index 00000000..0d316840 --- /dev/null +++ b/kdepim-runtime/resources/vcard/vcardresource.kcfg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + false + + + + true + + + diff --git a/kdepim-runtime/resources/vcard/wizard/CMakeLists.txt b/kdepim-runtime/resources/vcard/wizard/CMakeLists.txt new file mode 100644 index 00000000..51e225ed --- /dev/null +++ b/kdepim-runtime/resources/vcard/wizard/CMakeLists.txt @@ -0,0 +1,4 @@ +set(VCARD_FILE_DEFAULT_PATH "$HOME/.kde/share/apps/kaddressbook/contact.vcf") + +configure_file(vcardwizard.es.cmake ${CMAKE_CURRENT_BINARY_DIR}/vcardwizard.es) +install ( FILES vcardwizard.desktop ${CMAKE_CURRENT_BINARY_DIR}/vcardwizard.es vcardwizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/vcard ) diff --git a/kdepim-runtime/resources/vcard/wizard/Messages.sh b/kdepim-runtime/resources/vcard/wizard/Messages.sh new file mode 100644 index 00000000..e3657ed7 --- /dev/null +++ b/kdepim-runtime/resources/vcard/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_vcard.pot +$XGETTEXT -kqsTr *.es.cmake -j -o $podir/accountwizard_vcard.pot diff --git a/kdepim-runtime/resources/vcard/wizard/vcardwizard.desktop b/kdepim-runtime/resources/vcard/wizard/vcardwizard.desktop new file mode 100644 index 00000000..5a016943 --- /dev/null +++ b/kdepim-runtime/resources/vcard/wizard/vcardwizard.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Name=VCard File +Name[ca]=Fitxer vCard +Name[ca@valencia]=Fitxer vCard +Name[cs]=Soubor s vizitkou +Name[da]=VCard-fil +Name[de]=vCard-Datei +Name[en_GB]=VCard File +Name[es]=Archivo VCard +Name[et]=vCard-fail +Name[fi]=VCard-tiedosto +Name[fr]=Fichier « vCard » +Name[hu]=VCard fájl +Name[it]=File vCard +Name[ja]=VCard ファイル +Name[ko]=vCard íŒŒì¼ +Name[nb]=vCard-fil +Name[nds]=vCard-Datei +Name[nl]=VCard-bestand +Name[nn]=vCard-fil +Name[pl]=Plik VCard +Name[pt]=Ficheiro vCard +Name[pt_BR]=Arquivo VCard +Name[ru]=Файл vCard +Name[sk]=Súbor VCard +Name[sr]=В‑кард фајл +Name[sr@ijekavian]=В‑кард фајл +Name[sr@ijekavianlatin]=VCard fajl +Name[sr@latin]=VCard fajl +Name[sv]=VCard-fil +Name[tr]=VCard Dosyası +Name[uk]=Файл vCard +Name[x-test]=xxVCard Filexx +Name[zh_TW]=vCard 檔案 +Icon=text-directory +Comment=Loads contact from VCard File +Comment[ca]=Carrega contactes des d'un fitxer vCard +Comment[ca@valencia]=Carrega contactes des d'un fitxer vCard +Comment[cs]=NaÄítá data ze souboru vizitek +Comment[da]=Indlæs kontakt fra VCard-fil +Comment[de]=Lädt Kontakte aus einer vCard-Datei +Comment[en_GB]=Loads contact from VCard File +Comment[es]=Cargar contactos desde un archivo VCard +Comment[et]=Kontakti laadimine vCard-failist +Comment[fi]=Lataa yhteystiedon VuCard-tiedostosta +Comment[fr]=Charge un contact depuis un fichier au format « vCard » +Comment[hu]=Partnert tölt be egy VCard fájlból +Comment[it]=Carica contatti da un file vCard +Comment[ko]=vCard 파ì¼ì—ì„œ ì—°ë½ì²˜ë¥¼ 가져옵니다 +Comment[nb]=Laster kontakt fra vCard-fila +Comment[nds]=Laadt Daten ut en vCard-Datei +Comment[nl]=Laadt contactpersoon uit een VCard-bestand +Comment[pl]=Wczytuje kontakty z pliku VCard +Comment[pt]=Carrega um contacto de um ficheiro vCard +Comment[pt_BR]=Carrega os contatos de um arquivo VCard +Comment[ru]=Загрузка контакта из файла vCard +Comment[sk]=NaÄíta kontakt zo súboru vCard +Comment[sr]=Учитава контакт из в‑кард фајла +Comment[sr@ijekavian]=Учитава контакт из в‑кард фајла +Comment[sr@ijekavianlatin]=UÄitava kontakt iz vCard fajla +Comment[sr@latin]=UÄitava kontakt iz vCard fajla +Comment[sv]=Laddar data frÃ¥n en VCard-fil +Comment[tr]=Bir VCard dosyasından veri yükler +Comment[uk]=Завантажує дані запиÑу контакту із файла vCard +Comment[x-test]=xxLoads contact from VCard Filexx +Comment[zh_TW]=從 vCard 檔載入è¯çµ¡äºº + +[Wizard] +Type=text/vcard +Script=vcardwizard.es + +[Translate] +Filename=accountwizard_vcard diff --git a/kdepim-runtime/resources/vcard/wizard/vcardwizard.es.cmake b/kdepim-runtime/resources/vcard/wizard/vcardwizard.es.cmake new file mode 100644 index 00000000..7f8ad3e7 --- /dev/null +++ b/kdepim-runtime/resources/vcard/wizard/vcardwizard.es.cmake @@ -0,0 +1,43 @@ +/* + Copyright (c) 2010 Till Adam + + 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. +*/ + +var page = Dialog.addPage( "vcardwizard.ui", qsTr("Settings") ); + +page.widget().lineEdit.text = "${VCARD_FILE_DEFAULT_PATH}"; + +function validateInput() +{ + if ( page.widget().lineEdit.text == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var vcardRes = SetupManager.createResource( "akonadi_vcard_resource" ); + vcardRes.setOption( "Path", page.widget().lineEdit.text ); + vcardRes.setName( qsTr("Default Contact") ); + SetupManager.execute(); +} + +page.widget().lineEdit.textChanged.connect( validateInput ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/vcard/wizard/vcardwizard.ui b/kdepim-runtime/resources/vcard/wizard/vcardwizard.ui new file mode 100644 index 00000000..18d4f307 --- /dev/null +++ b/kdepim-runtime/resources/vcard/wizard/vcardwizard.ui @@ -0,0 +1,45 @@ + + + icalWizard + + + + 0 + 0 + 400 + 300 + + + + + + + + + Filename: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + diff --git a/kdepim-runtime/resources/vcarddir/CMakeLists.txt b/kdepim-runtime/resources/vcarddir/CMakeLists.txt new file mode 100644 index 00000000..517080e1 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/CMakeLists.txt @@ -0,0 +1,44 @@ +include_directories( + ${kdepim-runtime_SOURCE_DIR} + ${QT_QTDBUS_INCLUDE_DIR} +) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + + +########### next target ############### + +set( vcarddirresource_SRCS + vcarddirresource.cpp + ../shared/dirsettingsdialog.cpp +) + +kde4_add_ui_files(vcarddirresource_SRCS settingsdialog.ui) +kde4_add_kcfg_files(vcarddirresource_SRCS settings.kcfgc) +kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/vcarddirresource.kcfg org.kde.Akonadi.VCardDirectory.Settings) +qt4_add_dbus_adaptor(vcarddirresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.VCardDirectory.Settings.xml settings.h Settings +) + +install( FILES vcarddirresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents" ) + +kde4_add_executable(akonadi_vcarddir_resource ${vcarddirresource_SRCS}) + +if (Q_WS_MAC) + set_target_properties(akonadi_vcarddir_resource PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../Info.plist.template) + set_target_properties(akonadi_vcarddir_resource PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.VCardDirectory") + set_target_properties(akonadi_vcarddir_resource PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi VCardDirectory Resource") +endif () + + +target_link_libraries(akonadi_vcarddir_resource + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDEPIMLIBS_KABC_LIBS} + ${KDE4_KIO_LIBS} +) + +add_subdirectory(wizard) + +install(TARGETS akonadi_vcarddir_resource ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kdepim-runtime/resources/vcarddir/Messages.sh b/kdepim-runtime/resources/vcarddir/Messages.sh new file mode 100644 index 00000000..3157858b --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_vcarddir_resource.pot diff --git a/kdepim-runtime/resources/vcarddir/settings.kcfgc b/kdepim-runtime/resources/vcarddir/settings.kcfgc new file mode 100644 index 00000000..dd4cb033 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/settings.kcfgc @@ -0,0 +1,7 @@ +File=vcarddirresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +GlobalEnums=true diff --git a/kdepim-runtime/resources/vcarddir/settingsdialog.ui b/kdepim-runtime/resources/vcarddir/settingsdialog.ui new file mode 100644 index 00000000..f41ab578 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/settingsdialog.ui @@ -0,0 +1,221 @@ + + SettingsDialog + + + + 0 + 0 + 547 + 386 + + + + + + + 0 + + + + Directory + + + + + + Directory Name + + + + + + + + &Directory: + + + kcfg_Path + + + + + + + + + + + + Select the directory whose contents should be represented by this resource. If the directory does not exist, it will be created. + + + true + + + + + + + + + + Access Rights + + + + + + Read only + + + + + + + If read-only mode is enabled, no changes will be written to the directory selected above. Read-only mode will be automatically enabled if you do not have write access to the directory. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + + Tuning + + + + + + The options on this page allow you to change parameters that balance data safety and consistency against performance. In general you should be careful with changing anything here, the defaults are good enough in most cases. + + + true + + + + + + + + + Autosave delay: + + + + + + + 1 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + + + kcfg_ReadOnly + toggled(bool) + kcfg_AutosaveInterval + setDisabled(bool) + + + 273 + 205 + + + 157 + 101 + + + + + kcfg_ReadOnly + toggled(bool) + autosaveLabel + setDisabled(bool) + + + 273 + 205 + + + 56 + 101 + + + + +
diff --git a/kdepim-runtime/resources/vcarddir/vcarddirresource.cpp b/kdepim-runtime/resources/vcarddir/vcarddirresource.cpp new file mode 100644 index 00000000..72999a8a --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/vcarddirresource.cpp @@ -0,0 +1,278 @@ +/* + Copyright (c) 2008 Tobias Koenig + Copyright (c) 2008 Bertjan Broeksema + + 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 "vcarddirresource.h" + +#include "settingsadaptor.h" +#include "../shared/dirsettingsdialog.h" + +#include +#include +#include + +#include +#include +#include + +using namespace Akonadi; + +VCardDirResource::VCardDirResource( const QString &id ) + : ResourceBase( id ) +{ + // setup the resource + new SettingsAdaptor( Settings::self() ); + QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), + Settings::self(), QDBusConnection::ExportAdaptors ); + + changeRecorder()->itemFetchScope().fetchFullPayload(); +} + +VCardDirResource::~VCardDirResource() +{ + // clear cache + mAddressees.clear(); +} + +void VCardDirResource::aboutToQuit() +{ + Settings::self()->writeConfig(); +} + +void VCardDirResource::configure( WId windowId ) +{ + SettingsDialog dlg( windowId ); + dlg.setWindowIcon( KIcon( QLatin1String("text-directory") ) ); + if ( dlg.exec() ) { + initializeVCardDirectory(); + loadAddressees(); + + synchronize(); + + emit configurationDialogAccepted(); + } else { + emit configurationDialogRejected(); + } +} + +bool VCardDirResource::loadAddressees() +{ + mAddressees.clear(); + + QDirIterator it( vCardDirectoryName() ); + while ( it.hasNext() ) { + it.next(); + if ( it.fileName() != QLatin1String(".") && it.fileName() != QLatin1String("..") && it.fileName() != QLatin1String("WARNING_README.txt") ) { + QFile file( it.filePath() ); + if (file.open( QIODevice::ReadOnly )) { + const QByteArray data = file.readAll(); + file.close(); + + const KABC::Addressee addr = mConverter.parseVCard( data ); + if ( !addr.isEmpty() ) { + mAddressees.insert( addr.uid(), addr ); + } + } else { + kDebug()<<" file can't be load "<& ) +{ + const QString remoteId = item.remoteId(); + if ( !mAddressees.contains( remoteId ) ) { + emit error( i18n( "Contact with uid '%1' not found.", remoteId ) ); + return false; + } + + Item newItem( item ); + newItem.setPayload( mAddressees.value( remoteId ) ); + itemRetrieved( newItem ); + + return true; +} + +void VCardDirResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection& ) +{ + if ( Settings::self()->readOnly() ) { + emit error( i18n( "Trying to write to a read-only directory: '%1'", vCardDirectoryName() ) ); + cancelTask(); + return; + } + + KABC::Addressee addressee; + if ( item.hasPayload() ) + addressee = item.payload(); + + if ( !addressee.isEmpty() ) { + // add it to the cache... + mAddressees.insert( addressee.uid(), addressee ); + + // ... and write it through to the file system + const QByteArray data = mConverter.createVCard( addressee ); + + QFile file( vCardDirectoryFileName( addressee.uid() ) ); + file.open( QIODevice::WriteOnly ); + file.write( data ); + file.close(); + + // report everything ok + Item newItem( item ); + newItem.setRemoteId( addressee.uid() ); + changeCommitted( newItem ); + + } else { + changeProcessed(); + } +} + +void VCardDirResource::itemChanged( const Akonadi::Item &item, const QSet& ) +{ + if ( Settings::self()->readOnly() ) { + emit error( i18n( "Trying to write to a read-only directory: '%1'", vCardDirectoryName() ) ); + cancelTask(); + return; + } + + KABC::Addressee addressee; + if ( item.hasPayload() ) + addressee = item.payload(); + + if ( !addressee.isEmpty() ) { + // change it in the cache... + mAddressees.insert( addressee.uid(), addressee ); + + // ... and write it through to the file system + const QByteArray data = mConverter.createVCard( addressee ); + + QFile file( vCardDirectoryFileName( addressee.uid() ) ); + if (file.open( QIODevice::WriteOnly )) { + file.write( data ); + file.close(); + + Item newItem( item ); + newItem.setRemoteId( addressee.uid() ); + changeCommitted( newItem ); + } else { + kDebug()<<" We can't write in file "<readOnly() ) { + emit error( i18n( "Trying to write to a read-only directory: '%1'", vCardDirectoryName() ) ); + cancelTask(); + return; + } + + // remove it from the cache... + if ( mAddressees.contains( item.remoteId() ) ) + mAddressees.remove( item.remoteId() ); + + // ... and remove it from the file system + QFile::remove( vCardDirectoryFileName( item.remoteId() ) ); + + changeProcessed(); +} + +void VCardDirResource::retrieveCollections() +{ + Collection c; + c.setParentCollection( Collection::root() ); + c.setRemoteId( vCardDirectoryName() ); + c.setName( name() ); + QStringList mimeTypes; + mimeTypes << KABC::Addressee::mimeType(); + c.setContentMimeTypes( mimeTypes ); + if ( Settings::self()->readOnly() ) { + c.setRights( Collection::CanChangeCollection ); + } else { + Collection::Rights rights = Collection::ReadOnly; + rights |= Collection::CanChangeItem; + rights |= Collection::CanCreateItem; + rights |= Collection::CanDeleteItem; + rights |= Collection::CanChangeCollection; + c.setRights( rights ); + } + + EntityDisplayAttribute* attr = c.attribute( Collection::AddIfMissing ); + attr->setDisplayName( i18n( "Contacts Folder" ) ); + attr->setIconName( QLatin1String("x-office-address-book") ); + + Collection::List list; + list << c; + collectionsRetrieved( list ); +} + +void VCardDirResource::retrieveItems( const Akonadi::Collection& ) +{ + Item::List items; + + foreach ( const KABC::Addressee &addressee, mAddressees ) { + Item item; + item.setRemoteId( addressee.uid() ); + item.setMimeType( KABC::Addressee::mimeType() ); + items.append( item ); + } + + itemsRetrieved( items ); +} + +QString VCardDirResource::vCardDirectoryName() const +{ + return Settings::self()->path(); +} + +QString VCardDirResource::vCardDirectoryFileName( const QString &file ) const +{ + return Settings::self()->path() + QDir::separator() + file; +} + +void VCardDirResource::initializeVCardDirectory() const +{ + QDir dir( vCardDirectoryName() ); + + // if folder does not exists, create it + if ( !dir.exists() ) + QDir::root().mkpath( dir.absolutePath() ); + + // check whether warning file is in place... + QFile file( dir.absolutePath() + QDir::separator() + QLatin1String("WARNING_README.txt") ); + if ( !file.exists() ) { + // ... if not, create it + file.open( QIODevice::WriteOnly ); + file.write( "Important Warning!!!\n\n" + "Don't create or copy vCards inside this folder manually, they are managed by the Akonadi framework!\n" ); + file.close(); + } +} + +AKONADI_RESOURCE_MAIN( VCardDirResource ) + diff --git a/kdepim-runtime/resources/vcarddir/vcarddirresource.desktop b/kdepim-runtime/resources/vcarddir/vcarddirresource.desktop new file mode 100644 index 00000000..5a8e3920 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/vcarddirresource.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Name=vCard Directory +Name[bs]=VCard direktorij +Name[ca]=Directori vCard +Name[ca@valencia]=Directori vCard +Name[cs]=Adresář vizitek +Name[da]=vCard-mappe +Name[de]=vCard-Ordner +Name[el]=VCard Κατάλογος +Name[en_GB]=vCard Directory +Name[es]=Directorio vCard +Name[et]=vCardide kataloog +Name[fi]=vCard-kansio +Name[fr]=Dossier « vCard » +Name[ga]=Comhadlann v-Chártaí +Name[gl]=Directorio de vCard +Name[hu]=vCard mappa +Name[ia]=Directorio de VCard +Name[it]=Directory vCard +Name[kk]=VCard қапшығы +Name[ko]=vCard 디렉터리 +Name[lt]=vCard katalogas +Name[nb]=vCard-mappe +Name[nds]=VCard-Orner +Name[nl]=vCard-map +Name[pl]=Katalog vCard +Name[pt]=Pasta de vCard's +Name[pt_BR]=Pasta de vCards +Name[ru]=Каталог Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸ vCard +Name[sk]=Adresár VCard +Name[sr]=В‑кард фаÑцикла +Name[sr@ijekavian]=В‑кард фаÑцикла +Name[sr@ijekavianlatin]=VCard fascikla +Name[sr@latin]=VCard fascikla +Name[sv]=vCard-katalog +Name[tr]=VCard Dizini +Name[uk]=Каталог vCard +Name[x-test]=xxvCard Directoryxx +Name[zh_CN]=vCard 目录 +Name[zh_TW]=vCard 目錄 +Comment=Loads data from a directory with vCards +Comment[bs]=UÄitava podakte iz direktorija sa VCards +Comment[ca]=Carrega les dades des d'un directori amb vCard +Comment[ca@valencia]=Carrega dades des d'un directori amb vCard +Comment[cs]=NaÄítá data z adresáře vizitek +Comment[da]=Indlæser data fra en mappe med vCards +Comment[de]=Daten werden aus einem Ordner mit vCard-Dateien geladen +Comment[el]=ΦοÏτώνει δεδομένα από έναν κατάλογο που πεÏιέχει κάÏτες VCard +Comment[en_GB]=Loads data from a directory with vCards +Comment[es]=Carga datos desde un directorio con archivos vCard +Comment[et]=Andmete laadimine vCardide kataloogist +Comment[fi]=Lataa tietoa vCardeja sisältävästä kansiosta +Comment[fr]=Charge des données depuis un dossier contenant des fichiers « vCard » +Comment[ga]=Luchtaíonn sé seo sonraí ó chomhadlann ina bhfuil v-Chártaí +Comment[gl]=Carga datos desde un cartafol con vCards +Comment[hu]=Adatokat tölt be egy vCardokat tartalmazó mappából +Comment[ia]=Carga datos ex un directorio con vCards +Comment[it]=Carica dati da una directory con dei file vCard +Comment[kk]=VCard қапшығынан деректі алып береді +Comment[ko]=vCard 디렉터리ì—ì„œ ë°ì´í„°ë¥¼ 가져옵니다 +Comment[lt]=įkelia duomenis iÅ¡ katalogo su VCard failais +Comment[nb]=Laster data fra en mappe med vCard-filer +Comment[nds]=Laadt Daten ut en Orner mit VCard-Dateien +Comment[nl]=Laadt gegevens van een map met vCards +Comment[pl]=Wczytuje dane z katalogu wizytówkami vCard +Comment[pt]=Carrega os dados de uma pasta com ficheiros vCard +Comment[pt_BR]=Carrega os dados de uma pasta com vCards +Comment[ru]=Загрузка данных из папки Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸ vCard +Comment[sk]=NaÄíta dáta z adresára s vCards +Comment[sr]=Учитава податке из фаÑцикле Ñа в‑кардовима +Comment[sr@ijekavian]=Учитава податке из фаÑцикле Ñа в‑кардовима +Comment[sr@ijekavianlatin]=UÄitava podatke iz fascikle sa vCardovima +Comment[sr@latin]=UÄitava podatke iz fascikle sa vCardovima +Comment[sv]=Laddar data frÃ¥n en katalog med vCard-filer +Comment[tr]=VCard dosyaları bulunan bir dizinden verileri yükler +Comment[uk]=Завантажує дані з каталогу з vCard +Comment[x-test]=xxLoads data from a directory with vCardsxx +Comment[zh_CN]=ä»ŽåŒ…å« vCard æ–‡ä»¶çš„ç›®å½•è½½å…¥æ•°æ® +Comment[zh_TW]=從å«æœ‰ vCard 檔的目錄載入資料 +Type=AkonadiResource +Exec=akonadi_vcarddir_resource +Icon=text-directory + +X-Akonadi-MimeTypes=text/directory +X-Akonadi-Capabilities=Resource +X-Akonadi-Identifier=akonadi_vcarddir_resource diff --git a/kdepim-runtime/resources/vcarddir/vcarddirresource.h b/kdepim-runtime/resources/vcarddir/vcarddirresource.h new file mode 100644 index 00000000..1423bdfb --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/vcarddirresource.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2008 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. +*/ + +#ifndef VCARDDIRRESOURCE_H +#define VCARDDIRRESOURCE_H + +#include + +#include +#include + +class VCardDirResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::Observer +{ + Q_OBJECT + + public: + VCardDirResource( const QString &id ); + ~VCardDirResource(); + + public Q_SLOTS: + virtual void configure( WId windowId ); + virtual void aboutToQuit(); + + protected Q_SLOTS: + void retrieveCollections(); + void retrieveItems( const Akonadi::Collection &col ); + bool retrieveItem( const Akonadi::Item &item, const QSet &parts ); + + protected: + virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + virtual void itemChanged( const Akonadi::Item &item, const QSet &parts ); + virtual void itemRemoved( const Akonadi::Item &item ); + + private: + bool loadAddressees(); + QString vCardDirectoryName() const; + QString vCardDirectoryFileName( const QString &file ) const; + void initializeVCardDirectory() const; + + private: + QMap mAddressees; + KABC::VCardConverter mConverter; +}; + +#endif diff --git a/kdepim-runtime/resources/vcarddir/vcarddirresource.kcfg b/kdepim-runtime/resources/vcarddir/vcarddirresource.kcfg new file mode 100644 index 00000000..426e00d4 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/vcarddirresource.kcfg @@ -0,0 +1,22 @@ + + + + + + + + + + + 5 + + + + false + + + diff --git a/kdepim-runtime/resources/vcarddir/wizard/CMakeLists.txt b/kdepim-runtime/resources/vcarddir/wizard/CMakeLists.txt new file mode 100644 index 00000000..107459d1 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/wizard/CMakeLists.txt @@ -0,0 +1,4 @@ +set(VCARDDIR_FILE_DEFAULT_PATH "$HOME/") + +configure_file(vcarddirwizard.es.cmake ${CMAKE_CURRENT_BINARY_DIR}/vcarddirwizard.es) +install ( FILES vcarddirwizard.desktop ${CMAKE_CURRENT_BINARY_DIR}/vcarddirwizard.es vcarddirwizard.ui DESTINATION ${DATA_INSTALL_DIR}/akonadi/accountwizard/vcarddir ) diff --git a/kdepim-runtime/resources/vcarddir/wizard/Messages.sh b/kdepim-runtime/resources/vcarddir/wizard/Messages.sh new file mode 100644 index 00000000..4622ba0e --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/wizard/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/accountwizard_vcarddir.pot +$XGETTEXT -kqsTr *.es.cmake -j -o $podir/accountwizard_vcarddir.pot diff --git a/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.desktop b/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.desktop new file mode 100644 index 00000000..862f81ca --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Name=VCard Directory +Name[ca]=Directori vCard +Name[ca@valencia]=Directori vCard +Name[cs]=Adresář vizitek +Name[da]=VCard-mappe +Name[de]=vCard-Ordner +Name[en_GB]=VCard Directory +Name[es]=Directorio VCard +Name[et]=vCardide kataloog +Name[fi]=VCard-kansio +Name[fr]=Dossier « vCard » +Name[hu]=VCard mappa +Name[it]=Cartella vCard +Name[ja]=VCard ディレクトリ +Name[ko]=vCard 디렉터리 +Name[nb]=vCard-mappe +Name[nds]=vCard-Orner +Name[nl]=VCard-map +Name[nn]=vCard-katalog +Name[pl]=Katalog VCard +Name[pt]=Pasta de vCard's +Name[pt_BR]=Diretório vCard +Name[ru]=Каталог Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸ vCard +Name[sk]=Adresár VCard +Name[sr]=В‑кард фаÑцикла +Name[sr@ijekavian]=В‑кард фаÑцикла +Name[sr@ijekavianlatin]=VCard fascikla +Name[sr@latin]=VCard fascikla +Name[sv]=VCard-katalog +Name[tr]=VCard Dizini +Name[uk]=Каталог vCard +Name[x-test]=xxVCard Directoryxx +Name[zh_TW]=vCard 目錄 +Icon=text-directory +Comment=Loads contact from VCard Directory +Comment[ca]=Carrega contactes des d'un directori amb vCard +Comment[ca@valencia]=Carrega contactes des d'un directori amb vCard +Comment[cs]=NaÄítá data z adresáře vizitek +Comment[da]=Indlæser kontakt fra VCard-mappe +Comment[de]=Lädt Kontakte aus einem vCard-Ordner +Comment[en_GB]=Loads contact from VCard Directory +Comment[es]=Cargar contactos desde un directorio VCard +Comment[et]=Kontakti laadimine vCardide kataloogist +Comment[fi]=Lataa yhteystiedon VCard-kansiosta +Comment[fr]=Charge des données depuis un dossier « vCard » +Comment[hu]=Partnert tölt be egy VCard mappából +Comment[it]=Carica contatti da una cartella vCard +Comment[ko]=vCard 디렉터리ì—ì„œ ì—°ë½ì²˜ë¥¼ 가져옵니다 +Comment[nb]=Laster kontakt fra vCard-mappa +Comment[nds]=Laadt Kontakten ut en vCard-Orner +Comment[nl]=Laadt contactpersoon uit een VCard-map +Comment[pl]=Wczytuje kontakty z katalogu vCard +Comment[pt]=Carrega um contacto de uma pasta de ficheiros vCard +Comment[pt_BR]=Carrega os contatos de um diretório vCard +Comment[ru]=Загрузка контактов из каталога Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸ vCard +Comment[sk]=NaÄíta kontakt z adresára vCard +Comment[sr]=Учитава контакт из в‑кард фаÑцикле +Comment[sr@ijekavian]=Учитава контакт из в‑кард фаÑцикле +Comment[sr@ijekavianlatin]=UÄitava kontakt iz vCard fascikle +Comment[sr@latin]=UÄitava kontakt iz vCard fascikle +Comment[sv]=Laddar data frÃ¥n en VCard-katalog +Comment[tr]=Bir VCard dosyasından veri yükler +Comment[uk]=Завантажує дані запиÑу контакту із каталогу vCard +Comment[x-test]=xxLoads contact from VCard Directoryxx +Comment[zh_TW]=從 vCard 目錄載入è¯çµ¡äºº + +[Wizard] +Type=text/vcard +Script=vcarddirwizard.es + +[Translate] +Filename=accountwizard_vcarddir diff --git a/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.es.cmake b/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.es.cmake new file mode 100644 index 00000000..927fd36a --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.es.cmake @@ -0,0 +1,43 @@ +/* + Copyright (c) 2010 Till Adam + + 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. +*/ + +var page = Dialog.addPage( "vcarddirwizard.ui", qsTr("Settings") ); + +page.widget().lineEdit.text = "${VCARDDIR_FILE_DEFAULT_PATH}"; + +function validateInput() +{ + if ( page.widget().lineEdit.text == "" ) { + page.setValid( false ); + } else { + page.setValid( true ); + } +} + +function setup() +{ + var vcardRes = SetupManager.createResource( "akonadi_vcarddir_resource" ); + vcardRes.setOption( "Path", page.widget().lineEdit.text ); + vcardRes.setName( qsTr("Default Contact") ); + SetupManager.execute(); +} + +page.widget().lineEdit.textChanged.connect( validateInput ); +page.pageLeftNext.connect( setup ); +validateInput(); diff --git a/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.ui b/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.ui new file mode 100644 index 00000000..f0dfc5f1 --- /dev/null +++ b/kdepim-runtime/resources/vcarddir/wizard/vcarddirwizard.ui @@ -0,0 +1,45 @@ + + + icalWizard + + + + 0 + 0 + 400 + 300 + + + + + + + + + Path: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + diff --git a/kdepim-runtime/resourcetester/CMakeLists.txt b/kdepim-runtime/resourcetester/CMakeLists.txt new file mode 100644 index 00000000..c9d03132 --- /dev/null +++ b/kdepim-runtime/resourcetester/CMakeLists.txt @@ -0,0 +1,37 @@ + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) + +add_subdirectory( tests ) + +set( resourcetester_SRCS + global.cpp + main.cpp + resource.cpp + script.cpp + test.cpp + xmloperations.cpp + collectiontest.cpp + itemtest.cpp + qemu.cpp + system.cpp + wrappedobject.cpp +) + +kde4_add_executable(resourcetester ${resourcetester_SRCS}) + +target_link_libraries( resourcetester + ${KDEPIMLIBS_AKONADI_XML_LIBS} + ${KDEPIMLIBS_AKONADI_LIBS} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTXML_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ${QT_QTTEST_LIBRARY} + ${KDE4_KROSSCORE_LIBS} + ${KDE4_KROSSUI_LIBS} + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} + ${KDE4_KIO_LIBS} +) + diff --git a/kdepim-runtime/resourcetester/collectiontest.cpp b/kdepim-runtime/resourcetester/collectiontest.cpp new file mode 100644 index 00000000..5b83a224 --- /dev/null +++ b/kdepim-runtime/resourcetester/collectiontest.cpp @@ -0,0 +1,99 @@ +/* + Copyright (c) 2009 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 "collectiontest.h" +#include "test.h" + +#include +#include +#include +#include + +#include + +using namespace Akonadi; + +CollectionTest::CollectionTest(QObject* parent) : + QObject( parent ) +{ +} + +void CollectionTest::setParent(const Akonadi::Collection& parent) +{ + mParent = parent; +} + +void CollectionTest::setCollection(const Akonadi::Collection& collection) +{ + mCollection = collection; +} + +void CollectionTest::setParent(const QString& parentPath) +{ + CollectionPathResolver* resolver = new CollectionPathResolver( parentPath, this ); + if ( !resolver->exec() ) + Test::instance()->fail( resolver->errorString() ); + setParent( Collection( resolver->collection() ) ); +} + +void CollectionTest::setCollection(const QString& path) +{ + CollectionPathResolver* resolver = new CollectionPathResolver( path, this ); + if ( !resolver->exec() ) + Test::instance()->fail( resolver->errorString() ); + setCollection( Collection( resolver->collection() ) ); +} + +void CollectionTest::setName(const QString& name) +{ + mCollection.setName( name ); +} + +void CollectionTest::addContentType(const QString& type) +{ + mCollection.setContentMimeTypes( mCollection.contentMimeTypes() << type ); +} + +void CollectionTest::create() +{ + mCollection.setParentCollection( mParent ); + CollectionCreateJob* job = new CollectionCreateJob( mCollection, this ); + if ( !job->exec() ) + Test::instance()->fail( job->errorString() ); +} + +void CollectionTest::update() +{ + CollectionModifyJob* job = new CollectionModifyJob( mCollection, this ); + if ( !job->exec() ) + Test::instance()->fail( job->errorString() ); +} + +void CollectionTest::remove() +{ + CollectionDeleteJob* job = new CollectionDeleteJob( mCollection, this ); + if ( !job->exec() ) + Test::instance()->fail( job->errorString() ); +} + +QObject* CollectionTest::newInstance() +{ + return createNewInstance( this ); +} + diff --git a/kdepim-runtime/resourcetester/collectiontest.h b/kdepim-runtime/resourcetester/collectiontest.h new file mode 100644 index 00000000..f6f1d9b5 --- /dev/null +++ b/kdepim-runtime/resourcetester/collectiontest.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009 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 COLLECTIONTEST_H +#define COLLECTIONTEST_H + +#include "wrappedobject.h" + +#include + +class CollectionTest : public QObject, protected WrappedObject +{ + Q_OBJECT + public: + explicit CollectionTest( QObject *parent = 0 ); + + void setParent( const Akonadi::Collection &parent ); + void setCollection( const Akonadi::Collection &collection ); + + public slots: + QObject* newInstance(); + + void setParent( const QString &parentPath ); + void setCollection( const QString &path ); + void setName( const QString &name ); + void addContentType( const QString &type ); + + void create(); + void update(); + void remove(); + + private: + Akonadi::Collection mParent; + Akonadi::Collection mCollection; +}; + +#endif diff --git a/kdepim-runtime/resourcetester/global.cpp b/kdepim-runtime/resourcetester/global.cpp new file mode 100644 index 00000000..bc823560 --- /dev/null +++ b/kdepim-runtime/resourcetester/global.cpp @@ -0,0 +1,76 @@ +/* + * 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 "global.h" + +#include +#include +#include +#include + +class GlobalPrivate +{ + public: + GlobalPrivate() : parent( new QObject() ) {} + QString basePath; + QString vmPath; + QObject *parent; +}; + +K_GLOBAL_STATIC( GlobalPrivate, sInstance ) + +QString Global::basePath() +{ + return sInstance->basePath; +} + +void Global::setBasePath(const QString& path) +{ + sInstance->basePath = path; + if ( !path.endsWith( QDir::separator() ) ) + sInstance->basePath += QDir::separator(); +} + +QString Global::vmPath() +{ + if ( sInstance->vmPath.isEmpty() ) + setVMPath( KStandardDirs::locateLocal( "cache", "akonadi-resourcetester/", true ) ); + return sInstance->vmPath; +} + +void Global::setVMPath(const QString& path) +{ + kDebug() << path; + sInstance->vmPath = path; + if ( !path.endsWith( QDir::separator() ) ) + sInstance->vmPath += QDir::separator(); + const QDir dir( path ); + if ( !dir.exists() ) + QDir::root().mkpath( path ); +} + +QObject* Global::parent() +{ + Q_ASSERT( sInstance->parent ); + return sInstance->parent; +} + +void Global::cleanup() +{ + delete sInstance->parent; + sInstance->parent = 0; +} diff --git a/kdepim-runtime/resourcetester/global.h b/kdepim-runtime/resourcetester/global.h new file mode 100644 index 00000000..fd78e0e9 --- /dev/null +++ b/kdepim-runtime/resourcetester/global.h @@ -0,0 +1,37 @@ +/* + * 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 GLOBAL_H +#define GLOBAL_H + +#include + +class QObject; + +namespace Global +{ + QString basePath(); + void setBasePath( const QString &path ); + + QString vmPath(); + void setVMPath( const QString &path ); + + QObject *parent(); + void cleanup(); +} + +#endif diff --git a/kdepim-runtime/resourcetester/itemtest.cpp b/kdepim-runtime/resourcetester/itemtest.cpp new file mode 100644 index 00000000..705555fc --- /dev/null +++ b/kdepim-runtime/resourcetester/itemtest.cpp @@ -0,0 +1,82 @@ +/* + Copyright (c) 2009 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 "itemtest.h" + +#include "global.h" +#include "test.h" + +#include +#include + +#include +#include + +using namespace Akonadi; + +ItemTest::ItemTest( QObject *parent ) : + QObject( parent ) +{ + setObjectName( "global" ); +} + +void ItemTest::setParentCollection(const Akonadi::Collection& parent) +{ + mParent = parent; +} + +void ItemTest::setParentCollection(const QString& path) +{ + CollectionPathResolver* resolver = new CollectionPathResolver( path, this ); + if ( !resolver->exec() ) + Test::instance()->fail( resolver->errorString() ); + setParentCollection( Collection( resolver->collection() ) ); +} + +QString ItemTest::mimeType() const +{ + return mItem.mimeType(); +} + +void ItemTest::setMimeType(const QString& mimeType) +{ + mItem.setMimeType( mimeType ); +} + +void ItemTest::setPayloadFromFile(const QString& fileName) +{ + QFile file( Global::basePath() + fileName ); + if ( !file.open( QFile::ReadOnly ) ) + Test::instance()->fail( file.errorString() ); + mItem.setPayloadFromData( file.readAll() ); +} + +void ItemTest::create() +{ + ItemCreateJob* job = new ItemCreateJob( mItem, mParent, this ); + if ( !job->exec() ) + Test::instance()->fail( job->errorString() ); + mItem = job->item(); +} + +QObject* ItemTest::newInstance() +{ + return createNewInstance( this ); +} + diff --git a/kdepim-runtime/resourcetester/itemtest.h b/kdepim-runtime/resourcetester/itemtest.h new file mode 100644 index 00000000..cf46a7c0 --- /dev/null +++ b/kdepim-runtime/resourcetester/itemtest.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009 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 ITEMTEST_H +#define ITEMTEST_H + +#include "wrappedobject.h" + +#include +#include + +class ItemTest : public QObject, protected WrappedObject +{ + Q_OBJECT + public: + explicit ItemTest( QObject *parent = 0 ); + + void setParentCollection( const Akonadi::Collection &parent ); + + public slots: + QObject* newInstance(); + + void setParentCollection( const QString &path ); + QString mimeType() const; + void setMimeType( const QString &mimeType ); + void setPayloadFromFile( const QString &fileName ); + + void create(); + + private: + Akonadi::Collection mParent; + Akonadi::Item mItem; +}; + +Q_DECLARE_METATYPE( ItemTest* ) + +#endif diff --git a/kdepim-runtime/resourcetester/main.cpp b/kdepim-runtime/resourcetester/main.cpp new file mode 100644 index 00000000..b4bffcc6 --- /dev/null +++ b/kdepim-runtime/resourcetester/main.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009 Igor Trindade Oliveira + * + * 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 "resource.h" +#include "script.h" +#include "xmloperations.h" +#include "global.h" +#include "test.h" +#include "collectiontest.h" +#include "itemtest.h" +#include "system.h" +#include "qemu.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +void sigHandler( int signal) +{ + Q_UNUSED( signal ); + QCoreApplication::quit(); +} + +int main(int argc, char *argv[]) +{ + QString path; + + KAboutData aboutdata( "akonadi-RT", 0, + ki18n( "Akonadi Resource Tester" ), + "1.0", + ki18n( "Resource Tester" ), + KAboutData::License_GPL, + ki18n( "(c) 2009 Igor Trindade Oliveira" ) ); + + KCmdLineArgs::init( argc, argv, &aboutdata ); + + + KCmdLineOptions options; + options.add( "c" ).add( "config ", ki18n( "Configuration file to open" ), "script.js/py/rb" ); + KCmdLineArgs::addCmdLineOptions( options ); + + KApplication app; + + const KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + if ( args->isSet( "config" ) ) + path.append(args->getOption( "config" )) ; + else + return -1; + Global::setBasePath( QFileInfo( path ).absolutePath() ); + +#ifdef Q_OS_UNIX + signal( SIGINT, sigHandler ); + signal( SIGQUIT, sigHandler ); +#endif + + if ( !Akonadi::Control::start() ) + qFatal( "Unable to start Akonadi!" ); + + Script *script = new Script(); + + script->configure(path); + script->insertObject( new XmlOperations( Global::parent() ), "XmlOperations" ); + script->insertObject( new Resource( Global::parent() ), "Resource" ); + script->insertObject( Test::instance(), "Test" ); + script->insertObject( new CollectionTest( Global::parent() ), "CollectionTest" ); + script->insertObject( new ItemTest( Global::parent() ), "ItemTest" ); + script->insertObject( new System( Global::parent() ), "System" ); + script->insertObject( new QEmu( Global::parent() ), "QEmu" ); + QTimer::singleShot( 0, script, SLOT(start()) ); + + const int result = app.exec(); + Global::cleanup(); + return result; +} diff --git a/kdepim-runtime/resourcetester/qemu.cpp b/kdepim-runtime/resourcetester/qemu.cpp new file mode 100644 index 00000000..7aa39878 --- /dev/null +++ b/kdepim-runtime/resourcetester/qemu.cpp @@ -0,0 +1,204 @@ +/* + Copyright (c) 2009 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 "qemu.h" + +#include "global.h" +#include "test.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +QEmu::QEmu(QObject* parent) : + QObject( parent ), + mVMConfig( 0 ), + mVMProcess( 0 ), + mPortOffset( 42000 ), // TODO should be somewhat dynamic to allo running multiple instances in parallel + mMonitorPort( -1 ), + mStarted( false ) +{ + Q_ASSERT( parent ); +} + +QEmu::~QEmu() +{ + if ( mVMProcess && mVMProcess->state() == QProcess::Running ) + stop(); + delete mVMConfig; +} + +void QEmu::setVMConfig(const QString& configFileName) +{ + delete mVMConfig; + mVMConfig = new KConfig( Global::basePath() + '/' + configFileName ); +} + +void QEmu::start() +{ + Q_ASSERT( mVMConfig ); + if ( mVMProcess ) { + kWarning() << "VM already running."; + return; + } + + KConfigGroup emuConf( mVMConfig, "Emulator" ); + QStringList args = KShell::splitArgs( emuConf.readEntry( "Arguments", QString() ) ); + const QList ports = emuConf.readEntry( "Ports", QList() ); + foreach ( int port, ports ) { + args << "-redir" << QString::fromLatin1( "tcp:%1::%2" ).arg( port + mPortOffset ).arg( port ); + } + mMonitorPort = emuConf.readEntry( "MonitorPort", 23 ) + mPortOffset; + args << "-monitor" << QString::fromLatin1( "tcp:127.0.0.1:%1,server,nowait" ).arg( mMonitorPort ); + args << "-hda" << vmImage(); + + // If a SnapshotName is given in the configuration, load that snapshot + // with -loadvm and assume that it's OK to write to he image file. + // Othewise, use the -snapshot option to avoid changing the image + // file. + QString snapshotName = emuConf.readEntry( "SnapshotName", "" ); + if ( snapshotName.isEmpty() ) { + args << "-snapshot"; + } else { + args << "-loadvm" << snapshotName; + } + + kDebug() << "Starting QEMU with arguments" << args << "..."; + + mVMProcess = new KProcess( this ); + connect( mVMProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(vmFinished(int,QProcess::ExitStatus)) ); + mVMProcess->setProgram( "qemu", args ); + mVMProcess->start(); + mVMProcess->waitForStarted(); + mStarted = true; + kDebug() << "QEMU started."; + + if ( emuConf.readEntry( "WaitForPorts", true ) ) { + const QList waitPorts = emuConf.readEntry( "WaitForPorts", QList() ); + foreach ( int port, waitPorts ) + waitForPort( port ); + } +} + +void QEmu::stop() +{ + mStarted = false; + if ( !mVMProcess ) + return; + kDebug() << "Stopping QEMU..."; + + // send stop command via QEMU monitor + QTcpSocket socket; + socket.connectToHost( "localhost", mMonitorPort ); + if ( socket.waitForConnected() ) { + socket.write( "quit\n"); + socket.flush(); + socket.waitForBytesWritten(); + mVMProcess->waitForFinished(10000); + } else { + kDebug() << "Unable to connect to QEMU monitor:" << socket.errorString(); + } + + if ( mVMProcess->state() == QProcess::Running ) { + kDebug() << "qemu is still running. terminating"; + mVMProcess->terminate(); + } + + delete mVMProcess; + mVMProcess = 0; + kDebug() << "QEMU stopped."; +} + +QString QEmu::vmImage() const +{ + KConfigGroup conf( mVMConfig, "Image" ); + + const KUrl imageUrl = conf.readEntry( "Source", QString() ); + Q_ASSERT( !imageUrl.isEmpty() ); + + const QString imageArchiveFileName = imageUrl.fileName(); + Q_ASSERT( !imageArchiveFileName.isEmpty() ); + + // check if the image has been downloaded already + const QString localArchiveFileName = Global::vmPath() + imageArchiveFileName; + if ( !QFile::exists( localArchiveFileName ) ) { + kDebug() << "Downloading VM image from" << imageUrl << "to" << localArchiveFileName << "..."; + const bool result = KIO::NetAccess::file_copy( imageUrl, localArchiveFileName, 0 ); + if ( !result ) + kFatal() << "Downloading" << imageUrl << "failed!"; + kDebug() << "Downloading VM image complete."; + } + + // check if the image archive has been extracted yet + const QString extractedDirName = Global::vmPath() + imageArchiveFileName + ".directory"; + if ( !QDir::root().exists( extractedDirName ) ) { + kDebug() << "Extracting VM image..."; + QDir::root().mkpath( extractedDirName ); + KProcess proc; + proc.setWorkingDirectory( extractedDirName ); + proc.setProgram( "tar", QStringList() << "xvf" << localArchiveFileName ); + proc.execute(); + kDebug() << "Extracting VM image complete."; + } + + // check if the actual image file is there and return it + const QString imageFile = extractedDirName + QDir::separator() + conf.readEntry( "File", QString() ); + if ( !QFile::exists( imageFile ) ) + kFatal() << "Image file" << imageFile << "does not exist."; + + return imageFile; +} + +void QEmu::waitForPort(int port) +{ + kDebug() << "Waiting for port" << (port + mPortOffset) << "..."; + forever { + QTcpSocket socket; + socket.connectToHost( "localhost", port + mPortOffset ); + if ( !socket.waitForConnected( 5000 ) ) { + QTest::qWait( 5000 ); + continue; + } + if ( socket.waitForReadyRead( 5000 ) ) + break; + } +} + +int QEmu::portOffset() const +{ + return mPortOffset; +} + +void QEmu::vmFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + Q_UNUSED( exitCode ); + Q_UNUSED( exitStatus ); + if ( mStarted ) + Test::instance()->fail( "QEMU termineated!" ); +} + + diff --git a/kdepim-runtime/resourcetester/qemu.h b/kdepim-runtime/resourcetester/qemu.h new file mode 100644 index 00000000..e03a33f1 --- /dev/null +++ b/kdepim-runtime/resourcetester/qemu.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009 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 QEMU_H +#define QEMU_H + +#include + +class KConfig; + +class QEmu : public QObject +{ + Q_OBJECT + public: + explicit QEmu( QObject *parent ); + ~QEmu(); + + public slots: + void setVMConfig( const QString &configFileName ); + void start(); + void stop(); + int portOffset() const; + + private: + QString vmImage() const; + void waitForPort( int port ); + + private slots: + void vmFinished( int exitCode, QProcess::ExitStatus exitStatus ); + + private: + KConfig* mVMConfig; + KProcess* mVMProcess; + int mPortOffset; + int mMonitorPort; + bool mStarted; +}; + +#endif diff --git a/kdepim-runtime/resourcetester/resource.cpp b/kdepim-runtime/resourcetester/resource.cpp new file mode 100644 index 00000000..d1658e35 --- /dev/null +++ b/kdepim-runtime/resourcetester/resource.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009 Igor Trindade Oliveira + * 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 "resource.h" + +#include "global.h" +#include +#include "test.h" + +#include +#include + +#include +#include + +#include +#include +#include + +using namespace Akonadi; + +Resource::Resource(QObject* parent) : + QObject( parent ) +{ + Q_ASSERT( parent ); +} + +Resource::~Resource() +{ + destroy(); +} + +void Resource::setType(const QString& type) +{ + mTypeIdentifier = type; +} + +QString Resource::identifier() const +{ + return mInstance.identifier(); +} + +void Resource::setOption(const QString& key, const QVariant& value) +{ + mSettings.insert( key, value ); +} + +void Resource::setPathOption(const QString& key, const QString& path) +{ + if ( QFileInfo( path ).isAbsolute() ) + setOption( key, path ); + else + setOption( key, QString(Global::basePath() + QDir::separator() + path) ); +} + + +bool Resource::createResource() +{ + if ( mInstance.isValid() ) + return false; + + const AgentType type = AgentManager::self()->type( mTypeIdentifier ); + if ( !type.isValid() ) + return false; + + AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type, this ); + if ( !job->exec() ) { + kWarning() << job->errorText(); + return false; + } + mInstance = job->instance(); + + QDBusInterface iface( "org.freedesktop.Akonadi.Resource." + identifier(), "/Settings" ); + if ( !iface.isValid() ) + return false; + + // configure resource + for ( QHash::const_iterator it = mSettings.constBegin(); it != mSettings.constEnd(); ++it ) { + kDebug() << "Setting up " << it.key() << " for agent " << identifier(); + const QString methodName = QString::fromLatin1("set%1").arg( it.key() ); + const QVariant arg = it.value(); + QDBusReply reply = iface.call( methodName, arg ); + if ( !reply.isValid() ) + kError() << "Setting " << it.key() << " failed for agent " << identifier() << ":" << reply.error().message(); + } + mInstance.reconfigure(); + + ResourceSynchronizationJob *syncJob = new ResourceSynchronizationJob( mInstance, this ); + if ( !syncJob->exec() ) + kError() << "Synching resource failed: " << syncJob->errorString(); + + return true; +} + +void Resource::create() +{ + if ( !createResource() ) + Test::instance()->fail( "Creating resource failed." ); +} + + +void Resource::destroy() +{ + if ( !mInstance.isValid() ) + return; + AgentManager::self()->removeInstance( mInstance ); + mInstance = AgentInstance(); +} + +void Resource::write() +{ + QDBusInterface iface( "org.freedesktop.Akonadi", "/notifications/debug", "org.freedesktop.Akonadi.NotificationManager" ); + Q_ASSERT( iface.isValid() ); + QDBusReply result = iface.call( "emitPendingNotifications" ); + if ( !result.isValid() ) + Test::instance()->fail( result.error().message() ); + ResourceSynchronizationJob *syncJob = new ResourceSynchronizationJob( mInstance, this ); + if ( !syncJob->exec() ) + kError() << "Synching resource failed: " << syncJob->errorString(); +} + +void Resource::recreate() +{ + write(); + destroy(); + create(); +} + +QObject* Resource::newInstance() +{ + return createNewInstance( this ); +} + +QObject* Resource::newInstance(const QString& type) +{ + Resource* r = qobject_cast( createNewInstance( this ) ); + Q_ASSERT( r ); + r->setType( type ); + return r; +} + diff --git a/kdepim-runtime/resourcetester/resource.h b/kdepim-runtime/resourcetester/resource.h new file mode 100644 index 00000000..a4274663 --- /dev/null +++ b/kdepim-runtime/resourcetester/resource.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2009 Volker Krause + Copyright (c) 2009 Igor Trindade Oliveira + + 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 RESOURCE_H +#define RESOURCE_H + +#include "wrappedobject.h" +#include + +#include +#include +#include + +class Resource: public QObject, protected WrappedObject +{ + Q_OBJECT + public: + explicit Resource( QObject *parent ); + ~Resource(); + + public slots: + QObject* newInstance(); + QObject* newInstance( const QString &type ); + + void setType( const QString &type ); + QString identifier() const; + + void setOption( const QString &key, const QVariant &value ); + void setPathOption( const QString &key, const QString &path ); + + bool createResource(); + void create(); + void destroy(); + void write(); + void recreate(); + + private: + QString mTypeIdentifier; + Akonadi::AgentInstance mInstance; + QHash mSettings; +}; + +#endif diff --git a/kdepim-runtime/resourcetester/script.cpp b/kdepim-runtime/resourcetester/script.cpp new file mode 100644 index 00000000..809962a5 --- /dev/null +++ b/kdepim-runtime/resourcetester/script.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009 Igor Trindade Oliveira + * + * 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 "script.h" +#include "global.h" +#include +#include + +Script::Script() +{ + action = new Kross::Action(this, "ResourceTester"); + connect( action, SIGNAL(finished(Kross::Action*)), SLOT(finished(Kross::Action*)) ); + action->addObject( this, QLatin1String( "Script" ) ); +} + +void Script::configure(const QString &path) +{ + action->setFile(path); +} + +void Script::insertObject(QObject *object, const QString &objectName) +{ + action->addObject(object, objectName); +} + +void Script::include(const QString& path) +{ + QFile f( Global::basePath() + path ); + if ( !f.open( QFile::ReadOnly ) ) + kError() << "Unable to open file" << Global::basePath() + path; + else + action->evaluate( f.readAll() ); +} + +QString Script::absoluteFileName(const QString& path) +{ + return Global::basePath() + path; +} + +void Script::start() +{ + action->trigger(); +} + +void Script::finished(Kross::Action* action) +{ + if ( action->hadError() ) { + kError() << action->errorMessage() << action->errorTrace(); + QCoreApplication::instance()->exit( 1 ); + } else { + QCoreApplication::instance()->quit(); + } +} + + diff --git a/kdepim-runtime/resourcetester/script.h b/kdepim-runtime/resourcetester/script.h new file mode 100644 index 00000000..85e5c0c6 --- /dev/null +++ b/kdepim-runtime/resourcetester/script.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009 Igor Trindade Oliveira + * + * 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 SCRIPT_H +#define SCRIPT_H + +#include +#include + +class Script : public QObject +{ + Q_OBJECT + public: + Script(); + void configure(const QString &path); + void insertObject(QObject *object, const QString &objectName); + + public slots: + Q_SCRIPTABLE void include( const QString &path ); + Q_SCRIPTABLE QString absoluteFileName( const QString &path ); + + private slots: + void start(); + void finished( Kross::Action *action ); + + private: + Kross::Action *action; +}; + +#endif diff --git a/kdepim-runtime/resourcetester/system.cpp b/kdepim-runtime/resourcetester/system.cpp new file mode 100644 index 00000000..e528a08b --- /dev/null +++ b/kdepim-runtime/resourcetester/system.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2009 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 "system.h" +#include "test.h" +#include "global.h" + +#include +#include +#include + +System::System(QObject* parent) : + QObject( parent ) +{ +} + +void System::exec(const QString &_program, const QStringList& args) +{ + QString program = _program; + if ( QFile::exists( Global::basePath() + _program ) ) + program = Global::basePath() + _program; + Test::instance()->verify( KProcess::execute( program, args ) == 0 ); +} + +void System::sleep(int secs) +{ + QTest::qWait( secs * 1000 ); +} + diff --git a/kdepim-runtime/resourcetester/system.h b/kdepim-runtime/resourcetester/system.h new file mode 100644 index 00000000..688d52aa --- /dev/null +++ b/kdepim-runtime/resourcetester/system.h @@ -0,0 +1,36 @@ +/* + Copyright (c) 2009 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 SYSTEM_H +#define SYSTEM_H + +#include + +class System : public QObject +{ + Q_OBJECT + public: + explicit System( QObject *parent = 0 ); + + public slots: + void exec( const QString& program, const QStringList& args ); + void sleep( int secs ); +}; + +#endif diff --git a/kdepim-runtime/resourcetester/test.cpp b/kdepim-runtime/resourcetester/test.cpp new file mode 100644 index 00000000..3b935cc4 --- /dev/null +++ b/kdepim-runtime/resourcetester/test.cpp @@ -0,0 +1,80 @@ +/* + Copyright (c) 2009 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 "test.h" +#include "resource.h" +#include "global.h" + +#include +#include +#include + +Test* Test::mSelf = 0; + +Test::Test(QObject* parent) : + QObject( parent ) +{ +} + +void Test::verify(bool value) +{ + if ( !value ) + fail( "Assertion failed." ); +} + +void Test::verify( QObject* object, const QString &slot ) +{ + kDebug() << object << slot; + bool result = false; + if ( !QMetaObject::invokeMethod( object, slot.toLatin1(), Q_RETURN_ARG( bool, result ) ) ) + fail( "Unable to call method " + slot ); + + if ( result ) + return; + + QString lastError = QString( "Call to method " + slot + " returned false." ); + QMetaObject::invokeMethod( object, "lastError", Q_RETURN_ARG( QString, lastError ) ); + fail( lastError ); +} + +void Test::fail(const QString& error) +{ + kError() << error; + abort(); +} + +void Test::abort() +{ + Global::cleanup(); + exit( -1 ); +} + +void Test::alert(const QString& msg) +{ + KMessageBox::information( 0, msg ); +} + +Test* Test::instance() +{ + if ( !mSelf ) + mSelf = new Test( Global::parent() ); + return mSelf; +} + + diff --git a/kdepim-runtime/resourcetester/test.h b/kdepim-runtime/resourcetester/test.h new file mode 100644 index 00000000..8daebf45 --- /dev/null +++ b/kdepim-runtime/resourcetester/test.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009 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 TEST_H +#define TEST_H + +#include + +class Test : public QObject +{ + Q_OBJECT + public: + static Test* instance(); + + public slots: + void verify( bool value ); + void verify( QObject *object, const QString &slot ); + void fail( const QString &error ); + void abort(); + void alert( const QString &msg ); + + private: + Test( QObject *parent = 0 ); + + static Test* mSelf; +}; + +#endif diff --git a/kdepim-runtime/resourcetester/tests/CMakeLists.txt b/kdepim-runtime/resourcetester/tests/CMakeLists.txt new file mode 100644 index 00000000..9c7d9f48 --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/CMakeLists.txt @@ -0,0 +1 @@ +akonadi_add_resourcetest( ctor-test construct.es ) diff --git a/kdepim-runtime/resourcetester/tests/construct.es b/kdepim-runtime/resourcetester/tests/construct.es new file mode 100644 index 00000000..335aadd8 --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/construct.es @@ -0,0 +1,12 @@ +var i1 = ItemTest.newInstance(); +var i2 = ItemTest.newInstance(); + +print( i1 ); +print( i2 ); + +i1.setMimeType( "application/foo" ); +i2.setMimeType( "application/bar" ); + +print( i1.mimeType() + " != " + i2.mimeType() ); +Test.verify( i1.mimeType() != i2.mimeType() ); + diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/config-mysql-db.xml b/kdepim-runtime/resourcetester/tests/unittestenv/config-mysql-db.xml new file mode 100644 index 00000000..44ae937e --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/config-mysql-db.xml @@ -0,0 +1,6 @@ + + kdehome + xdgconfig-mysql.db + xdglocal + true + diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/config.xml b/kdepim-runtime/resourcetester/tests/unittestenv/config.xml new file mode 100644 index 00000000..0b5ea9c7 --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/config.xml @@ -0,0 +1,8 @@ + + kdehome + xdgconfig + xdglocal + akonadi_knut_resource + akonadi_knut_resource + akonadi_knut_resource + diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/apps/kwallet/kdewallet.kwl b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/apps/kwallet/kdewallet.kwl new file mode 100644 index 0000000000000000000000000000000000000000..dfb4fd6f130899932b63824476feae2242fdc2b3 GIT binary patch literal 288 zcmV+*0pI>hS3yinMN|q601XNN00000000B*RiDU}=*)NT3{7v}^}Wjg0002tj^=VQ zR8evXJm6EVoh)+z00028q|pE+d2wz)CLODzz7NL$0004E%+@m=OY35)7>+9~j=S_; zCGRq#K2h)5K@r>1Uxz%mCJze3Gj!@7>wsc=dP9U&kGg)>sTbnjpL4Y m@xR(is?#RdlSl54vRQMkVOl-_&0&@90|J*MRASzYandg0;DXQq literal 0 HcmV?d00001 diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc new file mode 100644 index 00000000..1cac492a --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/akonadi-firstrunrc @@ -0,0 +1,3 @@ +[ProcessedDefaults] +defaultaddressbook=done +defaultcalendar=done diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdebugrc b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdebugrc new file mode 100755 index 00000000..5cc0a643 --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdebugrc @@ -0,0 +1,78 @@ +[0] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=2 +FatalFilename[$e]=kdebug.dbg +FatalOutput=2 +InfoFilename[$e]=kdebug.dbg +InfoOutput=2 +WarnFilename[$e]=kdebug.dbg +WarnOutput=2 + +[264] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[5250] +InfoOutput=2 + +[7009] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7011] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7012] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 + +[7014] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=0 +FatalFilename[$e]=kdebug.dbg +FatalOutput=0 +InfoFilename[$e]=kdebug.dbg +InfoOutput=0 +WarnFilename[$e]=kdebug.dbg +WarnOutput=0 + +[7021] +AbortFatal=true +ErrorFilename[$e]=kdebug.dbg +ErrorOutput=4 +FatalFilename[$e]=kdebug.dbg +FatalOutput=4 +InfoFilename[$e]=kdebug.dbg +InfoOutput=4 +WarnFilename[$e]=kdebug.dbg +WarnOutput=4 diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdedrc b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdedrc new file mode 100644 index 00000000..41d17814 --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kdedrc @@ -0,0 +1,3 @@ +[General] +CheckSycoca=false +CheckFileStamps=false diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kwalletrc b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kwalletrc new file mode 100644 index 00000000..d063aeba --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/kdehome/share/config/kwalletrc @@ -0,0 +1,8 @@ +[Auto Allow] +kdewallet=Akonadi Resource + +[Wallet] +Close When Idle=false +Enabled=true +First Use=false +Use One Wallet=true diff --git a/kdepim-runtime/resourcetester/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc b/kdepim-runtime/resourcetester/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc new file mode 100644 index 00000000..fa9b2d47 --- /dev/null +++ b/kdepim-runtime/resourcetester/tests/unittestenv/xdgconfig-mysql.db/akonadi/akonadiserverrc @@ -0,0 +1,5 @@ +[%General] +ExternalPayload=false + +[Search] +Manager=Dummy diff --git a/kdepim-runtime/resourcetester/wrappedobject.cpp b/kdepim-runtime/resourcetester/wrappedobject.cpp new file mode 100644 index 00000000..36ba7c61 --- /dev/null +++ b/kdepim-runtime/resourcetester/wrappedobject.cpp @@ -0,0 +1,22 @@ +/* + Copyright (c) 2009 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 "wrappedobject.h" + +int WrappedObject::mInstanceCounter = 0; diff --git a/kdepim-runtime/resourcetester/wrappedobject.h b/kdepim-runtime/resourcetester/wrappedobject.h new file mode 100644 index 00000000..831d22d6 --- /dev/null +++ b/kdepim-runtime/resourcetester/wrappedobject.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009 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 WRAPPEDOBJECT_H +#define WRAPPEDOBJECT_H + +#include + +class WrappedObject +{ + protected: + template + QObject* createNewInstance( QObject *parent ) + { + T* instance = new T( parent ); + instance->setObjectName( instance->metaObject()->className() + QString::number( ++mInstanceCounter ) ); + return instance; + } + + private: + static int mInstanceCounter; +}; + +#endif diff --git a/kdepim-runtime/resourcetester/xmloperations.cpp b/kdepim-runtime/resourcetester/xmloperations.cpp new file mode 100644 index 00000000..9f414d96 --- /dev/null +++ b/kdepim-runtime/resourcetester/xmloperations.cpp @@ -0,0 +1,428 @@ +/* + Copyright (c) 2009 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 "xmloperations.h" +#include "global.h" +#include "test.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace Akonadi; + +template QTextStream& operator<<( QTextStream &s, const QSet &set ) +{ + s << '{'; + foreach ( const T &element, set ) + s << element << ", "; + s << '}'; + return s; +} + +QTextStream& operator<<( QTextStream &s, const QStringList &list ) +{ + s << '(' << list.join( ", " ) << ')'; + return s; +} + +XmlOperations::XmlOperations(QObject* parent) : + QObject( parent ), + mCollectionFields( 0xFF ), + mCollectionKey( RemoteId ), + mItemFields( 0xFF ), + mItemKey( ItemRemoteId ), + mNormalizeRemoteIds( false ) +{ +} + +XmlOperations::~XmlOperations() +{ +} + +Item XmlOperations::getItemByRemoteId(const QString& rid) +{ + return mDocument.itemByRemoteId( rid, true); +} + +Collection XmlOperations::getCollectionByRemoteId(const QString& rid) +{ + return mDocument.collectionByRemoteId(rid); +} + +void XmlOperations::setRootCollections(const QString& resourceId) +{ + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, this ); + job->fetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + job->fetchScope().setResource( resourceId ); + if ( job->exec() ) + setRootCollections( job->collections() ); + else + mErrorMsg = job->errorText(); +} + +void XmlOperations::setRootCollections(const Collection::List& roots) +{ + mRoots = roots; +} + +void XmlOperations::setXmlFile(const QString& fileName) +{ + mFileName = fileName; + if ( QFileInfo( fileName ).isAbsolute() ) + mDocument.loadFile( fileName ); + else + mDocument.loadFile( Global::basePath() + QDir::separator() + fileName ); +} + +QString XmlOperations::lastError() const +{ + return mErrorMsg; +} + +void XmlOperations::setCollectionKey(XmlOperations::CollectionField field) +{ + mCollectionKey = field; +} + +void XmlOperations::setCollectionKey(const QString& fieldName) +{ + const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); + setCollectionKey( static_cast( me.keyToValue( fieldName.toLatin1() ) ) ); +} + +void XmlOperations::ignoreCollectionField(XmlOperations::CollectionField field) +{ + mCollectionFields = mCollectionFields & ~field; +} + +void XmlOperations::ignoreCollectionField(const QString& fieldName) +{ + const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); + ignoreCollectionField( static_cast( me.keyToValue( fieldName.toLatin1() ) ) ); +} + +void XmlOperations::setItemKey(XmlOperations::ItemField field) +{ + mItemKey = field; +} + +void XmlOperations::setItemKey(const QString& _fieldName) +{ + QString fieldName = _fieldName; + if ( !fieldName.startsWith( QLatin1String ( "Item" ) ) ) + fieldName.prepend( "Item" ); + const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "ItemField" ) ); + setItemKey( static_cast( me.keyToValue( fieldName.toLatin1() ) ) ); +} + +void XmlOperations::ignoreItemField(XmlOperations::ItemField field) +{ + mItemFields = mItemFields & ~field; +} + +void XmlOperations::ignoreItemField(const QString& _fieldName) +{ + QString fieldName = _fieldName; + if ( !fieldName.startsWith( "Item" ) ) + fieldName.prepend( "Item" ); + const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "ItemField" ) ); + ignoreItemField( static_cast( me.keyToValue( fieldName.toLatin1() ) ) ); +} + +void XmlOperations::setNormalizeRemoteIds(bool enable) +{ + mNormalizeRemoteIds = enable; +} + +bool XmlOperations::compare() +{ + if ( !mDocument.isValid() ) { + mErrorMsg = mDocument.lastError(); + return false; + } + + if ( mRoots.isEmpty() ) { + if ( !mErrorMsg.isEmpty() ) + mErrorMsg = QLatin1String( "No root collections specified." ); + return false; + } + + const Collection::List docRoots = mDocument.childCollections( Collection::root() ); + if ( compareCollections( mRoots, docRoots ) ) + return true; + + XmlWriteJob *xmlJob = new XmlWriteJob( mRoots, mFileName + ".actual", this ); + if ( !xmlJob->exec() ) + kError() << xmlJob->errorText(); + return false; +} + +void XmlOperations::assertEqual() +{ + if ( !compare() ) + Test::instance()->fail( lastError() ); +} + +static QString normalizeRemoteId( const QString &in ) +{ + QString out( in ); + if ( in.startsWith( Global:: basePath() ) ) { + out = in.mid( Global::basePath().length() ); + if ( out.startsWith( QDir::separator() ) ) + out = out.mid( 1 ); + } + return out; +} + +Collection XmlOperations::normalizeCollection( const Collection &in ) const +{ + Collection out( in ); + if ( mNormalizeRemoteIds ) + out.setRemoteId( normalizeRemoteId( in.remoteId() ) ); + QStringList l = in.contentMimeTypes(); + std::sort( l.begin(), l.end() ); + out.setContentMimeTypes( l ); + return out; +} + +Item XmlOperations::normalizeItem( const Item& in ) const +{ + Item out( in ); + if ( mNormalizeRemoteIds ) + out.setRemoteId( normalizeRemoteId( in.remoteId() ) ); + return out; +} + +bool XmlOperations::compareCollections(const Collection::List& _cols, const Collection::List& _refCols) +{ + Collection::List cols; + foreach ( const Collection &c, _cols ) + cols.append( normalizeCollection( c ) ); + Collection::List refCols; + foreach ( const Collection &c, _refCols ) + refCols.append( normalizeCollection( c ) ); + + switch ( mCollectionKey ) { + case RemoteId: + sortEntityList( cols, &Collection::remoteId ); + sortEntityList( refCols, &Collection::remoteId ); + break; + case Name: + sortEntityList( cols, &Collection::name ); + sortEntityList( refCols, &Collection::name ); + break; + case None: + break; + default: + Q_ASSERT( false ); + } + + for ( int i = 0; i < cols.count(); ++i ) { + const Collection col = cols.at( i ); + if ( refCols.count() <= i ) { + mErrorMsg = QString::fromLatin1( "Additional collection with remote id '%1' was found." ).arg( col.remoteId() ); + return false; + } + + const Collection refCol = refCols.at( i ); + if ( !compareCollection( col, refCol ) ) + return false; + } + + if ( refCols.count() != cols.count() ) { + const Collection refCol = refCols.at( cols.count() ); + mErrorMsg = QString::fromLatin1( "Collection with remote id '%1' is missing." ).arg( refCol.remoteId() ); + return false; + } + + return true; +} + +bool XmlOperations::compareCollection(const Collection& col, const Collection& refCol) +{ + // compare the two collections + if ( !compareValue( col, refCol, &Collection::remoteId, RemoteId ) || + !compareValue( col, refCol, &Collection::contentMimeTypes, ContentMimeType ) || + !compareValue( col, refCol, &Collection::name, Name ) ) + return false; + + if ( (mCollectionFields & Attributes) && !compareAttributes( col, refCol ) ) + return false; + + // compare child items + ItemFetchJob *ijob = new ItemFetchJob( col, this ); + ijob->fetchScope().fetchAllAttributes( true ); + ijob->fetchScope().fetchFullPayload( true ); + if ( !ijob->exec() ) { + mErrorMsg = ijob->errorText(); + return false; + } + const Item::List items = ijob->items(); + const Item::List refItems = mDocument.items( refCol, true ); + if ( !compareItems( items, refItems ) ) + return false; + + // compare child collections + CollectionFetchJob *cjob = new CollectionFetchJob( col, CollectionFetchJob::FirstLevel, this ); + cjob->fetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + if ( !cjob->exec() ) { + mErrorMsg = cjob->errorText(); + return false; + } + const Collection::List cols = cjob->collections(); + const Collection::List refCols = mDocument.childCollections( refCol ); + return compareCollections( cols, refCols ); +} + +bool XmlOperations::hasItem(const Item& _item, const Collection& _col) +{ + ItemFetchJob *ijob = new ItemFetchJob( _col, this ); + ijob->fetchScope().fetchAllAttributes( true ); + ijob->fetchScope().fetchFullPayload( true ); + if ( !ijob->exec() ) { + mErrorMsg = ijob->errorText(); + return false; + } + const Item::List items = ijob->items(); + + for( int i = 0; i < items.count(); ++i) { + if( _item == items.at(i) ) { + return true; + } + } + return false; +} + +bool XmlOperations::hasItem(const Item& _item, const QString& rid) +{ + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, this ); + Collection::List colist; + + if ( job->exec() ) + colist = job->collections() ; + foreach( const Collection &collection, colist ) { + if(rid == collection.remoteId()) { + return hasItem(_item, collection); + } + } + return false; +} + + +bool XmlOperations::compareItems(const Item::List& _items, const Item::List& _refItems) +{ + Item::List items; + foreach ( const Item &i, _items ) + items.append( normalizeItem( i ) ); + Item::List refItems; + foreach ( const Item &i, _refItems ) + refItems.append( normalizeItem( i ) ); + + switch ( mItemKey ) { + case ItemRemoteId: + sortEntityList( items, &Item::remoteId ); + sortEntityList( refItems, &Item::remoteId ); + break; + case ItemNone: + break; + default: + Q_ASSERT( false ); + } + + for ( int i = 0; i < items.count(); ++i ) { + const Item item = items.at( i ); + if ( refItems.count() <= i ) { + mErrorMsg = QString::fromLatin1( "Additional item with remote id '%1' was found." ).arg( item.remoteId() ); + return false; + } + + const Item refItem = refItems.at( i ); + if ( !compareItem( item, refItem ) ) + return false; + } + + if ( refItems.count() != items.count() ) { + const Item refItem = refItems.at( items.count() ); + mErrorMsg = QString::fromLatin1( "Item with remote id '%1' is missing." ).arg( refItem.remoteId() ); + return false; + } + + return true; +} + +bool XmlOperations::compareItem(const Item& item, const Item& refItem) +{ + if ( !compareValue( item, refItem, &Item::remoteId, ItemRemoteId ) || + !compareValue( item, refItem, &Item::mimeType, ItemMimeType ) || + !compareValue( item, refItem, &Item::flags, ItemFlags ) || + !compareValue( item, refItem, &Item::payloadData, ItemPayload ) ) + return false; + + return compareAttributes( item, refItem ); +} + +bool XmlOperations::compareAttributes(const Entity& entity, const Entity& refEntity) +{ + Attribute::List attrs = entity.attributes(); + Attribute::List refAttrs = refEntity.attributes(); + std::sort( attrs.begin(), attrs.end(), boost::bind( &Attribute::type, _1 ) < boost::bind( &Attribute::type, _2 ) ); + std::sort( refAttrs.begin(), refAttrs.end(), boost::bind( &Attribute::type, _1 ) < boost::bind( &Attribute::type, _2 ) ); + + for ( int i = 0; i < attrs.count(); ++i ) { + Attribute* attr = attrs.at( i ); + if ( refAttrs.count() <= i ) { + mErrorMsg = QString::fromLatin1( "Additional attribute of type '%1' for object with remote id '%2' was found." ) + .arg( QString::fromLatin1( attr->type() ) ).arg( entity.remoteId() ); + return false; + } + + Attribute* refAttr = refAttrs.at( i ); + if ( attr->type() != refAttr->type() ) { + mErrorMsg = QString::fromLatin1( "Object with remote id '%1' misses attribute of type '%2'." ) + .arg( entity.remoteId() ).arg( QString::fromLatin1( refAttr->type() ) ); + return false; + } + + bool result = compareValue( attr->serialized(), refAttr->serialized() ); + if ( !result ) { + mErrorMsg.prepend( QString::fromLatin1( "Object with remote id '%1' differs in attribute '%2':\n" ) + .arg( entity.remoteId() ).arg( QString::fromLatin1( attr->type() ) ) ); + return false; + } + } + + if ( refAttrs.count() != attrs.count() ) { + Attribute* refAttr = refAttrs.at( attrs.count() ); + mErrorMsg = QString::fromLatin1( "Object with remote id '%1' misses attribute of type '%2'." ) + .arg( entity.remoteId() ).arg( QString::fromLatin1( refAttr->type() ) );; + return false; + } + + return true; +} + diff --git a/kdepim-runtime/resourcetester/xmloperations.h b/kdepim-runtime/resourcetester/xmloperations.h new file mode 100644 index 00000000..e75ad59f --- /dev/null +++ b/kdepim-runtime/resourcetester/xmloperations.h @@ -0,0 +1,179 @@ +/* + Copyright (c) 2009 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 XMLOPERATIONS_H +#define XMLOPERATIONS_H + +#include +#include +#include + +#include +#include +#include + +#include +#include + + +/** + Compares a Akonadi collection sub-tree with reference data supplied in an XML file. +*/ +class XmlOperations : public QObject +{ + Q_OBJECT + Q_ENUMS( CollectionField ItemField ) + + public: + explicit XmlOperations( QObject *parent = 0 ); + ~XmlOperations(); + + enum CollectionField { + None = 0, + RemoteId = 1, + Name = 2, + ContentMimeType = 4, + Attributes = 8 + }; + + enum ItemField { + ItemNone = 0, + ItemRemoteId = 1, + ItemMimeType = 2, + ItemFlags = 4, + ItemPayload = 8 + }; + + Q_DECLARE_FLAGS( CollectionFields, CollectionField ) + Q_DECLARE_FLAGS( ItemFields, ItemField ) + + void setCollectionKey( CollectionField field ); + void ignoreCollectionField( CollectionField field ); + void setItemKey( ItemField field ); + void ignoreItemField( ItemField field ); + + public slots: + void setRootCollections( const QString &resourceId ); + void setRootCollections( const Akonadi::Collection::List &roots ); + void setXmlFile( const QString &fileName ); + + Akonadi::Item getItemByRemoteId(const QString& rid); + Akonadi::Collection getCollectionByRemoteId(const QString& rid); + + void setCollectionKey( const QString &fieldName ); + void ignoreCollectionField( const QString &fieldName ); + void setItemKey( const QString &fieldName ); + void ignoreItemField( const QString &fieldName ); + + void setNormalizeRemoteIds( bool enable ); + + bool compare(); + void assertEqual(); + + QString lastError() const; + + bool compareCollections( const Akonadi::Collection::List &cols, const Akonadi::Collection::List &refCols ); + bool compareCollection( const Akonadi::Collection &col, const Akonadi::Collection &refCol ); + bool compareItems( const Akonadi::Item::List &items, const Akonadi::Item::List &refItems ); + bool compareItem( const Akonadi::Item &item, const Akonadi::Item &refItem ); + bool compareAttributes( const Akonadi::Entity &entity, const Akonadi::Entity &refEntity ); + bool hasItem(const Akonadi::Item& _item, const Akonadi::Collection& _col); + bool hasItem(const Akonadi::Item& _item, const QString& rid); + + private: + template + bool compareValue( const Akonadi::Collection &col, const Akonadi::Collection &refCol, + T (P::*property)() const, CollectionField propertyType ); + template + bool compareValue( const Akonadi::Item& item, const Akonadi::Item& refItem, + T (P::*property)() const, ItemField propertyType ); + template bool compareValue( const T& value, const T& refValue ); + + template + void sortEntityList( QList &list, T ( P::*property)() const ) const; + + + Akonadi::Collection normalizeCollection( const Akonadi::Collection &in ) const; + Akonadi::Item normalizeItem( const Akonadi::Item &in ) const; + + private: + Akonadi::Collection::List mRoots; + Akonadi::XmlDocument mDocument; + QString mFileName; + QString mErrorMsg; + CollectionFields mCollectionFields; + CollectionField mCollectionKey; + ItemFields mItemFields; + ItemField mItemKey; + bool mNormalizeRemoteIds; +}; + + +template +bool XmlOperations::compareValue( const Akonadi::Collection& col, const Akonadi::Collection& refCol, + T (P::*property)() const, + CollectionField propertyType ) +{ + if ( mCollectionFields & propertyType ) { + const bool result = compareValue( ((col).*(property))(), ((refCol).*(property))() ); + if ( !result ) { + const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) ); + mErrorMsg.prepend( QString::fromLatin1( "Collection with remote id '%1' differs in property '%2':\n" ) + .arg( col.remoteId() ).arg( me.valueToKey( propertyType ) ) ); + } + return result; + } + return true; +} + +template +bool XmlOperations::compareValue( const Akonadi::Item& item, const Akonadi::Item& refItem, + T (P::*property)() const, + ItemField propertyType ) +{ + if ( mItemFields & propertyType ) { + const bool result = compareValue( ((item).*(property))(), ((refItem).*(property))() ); + if ( !result ) { + const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "ItemField" ) ); + mErrorMsg.prepend( QString::fromLatin1( "Item with remote id '%1' differs in property '%2':\n" ) + .arg( item.remoteId() ).arg( me.valueToKey( propertyType ) ) ); + } + return result; + } + return true; +} + +template +bool XmlOperations::compareValue(const T& value, const T& refValue ) +{ + if ( value == refValue ) + return true; + QTextStream ts( &mErrorMsg ); + ts << " Actual: " << value << endl << " Expected: " << refValue; + return false; +} + +template +void XmlOperations::sortEntityList( QList &list, T ( P::*property)() const ) const +{ + std::sort( list.begin(), list.end(), boost::bind( property, _1 ) < boost::bind( property, _2 ) ); +} + + +#endif diff --git a/kdepim-runtime/tray/CMakeLists.txt b/kdepim-runtime/tray/CMakeLists.txt new file mode 100644 index 00000000..15299b49 --- /dev/null +++ b/kdepim-runtime/tray/CMakeLists.txt @@ -0,0 +1,22 @@ +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} + ${QT_INCLUDE_DIR} ${QT_QTDBUS_INCLUDE_DIR} ) + +add_subdirectory(icons) + +set(tray_sources main.cpp dock.cpp backup.cpp backupassistant.cpp restore.cpp restoreassistant.cpp global.cpp) + +qt4_generate_dbus_interface( ${CMAKE_CURRENT_SOURCE_DIR}/dock.h + org.freedesktop.akonaditray.xml ) +qt4_add_dbus_adaptor( tray_sources org.freedesktop.akonaditray.xml dock.h Dock ) + +kde4_add_app_icon(tray_sources "${CMAKE_CURRENT_SOURCE_DIR}/icons/hi*-app-akonaditray.png") + +kde4_add_executable(akonaditray ${tray_sources}) + +target_link_libraries(akonaditray ${KDE4_KIO_LIBS} ${KDE4_KDEUI_LIBS} ${AKONADI_COMMON_LIBRARIES} ${KDEPIMLIBS_AKONADI_LIBS}) + +install(TARGETS akonaditray ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(PROGRAMS akonaditray.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) diff --git a/kdepim-runtime/tray/Messages.sh b/kdepim-runtime/tray/Messages.sh new file mode 100644 index 00000000..744d3744 --- /dev/null +++ b/kdepim-runtime/tray/Messages.sh @@ -0,0 +1,2 @@ +#!/bin/sh +$XGETTEXT *.cpp -o $podir/akonaditray.pot diff --git a/kdepim-runtime/tray/akonaditray.desktop b/kdepim-runtime/tray/akonaditray.desktop new file mode 100644 index 00000000..aaa5a98c --- /dev/null +++ b/kdepim-runtime/tray/akonaditray.desktop @@ -0,0 +1,101 @@ +[Desktop Entry] +Type=Application +Categories=Qt;KDE;Utility; +Exec=akonaditray +Icon=akonaditray +Name=Akonaditray +Name[ar]=Akonaditray +Name[bs]=Akonaditray +Name[ca]=Akonaditray +Name[ca@valencia]=Akonaditray +Name[cs]=Akonaditray +Name[da]=Akonaditray +Name[de]=Akonadi-Miniprogramm +Name[el]=Akonaditray +Name[en_GB]=Akonaditray +Name[es]=Akonaditray +Name[et]=Akonaditray +Name[fi]=Akonaditray +Name[fr]=Miniature Akonadi +Name[ga]=Akonaditray +Name[gl]=Akonaditray +Name[hu]=Akonaditray +Name[ia]=Akonaditray +Name[it]=Akonaditray +Name[ja]=Akonaditray +Name[kk]=Akonaditray +Name[km]=Akonaditray +Name[ko]=Akonaditray +Name[lt]=Akondi dÄ—klas +Name[lv]=Akonaditray +Name[nb]=Akonaditray +Name[nds]=Akonaditray +Name[nl]=Akonadi-vak +Name[nn]=Akonaditray +Name[pa]=ਅਕੌਂਡੀ-ਟਰੇ +Name[pl]=Tacka Akonadi +Name[pt]=Akonaditray +Name[pt_BR]=Akonaditray +Name[ro]=Akonaditray +Name[ru]=Akonaditray +Name[sk]=Akonaditray +Name[sl]=Akonadi v sistemski vrstici +Name[sq]=Akonaditray +Name[sr]=Ðконадијева каÑета +Name[sr@ijekavian]=Ðконадијева каÑета +Name[sr@ijekavianlatin]=Akonadijeva kaseta +Name[sr@latin]=Akonadijeva kaseta +Name[sv]=Akonadi-bricka +Name[tr]=Akonaditray +Name[ug]=Akonaditray +Name[uk]=Akonaditray +Name[x-test]=xxAkonaditrayxx +Name[zh_CN]=Akonadi 托盘 +Name[zh_TW]=Akonaditray +GenericName=Akonadi Tray Utility +GenericName[ar]=أداة اكوندا لصينية النظام +GenericName[bs]=Alat sistemskog tray-a Akonadi +GenericName[ca]=Utilitat de la safata per a l'Akonadi +GenericName[ca@valencia]=Utilitat de la safata per a l'Akonadi +GenericName[da]=Akonadi-værktøj til statusomrÃ¥det +GenericName[de]=Akonadi für die Kontrollleiste +GenericName[el]=ΕÏγαλείο πλαισίου συστήματος του Akonadi +GenericName[en_GB]=Akonadi Tray Utility +GenericName[es]=Utilidad de bandeja de Akonadi +GenericName[et]=Akonadi süsteemse salve tööriist +GenericName[fi]=Akonadin ilmoitusaluetyökalu +GenericName[fr]=Utilitaire miniature Akonadi +GenericName[ga]=Uirlis Tráidire Akonadi +GenericName[gl]=Utilidade da bandexa do Akonadi +GenericName[hu]=Akonadi-tálca +GenericName[ia]=Utensile de Akondaitray +GenericName[it]=Utilità del vassoio di sistema di Akonadi +GenericName[ja]=Akonadi システムトレイユーティリティ +GenericName[kk]=Akonadi ÑөреÑÑ– +GenericName[km]=ឧបករណáŸâ€‹áž”្រើប្រាស់​ážáž¶ážŸâ€‹áž”្រពáŸáž“្ធ​របស់ Akonadi +GenericName[ko]=Akonadi íŠ¸ë ˆì´ ìœ í‹¸ë¦¬í‹° +GenericName[lt]=Akonadi dÄ—klo priemonÄ— +GenericName[lv]=Anokadi sistÄ“mas ikonas utilÄ«ta +GenericName[nb]=Akonadi kurvverktøy +GenericName[nds]=Akonadi-Systeemafsnittprogramm +GenericName[nl]=Akonadi-vak hulpmiddel +GenericName[nn]=Akonadi trauverktøy +GenericName[pa]=ਅਕੌਂਡੀ ਟਰੇ ਸਹੂਲਤ +GenericName[pl]=NarzÄ™dzie Akonadi na tacce systemowej +GenericName[pt]=Utilitário da Bandeja do Akonadi +GenericName[pt_BR]=Utilitário de notificação do Akonadi +GenericName[ro]=Utilitar Akonadi pentru tavă +GenericName[ru]=Поддержка ÑиÑтемного лотка Ð´Ð»Ñ Akonadi +GenericName[sk]=Tray utilita Akonadi +GenericName[sl]=Akonadijevo orodje v sistemski vrstici +GenericName[sr]=Ðконадијева каÑетна алатка +GenericName[sr@ijekavian]=Ðконадијева каÑетна алатка +GenericName[sr@ijekavianlatin]=Akonadijeva kasetna alatka +GenericName[sr@latin]=Akonadijeva kasetna alatka +GenericName[sv]=Akonadi-brickverktyg +GenericName[tr]=Akonadi Sistem Çekmecesi Aracı +GenericName[ug]=Akonadi قونداق قورالي +GenericName[uk]=Утиліта лотка Akonadi +GenericName[x-test]=xxAkonadi Tray Utilityxx +GenericName[zh_CN]=Akonadi 托盘工具 +GenericName[zh_TW]=Akonadi 系統匣工具 diff --git a/kdepim-runtime/tray/backup.cpp b/kdepim-runtime/tray/backup.cpp new file mode 100644 index 00000000..4a28b513 --- /dev/null +++ b/kdepim-runtime/tray/backup.cpp @@ -0,0 +1,163 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "backup.h" +#include "global.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + + +using namespace Akonadi; + +/** + * Use this class to create a backup. possible() will tell you if all + * apps needed for the backup are available. Don't proceed without them. + * After that call create() to get it running. Please make sure the parameter + * has the tar.bz2 extension. + */ +Backup::Backup( QWidget *parent ) : QWidget( parent ) +{ +} + +bool Backup::possible() +{ + Tray::Global global; + QString dbDumpAppName; + if( global.dbdriver() == QLatin1String("QPSQL") ) + dbDumpAppName = QLatin1String("pg_dump"); + else if( global.dbdriver() == QLatin1String("QMYSQL") ) + dbDumpAppName = QLatin1String("mysqldump"); + else { + kError() << "Could not find an application to dump the database."; + } + + m_dbDumpApp = KStandardDirs::findExe( dbDumpAppName ); + const QString bzip2 = KStandardDirs::findExe( QLatin1String("bzip2") ); + const QString tar = KStandardDirs::findExe( QLatin1String("tar") ); + kDebug() << "m_dbDumpApp:" << m_dbDumpApp << "bzip2:" << bzip2 << "tar:" << tar; + return !m_dbDumpApp.isEmpty() && !bzip2.isEmpty() && !tar.isEmpty(); +} + +void Backup::create( const KUrl& filename ) +{ + if ( filename.isEmpty() ) { + emit completed( false ); + return; + } + + const QString sep( QDir::separator() ); + /* first create the temp folder. */ + KTempDir *tempDir = new KTempDir( KStandardDirs::locateLocal( "tmp", QLatin1String("akonadi") ) ); + tempDir->setAutoRemove( false ); + KIO::NetAccess::mkdir( QString(tempDir->name() + QLatin1String("kdeconfig")), this ); + KIO::NetAccess::mkdir( QString(tempDir->name() + QLatin1String("akonadiconfig")), this ); + KIO::NetAccess::mkdir( QString(tempDir->name() + QLatin1String("db")), this ); + + QStringList filesToBackup; + + /* Copy over the KDE config files. */ + AgentManager *manager = AgentManager::self(); + const AgentInstance::List list = manager->instances(); + foreach( const AgentInstance &agent, list ) { + const QString agentFileName = agent.identifier() + QLatin1String("rc"); + const QString configFileName = KStandardDirs::locateLocal( "config", agentFileName ); + bool exists = KIO::NetAccess::exists( configFileName, KIO::NetAccess::DestinationSide, this ); + if ( exists ) { + KIO::NetAccess::file_copy( configFileName, + QString(tempDir->name() + QLatin1String("kdeconfig") + sep + agentFileName), this ); + filesToBackup << QLatin1String("kdeconfig") + sep + agentFileName; + } + } + + /* Copy over the Akonadi config files */ + const QString config = XdgBaseDirs::findResourceDir( "config", QLatin1String("akonadi") ); + QDir dir( config ); + const QStringList configlist = dir.entryList( QDir::Files ); + foreach( const QString& item, configlist ) { + KIO::NetAccess::file_copy( QString(config + sep + item), + QString(tempDir->name() + QLatin1String("akonadiconfig") + sep + item), this ); + filesToBackup << QLatin1String("akonadiconfig/") + item; + } + + /* Dump the database */ + Tray::Global global; + KProcess *proc = new KProcess( this ); + QStringList params; + + if( global.dbdriver() == QLatin1String("QMYSQL") ) { + params << QLatin1String("--single-transaction") + << QLatin1String("--flush-logs") + << QLatin1String("--triggers") + << QLatin1String("--result-file=") + tempDir->name() + QLatin1String("db") + sep + QLatin1String("database.sql") + << global.dboptions() + << global.dbname(); + } + else if ( global.dbdriver() == QLatin1String("QPSQL") ) { + params << QLatin1String("--format=custom") + << QLatin1String("--blobs") + << QLatin1String("--file=") + tempDir->name() + QLatin1String("db") + sep + QLatin1String("database.sql") + << global.dboptions() + << global.dbname(); + } + + kDebug() << "Executing: " << m_dbDumpApp << params; + proc->setProgram( m_dbDumpApp, params ); + int result = proc->execute(); + delete proc; + if ( result != 0 ) { + kWarning() << "Executed: " << m_dbDumpApp << params << "Result: " << result; + tempDir->unlink(); + delete tempDir; + emit completed( false ); + return; + } + filesToBackup << QLatin1String("db") + sep + QLatin1String("database.sql"); + + /* Make a nice tar file. */ + proc = new KProcess( this ); + params.clear(); + params << QLatin1String("-C") << tempDir->name(); + params << QLatin1String("-cjf"); + params << filename.toLocalFile() << filesToBackup; + proc->setWorkingDirectory( tempDir->name() ); + proc->setProgram( KStandardDirs::findExe( QLatin1String("tar") ), params ); + result = proc->execute(); + delete proc; + if ( result != 0 ) { + kWarning() << "Executed: " << KStandardDirs::findExe( QLatin1String("tar") ) << params << QLatin1String("Result: ") << result; + tempDir->unlink(); + delete tempDir; + emit completed( false ); + return; + } + + tempDir->unlink(); + delete tempDir; + emit completed( true ); +} + diff --git a/kdepim-runtime/tray/backup.h b/kdepim-runtime/tray/backup.h new file mode 100644 index 00000000..e730e5c5 --- /dev/null +++ b/kdepim-runtime/tray/backup.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +#ifndef BACKUP_H +#define BACKUP_H + +#include + +class KUrl; + +class Backup : public QWidget +{ + Q_OBJECT +public: + /** + * Constructor + */ + Backup( QWidget* ); + + /** + * Checks if all the needed applications are available. + */ + bool possible(); + + /** + * Creates a backup and emits completed() when done. + * The filename should be the filename, with tar.bz2 extension. + */ + void create( const KUrl &filename ); + +private: + QString m_dbDumpApp; + +Q_SIGNALS: + void completed( bool ); +}; + +#endif // DOCK_H + diff --git a/kdepim-runtime/tray/backupassistant.cpp b/kdepim-runtime/tray/backupassistant.cpp new file mode 100644 index 00000000..cee50521 --- /dev/null +++ b/kdepim-runtime/tray/backupassistant.cpp @@ -0,0 +1,115 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "backupassistant.h" +#include "backup.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +BackupAssistant::BackupAssistant( QWidget *parent ) : KAssistantDialog( parent ), m_selectFileButton( 0 ) +{ + m_backup = new Backup( this ); + connect( m_backup, SIGNAL(completed(bool)), SLOT(slotBackupComplete(bool)) ); + bool possible = m_backup->possible(); + + KVBox *box1 = new KVBox( this ); + QLabel *label1 = new QLabel( box1 ); + label1->setWordWrap( true ); + if ( !possible ) { + label1->setText( QLatin1Char('\n') + i18n( "The backup cannot be made. Either the mysqldump application " + "is not installed, or the bzip2 application is not found. " + "Please install those and make sure they can be found in " + "the current path. Restart this Assistant when this is fixed." ) ); + } else { + label1->setText( QLatin1Char('\n') + i18n( "Please select the file where to store " + "the backup, give it the extension .tar.bz2" ) + QLatin1String("\n\n") ); + + m_selectFileButton = new QPushButton( i18n( "&Click Here to Select the Backup Location..." ), box1 ); + connect( m_selectFileButton, SIGNAL(clicked(bool)), SLOT(slotSelectFile()) ); + + QLabel *label2 = new QLabel( QLatin1String("\n\n") + i18n( "Press 'Next' to start the Backup" ), box1 ); + label2->setWordWrap( true ); + label2->setAlignment( Qt::AlignRight ); + } + m_page1 = new KPageWidgetItem( box1, i18n( "Welcome to the Backup Assistant" ) ); + setValid( m_page1, false ); + + m_backupProgressLabel = new QLabel( this ); + m_backupProgressLabel->setWordWrap( true ); + m_page2 = new KPageWidgetItem( m_backupProgressLabel, i18n( "Making the backup" ) ); + setValid( m_page2, false ); + + addPage( m_page1 ); + addPage( m_page2 ); + showButton( KDialog::Help, false ); + + connect( this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), + SLOT(slotPageChanged(KPageWidgetItem*,KPageWidgetItem*)) ); +} + +void BackupAssistant::slotSelectFile() +{ + QString file = QLatin1String( "akonadibackup-") + + QDateTime::currentDateTime().toString( QLatin1String("yyyyMMdd") ) + QLatin1String(".tar.bz2" ); + + // Build one special, as we want the keyword /and/ a proposed filename + KFileDialog dlg( KUrl( QLatin1String("kfiledialog://BackupDir") ), QString(), this ); + dlg.setSelection( file ); + dlg.setOperationMode( KFileDialog::Saving ); + dlg.setMode( KFile::File ); + dlg.setWindowTitle( i18n( "Save As" ) ); + dlg.exec(); + + m_filename = dlg.selectedFile(); + if ( !m_filename.isEmpty() ) { + m_selectFileButton->setText( m_filename ); + setValid( m_page1, true ); + } +} + +void BackupAssistant::slotPageChanged( KPageWidgetItem *current, KPageWidgetItem* ) +{ + if ( current != m_page2 ) + return; + + setValid( m_page2, false ); + m_backupProgressLabel->setText( i18n( "Please be patient, the backup is being created..." ) ); + m_backup->create( m_filename ); +} + +void BackupAssistant::slotBackupComplete( bool ok ) +{ + if ( ok ) { + m_backupProgressLabel->setText( i18n( "The backup has been made. Please verify manually " + "if the backup is complete. Also note that KWallet " + "stored passwords are not in the backup, you might " + "want to verify you have a backup of those elsewhere." ) ); + setValid( m_page2, true ); + } else + m_backupProgressLabel->setText( i18n( "The backup process ended unexpectedly. Please " + "report a bug, so we can find out what the cause is." ) ); +} + diff --git a/kdepim-runtime/tray/backupassistant.h b/kdepim-runtime/tray/backupassistant.h new file mode 100644 index 00000000..f7ce1d80 --- /dev/null +++ b/kdepim-runtime/tray/backupassistant.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef BACKUPASSISTANT_H +#define BACKUPASSISTANT_H + +#include +class Backup; +class QLabel; +class QPushButton; + +/** + * Use this class to create a backup assistant. + */ +class BackupAssistant : public KAssistantDialog +{ + Q_OBJECT +public: + /** + * Constructor + */ + BackupAssistant( QWidget* ); + +private Q_SLOTS: + void slotSelectFile( ); + void slotPageChanged( KPageWidgetItem*, KPageWidgetItem* ); + void slotBackupComplete( bool ok ); + +private: + Backup *m_backup; + KPageWidgetItem *m_page1; + KPageWidgetItem *m_page2; + QLabel *m_backupProgressLabel; + QPushButton *m_selectFileButton; + QString m_filename; +}; + +#endif + diff --git a/kdepim-runtime/tray/dock.cpp b/kdepim-runtime/tray/dock.cpp new file mode 100644 index 00000000..74818537 --- /dev/null +++ b/kdepim-runtime/tray/dock.cpp @@ -0,0 +1,211 @@ +/* This file is part of the KDE project + Copyright (C) 2008-2009 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "dock.h" +#include "backupassistant.h" +#include "restoreassistant.h" +#include "akonaditrayadaptor.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace Akonadi; + +Tray::Tray() : QWidget() +{ + hide(); + new Dock( this ); +} + +void Tray::setVisible( bool ) +{ + // We don't want this Widget to get visible because of a click on the tray icon. + QWidget::setVisible( false ); +} + +Dock::Dock( QWidget *parent ) + : KStatusNotifierItem( 0 ), m_explicitStart( false ) +{ + m_parentWidget = parent; + + setIconByName(QLatin1String("akonaditray")); + setCategory(SystemServices); + setStatus(Passive); + KMenu *menu = new KMenu(); + m_title = menu->addTitle( i18n( "Akonadi" ) ); + + new AkonaditrayAdaptor( this ); + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.registerObject( QLatin1String("/Actions"), this ); + + m_stopAction = menu->addAction( i18n( "&Stop Akonadi" ), this, SLOT(slotStopAkonadi()) ); + m_startAction = menu->addAction( i18n( "S&tart Akonadi" ), this, SLOT(slotStartAkonadi()) ); + m_backupAction = menu->addAction( i18n( "Make &Backup..." ), this, SLOT(slotStartBackup()) ); + m_restoreAction = menu->addAction( i18n( "&Restore Backup..." ), this, SLOT(slotStartRestore()) ); + menu->addAction( KIcon( QLatin1String("configure") ), i18n( "&Configure..." ), this, SLOT(slotConfigure()) ); + + setContextMenu( menu ); + connect( menu, SIGNAL(aboutToShow()), SLOT(slotActivated()) ); + + connect( ServerManager::self(), SIGNAL(started()), SLOT(slotServerStarted()) ); + connect( ServerManager::self(), SIGNAL(stopped()), SLOT(slotServerStopped()) ); + + AgentManager *manager = AgentManager::self(); + connect( manager, + SIGNAL(instanceWarning(Akonadi::AgentInstance,QString)), + SLOT(slotInstanceWarning(Akonadi::AgentInstance,QString)) ); + connect( manager, + SIGNAL(instanceError(Akonadi::AgentInstance,QString)), + SLOT(slotInstanceError(Akonadi::AgentInstance,QString)) ); + + updateMenu( ServerManager::isRunning() ); +} + +Dock::~Dock() +{ +} + +void Dock::slotServerStarted() +{ + updateMenu( true ); + if ( m_explicitStart ) { + showMessage( i18n( "Akonadi available" ), + i18n( "The Akonadi server has been started and can be used now." ), QLatin1String("akonadi") ); + m_explicitStart = false; + } +} + +void Dock::slotServerStopped() +{ + updateMenu( false ); + showMessage( i18n( "Akonadi not available" ), + i18n( "The Akonadi server has been stopped, Akonadi related applications can no longer be used." ), QLatin1String("akonadi") ); +} + +void Dock::slotStopAkonadi() +{ + Akonadi::Control::stop( m_parentWidget ); +} + +void Dock::slotStartAkonadi() +{ + m_explicitStart = true; + Akonadi::Control::start( m_parentWidget ); +} + +void Dock::slotActivated() +{ + updateMenu( ServerManager::isRunning() ); +} + +void Dock::slotStartBackup() +{ + bool registered = ServerManager::isRunning(); + Q_ASSERT( registered ); + Q_UNUSED( registered ); + + QPointer backup = new BackupAssistant( m_parentWidget ); + backup->exec(); + delete backup; +} + +void Dock::slotStartRestore() +{ + bool registered = ServerManager::isRunning(); + Q_ASSERT( registered ); + Q_UNUSED( registered ); + + QPointer restore = new RestoreAssistant( m_parentWidget ); + restore->exec(); + delete restore; +} + +void Dock::updateMenu( bool registered ) +{ + if ( registered ) { + setToolTip( QLatin1String("akonadi"), i18n( "Akonadi available" ), + i18n( "The Akonadi server has been started and can be used now." ) ); + } else { + setToolTip( QLatin1String("akonadi"), i18n( "Akonadi not available" ), + i18n( "The Akonadi server has been stopped, Akonadi related applications can no longer be used." ) ); + } + + /* kdelibs... */ + QToolButton *button = static_cast(( static_cast( m_title ) )->defaultWidget() ); + QAction* action = button->defaultAction(); + action->setText( registered ? i18n( "Akonadi is running" ) : i18n( "Akonadi is not running" ) ); + button->setDefaultAction( action ); + + m_stopAction->setVisible( registered ); + m_backupAction->setEnabled( registered ); + m_restoreAction->setEnabled( registered ); + m_startAction->setVisible( !registered ); +} + +void Dock::slotInstanceWarning( const Akonadi::AgentInstance& agent, const QString& message ) +{ + QString msg = message; + if ( !agent.name().isEmpty() ) + msg = i18nc( ": ", "%1: %2", agent.name(), msg ); + infoMessage( msg, agent.name() ); +} + +void Dock::infoMessage( const QString &message, const QString &title ) +{ + KNotification::event( KNotification::Notification, title.isEmpty() ? i18n( "Akonadi message" ) : title, + message, QPixmap(), m_parentWidget ); +} + +void Dock::slotInstanceError( const Akonadi::AgentInstance& agent, const QString& message ) +{ + QString msg = message; + if ( !agent.name().isEmpty() ) + msg = i18nc( ": ", "%1: %2", agent.name(), msg ); + errorMessage( msg, agent.name() ); +} + +void Dock::errorMessage( const QString &message, const QString &title ) +{ + KNotification::event( KNotification::Error, title.isEmpty() ? i18n( "Akonadi error" ) : title, + message, DesktopIcon( QLatin1String("dialog-warning") ), m_parentWidget ); +} + +qlonglong Dock::getWinId() +{ + return ( qlonglong )m_parentWidget ->winId(); +} + +void Dock::slotConfigure() +{ + QProcess::startDetached( KStandardDirs::findExe( QLatin1String( "kcmshell4" ) ), QStringList() << QLatin1String("kcm_akonadi") ); +} + diff --git a/kdepim-runtime/tray/dock.h b/kdepim-runtime/tray/dock.h new file mode 100644 index 00000000..531e5c11 --- /dev/null +++ b/kdepim-runtime/tray/dock.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +#ifndef DOCK_H +#define DOCK_H + +#include + +#include + +#include + +class QLabel; +class QAction; + +class Tray : public QWidget +{ +public: + Tray(); + +protected: + void setVisible( bool ); +}; + +class Dock : public KStatusNotifierItem +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.akonaditray" ) + +public: + /** + * Contructor + * @param parent Parent Widget + */ + Dock( QWidget *parent ); + + /** + * Destructor + */ + ~Dock(); + +public Q_SLOTS: + void infoMessage( const QString&, const QString& = QString() ); + void errorMessage( const QString&, const QString& = QString() ); + qlonglong getWinId(); + +private Q_SLOTS: + void slotInstanceWarning( const Akonadi::AgentInstance&, const QString& ); + void slotInstanceError( const Akonadi::AgentInstance&, const QString& ); + void slotServerStarted(); + void slotServerStopped(); + void slotActivated(); + void slotStopAkonadi(); + void slotStartAkonadi(); + void slotStartBackup(); + void slotStartRestore(); + void slotConfigure(); + +private: + void updateMenu( bool ); + QWidget *m_parentWidget; + QAction *m_title; + QAction *m_stopAction; + QAction *m_startAction; + QAction *m_backupAction; + QAction *m_restoreAction; + bool m_explicitStart; +}; + +#endif // DOCK_H + diff --git a/kdepim-runtime/tray/global.cpp b/kdepim-runtime/tray/global.cpp new file mode 100644 index 00000000..0561e902 --- /dev/null +++ b/kdepim-runtime/tray/global.cpp @@ -0,0 +1,106 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "global.h" + +#include +#include + +#include + +using namespace Akonadi; + +namespace Tray +{ + +void Global::init() +{ + const QString serverConfigFile = XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite ); + QSettings settings( serverConfigFile, QSettings::IniFormat ); + + m_dbdriver = settings.value( QLatin1String("General/Driver"), QLatin1String("QMYSQL") ).toString(); + settings.beginGroup( m_dbdriver ); + + if( m_dbdriver == QLatin1String("QPSQL") ) { + m_dbname = settings.value( QLatin1String("Name"), QLatin1String("akonadi") ).toString(); + m_dboptions.append( QLatin1String("--host=") + settings.value( QLatin1String("Host"), QString() ).toString() ); + // If the server is started by the user, we don't need to know the username/password. + bool startServer = settings.value( QLatin1String("StartServer"), QLatin1String("true") ).toBool(); + if( !startServer ) { + // TODO: postgres will always ask for the user password ! implement .pgpass + m_dboptions.append( QLatin1String("--username=") + settings.value( QLatin1String("User"), QString() ).toString() ); + } + settings.endGroup(); + m_parsed = true; + } + + else if( m_dbdriver == QLatin1String("QMYSQL") ) { + m_dbname = settings.value( QLatin1String("Name"), QLatin1String("akonadi") ).toString(); + // If the server is started by the user, we don't need to know the username/password. + bool startServer = settings.value( QLatin1String("StartServer"), QString() ).toBool(); + if( !startServer ) { + m_dboptions.append( QLatin1String("--host=") + settings.value( QLatin1String("Host"), QString() ).toString() ); + m_dboptions.append( QLatin1String("--user=") + settings.value( QLatin1String("User"), QString() ).toString() ); + m_dboptions.append( QLatin1String("--password=") + settings.value( QLatin1String("Password"), QString() ).toString() ); + } + else { + const QString options = settings.value( QLatin1String("Options"), QString() ).toString(); + const QStringList list = options.split( QLatin1Char('=') ); + if( list.count() == 2 ) + m_dboptions.append( QLatin1String("--socket=") + list.at( 1 ) ); + else { + m_parsed = false; + return; + } + } + + settings.endGroup(); + m_parsed = true; + } + + else { + m_parsed = false; + } +} + +const QString Global::dbdriver() +{ + if ( !m_parsed ) + init(); + + return m_dbdriver; + +} + +const QStringList Global::dboptions() +{ + if ( !m_parsed ) + init(); + + return m_dboptions; +} + +const QString Global::dbname() +{ + if ( !m_parsed ) + init(); + + return m_dbname; +} + +} diff --git a/kdepim-runtime/tray/global.h b/kdepim-runtime/tray/global.h new file mode 100644 index 00000000..45fb9b3f --- /dev/null +++ b/kdepim-runtime/tray/global.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + + Copyright (C) 2006-2007 Omat Holding B.V. + Copyright (C) 2007 Frode M. Døving + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +#ifndef GLOBAL_H +#define GLOBAL_H + +#include + +namespace Tray +{ + +class Global +{ +public: + Global() : m_parsed( false ) {}; + + /** + * Returns a string usable to connect to the akonadiserver. + */ + const QStringList dboptions(); + + /** + * Returns the database to connect to. + */ + const QString dbname(); + + /** + * Returns the database driver to use. + */ + const QString dbdriver(); + +private: + void init(); + + bool m_parsed; + QString m_dbdriver; + QStringList m_dboptions; + QString m_dbname; +}; + +} + +#endif diff --git a/kdepim-runtime/tray/icons/CMakeLists.txt b/kdepim-runtime/tray/icons/CMakeLists.txt new file mode 100644 index 00000000..02e3c9ec --- /dev/null +++ b/kdepim-runtime/tray/icons/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons(${ICON_INSTALL_DIR}) diff --git a/kdepim-runtime/tray/icons/hi128-app-akonaditray.png b/kdepim-runtime/tray/icons/hi128-app-akonaditray.png new file mode 100644 index 0000000000000000000000000000000000000000..662b9e1cb06e077b37aa01959a114a60514f54f2 GIT binary patch literal 22934 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 yFn(ANUd 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 RgO~sS002ovPDHLkV1g}>^h*E$ literal 0 HcmV?d00001 diff --git a/kdepim-runtime/tray/icons/hi22-app-akonaditray.png b/kdepim-runtime/tray/icons/hi22-app-akonaditray.png new file mode 100644 index 0000000000000000000000000000000000000000..f8a06a33821ab07bff43ecabe6a8a95c51edaa92 GIT binary patch literal 1487 zcmV;=1u*)FP)Px#32;bRa{vGvuK)lWuK`{fksJU300(qQO+^RT1rZ4xIMY~FBme*a24YJ`L;(K) z{{a7>y{D4^00mD;L_t(|+O?2RY#h}c$G>mh%)I%(YwvE19TPjTfhf>akS2lz5sFmx z!U2RtIdG`j0}7XL3KCR>s;bngRRrRK5I592h^lH;34-7NGEyNS;h(|EI<{kH*RwOT z^Y_iX*Jtcj^g_AxGx|!Sk>>m7hyND@-@%U_dIik)Zu48geGp_WPfCop)BboEy~nfc z%GVdqeShPHr!G#4TFl=bY;_JC>ix!TIrk8vp%_;f4|9YYV+>YD*k0dW%HqMZD<7Zx z$Nw}w{o=||$8z7c%HibkI|JC04xke>T%c6OnKDk4akv^|^XdqjE1NRjUOQjr;kotY z^8(+3Vvl3bE&0NsGYiG8L9TLVf7Z2AmnBxKb<7Bxgxqs}=**@bMP zvr^V?0eBO8Xiyw~{#|#b%g%JWEfi%5p;HjmYzMhFi)jSZq%c%-<{GdA6JE=L-*(Yz z2gLKO-_I;OMgGfpY9{!}q^Y$g0tZA2Bv}P6ByxfOsFJ%W8c_jMgJDoG6=FFi9M6XD zTStuUO%ERW_13QO{Le4ItnpYq${{n3Tjsl1>u-Zs0+E^?Hc*l%cTTXQM()l~0h&g> zxx1m3Shfk)oqod~t-LVz#D{9ID2^AU_Ol`?k%u`B?Qg?MGHk3zsB(@huTiSNCgrHr zds8TtH3V-Uc0wT33aJ7pWEzuf7@hT+=#;K|HVa1uLPNlU0Hp+{Z=b>&OY30u6ed~> zkP53!;FUM#aj$~XwqYi4GqekZ{?cMoUY)k~d6losx z|M?}NQG-#$0T7s?!53Esuq1(04R_9!sdecvMH(hZ;}CHaVtW`vPy%;;0gcn!l8*B$ z0>z0xe)Ixg8Jg%#-5us!%r`RG2osD4r5`lhb-f`){n6K#Q^ajMVTg$ z$U#{H!!uwy7M$4*oVf*DU#)5#;-x=-+9w2iocQf~zss)`&+z^-@{Kj1(qO9ghRGl$ zP;(AmWuUZ%?Hh0=T)4dety^c|E!>RcMvcL>^5y3qpL*J2tkkiG48!NCYv0}Mn|pvY z0(^S|`FIOiBmfXd^?XyqGEhqj*vk9|2VozXN3xz`bUnHB_-XgmGB4XYr7?x#uOAQb z$o{=7WK5{WbtvxJZ{wYQ zQk!<2c--Z6-K2{L5ivuqf3=*wJsgbgD9XA^G@@CS5w_d*mj~wBpY7Y{Z3Uhi1-_RA zt~>S|JF^%osIJ!p-{Mb8OE}8fq*U$FBBSFpGeR|IG)h@fas$AQu?T!td!Ef5%jUMt pYTIHpGp)ulT`{7Sd}whN{{nuzq^pv@C$j(m002ovPDHLkV1nRezRv&v literal 0 HcmV?d00001 diff --git a/kdepim-runtime/tray/icons/hi32-app-akonaditray.png b/kdepim-runtime/tray/icons/hi32-app-akonaditray.png new file mode 100644 index 0000000000000000000000000000000000000000..193d07a9aa0841c46c8810421993ad264266a0f5 GIT binary patch literal 2530 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

BIT#3e5DVZwGUg0 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 F_&;zi3Pu0` literal 0 HcmV?d00001 diff --git a/kdepim-runtime/tray/main.cpp b/kdepim-runtime/tray/main.cpp new file mode 100644 index 00000000..56f0557c --- /dev/null +++ b/kdepim-runtime/tray/main.cpp @@ -0,0 +1,75 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include + +#include "dock.h" + +/** + * @class AkonadiTrayApplication + * @author Tom Albers + * This class is a simple inheritance from KUniqueApplication + * the reason that it is reimplemented is that when AkonadiTray + * is launched a second time it would in the orinal implementation + * make the show(). which we do not want. This + * class misses that call. + */ +class AkonadiTrayApplication : public KUniqueApplication +{ +public: + /** + * Similar to KUniqueApplication::newInstance, only without + * the call to raise the widget when a second instance is started. + */ + int newInstance() { + return 0; + } +}; + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "akonaditray", 0, + ki18n( "AkonadiTray" ), + "0.1", + ki18n( "System tray application to control basic Akonadi functions" ), + KAboutData::License_GPL, + ki18n( "(c) 2008 Omat Holding B.V." ), + KLocalizedString() ); + aboutData.addAuthor( ki18n( "Tom Albers" ), ki18n( "Maintainer and Author" ), + "tomalbers@kde.nl", "http://www.omat.nl" ); + aboutData.setProgramIconName( QLatin1String("akonaditray") ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + + if ( !KUniqueApplication::start() ) { + fprintf( stderr, "akonaditray is already running!\n" ); + exit( 0 ); + } + + AkonadiTrayApplication a; + a.setQuitOnLastWindowClosed( false ); + + Tray tray; + + return a.exec(); +} diff --git a/kdepim-runtime/tray/org.freedesktop.akonaditray.xml b/kdepim-runtime/tray/org.freedesktop.akonaditray.xml new file mode 100644 index 00000000..d8eafbaa --- /dev/null +++ b/kdepim-runtime/tray/org.freedesktop.akonaditray.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/kdepim-runtime/tray/restore.cpp b/kdepim-runtime/tray/restore.cpp new file mode 100644 index 00000000..7a171ad3 --- /dev/null +++ b/kdepim-runtime/tray/restore.cpp @@ -0,0 +1,164 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "restore.h" +#include "global.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + + +using namespace Akonadi; + +/** + * Use this class to restore a backup. possible() will tell you if all + * apps needed for the restore are available. Don't proceed without them. + * After that call restore() to get it running. Please make sure the parameter + * has the tar.bz2 extension. + */ +Restore::Restore( QWidget *parent ) : QWidget( parent ) +{ +} + +bool Restore::possible() +{ + Tray::Global global; + QString dbRestoreAppName; + if( global.dbdriver() == QLatin1String("QPSQL") ) + dbRestoreAppName = QLatin1String("pg_restore"); + else if( global.dbdriver() == QLatin1String("QMYSQL") ) + dbRestoreAppName = QLatin1String("mysql"); + else { + kError() << "Could not find an application to restore the database."; + } + + m_dbRestoreApp = KStandardDirs::findExe( dbRestoreAppName ); + const QString bzip2 = KStandardDirs::findExe( QLatin1String("bzip2") ); + const QString tar = KStandardDirs::findExe( QLatin1String("tar") ); + kDebug() << "m_dbRestoreApp:" << m_dbRestoreApp << "bzip2:" << bzip2 << "tar:" << tar; + return !m_dbRestoreApp.isEmpty() && !bzip2.isEmpty() && !tar.isEmpty(); +} + +void Restore::restore( const KUrl& filename ) +{ + if ( filename.isEmpty() ) { + emit completed( false ); + return; + } + + const QString sep = QDir::separator(); + + /* first create the temp folder. */ + KTempDir *tempDir = new KTempDir( KStandardDirs::locateLocal( "tmp", QLatin1String("akonadi") ) ); + tempDir->setAutoRemove( false ); + kDebug() << "Temp dir: "<< tempDir->name(); + + /* Extract the nice tar file. */ + KProcess *proc = new KProcess( this ); + QStringList params; + params << QLatin1String("-C") << tempDir->name(); + params << QLatin1String("-xjf"); + params << filename.toLocalFile(); + proc->setWorkingDirectory( tempDir->name() ); + proc->setProgram( KStandardDirs::findExe( QLatin1String("tar") ), params ); + int result = proc->execute(); + delete proc; + if ( result != 0 ) { + kWarning() << "Executed:" << KStandardDirs::findExe( QLatin1String("tar") ) << params << " Result: " << result; + tempDir->unlink(); + delete tempDir; + emit completed( false ); + return; + } + + /* Copy over the KDE configuration files. */ + QDir dir( tempDir->name() + QLatin1String("kdeconfig") + sep ); + dir.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks ); + QFileInfoList list = dir.entryInfoList(); + const int numberOfElement = list.size(); + for ( int i = 0; i < numberOfElement; ++i ) { + QFileInfo fileInfo = list.at( i ); + const QString source = fileInfo.absoluteFilePath(); + const QString dest = KStandardDirs::locateLocal( "config", fileInfo.fileName() ); + + kDebug() << "Restoring: " << source << "to:" << dest; + KIO::NetAccess::file_copy( source, dest, this ); + } + + /* Copy over the Akonadi configuration files. */ + const QString akonadiconfigfolder = XdgBaseDirs::findResourceDir( "config", QLatin1String("akonadi") ); + dir.setPath( tempDir->name() + QLatin1String("akonadiconfig") + sep ); + dir.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks ); + list = dir.entryInfoList(); + const int sizeOfList = list.size(); + for ( int i = 0; i < sizeOfList; ++i ) { + QFileInfo fileInfo = list.at( i ); + const QString source = fileInfo.absoluteFilePath(); + const QString dest = akonadiconfigfolder + sep + fileInfo.fileName(); + + kDebug() << "Restoring: " << source << "to:" << dest; + KIO::NetAccess::file_copy( source, dest, this ); + } + + /* Restore the database */ + Tray::Global global; + proc = new KProcess( this ); + params.clear(); + + if( global.dbdriver() == QLatin1String("QPSQL") ) { + params << global.dboptions() + << QLatin1String("--dbname=") + global.dbname() + << QLatin1String("--format=custom") + << QLatin1String("--clean") + << QLatin1String("--no-owner") + << QLatin1String("--no-privileges") + << tempDir->name() + QLatin1String("db") + sep + QLatin1String("database.sql"); + } + else if (global.dbdriver() == QLatin1String("QMYSQL") ) { + params << global.dboptions() + << QLatin1String("--database=") + global.dbname(); + } + + kDebug() << "Executing:" << m_dbRestoreApp << params; + ; + proc->setProgram( KStandardDirs::findExe( m_dbRestoreApp ), params ); + proc->setStandardInputFile(tempDir->name() + QLatin1String("db") + sep + QLatin1String("database.sql")); + result = proc->execute(); + delete proc; + if ( result != 0 ) { + kWarning() << "Executed:" << m_dbRestoreApp << params << " Result: " << result; + tempDir->unlink(); + delete tempDir; + emit completed( false ); + return; + } + + tempDir->unlink(); + delete tempDir; + emit completed( true ); +} + diff --git a/kdepim-runtime/tray/restore.h b/kdepim-runtime/tray/restore.h new file mode 100644 index 00000000..26cb5241 --- /dev/null +++ b/kdepim-runtime/tray/restore.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +#ifndef RESTORE_H +#define RESTORE_H + +#include + +class KUrl; + +class Restore : public QWidget +{ + Q_OBJECT +public: + /** + * Constructor + */ + Restore( QWidget* ); + + /** + * Checks if all the needed applications are available. + */ + bool possible(); + + /** + * Restores a backup and emits completed() when done. + * The filename should be the filename, with tar.bz2 extension. + */ + void restore( const KUrl &filename ); + +private: + QString m_dbRestoreApp; + +Q_SIGNALS: + void completed( bool ); +}; + +#endif // DOCK_H + diff --git a/kdepim-runtime/tray/restoreassistant.cpp b/kdepim-runtime/tray/restoreassistant.cpp new file mode 100644 index 00000000..25adbf2c --- /dev/null +++ b/kdepim-runtime/tray/restoreassistant.cpp @@ -0,0 +1,104 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "restoreassistant.h" +#include "restore.h" + +#include +#include + +#include +#include +#include +#include +#include + +RestoreAssistant::RestoreAssistant( QWidget *parent ) : KAssistantDialog( parent ), m_selectFileButton(0) +{ + m_restore = new Restore( this ); + connect( m_restore, SIGNAL(completed(bool)), SLOT(slotRestoreComplete(bool)) ); + bool possible = m_restore->possible(); + + KVBox *box1 = new KVBox( this ); + QLabel *label1 = new QLabel( box1 ); + label1->setWordWrap( true ); + if ( !possible ) { + label1->setText( QLatin1Char('\n') + i18n( "The backup cannot be restored. Either the mysql application " + "is not installed, or the bzip2 application is not found. " + "Please install those and make sure they can be found in " + "the current path. Restart this Assistant when this is fixed." ) ); + } else { + label1->setText( QLatin1Char('\n') + i18n( "Please select the file to restore. Note that restoring a " + "backup will overwrite all existing data. You might want to " + "make a backup first and please consider closing all Akonadi " + "applications (but do not stop the akonadi server)." ) + QLatin1String("\n\n") ); + + m_selectFileButton = new QPushButton( i18n( "&Click Here to Select the File to Restore..." ), box1 ); + connect( m_selectFileButton, SIGNAL(clicked(bool)), SLOT(slotSelectFile()) ); + + QLabel *label2 = new QLabel( QLatin1String("\n\n") + i18n( "Press 'Next' to start the Restore" ), box1 ); + label2->setWordWrap( true ); + label2->setAlignment( Qt::AlignRight ); + } + m_page1 = new KPageWidgetItem( box1, i18n( "Welcome to the Restore Assistant" ) ); + setValid( m_page1, false ); + + m_restoreProgressLabel = new QLabel( this ); + m_restoreProgressLabel->setWordWrap( true ); + m_page2 = new KPageWidgetItem( m_restoreProgressLabel, i18n( "Restoring" ) ); + setValid( m_page2, false ); + + addPage( m_page1 ); + addPage( m_page2 ); + showButton( KDialog::Help, false ); + + connect( this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), + SLOT(slotPageChanged(KPageWidgetItem*,KPageWidgetItem*)) ); +} + +void RestoreAssistant::slotSelectFile() +{ + m_filename = KFileDialog::getOpenFileName( KUrl( QLatin1String("kfiledialog://BackupDir") ) ); + if ( !m_filename.isEmpty() ) { + m_selectFileButton->setText( m_filename ); + setValid( m_page1, true ); + } +} + +void RestoreAssistant::slotPageChanged( KPageWidgetItem *current, KPageWidgetItem* ) +{ + if ( current != m_page2 ) + return; + + setValid( m_page2, false ); + m_restoreProgressLabel->setText( i18n( "Please be patient, the backup is being restored..." ) ); + m_restore->restore( m_filename ); +} + +void RestoreAssistant::slotRestoreComplete( bool ok ) +{ + if ( ok ) { + m_restoreProgressLabel->setText( i18n( "The backup has been restored. Note that KWallet " + "stored passwords are not restored, so applications might " + "ask for those." ) ); + setValid( m_page2, true ); + } else + m_restoreProgressLabel->setText( i18n( "The restore process ended unexpectedly. Please " + "report a bug, so we can find out what the cause is." ) ); +} + diff --git a/kdepim-runtime/tray/restoreassistant.h b/kdepim-runtime/tray/restoreassistant.h new file mode 100644 index 00000000..99440f77 --- /dev/null +++ b/kdepim-runtime/tray/restoreassistant.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + + Copyright (C) 2008 Omat Holding B.V. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef RESTOREASSISTANT_H +#define RESTOREASSISTANT_H + +#include +class Restore; +class QLabel; +class QPushButton; + +/** + * Use this class to create a backup assistant. + */ +class RestoreAssistant : public KAssistantDialog +{ + Q_OBJECT +public: + /** + * Constructor + */ + RestoreAssistant( QWidget* ); + +private Q_SLOTS: + void slotSelectFile( ); + void slotPageChanged( KPageWidgetItem*, KPageWidgetItem* ); + void slotRestoreComplete( bool ok ); + +private: + Restore *m_restore; + KPageWidgetItem *m_page1; + KPageWidgetItem *m_page2; + QLabel *m_restoreProgressLabel; + QPushButton *m_selectFileButton; + QString m_filename; +}; + +#endif +